From bdf50b83c961b492e4fd268f5a15b461411a5dbb Mon Sep 17 00:00:00 2001 From: vasanth53 Date: Tue, 17 Mar 2026 14:56:06 +0530 Subject: [PATCH 1/5] feat: add shell completion for nemoclaw CLI Add completion command supporting bash, zsh, and fish shells. Completes issue #155. --- bin/nemoclaw.js | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index a8a31188a..7c7b8aa7b 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -23,9 +23,16 @@ const policies = require("./lib/policies"); const GLOBAL_COMMANDS = new Set([ "onboard", "list", "deploy", "setup", "setup-spark", "start", "stop", "status", + "completion", "help", "--help", "-h", ]); +const SANDBOX_ACTIONS = [ + "connect", "status", "logs", "policy-add", "policy-list", "destroy" +]; + +const SHELL_TYPES = ["bash", "zsh", "fish"]; + // ── Commands ───────────────────────────────────────────────────── async function onboard() { @@ -266,6 +273,99 @@ function sandboxDestroy(sandboxName) { console.log(` ✓ Sandbox '${sandboxName}' destroyed`); } +// ── Shell Completion ───────────────────────────────────────────── + +function printCompletion(shell) { + if (!shell || !SHELL_TYPES.includes(shell)) { + console.log(" Usage: nemoclaw completion "); + console.log(""); + console.log(" Generate shell completion scripts."); + console.log(""); + console.log(" Shells supported: bash, zsh, fish"); + console.log(""); + console.log(" Example:"); + console.log(" # Bash:"); + console.log(" nemoclaw completion bash >> ~/.bashrc"); + console.log(""); + console.log(" # Zsh:"); + console.log(" nemoclaw completion zsh >> ~/.zshrc"); + console.log(""); + console.log(" # Fish:"); + console.log(" nemoclaw completion fish > ~/.config/fish/completions/nemoclaw.fish"); + return; + } + + const globalCmds = Array.from(GLOBAL_COMMANDS).filter(c => !c.startsWith("-")); + let sandboxNames = []; + try { + sandboxNames = registry.listSandboxes().sandboxes.map(s => s.name); + } catch { + sandboxNames = []; + } + + const sandboxNamesStr = sandboxNames.length > 0 ? sandboxNames.join(" ") : ""; + const sandboxNamesZsh = sandboxNames.length > 0 ? sandboxNames.map(s => `"${s}"`).join(" ") : ""; + const sandboxNamesFish = sandboxNames.length > 0 ? sandboxNames.map(s => `'${s}'`).join(" ") : ""; + + if (shell === "bash") { + console.log(`_nemoclaw_completions() { + local cur prev opts + COMPREPLY=() + cur="\${COMP_WORDS[COMP_CWORD]}" + prev="\${COMP_WORDS[COMP_CWORD-1]}" + + # Global commands + opts="${globalCmds.join(" ")}" + + # Sandbox names (if previous word is a known sandbox) + if [[ " ${sandboxNamesStr} " =~ " $prev " ]]; then + opts="${SANDBOX_ACTIONS.join(" ")}" + fi + + # Also add sandbox names as possible first argument + opts="$opts ${sandboxNamesStr}" + + COMPREPLY=(\$(compgen -W "\$opts" -- \$cur)) + return 0 +} + +complete -F _nemoclaw_completions nemoclaw`); + } else if (shell === "zsh") { + console.log(`# nemoclaw zsh completion + +local -a global_cmds +global_cmds=(${globalCmds.map(c => `"${c}"`).join(" ")}) + +local -a sandbox_actions +sandbox_actions=(${SANDBOX_ACTIONS.map(a => `"${a}"`).join(" ")}) + +local -a sandbox_names +sandbox_names=(${sandboxNamesZsh}) + +_nemoclaw() { + local -a cmd + cmd=(\${words[1,CURRENT-1]}) + + # Check if first word is a sandbox name + if [[ " \${sandbox_names[@]} " =~ " \${cmd[1]} " ]]; then + _describe 'sandbox actions' sandbox_actions + else + _describe 'commands' global_cmds + _describe 'sandboxes' sandbox_names + fi +} + +compdef _nemoclaw nemoclaw`); + } else if (shell === "fish") { + console.log(`# nemoclaw fish completion + +complete -c nemoclaw -f -a "${globalCmds.join(" ")} ${sandboxNamesStr}" -n "test (count (commandline -opc)) -eq 1" + +complete -c nemoclaw -f -a "${SANDBOX_ACTIONS.join(" ")}" -n "test (count (commandline -opc)) -ge 2; and contains (commandline -opc | head -1) ${sandboxNamesFish}" +`); + } +} + // ── Help ───────────────────────────────────────────────────────── function help() { @@ -296,6 +396,9 @@ function help() { nemoclaw stop Stop all services nemoclaw status Show sandbox list and service status + Shell Completion: + nemoclaw completion Generate shell completion script + Credentials are prompted on first use, then saved securely in ~/.nemoclaw/credentials.json (mode 600). `); @@ -323,6 +426,7 @@ const [cmd, ...args] = process.argv.slice(2); case "stop": stop(); break; case "status": showStatus(); break; case "list": listSandboxes(); break; + case "completion": printCompletion(args[0]); break; default: help(); break; } return; From 41b75baf907a33ed542190f7e9549a0bc80e84b9 Mon Sep 17 00:00:00 2001 From: vasanth53 Date: Sun, 22 Mar 2026 20:04:02 +0530 Subject: [PATCH 2/5] feat(cli): add self-update command for automatic CLI updates - Add 'nemoclaw update' command to check for updates - Add 'nemoclaw update --yes' to update without prompting - Add 'nemoclaw update --force' to force update check - Detects running from source and suggests 'git pull' instead - Uses npm registry and GitHub releases for version check Closes #642 --- bin/lib/update.js | 228 ++++++++++++++++++++++++++++++++++++++++++++++ bin/nemoclaw.js | 13 +++ 2 files changed, 241 insertions(+) create mode 100644 bin/lib/update.js diff --git a/bin/lib/update.js b/bin/lib/update.js new file mode 100644 index 000000000..a4b52a8fd --- /dev/null +++ b/bin/lib/update.js @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +// +// Self-update functionality for NemoClaw CLI + +const { execSync } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const os = require("os"); +const https = require("https"); +const http = require("http"); + +const INSTALL_SCRIPT_URL = "https://raw.githubusercontent.com/NVIDIA/NemoClaw/main/install.sh"; + +/** + * Compare two semver strings. Returns true if a >= b. + * @param {string} a + * @param {string} b + * @returns {boolean} + */ +function versionGte(a, b) { + const aParts = a.replace(/^v/, "").split(".").map(n => parseInt(n, 10) || 0); + const bParts = b.replace(/^v/, "").split(".").map(n => parseInt(n, 10) || 0); + for (let i = 0; i < 3; i++) { + const ai = aParts[i] || 0; + const bi = bParts[i] || 0; + if (ai > bi) return true; + if (ai < bi) return false; + } + return true; +} + +/** + * Fetch content from a URL using Node.js built-in http/https. + * @param {string} url + * @returns {Promise} + */ +function fetchUrl(url) { + return new Promise((resolve, reject) => { + const lib = url.startsWith("https") ? https : http; + const req = lib.get(url, { timeout: 10000 }, (res) => { + if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + fetchUrl(res.headers.location).then(resolve).catch(reject); + return; + } + if (res.statusCode !== 200) { + reject(new Error(`HTTP ${res.statusCode}`)); + return; + } + let data = ""; + res.on("data", chunk => data += chunk); + res.on("end", () => resolve(data)); + }); + req.on("error", reject); + req.on("timeout", () => { + req.destroy(); + reject(new Error("Request timed out")); + }); + }); +} + +/** + * Get the current installed version of NemoClaw. + * @returns {string} + */ +function getCurrentVersion() { + try { + const pkg = require("../../package.json"); + return pkg.version || "0.0.0"; + } catch { + return "0.0.0"; + } +} + +/** + * Get the current CLI path to determine if running from source. + * @returns {string|null} + */ +function getCurrentCliPath() { + try { + return execSync("which nemoclaw 2>/dev/null", { encoding: "utf-8" }).trim() || null; + } catch { + return null; + } +} + +/** + * Get the latest version from GitHub releases. + * @returns {Promise} + */ +async function getLatestVersion() { + try { + const data = await fetchUrl("https://api.github.com/repos/NVIDIA/NemoClaw/releases/latest"); + const release = JSON.parse(data); + return release.tag_name || release.name || "0.0.0"; + } catch { + return getCurrentVersion(); + } +} + +/** + * Get the latest version from npm. + * @returns {Promise} + */ +async function getLatestNpmVersion() { + try { + const data = await fetchUrl("https://registry.npmjs.org/nemoclaw/latest"); + const pkg = JSON.parse(data); + return pkg.version || "0.0.0"; + } catch { + return getCurrentVersion(); + } +} + +/** + * Check if an update is available. + * @returns {Promise<{current: string, latest: string, updateAvailable: boolean, runningFromSource: boolean}>} + */ +async function checkForUpdate() { + const cliPath = getCurrentCliPath(); + const runningFromSource = !cliPath || cliPath.includes("node_modules/.bin"); + + let current = getCurrentVersion(); + if (runningFromSource && cliPath) { + try { + const output = execSync(`"${cliPath}" --version 2>/dev/null`, { encoding: "utf-8" }); + const match = output.match(/(\d+\.\d+\.\d+)/); + if (match) current = match[1]; + } catch {} + } + + const latestNpm = await getLatestNpmVersion(); + const latestGithub = await getLatestVersion(); + + const latest = versionGte(latestNpm, latestGithub) ? latestNpm : latestGithub; + const updateAvailable = !versionGte(current, latest); + + return { current, latest, updateAvailable, runningFromSource }; +} + +/** + * Run the update. Downloads and executes the install script. + * @param {object} opts + * @param {boolean} opts.force - Force update even if already up to date + * @param {boolean} opts.yes - Skip confirmation prompt + * @returns {Promise} + */ +async function runUpdate(opts = {}) { + const { force = false, yes = false } = opts; + + console.log(""); + console.log(" Checking for updates..."); + console.log(""); + + const { current, latest, updateAvailable, runningFromSource } = await checkForUpdate(); + + console.log(` Current version: ${current}`); + console.log(` Latest version: ${latest}`); + + if (!force && !updateAvailable) { + console.log(""); + console.log(" You are running the latest version."); + return true; + } + + if (!yes) { + console.log(""); + console.log(" A new version is available!"); + console.log(""); + if (runningFromSource) { + console.log(" Since you're running from source, use 'git pull' to update:"); + console.log(" cd /path/to/NemoClaw && git pull"); + } else { + console.log(" Run 'nemoclaw update --yes' to update without prompting."); + } + return false; + } + + if (runningFromSource) { + console.log(""); + console.log(" Since you're running from source, use 'git pull' to update:"); + console.log(" cd /path/to/NemoClaw && git pull"); + return false; + } + + console.log(""); + console.log(" Updating NemoClaw..."); + console.log(""); + + try { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-update-")); + const scriptPath = path.join(tmpDir, "install.sh"); + + console.log(" Downloading installer..."); + const scriptContent = await fetchUrl(INSTALL_SCRIPT_URL); + fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 }); + + console.log(" Running installer..."); + execSync(`bash "${scriptPath}"`, { + stdio: "inherit", + cwd: tmpDir + }); + + fs.unlinkSync(scriptPath); + fs.rmdirSync(tmpDir); + + const newVersion = getCurrentVersion(); + console.log(""); + console.log(` Successfully updated to v${newVersion}`); + return true; + } catch (err) { + console.error(""); + console.error(` Update failed: ${err.message}`); + console.error(""); + console.error(" You can also update manually with:"); + console.error(" npm install -g nemoclaw"); + return false; + } +} + +module.exports = { + getCurrentVersion, + getCurrentCliPath, + getLatestVersion, + getLatestNpmVersion, + checkForUpdate, + runUpdate, +}; diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 7c7b8aa7b..90aebc7b0 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -24,6 +24,7 @@ const GLOBAL_COMMANDS = new Set([ "onboard", "list", "deploy", "setup", "setup-spark", "start", "stop", "status", "completion", + "update", "help", "--help", "-h", ]); @@ -366,6 +367,13 @@ complete -c nemoclaw -f -a "${SANDBOX_ACTIONS.join(" ")}" -n "test (count (comma } } +// ── Self-Update ───────────────────────────────────────────────── + +async function update(opts) { + const { runUpdate } = require("./lib/update"); + await runUpdate(opts); +} + // ── Help ───────────────────────────────────────────────────────── function help() { @@ -399,6 +407,10 @@ function help() { Shell Completion: nemoclaw completion Generate shell completion script + Update: + nemoclaw update Check for updates + nemoclaw update --yes Update to latest version + Credentials are prompted on first use, then saved securely in ~/.nemoclaw/credentials.json (mode 600). `); @@ -427,6 +439,7 @@ const [cmd, ...args] = process.argv.slice(2); case "status": showStatus(); break; case "list": listSandboxes(); break; case "completion": printCompletion(args[0]); break; + case "update": await update({ force: args.includes("--force"), yes: args.includes("--yes") }); break; default: help(); break; } return; From 9a97abedcf5e7afa33fb6a3fe2a493148e49e857 Mon Sep 17 00:00:00 2001 From: vasanth53 Date: Sun, 22 Mar 2026 20:09:54 +0530 Subject: [PATCH 3/5] chore: remove shell completion (already in main) --- bin/nemoclaw.js | 100 ------------------------------------------------ 1 file changed, 100 deletions(-) diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 90aebc7b0..43da0719c 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -23,7 +23,6 @@ const policies = require("./lib/policies"); const GLOBAL_COMMANDS = new Set([ "onboard", "list", "deploy", "setup", "setup-spark", "start", "stop", "status", - "completion", "update", "help", "--help", "-h", ]); @@ -32,8 +31,6 @@ const SANDBOX_ACTIONS = [ "connect", "status", "logs", "policy-add", "policy-list", "destroy" ]; -const SHELL_TYPES = ["bash", "zsh", "fish"]; - // ── Commands ───────────────────────────────────────────────────── async function onboard() { @@ -274,99 +271,6 @@ function sandboxDestroy(sandboxName) { console.log(` ✓ Sandbox '${sandboxName}' destroyed`); } -// ── Shell Completion ───────────────────────────────────────────── - -function printCompletion(shell) { - if (!shell || !SHELL_TYPES.includes(shell)) { - console.log(" Usage: nemoclaw completion "); - console.log(""); - console.log(" Generate shell completion scripts."); - console.log(""); - console.log(" Shells supported: bash, zsh, fish"); - console.log(""); - console.log(" Example:"); - console.log(" # Bash:"); - console.log(" nemoclaw completion bash >> ~/.bashrc"); - console.log(""); - console.log(" # Zsh:"); - console.log(" nemoclaw completion zsh >> ~/.zshrc"); - console.log(""); - console.log(" # Fish:"); - console.log(" nemoclaw completion fish > ~/.config/fish/completions/nemoclaw.fish"); - return; - } - - const globalCmds = Array.from(GLOBAL_COMMANDS).filter(c => !c.startsWith("-")); - let sandboxNames = []; - try { - sandboxNames = registry.listSandboxes().sandboxes.map(s => s.name); - } catch { - sandboxNames = []; - } - - const sandboxNamesStr = sandboxNames.length > 0 ? sandboxNames.join(" ") : ""; - const sandboxNamesZsh = sandboxNames.length > 0 ? sandboxNames.map(s => `"${s}"`).join(" ") : ""; - const sandboxNamesFish = sandboxNames.length > 0 ? sandboxNames.map(s => `'${s}'`).join(" ") : ""; - - if (shell === "bash") { - console.log(`_nemoclaw_completions() { - local cur prev opts - COMPREPLY=() - cur="\${COMP_WORDS[COMP_CWORD]}" - prev="\${COMP_WORDS[COMP_CWORD-1]}" - - # Global commands - opts="${globalCmds.join(" ")}" - - # Sandbox names (if previous word is a known sandbox) - if [[ " ${sandboxNamesStr} " =~ " $prev " ]]; then - opts="${SANDBOX_ACTIONS.join(" ")}" - fi - - # Also add sandbox names as possible first argument - opts="$opts ${sandboxNamesStr}" - - COMPREPLY=(\$(compgen -W "\$opts" -- \$cur)) - return 0 -} - -complete -F _nemoclaw_completions nemoclaw`); - } else if (shell === "zsh") { - console.log(`# nemoclaw zsh completion - -local -a global_cmds -global_cmds=(${globalCmds.map(c => `"${c}"`).join(" ")}) - -local -a sandbox_actions -sandbox_actions=(${SANDBOX_ACTIONS.map(a => `"${a}"`).join(" ")}) - -local -a sandbox_names -sandbox_names=(${sandboxNamesZsh}) - -_nemoclaw() { - local -a cmd - cmd=(\${words[1,CURRENT-1]}) - - # Check if first word is a sandbox name - if [[ " \${sandbox_names[@]} " =~ " \${cmd[1]} " ]]; then - _describe 'sandbox actions' sandbox_actions - else - _describe 'commands' global_cmds - _describe 'sandboxes' sandbox_names - fi -} - -compdef _nemoclaw nemoclaw`); - } else if (shell === "fish") { - console.log(`# nemoclaw fish completion - -complete -c nemoclaw -f -a "${globalCmds.join(" ")} ${sandboxNamesStr}" -n "test (count (commandline -opc)) -eq 1" - -complete -c nemoclaw -f -a "${SANDBOX_ACTIONS.join(" ")}" -n "test (count (commandline -opc)) -ge 2; and contains (commandline -opc | head -1) ${sandboxNamesFish}" -`); - } -} - // ── Self-Update ───────────────────────────────────────────────── async function update(opts) { @@ -404,9 +308,6 @@ function help() { nemoclaw stop Stop all services nemoclaw status Show sandbox list and service status - Shell Completion: - nemoclaw completion Generate shell completion script - Update: nemoclaw update Check for updates nemoclaw update --yes Update to latest version @@ -438,7 +339,6 @@ const [cmd, ...args] = process.argv.slice(2); case "stop": stop(); break; case "status": showStatus(); break; case "list": listSandboxes(); break; - case "completion": printCompletion(args[0]); break; case "update": await update({ force: args.includes("--force"), yes: args.includes("--yes") }); break; default: help(); break; } From dfddeda46c6f19bdbae391d87f3a285e685e4ade Mon Sep 17 00:00:00 2001 From: vasanth53 Date: Sun, 22 Mar 2026 21:04:53 +0530 Subject: [PATCH 4/5] fix: address PR review comments for self-update command - versionGte: strip prerelease/build suffixes before parsing - fetchUrl: add User-Agent header and 5-redirect limit - checkForUpdate: fix source detection (only when cliPath is falsy) - runUpdate: add SHA256 hash display, use fs.rmSync with recursive/force - getCurrentVersion: add clearCache param to read fresh version - bin/nemoclaw.js: remove unused SANDBOX_ACTIONS and SHELL_TYPES - help text: fix Update section formatting, add --force flag docs --- bin/lib/update.js | 47 ++++++++++++++++++++++++++++++++++------------- bin/nemoclaw.js | 9 ++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/bin/lib/update.js b/bin/lib/update.js index a4b52a8fd..7d4f75409 100644 --- a/bin/lib/update.js +++ b/bin/lib/update.js @@ -19,8 +19,9 @@ const INSTALL_SCRIPT_URL = "https://raw.githubusercontent.com/NVIDIA/NemoClaw/ma * @returns {boolean} */ function versionGte(a, b) { - const aParts = a.replace(/^v/, "").split(".").map(n => parseInt(n, 10) || 0); - const bParts = b.replace(/^v/, "").split(".").map(n => parseInt(n, 10) || 0); + const normalize = (v) => v.replace(/^v/, "").split(/[-+]/)[0].split(".").map(n => parseInt(n, 10) || 0); + const aParts = normalize(a); + const bParts = normalize(b); for (let i = 0; i < 3; i++) { const ai = aParts[i] || 0; const bi = bParts[i] || 0; @@ -35,12 +36,19 @@ function versionGte(a, b) { * @param {string} url * @returns {Promise} */ -function fetchUrl(url) { +function fetchUrl(url, redirectCount = 0) { return new Promise((resolve, reject) => { const lib = url.startsWith("https") ? https : http; - const req = lib.get(url, { timeout: 10000 }, (res) => { + const req = lib.get(url, { + timeout: 10000, + headers: { "User-Agent": "NemoClaw" } + }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - fetchUrl(res.headers.location).then(resolve).catch(reject); + if (redirectCount >= 5) { + reject(new Error("Too many redirects")); + return; + } + fetchUrl(res.headers.location, redirectCount + 1).then(resolve).catch(reject); return; } if (res.statusCode !== 200) { @@ -61,10 +69,14 @@ function fetchUrl(url) { /** * Get the current installed version of NemoClaw. + * @param {boolean} clearCache - Clear Node's require cache before reading * @returns {string} */ -function getCurrentVersion() { +function getCurrentVersion(clearCache = false) { try { + if (clearCache) { + delete require.cache[require.resolve("../../package.json")]; + } const pkg = require("../../package.json"); return pkg.version || "0.0.0"; } catch { @@ -118,7 +130,7 @@ async function getLatestNpmVersion() { */ async function checkForUpdate() { const cliPath = getCurrentCliPath(); - const runningFromSource = !cliPath || cliPath.includes("node_modules/.bin"); + const runningFromSource = !cliPath; let current = getCurrentVersion(); if (runningFromSource && cliPath) { @@ -187,12 +199,18 @@ async function runUpdate(opts = {}) { console.log(" Updating NemoClaw..."); console.log(""); + let tmpDir; try { - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-update-")); - const scriptPath = path.join(tmpDir, "install.sh"); + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-update-")); console.log(" Downloading installer..."); const scriptContent = await fetchUrl(INSTALL_SCRIPT_URL); + + const crypto = require("crypto"); + const hash = crypto.createHash("sha256").update(scriptContent).digest("hex"); + console.log(` Script SHA256: ${hash.substring(0, 16)}...`); + + const scriptPath = path.join(tmpDir, "install.sh"); fs.writeFileSync(scriptPath, scriptContent, { mode: 0o755 }); console.log(" Running installer..."); @@ -201,10 +219,7 @@ async function runUpdate(opts = {}) { cwd: tmpDir }); - fs.unlinkSync(scriptPath); - fs.rmdirSync(tmpDir); - - const newVersion = getCurrentVersion(); + const newVersion = getCurrentVersion(true); console.log(""); console.log(` Successfully updated to v${newVersion}`); return true; @@ -215,6 +230,12 @@ async function runUpdate(opts = {}) { console.error(" You can also update manually with:"); console.error(" npm install -g nemoclaw"); return false; + } finally { + try { + if (tmpDir && fs.existsSync(tmpDir)) { + fs.rmSync(tmpDir, { recursive: true, force: true }); + } + } catch {} } } diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 7cf853b12..2254a40b3 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -41,12 +41,6 @@ const GLOBAL_COMMANDS = new Set([ "help", "--help", "-h", "--version", "-v", ]); -const SANDBOX_ACTIONS = [ - "connect", "status", "logs", "policy-add", "policy-list", "destroy" -]; - -const SHELL_TYPES = ["bash", "zsh", "fish"]; - const REMOTE_UNINSTALL_URL = "https://raw.githubusercontent.com/NVIDIA/NemoClaw/refs/heads/main/uninstall.sh"; function resolveUninstallScript() { @@ -435,9 +429,10 @@ function help() { nemoclaw stop Stop all services nemoclaw status Show sandbox list and service status - Update: + ${G}Update:${R} nemoclaw update Check for updates nemoclaw update --yes Update to latest version + nemoclaw update --force Bypass update availability checks Troubleshooting: nemoclaw debug [--quick] Collect diagnostics for bug reports From 29f4f9c39c1ece1a82117413a9234dc9b98bbffa Mon Sep 17 00:00:00 2001 From: vasanth53 Date: Sun, 22 Mar 2026 21:19:09 +0530 Subject: [PATCH 5/5] fix: address remaining PR comments - Fix --force message when no new version (show 'reinstalling current version') - Remove unused 'completion' from GLOBAL_COMMANDS (no handler exists) --- bin/lib/update.js | 11 +++++++++-- bin/nemoclaw.js | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/lib/update.js b/bin/lib/update.js index 7d4f75409..a1b0f20b6 100644 --- a/bin/lib/update.js +++ b/bin/lib/update.js @@ -177,13 +177,20 @@ async function runUpdate(opts = {}) { if (!yes) { console.log(""); - console.log(" A new version is available!"); + if (updateAvailable) { + console.log(" A new version is available!"); + } else if (force) { + console.log(" Reinstalling current version (--force was provided)."); + } else { + console.log(" You are running the latest version."); + } console.log(""); if (runningFromSource) { console.log(" Since you're running from source, use 'git pull' to update:"); console.log(" cd /path/to/NemoClaw && git pull"); } else { - console.log(" Run 'nemoclaw update --yes' to update without prompting."); + const cmd = `nemoclaw update --yes${force ? " --force" : ""}`; + console.log(` Run '${cmd}' to update without prompting.`); } return false; } diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 2254a40b3..ff20ed549 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -36,7 +36,6 @@ const policies = require("./lib/policies"); const GLOBAL_COMMANDS = new Set([ "onboard", "list", "deploy", "setup", "setup-spark", "start", "stop", "status", "debug", "uninstall", - "completion", "update", "help", "--help", "-h", "--version", "-v", ]);