Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 284 additions & 0 deletions bin/lib/completion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

const registry = require("./registry");

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

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.

⚠️ 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
Copy Markdown
Collaborator 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.


const SANDBOX_ACTIONS = [
"connect", "status", "logs", "policy-add", "policy-list", "destroy",
];

// Flags accepted by scripts/debug.sh — keep in sync or update the
// "completion flags match debug.sh" test when debug.sh gains new flags.
const DEBUG_FLAGS = ["--quick", "--sandbox", "--output", "--help"];

function getSandboxNames() {
try {
const { sandboxes } = registry.listSandboxes();
return sandboxes.map((s) => s.name);
} catch {
return [];
}
}

function bash() {
const globalCmds = GLOBAL_COMMANDS.join(" ");
const sandboxActions = SANDBOX_ACTIONS.join(" ");
const debugFlags = DEBUG_FLAGS.join(" ");
const noArgCmds = GLOBAL_COMMANDS.filter((c) => !["debug", "completion"].includes(c)).join("|");

return `# nemoclaw bash completion
# Add to ~/.bashrc: eval "$(nemoclaw completion bash)"

_nemoclaw() {
local cur prev words cword
if type _init_completion &>/dev/null; then
_init_completion || return
else
cur="\${COMP_WORDS[COMP_CWORD]}"
prev="\${COMP_WORDS[COMP_CWORD-1]}"
words=("\${COMP_WORDS[@]}")
cword=$COMP_CWORD
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.

local global_cmds="${globalCmds}"
local sandbox_actions="${sandboxActions}"

if [[ $cword -eq 1 ]]; then
# Complete global commands + sandbox names
local sandboxes
sandboxes="$(nemoclaw completion --list-sandboxes 2>/dev/null)"
COMPREPLY=($(compgen -W "$global_cmds $sandboxes" -- "$cur"))
return
fi

if [[ $cword -eq 2 ]]; then
# If first arg is a sandbox name, complete with actions
case "$prev" in
completion)
COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
return ;;
${noArgCmds})
return ;;
debug)
COMPREPLY=($(compgen -W "${debugFlags}" -- "$cur"))
return ;;
esac
# Assume sandbox name → offer actions
COMPREPLY=($(compgen -W "$sandbox_actions" -- "$cur"))
return
fi

if [[ $cword -eq 3 ]]; then
local action="\${words[2]}"
case "$action" in
logs)
COMPREPLY=($(compgen -W "--follow" -- "$cur"))
return ;;
destroy)
COMPREPLY=($(compgen -W "--yes --force" -- "$cur"))
return ;;
esac
fi
}

complete -F _nemoclaw nemoclaw
`;
}

// The zsh completion script uses plain string concatenation instead of a single
// template literal. This avoids mixing JS interpolation (${expr}) with zsh
// parameter expansion (${words[2]}, ${(f)...}) in the same string, which
// previously caused escaping bugs caught only by the linter.

function zsh() {
const noArgCmds = GLOBAL_COMMANDS.filter((c) => !["debug", "completion"].includes(c)).join("|");

return "#compdef nemoclaw\n"
+ "# nemoclaw zsh completion\n"
+ '# Add to ~/.zshrc: eval "$(nemoclaw completion zsh)"\n'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
+ "\n"
+ "_nemoclaw() {\n"
+ " local -a global_cmds sandbox_actions sandboxes\n"
+ "\n"
+ " global_cmds=(\n"
+ " 'onboard:Configure inference endpoint and credentials'\n"
+ " 'list:List all sandboxes'\n"
+ " 'deploy:Deploy to a Brev VM'\n"
+ " 'setup-spark:Set up on DGX Spark'\n"
+ " 'start:Start auxiliary services'\n"
+ " 'stop:Stop all services'\n"
+ " 'status:Show sandbox list and service status'\n"
+ " 'debug:Collect diagnostics for bug reports'\n"
+ " 'uninstall:Uninstall NemoClaw'\n"
+ " 'help:Show help'\n"
+ " 'completion:Generate shell completion script'\n"
+ " '--help:Show help'\n"
+ " '-h:Show help'\n"
+ " '--version:Show version'\n"
+ " '-v:Show version'\n"
+ " )\n"
+ "\n"
+ " sandbox_actions=(\n"
+ " 'connect:Shell into a running sandbox'\n"
+ " 'status:Sandbox health and NIM status'\n"
+ " 'logs:Stream sandbox logs'\n"
+ " 'policy-add:Add a network or filesystem policy preset'\n"
+ " 'policy-list:List presets'\n"
+ " 'destroy:Stop NIM and delete sandbox'\n"
+ " )\n"
+ "\n"
+ " if (( CURRENT == 2 )); then\n"
+ " # Get sandbox names dynamically\n"
+ ' sandboxes=(${(f)"$(nemoclaw completion --list-sandboxes 2>/dev/null)"})\n'
+ " _describe 'command' global_cmds -- sandboxes\n"
+ " return\n"
+ " fi\n"
+ "\n"
+ " if (( CURRENT == 3 )); then\n"
+ ' case "${words[2]}" in\n'
+ " completion)\n"
+ " _values 'shell' bash zsh fish\n"
+ " return ;;\n"
+ " " + noArgCmds + ")\n"
+ " return ;;\n"
+ " debug)\n"
+ " _arguments"
+ DEBUG_FLAGS.map((f) => {
const desc = { "--quick": "Quick diagnostics", "--sandbox": "Target sandbox", "--output": "Save to file", "--help": "Show help" }[f] || f;
const extra = f === "--output" ? ":file:_files" : f === "--sandbox" ? ":name: " : "";
return " '" + f + "[" + desc + "]" + extra + "'";
}).join("")
+ "\n"
+ " return ;;\n"
+ " esac\n"
+ " # Assume sandbox name → offer actions\n"
+ " _describe 'action' sandbox_actions\n"
+ " return\n"
+ " fi\n"
+ "\n"
+ " if (( CURRENT == 4 )); then\n"
+ ' case "${words[3]}" in\n'
+ " logs)\n"
+ " _arguments '--follow[Follow log output]'\n"
+ " return ;;\n"
+ " destroy)\n"
+ " _arguments '--yes[Skip confirmation]' '--force[Skip confirmation]'\n"
+ " return ;;\n"
+ " esac\n"
+ " fi\n"
+ "}\n"
+ "\n"
+ "compdef _nemoclaw nemoclaw\n";
}

function fish() {
return `# nemoclaw fish completion
# Add to ~/.config/fish/completions/nemoclaw.fish
# Or run: nemoclaw completion fish | source

# Disable file completions by default
complete -c nemoclaw -f

# Global commands
complete -c nemoclaw -n '__fish_use_subcommand' -a 'onboard' -d 'Configure inference endpoint and credentials'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'list' -d 'List all sandboxes'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'deploy' -d 'Deploy to a Brev VM'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'setup-spark' -d 'Set up on DGX Spark'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'start' -d 'Start auxiliary services'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'stop' -d 'Stop all services'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'status' -d 'Show sandbox list and service status'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'debug' -d 'Collect diagnostics for bug reports'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'uninstall' -d 'Uninstall NemoClaw'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'help' -d 'Show help'
complete -c nemoclaw -n '__fish_use_subcommand' -a 'completion' -d 'Generate shell completion script'
complete -c nemoclaw -n '__fish_use_subcommand' -l help -d 'Show help'
complete -c nemoclaw -n '__fish_use_subcommand' -s h -d 'Show help'
complete -c nemoclaw -n '__fish_use_subcommand' -l version -d 'Show version'
complete -c nemoclaw -n '__fish_use_subcommand' -s v -d 'Show version'

# Dynamic sandbox names
complete -c nemoclaw -n '__fish_use_subcommand' -a '(nemoclaw completion --list-sandboxes 2>/dev/null)'

# Sandbox actions (when first arg is not a global command)
set -l global_cmds onboard list deploy setup-spark start stop status debug uninstall help completion
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'connect' -d 'Shell into sandbox'
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'status' -d 'Sandbox health and NIM status'
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'logs' -d 'Stream sandbox logs'
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'policy-add' -d 'Add a policy preset'
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'policy-list' -d 'List presets'
complete -c nemoclaw -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from $global_cmds" -a 'destroy' -d 'Stop NIM and delete sandbox'

# debug flags
complete -c nemoclaw -n '__fish_seen_subcommand_from debug' -l quick -d 'Quick diagnostics'
complete -c nemoclaw -n '__fish_seen_subcommand_from debug' -l sandbox -d 'Target sandbox' -r
complete -c nemoclaw -n '__fish_seen_subcommand_from debug' -l output -d 'Save diagnostics to file' -r
complete -c nemoclaw -n '__fish_seen_subcommand_from debug' -l help -d 'Show help'

# logs flags
complete -c nemoclaw -n '__fish_seen_subcommand_from logs' -l follow -d 'Follow log output'

# destroy flags
complete -c nemoclaw -n '__fish_seen_subcommand_from destroy' -l yes -d 'Skip confirmation'
complete -c nemoclaw -n '__fish_seen_subcommand_from destroy' -l force -d 'Skip confirmation'

# completion subcommands
complete -c nemoclaw -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'
`;
}

function listSandboxNames() {
const names = getSandboxNames();
if (names.length > 0) {
console.log(names.join("\n"));
}
}

function run(args) {
if (args[0] === "--list-sandboxes") {
listSandboxNames();
return;
}

const shell = args[0];

if (!shell) {
// Auto-detect from SHELL env
const envShell = process.env.SHELL || "";
if (envShell.includes("zsh")) {
process.stdout.write(zsh());
} else if (envShell.includes("fish")) {
process.stdout.write(fish());
} else if (envShell.includes("bash")) {
process.stdout.write(bash());
} else {
console.error(` Unknown shell: ${envShell || "<unset>"}`);
console.error(" Supported: bash, zsh, fish");
process.exit(1);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
return;
}

switch (shell) {
case "bash":
process.stdout.write(bash());
break;
case "zsh":
process.stdout.write(zsh());
break;
case "fish":
process.stdout.write(fish());
break;
default:
console.error(` Unknown shell: ${shell}`);
console.error(" Supported: bash, zsh, fish");
process.exit(1);
}
}

module.exports = { run, DEBUG_FLAGS };
6 changes: 5 additions & 1 deletion bin/nemoclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const policies = require("./lib/policies");
const GLOBAL_COMMANDS = new Set([
"onboard", "list", "deploy", "setup", "setup-spark",
"start", "stop", "status", "debug", "uninstall",
"help", "--help", "-h", "--version", "-v",
"completion", "help", "--help", "-h", "--version", "-v",
]);

const REMOTE_UNINSTALL_URL = "https://raw.githubusercontent.com/NVIDIA/NemoClaw/refs/heads/main/uninstall.sh";
Expand Down Expand Up @@ -427,6 +427,9 @@ function help() {
nemoclaw debug [--quick] Collect diagnostics for bug reports
nemoclaw debug --output FILE Save diagnostics tarball for GitHub issues

${G}Shell Completion:${R}
nemoclaw completion [bash|zsh|fish] Generate shell completion script

Cleanup:
nemoclaw uninstall [flags] Run uninstall.sh (local first, curl fallback)

Expand Down Expand Up @@ -464,6 +467,7 @@ const [cmd, ...args] = process.argv.slice(2);
case "status": showStatus(); break;
case "debug": debug(args); break;
case "uninstall": uninstall(args); break;
case "completion": require("./lib/completion").run(args); break;
case "list": listSandboxes(); break;
case "--version":
case "-v": {
Expand Down
Loading
Loading