Skip to content

feat(cli): add nemoclaw list --json#2147

Merged
cv merged 18 commits into
mainfrom
feat/list-json
Apr 30, 2026
Merged

feat(cli): add nemoclaw list --json#2147
cv merged 18 commits into
mainfrom
feat/list-json

Conversation

@cv

@cv cv commented Apr 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

This adds machine-readable nemoclaw list --json output while preserving the existing human-readable list view. It extracts structured inventory generation from the list renderer so future CLI work can reuse the same inventory path. This carries forward the earlier list --json proposal from #182 by @dumko2001 (Sidharth Rajmohan).

Changes

  • Add getSandboxInventory() and renderSandboxInventoryText() in src/lib/inventory-commands.ts so the sandbox inventory can be reused for both text and JSON output.
  • Update src/nemoclaw.ts so nemoclaw list accepts --json, emits pretty JSON to stdout, and rejects unsupported list flags with list-specific usage guidance.
  • Add tests in src/lib/inventory-commands.test.ts and test/cli.test.ts for empty and populated JSON output plus invalid list arguments.
  • Update docs/reference/commands.md and regenerate .agents/skills/nemoclaw-user-reference/references/commands.md for nemoclaw list --json.

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

AI Disclosure

  • AI-assisted — tool: OpenAI Codex

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • New Features

    • nemoclaw list gains a --json mode that emits stable, machine-readable inventory (schemaVersion, default sandbox, recovery metadata, sandboxes) and improved CLI help showing nemoclaw list [--json].
  • Refactor

    • Listing logic split into inventory collection vs. rendering and integrated with the CLI command framework for consistent behavior.
  • Documentation

    • Reference docs and examples updated to demonstrate --json usage.
  • Tests

    • Added tests for JSON output shapes, help text, and argument validation.

Signed-off-by: Carlos Villela <cvillela@nvidia.com>
@cv cv self-assigned this Apr 21, 2026
@coderabbitai

coderabbitai Bot commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a --json flag to nemoclaw list and refactored listing into a data-collection + rendering pipeline. Introduced exported types and getSandboxInventory() / renderSandboxInventoryText(), an Oclif ListCommand and runner, new CLI wiring, tests, and docs to support JSON output.

Changes

Cohort / File(s) Summary
Documentation
\.agents/skills/nemoclaw-user-reference/references/commands.md, docs/reference/commands.md
Documented --json option for nemoclaw list, described machine-readable output shape and added example invocation.
Inventory logic & types
src/lib/inventory-commands.ts
Refactored list into data-collection + renderer; added SandboxInventoryRow, SandboxInventoryResult, getSandboxInventory(...), and renderSandboxInventoryText(...). Moved active session counting and connected derivation into inventory construction; live inference invoked during inventory build when sandboxes exist.
Inventory tests
src/lib/inventory-commands.test.ts
Added tests for getSandboxInventory() covering empty and populated registry scenarios; adjusted imports and asserted recovery metadata, lastOnboardedSandbox, activeSessionCount, connected, and getLiveInference invocation.
Oclif command & runner
src/lib/list-command.ts, src/lib/oclif-commands.ts, src/lib/oclif-runner.ts
Added Oclif ListCommand (JSON-enabled), a command map, and runRegisteredOclifCommand(...) to execute registered Oclif commands with error/exit handling.
List command deps
src/lib/list-command-deps.ts
New buildListCommandDeps() producing ListSandboxesCommandDeps: resolves OpenShell, lazily probes SSH processes for active session counts, exposes getLiveInference, recoverRegistryEntries, and loadLastSession, with guarded fallbacks.
CLI entry & wiring
src/nemoclaw.ts
Replaced inline list implementation with Oclif dispatch via runRegisteredOclifCommand("list", args, ...); changed listSandboxes(args = []) to accept args and exported captureOpenshell and recoverRegistryEntries. Updated help synopsis to include --json.
CLI tests
test/cli.test.ts
Added tests for list --help, list --json (empty and populated registry exact JSON outputs), and unknown list flag parse failure.
package config
package.json
Added oclif configuration block and @oclif/core dependency to enable Oclif runtime mapping.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as "User / CLI"
  participant Runner as "Oclif Runner\n(runRegisteredOclifCommand)"
  participant ListCmd as "ListCommand (Oclif)"
  participant Inventory as "getSandboxInventory"
  participant Registry as "recoverRegistryEntries / loadLastSession"
  participant Sessions as "getActiveSessionCount (SSH probe)"
  participant Inference as "getLiveInference (openshell)"
  participant Renderer as "renderSandboxInventoryText / JSON output"

  User->>Runner: invoke `nemoclaw list` (+ args)
  Runner->>ListCmd: run command with parsed args
  ListCmd->>Inventory: getSandboxInventory(deps)
  Inventory->>Registry: recover registry entries & last session
  Registry-->>Inventory: sandbox records + recovery metadata
  Inventory->>Sessions: probe active session counts (cached)
  Sessions-->>Inventory: activeSessionCount per sandbox
  Inventory->>Inference: (if sandboxes exist) fetch live inference
  Inference-->>Inventory: inference data
  Inventory-->>ListCmd: SandboxInventoryResult
  ListCmd->>Renderer: choose JSON or text render
  Renderer-->>User: output to stdout
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through sandboxes, counted each one,

Collected tiny JSON crumbs beneath the sun.
Text for humans, JSON for bots, too—
I tidy up listings, then nibble a carrot stew.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main feature being added: JSON output support for the nemoclaw list command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/list-json

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
src/lib/inventory-commands.ts (1)

105-107: Remove the no-op live inference probe from inventory generation.

Line 105 to Line 107 calls getLiveInference() but discards the result, which adds external-call overhead without affecting output.

♻️ Proposed cleanup
-  if (recovery.sandboxes.length > 0) {
-    deps.getLiveInference();
-  }
🤖 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 105 - 107, The call to
deps.getLiveInference() inside the recovery.sandboxes check is a no-op (its
return is discarded) and should be removed to avoid unnecessary external calls;
locate the block referencing recovery.sandboxes and deps.getLiveInference() in
inventory-commands.ts and delete that call (or, if the intent was to use its
result, replace the discarded call with proper handling/assignment or awaiting
of getLiveInference() where its value/side-effects are actually needed).
🤖 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/nemoclaw.ts`:
- Around line 1182-1195: The loop that parses list flags currently reports all
args as unknown (using args.join) even when some (like "--json") are valid;
update the parsing in the for..of loop to collect only truly unrecognized flags
into an array (e.g., unknownFlags) as you iterate, set options.json when arg ===
"--json", handle help flags, and after the loop if unknownFlags.length > 0 call
console.error with unknownFlags.join(", ") and the usage then exit(1); leave
behavior unchanged when there are no unknown flags.

---

Nitpick comments:
In `@src/lib/inventory-commands.ts`:
- Around line 105-107: The call to deps.getLiveInference() inside the
recovery.sandboxes check is a no-op (its return is discarded) and should be
removed to avoid unnecessary external calls; locate the block referencing
recovery.sandboxes and deps.getLiveInference() in inventory-commands.ts and
delete that call (or, if the intent was to use its result, replace the discarded
call with proper handling/assignment or awaiting of getLiveInference() where its
value/side-effects are actually needed).
🪄 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 Plus

Run ID: 0caf55bb-e4e9-4604-9f5e-d068ff6817ec

📥 Commits

Reviewing files that changed from the base of the PR and between bd82e1f and 27a958d.

📒 Files selected for processing (6)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • docs/reference/commands.md
  • src/lib/inventory-commands.test.ts
  • src/lib/inventory-commands.ts
  • src/nemoclaw.ts
  • test/cli.test.ts

Comment thread src/nemoclaw.ts Outdated
@cv cv mentioned this pull request Apr 21, 2026
13 tasks
@cv cv added the v0.0.22 label Apr 21, 2026
@cv cv enabled auto-merge (squash) April 21, 2026 08:18
@cv cv requested a review from ericksoa April 21, 2026 18:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/nemoclaw.ts (1)

1183-1195: ⚠️ Potential issue | 🟡 Minor

Report only truly unknown list flags.

At Line 1193, using args.join(", ") can mislabel valid flags (e.g., --json --bogus) as unknown. This was already flagged in a previous review and is still present.

Suggested fix
 function parseListArgs(args = []) {
   const options = { json: false };
+  const unknownArgs = [];

   for (const arg of args) {
     if (arg === "--json") {
       options.json = true;
       continue;
@@
       console.log("");
       process.exit(0);
     }
-    console.error(`  Unknown argument(s) for list: ${args.join(", ")}`);
-    console.error("  Usage: nemoclaw list [--json]");
-    process.exit(1);
+    unknownArgs.push(arg);
+  }
+
+  if (unknownArgs.length > 0) {
+    console.error(`  Unknown argument(s) for list: ${unknownArgs.join(", ")}`);
+    console.error("  Usage: nemoclaw list [--json]");
+    process.exit(1);
   }

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

In `@src/nemoclaw.ts` around lines 1183 - 1195, The current loop over for (const
arg of args) misreports unknown flags by logging args.join(", ") so valid flags
get labeled unknown; update the handler inside the loop to log only the
offending arg (use the loop variable arg) when it is neither "--json" nor help,
e.g., replace the console.error that uses args.join with one that interpolates
the single arg, then print usage and call process.exit(1) as before; keep
references to the same loop, options.json assignment, and process.exit call so
only the actual unknown flag is reported.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/nemoclaw.ts`:
- Around line 1183-1195: The current loop over for (const arg of args)
misreports unknown flags by logging args.join(", ") so valid flags get labeled
unknown; update the handler inside the loop to log only the offending arg (use
the loop variable arg) when it is neither "--json" nor help, e.g., replace the
console.error that uses args.join with one that interpolates the single arg,
then print usage and call process.exit(1) as before; keep references to the same
loop, options.json assignment, and process.exit call so only the actual unknown
flag is reported.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dccdf718-cf79-4569-ad63-893cfe3f49e3

📥 Commits

Reviewing files that changed from the base of the PR and between 27a958d and ce7f648.

📒 Files selected for processing (3)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • docs/reference/commands.md
  • src/nemoclaw.ts
✅ Files skipped from review due to trivial changes (2)
  • .agents/skills/nemoclaw-user-reference/references/commands.md
  • docs/reference/commands.md

cv added 2 commits April 21, 2026 14:43
## Summary
This stacked PR builds on #2147 by routing `nemoclaw list` through an
`@oclif/core` command adapter while preserving the current `list`, `list
--json`, `list --help`, and invalid-argument behavior. It removes the
hand-rolled `list` flag parser from the legacy dispatcher and
establishes the first reusable oclif path for the root CLI.

You thought I gave up on #924? ;) 

## Changes
- Add `@oclif/core` as a root CLI dependency and introduce
`src/lib/list-command.ts` as an oclif-backed adapter for `nemoclaw
list`.
- Replace the manual `parseListArgs()` path in `src/nemoclaw.ts` with
`buildListCommandDeps()` and `runListCommand()` so the legacy dispatcher
only supplies runtime dependencies.
- Add `src/lib/list-command.test.ts` plus CLI coverage in
`test/cli.test.ts` for `list --help`, JSON output, and invalid `list`
arguments.

## Type of Change
- [x] Code change (feature, bug fix, or refactor)
- [ ] Code change with doc updates
- [ ] Doc only (prose changes, no code sample modifications)
- [ ] Doc only (includes code sample changes)

## Verification
- [x] `npx prek run --all-files` passes
- [x] `npm test` passes
- [x] Tests added or updated for new or changed behavior
- [x] No secrets, API keys, or credentials committed
- [ ] Docs updated for user-facing behavior changes
- [ ] `make docs` builds without warnings (doc changes only)
- [ ] Doc pages follow the [style
guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md)
(doc changes only)
- [ ] New doc pages include SPDX header and frontmatter (new pages only)

## AI Disclosure
- [x] AI-assisted — tool: OpenAI Codex

---
Signed-off-by: Carlos Villela <cvillela@nvidia.com>

---------

Signed-off-by: Carlos Villela <cvillela@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/lib/list-command-deps.ts (1)

32-37: Cache failed SSH probes as null to avoid repeated timeout hits.

At Line 33, if sessionDeps.getSshProcesses() throws, cachedSshOutput stays undefined, so each sandbox row can retry the expensive probe and hit the timeout again.

Proposed fix
   let cachedSshOutput: string | null | undefined;
   const getCachedSshOutput = () => {
     if (cachedSshOutput === undefined && sessionDeps) {
-      cachedSshOutput = sessionDeps.getSshProcesses();
+      try {
+        cachedSshOutput = sessionDeps.getSshProcesses();
+      } catch {
+        cachedSshOutput = null;
+      }
     }
     return cachedSshOutput ?? null;
   };

Also applies to: 47-54

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

In `@src/lib/list-command-deps.ts` around lines 32 - 37, The current
getCachedSshOutput getter calls sessionDeps.getSshProcesses() without handling
exceptions so a thrown error leaves cachedSshOutput undefined and causes
repeated expensive retries; wrap the call in a try/catch, on success set
cachedSshOutput to the result and return it, and on any error set
cachedSshOutput = null and return null (so failures are cached), and apply the
same defensive try/catch + null-caching change to the other similar SSH probe
getter (the other occurrence that also references cachedSshOutput and
sessionDeps.getSshProcesses).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/list-command-deps.ts`:
- Around line 32-37: The current getCachedSshOutput getter calls
sessionDeps.getSshProcesses() without handling exceptions so a thrown error
leaves cachedSshOutput undefined and causes repeated expensive retries; wrap the
call in a try/catch, on success set cachedSshOutput to the result and return it,
and on any error set cachedSshOutput = null and return null (so failures are
cached), and apply the same defensive try/catch + null-caching change to the
other similar SSH probe getter (the other occurrence that also references
cachedSshOutput and sessionDeps.getSshProcesses).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 55079447-ee5b-43b1-be2a-67964ef869de

📥 Commits

Reviewing files that changed from the base of the PR and between b372fbe and ea5b163.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • package.json
  • src/lib/list-command-deps.ts
  • src/lib/list-command.ts
  • src/lib/oclif-commands.ts
  • src/lib/oclif-runner.ts
  • src/nemoclaw.ts
  • test/cli.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/oclif-commands.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/nemoclaw.ts

@cv cv added v0.0.23 and removed v0.0.22 labels Apr 22, 2026

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good parts

The inventory extraction is well-designed: getSandboxInventory() / renderSandboxInventoryText() / SandboxInventoryResult with schemaVersion: 1 is a clean separation. The tests are solid. This is the right architecture for --json support.

Problem: @oclif/core as a production dependency

NemoClaw currently has 3 production dependencies (js-yaml, p-retry, yaml). This PR adds @oclif/core, a full CLI framework that pulls in ~20 transitive dependencies (ejs, jake, filelist, debug, ansis, cli-spinners, semver, minimatch, etc.). That's a >5x increase in the production dependency tree to parse a single --json flag.

The oclif scaffolding — oclif-commands.ts, oclif-runner.ts, list-command.ts, list-command-deps.ts, the oclif section in package.json — is framework overhead that doesn't earn its keep for one command with one flag.

Suggested approach

Keep getSandboxInventory(), renderSandboxInventoryText(), SandboxInventoryResult, and the tests. Drop oclif entirely. The --json flag can be handled in the existing command dispatcher in nemoclaw.ts in ~10 lines:

if (cmd === "list") {
  const jsonMode = actionArgs.includes("--json");
  const inventory = await getSandboxInventory(buildListCommandDeps());
  if (jsonMode) {
    console.log(JSON.stringify(inventory, null, 2));
  } else {
    renderSandboxInventoryText(inventory);
  }
  return;
}

No new dependency, no framework, same feature.

@cv

cv commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator Author

@ericksoa the oclif dependency isn't justified for a single --json, but the plan is to roll that out across all CLIs and commands. This is PR 1 out of many. We should not be building a CLI framework ourselves.

@prekshivyas prekshivyas requested a review from ericksoa April 23, 2026 15:48
@jyaunches jyaunches added v0.0.24 and removed v0.0.23 labels Apr 23, 2026
@cv cv added v0.0.25 and removed v0.0.24 labels Apr 24, 2026
@cv

cv commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up salvage summary (previous comment was mangled by shell quoting):

  • merged latest main into this branch and resolved the merge conflicts
  • preserved the live-gateway text rendering for nemoclaw list
  • removed the JSON-only no-op live inference probe
  • cached failed SSH probe results so repeated row rendering does not retry the timeout path
  • refreshed the list / help tests around the new JSON flow

Local validation:

  • npm run build:cli
  • npm test -- src/lib/inventory-commands.test.ts src/lib/command-registry.test.ts test/cli.test.ts

CI is rerunning now.

@cv cv added v0.0.29 and removed v0.0.28 labels Apr 28, 2026
# Conflicts:
#	src/nemoclaw.ts
#	test/cli.test.ts
@cv cv added v0.0.32 and removed v0.0.31 labels Apr 30, 2026

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me after the salvage pass. The list inventory extraction is clean, the JSON mode is covered by focused tests, the text rendering behavior is preserved, and the oclif dependency is justified as the first step toward moving the CLI surface onto a real command framework.

@cv cv merged commit fa32fdb into main Apr 30, 2026
20 of 21 checks passed
prekshivyas added a commit that referenced this pull request Apr 30, 2026
Follow the oclif command framework introduced in #2147 (list --json):

- Convert ShareCommand to an oclif Command class with Args parsing
- Add share-command-deps.ts runtime bridge (same pattern as
  list-command-deps.ts) for captureOpenshell / ensureLiveSandboxOrExit
- Register in oclif-commands.ts alongside ListCommand
- Dispatch via runRegisteredOclifCommand() in nemoclaw.ts — sandbox
  name passed as first positional arg, parsed by oclif Args

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cv cv deleted the feat/list-json branch May 27, 2026 21:16
@wscurran wscurran added area: docs Documentation, examples, guides, or docs build feature PR adds or expands user-visible functionality and removed documentation labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: docs Documentation, examples, guides, or docs build feature PR adds or expands user-visible functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants