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
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,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>] [--yes-i-accept-third-party-software]
$ nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--provider <name>] [--agent <name>] [--yes-i-accept-third-party-software]
```

> **Warning:** For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox.
Expand Down Expand Up @@ -97,6 +97,12 @@ or:
$ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 nemoclaw onboard --non-interactive
```

To pick a non-default provider in scripted installs, pass `--provider` or set `NEMOCLAW_PROVIDER`:

```console
$ nemoclaw onboard --non-interactive --provider openai --yes-i-accept-third-party-software
```

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

```console
Expand Down
8 changes: 7 additions & 1 deletion 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>] [--yes-i-accept-third-party-software]
$ nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--provider <name>] [--agent <name>] [--yes-i-accept-third-party-software]
```

:::{warning}
Expand Down Expand Up @@ -119,6 +119,12 @@ or:
$ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 nemoclaw onboard --non-interactive
```

To pick a non-default provider in scripted installs, pass `--provider` or set `NEMOCLAW_PROVIDER`:

```console
$ nemoclaw onboard --non-interactive --provider openai --yes-i-accept-third-party-software
```

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

```console
Expand Down
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ bootstrap_usage() {
printf " curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- [options]\n\n"
printf " Options:\n"
printf " --non-interactive Skip prompts (uses env vars / defaults)\n"
printf " --provider <name> Default inference provider for non-interactive onboard\n"
printf " --yes-i-accept-third-party-software Accept the third-party software notice in non-interactive mode\n"
printf " --version, -v Print installer version and exit\n"
printf " --help, -h Show this help message and exit\n\n"
Expand Down
19 changes: 16 additions & 3 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ usage() {
printf " curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- [options]\n\n"
printf " ${C_DIM}Options:${C_RESET}\n"
printf " --non-interactive Skip prompts (uses env vars / defaults)\n"
printf " --provider <name> Default inference provider for non-interactive onboard\n"
printf " --yes-i-accept-third-party-software Accept the third-party software notice in non-interactive mode\n"
printf " --version, -v Print installer version and exit\n"
printf " --help, -h Show this help message and exit\n\n"
Expand Down Expand Up @@ -1180,10 +1181,19 @@ post_install_message() {
main() {
# Parse flags
NON_INTERACTIVE=""
PROVIDER=""
ACCEPT_THIRD_PARTY_SOFTWARE=""
for arg in "$@"; do
case "$arg" in
while [[ $# -gt 0 ]]; do
case "$1" in
--non-interactive) NON_INTERACTIVE=1 ;;
--provider)
shift
if [[ $# -eq 0 || "$1" == --* ]]; then
usage
error "--provider requires a provider name"
fi
PROVIDER="$1"
;;
--yes-i-accept-third-party-software) ACCEPT_THIRD_PARTY_SOFTWARE=1 ;;
--version | -v)
local version_suffix
Expand All @@ -1197,14 +1207,17 @@ main() {
;;
*)
usage
error "Unknown option: $arg"
error "Unknown option: $1"
;;
esac
shift
done
# Also honor env var
NON_INTERACTIVE="${NON_INTERACTIVE:-${NEMOCLAW_NON_INTERACTIVE:-}}"
PROVIDER="${PROVIDER:-${NEMOCLAW_PROVIDER:-}}"
ACCEPT_THIRD_PARTY_SOFTWARE="${ACCEPT_THIRD_PARTY_SOFTWARE:-${NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE:-}}"
export NEMOCLAW_NON_INTERACTIVE="${NON_INTERACTIVE}"
export NEMOCLAW_PROVIDER="${PROVIDER}"
export NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE="${ACCEPT_THIRD_PARTY_SOFTWARE}"

_INSTALL_START=$SECONDS
Expand Down
50 changes: 50 additions & 0 deletions src/lib/onboard-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe("onboard command", () => {
resume: true,
recreateSandbox: false,
fromDockerfile: null,
provider: null,
acceptThirdPartySoftware: true,
agent: null,
dangerouslySkipPermissions: false,
Expand All @@ -54,6 +55,7 @@ describe("onboard command", () => {
resume: false,
recreateSandbox: false,
fromDockerfile: null,
provider: null,
acceptThirdPartySoftware: true,
agent: null,
dangerouslySkipPermissions: false,
Expand All @@ -78,6 +80,7 @@ describe("onboard command", () => {
resume: true,
recreateSandbox: false,
fromDockerfile: null,
provider: null,
acceptThirdPartySoftware: false,
agent: null,
dangerouslySkipPermissions: false,
Expand All @@ -102,6 +105,7 @@ describe("onboard command", () => {
expect(runOnboard).not.toHaveBeenCalled();
expect(lines.join("\n")).toContain("Usage: nemoclaw onboard");
expect(lines.join("\n")).toContain("--from <Dockerfile>");
expect(lines.join("\n")).toContain("--provider <name>");
expect(lines.join("\n")).toContain("--agent <name>");
expect(lines.join("\n")).toContain("--dangerously-skip-permissions");
});
Expand All @@ -125,12 +129,56 @@ describe("onboard command", () => {
resume: true,
recreateSandbox: false,
fromDockerfile: "/tmp/Custom.Dockerfile",
provider: null,
acceptThirdPartySoftware: false,
agent: null,
dangerouslySkipPermissions: false,
});
});

it("parses --provider <name>", () => {
expect(
parseOnboardArgs(
["--non-interactive", "--provider", "openai"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: ((code: number) => {
throw new Error(String(code));
}) as never,
},
),
).toEqual({
nonInteractive: true,
resume: false,
recreateSandbox: false,
fromDockerfile: null,
provider: "openai",
acceptThirdPartySoftware: false,
agent: null,
dangerouslySkipPermissions: false,
});
});

it("exits when --provider is missing its provider name", () => {
expect(() =>
parseOnboardArgs(
["--provider"],
"--yes-i-accept-third-party-software",
"NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE",
{
env: {},
error: () => {},
exit: ((code: number) => {
throw new Error(`exit:${code}`);
}) as never,
},
),
).toThrow("exit:1");
});

it("exits when --from is missing its Dockerfile path", () => {
expect(() =>
parseOnboardArgs(
Expand Down Expand Up @@ -188,6 +236,7 @@ describe("onboard command", () => {
resume: false,
recreateSandbox: false,
fromDockerfile: null,
provider: null,
acceptThirdPartySoftware: false,
agent: "openclaw",
dangerouslySkipPermissions: true,
Expand Down Expand Up @@ -239,6 +288,7 @@ describe("onboard command", () => {
resume: false,
recreateSandbox: false,
fromDockerfile: null,
provider: null,
acceptThirdPartySoftware: false,
agent: null,
dangerouslySkipPermissions: false,
Expand Down
16 changes: 15 additions & 1 deletion src/lib/onboard-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface OnboardCommandOptions {
resume: boolean;
recreateSandbox: boolean;
fromDockerfile: string | null;
provider: string | null;
acceptThirdPartySoftware: boolean;
agent: string | null;
dangerouslySkipPermissions: boolean;
Expand Down Expand Up @@ -36,7 +37,7 @@ const ONBOARD_BASE_ARGS = [

function onboardUsageLines(noticeAcceptFlag: string): string[] {
return [
` Usage: nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--agent <name>] [--dangerously-skip-permissions] [${noticeAcceptFlag}]`,
` Usage: nemoclaw onboard [--non-interactive] [--resume] [--recreate-sandbox] [--from <Dockerfile>] [--provider <name>] [--agent <name>] [--dangerously-skip-permissions] [${noticeAcceptFlag}]`,
"",
];
}
Expand Down Expand Up @@ -69,6 +70,18 @@ export function parseOnboardArgs(
parsedArgs.splice(fromIdx, 2);
}

let provider: string | null = null;
const providerIdx = parsedArgs.indexOf("--provider");
if (providerIdx !== -1) {
provider = parsedArgs[providerIdx + 1] || null;
if (!provider || provider.startsWith("--")) {
error(" --provider requires a provider name");
printOnboardUsage(error, noticeAcceptFlag);
exit(1);
}
parsedArgs.splice(providerIdx, 2);
}

let agent: string | null = null;
const agentIdx = parsedArgs.indexOf("--agent");
if (agentIdx !== -1) {
Expand Down Expand Up @@ -101,6 +114,7 @@ export function parseOnboardArgs(
resume: parsedArgs.includes("--resume"),
recreateSandbox: parsedArgs.includes("--recreate-sandbox"),
fromDockerfile,
provider,
acceptThirdPartySoftware:
parsedArgs.includes(noticeAcceptFlag) || String(deps.env[noticeAcceptEnv] || "") === "1",
agent,
Expand Down
3 changes: 3 additions & 0 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5827,6 +5827,9 @@ function skippedStepMessage(stepName, detail, reason = "resume") {
async function onboard(opts = {}) {
NON_INTERACTIVE = opts.nonInteractive || process.env.NEMOCLAW_NON_INTERACTIVE === "1";
RECREATE_SANDBOX = opts.recreateSandbox || process.env.NEMOCLAW_RECREATE_SANDBOX === "1";
if (opts.provider) {
process.env.NEMOCLAW_PROVIDER = opts.provider;
}
const dangerouslySkipPermissions =
opts.dangerouslySkipPermissions || process.env.NEMOCLAW_DANGEROUSLY_SKIP_PERMISSIONS === "1";
if (dangerouslySkipPermissions) {
Expand Down
8 changes: 8 additions & 0 deletions test/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ describe("CLI dispatch", () => {
expect(r.code).toBe(0);
expect(r.out.includes("Usage: nemoclaw onboard")).toBeTruthy();
expect(r.out.includes("--from <Dockerfile>")).toBeTruthy();
expect(r.out.includes("--provider <name>")).toBeTruthy();
});

it("unknown onboard option exits 1", () => {
Expand All @@ -142,6 +143,13 @@ describe("CLI dispatch", () => {
expect(r.out.includes("Unknown onboard option(s): --non-interactiv")).toBeTruthy();
});

it("accepts onboard --provider in CLI parsing", () => {
const r = run("onboard --provider openai --non-interactiv");
expect(r.code).toBe(1);
expect(r.out.includes("Unknown onboard option(s): --non-interactiv")).toBeTruthy();
expect(r.out.includes("Unknown onboard option(s): --provider")).toBeFalsy();
});

it("setup --help exits 0 and shows onboard usage", () => {
const r = run("setup --help");
expect(r.code).toBe(0);
Expand Down
104 changes: 104 additions & 0 deletions test/install-preflight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,110 @@ describe("installer flag parsing", () => {
expect(result.status).toBe(0);
expect(`${result.stdout}${result.stderr}`).toMatch(/NEMOCLAW_INSTALL_TAG/);
});

it("--help documents the provider flag", () => {
const result = spawnSync("bash", [INSTALLER, "--help"], {
cwd: path.join(import.meta.dirname, ".."),
encoding: "utf-8",
});

expect(result.status).toBe(0);
expect(`${result.stdout}${result.stderr}`).toMatch(/--provider <name>/);
});

it("rejects --provider without a provider name", () => {
const result = spawnSync("bash", [INSTALLER, "--provider"], {
cwd: path.join(import.meta.dirname, ".."),
encoding: "utf-8",
});

expect(result.status).not.toBe(0);
const output = `${result.stdout}${result.stderr}`;
expect(output).toMatch(/--provider requires a provider name/);
expect(output).toMatch(/NemoClaw Installer/);
});

it("forwards --provider into the onboard environment for non-interactive installs", () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-install-provider-forward-"));
const fakeBin = path.join(tmp, "bin");
const prefix = path.join(tmp, "prefix");
const onboardLog = path.join(tmp, "onboard.log");
fs.mkdirSync(fakeBin);
fs.mkdirSync(path.join(prefix, "bin"), { recursive: true });

writeNodeStub(fakeBin);
writeExecutable(
path.join(fakeBin, "docker"),
`#!/usr/bin/env bash
if [ "$1" = "info" ]; then
echo '{"ServerVersion":"29.3.1","OperatingSystem":"Ubuntu 24.04","CgroupVersion":"2"}'
exit 0
fi
exit 0
`,
);
writeExecutable(
path.join(fakeBin, "openshell"),
`#!/usr/bin/env bash
if [ "$1" = "--version" ]; then
echo "openshell 0.0.22"
exit 0
fi
exit 0
`,
);
writeNpmStub(
fakeBin,
`if [ "$1" = "pack" ]; then
tmpdir="$4"
mkdir -p "$tmpdir/package"
tar -czf "$tmpdir/openclaw-2026.3.11.tgz" -C "$tmpdir" package
exit 0
fi
if [ "$1" = "install" ]; then exit 0; fi
if [ "$1" = "run" ] && { [ "$2" = "build" ] || [ "$2" = "build:cli" ] || [ "$2" = "--if-present" ]; }; then exit 0; fi
if [ "$1" = "link" ]; then
cat > "$NPM_PREFIX/bin/nemoclaw" <<'EOS'
#!/usr/bin/env bash
if [ "$1" = "--version" ]; then echo "nemoclaw v0.1.0-test"; exit 0; fi
printf 'args:%s\\n' "$*" >> "$NEMOCLAW_ONBOARD_LOG"
printf 'provider:%s\\n' "\${NEMOCLAW_PROVIDER:-}" >> "$NEMOCLAW_ONBOARD_LOG"
exit 0
EOS
chmod +x "$NPM_PREFIX/bin/nemoclaw"
exit 0
fi`,
);

const result = spawnSync(
"bash",
[
INSTALLER,
"--non-interactive",
"--provider",
"openai",
"--yes-i-accept-third-party-software",
],
{
cwd: path.join(import.meta.dirname, ".."),
encoding: "utf-8",
env: {
...process.env,
HOME: tmp,
PATH: `${fakeBin}:${TEST_SYSTEM_PATH}`,
NPM_PREFIX: prefix,
NEMOCLAW_ONBOARD_LOG: onboardLog,
},
},
);

expect(result.status).toBe(0);
const onboardInvocation = fs.readFileSync(onboardLog, "utf-8");
expect(onboardInvocation).toMatch(
/^args:onboard --non-interactive --yes-i-accept-third-party-software$/m,
);
expect(onboardInvocation).toMatch(/^provider:openai$/m);
});
});

// ---------------------------------------------------------------------------
Expand Down
Loading