Skip to content
Open
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
5 changes: 3 additions & 2 deletions bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const fs = require("fs");
const os = require("os");
const path = require("path");
const { spawn, spawnSync } = require("child_process");
const { ROOT, SCRIPTS, run, runCapture, shellQuote } = require("./runner");
const { ROOT, SCRIPTS, run, runCapture, shellQuote, getDashboardUrl } = require("./runner");
const {
getDefaultOllamaModel,
getBootstrapOllamaModelOptions,
Expand Down Expand Up @@ -2233,8 +2233,9 @@ function printDashboard(sandboxName, model, provider, nimContainer = null) {
const token = fetchGatewayAuthTokenFromSandbox(sandboxName);

console.log("");
const dashboardUrl = getDashboardUrl(sandboxName);
console.log(` ${"─".repeat(50)}`);
// console.log(` Dashboard http://localhost:18789/`);
console.log(` Dashboard ${dashboardUrl}`);
console.log(` Sandbox ${sandboxName} (Landlock + seccomp + netns)`);
console.log(` Model ${model} (${providerLabel})`);
console.log(` NIM ${nimLabel}`);
Expand Down
36 changes: 35 additions & 1 deletion bin/lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,38 @@ function validateName(name, label = "name") {
return name;
}

module.exports = { ROOT, SCRIPTS, run, runCapture, runInteractive, shellQuote, validateName };
/**
* Read the gateway auth token from a sandbox's openclaw.json.
* Returns the token string, or "" if it cannot be read.
*/
function readSandboxToken(sandboxName) {
const tmpDir = require("os").tmpdir();
const destDir = path.join(tmpDir, `nemoclaw-token-${Date.now()}`);
try {
require("fs").mkdirSync(destDir, { recursive: true });
execSync(
`openshell sandbox download ${shellQuote(sandboxName)} /sandbox/.openclaw/openclaw.json ${shellQuote(destDir)}`,
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], cwd: ROOT, timeout: 15000 }
);
const configPath = path.join(destDir, "openclaw.json");
const cfg = JSON.parse(require("fs").readFileSync(configPath, "utf-8"));
const token = (cfg.gateway && cfg.gateway.auth && cfg.gateway.auth.token) || "";
return /^[0-9a-f]{64}$/.test(token) ? token : "";
} catch {
return "";
} finally {
try { require("fs").rmSync(destDir, { recursive: true, force: true }); } catch {}
}
}

/**
* Build the full dashboard URL with auth token for a sandbox.
* Returns the URL string, or the bare URL if the token cannot be read.
*/
function getDashboardUrl(sandboxName, port = 18789) {
const token = readSandboxToken(sandboxName);
const base = `http://127.0.0.1:${port}/`;
return token ? `${base}#token=${token}` : base;
}

module.exports = { ROOT, SCRIPTS, run, runCapture, runInteractive, shellQuote, validateName, readSandboxToken, getDashboardUrl };
5 changes: 4 additions & 1 deletion bin/nemoclaw.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const R = _useColor ? "\x1b[0m" : "";
const _RD = _useColor ? "\x1b[1;31m" : "";
const YW = _useColor ? "\x1b[1;33m" : "";

const { ROOT, SCRIPTS, run, runCapture, runInteractive, shellQuote, validateName } = require("./lib/runner");
const { ROOT, SCRIPTS, run, runCapture, runInteractive, shellQuote, validateName, getDashboardUrl } = require("./lib/runner");
const {
ensureApiKey,
ensureGithubToken,
Expand Down Expand Up @@ -328,6 +328,9 @@ function sandboxStatus(sandboxName) {
if (nimStat.running) {
console.log(` Healthy: ${nimStat.healthy ? "yes" : "no"}`);
}

const dashboardUrl = getDashboardUrl(sandboxName);
console.log(` Dashboard: ${dashboardUrl}`);
console.log("");
}

Expand Down
25 changes: 25 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,30 @@ print_done() {
local elapsed=$((SECONDS - _INSTALL_START))
local sandbox_name
sandbox_name="$(resolve_default_sandbox_name)"

# Read the gateway auth token from the sandbox's openclaw.json
local token=""
if command_exists openshell && [[ -n "$sandbox_name" ]]; then
local _token_dir
_token_dir="$(mktemp -d)"
if openshell sandbox download "$sandbox_name" /sandbox/.openclaw/openclaw.json "$_token_dir" >/dev/null 2>&1 \
&& [[ -f "$_token_dir/openclaw.json" ]]; then
token="$(python3 -c "
import json, sys
try:
cfg = json.load(open('${_token_dir}/openclaw.json'))
print(cfg.get('gateway',{}).get('auth',{}).get('token',''))
except Exception:
pass
" 2>/dev/null | grep -xE '[0-9a-f]{64}' | head -1)" || token=""
fi
command -v rm &>/dev/null && rm -rf "$_token_dir" 2>/dev/null || true
fi
local dashboard_url="http://127.0.0.1:18789/"
if [[ -n "$token" ]]; then
dashboard_url="http://127.0.0.1:18789/#token=${token}"
fi

info "=== Installation complete ==="
printf "\n"
printf " ${C_GREEN}${C_BOLD}NemoClaw${C_RESET} ${C_DIM}(%ss)${C_RESET}\n" "$elapsed"
Expand All @@ -134,6 +158,7 @@ print_done() {
printf " ${C_DIM}Sandbox in, break things, and tell us what you find.${C_RESET}\n"
printf "\n"
printf " ${C_GREEN}Next:${C_RESET}\n"
printf " ${C_BOLD}Dashboard${C_RESET} ${C_DIM}%s${C_RESET}\n" "$dashboard_url"
printf " %s$%s nemoclaw %s connect\n" "$C_GREEN" "$C_RESET" "$sandbox_name"
printf " %ssandbox@%s$%s openclaw tui\n" "$C_GREEN" "$sandbox_name" "$C_RESET"
printf "\n"
Expand Down