Skip to content

feat(cli): add shell completion for bash, zsh, and fish#895

Closed
prekshivyas wants to merge 12 commits intoNVIDIA:mainfrom
prekshivyas:feat/shell-completion
Closed

feat(cli): add shell completion for bash, zsh, and fish#895
prekshivyas wants to merge 12 commits intoNVIDIA:mainfrom
prekshivyas:feat/shell-completion

Conversation

@prekshivyas
Copy link
Contributor

@prekshivyas prekshivyas commented Mar 25, 2026

Summary

Add a nemoclaw completion [bash|zsh|fish] subcommand that generates shell completion scripts. Auto-detects the current shell when run without arguments.

Fixes #155

Changes

  • bin/lib/completion.js — generates bash, zsh, and fish completion scripts
  • bin/nemoclaw.js — registers completion as a global command
  • test/completion.test.js — 7 test cases

Details

Completions cover:

  • Global commands (onboard, list, deploy, etc.)
  • Dynamic sandbox names from the registry
  • Sandbox-scoped actions (connect, status, logs, etc.)
  • Per-command flags (--follow, --yes, --quick, --output)

Hidden --list-sandboxes flag is used internally by completion scripts for dynamic sandbox name completion at tab-time.

Type of Change

  • Code change for a new feature, bug fix, or refactor.

Testing

  • npx prek run --all-files passes (or equivalently make check).
  • npm test passes.
# Usage
eval "$(nemoclaw completion bash)"   # bash
eval "$(nemoclaw completion zsh)"    # zsh
nemoclaw completion fish | source    # fish
nemoclaw completion                  # auto-detect

Checklist

General

Code Changes

  • Tests added or updated for new or changed behavior.
  • No secrets, API keys, or credentials committed.

Signed-off-by: Prekshi Vyas prekshiv@nvidia.com

Summary by CodeRabbit

  • New Features

    • Added shell completion for bash, zsh, and fish via nemoclaw completion [bash|zsh|fish]; omitting the shell auto-detects it. Completions include global commands, sandbox names, actions, and relevant flags. Adds a mode to list sandbox names for integration.
    • Help updated to document "Shell Completion".
  • Tests

    • Added automated tests validating completion output, list-sandboxes mode, unknown-shell handling, and help text.

Add a `nemoclaw completion [bash|zsh|fish]` subcommand that outputs
shell-specific completion scripts. Completions cover global commands,
dynamic sandbox names from the registry, sandbox-scoped actions, and
per-command flags (--follow, --yes, --quick, etc.).

Auto-detects the current shell when invoked without arguments. Includes
a hidden --list-sandboxes flag used internally by the completion scripts
for dynamic sandbox name completion at tab-time.

Fixes NVIDIA#155

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

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

Adds a new completion CLI subcommand and CommonJS module that emits bash/zsh/fish completion scripts, supports --list-sandboxes for dynamic sandbox enumeration, auto-detects shell from SHELL, and updates help/tests to exercise the new functionality.

Changes

Cohort / File(s) Summary
Completion module
bin/lib/completion.js
New CommonJS module exporting run(args) and DEBUG_FLAGS; builds shell-specific completion scripts for bash, zsh, and fish; supports --list-sandboxes; queries registry.listSandboxes() for sandbox names; auto-detects shell and errors on unsupported shells.
CLI integration
bin/nemoclaw.js
Added completion global command dispatch to require("./lib/completion").run(args) and updated help() output to include a "Shell Completion" section.
Tests
test/completion.test.js
New Vitest suite exercising nemoclaw completion (bash/zsh/fish), shell auto-detection, --list-sandboxes, unknown-shell error behavior, help text presence, and cross-validating DEBUG_FLAGS against scripts/debug.sh; runs with isolated HOME.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as "nemoclaw CLI"
    participant Completion as "completion module"
    participant Registry as "registry"

    User->>CLI: nemoclaw completion [shell] or --list-sandboxes
    CLI->>Completion: run(args)
    alt --list-sandboxes
        Completion->>Registry: listSandboxes()
        Registry-->>Completion: sandbox names
        Completion-->>User: print sandbox names (one per line)
    else normal completion request
        Completion->>Registry: listSandboxes()
        Registry-->>Completion: sandbox names
        Completion->>Completion: assemble script (global cmds, sandbox actions, debug flags, sandbox names)
        Completion-->>User: output completion script
    end
    alt unknown shell
        Completion-->>User: stderr "Unknown shell" + supported shells
        CLI-->>User: exit 1
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hop through lines and find each name,

I whisper completions, tame the game.
Bash, zsh, fish — I stitch the song,
Tab folds tidy, keys belong.
Little rabbit, code made bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 PR title accurately describes the main feature added: shell completion support for bash, zsh, and fish shells.
Linked Issues check ✅ Passed The PR implements all requirements from issue #155: completion subcommand for bash/zsh/fish, auto-detection, global commands, sandbox actions, dynamic sandbox names, and instructions.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing shell completion per issue #155; no unrelated code modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Fix ESLint no-undef (words) and no-useless-escape (\$) errors in
the generated bash and zsh completion output.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/completion.js`:
- Around line 47-54: The case statement that determines command-specific
completions is missing the setup-spark command, so when users type "nemoclaw
setup-spark " the generic sandbox completions are offered; update the
switch/case block (the pattern grouping that currently contains
"onboard|list|start|stop|status|help|completion|uninstall" and the separate
"deploy" branch) to include "setup-spark" (or add a dedicated "setup-spark)
return ;;" branch) so that the function returns early with no completions for
setup-spark and prevents the fallback sandbox actions from being suggested.
- Around line 77-144: In function zsh, update the case that runs when (( CURRENT
== 3 )) and switches on "${words[2]}" inside the zsh completion script to
include setup-spark alongside
onboard|list|start|stop|status|help|completion|uninstall so that setup-spark
returns early and does not fall through to the sandbox action suggestions;
modify the case pattern in the _nemoclaw function where "${words[2]}" is matched
to add setup-spark to the existing alternation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0e259828-e2fe-4bbb-98d2-1872c35306f7

📥 Commits

Reviewing files that changed from the base of the PR and between 5748a32 and 4150dcc.

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

Copy link

@KimBioInfoStudio KimBioInfoStudio left a comment

Choose a reason for hiding this comment

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

Nice work — the hand-written approach is the right call for this CLI's architecture. A few suggestions after reading the full diff + nemoclaw.js:

1. Command/action lists are duplicated and already drifting

completion.js hardcodes its own GLOBAL_COMMANDS and SANDBOX_ACTIONS arrays, separate from nemoclaw.js. They're already out of sync — completion.js is missing setup (deprecated but still functional). This will get worse as commands are added.

Consider exporting the canonical lists from a shared module (or from nemoclaw.js itself) so completion stays in sync automatically.

2. Completion should include --help / -h / --version

completion.js drops --help, -h, and --version from the completable tokens, but nemoclaw.js handles all of them as global commands. In non-interactive contexts (e.g. an AI agent or script discovering CLI usage via nemoclaw --help), tab-completing --h--help is expected behavior. These flags should be included in the first-position completions alongside the named commands.

3. bash: _init_completion may not exist on macOS

_init_completion || return

_init_completion comes from the bash-completion package, which isn't installed by default on macOS. Users who haven't brew install bash-completion will get a silent failure. A fallback would fix this:

if type _init_completion &>/dev/null; then
  _init_completion || return
else
  cur="${COMP_WORDS[COMP_CWORD]}"
  prev="${COMP_WORDS[COMP_CWORD-1]}"
  cword=$COMP_CWORD
fi

4. args.includes("--list-sandboxes") is too loose

if (args.includes("--list-sandboxes")) {

If someone runs nemoclaw completion --list-sandboxes bash, the --list-sandboxes check swallows the request before bash is ever looked at. Checking args[0] === "--list-sandboxes" would be more precise and match the positional nature of the other arguments.

5. zsh template mixes JS interpolation and escaped shell variables

The zsh() function uses JS template literals with ${GLOBAL_COMMANDS.join(...)} for JS interpolation alongside \${(f)"$(...)"} for zsh syntax. It works, but it's easy to accidentally drop or add a backslash when editing. Minor, but worth a comment or consider using plain string concatenation for the shell-variable-heavy sections.

6. (Nit) debug flags in completion may drift from debug.sh

The completion hardcodes --quick --output --help for debug, but nemoclaw.js passes args straight through to debug.sh. If that script gains flags, completion won't know. Not a blocker — just something to keep in mind, or worth a code comment.


7. Looking ahead: CLI framework comparison for future refactoring

The hand-written dispatch in nemoclaw.js works today, but as the CLI grows (more commands, more flags, nested subcommands), maintaining the switch/case router + hand-written completion in parallel will become a drag. If a refactor is ever on the table, here's how the main Node.js CLI frameworks stack up against NemoClaw's specific needs:

Key requirements from current architecture

  • Dynamic positional arguments: nemoclaw <sandbox-name> <action> — first arg can be a command OR a dynamic entity name
  • Zero-config completion: shell completion should derive from command definitions, not a separate file
  • Async command handlers: several commands (onboard, deploy, destroy) are async
  • Minimal dependencies: current CLI is dependency-light by design
  • Interactive prompts: onboard and policy-add use interactive input

Framework comparison

yargs commander oclif clipanion citty
Built-in completion .completion() bash/zsh ❌ needs plugin ✅ plugin, bash/zsh/fish ✅ built-in
Dynamic positional args ⚠️ middleware hack ⚠️ .arguments() + manual ✅ hooks + arg parser ✅ rich routing ⚠️ manual
<name> <action> pattern ❌ awkward, needs catch-all ❌ doesn't fit subcommand model ✅ topic:command or custom ✅ path-based routing fits well
Async handlers
Bundle size (approx) ~180KB ~60KB ~2MB+ (framework) ~40KB ~15KB
Learning curve Low Low High Medium Low
Interactive prompts External (inquirer) External Built-in (@oclif/prompts) External External
TypeScript-first ⚠️ types available

Recommendation

If refactoring: clipanion is the best fit.

  • Its path-based command routing naturally handles nemoclaw <name> <action> — you define a catch-all command class with a positional arg that checks against the registry, exactly like the current dispatch logic but declarative
  • Built-in completion derives from command definitions — no separate completion.js to maintain
  • Smallest bundle size among frameworks that actually solve the completion problem (~40KB)
  • Used in production by Yarn Berry, so it's battle-tested for CLI tools with complex routing

If staying hand-written (totally valid): the suggestions in points 1–6 above are the highest-value improvements for this PR.

@prekshivyas
Copy link
Contributor Author

Note: #160 also addresses #155. This PR differs in a few ways — separate module, dynamic sandbox name completion at tab-time, shell auto-detection, per-command flag completion, and tests.

- Add setup-spark and deploy to no-arg case in bash/zsh completion
- Add --help, -h, --version, -v to first-position completions
- Add _init_completion fallback for macOS without bash-completion
- Use args[0] instead of args.includes for --list-sandboxes check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@prekshivyas
Copy link
Contributor Author

prekshivyas commented Mar 25, 2026

Thanks for the thorough review! Pushed fixes for points 1–4.

checking on 5, 6

Copy link
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/completion.js`:
- Around line 33-37: The fallback branch that sets cur, prev and cword when
_init_completion is not available fails to initialize the words array, causing
${words[2]} to be undefined; update the fallback to also initialize words (e.g.,
assign words from COMP_WORDS like words=( "${COMP_WORDS[@]}" ) or an equivalent
POSIX-safe array) so subsequent references to words (such as ${words[2]}) work
on systems without bash-completion; ensure the change is applied alongside the
existing cur/prev/cword assignments in the same fallback block.
- Around line 6-10: The GLOBAL_COMMANDS array is missing the legacy "setup"
command used by the CLI dispatcher; update the GLOBAL_COMMANDS constant to
include "setup" alongside "setup-spark" so shell autocompletion lists it (locate
the GLOBAL_COMMANDS declaration in completion.js and add the string "setup" into
that array).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bec56c08-c3d4-4751-8eea-9fcd0dda6092

📥 Commits

Reviewing files that changed from the base of the PR and between 4150dcc and cf8815c.

📒 Files selected for processing (1)
  • bin/lib/completion.js

Comment on lines +6 to +10
const GLOBAL_COMMANDS = [
"onboard", "list", "deploy", "setup-spark",
"start", "stop", "status", "debug", "uninstall",
"help", "completion", "--help", "-h", "--version", "-v",
];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if 'setup' is a recognized command in the CLI dispatcher
rg -n "setup" bin/nemoclaw.js | head -20

Repository: NVIDIA/NemoClaw

Length of output: 967


Add setup command to GLOBAL_COMMANDS.

The setup command is a valid CLI command (defined in bin/nemoclaw.js line 87) and is callable via the dispatcher, but it is missing from the GLOBAL_COMMANDS array. Although deprecated in favor of onboard, it remains functional for backwards compatibility and should be included in shell completion:

const GLOBAL_COMMANDS = [
  "onboard", "list", "deploy", "setup", "setup-spark",
  "start", "stop", "status", "debug", "uninstall",
  "help", "completion", "--help", "-h", "--version", "-v",
];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/completion.js` around lines 6 - 10, The GLOBAL_COMMANDS array is
missing the legacy "setup" command used by the CLI dispatcher; update the
GLOBAL_COMMANDS constant to include "setup" alongside "setup-spark" so shell
autocompletion lists it (locate the GLOBAL_COMMANDS declaration in completion.js
and add the string "setup" into that array).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Intentionally omitted — setup is deprecated (prints a warning and redirects to onboard). Completion should guide users to onboard, not the legacy alias.

prekshivyas and others added 3 commits March 25, 2026 10:11
- Add setup-spark and deploy to no-arg case in bash/zsh completion
- Add --help, -h, --version, -v to first-position completions
- Add _init_completion fallback for macOS without bash-completion
- Use args[0] instead of args.includes for --list-sandboxes check
- Refactor zsh() to string concatenation to avoid JS/shell escaping bugs
- Add missing --sandbox flag to debug completions (was already drifted)
- Add test that asserts completion DEBUG_FLAGS match scripts/debug.sh
- Export DEBUG_FLAGS constant for testability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When _init_completion is unavailable (macOS without bash-completion),
the fallback now also sets words from COMP_WORDS so ${words[2]}
resolves correctly at cword==3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous regex also matched help-text lines ending with ')'.
Narrowed to only match bash case patterns like '--flag)' or
'--flag | -f)'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
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

♻️ Duplicate comments (2)
bin/lib/completion.js (2)

6-10: ⚠️ Potential issue | 🟡 Minor

Keep the shell command inventories aligned with the dispatcher.

bin/nemoclaw.js still accepts setup as a top-level command, but it is missing from the shared list here, the zsh global_cmds, and fish's global_cmds guard. That leaves nemoclaw setup unsuggested and can fall through to sandbox-action completion. Consider generating these shell-specific lists from one shared source so they stop drifting.

Also applies to: 105-121, 184-205

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

In `@bin/lib/completion.js` around lines 6 - 10, The global command list in
completion generation is missing the "setup" command and is out of sync with the
dispatcher in bin/nemoclaw.js; update the shared command source so all shells
use the same inventory (or add "setup" to GLOBAL_COMMANDS and the corresponding
zsh global_cmds and fish global_cmds guards) and refactor generation to derive
those shell-specific arrays from that single list (look for the GLOBAL_COMMANDS
constant and the zsh/fish global_cmds blocks to make the change).

39-46: ⚠️ Potential issue | 🟠 Major

Initialize words in the bash fallback path.

When _init_completion is unavailable, Line 74 still reads ${words[2]}, but the fallback only sets cur, prev, and cword. Third-position flag completion after sandbox actions will silently break in that path.

Proposed fix
 _nemoclaw() {
-  local cur prev words cword
+  local cur prev cword
+  local -a words
   if type _init_completion &>/dev/null; then
     _init_completion || return
   else
     cur="${COMP_WORDS[COMP_CWORD]}"
     prev="${COMP_WORDS[COMP_CWORD-1]}"
     cword=$COMP_CWORD
+    words=("${COMP_WORDS[@]}")
   fi
In bash programmable completion, if `_init_completion` is unavailable and a script falls back to `COMP_WORDS`/`COMP_CWORD`, does it need to initialize a local `words` array before using `${words[2]}`?

Also applies to: 73-75

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

In `@bin/lib/completion.js` around lines 39 - 46, The fallback branch that runs
when _init_completion is unavailable declares but does not populate the words
array, yet later code reads ${words[2]} causing silent breakage; in the else
branch (where cur, prev, cword are set from COMP_WORDS/COMP_CWORD) initialize
words from COMP_WORDS (e.g. assign words=("${COMP_WORDS[@]}") or equivalent) so
subsequent uses of words[...] work correctly; update the else block that
references cur, prev, cword, and words to populate words before any ${words[n]}
reads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/completion.js`:
- Line 33: The issue is that the global no-argument command list includes
"completion", causing the CLI to return early instead of offering shell options;
update every place where GLOBAL_COMMANDS is filtered into noArgCmds (and similar
filters at the other occurrences) to exclude both "debug" and "completion" (or
build the no-arg list from an explicit allowed array) so that the "completion"
command is not treated as a no-arg command; update the expressions that create
noArgCmds and any matching checks that use that list (referencing noArgCmds,
GLOBAL_COMMANDS, and the "completion" command name) accordingly.

In `@test/completion.test.js`:
- Around line 83-88: The current regex used to build casePatterns is too
permissive and picks up usage lines; update the regex so it only matches
case-label lines that end with a closing parenthesis (and may contain
pipe-separated flags) — e.g. replace the pattern in the match call for
casePatterns with one that requires flags like "--flag" (optionally "|--other")
followed immediately by ")" such as /^\s+--[^\s|)]+(?:\|--[^\s|)]+)*\)/gm; keep
the subsequent mapping logic that trims, strips the trailing ")" and splits on
"|" for debugShFlags so only true case labels from debugSh are parsed.

---

Duplicate comments:
In `@bin/lib/completion.js`:
- Around line 6-10: The global command list in completion generation is missing
the "setup" command and is out of sync with the dispatcher in bin/nemoclaw.js;
update the shared command source so all shells use the same inventory (or add
"setup" to GLOBAL_COMMANDS and the corresponding zsh global_cmds and fish
global_cmds guards) and refactor generation to derive those shell-specific
arrays from that single list (look for the GLOBAL_COMMANDS constant and the
zsh/fish global_cmds blocks to make the change).
- Around line 39-46: The fallback branch that runs when _init_completion is
unavailable declares but does not populate the words array, yet later code reads
${words[2]} causing silent breakage; in the else branch (where cur, prev, cword
are set from COMP_WORDS/COMP_CWORD) initialize words from COMP_WORDS (e.g.
assign words=("${COMP_WORDS[@]}") or equivalent) so subsequent uses of
words[...] work correctly; update the else block that references cur, prev,
cword, and words to populate words before any ${words[n]} reads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 441ced39-829a-4ed4-a906-96ecdc5e3428

📥 Commits

Reviewing files that changed from the base of the PR and between cf8815c and d048723.

📒 Files selected for processing (2)
  • bin/lib/completion.js
  • test/completion.test.js

Comment on lines +83 to +88
const casePatterns = debugSh.match(/^\s+--[^\n]+\)/gm) || [];
const debugShFlags = casePatterns
.map((m) => m.trim().replace(/\)$/, "").split(/\s*\|\s*/))
.flat()
.map((f) => f.trim())
.filter((f) => f.startsWith("--"));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Parse only case labels from debug.sh.

Line 83 currently matches any indented --... ) line, so usage text like --sandbox NAME ... (default: ...) is being treated as a flag. That is why CI is failing at Line 97 even though DEBUG_FLAGS already contains --sandbox.

Proposed fix
-    const casePatterns = debugSh.match(/^\s+--[^\n]+\)/gm) || [];
-    const debugShFlags = casePatterns
-      .map((m) => m.trim().replace(/\)$/, "").split(/\s*\|\s*/))
+    const casePatterns = Array.from(
+      debugSh.matchAll(
+        /^\s+((?:--[^\s|)]+|-[^\s|)]+)(?:\s*\|\s*(?:--[^\s|)]+|-[^\s|)]+)*)\)\s*$/gm,
+      ),
+      ([, pattern]) => pattern,
+    );
+    const debugShFlags = casePatterns
+      .map((pattern) => pattern.split(/\s*\|\s*/))
       .flat()
       .map((f) => f.trim())
       .filter((f) => f.startsWith("--"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/completion.test.js` around lines 83 - 88, The current regex used to
build casePatterns is too permissive and picks up usage lines; update the regex
so it only matches case-label lines that end with a closing parenthesis (and may
contain pipe-separated flags) — e.g. replace the pattern in the match call for
casePatterns with one that requires flags like "--flag" (optionally "|--other")
followed immediately by ")" such as /^\s+--[^\s|)]+(?:\|--[^\s|)]+)*\)/gm; keep
the subsequent mapping logic that trims, strips the trailing ")" and splits on
"|" for debugShFlags so only true case labels from debugSh are parsed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
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.

🧹 Nitpick comments (2)
test/completion.test.js (2)

48-53: Strengthen the auto-detect test assertion.

At Line 52, checking only output length can pass with unrelated output. Assert at least one shell-specific completion marker.

Proposed test hardening
   it("completion with no arg auto-detects shell", () => {
     const r = run("completion");
     expect(r.code).toBe(0);
-    // Should output some completion script regardless of shell
-    expect(r.out.length).toBeGreaterThan(100);
+    // Should output a known completion marker for one of the supported shells.
+    expect(
+      r.out.includes("_nemoclaw") ||
+        r.out.includes("#compdef nemoclaw") ||
+        r.out.includes("complete -c nemoclaw"),
+    ).toBe(true);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/completion.test.js` around lines 48 - 53, The test "completion with no
arg auto-detects shell" is too weak—after calling run("completion") and getting
r.out you should assert for at least one shell-specific completion marker
instead of only length; update the assertion on r.out to check that it contains
one of known markers (e.g., "complete -C" or "complete -o", "compdef", or
"autoload -U +X compinit") so that the test verifies an actual shell completion
script was emitted; locate the test using run("completion") and replace or
augment the expect(r.out.length).toBeGreaterThan(100) check with an assertion
that r.out includes at least one of those markers.

65-66: Fix the HOME comment to match actual setup.

Line 65 says “empty HOME”, but Line 16 sets HOME to an isolated temp path. Update wording to avoid confusion.

Comment-only fix
-    // With empty HOME, no sandboxes expected — just no error
+    // With isolated HOME, no pre-existing sandboxes expected — just no error
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/completion.test.js` around lines 65 - 66, Update the misleading test
comment that says “empty HOME” to accurately reflect the test setup which sets
HOME to an isolated temporary path (the test’s HOME isolation done earlier in
the file), e.g. change it to “With isolated HOME (temporary path), no sandboxes
expected — just no error” so the comment matches the actual setup used by the
test case in test/completion.test.js.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/completion.test.js`:
- Around line 48-53: The test "completion with no arg auto-detects shell" is too
weak—after calling run("completion") and getting r.out you should assert for at
least one shell-specific completion marker instead of only length; update the
assertion on r.out to check that it contains one of known markers (e.g.,
"complete -C" or "complete -o", "compdef", or "autoload -U +X compinit") so that
the test verifies an actual shell completion script was emitted; locate the test
using run("completion") and replace or augment the
expect(r.out.length).toBeGreaterThan(100) check with an assertion that r.out
includes at least one of those markers.
- Around line 65-66: Update the misleading test comment that says “empty HOME”
to accurately reflect the test setup which sets HOME to an isolated temporary
path (the test’s HOME isolation done earlier in the file), e.g. change it to
“With isolated HOME (temporary path), no sandboxes expected — just no error” so
the comment matches the actual setup used by the test case in
test/completion.test.js.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67efb472-e2b8-4377-bc64-bc64d78c206c

📥 Commits

Reviewing files that changed from the base of the PR and between d048723 and 74b742e.

📒 Files selected for processing (2)
  • bin/lib/completion.js
  • test/completion.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • bin/lib/completion.js

prekshivyas and others added 2 commits March 25, 2026 10:37
Copy link
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

♻️ Duplicate comments (1)
bin/lib/completion.js (1)

33-33: ⚠️ Potential issue | 🟠 Major

completion still short-circuits in bash and zsh.

Lines 33 and 97 exclude only debug from noArgCmds, so nemoclaw completion <TAB> still hits the early return on Lines 63-64 and 142-143 instead of offering bash, zsh, and fish.

Suggested fix
-  const noArgCmds = GLOBAL_COMMANDS.filter((c) => c !== "debug").join("|");
+  const noArgCmds = GLOBAL_COMMANDS
+    .filter((c) => !["debug", "completion"].includes(c))
+    .join("|");
@@
     case "$prev" in
+      completion)
+        COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
+        return ;;
       ${noArgCmds})
         return ;;
@@
-  const noArgCmds = GLOBAL_COMMANDS.filter((c) => c !== "debug").join("|");
+  const noArgCmds = GLOBAL_COMMANDS
+    .filter((c) => !["debug", "completion"].includes(c))
+    .join("|");
@@
     case "${words[2]}" in
+      completion)
+        _values 'shell' bash zsh fish
+        return ;;
       ${noArgCmds})
         return ;;

Also applies to: 62-68, 97-97, 141-153

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

In `@bin/lib/completion.js` at line 33, The short-circuit logic builds noArgCmds
by filtering out only "debug", which causes the "completion" command to be
treated as a no-arg command and return early; update the filter used when
building noArgCmds (and any duplicated instances where noArgCmds is computed) to
also exclude "completion" (i.e., GLOBAL_COMMANDS.filter(c => c !== "debug" && c
!== "completion") ), so that when the current command is "completion" the
completion handler will proceed to offer "bash", "zsh", and "fish" instead of
hitting the early return; apply this change to every place where noArgCmds (or
the same filter logic) is computed in the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/completion.js`:
- Around line 244-253: The auto-detection currently falls back to emitting
bash() for any unsupported or unset SHELL; update the no-arg branch that reads
envShell so it only emits zsh(), fish(), or bash() when envShell explicitly
matches those shells and otherwise writes an error to stderr (using
process.stderr.write or processLogger) and exits with a non-zero code; reference
the same symbols: shell, envShell, zsh(), fish(), bash() to locate and change
the logic so unknown/empty SHELL does not silently default to bash but fails
like the explicit-shell path.
- Around line 99-101: The generated zsh completion string currently invokes the
completer but does not register it; replace the invocation `_nemoclaw "$@"` with
a registration call `compdef _nemoclaw nemoclaw` so the function `_nemoclaw` is
registered as the completer for the `nemoclaw` command when `completion zsh`
output is eval'd; update the returned string in the completion builder (the
function that returns the zsh completion block) to include the `compdef
_nemoclaw nemoclaw` line instead of the one-shot `_nemoclaw "$@"` call.

---

Duplicate comments:
In `@bin/lib/completion.js`:
- Line 33: The short-circuit logic builds noArgCmds by filtering out only
"debug", which causes the "completion" command to be treated as a no-arg command
and return early; update the filter used when building noArgCmds (and any
duplicated instances where noArgCmds is computed) to also exclude "completion"
(i.e., GLOBAL_COMMANDS.filter(c => c !== "debug" && c !== "completion") ), so
that when the current command is "completion" the completion handler will
proceed to offer "bash", "zsh", and "fish" instead of hitting the early return;
apply this change to every place where noArgCmds (or the same filter logic) is
computed in the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ab258468-accf-48dd-9f3d-d1d502b9c612

📥 Commits

Reviewing files that changed from the base of the PR and between 74b742e and dfedd5e.

📒 Files selected for processing (1)
  • bin/lib/completion.js

prekshivyas and others added 2 commits March 25, 2026 10:48
- Don't treat completion as no-arg: offer bash/zsh/fish as subcommands
- Replace _nemoclaw "$@" with compdef _nemoclaw nemoclaw for zsh registration
- Error on unknown/unset SHELL in auto-detect instead of defaulting to bash
- Update tests for zsh compdef and explicit SHELL in auto-detect test

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@prekshivyas
Copy link
Contributor Author

Update: 5 and 6 are addressed

@cv
Copy link
Contributor

cv commented Mar 25, 2026

Nice work — the hand-written approach is the right call for this CLI's architecture.

It is not, as evidenced by the mountain of issues found immediately following that post.

We should be using a library that knows how to automatically generate completions for all 3 shells. @prekshivyas that would be a bit more foundational work. Try having a look into https://oclif.io and similar libs.

Copy link
Contributor

@cv cv left a comment

Choose a reason for hiding this comment

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

@prekshivyas
Copy link
Contributor Author

Closing in favor of #909 — the handwritten completion approach is being replaced by an oclif migration that provides shell completions (bash/zsh) out of the box via @oclif/plugin-autocomplete, along with auto-generated help, flag validation, and TypeScript. Fish completion will be addressed as a follow-up.

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.

feat: add shell completion for nemoclaw CLI

3 participants