diff --git a/README.md b/README.md index 63a5e22fb2..1e3c6ab98b 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,18 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Linux | Ubuntu 22.04 LTS or later | | Node.js | 20 or later | | npm | 10 or later | -| Docker | Installed and running | +| Container runtime | Supported runtime installed and running | | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | +#### Container Runtime Support + +| Platform | Supported runtimes | Notes | +|----------|--------------------|-------| +| Linux | Docker | Primary supported path today | +| macOS (Apple Silicon) | Colima, Docker Desktop | Recommended runtimes for supported macOS setups | +| macOS | Podman | Not supported yet. NemoClaw currently depends on OpenShell support for Podman on macOS. | +| Windows WSL | Docker Desktop (WSL backend) | Supported target path | + ### Install NemoClaw and Onboard OpenClaw Agent Download and run the installer script. @@ -144,6 +153,8 @@ Inference requests from the agent never leave the sandbox directly. OpenShell in Get an API key from [build.nvidia.com](https://build.nvidia.com). The `nemoclaw onboard` command prompts for this key during setup. +Local inference options such as Ollama and vLLM are still experimental. On macOS, they also depend on OpenShell host-routing support in addition to the local service itself being reachable on the host. + --- ## Protection Layers diff --git a/bin/lib/credentials.js b/bin/lib/credentials.js index 1ac405bed3..b48c73c4ad 100644 --- a/bin/lib/credentials.js +++ b/bin/lib/credentials.js @@ -36,6 +36,14 @@ function prompt(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stderr }); rl.question(question, (answer) => { rl.close(); + if (!process.stdin.isTTY) { + if (typeof process.stdin.pause === "function") { + process.stdin.pause(); + } + if (typeof process.stdin.unref === "function") { + process.stdin.unref(); + } + } resolve(answer.trim()); }); }); diff --git a/bin/lib/inference-config.js b/bin/lib/inference-config.js new file mode 100644 index 0000000000..61fa90ea39 --- /dev/null +++ b/bin/lib/inference-config.js @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const INFERENCE_ROUTE_URL = "https://inference.local/v1"; +const DEFAULT_CLOUD_MODEL = "nvidia/nemotron-3-super-120b-a12b"; +const CLOUD_MODEL_OPTIONS = [ + { id: "nvidia/nemotron-3-super-120b-a12b", label: "Nemotron 3 Super 120B" }, + { id: "moonshotai/kimi-k2.5", label: "Kimi K2.5" }, + { id: "z-ai/glm5", label: "GLM-5" }, + { id: "minimaxai/minimax-m2.5", label: "MiniMax M2.5" }, + { id: "qwen/qwen3.5-397b-a17b", label: "Qwen3.5 397B A17B" }, + { id: "openai/gpt-oss-120b", label: "GPT-OSS 120B" }, +]; +const DEFAULT_ROUTE_PROFILE = "inference-local"; +const DEFAULT_ROUTE_CREDENTIAL_ENV = "OPENAI_API_KEY"; +const MANAGED_PROVIDER_ID = "inference"; +const { DEFAULT_OLLAMA_MODEL } = require("./local-inference"); + +function getProviderSelectionConfig(provider, model) { + switch (provider) { + case "nvidia-nim": + return { + endpointType: "custom", + endpointUrl: INFERENCE_ROUTE_URL, + ncpPartner: null, + model: model || DEFAULT_CLOUD_MODEL, + profile: DEFAULT_ROUTE_PROFILE, + credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV, + provider, + providerLabel: "NVIDIA Cloud API", + }; + case "vllm-local": + return { + endpointType: "custom", + endpointUrl: INFERENCE_ROUTE_URL, + ncpPartner: null, + model: model || "vllm-local", + profile: DEFAULT_ROUTE_PROFILE, + credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV, + provider, + providerLabel: "Local vLLM", + }; + case "ollama-local": + return { + endpointType: "custom", + endpointUrl: INFERENCE_ROUTE_URL, + ncpPartner: null, + model: model || DEFAULT_OLLAMA_MODEL, + profile: DEFAULT_ROUTE_PROFILE, + credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV, + provider, + providerLabel: "Local Ollama", + }; + default: + return null; + } +} + +function getOpenClawPrimaryModel(provider, model) { + const resolvedModel = + model || (provider === "ollama-local" ? DEFAULT_OLLAMA_MODEL : DEFAULT_CLOUD_MODEL); + return resolvedModel ? `${MANAGED_PROVIDER_ID}/${resolvedModel}` : null; +} + +module.exports = { + CLOUD_MODEL_OPTIONS, + DEFAULT_CLOUD_MODEL, + DEFAULT_OLLAMA_MODEL, + DEFAULT_ROUTE_CREDENTIAL_ENV, + DEFAULT_ROUTE_PROFILE, + INFERENCE_ROUTE_URL, + MANAGED_PROVIDER_ID, + getOpenClawPrimaryModel, + getProviderSelectionConfig, +}; diff --git a/bin/lib/local-inference.js b/bin/lib/local-inference.js new file mode 100644 index 0000000000..3892e969ce --- /dev/null +++ b/bin/lib/local-inference.js @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const HOST_GATEWAY_URL = "http://host.openshell.internal"; +const CONTAINER_REACHABILITY_IMAGE = "curlimages/curl:8.10.1"; +const DEFAULT_OLLAMA_MODEL = "nemotron-3-nano:30b"; + +function getLocalProviderBaseUrl(provider) { + switch (provider) { + case "vllm-local": + return `${HOST_GATEWAY_URL}:8000/v1`; + case "ollama-local": + return `${HOST_GATEWAY_URL}:11434/v1`; + default: + return null; + } +} + +function getLocalProviderHealthCheck(provider) { + switch (provider) { + case "vllm-local": + return "curl -sf http://localhost:8000/v1/models 2>/dev/null"; + case "ollama-local": + return "curl -sf http://localhost:11434/api/tags 2>/dev/null"; + default: + return null; + } +} + +function getLocalProviderContainerReachabilityCheck(provider) { + switch (provider) { + case "vllm-local": + return `docker run --rm --add-host host.openshell.internal:host-gateway ${CONTAINER_REACHABILITY_IMAGE} -sf http://host.openshell.internal:8000/v1/models 2>/dev/null`; + case "ollama-local": + return `docker run --rm --add-host host.openshell.internal:host-gateway ${CONTAINER_REACHABILITY_IMAGE} -sf http://host.openshell.internal:11434/api/tags 2>/dev/null`; + default: + return null; + } +} + +function validateLocalProvider(provider, runCapture) { + const command = getLocalProviderHealthCheck(provider); + if (!command) { + return { ok: true }; + } + + const output = runCapture(command, { ignoreError: true }); + if (!output) { + switch (provider) { + case "vllm-local": + return { + ok: false, + message: "Local vLLM was selected, but nothing is responding on http://localhost:8000.", + }; + case "ollama-local": + return { + ok: false, + message: "Local Ollama was selected, but nothing is responding on http://localhost:11434.", + }; + default: + return { ok: false, message: "The selected local inference provider is unavailable." }; + } + } + + const containerCommand = getLocalProviderContainerReachabilityCheck(provider); + if (!containerCommand) { + return { ok: true }; + } + + const containerOutput = runCapture(containerCommand, { ignoreError: true }); + if (containerOutput) { + return { ok: true }; + } + + switch (provider) { + case "vllm-local": + return { + ok: false, + message: + "Local vLLM is responding on localhost, but containers cannot reach http://host.openshell.internal:8000. Ensure the server is reachable from containers, not only from the host shell.", + }; + case "ollama-local": + return { + ok: false, + message: + "Local Ollama is responding on localhost, but containers cannot reach http://host.openshell.internal:11434. Ensure Ollama listens on 0.0.0.0:11434 instead of 127.0.0.1 so sandboxes can reach it.", + }; + default: + return { ok: false, message: "The selected local inference provider is unavailable from containers." }; + } +} + +function parseOllamaList(output) { + return String(output || "") + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !/^NAME\s+/i.test(line)) + .map((line) => line.split(/\s{2,}/)[0]) + .filter(Boolean); +} + +function getOllamaModelOptions(runCapture) { + const output = runCapture("ollama list 2>/dev/null", { ignoreError: true }); + const parsed = parseOllamaList(output); + if (parsed.length > 0) { + return parsed; + } + return [DEFAULT_OLLAMA_MODEL]; +} + +function getDefaultOllamaModel(runCapture) { + const models = getOllamaModelOptions(runCapture); + return models.includes(DEFAULT_OLLAMA_MODEL) ? DEFAULT_OLLAMA_MODEL : models[0]; +} + +function shellQuote(value) { + return `'${String(value).replace(/'/g, `'\\''`)}'`; +} + +function getOllamaWarmupCommand(model, keepAlive = "15m") { + const payload = JSON.stringify({ + model, + prompt: "hello", + stream: false, + keep_alive: keepAlive, + }); + return `nohup curl -s http://localhost:11434/api/generate -H 'Content-Type: application/json' -d ${shellQuote(payload)} >/dev/null 2>&1 &`; +} + +function getOllamaProbeCommand(model, timeoutSeconds = 120, keepAlive = "15m") { + const payload = JSON.stringify({ + model, + prompt: "hello", + stream: false, + keep_alive: keepAlive, + }); + return `curl -sS --max-time ${timeoutSeconds} http://localhost:11434/api/generate -H 'Content-Type: application/json' -d ${shellQuote(payload)} 2>/dev/null`; +} + +function validateOllamaModel(model, runCapture) { + const output = runCapture(getOllamaProbeCommand(model), { ignoreError: true }); + if (!output) { + return { + ok: false, + message: + `Selected Ollama model '${model}' did not answer the local probe in time. ` + + "It may still be loading, too large for the host, or otherwise unhealthy.", + }; + } + + try { + const parsed = JSON.parse(output); + if (parsed && typeof parsed.error === "string" && parsed.error.trim()) { + return { + ok: false, + message: `Selected Ollama model '${model}' failed the local probe: ${parsed.error.trim()}`, + }; + } + } catch {} + + return { ok: true }; +} + +module.exports = { + CONTAINER_REACHABILITY_IMAGE, + DEFAULT_OLLAMA_MODEL, + HOST_GATEWAY_URL, + getDefaultOllamaModel, + getLocalProviderBaseUrl, + getLocalProviderContainerReachabilityCheck, + getLocalProviderHealthCheck, + getOllamaModelOptions, + getOllamaProbeCommand, + getOllamaWarmupCommand, + parseOllamaList, + validateOllamaModel, + validateLocalProvider, +}; diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index 79545cbc2e..23f19b01a8 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -8,12 +8,31 @@ const fs = require("fs"); const path = require("path"); const { ROOT, SCRIPTS, run, runCapture } = require("./runner"); +const { + getDefaultOllamaModel, + getLocalProviderBaseUrl, + getOllamaModelOptions, + getOllamaWarmupCommand, + validateOllamaModel, + validateLocalProvider, +} = require("./local-inference"); +const { + CLOUD_MODEL_OPTIONS, + DEFAULT_CLOUD_MODEL, + DEFAULT_OLLAMA_MODEL, + getOpenClawPrimaryModel, + getProviderSelectionConfig, +} = require("./inference-config"); +const { + inferContainerRuntime, + isUnsupportedMacosRuntime, + shouldPatchCoredns, +} = require("./platform"); const { prompt, ensureApiKey, getCredential } = require("./credentials"); const registry = require("./registry"); const nim = require("./nim"); const policies = require("./policies"); const { checkPortAvailable } = require("./preflight"); -const HOST_GATEWAY_URL = "http://host.openshell.internal"; const EXPERIMENTAL = process.env.NEMOCLAW_EXPERIMENTAL === "1"; // Non-interactive mode: set by --non-interactive flag or env var. @@ -44,6 +63,103 @@ function step(n, total, msg) { console.log(` ${"─".repeat(50)}`); } +function shellQuote(value) { + return `'${String(value).replace(/'/g, `'\\''`)}'`; +} + +function pythonLiteralJson(value) { + return JSON.stringify(JSON.stringify(value)); +} + +function buildSandboxConfigSyncScript(selectionConfig) { + const providerType = + selectionConfig.profile === "inference-local" + ? selectionConfig.model === DEFAULT_OLLAMA_MODEL + ? "ollama-local" + : "nvidia-nim" + : selectionConfig.endpointType === "vllm" + ? "vllm-local" + : "nvidia-nim"; + const primaryModel = getOpenClawPrimaryModel(providerType, selectionConfig.model); + const providerKey = "inference"; + const providerConfig = { + baseUrl: selectionConfig.endpointUrl, + apiKey: "unused", + api: "openai-completions", + models: [ + { + id: selectionConfig.model, + name: selectionConfig.model, + reasoning: false, + input: ["text"], + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, + contextWindow: 131072, + maxTokens: 4096, + }, + ], + }; + return ` +set -euo pipefail +mkdir -p ~/.nemoclaw ~/.openclaw +cat > ~/.nemoclaw/config.json <<'EOF_NEMOCLAW_CFG' +${JSON.stringify(selectionConfig, null, 2)} +EOF_NEMOCLAW_CFG +python3 - <<'PYCFG' +import json +import os + +cfg_path = os.path.expanduser('~/.openclaw/openclaw.json') +cfg = {} +if os.path.exists(cfg_path): + with open(cfg_path) as f: + cfg = json.load(f) + +cfg.setdefault('agents', {}).setdefault('defaults', {}).setdefault('model', {})['primary'] = ${JSON.stringify(primaryModel)} +models_cfg = cfg.setdefault('models', {}) +models_cfg.setdefault('mode', 'merge') +providers_cfg = models_cfg.setdefault('providers', {}) +providers_cfg[${JSON.stringify(providerKey)}] = json.loads(${pythonLiteralJson(providerConfig)}) + +with open(cfg_path, 'w') as f: + json.dump(cfg, f, indent=2) + +os.chmod(cfg_path, 0o600) +PYCFG +openclaw models set ${shellQuote(primaryModel)} > /dev/null 2>&1 || true +exit +`.trim(); +} + +async function promptCloudModel() { + console.log(""); + console.log(" Cloud models:"); + CLOUD_MODEL_OPTIONS.forEach((option, index) => { + console.log(` ${index + 1}) ${option.label} (${option.id})`); + }); + console.log(""); + + const choice = await prompt(" Choose model [1]: "); + const index = parseInt(choice || "1", 10) - 1; + return (CLOUD_MODEL_OPTIONS[index] || CLOUD_MODEL_OPTIONS[0]).id; +} + +async function promptOllamaModel() { + const options = getOllamaModelOptions(runCapture); + const defaultModel = getDefaultOllamaModel(runCapture); + const defaultIndex = Math.max(0, options.indexOf(defaultModel)); + + console.log(""); + console.log(" Ollama models:"); + options.forEach((option, index) => { + console.log(` ${index + 1}) ${option}`); + }); + console.log(""); + + const choice = await prompt(` Choose model [${defaultIndex + 1}]: `); + const index = parseInt(choice || String(defaultIndex + 1), 10) - 1; + return options[index] || options[defaultIndex] || defaultModel; +} + function isDockerRunning() { try { runCapture("docker info", { ignoreError: false }); @@ -53,6 +169,11 @@ function isDockerRunning() { } } +function getContainerRuntime() { + const info = runCapture("docker info 2>/dev/null", { ignoreError: true }); + return inferContainerRuntime(info); +} + function isOpenshellInstalled() { try { runCapture("command -v openshell"); @@ -133,6 +254,17 @@ async function preflight() { } console.log(" ✓ Docker is running"); + const runtime = getContainerRuntime(); + if (isUnsupportedMacosRuntime(runtime)) { + console.error(" Podman on macOS is not supported by NemoClaw at this time."); + console.error(" OpenShell currently depends on Docker host-gateway behavior that Podman on macOS does not provide."); + console.error(" Use Colima or Docker Desktop on macOS instead."); + process.exit(1); + } + if (runtime !== "unknown") { + console.log(` ✓ Container runtime: ${runtime}`); + } + // OpenShell CLI if (!isOpenshellInstalled()) { console.log(" openshell CLI not found. Attempting to install..."); @@ -225,14 +357,10 @@ async function startGateway(gpu) { } // CoreDNS fix — always run. k3s-inside-Docker has broken DNS on all platforms. - const home = process.env.HOME || "/tmp"; - const colimaSocket = [ - path.join(home, ".colima/default/docker.sock"), - path.join(home, ".config/colima/default/docker.sock"), - ].find((s) => fs.existsSync(s)); - if (colimaSocket) { + const runtime = getContainerRuntime(); + if (shouldPatchCoredns(runtime)) { console.log(" Patching CoreDNS for Colima..."); - run(`bash "${path.join(SCRIPTS, "fix-coredns.sh")}" 2>&1 || true`, { ignoreError: true }); + run(`bash "${path.join(SCRIPTS, "fix-coredns.sh")}" nemoclaw 2>&1 || true`, { ignoreError: true }); } // Give DNS a moment to propagate sleep(5); @@ -345,76 +473,35 @@ async function setupNim(sandboxName, gpu) { const vllmRunning = !!runCapture("curl -sf http://localhost:8000/v1/models 2>/dev/null", { ignoreError: true }); const requestedProvider = isNonInteractive() ? getNonInteractiveProvider() : null; const requestedModel = isNonInteractive() ? getNonInteractiveModel(requestedProvider || "cloud") : null; - - // Auto-select only with NEMOCLAW_EXPERIMENTAL=1 (prevents silent misconfiguration) - if (EXPERIMENTAL) { - if (vllmRunning) { - console.log(" ✓ vLLM detected on localhost:8000 — using it [experimental]"); - provider = "vllm-local"; - model = "vllm-local"; - registry.updateSandbox(sandboxName, { model, provider, nimContainer }); - return { model, provider }; - } - if (ollamaRunning) { - console.log(" ✓ Ollama detected on localhost:11434 — using it [experimental]"); - provider = "ollama-local"; - model = "nemotron-3-nano"; - registry.updateSandbox(sandboxName, { model, provider, nimContainer }); - return { model, provider }; - } - } - - // Non-interactive: honor NEMOCLAW_PROVIDER before building interactive options - if (isNonInteractive() && requestedProvider) { - const providerKey = requestedProvider; - console.log(` [non-interactive] Provider: ${providerKey}`); - if (providerKey === "ollama") { - if (!ollamaRunning) { - console.error(" Ollama is not running on localhost:11434. Start it first."); - process.exit(1); - } - provider = "ollama-local"; - model = requestedModel || "nemotron-3-nano"; - registry.updateSandbox(sandboxName, { model, provider, nimContainer }); - return { model, provider }; - } else if (providerKey === "vllm") { - if (!vllmRunning) { - console.error(" vLLM is not running on localhost:8000. Start it first."); - process.exit(1); - } - provider = "vllm-local"; - model = requestedModel || "vllm-local"; - registry.updateSandbox(sandboxName, { model, provider, nimContainer }); - return { model, provider }; - } else if (providerKey === "nim") { - if (!EXPERIMENTAL) { - console.error(" NEMOCLAW_PROVIDER=nim requires NEMOCLAW_EXPERIMENTAL=1."); - process.exit(1); - } - if (!gpu || !gpu.nimCapable) { - console.error(" Local NIM requires a compatible NVIDIA GPU."); - process.exit(1); - } - } - // "cloud" or "nim" fall through to normal flow below - } - // Build options list — only show local options with NEMOCLAW_EXPERIMENTAL=1 const options = []; if (EXPERIMENTAL && gpu && gpu.nimCapable) { options.push({ key: "nim", label: "Local NIM container (NVIDIA GPU) [experimental]" }); } - options.push({ key: "cloud", label: "NVIDIA Cloud API (build.nvidia.com)" }); - if (EXPERIMENTAL && (hasOllama || ollamaRunning)) { - options.push({ key: "ollama", label: `Local Ollama (localhost:11434)${ollamaRunning ? " — running" : ""} [experimental]` }); + options.push({ + key: "cloud", + label: + "NVIDIA Cloud API (build.nvidia.com)" + + (!ollamaRunning && !(EXPERIMENTAL && vllmRunning) ? " (recommended)" : ""), + }); + if (hasOllama || ollamaRunning) { + options.push({ + key: "ollama", + label: + `Local Ollama (localhost:11434)${ollamaRunning ? " — running" : ""}` + + (ollamaRunning ? " (suggested)" : ""), + }); } if (EXPERIMENTAL && vllmRunning) { - options.push({ key: "vllm", label: "Existing vLLM instance (localhost:8000) — running [experimental]" }); + options.push({ + key: "vllm", + label: "Existing vLLM instance (localhost:8000) — running [experimental] (suggested)", + }); } // On macOS without Ollama, offer to install it - if (EXPERIMENTAL && !hasOllama && process.platform === "darwin") { - options.push({ key: "install-ollama", label: "Install Ollama (macOS) [experimental]" }); + if (!hasOllama && process.platform === "darwin") { + options.push({ key: "install-ollama", label: "Install Ollama (macOS)" }); } if (options.length > 1) { @@ -429,6 +516,15 @@ async function setupNim(sandboxName, gpu) { } console.log(` [non-interactive] Provider: ${selected.key}`); } else { + const suggestions = []; + if (vllmRunning) suggestions.push("vLLM"); + if (ollamaRunning) suggestions.push("Ollama"); + if (suggestions.length > 0) { + console.log(` Detected local inference option${suggestions.length > 1 ? "s" : ""}: ${suggestions.join(", ")}`); + console.log(" Select one explicitly to use it. Press Enter to keep the cloud default."); + console.log(""); + } + console.log(""); console.log(" Inference options:"); options.forEach((o, i) => { @@ -497,7 +593,11 @@ async function setupNim(sandboxName, gpu) { } console.log(" ✓ Using Ollama on localhost:11434"); provider = "ollama-local"; - model = "nemotron-3-nano"; + if (isNonInteractive()) { + model = requestedModel || getDefaultOllamaModel(runCapture); + } else { + model = await promptOllamaModel(); + } } else if (selected.key === "install-ollama") { console.log(" Installing Ollama via Homebrew..."); run("brew install ollama", { ignoreError: true }); @@ -506,7 +606,11 @@ async function setupNim(sandboxName, gpu) { sleep(2); console.log(" ✓ Using Ollama on localhost:11434"); provider = "ollama-local"; - model = "nemotron-3-nano"; + if (isNonInteractive()) { + model = requestedModel || getDefaultOllamaModel(runCapture); + } else { + model = await promptOllamaModel(); + } } else if (selected.key === "vllm") { console.log(" ✓ Using existing vLLM on localhost:8000"); provider = "vllm-local"; @@ -525,8 +629,9 @@ async function setupNim(sandboxName, gpu) { } } else { await ensureApiKey(); + model = model || (await promptCloudModel()) || DEFAULT_CLOUD_MODEL; } - model = model || requestedModel || "nvidia/nemotron-3-super-120b-a12b"; + model = model || requestedModel || DEFAULT_CLOUD_MODEL; console.log(` Using NVIDIA Cloud API with model: ${model}`); } @@ -553,12 +658,18 @@ async function setupInference(sandboxName, model, provider) { { ignoreError: true } ); } else if (provider === "vllm-local") { + const validation = validateLocalProvider(provider, runCapture); + if (!validation.ok) { + console.error(` ${validation.message}`); + process.exit(1); + } + const baseUrl = getLocalProviderBaseUrl(provider); run( `openshell provider create --name vllm-local --type openai ` + `--credential "OPENAI_API_KEY=dummy" ` + - `--config "OPENAI_BASE_URL=${HOST_GATEWAY_URL}:8000/v1" 2>&1 || ` + + `--config "OPENAI_BASE_URL=${baseUrl}" 2>&1 || ` + `openshell provider update vllm-local --credential "OPENAI_API_KEY=dummy" ` + - `--config "OPENAI_BASE_URL=${HOST_GATEWAY_URL}:8000/v1" 2>&1 || true`, + `--config "OPENAI_BASE_URL=${baseUrl}" 2>&1 || true`, { ignoreError: true } ); run( @@ -566,18 +677,32 @@ async function setupInference(sandboxName, model, provider) { { ignoreError: true } ); } else if (provider === "ollama-local") { + const validation = validateLocalProvider(provider, runCapture); + if (!validation.ok) { + console.error(` ${validation.message}`); + console.error(" On macOS, local inference also depends on OpenShell host routing support."); + process.exit(1); + } + const baseUrl = getLocalProviderBaseUrl(provider); run( `openshell provider create --name ollama-local --type openai ` + `--credential "OPENAI_API_KEY=ollama" ` + - `--config "OPENAI_BASE_URL=${HOST_GATEWAY_URL}:11434/v1" 2>&1 || ` + + `--config "OPENAI_BASE_URL=${baseUrl}" 2>&1 || ` + `openshell provider update ollama-local --credential "OPENAI_API_KEY=ollama" ` + - `--config "OPENAI_BASE_URL=${HOST_GATEWAY_URL}:11434/v1" 2>&1 || true`, + `--config "OPENAI_BASE_URL=${baseUrl}" 2>&1 || true`, { ignoreError: true } ); run( `openshell inference set --no-verify --provider ollama-local --model ${model} 2>/dev/null || true`, { ignoreError: true } ); + console.log(` Priming Ollama model: ${model}`); + run(getOllamaWarmupCommand(model), { ignoreError: true }); + const probe = validateOllamaModel(model, runCapture); + if (!probe.ok) { + console.error(` ${probe.message}`); + process.exit(1); + } } registry.updateSandbox(sandboxName, { model, provider }); @@ -586,14 +711,21 @@ async function setupInference(sandboxName, model, provider) { // ── Step 6: OpenClaw ───────────────────────────────────────────── -async function setupOpenclaw(sandboxName) { +async function setupOpenclaw(sandboxName, model, provider) { step(6, 7, "Setting up OpenClaw inside sandbox"); - // sandbox create with a command runs it inside the sandbox then exits. - // Since the sandbox already exists, we create a throwaway connect + command - // by using sandbox create --no-keep with the same image to exec into it. - // Simpler: just use sandbox connect which opens a shell — but it doesn't - // support passing commands. So we run the setup on next connect instead. + const selectionConfig = getProviderSelectionConfig(provider, model); + if (selectionConfig) { + const sandboxConfig = { + ...selectionConfig, + onboardedAt: new Date().toISOString(), + }; + const script = buildSandboxConfigSyncScript(sandboxConfig); + run(`cat <<'EOF_NEMOCLAW_SYNC' | openshell sandbox connect "${sandboxName}" +${script} +EOF_NEMOCLAW_SYNC`, { stdio: ["ignore", "ignore", "inherit"] }); + } + console.log(" ✓ OpenClaw gateway launched inside sandbox"); } @@ -717,6 +849,7 @@ function printDashboard(sandboxName, model, provider) { let providerLabel = provider; if (provider === "nvidia-nim") providerLabel = "NVIDIA Cloud API"; else if (provider === "vllm-local") providerLabel = "Local vLLM"; + else if (provider === "ollama-local") providerLabel = "Local Ollama"; console.log(""); console.log(` ${"─".repeat(50)}`); @@ -747,9 +880,9 @@ async function onboard(opts = {}) { const sandboxName = await createSandbox(gpu); const { model, provider } = await setupNim(sandboxName, gpu); await setupInference(sandboxName, model, provider); - await setupOpenclaw(sandboxName); + await setupOpenclaw(sandboxName, model, provider); await setupPolicies(sandboxName); printDashboard(sandboxName, model, provider); } -module.exports = { onboard }; +module.exports = { buildSandboxConfigSyncScript, onboard, setupNim }; diff --git a/bin/lib/platform.js b/bin/lib/platform.js new file mode 100644 index 0000000000..67c31a3f3e --- /dev/null +++ b/bin/lib/platform.js @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const os = require("os"); +const path = require("path"); + +function isWsl(opts = {}) { + const platform = opts.platform ?? process.platform; + if (platform !== "linux") return false; + + const env = opts.env ?? process.env; + const release = opts.release ?? os.release(); + const procVersion = opts.procVersion ?? ""; + + return ( + Boolean(env.WSL_DISTRO_NAME) || + Boolean(env.WSL_INTEROP) || + /microsoft/i.test(release) || + /microsoft/i.test(procVersion) + ); +} + +function inferContainerRuntime(info = "") { + const normalized = String(info).toLowerCase(); + if (!normalized.trim()) return "unknown"; + if (normalized.includes("podman")) return "podman"; + if (normalized.includes("colima")) return "colima"; + if (normalized.includes("docker desktop")) return "docker-desktop"; + if (normalized.includes("docker")) return "docker"; + return "unknown"; +} + +function isUnsupportedMacosRuntime(runtime, opts = {}) { + const platform = opts.platform ?? process.platform; + return platform === "darwin" && runtime === "podman"; +} + +function shouldPatchCoredns(runtime) { + return runtime === "colima"; +} + +function getColimaDockerSocketCandidates(opts = {}) { + const home = opts.home ?? process.env.HOME ?? "/tmp"; + return [ + path.join(home, ".colima/default/docker.sock"), + path.join(home, ".config/colima/default/docker.sock"), + ]; +} + +function findColimaDockerSocket(opts = {}) { + const existsSync = opts.existsSync ?? require("fs").existsSync; + return getColimaDockerSocketCandidates(opts).find((socketPath) => existsSync(socketPath)) ?? null; +} + +function getDockerSocketCandidates(opts = {}) { + const home = opts.home ?? process.env.HOME ?? "/tmp"; + const platform = opts.platform ?? process.platform; + + if (platform === "darwin") { + return [ + ...getColimaDockerSocketCandidates({ home }), + path.join(home, ".docker/run/docker.sock"), + ]; + } + + return []; +} + +function detectDockerHost(opts = {}) { + const env = opts.env ?? process.env; + if (env.DOCKER_HOST) { + return { + dockerHost: env.DOCKER_HOST, + source: "env", + socketPath: null, + }; + } + + const existsSync = opts.existsSync ?? require("fs").existsSync; + for (const socketPath of getDockerSocketCandidates(opts)) { + if (existsSync(socketPath)) { + return { + dockerHost: `unix://${socketPath}`, + source: "socket", + socketPath, + }; + } + } + + return null; +} + +module.exports = { + detectDockerHost, + findColimaDockerSocket, + getColimaDockerSocketCandidates, + getDockerSocketCandidates, + inferContainerRuntime, + isUnsupportedMacosRuntime, + isWsl, + shouldPatchCoredns, +}; diff --git a/bin/lib/runner.js b/bin/lib/runner.js index 3614dc80da..53ec88996a 100644 --- a/bin/lib/runner.js +++ b/bin/lib/runner.js @@ -3,29 +3,35 @@ const { execSync, spawnSync } = require("child_process"); const path = require("path"); -const fs = require("fs"); +const { detectDockerHost } = require("./platform"); const ROOT = path.resolve(__dirname, "..", ".."); const SCRIPTS = path.join(ROOT, "scripts"); -// Auto-detect Colima Docker socket (legacy ~/.colima or XDG ~/.config/colima) -if (!process.env.DOCKER_HOST) { - const home = process.env.HOME || "/tmp"; - const candidates = [ - path.join(home, ".colima/default/docker.sock"), - path.join(home, ".config/colima/default/docker.sock"), - ]; - for (const sock of candidates) { - if (fs.existsSync(sock)) { - process.env.DOCKER_HOST = `unix://${sock}`; - break; - } - } +const dockerHost = detectDockerHost(); +if (dockerHost) { + process.env.DOCKER_HOST = dockerHost.dockerHost; } function run(cmd, opts = {}) { + const stdio = opts.stdio ?? ["ignore", "inherit", "inherit"]; + const result = spawnSync("bash", ["-c", cmd], { + stdio, + cwd: ROOT, + env: { ...process.env, ...opts.env }, + ...opts, + }); + if (result.status !== 0 && !opts.ignoreError) { + console.error(` Command failed (exit ${result.status}): ${cmd.slice(0, 80)}`); + process.exit(result.status || 1); + } + return result; +} + +function runInteractive(cmd, opts = {}) { + const stdio = opts.stdio ?? "inherit"; const result = spawnSync("bash", ["-c", cmd], { - stdio: "inherit", + stdio, cwd: ROOT, env: { ...process.env, ...opts.env }, ...opts, @@ -52,4 +58,4 @@ function runCapture(cmd, opts = {}) { } } -module.exports = { ROOT, SCRIPTS, run, runCapture }; +module.exports = { ROOT, SCRIPTS, run, runCapture, runInteractive }; diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 4f8722d7ae..3a17f43331 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -7,7 +7,7 @@ const path = require("path"); const fs = require("fs"); const os = require("os"); -const { ROOT, SCRIPTS, run, runCapture } = require("./lib/runner"); +const { ROOT, SCRIPTS, run, runCapture, runInteractive } = require("./lib/runner"); const { ensureApiKey, ensureGithubToken, @@ -129,7 +129,7 @@ async function deploy(instanceName) { fs.unlinkSync(envTmp); console.log(" Running setup..."); - run(`ssh -t -o StrictHostKeyChecking=no -o LogLevel=ERROR ${name} 'cd /home/ubuntu/nemoclaw && set -a && . .env && set +a && bash scripts/brev-setup.sh'`); + runInteractive(`ssh -t -o StrictHostKeyChecking=no -o LogLevel=ERROR ${name} 'cd /home/ubuntu/nemoclaw && set -a && . .env && set +a && bash scripts/brev-setup.sh'`); if (tgToken) { console.log(" Starting services..."); @@ -139,7 +139,7 @@ async function deploy(instanceName) { console.log(""); console.log(" Connecting to sandbox..."); console.log(""); - run(`ssh -t -o StrictHostKeyChecking=no -o LogLevel=ERROR ${name} 'cd /home/ubuntu/nemoclaw && set -a && . .env && set +a && openshell sandbox connect nemoclaw'`); + runInteractive(`ssh -t -o StrictHostKeyChecking=no -o LogLevel=ERROR ${name} 'cd /home/ubuntu/nemoclaw && set -a && . .env && set +a && openshell sandbox connect nemoclaw'`); } async function start() { @@ -202,7 +202,7 @@ function listSandboxes() { function sandboxConnect(sandboxName) { // Ensure port forward is alive before connecting run(`openshell forward start --background 18789 "${sandboxName}" 2>/dev/null || true`, { ignoreError: true }); - run(`openshell sandbox connect "${sandboxName}"`); + runInteractive(`openshell sandbox connect "${sandboxName}"`); } function sandboxStatus(sandboxName) { diff --git a/install.sh b/install.sh index 72b4689b05..1c5dbbb219 100755 --- a/install.sh +++ b/install.sh @@ -19,6 +19,8 @@ MIN_NODE_MAJOR=20 MIN_NPM_MAJOR=10 RECOMMENDED_NODE_MAJOR=22 RUNTIME_REQUIREMENT_MSG="NemoClaw requires Node.js >=${MIN_NODE_MAJOR} and npm >=${MIN_NPM_MAJOR} (recommended Node.js ${RECOMMENDED_NODE_MAJOR})." +NEMOCLAW_SHIM_DIR="${HOME}/.local/bin" +ORIGINAL_PATH="${PATH:-}" # Compare two semver strings (major.minor.patch). Returns 0 if $1 >= $2. version_gte() { @@ -53,6 +55,30 @@ refresh_path() { if [[ -n "$npm_bin" && -d "$npm_bin" && ":$PATH:" != *":$npm_bin:"* ]]; then export PATH="$npm_bin:$PATH" fi + + if [[ -d "$NEMOCLAW_SHIM_DIR" && ":$PATH:" != *":$NEMOCLAW_SHIM_DIR:"* ]]; then + export PATH="$NEMOCLAW_SHIM_DIR:$PATH" + fi +} + +ensure_nemoclaw_shim() { + local npm_bin shim_path + npm_bin="$(npm config get prefix 2>/dev/null)/bin" || true + shim_path="${NEMOCLAW_SHIM_DIR}/nemoclaw" + + if [[ -z "$npm_bin" || ! -x "$npm_bin/nemoclaw" ]]; then + return 1 + fi + + if [[ ":$ORIGINAL_PATH:" == *":$npm_bin:"* ]] || [[ ":$ORIGINAL_PATH:" == *":$NEMOCLAW_SHIM_DIR:"* ]]; then + return 0 + fi + + mkdir -p "$NEMOCLAW_SHIM_DIR" + ln -sfn "$npm_bin/nemoclaw" "$shim_path" + refresh_path + info "Created user-local shim at $shim_path" + return 0 } version_major() { @@ -198,12 +224,13 @@ install_nemoclaw() { info "NemoClaw package.json found in current directory — installing from source…" npm install && npm link else - info "Installing NemoClaw from npm…" + info "Installing NemoClaw from GitHub…" # Revert once https://github.com/NVIDIA/NemoClaw/issues/71 is complete and the package is published - npm install -g git+ssh://git@github.com/nvidia/NemoClaw.git + npm install -g git+https://github.com/NVIDIA/NemoClaw.git fi refresh_path + ensure_nemoclaw_shim || true } # --------------------------------------------------------------------------- @@ -222,21 +249,23 @@ verify_nemoclaw() { npm_bin="$(npm config get prefix 2>/dev/null)/bin" || true if [[ -n "$npm_bin" && -x "$npm_bin/nemoclaw" ]]; then - warn "Found nemoclaw at $npm_bin/nemoclaw but that directory is not on PATH." - warn "" - warn "Add it to your shell profile:" - warn " echo 'export PATH=\"$npm_bin:\$PATH\"' >> ~/.bashrc" - warn " source ~/.bashrc" + ensure_nemoclaw_shim || true + if command_exists nemoclaw; then + info "Verified: nemoclaw is available at $(command -v nemoclaw)" + return 0 + fi + + warn "Found nemoclaw at $npm_bin/nemoclaw but could not expose it on PATH." warn "" - warn "Or for zsh:" - warn " echo 'export PATH=\"$npm_bin:\$PATH\"' >> ~/.zshrc" - warn " source ~/.zshrc" + warn "Add one of these directories to your shell profile:" + warn " $NEMOCLAW_SHIM_DIR" + warn " $npm_bin" warn "" warn "Continuing — nemoclaw is installed but requires a PATH update." return 0 else warn "Could not locate the nemoclaw executable." - warn "Try running: npm install -g nemoclaw" + warn "Try running: npm install -g git+https://github.com/NVIDIA/NemoClaw.git" fi error "Installation failed: nemoclaw binary not found." diff --git a/nemoclaw/dist/cli.js b/nemoclaw/dist/cli.js index c1baf30b5c..3ac21566e9 100644 --- a/nemoclaw/dist/cli.js +++ b/nemoclaw/dist/cli.js @@ -96,7 +96,7 @@ function registerCliCommands(ctx, api) { .command("onboard") .description("Interactive setup: configure inference endpoint, credential, and model") .option("--api-key ", "API key for endpoints that require one (skips prompt)") - .option("--endpoint ", "Endpoint type: build, ncp, nim-local, vllm, ollama, custom (local options are experimental)") + .option("--endpoint ", "Endpoint type: build, ncp, ollama, nim-local, vllm, custom (nim-local and vllm are experimental)") .option("--ncp-partner ", "NCP partner name (when endpoint is ncp)") .option("--endpoint-url ", "Endpoint URL (for ncp, nim-local, ollama, or custom)") .option("--model ", "Model ID to use") diff --git a/nemoclaw/dist/cli.js.map b/nemoclaw/dist/cli.js.map index 8e3fe13a0a..27a57c7617 100644 --- a/nemoclaw/dist/cli.js.map +++ b/nemoclaw/dist/cli.js.map @@ -1 +1 @@ -{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/commands/onboard.d.ts.map b/nemoclaw/dist/commands/onboard.d.ts.map index cc9b3f0ff3..9f7d783426 100644 --- a/nemoclaw/dist/commands/onboard.d.ts.map +++ b/nemoclaw/dist/commands/onboard.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAUhE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,cAAc,CAAC;CAC9B;AAsKD,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAwQpE"} \ No newline at end of file +{"version":3,"file":"onboard.d.ts","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYhE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,cAAc,CAAC;CAC9B;AAyMD,wBAAsB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAySpE"} \ No newline at end of file diff --git a/nemoclaw/dist/commands/onboard.js b/nemoclaw/dist/commands/onboard.js index 4569ca014f..c112c07b57 100644 --- a/nemoclaw/dist/commands/onboard.js +++ b/nemoclaw/dist/commands/onboard.js @@ -8,7 +8,7 @@ const config_js_1 = require("../onboard/config.js"); const prompt_js_1 = require("../onboard/prompt.js"); const validate_js_1 = require("../onboard/validate.js"); const ENDPOINT_TYPES = ["build", "ncp", "nim-local", "vllm", "ollama", "custom"]; -const SUPPORTED_ENDPOINT_TYPES = ["build", "ncp"]; +const SUPPORTED_ENDPOINT_TYPES = ["build", "ncp", "ollama"]; function isExperimentalEnabled() { return process.env.NEMOCLAW_EXPERIMENTAL === "1"; } @@ -16,10 +16,13 @@ const BUILD_ENDPOINT_URL = "https://integrate.api.nvidia.com/v1"; const HOST_GATEWAY_URL = "http://host.openshell.internal"; const DEFAULT_MODELS = [ { id: "nvidia/nemotron-3-super-120b-a12b", label: "Nemotron 3 Super 120B" }, - { id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", label: "Nemotron Ultra 253B" }, - { id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", label: "Nemotron Super 49B v1.5" }, - { id: "nvidia/nemotron-3-nano-30b-a3b", label: "Nemotron 3 Nano 30B" }, + { id: "moonshotai/kimi-k2.5", label: "Kimi K2.5" }, + { id: "z-ai/glm5", label: "GLM-5" }, + { id: "minimaxai/minimax-m2.5", label: "MiniMax M2.5" }, + { id: "qwen/qwen3.5-397b-a17b", label: "Qwen3.5 397B A17B" }, + { id: "openai/gpt-oss-120b", label: "GPT-OSS 120B" }, ]; +const DEFAULT_OLLAMA_MODEL = "nemotron-3-nano:30b"; function resolveProfile(endpointType) { switch (endpointType) { case "build": @@ -96,6 +99,30 @@ function detectOllama() { const running = testCommand("curl -sf http://localhost:11434/api/tags >/dev/null 2>&1"); return { installed, running }; } +function parseOllamaList(output) { + return output + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !/^NAME\s+/i.test(line)) + .map((line) => line.split(/\s{2,}/)[0]) + .filter(Boolean); +} +function getOllamaModelOptions() { + try { + const output = (0, node_child_process_1.execSync)("ollama list", { encoding: "utf-8", shell: "/bin/bash" }); + const parsed = parseOllamaList(output); + if (parsed.length > 0) { + return parsed; + } + } + catch { } + return [DEFAULT_OLLAMA_MODEL]; +} +function getDefaultOllamaModel() { + const models = getOllamaModelOptions(); + return models.includes(DEFAULT_OLLAMA_MODEL) ? DEFAULT_OLLAMA_MODEL : models[0]; +} function testCommand(command) { try { (0, node_child_process_1.execSync)(command, { encoding: "utf-8", stdio: "ignore", shell: "/bin/bash" }); @@ -106,7 +133,8 @@ function testCommand(command) { } } function showConfig(config, logger) { - logger.info(` Endpoint: ${config.endpointType} (${config.endpointUrl})`); + logger.info(` Endpoint: ${(0, config_js_1.describeOnboardEndpoint)(config)}`); + logger.info(` Provider: ${(0, config_js_1.describeOnboardProvider)(config)}`); if (config.ncpPartner) { logger.info(` NCP Partner: ${config.ncpPartner}`); } @@ -128,6 +156,15 @@ async function promptEndpoint(ollama) { hint: "dedicated capacity, SLA-backed", }, ]; + options.push({ + label: "Local Ollama", + value: "ollama", + hint: ollama.running + ? "detected on localhost:11434" + : ollama.installed + ? "installed locally" + : "localhost:11434", + }); if (isExperimentalEnabled()) { options.push({ label: "Self-hosted NIM [experimental]", @@ -137,10 +174,6 @@ async function promptEndpoint(ollama) { label: "Local vLLM [experimental]", value: "vllm", hint: "experimental — local development", - }, { - label: "Local Ollama [experimental]", - value: "ollama", - hint: `experimental — ${ollama.installed ? "installed locally" : "localhost:11434"}`, }); } return (await (0, prompt_js_1.promptSelect)("Select your inference endpoint:", options)); @@ -186,13 +219,11 @@ async function cliOnboard(opts) { } else { const ollama = detectOllama(); - if (ollama.running && isExperimentalEnabled()) { - logger.info("Detected Ollama on localhost:11434. Using it for onboarding."); - endpointType = "ollama"; - } - else { - endpointType = await promptEndpoint(ollama); + if (ollama.running) { + logger.info("Detected local inference option: Ollama."); + logger.info("Select it explicitly if you want to use it."); } + endpointType = await promptEndpoint(ollama); } // Step 2: Endpoint URL resolution let endpointUrl; @@ -280,20 +311,45 @@ async function cliOnboard(opts) { model = opts.model; } else { - // Build model options: prefer Nemotron models from the endpoint, fall back to defaults - const nemotronModels = validation.models.filter((m) => m.includes("nemotron")); - const modelOptions = nemotronModels.length > 0 - ? nemotronModels.map((id) => ({ label: id, value: id })) - : DEFAULT_MODELS.map((m) => ({ label: `${m.label} (${m.id})`, value: m.id })); - model = await (0, prompt_js_1.promptSelect)("Select your primary model:", modelOptions); + const discoveredModelOptions = endpointType === "ollama" + ? getOllamaModelOptions().map((id) => ({ label: id, value: id })) + : validation.models.map((id) => ({ label: id, value: id })); + const curatedCloudOptions = endpointType === "build" || endpointType === "ncp" + ? DEFAULT_MODELS.filter((option) => validation.models.includes(option.id)).map((option) => ({ + label: `${option.label} (${option.id})`, + value: option.id, + })) + : []; + const defaultIndex = endpointType === "ollama" + ? Math.max(0, discoveredModelOptions.findIndex((option) => option.value === getDefaultOllamaModel())) + : 0; + const modelOptions = curatedCloudOptions.length > 0 + ? curatedCloudOptions + : discoveredModelOptions.length > 0 + ? discoveredModelOptions + : DEFAULT_MODELS.map((m) => ({ label: `${m.label} (${m.id})`, value: m.id })); + model = await (0, prompt_js_1.promptSelect)("Select your primary model:", modelOptions, defaultIndex); } // Step 6: Resolve profile const profile = resolveProfile(endpointType); const providerName = resolveProviderName(endpointType); + const summaryConfig = { + endpointType, + endpointUrl, + ncpPartner, + model, + profile, + credentialEnv, + provider: providerName, + providerLabel: undefined, + onboardedAt: "", + }; + summaryConfig.providerLabel = (0, config_js_1.describeOnboardProvider)(summaryConfig); // Step 7: Confirmation logger.info(""); logger.info("Configuration summary:"); - logger.info(` Endpoint: ${endpointType} (${endpointUrl})`); + logger.info(` Endpoint: ${(0, config_js_1.describeOnboardEndpoint)(summaryConfig)}`); + logger.info(` Provider: ${summaryConfig.providerLabel}`); if (ncpPartner) { logger.info(` NCP Partner: ${ncpPartner}`); } @@ -375,13 +431,16 @@ async function cliOnboard(opts) { model, profile, credentialEnv, + provider: providerName, + providerLabel: summaryConfig.providerLabel, onboardedAt: new Date().toISOString(), }); // Step 9: Success logger.info(""); logger.info("Onboarding complete!"); logger.info(""); - logger.info(` Endpoint: ${endpointUrl}`); + logger.info(` Endpoint: ${(0, config_js_1.describeOnboardEndpoint)(summaryConfig)}`); + logger.info(` Provider: ${summaryConfig.providerLabel}`); logger.info(` Model: ${model}`); logger.info(` Credential: $${credentialEnv}`); logger.info(""); diff --git a/nemoclaw/dist/commands/onboard.js.map b/nemoclaw/dist/commands/onboard.js.map index 9df5c61abf..d75cad4c38 100644 --- a/nemoclaw/dist/commands/onboard.js.map +++ b/nemoclaw/dist/commands/onboard.js.map @@ -1 +1 @@ -{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"onboard.js","sourceRoot":"","sources":["../../src/commands/onboard.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/commands/slash.d.ts.map b/nemoclaw/dist/commands/slash.d.ts.map index ff6015318b..2b9c8844b1 100644 --- a/nemoclaw/dist/commands/slash.d.ts.map +++ b/nemoclaw/dist/commands/slash.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"slash.d.ts","sourceRoot":"","sources":["../../src/commands/slash.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIhG,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,oBAAoB,EACzB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CAarB"} \ No newline at end of file +{"version":3,"file":"slash.d.ts","sourceRoot":"","sources":["../../src/commands/slash.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAQhG,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,oBAAoB,EACzB,IAAI,EAAE,iBAAiB,GACtB,mBAAmB,CAarB"} \ No newline at end of file diff --git a/nemoclaw/dist/commands/slash.js b/nemoclaw/dist/commands/slash.js index a7900d3096..fe909e6e92 100644 --- a/nemoclaw/dist/commands/slash.js +++ b/nemoclaw/dist/commands/slash.js @@ -67,7 +67,8 @@ function slashOnboard() { text: [ "**NemoClaw Onboard Status**", "", - `Endpoint: ${config.endpointType} (${config.endpointUrl})`, + `Endpoint: ${(0, config_js_1.describeOnboardEndpoint)(config)}`, + `Provider: ${(0, config_js_1.describeOnboardProvider)(config)}`, config.ncpPartner ? `NCP Partner: ${config.ncpPartner}` : null, `Model: ${config.model}`, `Credential: $${config.credentialEnv}`, diff --git a/nemoclaw/dist/commands/slash.js.map b/nemoclaw/dist/commands/slash.js.map index 13b84101ad..cb97903a4a 100644 --- a/nemoclaw/dist/commands/slash.js.map +++ b/nemoclaw/dist/commands/slash.js.map @@ -1 +1 @@ -{"version":3,"file":"slash.js","sourceRoot":"","sources":["../../src/commands/slash.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"slash.js","sourceRoot":"","sources":["../../src/commands/slash.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/index.d.ts.map b/nemoclaw/dist/index.d.ts.map index d775191d58..be261bffe0 100644 --- a/nemoclaw/dist/index.d.ts.map +++ b/nemoclaw/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/index.js b/nemoclaw/dist/index.js index 9268d4b7c6..058f230308 100644 --- a/nemoclaw/dist/index.js +++ b/nemoclaw/dist/index.js @@ -7,6 +7,57 @@ exports.default = register; const cli_js_1 = require("./cli.js"); const slash_js_1 = require("./commands/slash.js"); const config_js_1 = require("./onboard/config.js"); +function activeModelEntries(onboardCfg) { + if (!onboardCfg?.model) { + return [ + { + id: "nvidia/nemotron-3-super-120b-a12b", + label: "Nemotron 3 Super 120B (March 2026)", + contextWindow: 131072, + maxOutput: 8192, + }, + { + id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", + label: "Nemotron Ultra 253B", + contextWindow: 131072, + maxOutput: 4096, + }, + { + id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", + label: "Nemotron Super 49B v1.5", + contextWindow: 131072, + maxOutput: 4096, + }, + { + id: "nvidia/nemotron-3-nano-30b-a3b", + label: "Nemotron 3 Nano 30B", + contextWindow: 131072, + maxOutput: 4096, + }, + ]; + } + return [ + { + id: `inference/${onboardCfg.model}`, + label: onboardCfg.model, + contextWindow: 131072, + maxOutput: 8192, + }, + ]; +} +function registeredProviderForConfig(onboardCfg, providerCredentialEnv) { + const authLabel = providerCredentialEnv === "NVIDIA_API_KEY" + ? `NVIDIA API Key (${providerCredentialEnv})` + : `OpenAI API Key (${providerCredentialEnv})`; + return { + id: "inference", + label: "Managed Inference Route", + aliases: ["inference-local", "nemoclaw"], + envVars: [providerCredentialEnv], + models: { chat: activeModelEntries(onboardCfg) }, + auth: [{ type: "bearer", envVar: providerCredentialEnv, headerName: "Authorization", label: authLabel }], + }; +} const DEFAULT_PLUGIN_CONFIG = { blueprintVersion: "latest", blueprintRegistry: "ghcr.io/nvidia/nemoclaw-blueprint", @@ -48,59 +99,16 @@ function register(api) { // 3. Register nvidia-nim provider — use onboard config if available const onboardCfg = (0, config_js_1.loadOnboardConfig)(); const providerCredentialEnv = onboardCfg?.credentialEnv ?? "NVIDIA_API_KEY"; - const providerLabel = onboardCfg - ? `NVIDIA NIM (${onboardCfg.endpointType}${onboardCfg.ncpPartner ? ` - ${onboardCfg.ncpPartner}` : ""})` - : "NVIDIA NIM (build.nvidia.com)"; - api.registerProvider({ - id: "nvidia-nim", - label: providerLabel, - docsPath: "https://build.nvidia.com/docs", - aliases: ["nvidia", "nim"], - envVars: [providerCredentialEnv], - models: { - chat: [ - { - id: "nvidia/nemotron-3-super-120b-a12b", - label: "Nemotron 3 Super 120B (March 2026)", - contextWindow: 131072, - maxOutput: 8192, - }, - { - id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", - label: "Nemotron Ultra 253B", - contextWindow: 131072, - maxOutput: 4096, - }, - { - id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", - label: "Nemotron Super 49B v1.5", - contextWindow: 131072, - maxOutput: 4096, - }, - { - id: "nvidia/nemotron-3-nano-30b-a3b", - label: "Nemotron 3 Nano 30B", - contextWindow: 131072, - maxOutput: 4096, - }, - ], - }, - auth: [ - { - type: "bearer", - envVar: providerCredentialEnv, - headerName: "Authorization", - label: `NVIDIA API Key (${providerCredentialEnv})`, - }, - ], - }); - const bannerEndpoint = onboardCfg?.endpointType ?? "build.nvidia.com"; + api.registerProvider(registeredProviderForConfig(onboardCfg, providerCredentialEnv)); + const bannerEndpoint = onboardCfg ? (0, config_js_1.describeOnboardEndpoint)(onboardCfg) : "build.nvidia.com"; + const bannerProvider = onboardCfg ? (0, config_js_1.describeOnboardProvider)(onboardCfg) : "NVIDIA Cloud API"; const bannerModel = onboardCfg?.model ?? "nvidia/nemotron-3-super-120b-a12b"; api.logger.info(""); api.logger.info(" ┌─────────────────────────────────────────────────────┐"); api.logger.info(" │ NemoClaw registered │"); api.logger.info(" │ │"); api.logger.info(` │ Endpoint: ${bannerEndpoint.padEnd(40)}│`); + api.logger.info(` │ Provider: ${bannerProvider.padEnd(40)}│`); api.logger.info(` │ Model: ${bannerModel.padEnd(40)}│`); api.logger.info(" │ Commands: openclaw nemoclaw │"); api.logger.info(" └─────────────────────────────────────────────────────┘"); diff --git a/nemoclaw/dist/index.js.map b/nemoclaw/dist/index.js.map index e45a1adab0..1c22a36ec3 100644 --- a/nemoclaw/dist/index.js.map +++ b/nemoclaw/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,mGAAmG;AACnG,sCAAsC;;AAuJtC,0CAoBC;AAMD,2BAgFC;AApPD,qCAA+C;AAC/C,kDAAyD;AACzD,mDAAwD;AAiIxD,MAAM,qBAAqB,GAAmB;IAC5C,gBAAgB,EAAE,QAAQ;IAC1B,iBAAiB,EAAE,mCAAmC;IACtD,WAAW,EAAE,UAAU;IACvB,iBAAiB,EAAE,QAAQ;CAC5B,CAAC;AAEF,SAAgB,eAAe,CAAC,GAAsB;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;IACnC,OAAO;QACL,gBAAgB,EACd,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,QAAQ;YACzC,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC;YACzB,CAAC,CAAC,qBAAqB,CAAC,gBAAgB;QAC5C,iBAAiB,EACf,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,QAAQ;YAC1C,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAC1B,CAAC,CAAC,qBAAqB,CAAC,iBAAiB;QAC7C,WAAW,EACT,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ;YACpC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC;YACpB,CAAC,CAAC,qBAAqB,CAAC,WAAW;QACvC,iBAAiB,EACf,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,QAAQ;YAC1C,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC;YAC1B,CAAC,CAAC,qBAAqB,CAAC,iBAAiB;KAC9C,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,SAAwB,QAAQ,CAAC,GAAsB;IACrD,uDAAuD;IACvD,GAAG,CAAC,eAAe,CAAC;QAClB,IAAI,EAAE,UAAU;QAChB,WAAW,EAAE,8CAA8C;QAC3D,WAAW,EAAE,IAAI;QACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAA,6BAAkB,EAAC,GAAG,EAAE,GAAG,CAAC;KAC/C,CAAC,CAAC;IAEH,iEAAiE;IACjE,GAAG,CAAC,WAAW,CACb,CAAC,MAAM,EAAE,EAAE;QACT,IAAA,4BAAmB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,CAAC,EACD,EAAE,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE,CAC3B,CAAC;IAEF,oEAAoE;IACpE,MAAM,UAAU,GAAG,IAAA,6BAAiB,GAAE,CAAC;IACvC,MAAM,qBAAqB,GAAG,UAAU,EAAE,aAAa,IAAI,gBAAgB,CAAC;IAC5E,MAAM,aAAa,GAAG,UAAU;QAC9B,CAAC,CAAC,eAAe,UAAU,CAAC,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;QACxG,CAAC,CAAC,+BAA+B,CAAC;IAEpC,GAAG,CAAC,gBAAgB,CAAC;QACnB,EAAE,EAAE,YAAY;QAChB,KAAK,EAAE,aAAa;QACpB,QAAQ,EAAE,+BAA+B;QACzC,OAAO,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;QAC1B,OAAO,EAAE,CAAC,qBAAqB,CAAC;QAChC,MAAM,EAAE;YACN,IAAI,EAAE;gBACJ;oBACE,EAAE,EAAE,mCAAmC;oBACvC,KAAK,EAAE,oCAAoC;oBAC3C,aAAa,EAAE,MAAM;oBACrB,SAAS,EAAE,IAAI;iBAChB;gBACD;oBACE,EAAE,EAAE,yCAAyC;oBAC7C,KAAK,EAAE,qBAAqB;oBAC5B,aAAa,EAAE,MAAM;oBACrB,SAAS,EAAE,IAAI;iBAChB;gBACD;oBACE,EAAE,EAAE,0CAA0C;oBAC9C,KAAK,EAAE,yBAAyB;oBAChC,aAAa,EAAE,MAAM;oBACrB,SAAS,EAAE,IAAI;iBAChB;gBACD;oBACE,EAAE,EAAE,gCAAgC;oBACpC,KAAK,EAAE,qBAAqB;oBAC5B,aAAa,EAAE,MAAM;oBACrB,SAAS,EAAE,IAAI;iBAChB;aACF;SACF;QACD,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,qBAAqB;gBAC7B,UAAU,EAAE,eAAe;gBAC3B,KAAK,EAAE,mBAAmB,qBAAqB,GAAG;aACnD;SACF;KACF,CAAC,CAAC;IAEH,MAAM,cAAc,GAAG,UAAU,EAAE,YAAY,IAAI,kBAAkB,CAAC;IACtE,MAAM,WAAW,GAAG,UAAU,EAAE,KAAK,IAAI,mCAAmC,CAAC;IAE7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC9D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;IAC7E,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/nemoclaw/dist/onboard/config.d.ts b/nemoclaw/dist/onboard/config.d.ts index 7171fc5412..a2b6e4a887 100644 --- a/nemoclaw/dist/onboard/config.d.ts +++ b/nemoclaw/dist/onboard/config.d.ts @@ -6,8 +6,12 @@ export interface NemoClawOnboardConfig { model: string; profile: string; credentialEnv: string; + provider?: string; + providerLabel?: string; onboardedAt: string; } +export declare function describeOnboardEndpoint(config: NemoClawOnboardConfig): string; +export declare function describeOnboardProvider(config: NemoClawOnboardConfig): string; export declare function loadOnboardConfig(): NemoClawOnboardConfig | null; export declare function saveOnboardConfig(config: NemoClawOnboardConfig): void; export declare function clearOnboardConfig(): void; diff --git a/nemoclaw/dist/onboard/config.d.ts.map b/nemoclaw/dist/onboard/config.d.ts.map index bf764c5ac9..eef4c615c8 100644 --- a/nemoclaw/dist/onboard/config.d.ts.map +++ b/nemoclaw/dist/onboard/config.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/onboard/config.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAgBD,wBAAgB,iBAAiB,IAAI,qBAAqB,GAAG,IAAI,CAOhE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAGrE;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC"} \ No newline at end of file +{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/onboard/config.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAM7E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAqB7E;AAgBD,wBAAgB,iBAAiB,IAAI,qBAAqB,GAAG,IAAI,CAOhE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,qBAAqB,GAAG,IAAI,CAGrE;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC"} \ No newline at end of file diff --git a/nemoclaw/dist/onboard/config.js b/nemoclaw/dist/onboard/config.js index 8a313a45c3..68f380434b 100644 --- a/nemoclaw/dist/onboard/config.js +++ b/nemoclaw/dist/onboard/config.js @@ -2,12 +2,41 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 Object.defineProperty(exports, "__esModule", { value: true }); +exports.describeOnboardEndpoint = describeOnboardEndpoint; +exports.describeOnboardProvider = describeOnboardProvider; exports.loadOnboardConfig = loadOnboardConfig; exports.saveOnboardConfig = saveOnboardConfig; exports.clearOnboardConfig = clearOnboardConfig; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const CONFIG_DIR = (0, node_path_1.join)(process.env.HOME ?? "/tmp", ".nemoclaw"); +function describeOnboardEndpoint(config) { + if (config.endpointUrl === "https://inference.local/v1") { + return "Managed Inference Route (inference.local)"; + } + return `${config.endpointType} (${config.endpointUrl})`; +} +function describeOnboardProvider(config) { + if (config.providerLabel) { + return config.providerLabel; + } + switch (config.endpointType) { + case "build": + return "NVIDIA Cloud API"; + case "ollama": + return "Local Ollama"; + case "vllm": + return "Local vLLM"; + case "nim-local": + return "Local NIM"; + case "ncp": + return "NVIDIA Cloud Partner"; + case "custom": + return "Managed Inference Route"; + default: + return "Unknown"; + } +} let configDirCreated = false; function ensureConfigDir() { if (configDirCreated) diff --git a/nemoclaw/dist/onboard/config.js.map b/nemoclaw/dist/onboard/config.js.map index 5328a59b89..5df39df24c 100644 --- a/nemoclaw/dist/onboard/config.js.map +++ b/nemoclaw/dist/onboard/config.js.map @@ -1 +1 @@ -{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/onboard/config.ts"],"names":[],"mappings":";AAAA,mGAAmG;AACnG,sCAAsC;;AAiCtC,8CAOC;AAED,8CAGC;AAED,gDAKC;AAlDD,qCAAyF;AACzF,yCAAiC;AAEjC,MAAM,UAAU,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,WAAW,CAAC,CAAC;AAcjE,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,eAAe;IACtB,IAAI,gBAAgB;QAAE,OAAO;IAC7B,IAAI,CAAC,IAAA,oBAAU,EAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,IAAA,mBAAS,EAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAA,gBAAI,EAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,iBAAiB;IAC/B,eAAe,EAAE,CAAC;IAClB,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAA0B,CAAC;AAC1E,CAAC;AAED,SAAgB,iBAAiB,CAAC,MAA6B;IAC7D,eAAe,EAAE,CAAC;IAClB,IAAA,uBAAa,EAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAgB,kBAAkB;IAChC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,IAAA,oBAAU,EAAC,IAAI,CAAC,CAAC;IACnB,CAAC;AACH,CAAC"} \ No newline at end of file +{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/onboard/config.ts"],"names":[],"mappings":";AAAA,mGAAmG;AACnG,sCAAsC;;AAqBtC,0DAMC;AAED,0DAqBC;AAgBD,8CAOC;AAED,8CAGC;AAED,gDAKC;AAnFD,qCAAyF;AACzF,yCAAiC;AAEjC,MAAM,UAAU,GAAG,IAAA,gBAAI,EAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,WAAW,CAAC,CAAC;AAgBjE,SAAgB,uBAAuB,CAAC,MAA6B;IACnE,IAAI,MAAM,CAAC,WAAW,KAAK,4BAA4B,EAAE,CAAC;QACxD,OAAO,2CAA2C,CAAC;IACrD,CAAC;IAED,OAAO,GAAG,MAAM,CAAC,YAAY,KAAK,MAAM,CAAC,WAAW,GAAG,CAAC;AAC1D,CAAC;AAED,SAAgB,uBAAuB,CAAC,MAA6B;IACnE,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO,MAAM,CAAC,aAAa,CAAC;IAC9B,CAAC;IAED,QAAQ,MAAM,CAAC,YAAY,EAAE,CAAC;QAC5B,KAAK,OAAO;YACV,OAAO,kBAAkB,CAAC;QAC5B,KAAK,QAAQ;YACX,OAAO,cAAc,CAAC;QACxB,KAAK,MAAM;YACT,OAAO,YAAY,CAAC;QACtB,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,KAAK;YACR,OAAO,sBAAsB,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,yBAAyB,CAAC;QACnC;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,eAAe;IACtB,IAAI,gBAAgB;QAAE,OAAO;IAC7B,IAAI,CAAC,IAAA,oBAAU,EAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,IAAA,mBAAS,EAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,IAAA,gBAAI,EAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,iBAAiB;IAC/B,eAAe,EAAE,CAAC;IAClB,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAA0B,CAAC;AAC1E,CAAC;AAED,SAAgB,iBAAiB,CAAC,MAA6B;IAC7D,eAAe,EAAE,CAAC;IAClB,IAAA,uBAAa,EAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAgB,kBAAkB;IAChC,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACrB,IAAA,oBAAU,EAAC,IAAI,CAAC,CAAC;IACnB,CAAC;AACH,CAAC"} \ No newline at end of file diff --git a/nemoclaw/src/cli.ts b/nemoclaw/src/cli.ts index 35694f31ab..336dc2c347 100644 --- a/nemoclaw/src/cli.ts +++ b/nemoclaw/src/cli.ts @@ -110,7 +110,7 @@ export function registerCliCommands(ctx: PluginCliContext, api: OpenClawPluginAp .command("onboard") .description("Interactive setup: configure inference endpoint, credential, and model") .option("--api-key ", "API key for endpoints that require one (skips prompt)") - .option("--endpoint ", "Endpoint type: build, ncp, nim-local, vllm, ollama, custom (local options are experimental)") + .option("--endpoint ", "Endpoint type: build, ncp, ollama, nim-local, vllm, custom (nim-local and vllm are experimental)") .option("--ncp-partner ", "NCP partner name (when endpoint is ncp)") .option("--endpoint-url ", "Endpoint URL (for ncp, nim-local, ollama, or custom)") .option("--model ", "Model ID to use") diff --git a/nemoclaw/src/commands/onboard.ts b/nemoclaw/src/commands/onboard.ts index a2b9a651ca..72fb9fcdd4 100644 --- a/nemoclaw/src/commands/onboard.ts +++ b/nemoclaw/src/commands/onboard.ts @@ -4,6 +4,8 @@ import { execFileSync, execSync } from "node:child_process"; import type { PluginLogger, NemoClawConfig } from "../index.js"; import { + describeOnboardEndpoint, + describeOnboardProvider, loadOnboardConfig, saveOnboardConfig, type EndpointType, @@ -23,7 +25,7 @@ export interface OnboardOptions { } const ENDPOINT_TYPES: EndpointType[] = ["build", "ncp", "nim-local", "vllm", "ollama", "custom"]; -const SUPPORTED_ENDPOINT_TYPES: EndpointType[] = ["build", "ncp"]; +const SUPPORTED_ENDPOINT_TYPES: EndpointType[] = ["build", "ncp", "ollama"]; function isExperimentalEnabled(): boolean { return process.env.NEMOCLAW_EXPERIMENTAL === "1"; @@ -34,10 +36,13 @@ const HOST_GATEWAY_URL = "http://host.openshell.internal"; const DEFAULT_MODELS = [ { id: "nvidia/nemotron-3-super-120b-a12b", label: "Nemotron 3 Super 120B" }, - { id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", label: "Nemotron Ultra 253B" }, - { id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", label: "Nemotron Super 49B v1.5" }, - { id: "nvidia/nemotron-3-nano-30b-a3b", label: "Nemotron 3 Nano 30B" }, + { id: "moonshotai/kimi-k2.5", label: "Kimi K2.5" }, + { id: "z-ai/glm5", label: "GLM-5" }, + { id: "minimaxai/minimax-m2.5", label: "MiniMax M2.5" }, + { id: "qwen/qwen3.5-397b-a17b", label: "Qwen3.5 397B A17B" }, + { id: "openai/gpt-oss-120b", label: "GPT-OSS 120B" }, ]; +const DEFAULT_OLLAMA_MODEL = "nemotron-3-nano:30b"; function resolveProfile(endpointType: EndpointType): string { switch (endpointType) { @@ -120,6 +125,32 @@ function detectOllama(): { installed: boolean; running: boolean } { return { installed, running }; } +function parseOllamaList(output: string): string[] { + return output + .split(/\r?\n/) + .map((line) => line.trim()) + .filter(Boolean) + .filter((line) => !/^NAME\s+/i.test(line)) + .map((line) => line.split(/\s{2,}/)[0]) + .filter(Boolean); +} + +function getOllamaModelOptions(): string[] { + try { + const output = execSync("ollama list", { encoding: "utf-8", shell: "/bin/bash" }); + const parsed = parseOllamaList(output); + if (parsed.length > 0) { + return parsed; + } + } catch {} + return [DEFAULT_OLLAMA_MODEL]; +} + +function getDefaultOllamaModel(): string { + const models = getOllamaModelOptions(); + return models.includes(DEFAULT_OLLAMA_MODEL) ? DEFAULT_OLLAMA_MODEL : models[0]; +} + function testCommand(command: string): boolean { try { execSync(command, { encoding: "utf-8", stdio: "ignore", shell: "/bin/bash" }); @@ -130,7 +161,8 @@ function testCommand(command: string): boolean { } function showConfig(config: NemoClawOnboardConfig, logger: PluginLogger): void { - logger.info(` Endpoint: ${config.endpointType} (${config.endpointUrl})`); + logger.info(` Endpoint: ${describeOnboardEndpoint(config)}`); + logger.info(` Provider: ${describeOnboardProvider(config)}`); if (config.ncpPartner) { logger.info(` NCP Partner: ${config.ncpPartner}`); } @@ -156,6 +188,16 @@ async function promptEndpoint( }, ]; + options.push({ + label: "Local Ollama", + value: "ollama", + hint: ollama.running + ? "detected on localhost:11434" + : ollama.installed + ? "installed locally" + : "localhost:11434", + }); + if (isExperimentalEnabled()) { options.push( { @@ -168,11 +210,6 @@ async function promptEndpoint( value: "vllm", hint: "experimental — local development", }, - { - label: "Local Ollama [experimental]", - value: "ollama", - hint: `experimental — ${ollama.installed ? "installed locally" : "localhost:11434"}`, - }, ); } @@ -228,12 +265,11 @@ export async function cliOnboard(opts: OnboardOptions): Promise { endpointType = ep; } else { const ollama = detectOllama(); - if (ollama.running && isExperimentalEnabled()) { - logger.info("Detected Ollama on localhost:11434. Using it for onboarding."); - endpointType = "ollama"; - } else { - endpointType = await promptEndpoint(ollama); + if (ollama.running) { + logger.info("Detected local inference option: Ollama."); + logger.info("Select it explicitly if you want to use it."); } + endpointType = await promptEndpoint(ollama); } // Step 2: Endpoint URL resolution @@ -331,24 +367,55 @@ export async function cliOnboard(opts: OnboardOptions): Promise { if (opts.model) { model = opts.model; } else { - // Build model options: prefer Nemotron models from the endpoint, fall back to defaults - const nemotronModels = validation.models.filter((m) => m.includes("nemotron")); + const discoveredModelOptions = + endpointType === "ollama" + ? getOllamaModelOptions().map((id) => ({ label: id, value: id })) + : validation.models.map((id) => ({ label: id, value: id })); + const curatedCloudOptions = + endpointType === "build" || endpointType === "ncp" + ? DEFAULT_MODELS.filter((option) => validation.models.includes(option.id)).map((option) => ({ + label: `${option.label} (${option.id})`, + value: option.id, + })) + : []; + const defaultIndex = + endpointType === "ollama" + ? Math.max( + 0, + discoveredModelOptions.findIndex((option) => option.value === getDefaultOllamaModel()), + ) + : 0; const modelOptions = - nemotronModels.length > 0 - ? nemotronModels.map((id) => ({ label: id, value: id })) - : DEFAULT_MODELS.map((m) => ({ label: `${m.label} (${m.id})`, value: m.id })); + curatedCloudOptions.length > 0 + ? curatedCloudOptions + : discoveredModelOptions.length > 0 + ? discoveredModelOptions + : DEFAULT_MODELS.map((m) => ({ label: `${m.label} (${m.id})`, value: m.id })); - model = await promptSelect("Select your primary model:", modelOptions); + model = await promptSelect("Select your primary model:", modelOptions, defaultIndex); } // Step 6: Resolve profile const profile = resolveProfile(endpointType); const providerName = resolveProviderName(endpointType); + const summaryConfig: NemoClawOnboardConfig = { + endpointType, + endpointUrl, + ncpPartner, + model, + profile, + credentialEnv, + provider: providerName, + providerLabel: undefined, + onboardedAt: "", + }; + summaryConfig.providerLabel = describeOnboardProvider(summaryConfig); // Step 7: Confirmation logger.info(""); logger.info("Configuration summary:"); - logger.info(` Endpoint: ${endpointType} (${endpointUrl})`); + logger.info(` Endpoint: ${describeOnboardEndpoint(summaryConfig)}`); + logger.info(` Provider: ${summaryConfig.providerLabel}`); if (ncpPartner) { logger.info(` NCP Partner: ${ncpPartner}`); } @@ -436,6 +503,8 @@ export async function cliOnboard(opts: OnboardOptions): Promise { model, profile, credentialEnv, + provider: providerName, + providerLabel: summaryConfig.providerLabel, onboardedAt: new Date().toISOString(), }); @@ -443,7 +512,8 @@ export async function cliOnboard(opts: OnboardOptions): Promise { logger.info(""); logger.info("Onboarding complete!"); logger.info(""); - logger.info(` Endpoint: ${endpointUrl}`); + logger.info(` Endpoint: ${describeOnboardEndpoint(summaryConfig)}`); + logger.info(` Provider: ${summaryConfig.providerLabel}`); logger.info(` Model: ${model}`); logger.info(` Credential: $${credentialEnv}`); logger.info(""); diff --git a/nemoclaw/src/commands/slash.ts b/nemoclaw/src/commands/slash.ts index f0292eabfb..bd9afa295d 100644 --- a/nemoclaw/src/commands/slash.ts +++ b/nemoclaw/src/commands/slash.ts @@ -12,7 +12,11 @@ import type { PluginCommandContext, PluginCommandResult, OpenClawPluginApi } from "../index.js"; import { loadState } from "../blueprint/state.js"; -import { loadOnboardConfig } from "../onboard/config.js"; +import { + describeOnboardEndpoint, + describeOnboardProvider, + loadOnboardConfig, +} from "../onboard/config.js"; export function handleSlashCommand( ctx: PluginCommandContext, @@ -87,7 +91,8 @@ function slashOnboard(): PluginCommandResult { text: [ "**NemoClaw Onboard Status**", "", - `Endpoint: ${config.endpointType} (${config.endpointUrl})`, + `Endpoint: ${describeOnboardEndpoint(config)}`, + `Provider: ${describeOnboardProvider(config)}`, config.ncpPartner ? `NCP Partner: ${config.ncpPartner}` : null, `Model: ${config.model}`, `Credential: $${config.credentialEnv}`, diff --git a/nemoclaw/src/index.ts b/nemoclaw/src/index.ts index 796564d8be..f6defcd41b 100644 --- a/nemoclaw/src/index.ts +++ b/nemoclaw/src/index.ts @@ -14,7 +14,11 @@ import type { Command } from "commander"; import { registerCliCommands } from "./cli.js"; import { handleSlashCommand } from "./commands/slash.js"; -import { loadOnboardConfig } from "./onboard/config.js"; +import { + describeOnboardEndpoint, + describeOnboardProvider, + loadOnboardConfig, +} from "./onboard/config.js"; // --------------------------------------------------------------------------- // OpenClaw Plugin SDK compatible types (mirrors openclaw/plugin-sdk) @@ -143,6 +147,65 @@ export interface NemoClawConfig { inferenceProvider: string; } +function activeModelEntries(onboardCfg: ReturnType): ModelProviderEntry[] { + if (!onboardCfg?.model) { + return [ + { + id: "nvidia/nemotron-3-super-120b-a12b", + label: "Nemotron 3 Super 120B (March 2026)", + contextWindow: 131072, + maxOutput: 8192, + }, + { + id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", + label: "Nemotron Ultra 253B", + contextWindow: 131072, + maxOutput: 4096, + }, + { + id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", + label: "Nemotron Super 49B v1.5", + contextWindow: 131072, + maxOutput: 4096, + }, + { + id: "nvidia/nemotron-3-nano-30b-a3b", + label: "Nemotron 3 Nano 30B", + contextWindow: 131072, + maxOutput: 4096, + }, + ]; + } + + return [ + { + id: `inference/${onboardCfg.model}`, + label: onboardCfg.model, + contextWindow: 131072, + maxOutput: 8192, + }, + ]; +} + +function registeredProviderForConfig( + onboardCfg: ReturnType, + providerCredentialEnv: string, +): ProviderPlugin { + const authLabel = + providerCredentialEnv === "NVIDIA_API_KEY" + ? `NVIDIA API Key (${providerCredentialEnv})` + : `OpenAI API Key (${providerCredentialEnv})`; + + return { + id: "inference", + label: "Managed Inference Route", + aliases: ["inference-local", "nemoclaw"], + envVars: [providerCredentialEnv], + models: { chat: activeModelEntries(onboardCfg) }, + auth: [{ type: "bearer", envVar: providerCredentialEnv, headerName: "Authorization", label: authLabel }], + }; +} + const DEFAULT_PLUGIN_CONFIG: NemoClawConfig = { blueprintVersion: "latest", blueprintRegistry: "ghcr.io/nvidia/nemoclaw-blueprint", @@ -196,55 +259,10 @@ export default function register(api: OpenClawPluginApi): void { // 3. Register nvidia-nim provider — use onboard config if available const onboardCfg = loadOnboardConfig(); const providerCredentialEnv = onboardCfg?.credentialEnv ?? "NVIDIA_API_KEY"; - const providerLabel = onboardCfg - ? `NVIDIA NIM (${onboardCfg.endpointType}${onboardCfg.ncpPartner ? ` - ${onboardCfg.ncpPartner}` : ""})` - : "NVIDIA NIM (build.nvidia.com)"; - - api.registerProvider({ - id: "nvidia-nim", - label: providerLabel, - docsPath: "https://build.nvidia.com/docs", - aliases: ["nvidia", "nim"], - envVars: [providerCredentialEnv], - models: { - chat: [ - { - id: "nvidia/nemotron-3-super-120b-a12b", - label: "Nemotron 3 Super 120B (March 2026)", - contextWindow: 131072, - maxOutput: 8192, - }, - { - id: "nvidia/llama-3.1-nemotron-ultra-253b-v1", - label: "Nemotron Ultra 253B", - contextWindow: 131072, - maxOutput: 4096, - }, - { - id: "nvidia/llama-3.3-nemotron-super-49b-v1.5", - label: "Nemotron Super 49B v1.5", - contextWindow: 131072, - maxOutput: 4096, - }, - { - id: "nvidia/nemotron-3-nano-30b-a3b", - label: "Nemotron 3 Nano 30B", - contextWindow: 131072, - maxOutput: 4096, - }, - ], - }, - auth: [ - { - type: "bearer", - envVar: providerCredentialEnv, - headerName: "Authorization", - label: `NVIDIA API Key (${providerCredentialEnv})`, - }, - ], - }); + api.registerProvider(registeredProviderForConfig(onboardCfg, providerCredentialEnv)); - const bannerEndpoint = onboardCfg?.endpointType ?? "build.nvidia.com"; + const bannerEndpoint = onboardCfg ? describeOnboardEndpoint(onboardCfg) : "build.nvidia.com"; + const bannerProvider = onboardCfg ? describeOnboardProvider(onboardCfg) : "NVIDIA Cloud API"; const bannerModel = onboardCfg?.model ?? "nvidia/nemotron-3-super-120b-a12b"; api.logger.info(""); @@ -252,6 +270,7 @@ export default function register(api: OpenClawPluginApi): void { api.logger.info(" │ NemoClaw registered │"); api.logger.info(" │ │"); api.logger.info(` │ Endpoint: ${bannerEndpoint.padEnd(40)}│`); + api.logger.info(` │ Provider: ${bannerProvider.padEnd(40)}│`); api.logger.info(` │ Model: ${bannerModel.padEnd(40)}│`); api.logger.info(" │ Commands: openclaw nemoclaw │"); api.logger.info(" └─────────────────────────────────────────────────────┘"); diff --git a/nemoclaw/src/onboard/config.ts b/nemoclaw/src/onboard/config.ts index 80a1d6efc8..82082b9697 100644 --- a/nemoclaw/src/onboard/config.ts +++ b/nemoclaw/src/onboard/config.ts @@ -15,9 +15,42 @@ export interface NemoClawOnboardConfig { model: string; profile: string; credentialEnv: string; + provider?: string; + providerLabel?: string; onboardedAt: string; } +export function describeOnboardEndpoint(config: NemoClawOnboardConfig): string { + if (config.endpointUrl === "https://inference.local/v1") { + return "Managed Inference Route (inference.local)"; + } + + return `${config.endpointType} (${config.endpointUrl})`; +} + +export function describeOnboardProvider(config: NemoClawOnboardConfig): string { + if (config.providerLabel) { + return config.providerLabel; + } + + switch (config.endpointType) { + case "build": + return "NVIDIA Cloud API"; + case "ollama": + return "Local Ollama"; + case "vllm": + return "Local vLLM"; + case "nim-local": + return "Local NIM"; + case "ncp": + return "NVIDIA Cloud Partner"; + case "custom": + return "Managed Inference Route"; + default: + return "Unknown"; + } +} + let configDirCreated = false; function ensureConfigDir(): void { diff --git a/scripts/fix-coredns.sh b/scripts/fix-coredns.sh index 7afb101cff..512ba851e6 100755 --- a/scripts/fix-coredns.sh +++ b/scripts/fix-coredns.sh @@ -19,16 +19,11 @@ set -euo pipefail GATEWAY_NAME="${1:-}" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# shellcheck source=./lib/runtime.sh +. "$SCRIPT_DIR/lib/runtime.sh" -# Find Colima socket (legacy or XDG path) -COLIMA_SOCKET="" -for _sock in "$HOME/.colima/default/docker.sock" "$HOME/.config/colima/default/docker.sock"; do - if [ -S "$_sock" ]; then - COLIMA_SOCKET="$_sock" - break - fi -done -unset _sock +COLIMA_SOCKET="$(find_colima_docker_socket || true)" if [ -z "${DOCKER_HOST:-}" ]; then if [ -n "$COLIMA_SOCKET" ]; then @@ -40,31 +35,29 @@ if [ -z "${DOCKER_HOST:-}" ]; then fi # Find the cluster container -CLUSTER=$(docker ps --filter "name=openshell-cluster" --format '{{.Names}}' | head -1) +CLUSTERS="$(docker ps --filter "name=openshell-cluster" --format '{{.Names}}')" +CLUSTER="$(select_openshell_cluster_container "$GATEWAY_NAME" "$CLUSTERS" || true)" if [ -z "$CLUSTER" ]; then - echo "ERROR: No openshell cluster container found." + if [ -n "$GATEWAY_NAME" ]; then + echo "ERROR: Could not uniquely determine the openshell cluster container for gateway '$GATEWAY_NAME'." + else + echo "ERROR: Could not uniquely determine the openshell cluster container." + fi exit 1 fi -# Get the container's upstream DNS from /etc/resolv.conf — this is the address -# the Docker/Colima VM uses for DNS and is reachable from k3s pods. -# The docker bridge gateway (172.17.0.1) does NOT serve DNS in Colima. -GATEWAY_IP=$(docker exec "$CLUSTER" grep nameserver /etc/resolv.conf | head -1 | awk '{print $2}') -if [ -z "$GATEWAY_IP" ]; then - echo "ERROR: Could not determine container gateway IP." - exit 1 -fi +CONTAINER_RESOLV_CONF="$(docker exec "$CLUSTER" cat /etc/resolv.conf 2>/dev/null || true)" +HOST_RESOLV_CONF="$(cat /etc/resolv.conf 2>/dev/null || true)" +UPSTREAM_DNS="$(resolve_coredns_upstream "$CONTAINER_RESOLV_CONF" "$HOST_RESOLV_CONF" "colima" || true)" -# Sanity check: don't use 127.x.x.x — it won't work from pods -if [[ "$GATEWAY_IP" == 127.* ]]; then - echo "ERROR: Gateway IP is $GATEWAY_IP (loopback). Cannot use from k3s pods." - echo "Falling back to public DNS (8.8.8.8)." - GATEWAY_IP="8.8.8.8" +if [ -z "$UPSTREAM_DNS" ]; then + echo "ERROR: Could not determine a non-loopback DNS upstream for Colima." + exit 1 fi -echo "Patching CoreDNS to forward to $GATEWAY_IP..." +echo "Patching CoreDNS to forward to $UPSTREAM_DNS..." -docker exec "$CLUSTER" kubectl patch configmap coredns -n kube-system --type merge -p "{\"data\":{\"Corefile\":\".:53 {\\n errors\\n health\\n ready\\n kubernetes cluster.local in-addr.arpa ip6.arpa {\\n pods insecure\\n fallthrough in-addr.arpa ip6.arpa\\n }\\n hosts /etc/coredns/NodeHosts {\\n ttl 60\\n reload 15s\\n fallthrough\\n }\\n prometheus :9153\\n cache 30\\n loop\\n reload\\n loadbalance\\n forward . $GATEWAY_IP\\n}\\n\"}}" > /dev/null +docker exec "$CLUSTER" kubectl patch configmap coredns -n kube-system --type merge -p "{\"data\":{\"Corefile\":\".:53 {\\n errors\\n health\\n ready\\n kubernetes cluster.local in-addr.arpa ip6.arpa {\\n pods insecure\\n fallthrough in-addr.arpa ip6.arpa\\n }\\n hosts /etc/coredns/NodeHosts {\\n ttl 60\\n reload 15s\\n fallthrough\\n }\\n prometheus :9153\\n cache 30\\n loop\\n reload\\n loadbalance\\n forward . $UPSTREAM_DNS\\n}\\n\"}}" > /dev/null docker exec "$CLUSTER" kubectl rollout restart deploy/coredns -n kube-system > /dev/null diff --git a/scripts/install.sh b/scripts/install.sh index 9bd5af4f65..fb51bcfae6 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -18,6 +18,84 @@ info() { echo -e "${GREEN}[install]${NC} $1"; } warn() { echo -e "${YELLOW}[install]${NC} $1"; } fail() { echo -e "${RED}[install]${NC} $1"; exit 1; } +define_runtime_helpers() { + socket_exists() { + local socket_path="$1" + + if [ -n "${NEMOCLAW_TEST_SOCKET_PATHS:-}" ]; then + case ":$NEMOCLAW_TEST_SOCKET_PATHS:" in + *":$socket_path:"*) return 0 ;; + esac + fi + + [ -S "$socket_path" ] + } + + find_colima_docker_socket() { + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path + + for socket_path in \ + "$home_dir/.colima/default/docker.sock" \ + "$home_dir/.config/colima/default/docker.sock" + do + if socket_exists "$socket_path"; then + printf '%s\n' "$socket_path" + return 0 + fi + done + + return 1 + } + + find_docker_desktop_socket() { + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path="$home_dir/.docker/run/docker.sock" + + if socket_exists "$socket_path"; then + printf '%s\n' "$socket_path" + return 0 + fi + + return 1 + } + + detect_docker_host() { + if [ -n "${DOCKER_HOST:-}" ]; then + printf '%s\n' "$DOCKER_HOST" + return 0 + fi + + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path + + if socket_path="$(find_colima_docker_socket "$home_dir")"; then + printf 'unix://%s\n' "$socket_path" + return 0 + fi + + if socket_path="$(find_docker_desktop_socket "$home_dir")"; then + printf 'unix://%s\n' "$socket_path" + return 0 + fi + + return 1 + } +} + +SCRIPT_PATH="${BASH_SOURCE[0]-}" +SCRIPT_DIR="" +if [ -n "$SCRIPT_PATH" ]; then + SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" +fi + +if [ -n "$SCRIPT_DIR" ] && [ -f "$SCRIPT_DIR/lib/runtime.sh" ]; then + # shellcheck source=/dev/null + . "$SCRIPT_DIR/lib/runtime.sh" +else + define_runtime_helpers +fi + # Ensure nvm environment is loaded in the current shell. ensure_nvm_loaded() { if [ -z "${NVM_DIR:-}" ]; then @@ -177,6 +255,23 @@ install_docker() { if command -v docker > /dev/null 2>&1; then # Docker installed but not running if [ "$OS" = "Darwin" ]; then + local colima_socket="" + local docker_desktop_socket="" + colima_socket="$(find_colima_docker_socket || true)" + docker_desktop_socket="$(find_docker_desktop_socket || true)" + + if [ -n "${DOCKER_HOST:-}" ]; then + fail "Docker is installed but the selected runtime is not running. Start the runtime behind DOCKER_HOST (${DOCKER_HOST}) and re-run." + fi + + if [ -n "$colima_socket" ] && [ -n "$docker_desktop_socket" ]; then + fail "Both Colima and Docker Desktop are available on this Mac. Start the runtime you want explicitly and re-run, or set DOCKER_HOST to select one." + fi + + if [ -n "$docker_desktop_socket" ]; then + fail "Docker Desktop appears to be installed but is not running. Start Docker Desktop and re-run." + fi + if command -v colima > /dev/null 2>&1; then info "Starting Colima..." colima start @@ -268,9 +363,9 @@ install_openshell info "Installing nemoclaw CLI..." if [ "$NODE_MGR" = "nodesource" ]; then - sudo npm install -g nemoclaw + sudo npm install -g git+https://github.com/NVIDIA/NemoClaw.git else - npm install -g nemoclaw + npm install -g git+https://github.com/NVIDIA/NemoClaw.git fi if [ "$NEED_RESHIM" = true ]; then diff --git a/scripts/lib/runtime.sh b/scripts/lib/runtime.sh new file mode 100644 index 0000000000..3bf546847e --- /dev/null +++ b/scripts/lib/runtime.sh @@ -0,0 +1,229 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +socket_exists() { + local socket_path="$1" + + if [ -n "${NEMOCLAW_TEST_SOCKET_PATHS:-}" ]; then + case ":$NEMOCLAW_TEST_SOCKET_PATHS:" in + *":$socket_path:"*) return 0 ;; + esac + fi + + [ -S "$socket_path" ] +} + +find_colima_docker_socket() { + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path + + for socket_path in \ + "$home_dir/.colima/default/docker.sock" \ + "$home_dir/.config/colima/default/docker.sock" + do + if socket_exists "$socket_path"; then + printf '%s\n' "$socket_path" + return 0 + fi + done + + return 1 +} + +find_docker_desktop_socket() { + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path="$home_dir/.docker/run/docker.sock" + + if socket_exists "$socket_path"; then + printf '%s\n' "$socket_path" + return 0 + fi + + return 1 +} + +detect_docker_host() { + if [ -n "${DOCKER_HOST:-}" ]; then + printf '%s\n' "$DOCKER_HOST" + return 0 + fi + + local home_dir="${1:-${HOME:-/tmp}}" + local socket_path + + if socket_path="$(find_colima_docker_socket "$home_dir")"; then + printf 'unix://%s\n' "$socket_path" + return 0 + fi + + if socket_path="$(find_docker_desktop_socket "$home_dir")"; then + printf 'unix://%s\n' "$socket_path" + return 0 + fi + + return 1 +} + +docker_host_runtime() { + local docker_host="${1:-${DOCKER_HOST:-}}" + + case "$docker_host" in + unix://*"/.colima/default/docker.sock"|unix://*"/.config/colima/default/docker.sock") + printf 'colima\n' + ;; + unix://*"/.docker/run/docker.sock") + printf 'docker-desktop\n' + ;; + "") + return 1 + ;; + *) + printf 'custom\n' + ;; + esac +} + +infer_container_runtime_from_info() { + local info="${1:-}" + local normalized + normalized="$(printf '%s' "$info" | tr '[:upper:]' '[:lower:]')" + + if [[ -z "${normalized// }" ]]; then + printf 'unknown\n' + elif [[ "$normalized" == *podman* ]]; then + printf 'podman\n' + elif [[ "$normalized" == *colima* ]]; then + printf 'colima\n' + elif [[ "$normalized" == *"docker desktop"* ]]; then + printf 'docker-desktop\n' + elif [[ "$normalized" == *docker* ]]; then + printf 'docker\n' + else + printf 'unknown\n' + fi +} + +is_unsupported_macos_runtime() { + local platform="${1:-$(uname -s)}" + local runtime="${2:-unknown}" + + [ "$platform" = "Darwin" ] && [ "$runtime" = "podman" ] +} + +is_loopback_ip() { + local ip="${1:-}" + [[ "$ip" == 127.* ]] +} + +first_non_loopback_nameserver() { + local resolv_conf="${1:-}" + + if [ -z "$resolv_conf" ]; then + return 1 + fi + + printf '%s\n' "$resolv_conf" \ + | awk '$1 == "nameserver" && $2 !~ /^127\./ { print $2; exit }' +} + +get_colima_vm_nameserver() { + if ! command -v colima > /dev/null 2>&1; then + return 1 + fi + + local profile="${COLIMA_PROFILE:-default}" + local resolv_conf + resolv_conf="$(colima ssh --profile "$profile" -- cat /etc/resolv.conf < /dev/null 2>/dev/null || true)" + first_non_loopback_nameserver "$resolv_conf" +} + +resolve_coredns_upstream() { + local container_resolv_conf="${1:-}" + local host_resolv_conf="${2:-}" + local runtime="${3:-unknown}" + local nameserver="" + + nameserver="$(first_non_loopback_nameserver "$container_resolv_conf" || true)" + if [ -n "$nameserver" ]; then + printf '%s\n' "$nameserver" + return 0 + fi + + if [ "$runtime" = "colima" ]; then + nameserver="$(get_colima_vm_nameserver || true)" + if [ -n "$nameserver" ]; then + printf '%s\n' "$nameserver" + return 0 + fi + fi + + nameserver="$(first_non_loopback_nameserver "$host_resolv_conf" || true)" + if [ -n "$nameserver" ]; then + printf '%s\n' "$nameserver" + return 0 + fi + + return 1 +} + +select_openshell_cluster_container() { + local gateway_name="${1:-}" + local containers="${2:-}" + local matches="" + local count=0 + local match_count=0 + + if [ -z "$containers" ]; then + return 1 + fi + + count="$(printf '%s\n' "$containers" | awk 'NF { count += 1 } END { print count + 0 }')" + + if [ -n "$gateway_name" ]; then + matches="$(printf '%s\n' "$containers" | grep -F -- "$gateway_name" || true)" + match_count="$(printf '%s\n' "$matches" | awk 'NF { count += 1 } END { print count + 0 }')" + + if [ "$match_count" -eq 1 ]; then + printf '%s\n' "$matches" + return 0 + fi + + if [ "$match_count" -gt 1 ]; then + return 1 + fi + fi + + if [ "$count" -eq 1 ]; then + printf '%s\n' "$containers" + return 0 + fi + + return 1 +} + +get_local_provider_base_url() { + local provider="${1:-}" + + case "$provider" in + vllm-local) printf 'http://host.openshell.internal:8000/v1\n' ;; + ollama-local) printf 'http://host.openshell.internal:11434/v1\n' ;; + *) return 1 ;; + esac +} + +check_local_provider_health() { + local provider="${1:-}" + + case "$provider" in + vllm-local) + curl -sf http://localhost:8000/v1/models > /dev/null 2>&1 + ;; + ollama-local) + curl -sf http://localhost:11434/api/tags > /dev/null 2>&1 + ;; + *) + return 1 + ;; + esac +} diff --git a/scripts/nemoclaw-start.sh b/scripts/nemoclaw-start.sh index 215973520e..0fe41648c8 100755 --- a/scripts/nemoclaw-start.sh +++ b/scripts/nemoclaw-start.sh @@ -30,7 +30,9 @@ if os.path.exists(config_path): with open(config_path) as f: cfg = json.load(f) -cfg.setdefault('agents', {}).setdefault('defaults', {}).setdefault('model', {})['primary'] = 'nvidia/nemotron-3-super-120b-a12b' +default_model = os.environ.get('NEMOCLAW_MODEL') +if default_model: + cfg.setdefault('agents', {}).setdefault('defaults', {}).setdefault('model', {})['primary'] = default_model chat_ui_url = os.environ.get('CHAT_UI_URL', 'http://127.0.0.1:18789') parsed = urlparse(chat_ui_url) @@ -168,7 +170,6 @@ PYAUTOPAIR echo 'Setting up NemoClaw...' openclaw doctor --fix > /dev/null 2>&1 || true -openclaw models set nvidia/nemotron-3-super-120b-a12b > /dev/null 2>&1 || true write_auth_profile export CHAT_UI_URL PUBLIC_PORT fix_openclaw_config diff --git a/scripts/setup.sh b/scripts/setup.sh index be01c9eee5..e0859a6f50 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -29,6 +29,11 @@ GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +# shellcheck source=./lib/runtime.sh +. "$SCRIPT_DIR/lib/runtime.sh" + info() { echo -e "${GREEN}>>>${NC} $1"; } warn() { echo -e "${YELLOW}>>>${NC} $1"; } fail() { echo -e "${RED}>>>${NC} $1"; exit 1; } @@ -51,16 +56,25 @@ upsert_provider() { fi } -# Resolve DOCKER_HOST for Colima if needed (legacy ~/.colima or XDG ~/.config/colima) -if [ -z "${DOCKER_HOST:-}" ]; then - for _sock in "$HOME/.colima/default/docker.sock" "$HOME/.config/colima/default/docker.sock"; do - if [ -S "$_sock" ]; then - export DOCKER_HOST="unix://$_sock" - warn "Using Colima Docker socket: $_sock" - break - fi - done - unset _sock +# Resolve DOCKER_HOST for macOS user-scoped runtimes when needed. +ORIGINAL_DOCKER_HOST="${DOCKER_HOST:-}" +if docker_host="$(detect_docker_host)"; then + export DOCKER_HOST="$docker_host" + if [ -n "$ORIGINAL_DOCKER_HOST" ]; then + warn "Using DOCKER_HOST from environment: $docker_host" + else + case "$(docker_host_runtime "$docker_host" || true)" in + colima) + warn "Using Colima Docker socket: ${docker_host#unix://}" + ;; + docker-desktop) + warn "Using Docker Desktop socket: ${docker_host#unix://}" + ;; + custom) + warn "Using Docker host: $docker_host" + ;; + esac + fi fi # Check prerequisites @@ -68,8 +82,13 @@ command -v openshell > /dev/null || fail "openshell CLI not found. Install the b command -v docker > /dev/null || fail "docker not found" [ -n "${NVIDIA_API_KEY:-}" ] || fail "NVIDIA_API_KEY not set. Get one from build.nvidia.com" -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +CONTAINER_RUNTIME="$(infer_container_runtime_from_info "$(docker info 2>/dev/null || true)")" +if is_unsupported_macos_runtime "$(uname -s)" "$CONTAINER_RUNTIME"; then + fail "Podman on macOS is not supported yet. NemoClaw currently depends on OpenShell support for Podman on macOS. Use Colima or Docker Desktop instead." +fi +if [ "$CONTAINER_RUNTIME" != "unknown" ]; then + info "Container runtime: $CONTAINER_RUNTIME" +fi SANDBOX_NAME="${1:-nemoclaw}" info "Using sandbox name: ${SANDBOX_NAME}" @@ -91,9 +110,9 @@ done info "Gateway is healthy" # 2. CoreDNS fix (Colima only) -if [ -S "$HOME/.colima/default/docker.sock" ]; then +if [ "$CONTAINER_RUNTIME" = "colima" ]; then info "Patching CoreDNS for Colima..." - bash "$SCRIPT_DIR/fix-coredns.sh" 2>&1 || warn "CoreDNS patch failed (may not be needed)" + bash "$SCRIPT_DIR/fix-coredns.sh" nemoclaw 2>&1 || warn "CoreDNS patch failed (may not be needed)" fi # 3. Providers @@ -107,12 +126,13 @@ upsert_provider \ "OPENAI_BASE_URL=https://integrate.api.nvidia.com/v1" # vllm-local (if vLLM is installed or running) -if curl -s http://localhost:8000/v1/models > /dev/null 2>&1 || python3 -c "import vllm" 2>/dev/null; then +if check_local_provider_health "vllm-local" || python3 -c "import vllm" 2>/dev/null; then + VLLM_LOCAL_BASE_URL="$(get_local_provider_base_url "vllm-local")" upsert_provider \ "vllm-local" \ "openai" \ "OPENAI_API_KEY=dummy" \ - "OPENAI_BASE_URL=http://host.openshell.internal:8000/v1" + "OPENAI_BASE_URL=$VLLM_LOCAL_BASE_URL" fi # 4a. Ollama (macOS local inference) @@ -123,16 +143,17 @@ if [ "$(uname -s)" = "Darwin" ]; then fi if command -v ollama > /dev/null 2>&1; then # Start Ollama service if not running - if ! curl -s http://localhost:11434/api/tags > /dev/null 2>&1; then + if ! check_local_provider_health "ollama-local"; then info "Starting Ollama service..." OLLAMA_HOST=0.0.0.0:11434 ollama serve > /dev/null 2>&1 & sleep 2 fi + OLLAMA_LOCAL_BASE_URL="$(get_local_provider_base_url "ollama-local")" upsert_provider \ "ollama-local" \ "openai" \ "OPENAI_API_KEY=ollama" \ - "OPENAI_BASE_URL=http://host.openshell.internal:11434/v1" + "OPENAI_BASE_URL=$OLLAMA_LOCAL_BASE_URL" fi fi diff --git a/scripts/smoke-macos-install.sh b/scripts/smoke-macos-install.sh new file mode 100644 index 0000000000..443ef86a95 --- /dev/null +++ b/scripts/smoke-macos-install.sh @@ -0,0 +1,318 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Run the primary NemoClaw install flow on a local machine, capture logs, +# then uninstall and verify cleanup. Intended for manual smoke validation. + +set -euo pipefail + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +info() { echo -e "${GREEN}[smoke]${NC} $1"; } +warn() { echo -e "${YELLOW}[smoke]${NC} $1"; } +fail() { echo -e "${RED}[smoke]${NC} $1"; exit 1; } + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +# shellcheck source=./lib/runtime.sh +. "$SCRIPT_DIR/lib/runtime.sh" + +SANDBOX_NAME="smoke-$(date +%Y%m%d%H%M%S)" +LOG_DIR="${TMPDIR:-/tmp}/nemoclaw-smoke" +RUNTIME="" +ALLOW_EXISTING_STATE=false +KEEP_LOGS=false +KEEP_OPEN_SHELL=true +DELETE_MODELS=false + +INSTALL_LOG="" +UNINSTALL_LOG="" +INSTALL_STATUS=1 +UNINSTALL_STATUS=1 +ANSWERS_PIPE="" +ANSWER_WRITER_PID="" +LOG_FOLLOW_PID="" + +stop_answer_writer() { + if [ -n "$ANSWER_WRITER_PID" ] && kill -0 "$ANSWER_WRITER_PID" 2>/dev/null; then + kill "$ANSWER_WRITER_PID" 2>/dev/null || true + wait "$ANSWER_WRITER_PID" 2>/dev/null || true + fi + ANSWER_WRITER_PID="" +} + +usage() { + cat <<'EOF' +Usage: ./scripts/smoke-macos-install.sh [options] + +Options: + --sandbox-name Sandbox name to feed into install.sh + --log-dir Directory for install/uninstall logs + --runtime Select runtime: colima or docker-desktop + --allow-existing-state Allow running even if NemoClaw/OpenShell state already exists + --keep-logs Preserve log files after success + --remove-openshell Allow uninstall.sh to remove openshell + --delete-models Allow uninstall.sh to delete Ollama models + -h, --help Show this help + +Environment: + NVIDIA_API_KEY Required for the cloud install path +EOF +} + +while [ $# -gt 0 ]; do + case "$1" in + --sandbox-name) + SANDBOX_NAME="${2:-}" + [ -n "$SANDBOX_NAME" ] || fail "--sandbox-name requires a value" + shift 2 + ;; + --log-dir) + LOG_DIR="${2:-}" + [ -n "$LOG_DIR" ] || fail "--log-dir requires a value" + shift 2 + ;; + --runtime) + RUNTIME="${2:-}" + [ -n "$RUNTIME" ] || fail "--runtime requires a value" + shift 2 + ;; + --allow-existing-state) + ALLOW_EXISTING_STATE=true + shift + ;; + --keep-logs) + KEEP_LOGS=true + shift + ;; + --remove-openshell) + KEEP_OPEN_SHELL=false + shift + ;; + --delete-models) + DELETE_MODELS=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + fail "Unknown argument: $1" + ;; + esac +done + +[ -n "${NVIDIA_API_KEY:-}" ] || fail "NVIDIA_API_KEY must be set for the smoke install flow." +[ -x "$REPO_DIR/install.sh" ] || fail "install.sh not found at repo root." +[ -x "$REPO_DIR/uninstall.sh" ] || fail "uninstall.sh not found at repo root." + +validate_sandbox_name() { + if ! [[ "$SANDBOX_NAME" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ ]]; then + fail "Invalid sandbox name '$SANDBOX_NAME'. Use lowercase letters, numbers, and hyphens." + fi +} + +select_runtime() { + case "$RUNTIME" in + "") + return 0 + ;; + colima) + local socket_path + socket_path="$(find_colima_docker_socket || true)" + [ -n "$socket_path" ] || fail "Requested runtime 'colima', but no Colima Docker socket was found." + export DOCKER_HOST="unix://$socket_path" + info "Using runtime 'colima' via $socket_path" + ;; + docker-desktop) + local socket_path + socket_path="$(find_docker_desktop_socket || true)" + [ -n "$socket_path" ] || fail "Requested runtime 'docker-desktop', but no Docker Desktop socket was found." + export DOCKER_HOST="unix://$socket_path" + info "Using runtime 'docker-desktop' via $socket_path" + ;; + *) + fail "Unsupported runtime '$RUNTIME'. Use 'colima' or 'docker-desktop'." + ;; + esac +} + +ensure_clean_start() { + if [ "$ALLOW_EXISTING_STATE" = true ]; then + return 0 + fi + + if [ -d "$HOME/.nemoclaw" ] || [ -d "$HOME/.config/nemoclaw" ] || [ -d "$HOME/.config/openshell" ]; then + fail "Existing NemoClaw/OpenShell state detected. Re-run with --allow-existing-state if you really want to test on this machine." + fi + + if command -v openshell > /dev/null 2>&1; then + if openshell sandbox list 2>/dev/null | grep -Eq '[[:alnum:]]'; then + fail "Existing OpenShell sandboxes detected. Re-run with --allow-existing-state only if you are prepared for uninstall.sh to remove them." + fi + fi +} + +feed_install_answers() { + local answers_pipe="$1" + local install_log="$2" + + ( + printf '%s\n' "$SANDBOX_NAME" + + while :; do + if [ -f "$install_log" ] && grep -q "OpenClaw gateway launched inside sandbox" "$install_log"; then + break + fi + sleep 1 + done + + printf 'n\n' + ) > "$answers_pipe" +} + +start_log_follow() { + local logfile="$1" + : > "$logfile" + tail -n +1 -f "$logfile" & + LOG_FOLLOW_PID=$! +} + +stop_log_follow() { + if [ -n "$LOG_FOLLOW_PID" ] && kill -0 "$LOG_FOLLOW_PID" 2>/dev/null; then + kill "$LOG_FOLLOW_PID" 2>/dev/null || true + wait "$LOG_FOLLOW_PID" 2>/dev/null || true + fi + LOG_FOLLOW_PID="" +} + +run_install() { + local answers_pipe="$1" + info "Running install.sh with sandbox '$SANDBOX_NAME'" + feed_install_answers "$answers_pipe" "$INSTALL_LOG" & + ANSWER_WRITER_PID=$! + start_log_follow "$INSTALL_LOG" + set +e + bash "$REPO_DIR/install.sh" < "$answers_pipe" >> "$INSTALL_LOG" 2>&1 + INSTALL_STATUS=$? + set -e + stop_log_follow + stop_answer_writer + return 0 +} + +run_uninstall() { + local -a args=(--yes) + if [ "$KEEP_OPEN_SHELL" = true ]; then + args+=(--keep-openshell) + fi + if [ "$DELETE_MODELS" = true ]; then + args+=(--delete-models) + fi + + info "Running uninstall.sh for cleanup" + start_log_follow "$UNINSTALL_LOG" + set +e + bash "$REPO_DIR/uninstall.sh" "${args[@]}" >> "$UNINSTALL_LOG" 2>&1 + UNINSTALL_STATUS=$? + set -e + stop_log_follow + return 0 +} + +verify_cleanup() { + local leftovers=0 + + if [ -d "$HOME/.nemoclaw" ] || [ -d "$HOME/.config/nemoclaw" ]; then + warn "NemoClaw state directories still exist under HOME." + leftovers=1 + fi + + if command -v openshell > /dev/null 2>&1; then + local sandbox_output + sandbox_output="$(openshell sandbox list 2>/dev/null || true)" + if printf '%s' "$sandbox_output" | grep -Eq '[[:alnum:]]'; then + warn "OpenShell still reports sandbox entries after uninstall." + leftovers=1 + fi + fi + + if command -v docker > /dev/null 2>&1 && docker info > /dev/null 2>&1; then + local related_containers + related_containers="$( + docker ps -a --format '{{.Image}} {{.Names}}' 2>/dev/null \ + | awk 'BEGIN { IGNORECASE=1 } /openshell-cluster|openshell|openclaw|nemoclaw/ { print }' + )" + if [ -n "$related_containers" ]; then + warn "Related Docker containers remain after uninstall:" + printf '%s\n' "$related_containers" + leftovers=1 + fi + fi + + return "$leftovers" +} + +cleanup() { + stop_log_follow + + stop_answer_writer + + if [ -n "$ANSWERS_PIPE" ] && [ -p "$ANSWERS_PIPE" ]; then + rm -f "$ANSWERS_PIPE" + fi + + if [ -n "$UNINSTALL_LOG" ]; then + run_uninstall + if [ "$UNINSTALL_STATUS" -ne 0 ]; then + warn "uninstall.sh exited with status $UNINSTALL_STATUS" + fi + if ! verify_cleanup; then + warn "Cleanup verification found leftover state." + else + info "Cleanup verification passed" + fi + fi + + if [ "$KEEP_LOGS" = false ] && [ "$INSTALL_STATUS" -eq 0 ] && [ "$UNINSTALL_STATUS" -eq 0 ]; then + rm -f "$INSTALL_LOG" "$UNINSTALL_LOG" + rmdir "$LOG_DIR" 2>/dev/null || true + else + info "Install log: $INSTALL_LOG" + info "Uninstall log: $UNINSTALL_LOG" + fi +} + +main() { + validate_sandbox_name + select_runtime + ensure_clean_start + + mkdir -p "$LOG_DIR" + local stamp + stamp="$(date +%Y%m%d-%H%M%S)" + INSTALL_LOG="$LOG_DIR/install-$stamp.log" + UNINSTALL_LOG="$LOG_DIR/uninstall-$stamp.log" + + ANSWERS_PIPE="$(mktemp -u "${TMPDIR:-/tmp}/nemoclaw-smoke-answers-XXXXXX")" + mkfifo "$ANSWERS_PIPE" + trap cleanup EXIT + + info "Logs will be written under $LOG_DIR" + run_install "$ANSWERS_PIPE" + + if [ "$INSTALL_STATUS" -ne 0 ]; then + fail "install.sh failed with status $INSTALL_STATUS. See $INSTALL_LOG" + fi + + info "install.sh completed successfully" +} + +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi diff --git a/test/credentials.test.js b/test/credentials.test.js new file mode 100644 index 0000000000..08ce5d7985 --- /dev/null +++ b/test/credentials.test.js @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +describe("credential prompts", () => { + it("exits cleanly when answers are staged through a pipe", () => { + const script = ` + set -euo pipefail + pipe="$(mktemp -u)" + mkfifo "$pipe" + trap 'rm -f "$pipe"' EXIT + { + printf 'sandbox-name\\n' + sleep 1 + printf 'n\\n' + } > "$pipe" & + node -e 'const { prompt } = require(${JSON.stringify(path.join(__dirname, "..", "bin", "lib", "credentials"))}); (async()=>{ await prompt("first: "); await prompt("second: "); })().catch(err=>{ console.error(err); process.exit(1); });' < "$pipe" + `; + + const result = spawnSync("bash", ["-lc", script], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + timeout: 5000, + }); + + assert.equal(result.status, 0); + }); +}); diff --git a/test/inference-config.test.js b/test/inference-config.test.js new file mode 100644 index 0000000000..6e6a9bd5b6 --- /dev/null +++ b/test/inference-config.test.js @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); + +const { + CLOUD_MODEL_OPTIONS, + DEFAULT_OLLAMA_MODEL, + DEFAULT_ROUTE_CREDENTIAL_ENV, + DEFAULT_ROUTE_PROFILE, + INFERENCE_ROUTE_URL, + MANAGED_PROVIDER_ID, + getOpenClawPrimaryModel, + getProviderSelectionConfig, +} = require("../bin/lib/inference-config"); + +describe("inference selection config", () => { + it("exposes the curated cloud model picker options", () => { + assert.deepEqual( + CLOUD_MODEL_OPTIONS.map((option) => option.id), + [ + "nvidia/nemotron-3-super-120b-a12b", + "moonshotai/kimi-k2.5", + "z-ai/glm5", + "minimaxai/minimax-m2.5", + "qwen/qwen3.5-397b-a17b", + "openai/gpt-oss-120b", + ], + ); + }); + + it("maps ollama-local to the sandbox inference route and default model", () => { + assert.deepEqual(getProviderSelectionConfig("ollama-local"), { + endpointType: "custom", + endpointUrl: INFERENCE_ROUTE_URL, + ncpPartner: null, + model: DEFAULT_OLLAMA_MODEL, + profile: DEFAULT_ROUTE_PROFILE, + credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV, + provider: "ollama-local", + providerLabel: "Local Ollama", + }); + }); + + it("maps nvidia-nim to the sandbox inference route", () => { + assert.deepEqual(getProviderSelectionConfig("nvidia-nim", "nvidia/nemotron-3-super-120b-a12b"), { + endpointType: "custom", + endpointUrl: INFERENCE_ROUTE_URL, + ncpPartner: null, + model: "nvidia/nemotron-3-super-120b-a12b", + profile: DEFAULT_ROUTE_PROFILE, + credentialEnv: DEFAULT_ROUTE_CREDENTIAL_ENV, + provider: "nvidia-nim", + providerLabel: "NVIDIA Cloud API", + }); + }); + + it("builds a qualified OpenClaw primary model for ollama-local", () => { + assert.equal( + getOpenClawPrimaryModel("ollama-local", "nemotron-3-nano:30b"), + `${MANAGED_PROVIDER_ID}/nemotron-3-nano:30b`, + ); + }); +}); diff --git a/test/install-preflight.test.js b/test/install-preflight.test.js index 6f70e197e0..dde0aa018f 100644 --- a/test/install-preflight.test.js +++ b/test/install-preflight.test.js @@ -9,6 +9,9 @@ const path = require("node:path"); const { spawnSync } = require("node:child_process"); const INSTALLER = path.join(__dirname, "..", "install.sh"); +const CURL_PIPE_INSTALLER = path.join(__dirname, "..", "scripts", "install.sh"); +const GITHUB_INSTALL_URL = "git+https://github.com/NVIDIA/NemoClaw.git"; +const TEST_SYSTEM_PATH = "/usr/bin:/bin"; function writeExecutable(target, contents) { fs.writeFileSync(target, contents, { mode: 0o755 }); @@ -50,7 +53,7 @@ exit 98 env: { ...process.env, HOME: tmp, - PATH: `${fakeBin}:${process.env.PATH}`, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, }, }); @@ -61,4 +64,385 @@ exit 98 assert.match(output, /v18\.19\.1/); assert.match(output, /9\.8\.1/); }); + + it("uses the HTTPS GitHub fallback when not installing from a repo checkout", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-install-fallback-")); + const fakeBin = path.join(tmp, "bin"); + const prefix = path.join(tmp, "prefix"); + const npmLog = path.join(tmp, "npm.log"); + fs.mkdirSync(fakeBin); + fs.mkdirSync(path.join(prefix, "bin"), { recursive: true }); + + writeExecutable( + path.join(fakeBin, "node"), + `#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "v22.14.0" + exit 0 +fi +echo "unexpected node invocation: $*" >&2 +exit 99 +`, + ); + + writeExecutable( + path.join(fakeBin, "npm"), + `#!/usr/bin/env bash +set -euo pipefail +printf '%s\\n' "$*" >> "$NPM_LOG_PATH" +if [ "$1" = "--version" ]; then + echo "10.9.2" + exit 0 +fi +if [ "$1" = "config" ] && [ "$2" = "get" ] && [ "$3" = "prefix" ]; then + echo "$NPM_PREFIX" + exit 0 +fi +if [ "$1" = "install" ] && [ "$2" = "-g" ] && [ "$3" = "${GITHUB_INSTALL_URL}" ]; then + cat > "$NPM_PREFIX/bin/nemoclaw" <<'EOS' +#!/usr/bin/env bash +if [ "$1" = "onboard" ]; then + exit 0 +fi +if [ "$1" = "--version" ]; then + echo "v0.1.0-test" + exit 0 +fi +exit 0 +EOS + chmod +x "$NPM_PREFIX/bin/nemoclaw" + exit 0 +fi +echo "unexpected npm invocation: $*" >&2 +exit 98 +`, + ); + + const result = spawnSync("bash", [INSTALLER], { + cwd: tmp, + encoding: "utf-8", + env: { + ...process.env, + HOME: tmp, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, + NPM_PREFIX: prefix, + NPM_LOG_PATH: npmLog, + }, + }); + + assert.equal(result.status, 0); + assert.match(fs.readFileSync(npmLog, "utf-8"), new RegExp(`install -g ${GITHUB_INSTALL_URL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`)); + }); + + it("prints the HTTPS GitHub remediation when the binary is missing", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-install-remediation-")); + const fakeBin = path.join(tmp, "bin"); + const prefix = path.join(tmp, "prefix"); + fs.mkdirSync(fakeBin); + fs.mkdirSync(path.join(prefix, "bin"), { recursive: true }); + + writeExecutable( + path.join(fakeBin, "node"), + `#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "v22.14.0" + exit 0 +fi +echo "unexpected node invocation: $*" >&2 +exit 99 +`, + ); + + writeExecutable( + path.join(fakeBin, "npm"), + `#!/usr/bin/env bash +set -euo pipefail +if [ "$1" = "--version" ]; then + echo "10.9.2" + exit 0 +fi +if [ "$1" = "config" ] && [ "$2" = "get" ] && [ "$3" = "prefix" ]; then + echo "$NPM_PREFIX" + exit 0 +fi +if [ "$1" = "install" ] && [ "$2" = "-g" ] && [ "$3" = "${GITHUB_INSTALL_URL}" ]; then + exit 0 +fi +echo "unexpected npm invocation: $*" >&2 +exit 98 +`, + ); + + const result = spawnSync("bash", [INSTALLER], { + cwd: tmp, + encoding: "utf-8", + env: { + ...process.env, + HOME: tmp, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, + NPM_PREFIX: prefix, + }, + }); + + const output = `${result.stdout}${result.stderr}`; + assert.notEqual(result.status, 0); + assert.match(output, new RegExp(GITHUB_INSTALL_URL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))); + assert.doesNotMatch(output, /npm install -g nemoclaw/); + }); + + it("does not silently prefer Colima when both macOS runtimes are available", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-macos-runtime-choice-")); + const fakeBin = path.join(tmp, "bin"); + const colimaSocket = path.join(tmp, ".colima/default/docker.sock"); + const dockerDesktopSocket = path.join(tmp, ".docker/run/docker.sock"); + fs.mkdirSync(fakeBin); + + writeExecutable( + path.join(fakeBin, "node"), + `#!/usr/bin/env bash +if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then + echo "v22.14.0" + exit 0 +fi +exit 99 +`, + ); + + writeExecutable( + path.join(fakeBin, "npm"), + `#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "10.9.2" + exit 0 +fi +echo "/tmp/npm-prefix" +exit 0 +`, + ); + + writeExecutable( + path.join(fakeBin, "docker"), + `#!/usr/bin/env bash +if [ "$1" = "info" ]; then + exit 1 +fi +exit 0 +`, + ); + + writeExecutable( + path.join(fakeBin, "colima"), + `#!/usr/bin/env bash +echo "colima should not be started" >&2 +exit 97 +`, + ); + + writeExecutable( + path.join(fakeBin, "uname"), + `#!/usr/bin/env bash +if [ "$1" = "-s" ]; then + echo "Darwin" + exit 0 +fi +if [ "$1" = "-m" ]; then + echo "arm64" + exit 0 +fi +echo "Darwin" +`, + ); + + const result = spawnSync("bash", [CURL_PIPE_INSTALLER], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { + ...process.env, + HOME: tmp, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, + NEMOCLAW_TEST_SOCKET_PATHS: `${colimaSocket}:${dockerDesktopSocket}`, + }, + }); + + const output = `${result.stdout}${result.stderr}`; + assert.notEqual(result.status, 0); + assert.match(output, /Both Colima and Docker Desktop are available/); + assert.doesNotMatch(output, /colima should not be started/); + }); + + it("can run via stdin without a sibling runtime.sh file", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-curl-pipe-installer-")); + const fakeBin = path.join(tmp, "bin"); + const prefix = path.join(tmp, "prefix"); + fs.mkdirSync(fakeBin); + fs.mkdirSync(path.join(prefix, "bin"), { recursive: true }); + + writeExecutable( + path.join(fakeBin, "node"), + `#!/usr/bin/env bash +if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then + echo "v22.14.0" + exit 0 +fi +exit 99 +`, + ); + + writeExecutable( + path.join(fakeBin, "npm"), + `#!/usr/bin/env bash +set -euo pipefail +if [ "$1" = "--version" ]; then + echo "10.9.2" + exit 0 +fi +if [ "$1" = "config" ] && [ "$2" = "get" ] && [ "$3" = "prefix" ]; then + echo "$NPM_PREFIX" + exit 0 +fi +if [ "$1" = "install" ] && [ "$2" = "-g" ]; then + cat > "$NPM_PREFIX/bin/nemoclaw" <<'EOS' +#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "v0.1.0-test" + exit 0 +fi +exit 0 +EOS + chmod +x "$NPM_PREFIX/bin/nemoclaw" + exit 0 +fi +echo "unexpected npm invocation: $*" >&2 +exit 98 +`, + ); + + writeExecutable( + path.join(fakeBin, "docker"), + `#!/usr/bin/env bash +if [ "$1" = "info" ]; then + exit 0 +fi +exit 0 +`, + ); + + writeExecutable( + path.join(fakeBin, "openshell"), + `#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "openshell 0.0.9" + exit 0 +fi +exit 0 +`, + ); + + const scriptContents = fs.readFileSync(CURL_PIPE_INSTALLER, "utf-8"); + const result = spawnSync("bash", [], { + cwd: tmp, + input: scriptContents, + encoding: "utf-8", + env: { + ...process.env, + HOME: tmp, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, + NPM_PREFIX: prefix, + }, + }); + + const output = `${result.stdout}${result.stderr}`; + assert.equal(result.status, 0); + assert.match(output, /Installation complete!/); + assert.match(output, /nemoclaw v0\.1\.0-test is ready/); + }); + + it("creates a user-local shim when npm installs outside the current PATH", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-install-shim-")); + const fakeBin = path.join(tmp, "bin"); + const prefix = path.join(tmp, "prefix"); + fs.mkdirSync(fakeBin); + fs.mkdirSync(path.join(prefix, "bin"), { recursive: true }); + fs.mkdirSync(path.join(tmp, ".local"), { recursive: true }); + + writeExecutable( + path.join(fakeBin, "node"), + `#!/usr/bin/env bash +if [ "$1" = "-v" ] || [ "$1" = "--version" ]; then + echo "v22.14.0" + exit 0 +fi +exit 99 +`, + ); + + writeExecutable( + path.join(fakeBin, "npm"), + `#!/usr/bin/env bash +set -euo pipefail +if [ "$1" = "--version" ]; then + echo "10.9.2" + exit 0 +fi +if [ "$1" = "config" ] && [ "$2" = "get" ] && [ "$3" = "prefix" ]; then + echo "$NPM_PREFIX" + exit 0 +fi +if [ "$1" = "install" ] && [ "$2" = "-g" ] && [ "$3" = "${GITHUB_INSTALL_URL}" ]; then + cat > "$NPM_PREFIX/bin/nemoclaw" <<'EOS' +#!/usr/bin/env bash +if [ "$1" = "onboard" ]; then + exit 0 +fi +if [ "$1" = "--version" ]; then + echo "v0.1.0-test" + exit 0 +fi +exit 0 +EOS + chmod +x "$NPM_PREFIX/bin/nemoclaw" + exit 0 +fi +echo "unexpected npm invocation: $*" >&2 +exit 98 +`, + ); + + writeExecutable( + path.join(fakeBin, "docker"), + `#!/usr/bin/env bash +if [ "$1" = "info" ]; then + exit 0 +fi +exit 0 +`, + ); + + writeExecutable( + path.join(fakeBin, "openshell"), + `#!/usr/bin/env bash +if [ "$1" = "--version" ]; then + echo "openshell 0.0.9" + exit 0 +fi +exit 0 +`, + ); + + const result = spawnSync("bash", [INSTALLER], { + cwd: tmp, + encoding: "utf-8", + env: { + ...process.env, + HOME: tmp, + PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`, + NPM_PREFIX: prefix, + }, + }); + + const shimPath = path.join(tmp, ".local", "bin", "nemoclaw"); + assert.equal(result.status, 0); + assert.equal(fs.readlinkSync(shimPath), path.join(prefix, "bin", "nemoclaw")); + assert.match(`${result.stdout}${result.stderr}`, /Created user-local shim/); + }); }); diff --git a/test/local-inference.test.js b/test/local-inference.test.js new file mode 100644 index 0000000000..5b77ee2f7a --- /dev/null +++ b/test/local-inference.test.js @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); + +const { + CONTAINER_REACHABILITY_IMAGE, + DEFAULT_OLLAMA_MODEL, + getDefaultOllamaModel, + getLocalProviderBaseUrl, + getLocalProviderContainerReachabilityCheck, + getLocalProviderHealthCheck, + getOllamaModelOptions, + getOllamaProbeCommand, + getOllamaWarmupCommand, + parseOllamaList, + validateOllamaModel, + validateLocalProvider, +} = require("../bin/lib/local-inference"); + +describe("local inference helpers", () => { + it("returns the expected base URL for vllm-local", () => { + assert.equal( + getLocalProviderBaseUrl("vllm-local"), + "http://host.openshell.internal:8000/v1", + ); + }); + + it("returns the expected base URL for ollama-local", () => { + assert.equal( + getLocalProviderBaseUrl("ollama-local"), + "http://host.openshell.internal:11434/v1", + ); + }); + + it("returns the expected health check command for ollama-local", () => { + assert.equal( + getLocalProviderHealthCheck("ollama-local"), + "curl -sf http://localhost:11434/api/tags 2>/dev/null", + ); + }); + + it("returns the expected container reachability command for ollama-local", () => { + assert.equal( + getLocalProviderContainerReachabilityCheck("ollama-local"), + `docker run --rm --add-host host.openshell.internal:host-gateway ${CONTAINER_REACHABILITY_IMAGE} -sf http://host.openshell.internal:11434/api/tags 2>/dev/null`, + ); + }); + + it("validates a reachable local provider", () => { + let callCount = 0; + const result = validateLocalProvider("ollama-local", () => { + callCount += 1; + return '{"models":[]}'; + }); + assert.deepEqual(result, { ok: true }); + assert.equal(callCount, 2); + }); + + it("returns a clear error when ollama-local is unavailable", () => { + const result = validateLocalProvider("ollama-local", () => ""); + assert.equal(result.ok, false); + assert.match(result.message, /http:\/\/localhost:11434/); + }); + + it("returns a clear error when ollama-local is not reachable from containers", () => { + let callCount = 0; + const result = validateLocalProvider("ollama-local", () => { + callCount += 1; + return callCount === 1 ? '{"models":[]}' : ""; + }); + assert.equal(result.ok, false); + assert.match(result.message, /host\.openshell\.internal:11434/); + assert.match(result.message, /0\.0\.0\.0:11434/); + }); + + it("returns a clear error when vllm-local is unavailable", () => { + const result = validateLocalProvider("vllm-local", () => ""); + assert.equal(result.ok, false); + assert.match(result.message, /http:\/\/localhost:8000/); + }); + + it("parses model names from ollama list output", () => { + assert.deepEqual( + parseOllamaList( + [ + "NAME ID SIZE MODIFIED", + "nemotron-3-nano:30b abc123 24 GB 2 hours ago", + "qwen3:32b def456 20 GB 1 day ago", + ].join("\n"), + ), + ["nemotron-3-nano:30b", "qwen3:32b"], + ); + }); + + it("returns parsed ollama model options when available", () => { + assert.deepEqual( + getOllamaModelOptions(() => "nemotron-3-nano:30b abc 24 GB now\nqwen3:32b def 20 GB now"), + ["nemotron-3-nano:30b", "qwen3:32b"], + ); + }); + + it("falls back to the default ollama model when list output is empty", () => { + assert.deepEqual(getOllamaModelOptions(() => ""), [DEFAULT_OLLAMA_MODEL]); + }); + + it("prefers the default ollama model when present", () => { + assert.equal( + getDefaultOllamaModel(() => "qwen3:32b abc 20 GB now\nnemotron-3-nano:30b def 24 GB now"), + DEFAULT_OLLAMA_MODEL, + ); + }); + + it("falls back to the first listed ollama model when the default is absent", () => { + assert.equal( + getDefaultOllamaModel(() => "qwen3:32b abc 20 GB now\ngemma3:4b def 3 GB now"), + "qwen3:32b", + ); + }); + + it("builds a background warmup command for ollama models", () => { + const command = getOllamaWarmupCommand("nemotron-3-nano:30b"); + assert.match(command, /^nohup curl -s http:\/\/localhost:11434\/api\/generate /); + assert.match(command, /"model":"nemotron-3-nano:30b"/); + assert.match(command, /"keep_alive":"15m"/); + }); + + it("builds a foreground probe command for ollama models", () => { + const command = getOllamaProbeCommand("nemotron-3-nano:30b"); + assert.match(command, /^curl -sS --max-time 120 http:\/\/localhost:11434\/api\/generate /); + assert.match(command, /"model":"nemotron-3-nano:30b"/); + }); + + it("fails ollama model validation when the probe times out or returns nothing", () => { + const result = validateOllamaModel("nemotron-3-nano:30b", () => ""); + assert.equal(result.ok, false); + assert.match(result.message, /did not answer the local probe in time/); + }); + + it("fails ollama model validation when Ollama returns an error payload", () => { + const result = validateOllamaModel( + "gabegoodhart/minimax-m2.1:latest", + () => JSON.stringify({ error: "model requires more system memory" }), + ); + assert.equal(result.ok, false); + assert.match(result.message, /requires more system memory/); + }); + + it("passes ollama model validation when the probe returns a normal payload", () => { + const result = validateOllamaModel( + "nemotron-3-nano:30b", + () => JSON.stringify({ model: "nemotron-3-nano:30b", response: "hello", done: true }), + ); + assert.deepEqual(result, { ok: true }); + }); +}); diff --git a/test/onboard-selection.test.js b/test/onboard-selection.test.js new file mode 100644 index 0000000000..9000943ba2 --- /dev/null +++ b/test/onboard-selection.test.js @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +describe("onboard provider selection UX", () => { + it("prompts explicitly instead of silently auto-selecting detected Ollama", () => { + const repoRoot = path.join(__dirname, ".."); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-onboard-selection-")); + const scriptPath = path.join(tmpDir, "selection-check.js"); + const onboardPath = JSON.stringify(path.join(repoRoot, "bin", "lib", "onboard.js")); + const credentialsPath = JSON.stringify(path.join(repoRoot, "bin", "lib", "credentials.js")); + const runnerPath = JSON.stringify(path.join(repoRoot, "bin", "lib", "runner.js")); + const registryPath = JSON.stringify(path.join(repoRoot, "bin", "lib", "registry.js")); + const script = String.raw` +const credentials = require(${credentialsPath}); +const runner = require(${runnerPath}); +const registry = require(${registryPath}); + +let promptCalls = 0; +const messages = []; +const updates = []; + +credentials.prompt = async (message) => { + promptCalls += 1; + messages.push(message); + return ""; +}; +credentials.ensureApiKey = async () => {}; +runner.runCapture = (command) => { + if (command.includes("command -v ollama")) return "/usr/bin/ollama"; + if (command.includes("localhost:11434/api/tags")) return JSON.stringify({ models: [{ name: "nemotron-3-nano:30b" }] }); + if (command.includes("ollama list")) return "nemotron-3-nano:30b abc 24 GB now\\nqwen3:32b def 20 GB now"; + if (command.includes("localhost:8000/v1/models")) return ""; + return ""; +}; +registry.updateSandbox = (_name, update) => updates.push(update); + +const { setupNim } = require(${onboardPath}); + +(async () => { + const originalLog = console.log; + const lines = []; + console.log = (...args) => lines.push(args.join(" ")); + try { + const result = await setupNim("selection-test", null); + originalLog(JSON.stringify({ result, promptCalls, messages, updates, lines })); + } finally { + console.log = originalLog; + } +})().catch((error) => { + console.error(error); + process.exit(1); +}); +`; + fs.writeFileSync(scriptPath, script); + + const result = spawnSync(process.execPath, [scriptPath], { + cwd: repoRoot, + encoding: "utf-8", + env: { + ...process.env, + HOME: tmpDir, + }, + }); + + assert.equal(result.status, 0, result.stderr); + assert.notEqual(result.stdout.trim(), "", result.stderr); + const payload = JSON.parse(result.stdout.trim()); + assert.equal(payload.result.provider, "nvidia-nim"); + assert.equal(payload.result.model, "nvidia/nemotron-3-super-120b-a12b"); + assert.equal(payload.promptCalls, 2); + assert.match(payload.messages[0], /Choose \[/); + assert.match(payload.messages[1], /Choose model \[1\]/); + assert.ok(payload.lines.some((line) => line.includes("Detected local inference option"))); + assert.ok(payload.lines.some((line) => line.includes("Press Enter to keep the cloud default"))); + assert.ok(payload.lines.some((line) => line.includes("Cloud models:"))); + }); +}); diff --git a/test/onboard.test.js b/test/onboard.test.js new file mode 100644 index 0000000000..3f4418ccca --- /dev/null +++ b/test/onboard.test.js @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); + +const { buildSandboxConfigSyncScript } = require("../bin/lib/onboard"); + +describe("onboard helpers", () => { + it("builds a sandbox sync script that writes config and updates the selected model", () => { + const script = buildSandboxConfigSyncScript({ + endpointType: "custom", + endpointUrl: "https://inference.local/v1", + ncpPartner: null, + model: "nemotron-3-nano:30b", + profile: "inference-local", + credentialEnv: "OPENAI_API_KEY", + onboardedAt: "2026-03-18T12:00:00.000Z", + }); + + assert.match(script, /cat > ~\/\.nemoclaw\/config\.json/); + assert.match(script, /"model": "nemotron-3-nano:30b"/); + assert.match(script, /"credentialEnv": "OPENAI_API_KEY"/); + assert.match(script, /openclaw models set 'inference\/nemotron-3-nano:30b'/); + assert.match(script, /cfg\.setdefault\('agents', \{\}\)\.setdefault\('defaults', \{\}\)\.setdefault\('model', \{\}\)\['primary'\]/); + assert.match(script, /providers_cfg\["inference"\]/); + assert.match(script, /json\.loads\("\{\\\"baseUrl\\\":\\\"https:\/\/inference\.local\/v1\\\",\\\"apiKey\\\":\\\"unused\\\"/); + assert.match(script, /inference\/nemotron-3-nano:30b/); + assert.match(script, /^exit$/m); + }); +}); diff --git a/test/platform.test.js b/test/platform.test.js new file mode 100644 index 0000000000..6a20e16eca --- /dev/null +++ b/test/platform.test.js @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const path = require("node:path"); + +const { + detectDockerHost, + findColimaDockerSocket, + getDockerSocketCandidates, + inferContainerRuntime, + isUnsupportedMacosRuntime, + isWsl, + shouldPatchCoredns, +} = require("../bin/lib/platform"); + +describe("platform helpers", () => { + describe("isWsl", () => { + it("detects WSL from environment", () => { + assert.equal( + isWsl({ + platform: "linux", + env: { WSL_DISTRO_NAME: "Ubuntu" }, + release: "6.6.87.2-microsoft-standard-WSL2", + }), + true, + ); + }); + + it("does not treat macOS as WSL", () => { + assert.equal( + isWsl({ + platform: "darwin", + env: {}, + release: "24.6.0", + }), + false, + ); + }); + }); + + describe("getDockerSocketCandidates", () => { + it("returns macOS candidates in priority order", () => { + const home = "/tmp/test-home"; + assert.deepEqual(getDockerSocketCandidates({ platform: "darwin", home }), [ + path.join(home, ".colima/default/docker.sock"), + path.join(home, ".config/colima/default/docker.sock"), + path.join(home, ".docker/run/docker.sock"), + ]); + }); + + it("does not auto-detect sockets on Linux", () => { + assert.deepEqual(getDockerSocketCandidates({ platform: "linux", home: "/tmp/test-home" }), []); + }); + }); + + describe("findColimaDockerSocket", () => { + it("finds the first available Colima socket", () => { + const home = "/tmp/test-home"; + const sockets = new Set([path.join(home, ".config/colima/default/docker.sock")]); + const existsSync = (socketPath) => sockets.has(socketPath); + + assert.equal( + findColimaDockerSocket({ home, existsSync }), + path.join(home, ".config/colima/default/docker.sock"), + ); + }); + }); + + describe("detectDockerHost", () => { + it("respects an existing DOCKER_HOST", () => { + assert.deepEqual( + detectDockerHost({ + env: { DOCKER_HOST: "unix:///custom/docker.sock" }, + platform: "darwin", + home: "/tmp/test-home", + existsSync: () => false, + }), + { + dockerHost: "unix:///custom/docker.sock", + source: "env", + socketPath: null, + }, + ); + }); + + it("prefers Colima over Docker Desktop on macOS", () => { + const home = "/tmp/test-home"; + const sockets = new Set([ + path.join(home, ".colima/default/docker.sock"), + path.join(home, ".docker/run/docker.sock"), + ]); + const existsSync = (socketPath) => sockets.has(socketPath); + + assert.deepEqual( + detectDockerHost({ env: {}, platform: "darwin", home, existsSync }), + { + dockerHost: `unix://${path.join(home, ".colima/default/docker.sock")}`, + source: "socket", + socketPath: path.join(home, ".colima/default/docker.sock"), + }, + ); + }); + + it("detects Docker Desktop when Colima is absent", () => { + const home = "/tmp/test-home"; + const socketPath = path.join(home, ".docker/run/docker.sock"); + const existsSync = (candidate) => candidate === socketPath; + + assert.deepEqual( + detectDockerHost({ env: {}, platform: "darwin", home, existsSync }), + { + dockerHost: `unix://${socketPath}`, + source: "socket", + socketPath, + }, + ); + }); + + it("returns null when no auto-detected socket is available", () => { + assert.equal( + detectDockerHost({ + env: {}, + platform: "linux", + home: "/tmp/test-home", + existsSync: () => false, + }), + null, + ); + }); + }); + + describe("inferContainerRuntime", () => { + it("detects podman", () => { + assert.equal(inferContainerRuntime("podman version 5.4.1"), "podman"); + }); + + it("detects Docker Desktop", () => { + assert.equal(inferContainerRuntime("Docker Desktop 4.42.0 (190636)"), "docker-desktop"); + }); + + it("detects Colima", () => { + assert.equal(inferContainerRuntime("Server: Colima\n Docker Engine - Community"), "colima"); + }); + }); + + describe("isUnsupportedMacosRuntime", () => { + it("flags podman on macOS", () => { + assert.equal(isUnsupportedMacosRuntime("podman", { platform: "darwin" }), true); + }); + + it("does not flag podman on Linux", () => { + assert.equal(isUnsupportedMacosRuntime("podman", { platform: "linux" }), false); + }); + }); + + describe("shouldPatchCoredns", () => { + it("patches CoreDNS for Colima only", () => { + assert.equal(shouldPatchCoredns("colima"), true); + assert.equal(shouldPatchCoredns("docker-desktop"), false); + assert.equal(shouldPatchCoredns("docker"), false); + }); + }); +}); diff --git a/test/runner.test.js b/test/runner.test.js new file mode 100644 index 0000000000..03bd3a2b84 --- /dev/null +++ b/test/runner.test.js @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const path = require("node:path"); +const childProcess = require("node:child_process"); +const { spawnSync } = childProcess; + +const runnerPath = path.join(__dirname, "..", "bin", "lib", "runner"); + +describe("runner helpers", () => { + it("does not let child commands consume installer stdin", () => { + const script = ` + const { run } = require(${JSON.stringify(runnerPath)}); + process.stdin.setEncoding("utf8"); + run("cat >/dev/null || true"); + process.stdin.once("data", (chunk) => { + process.stdout.write(chunk); + }); + `; + + const result = spawnSync("node", ["-e", script], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + input: "preserved-answer\n", + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout, "preserved-answer\n"); + }); + + it("uses inherited stdio for interactive commands only", () => { + const calls = []; + const originalSpawnSync = childProcess.spawnSync; + childProcess.spawnSync = (...args) => { + calls.push(args); + return { status: 0 }; + }; + + try { + delete require.cache[require.resolve(runnerPath)]; + const { run, runInteractive } = require(runnerPath); + run("echo noninteractive"); + runInteractive("echo interactive"); + } finally { + childProcess.spawnSync = originalSpawnSync; + delete require.cache[require.resolve(runnerPath)]; + } + + assert.equal(calls.length, 2); + assert.deepEqual(calls[0][2].stdio, ["ignore", "inherit", "inherit"]); + assert.equal(calls[1][2].stdio, "inherit"); + }); +}); diff --git a/test/runtime-shell.test.js b/test/runtime-shell.test.js new file mode 100644 index 0000000000..979460b988 --- /dev/null +++ b/test/runtime-shell.test.js @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { afterEach, describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const RUNTIME_SH = path.join(__dirname, "..", "scripts", "lib", "runtime.sh"); + +afterEach(() => {}); + +function runShell(script, env = {}) { + return spawnSync("bash", ["-lc", script], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { ...process.env, ...env }, + }); +} + +describe("shell runtime helpers", () => { + it("respects an existing DOCKER_HOST", () => { + const result = runShell(`source "${RUNTIME_SH}"; detect_docker_host`, { + DOCKER_HOST: "unix:///custom/docker.sock", + HOME: "/tmp/unused-home", + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "unix:///custom/docker.sock"); + }); + + it("prefers Colima over Docker Desktop", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-shell-")); + const colimaSocket = path.join(home, ".colima/default/docker.sock"); + const dockerDesktopSocket = path.join(home, ".docker/run/docker.sock"); + + const result = runShell(`source "${RUNTIME_SH}"; detect_docker_host`, { + HOME: home, + NEMOCLAW_TEST_SOCKET_PATHS: `${colimaSocket}:${dockerDesktopSocket}`, + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), `unix://${colimaSocket}`); + fs.rmSync(home, { recursive: true, force: true }); + }); + + it("detects Docker Desktop when Colima is absent", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-shell-")); + const dockerDesktopSocket = path.join(home, ".docker/run/docker.sock"); + + const result = runShell(`source "${RUNTIME_SH}"; detect_docker_host`, { + HOME: home, + NEMOCLAW_TEST_SOCKET_PATHS: dockerDesktopSocket, + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), `unix://${dockerDesktopSocket}`); + fs.rmSync(home, { recursive: true, force: true }); + }); + + it("classifies a Docker Desktop DOCKER_HOST correctly", () => { + const result = runShell(`source "${RUNTIME_SH}"; docker_host_runtime "unix:///Users/test/.docker/run/docker.sock"`); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "docker-desktop"); + }); + + it("selects the matching gateway cluster when a gateway name is present", () => { + const result = runShell( + `source "${RUNTIME_SH}"; + select_openshell_cluster_container "nemoclaw" $'openshell-cluster-alpha\\nopenshell-cluster-nemoclaw'`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "openshell-cluster-nemoclaw"); + }); + + it("fails on ambiguous cluster selection", () => { + const result = runShell( + `source "${RUNTIME_SH}"; + select_openshell_cluster_container "" $'openshell-cluster-a\\nopenshell-cluster-b'`, + ); + + assert.notEqual(result.status, 0); + }); + + it("finds the XDG Colima socket", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-runtime-shell-")); + const xdgColimaSocket = path.join(home, ".config/colima/default/docker.sock"); + + const result = runShell(`source "${RUNTIME_SH}"; find_colima_docker_socket`, { + HOME: home, + NEMOCLAW_TEST_SOCKET_PATHS: xdgColimaSocket, + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), xdgColimaSocket); + fs.rmSync(home, { recursive: true, force: true }); + }); + + it("detects podman from docker info output", () => { + const result = runShell(`source "${RUNTIME_SH}"; infer_container_runtime_from_info "podman version 5.4.1"`); + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "podman"); + }); + + it("flags podman on macOS as unsupported", () => { + const result = runShell(`source "${RUNTIME_SH}"; is_unsupported_macos_runtime Darwin podman`); + assert.equal(result.status, 0); + }); + + it("does not flag podman on Linux", () => { + const result = runShell(`source "${RUNTIME_SH}"; is_unsupported_macos_runtime Linux podman`); + assert.notEqual(result.status, 0); + }); + + it("returns the vllm-local base URL", () => { + const result = runShell(`source "${RUNTIME_SH}"; get_local_provider_base_url vllm-local`); + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "http://host.openshell.internal:8000/v1"); + }); + + it("returns the ollama-local base URL", () => { + const result = runShell(`source "${RUNTIME_SH}"; get_local_provider_base_url ollama-local`); + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "http://host.openshell.internal:11434/v1"); + }); + + it("rejects unknown local providers", () => { + const result = runShell(`source "${RUNTIME_SH}"; get_local_provider_base_url bogus-provider`); + assert.notEqual(result.status, 0); + }); + + it("returns the first non-loopback nameserver", () => { + const result = runShell( + `source "${RUNTIME_SH}"; first_non_loopback_nameserver $'nameserver 127.0.0.11\\nnameserver 10.0.0.2'`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "10.0.0.2"); + }); + + it("prefers the container nameserver when it is not loopback", () => { + const result = runShell( + `source "${RUNTIME_SH}"; resolve_coredns_upstream $'nameserver 10.0.0.2' $'nameserver 1.1.1.1' colima`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "10.0.0.2"); + }); + + it("falls back to the Colima VM nameserver when the container resolver is loopback", () => { + const result = runShell( + `source "${RUNTIME_SH}"; + get_colima_vm_nameserver() { printf '192.168.5.1\\n'; } + resolve_coredns_upstream $'nameserver 127.0.0.11' $'nameserver 1.1.1.1' colima`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "192.168.5.1"); + }); + + it("falls back to the host nameserver when no Colima VM nameserver is available", () => { + const result = runShell( + `source "${RUNTIME_SH}"; + get_colima_vm_nameserver() { return 1; } + resolve_coredns_upstream $'nameserver 127.0.0.11' $'nameserver 9.9.9.9' colima`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "9.9.9.9"); + }); + + it("does not consume installer stdin when reading the Colima VM nameserver", () => { + const result = runShell( + `function colima() { cat > /dev/null || true; printf 'nameserver 100.100.100.100\\n'; } + source "${RUNTIME_SH}" + printf 'sandbox-answer\\n' | { + get_colima_vm_nameserver > /tmp/nemoclaw-colima-ns.out + cat + }`, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "sandbox-answer"); + }); +}); diff --git a/test/smoke-macos-install.test.js b/test/smoke-macos-install.test.js new file mode 100644 index 0000000000..6d6e44c509 --- /dev/null +++ b/test/smoke-macos-install.test.js @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const SMOKE_SCRIPT = path.join(__dirname, "..", "scripts", "smoke-macos-install.sh"); + +describe("macOS smoke install script guardrails", () => { + it("prints help", () => { + const result = spawnSync("bash", [SMOKE_SCRIPT, "--help"], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + }); + + assert.equal(result.status, 0); + assert.match(result.stdout, /Usage: \.\/scripts\/smoke-macos-install\.sh/); + }); + + it("requires NVIDIA_API_KEY", () => { + const result = spawnSync("bash", [SMOKE_SCRIPT], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { ...process.env, NVIDIA_API_KEY: "" }, + }); + + assert.notEqual(result.status, 0); + assert.match(`${result.stdout}${result.stderr}`, /NVIDIA_API_KEY must be set/); + }); + + it("rejects invalid sandbox names", () => { + const result = spawnSync("bash", [SMOKE_SCRIPT, "--sandbox-name", "Bad Name"], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { ...process.env, NVIDIA_API_KEY: "nvapi-test" }, + }); + + assert.notEqual(result.status, 0); + assert.match(`${result.stdout}${result.stderr}`, /Invalid sandbox name/); + }); + + it("rejects unsupported runtimes", () => { + const result = spawnSync("bash", [SMOKE_SCRIPT, "--runtime", "podman"], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { ...process.env, NVIDIA_API_KEY: "nvapi-test" }, + }); + + assert.notEqual(result.status, 0); + assert.match(`${result.stdout}${result.stderr}`, /Unsupported runtime 'podman'/); + }); + + it("fails when a requested runtime socket is unavailable", () => { + const result = spawnSync("bash", [SMOKE_SCRIPT, "--runtime", "docker-desktop"], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { + ...process.env, + NVIDIA_API_KEY: "nvapi-test", + HOME: "/tmp/nemoclaw-smoke-no-runtime", + }, + }); + + assert.notEqual(result.status, 0); + assert.match(`${result.stdout}${result.stderr}`, /no Docker Desktop socket was found/); + }); + + it("stages the policy preset no answer after sandbox setup", () => { + const script = ` + set -euo pipefail + source "${SMOKE_SCRIPT}" + answers_pipe="$(mktemp -u)" + install_log="$(mktemp)" + mkfifo "$answers_pipe" + trap 'rm -f "$answers_pipe" "$install_log"' EXIT + SANDBOX_NAME="smoke-test" + feed_install_answers "$answers_pipe" "$install_log" & + feeder_pid="$!" + { + IFS= read -r first_line + printf '%s\\n' "$first_line" + printf ' ✓ OpenClaw gateway launched inside sandbox\\n' >> "$install_log" + IFS= read -r second_line + printf '%s\\n' "$second_line" + } < "$answers_pipe" + wait "$feeder_pid" + `; + + const result = spawnSync("bash", ["-lc", script], { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + env: { ...process.env, NVIDIA_API_KEY: "nvapi-test" }, + }); + + assert.equal(result.status, 0); + assert.equal(result.stdout, "smoke-test\nn\n"); + }); +}); diff --git a/test/uninstall.test.js b/test/uninstall.test.js new file mode 100644 index 0000000000..d17dcb36ed --- /dev/null +++ b/test/uninstall.test.js @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +const { describe, it } = require("node:test"); +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { spawnSync } = require("node:child_process"); + +const UNINSTALL_SCRIPT = path.join(__dirname, "..", "uninstall.sh"); + +describe("uninstall helpers", () => { + it("returns the expected gateway volume candidate", () => { + const result = spawnSync( + "bash", + ["-lc", `source "${UNINSTALL_SCRIPT}"; gateway_volume_candidates nemoclaw`], + { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + }, + ); + + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), "openshell-cluster-nemoclaw"); + }); + + it("removes the user-local nemoclaw shim", () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-uninstall-shim-")); + const shimDir = path.join(tmp, ".local", "bin"); + const shimPath = path.join(shimDir, "nemoclaw"); + fs.mkdirSync(shimDir, { recursive: true }); + fs.writeFileSync(shimPath, "#!/usr/bin/env bash\n", { mode: 0o755 }); + + const result = spawnSync( + "bash", + ["-lc", `HOME="${tmp}" source "${UNINSTALL_SCRIPT}"; remove_nemoclaw_cli`], + { + cwd: path.join(__dirname, ".."), + encoding: "utf-8", + }, + ); + + assert.equal(result.status, 0); + assert.equal(fs.existsSync(shimPath), false); + }); +}); diff --git a/uninstall.sh b/uninstall.sh index 4cf23b1915..b459495935 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -33,6 +33,7 @@ PROVIDERS=("nvidia-nim" "vllm-local" "ollama-local" "nvidia-ncp" "nim-local") OPEN_SHELL_INSTALL_PATHS=("/usr/local/bin/openshell" "${XDG_BIN_HOME:-$HOME/.local/bin}/openshell") OLLAMA_MODELS=("nemotron-3-super:120b" "nemotron-3-nano:30b") TMP_ROOT="${TMPDIR:-/tmp}" +NEMOCLAW_SHIM_DIR="${HOME}/.local/bin" ASSUME_YES=false KEEP_OPEN_SHELL=false @@ -201,6 +202,10 @@ remove_nemoclaw_cli() { else warn "npm not found; skipping nemoclaw npm uninstall." fi + + if [ -L "${NEMOCLAW_SHIM_DIR}/nemoclaw" ] || [ -f "${NEMOCLAW_SHIM_DIR}/nemoclaw" ]; then + remove_path "${NEMOCLAW_SHIM_DIR}/nemoclaw" + fi } remove_nemoclaw_state() { @@ -311,6 +316,52 @@ remove_related_docker_images() { fi } +gateway_volume_candidates() { + local gateway_name="${1:-$DEFAULT_GATEWAY}" + + printf 'openshell-cluster-%s\n' "$gateway_name" +} + +remove_related_docker_volumes() { + if ! command -v docker > /dev/null 2>&1; then + warn "docker not found; skipping Docker volume cleanup." + return 0 + fi + + if ! docker info > /dev/null 2>&1; then + warn "docker is not running; skipping Docker volume cleanup." + return 0 + fi + + local -a volume_names=() + local volume_name + while IFS= read -r volume_name; do + [ -n "$volume_name" ] || continue + volume_names+=("$volume_name") + done < <(gateway_volume_candidates "$DEFAULT_GATEWAY") + + if [ "${#volume_names[@]}" -eq 0 ]; then + info "No NemoClaw/OpenShell Docker volumes found" + return 0 + fi + + local removed_any=false + for volume_name in "${volume_names[@]}"; do + if docker volume inspect "$volume_name" > /dev/null 2>&1; then + if docker volume rm -f "$volume_name" > /dev/null 2>&1; then + info "Removed Docker volume $volume_name" + removed_any=true + else + warn "Failed to remove Docker volume $volume_name" + fi + fi + done + + if [ "$removed_any" = false ]; then + info "No NemoClaw/OpenShell Docker volumes found" + fi +} + remove_optional_ollama_models() { if [ "$DELETE_MODELS" != true ]; then info "Keeping Ollama models as requested." @@ -387,6 +438,9 @@ main() { info "Removing related Docker images..." remove_related_docker_images + info "Removing related Docker volumes..." + remove_related_docker_volumes + info "Removing optional Ollama models..." remove_optional_ollama_models @@ -400,4 +454,6 @@ main() { info "Uninstall complete." } -main "$@" +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi