Skip to content

refactor(cli): extract OpenShell command helpers#1544

Merged
brandonpelfrey merged 6 commits intomainfrom
cv/extract-openshell-helpers
Apr 7, 2026
Merged

refactor(cli): extract OpenShell command helpers#1544
brandonpelfrey merged 6 commits intomainfrom
cv/extract-openshell-helpers

Conversation

@cv
Copy link
Copy Markdown
Contributor

@cv cv commented Apr 6, 2026

Summary

Extract the reusable OpenShell command helpers from bin/nemoclaw.js into a typed module under src/lib/openshell.ts.

Why

This is one of the small follow-through PRs from #924: shrink the monolithic dispatcher and move reusable CLI logic into typed modules that future command wrappers can share.

Changes

  • add src/lib/openshell.ts with:
    • runOpenshellCommand()
    • captureOpenshellCommand()
    • stripAnsi()
    • parseVersionFromText()
    • versionGte()
    • getInstalledOpenshellVersion()
  • add focused tests in src/lib/openshell.test.ts
  • reduce bin/nemoclaw.js to thin wrappers around the extracted helpers

Testing

  • npm run build:cli
  • npx vitest run src/lib/openshell.test.ts test/cli.test.js

Relates to #924.

Summary by CodeRabbit

  • Refactor

    • Centralized OpenShell command handling and sandbox inventory/status logic into shared utilities, yielding more consistent command execution, clearer failure/error reporting, and more reliable sandbox status and listing output.
  • Tests

    • Added comprehensive tests for OpenShell helpers and inventory/status commands, covering version parsing, command execution behaviors, and inventory display scenarios.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 19e85b0d-8f55-4c02-86e9-a592c2d73fd5

📥 Commits

Reviewing files that changed from the base of the PR and between 3f60b88 and 159406e.

📒 Files selected for processing (5)
  • bin/nemoclaw.js
  • src/lib/inventory-commands.test.ts
  • src/lib/inventory-commands.ts
  • src/lib/openshell.test.ts
  • src/lib/openshell.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/openshell.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/inventory-commands.test.ts
  • bin/nemoclaw.js
  • src/lib/openshell.ts
  • src/lib/inventory-commands.ts

📝 Walkthrough

Walkthrough

Centralizes OpenShell invocation and sandbox inventory rendering: adds src/lib/openshell.ts and src/lib/inventory-commands.ts (with tests) and updates bin/nemoclaw.js to delegate command execution, version parsing, output capture, and inventory/status presentation to those new modules.

Changes

Cohort / File(s) Summary
CLI entrypoint
bin/nemoclaw.js
Delegates OpenShell execution/capture and inventory rendering to new helpers; replaces local spawnSync wrappers and version-parsing utilities with imported runOpenshellCommand/captureOpenshellCommand and getInstalledOpenshellVersionOrNull; wires callbacks (log, exit, errorLine) into imported commands.
OpenShell utilities
src/lib/openshell.ts, src/lib/openshell.test.ts
New module providing stripAnsi, parseVersionFromText, versionGte, runOpenshellCommand, captureOpenshellCommand, and getInstalledOpenshellVersion with typed options/results; standardized spawn error/failure handling, env handling, and stdout/stderr capture behavior. Tests mock spawn behavior and validate parsing, comparisons, capture semantics, and exit/error handling.
Inventory & status commands
src/lib/inventory-commands.ts, src/lib/inventory-commands.test.ts
New module exporting listSandboxesCommand and showStatusCommand plus typed interfaces (SandboxEntry, RecoveryResult, deps). Implements recovery messaging, live inference resolution for default sandbox, formatted sandbox listing, and delegation to showServiceStatus. Tests assert exact logged output and delegation behavior.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as "bin/nemoclaw.js"
  participant Inventory as "inventory-commands"
  participant OpenShell as "openshell"
  participant ShellBin as "openshell binary"
  participant Registry as "registry / last-session"
  participant Service as "showServiceStatus"

  CLI->>Inventory: listSandboxesCommand(deps)
  Inventory->>Registry: recoverRegistryEntries() / loadLastSession()
  Inventory-->>CLI: formatted sandbox list (logs)
  CLI->>OpenShell: getInstalledOpenshellVersion(...) / run/capture commands
  OpenShell->>ShellBin: spawnSync(args)
  ShellBin-->>OpenShell: stdout / stderr / status
  OpenShell-->>CLI: parsed version / {status, output}
  CLI->>Service: showServiceStatus({ sandboxName })
  Service-->>CLI: service status output
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I nibbled at ANSI, chased down a version line,
Moved shells and sandboxes into tidy design.
Logs now hop neatly, errors called with care,
CLI and helpers — a teamwork affair. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: extracting OpenShell command helpers from bin/nemoclaw.js into reusable typed modules (src/lib/openshell.ts with supporting tests), which is the central refactoring objective.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cv/extract-openshell-helpers

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/openshell.test.ts (1)

6-13: Point this suite at the source module, not dist.

Importing ../../dist/lib/openshell makes vitest depend on a prior build:cli and can let stale emitted output mask source regressions. Importing ./openshell keeps the test focused on the code under review.

Suggested fix
 import {
   captureOpenshellCommand,
   getInstalledOpenshellVersion,
   parseVersionFromText,
   runOpenshellCommand,
   stripAnsi,
   versionGte,
-} from "../../dist/lib/openshell";
+} from "./openshell";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/openshell.test.ts` around lines 6 - 13, The test imports the compiled
module at "../../dist/lib/openshell" which forces a build and can hide source
issues; update the import to point at the source module (the local openshell
module) instead so vitest runs against current source. Locate the import
statement that brings in captureOpenshellCommand, getInstalledOpenshellVersion,
parseVersionFromText, runOpenshellCommand, stripAnsi, and versionGte and change
its path to the source file (e.g., the local ./openshell module) so the tests
reference the source implementation.
bin/nemoclaw.js (1)

91-97: Let the extracted helper own the default env merge.

These wrappers still spread process.env, even though the shared helper already does that. Removing the duplicate merge keeps the wrapper truly thin and avoids encoding the same default in two places.

Suggested fix
 function runOpenshell(args, opts = {}) {
   return runOpenshellCommand(getOpenshellBinary(), args, {
     cwd: ROOT,
-    env: { ...process.env, ...opts.env },
+    env: opts.env,
     stdio: opts.stdio,
     ignoreError: opts.ignoreError,
     errorLine: console.error,
     exit: (code) => process.exit(code),
   });
 }
 
 function captureOpenshell(args, opts = {}) {
   return captureOpenshellCommand(getOpenshellBinary(), args, {
     cwd: ROOT,
-    env: { ...process.env, ...opts.env },
+    env: opts.env,
     ignoreError: opts.ignoreError,
   });
 }
@@
 function getInstalledOpenshellVersionOrNull() {
   return getInstalledOpenshellVersion(getOpenshellBinary(), {
     cwd: ROOT,
-    env: process.env,
   });
 }

Also applies to: 102-105, 140-144

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/nemoclaw.js` around lines 91 - 97, The wrapper calls to
runOpenshellCommand in bin/nemoclaw.js currently re-merge process.env (e.g., the
options object built at the return of runOpenshellCommand(getOpenshellBinary(),
args, { cwd: ROOT, env: { ...process.env, ...opts.env }, ... })) — remove the
duplicate merge so the wrapper simply passes opts.env (or undefined) and lets
the shared helper perform the default merge; update the other similar wrappers
mentioned (the calls around lines 102-105 and 140-144 that set env) to stop
spreading process.env and just forward opts.env to
runOpenshellCommand/getOpenshellBinary invocations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/openshell.ts`:
- Around line 67-78: In runOpenshellCommand and captureOpenshellCommand, add an
explicit check for spawnSyncImpl's result.error immediately after the call
(before checking result.status), and when present log the error via
(opts.errorLine ?? console.error) including result.error.message/details and
return via (opts.exit ?? ((code) => process.exit(code))) with a non-zero code
(e.g., 1); only then proceed to the existing result.status handling (use
result.status ?? 1 for exit codes in captureOpenshellCommand) so startup
failures (binary not found / permission denied) are reported and handled
correctly.

---

Nitpick comments:
In `@bin/nemoclaw.js`:
- Around line 91-97: The wrapper calls to runOpenshellCommand in bin/nemoclaw.js
currently re-merge process.env (e.g., the options object built at the return of
runOpenshellCommand(getOpenshellBinary(), args, { cwd: ROOT, env: {
...process.env, ...opts.env }, ... })) — remove the duplicate merge so the
wrapper simply passes opts.env (or undefined) and lets the shared helper perform
the default merge; update the other similar wrappers mentioned (the calls around
lines 102-105 and 140-144 that set env) to stop spreading process.env and just
forward opts.env to runOpenshellCommand/getOpenshellBinary invocations.

In `@src/lib/openshell.test.ts`:
- Around line 6-13: The test imports the compiled module at
"../../dist/lib/openshell" which forces a build and can hide source issues;
update the import to point at the source module (the local openshell module)
instead so vitest runs against current source. Locate the import statement that
brings in captureOpenshellCommand, getInstalledOpenshellVersion,
parseVersionFromText, runOpenshellCommand, stripAnsi, and versionGte and change
its path to the source file (e.g., the local ./openshell module) so the tests
reference the source implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8852a87b-09fe-48e2-913c-fa4af221db02

📥 Commits

Reviewing files that changed from the base of the PR and between 7d05229 and 6ef660a.

📒 Files selected for processing (3)
  • bin/nemoclaw.js
  • src/lib/openshell.test.ts
  • src/lib/openshell.ts

@cv cv requested review from brandonpelfrey and prekshivyas April 6, 2026 23:55
cv added 3 commits April 6, 2026 18:20
## Summary

Extract the top-level `list` and `status` command handlers from
`bin/nemoclaw.js` into a typed module under
`src/lib/inventory-commands.ts`.

## Why

This continues the #924 follow-through by shrinking the monolithic
dispatcher and moving command logic into reusable typed modules.

## Changes

- add `src/lib/inventory-commands.ts` with:
  - `listSandboxesCommand()`
  - `showStatusCommand()`
- add focused tests in `src/lib/inventory-commands.test.ts`
- reduce `bin/nemoclaw.js` to thin wrappers that inject legacy
dependencies

## Testing

- `npm run build:cli`
- `npx vitest run src/lib/inventory-commands.test.ts test/cli.test.js`
- `npx vitest run --project cli`

## Stack

Depends on #1544 (`refactor(cli): extract OpenShell command helpers`).
Once that merges, this PR can be retargeted to `main`.

Relates to #924.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/lib/inventory-commands.ts (1)

19-22: Consider importing GatewayInference from inference-config.ts instead of duplicating.

Per context snippet 3, this interface is already defined in src/lib/inference-config.ts. Duplicating it here risks divergence if the original is modified.

Suggested change
+import type { GatewayInference } from "./inference-config";
+
 export interface SandboxEntry {
   ...
 }
 
 export interface RecoveryResult {
   ...
 }
 
-export interface GatewayInference {
-  provider: string | null;
-  model: string | null;
-}
+export type { GatewayInference };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/inventory-commands.ts` around lines 19 - 22, Remove the duplicated
GatewayInference interface declaration and import the shared type from
src/lib/inference-config.ts; update this file to replace the local declaration
with an import statement for GatewayInference and ensure all uses in this module
reference the imported type (look for the GatewayInference symbol to locate
usages), so the project uses the single source of truth in inference-config.ts.
src/lib/inventory-commands.test.ts (1)

6-6: Consider importing from source instead of dist/.

Importing from ../../dist/lib/inventory-commands requires a build step before tests can run and may cause stale results if the build is outdated. Typically, test files import directly from source (e.g., ./inventory-commands or ./inventory-commands.ts) so that changes are immediately reflected.

Suggested change
-import { listSandboxesCommand, showStatusCommand } from "../../dist/lib/inventory-commands";
+import { listSandboxesCommand, showStatusCommand } from "./inventory-commands";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/inventory-commands.test.ts` at line 6, The test imports
listSandboxesCommand and showStatusCommand from the built output (dist), causing
a build dependency and potential stale code; update the import to reference the
source module (the inventory-commands source file) so tests load the unbuilt
code directly and reflect changes immediately, and then run tests to ensure
listSandboxesCommand and showStatusCommand resolve correctly; adjust any test
tooling or tsconfig paths if necessary to allow importing the TypeScript source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/inventory-commands.ts`:
- Around line 94-98: The status output is incorrectly showing the live model for
every sandbox; change the model selection so only the default sandbox uses
live.model. In the loop over sandboxes (symbols: sandboxes, defaultSandbox,
live, sb), replace the current model assignment ((live && live.model) ||
sb.model) with logic that uses live.model only when sb.name === defaultSandbox
and live?.model is present, otherwise use sb.model; keep the def marker logic
unchanged and update the log call to use this new model variable.
- Around line 73-80: The loop currently applies live inference values to every
sandbox because it uses (live && live.model) and (live && live.provider) for all
sb entries; change it so live.model and live.provider are used only when the
current sb is the default sandbox (sb.name === defaultSandbox). Update the model
and provider assignments in the for-loop (where model and provider are computed)
to prefer sb.model/sb.provider for non-default sandboxes and only fall back to
live.model/live.provider when sb.name matches defaultSandbox.

---

Nitpick comments:
In `@src/lib/inventory-commands.test.ts`:
- Line 6: The test imports listSandboxesCommand and showStatusCommand from the
built output (dist), causing a build dependency and potential stale code; update
the import to reference the source module (the inventory-commands source file)
so tests load the unbuilt code directly and reflect changes immediately, and
then run tests to ensure listSandboxesCommand and showStatusCommand resolve
correctly; adjust any test tooling or tsconfig paths if necessary to allow
importing the TypeScript source.

In `@src/lib/inventory-commands.ts`:
- Around line 19-22: Remove the duplicated GatewayInference interface
declaration and import the shared type from src/lib/inference-config.ts; update
this file to replace the local declaration with an import statement for
GatewayInference and ensure all uses in this module reference the imported type
(look for the GatewayInference symbol to locate usages), so the project uses the
single source of truth in inference-config.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 485cba4a-17f1-4fe9-a73d-3a50310f5fe6

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef660a and 3f60b88.

📒 Files selected for processing (3)
  • bin/nemoclaw.js
  • src/lib/inventory-commands.test.ts
  • src/lib/inventory-commands.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/nemoclaw.js

@brandonpelfrey brandonpelfrey merged commit ba3f12e into main Apr 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants