-
Notifications
You must be signed in to change notification settings - Fork 2k
feat(cli): add gateway-token command to retrieve auth token #944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
b6c8f6c
b926994
77477f4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -22,7 +22,11 @@ const YW = _useColor ? "\x1b[1;33m" : ""; | |||||||||||||||||
|
|
||||||||||||||||||
| const { ROOT, SCRIPTS, run, runCapture: _runCapture, runInteractive, shellQuote, validateName } = require("./lib/runner"); | ||||||||||||||||||
| const { resolveOpenshell } = require("./lib/resolve-openshell"); | ||||||||||||||||||
| const { startGatewayForRecovery } = require("./lib/onboard"); | ||||||||||||||||||
| const { | ||||||||||||||||||
| buildControlUiUrls, | ||||||||||||||||||
| fetchGatewayAuthTokenFromSandbox, | ||||||||||||||||||
| startGatewayForRecovery, | ||||||||||||||||||
| } = require("./lib/onboard"); | ||||||||||||||||||
| const { | ||||||||||||||||||
| ensureApiKey, | ||||||||||||||||||
| ensureGithubToken, | ||||||||||||||||||
|
|
@@ -647,6 +651,52 @@ function sandboxLogs(sandboxName, follow) { | |||||||||||||||||
| runOpenshell(args); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function sandboxGatewayToken(sandboxName) { | ||||||||||||||||||
| const token = fetchGatewayAuthTokenFromSandbox(sandboxName); | ||||||||||||||||||
| if (!token) { | ||||||||||||||||||
| console.error(` ${_RD}Error${R}: Could not retrieve gateway auth token from sandbox '${sandboxName}'.`); | ||||||||||||||||||
| console.error(` Make sure the sandbox is running and accessible.`); | ||||||||||||||||||
| process.exit(1); | ||||||||||||||||||
| } | ||||||||||||||||||
| // Print warning to stderr before token to stdout | ||||||||||||||||||
| console.error(` ${YW}⚠️ Treat this token like a password. Do not share it or include it in logs/screencasts.${R}`); | ||||||||||||||||||
| console.log(token); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| async function sandboxOpen(sandboxName) { | ||||||||||||||||||
| await ensureLiveSandboxOrExit(sandboxName); | ||||||||||||||||||
| // Ensure port forward is alive before opening | ||||||||||||||||||
| runOpenshell(["forward", "start", "--background", "18789", sandboxName], { ignoreError: true }); | ||||||||||||||||||
|
Comment on lines
+694
to
+695
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stop any existing Control UI forward before reusing port
🛠️ Suggested fix async function sandboxOpen(sandboxName) {
await ensureLiveSandboxOrExit(sandboxName);
// Start port forward and wait for it to be ready
+ runOpenshell(["forward", "stop", "18789"], { ignoreError: true });
runOpenshell(["forward", "start", "--background", "18789", sandboxName], { ignoreError: true });
await waitForPort(18789, 10000);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
|
|
||||||||||||||||||
| const token = fetchGatewayAuthTokenFromSandbox(sandboxName); | ||||||||||||||||||
| if (!token) { | ||||||||||||||||||
| console.error(` ${_RD}Error${R}: Could not retrieve gateway auth token from sandbox '${sandboxName}'.`); | ||||||||||||||||||
| console.error(` Make sure the sandbox is running and accessible.`); | ||||||||||||||||||
| process.exit(1); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const urls = buildControlUiUrls(token); | ||||||||||||||||||
| const url = urls[0]; // Use the first URL (localhost) | ||||||||||||||||||
|
|
||||||||||||||||||
| // Log without token to avoid exposure in terminal history/screencasts | ||||||||||||||||||
| const safeUrl = url.replace(/#.*$/, ""); | ||||||||||||||||||
| console.log(` Opening Control UI at ${safeUrl} ...`); | ||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
|
||||||||||||||||||
| // Open URL in default browser (platform-appropriate) | ||||||||||||||||||
| const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open"; | ||||||||||||||||||
| try { | ||||||||||||||||||
| const child = require("child_process").spawn(opener, [url], { | ||||||||||||||||||
| stdio: "ignore", | ||||||||||||||||||
| detached: true, | ||||||||||||||||||
| }); | ||||||||||||||||||
| child.unref(); | ||||||||||||||||||
| } catch (_e) { | ||||||||||||||||||
| console.error(` ${_RD}Error${R}: Could not open browser. Please open this URL manually:`); | ||||||||||||||||||
| console.error(` ${url}`); | ||||||||||||||||||
| process.exit(1); | ||||||||||||||||||
|
Comment on lines
+712
to
+726
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No, spawn("start", [url]) does not work on Windows because start is a cmd.exe builtin command, not a standalone executable. There is no start.exe in the PATH, so spawn (which does not use a shell by default) fails with ENOENT. To use start, you must either spawn cmd.exe explicitly with spawn("cmd.exe", ["/c", "start", "", url]) (the empty quoted string "" is needed as a window title placeholder before the URL if it has spaces), use spawn("start", [url], {shell: true}), or use exec which always spawns a shell. Command-launch failures like ENOENT are reported asynchronously via the ChildProcess error event, not thrown synchronously. The spawn function returns a ChildProcess instance immediately (even on failure to launch), and errors such as inability to find the executable are emitted on the 'error' event. Unhandled errors will crash the process with an uncaught exception, but they are not synchronous throws. Citations:
🏁 Script executed: # First, let's find and read the bin/nemoclaw.js file
fd nemoclaw.js --type fRepository: NVIDIA/NemoClaw Length of output: 74 🏁 Script executed: # Read lines 685-696 and some surrounding context to understand the function
sed -n '670,710p' bin/nemoclaw.js | cat -nRepository: NVIDIA/NemoClaw Length of output: 1777 🏁 Script executed: # Search for where this function is defined and what the 'url' parameter contains
rg -B 20 'const opener = process.platform' bin/nemoclaw.jsRepository: NVIDIA/NemoClaw Length of output: 1023 Use The code has three related issues:
🤖 Prompt for AI Agents |
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| async function sandboxPolicyAdd(sandboxName) { | ||||||||||||||||||
| const allPresets = policies.listPresets(); | ||||||||||||||||||
| const applied = policies.getAppliedPresets(sandboxName); | ||||||||||||||||||
|
|
@@ -724,6 +774,8 @@ function help() { | |||||||||||||||||
| nemoclaw <name> connect Shell into a running sandbox | ||||||||||||||||||
| nemoclaw <name> status Sandbox health + NIM status | ||||||||||||||||||
| nemoclaw <name> logs ${D}[--follow]${R} Stream sandbox logs | ||||||||||||||||||
| nemoclaw <name> open Open Control UI in browser with auth token | ||||||||||||||||||
| nemoclaw <name> gateway-token Print the gateway auth token ${D}(for scripts)${R} | ||||||||||||||||||
| nemoclaw <name> destroy Stop NIM + delete sandbox ${D}(--yes to skip prompt)${R} | ||||||||||||||||||
|
|
||||||||||||||||||
| ${G}Policy Presets:${R} | ||||||||||||||||||
|
|
@@ -800,15 +852,17 @@ const [cmd, ...args] = process.argv.slice(2); | |||||||||||||||||
| const actionArgs = args.slice(1); | ||||||||||||||||||
|
|
||||||||||||||||||
| switch (action) { | ||||||||||||||||||
| case "connect": await sandboxConnect(cmd); break; | ||||||||||||||||||
| case "status": await sandboxStatus(cmd); break; | ||||||||||||||||||
| case "logs": sandboxLogs(cmd, actionArgs.includes("--follow")); break; | ||||||||||||||||||
| case "policy-add": await sandboxPolicyAdd(cmd); break; | ||||||||||||||||||
| case "policy-list": sandboxPolicyList(cmd); break; | ||||||||||||||||||
| case "destroy": await sandboxDestroy(cmd, actionArgs); break; | ||||||||||||||||||
| case "connect": await sandboxConnect(cmd); break; | ||||||||||||||||||
| case "status": await sandboxStatus(cmd); break; | ||||||||||||||||||
| case "logs": sandboxLogs(cmd, actionArgs.includes("--follow")); break; | ||||||||||||||||||
| case "policy-add": await sandboxPolicyAdd(cmd); break; | ||||||||||||||||||
| case "policy-list": sandboxPolicyList(cmd); break; | ||||||||||||||||||
| case "gateway-token": sandboxGatewayToken(cmd); break; | ||||||||||||||||||
| case "open": await sandboxOpen(cmd); break; | ||||||||||||||||||
| case "destroy": await sandboxDestroy(cmd, actionArgs); break; | ||||||||||||||||||
| default: | ||||||||||||||||||
| console.error(` Unknown action: ${action}`); | ||||||||||||||||||
| console.error(` Valid actions: connect, status, logs, policy-add, policy-list, destroy`); | ||||||||||||||||||
| console.error(` Valid actions: connect, status, logs, policy-add, policy-list, gateway-token, open, destroy`); | ||||||||||||||||||
| process.exit(1); | ||||||||||||||||||
| } | ||||||||||||||||||
| return; | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Run the same live-sandbox reconciliation before printing a token.
Line 655 goes straight to
fetchGatewayAuthTokenFromSandbox(), sogateway-tokenskips the stale-registry cleanup and gateway recovery thatconnectandopenalready use. Because that helper returnsnullfor both transport failures and missing token, this command can fail after a gateway restart even when the sandbox is still recoverable.Suggested fix
Also applies to: 860-860
🤖 Prompt for AI Agents