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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/deployment/set-up-telegram-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Channel entries in `/sandbox/.openclaw/openclaw.json` are fixed at image build t
If you add or change `TELEGRAM_BOT_TOKEN` (or toggle channels) after a sandbox already exists, you typically need to run `nemoclaw onboard` again so the image and provider attachments are rebuilt with the new settings.

NemoClaw stores a SHA-256 hash of each messaging token in the sandbox registry at creation time.
When you re-run `nemoclaw onboard --non-interactive` with a new token, NemoClaw detects the change, backs up workspace state, deletes the sandbox, recreates it with the new credential, and restores the backup.
When you re-run `nemoclaw onboard --non-interactive --name <sandbox-name>` with a new token, NemoClaw detects the change, backs up workspace state, deletes the sandbox, recreates it with the new credential, and restores the backup.
This makes credential rotation safe to script.

Telegram, Discord, and Slack each allow only one active consumer per bot token.
Expand Down
7 changes: 6 additions & 1 deletion docs/inference/use-local-inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ path to the model server.
```console
$ NEMOCLAW_PROVIDER=ollama \
NEMOCLAW_MODEL=qwen2.5:14b \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand Down Expand Up @@ -137,6 +138,7 @@ $ NEMOCLAW_PROVIDER=custom \
NEMOCLAW_ENDPOINT_URL=http://localhost:8000/v1 \
NEMOCLAW_MODEL=meta-llama/Llama-3.1-8B-Instruct \
COMPATIBLE_API_KEY=dummy \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand Down Expand Up @@ -189,6 +191,7 @@ $ NEMOCLAW_PROVIDER=anthropicCompatible \
NEMOCLAW_ENDPOINT_URL=http://localhost:8080 \
NEMOCLAW_MODEL=my-model \
COMPATIBLE_ANTHROPIC_API_KEY=dummy \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand All @@ -215,6 +218,7 @@ The vLLM `/v1/responses` endpoint does not run the `--tool-call-parser`, so tool
```console
$ NEMOCLAW_EXPERIMENTAL=1 \
NEMOCLAW_PROVIDER=vllm \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand All @@ -237,7 +241,7 @@ NemoClaw filters available models by GPU VRAM, pulls the NIM container image, st
NIM container images are hosted on `nvcr.io` and require NGC registry authentication before `docker pull` succeeds.
If Docker is not already logged in to `nvcr.io`, onboard prompts for an [NGC API key](https://org.ngc.nvidia.com/setup/api-key) and runs `docker login nvcr.io` over `--password-stdin` so the key is never written to disk or shell history.
The prompt masks the key during input and retries once on a bad key before failing.
In non-interactive mode, onboard exits with login instructions if Docker is not already authenticated; run `docker login nvcr.io` yourself, then re-run `nemoclaw onboard --non-interactive`.
In non-interactive mode, onboard exits with login instructions if Docker is not already authenticated; run `docker login nvcr.io` yourself, then re-run `nemoclaw onboard --non-interactive` (with `--name <sandbox-name>` or `NEMOCLAW_SANDBOX_NAME` set).

:::{note}
NIM uses vLLM internally.
Expand All @@ -249,6 +253,7 @@ The same `chat/completions` API path restriction applies.
```console
$ NEMOCLAW_EXPERIMENTAL=1 \
NEMOCLAW_PROVIDER=nim \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand Down
11 changes: 6 additions & 5 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The wizard creates an OpenShell gateway, registers inference providers, builds t
Use this command for new installs and for recreating a sandbox after changes to policy or configuration.

```console
$ nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--agent <name>] [--dangerously-skip-permissions] [--yes-i-accept-third-party-software]
$ nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--name <sandbox-name>] [--agent <name>] [--dangerously-skip-permissions] [--yes-i-accept-third-party-software]
```

:::{warning}
Expand Down Expand Up @@ -99,7 +99,7 @@ For details on tiers and the presets each includes, see [Network Policies](netwo
In non-interactive mode, set the tier with `NEMOCLAW_POLICY_TIER` (default: `balanced`):

```console
$ NEMOCLAW_POLICY_TIER=restricted nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
$ NEMOCLAW_POLICY_TIER=restricted NEMOCLAW_SANDBOX_NAME=my-assistant nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
```

If you enable Brave Search during onboarding, NemoClaw currently stores the Brave API key in the sandbox's OpenClaw configuration.
Expand All @@ -110,19 +110,20 @@ Treat Brave Search as an explicit opt-in and use a dedicated low-privilege Brave
For non-interactive onboarding, you must explicitly accept the third-party software notice:

```console
$ nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
$ nemoclaw onboard --non-interactive --name my-assistant --yes-i-accept-third-party-software
```

or:

```console
$ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 nemoclaw onboard --non-interactive
$ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 NEMOCLAW_SANDBOX_NAME=my-assistant nemoclaw onboard --non-interactive
```

To enable Brave Search in non-interactive mode, set:

```console
$ BRAVE_API_KEY=... \
NEMOCLAW_SANDBOX_NAME=my-assistant \
nemoclaw onboard --non-interactive
```

Expand Down Expand Up @@ -193,7 +194,7 @@ $ nemoclaw onboard --dangerously-skip-permissions
Onboarding prints an explicit warning at start so the reduced security posture is visible in logs. The flag is also honored via `NEMOCLAW_DANGEROUSLY_SKIP_PERMISSIONS=1` for non-interactive runs:

```console
$ NEMOCLAW_DANGEROUSLY_SKIP_PERMISSIONS=1 nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
$ NEMOCLAW_DANGEROUSLY_SKIP_PERMISSIONS=1 NEMOCLAW_SANDBOX_NAME=my-assistant nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
```

The flag is persisted on the sandbox registry entry, so `nemoclaw <sandbox> status` surfaces `Permissions: dangerously-skip-permissions (shields permanently down)` for sandboxes created this way. To tighten a sandbox after the fact, re-run `nemoclaw onboard` without the flag.
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/network-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Tier definitions are stored in `nemoclaw-blueprint/policies/tiers.yaml`.
In non-interactive mode, set the tier with `NEMOCLAW_POLICY_TIER`:

```console
$ NEMOCLAW_POLICY_TIER=open nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
$ NEMOCLAW_POLICY_TIER=open NEMOCLAW_SANDBOX_NAME=my-assistant nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
```

If the value does not match a known tier, onboarding exits with an error listing the valid options.
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ Then re-run `nemoclaw onboard`.

### Updated messaging token is not picked up

Re-running `nemoclaw onboard --non-interactive` with a new
Re-running `nemoclaw onboard --non-interactive --name <sandbox-name>` with a new
`TELEGRAM_BOT_TOKEN`, `DISCORD_BOT_TOKEN`, or `SLACK_BOT_TOKEN` previously
Comment thread
coderabbitai[bot] marked this conversation as resolved.
reported success while the sandbox kept polling with the old credential.
Current NemoClaw stores SHA-256 hashes of messaging credentials in the
Expand All @@ -350,7 +350,7 @@ If you suspect a sandbox is still using a stale token, re-run onboarding so
the credential check runs:

```console
$ nemoclaw onboard --non-interactive
$ TELEGRAM_BOT_TOKEN=<new-token> NEMOCLAW_SANDBOX_NAME=<sandbox-name> nemoclaw onboard --non-interactive --yes-i-accept-third-party-software
```

### Sandbox creation killed by OOM (exit 137)
Expand Down
182 changes: 182 additions & 0 deletions src/lib/onboard-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand All @@ -65,6 +66,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand All @@ -89,6 +91,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand Down Expand Up @@ -134,6 +137,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand All @@ -159,6 +163,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand Down Expand Up @@ -235,6 +240,7 @@ describe("onboard command", () => {
agent: "openclaw",
dangerouslySkipPermissions: true,
controlUiPort: null,
sandboxName: null,
});
});

Expand Down Expand Up @@ -369,6 +375,7 @@ describe("onboard command", () => {
agent: null,
dangerouslySkipPermissions: false,
controlUiPort: null,
sandboxName: null,
});
});

Expand All @@ -390,4 +397,179 @@ describe("onboard command", () => {
expect(lines.join("\n")).toContain("Use `nemoclaw onboard` instead");
expect(runOnboard).toHaveBeenCalledTimes(1);
});
it("parses --name <sandbox-name>", () => {
const result = parseOnboardArgs(
["--non-interactive", "--name", "deepobs", "--from", "/path/to/Dockerfile"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: exitWithCode,
},
);
expect(result.sandboxName).toBe("deepobs");
expect(result.fromDockerfile).toBe("/path/to/Dockerfile");
expect(result.nonInteractive).toBe(true);
});

it("rejects --name with no following value", () => {
const errorMessages: string[] = [];
expect(() =>
parseOnboardArgs(
["--name"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: (msg) => {
errorMessages.push(String(msg ?? ""));
},
exit: exitWithCode,
},
),
).toThrow();
expect(errorMessages.join("\n")).toMatch(/--name requires a sandbox name/);
});

it("rejects --name <flag> (treats next flag as missing value)", () => {
const errorMessages: string[] = [];
expect(() =>
parseOnboardArgs(
["--name", "--from", "/path/to/Dockerfile"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: (msg) => {
errorMessages.push(String(msg ?? ""));
},
exit: exitWithCode,
},
),
).toThrow();
expect(errorMessages.join("\n")).toMatch(/--name requires a sandbox name/);
});

it("propagates --name to NEMOCLAW_SANDBOX_NAME so promptValidatedSandboxName picks it up", async () => {
const env: NodeJS.ProcessEnv = {};
const runOnboard = vi.fn(async () => {});
await runOnboardCommand({
args: ["--non-interactive", "--name", "my-deepobs"],
noticeAcceptFlag: "--yes-i-accept-third-party-software",
noticeAcceptEnv: "NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
env,
runOnboard,
error: () => {},
exit: exitWithCode,
});
expect(env.NEMOCLAW_SANDBOX_NAME).toBe("my-deepobs");
expect(runOnboard).toHaveBeenCalledWith(
expect.objectContaining({ sandboxName: "my-deepobs" }),
);
});

it("does not touch NEMOCLAW_SANDBOX_NAME when --name is absent", async () => {
const env: NodeJS.ProcessEnv = { NEMOCLAW_SANDBOX_NAME: "existing-default" };
const runOnboard = vi.fn(async () => {});
await runOnboardCommand({
args: ["--non-interactive"],
noticeAcceptFlag: "--yes-i-accept-third-party-software",
noticeAcceptEnv: "NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
env,
runOnboard,
error: () => {},
exit: exitWithCode,
});
expect(env.NEMOCLAW_SANDBOX_NAME).toBe("existing-default");
});

it("rejects --non-interactive without --name and without NEMOCLAW_SANDBOX_NAME", () => {
const errorMessages: string[] = [];
expect(() =>
parseOnboardArgs(
["--non-interactive", "--from", "/path/to/Dockerfile"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: (msg) => {
errorMessages.push(String(msg ?? ""));
},
exit: exitWithCode,
},
),
).toThrow();
expect(errorMessages.join("\n")).toMatch(
/--non-interactive requires --name <sandbox-name>/,
);
});

it("accepts --non-interactive when --name is provided", () => {
const result = parseOnboardArgs(
["--non-interactive", "--name", "my-deepobs"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: exitWithCode,
},
);
expect(result.nonInteractive).toBe(true);
expect(result.sandboxName).toBe("my-deepobs");
});

it("accepts --non-interactive when NEMOCLAW_SANDBOX_NAME env var is set", () => {
const result = parseOnboardArgs(
["--non-interactive"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: { NEMOCLAW_SANDBOX_NAME: "from-env" },
error: () => {},
exit: exitWithCode,
},
);
expect(result.nonInteractive).toBe(true);
expect(result.sandboxName).toBe(null);
});

it("treats whitespace-only NEMOCLAW_SANDBOX_NAME as missing", () => {
const errorMessages: string[] = [];
expect(() =>
parseOnboardArgs(
["--non-interactive"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: { NEMOCLAW_SANDBOX_NAME: " " },
error: (msg) => {
errorMessages.push(String(msg ?? ""));
},
exit: exitWithCode,
},
),
).toThrow();
expect(errorMessages.join("\n")).toMatch(
/--non-interactive requires --name <sandbox-name>/,
);
});

it("exempts --resume from the non-interactive name requirement (session has the name)", () => {
const result = parseOnboardArgs(
["--non-interactive", "--resume"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: exitWithCode,
},
);
expect(result.nonInteractive).toBe(true);
expect(result.resume).toBe(true);
expect(result.sandboxName).toBe(null);
});

});
Loading