mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
feat(onboard): add skip bootstrap flag (#71218)
This commit is contained in:
@@ -23,6 +23,7 @@ Interactive onboarding for local or remote Gateway setup.
|
||||
openclaw onboard
|
||||
openclaw onboard --flow quickstart
|
||||
openclaw onboard --flow manual
|
||||
openclaw onboard --skip-bootstrap
|
||||
openclaw onboard --mode remote --remote-url wss://gateway-host:18789
|
||||
```
|
||||
|
||||
@@ -115,6 +116,7 @@ Non-interactive local gateway health:
|
||||
- Unless you pass `--skip-health`, onboarding waits for a reachable local gateway before it exits successfully.
|
||||
- `--install-daemon` starts the managed gateway install path first. Without it, you must already have a local gateway running, for example `openclaw gateway run`.
|
||||
- If you only want config/workspace/bootstrap writes in automation, use `--skip-health`.
|
||||
- If you manage workspace files yourself, pass `--skip-bootstrap` to set `agents.defaults.skipBootstrap: true` and skip creating `AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`, and `BOOTSTRAP.md`.
|
||||
- On native Windows, `--install-daemon` tries Scheduled Tasks first and falls back to a per-user Startup-folder login item if task creation is denied.
|
||||
|
||||
Interactive onboarding behavior with reference mode:
|
||||
|
||||
@@ -28,8 +28,10 @@ inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspac
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -43,7 +45,7 @@ If you already manage the workspace files yourself, you can disable bootstrap
|
||||
file creation:
|
||||
|
||||
```json5
|
||||
{ agent: { skipBootstrap: true } }
|
||||
{ agents: { defaults: { skipBootstrap: true } } }
|
||||
```
|
||||
|
||||
## Extra workspace folders
|
||||
|
||||
@@ -44,7 +44,7 @@ If a file is missing, OpenClaw injects a single “missing file” marker line (
|
||||
To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
||||
|
||||
```json5
|
||||
{ agent: { skipBootstrap: true } }
|
||||
{ agents: { defaults: { skipBootstrap: true } } }
|
||||
```
|
||||
|
||||
## Built-in tools
|
||||
|
||||
@@ -22,6 +22,10 @@ On the first agent run, OpenClaw bootstraps the workspace (default
|
||||
- Writes identity + preferences to `IDENTITY.md`, `USER.md`, `SOUL.md`.
|
||||
- Removes `BOOTSTRAP.md` when finished so it only runs once.
|
||||
|
||||
## Skipping bootstrapping
|
||||
|
||||
To skip this for a pre-seeded workspace, run `openclaw onboard --skip-bootstrap`.
|
||||
|
||||
## Where it runs
|
||||
|
||||
Bootstrapping always runs on the **gateway host**. If the macOS app connects to
|
||||
|
||||
@@ -87,8 +87,10 @@ Optional: choose a different workspace with `agents.defaults.workspace` (support
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/.openclaw/workspace",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -97,8 +99,10 @@ If you already ship your own workspace files from a repo, you can disable bootst
|
||||
|
||||
```json5
|
||||
{
|
||||
agent: {
|
||||
skipBootstrap: true,
|
||||
agents: {
|
||||
defaults: {
|
||||
skipBootstrap: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -25,11 +25,14 @@ openclaw onboard --non-interactive \
|
||||
--gateway-bind loopback \
|
||||
--install-daemon \
|
||||
--daemon-runtime node \
|
||||
--skip-bootstrap \
|
||||
--skip-skills
|
||||
```
|
||||
|
||||
Add `--json` for a machine-readable summary.
|
||||
|
||||
Use `--skip-bootstrap` when your automation pre-seeds workspace files and does not want onboarding to create the default bootstrap files.
|
||||
|
||||
Use `--secret-input-mode ref` to store env-backed refs in auth profiles instead of plaintext values.
|
||||
Interactive selection between env refs and configured provider refs (`file` or `exec`) is available in the onboarding flow.
|
||||
|
||||
|
||||
@@ -260,6 +260,7 @@ is only a legacy import source.
|
||||
Typical fields in `~/.openclaw/openclaw.json`:
|
||||
|
||||
- `agents.defaults.workspace`
|
||||
- `agents.defaults.skipBootstrap` when `--skip-bootstrap` is passed
|
||||
- `agents.defaults.model` / `models.providers` (if Minimax chosen)
|
||||
- `tools.profile` (local onboarding defaults to `"coding"` when unset; existing explicit values are preserved)
|
||||
- `gateway.*` (mode, bind, auth, tailscale)
|
||||
|
||||
@@ -133,6 +133,16 @@ describe("registerOnboardCommand", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards --skip-bootstrap to setup wizard options", async () => {
|
||||
await runCli(["onboard", "--skip-bootstrap"]);
|
||||
expect(setupWizardCommandMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
skipBootstrap: true,
|
||||
}),
|
||||
runtime,
|
||||
);
|
||||
});
|
||||
|
||||
it("parses --mistral-api-key and forwards mistralApiKey", async () => {
|
||||
await runCli(["onboard", "--mistral-api-key", "sk-mistral-test"]);
|
||||
expect(setupWizardCommandMock).toHaveBeenCalledWith(
|
||||
|
||||
@@ -133,6 +133,7 @@ export function registerOnboardCommand(program: Command) {
|
||||
.option("--daemon-runtime <runtime>", "Daemon runtime: node|bun")
|
||||
.option("--skip-channels", "Skip channel setup")
|
||||
.option("--skip-skills", "Skip skills setup")
|
||||
.option("--skip-bootstrap", "Skip creating default agent workspace files")
|
||||
.option("--skip-search", "Skip search provider setup")
|
||||
.option("--skip-health", "Skip health check")
|
||||
.option("--skip-ui", "Skip Control UI/TUI prompts")
|
||||
@@ -189,6 +190,7 @@ export function registerOnboardCommand(program: Command) {
|
||||
daemonRuntime: opts.daemonRuntime as GatewayDaemonRuntime | undefined,
|
||||
skipChannels: Boolean(opts.skipChannels),
|
||||
skipSkills: Boolean(opts.skipSkills),
|
||||
skipBootstrap: Boolean(opts.skipBootstrap),
|
||||
skipSearch: Boolean(opts.skipSearch),
|
||||
skipHealth: Boolean(opts.skipHealth),
|
||||
skipUi: Boolean(opts.skipUi),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { setConfigValueAtPath } from "../config/config-paths.js";
|
||||
import type { DmScope } from "../config/types.base.js";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { ToolProfileId } from "../config/types.tools.js";
|
||||
@@ -32,3 +33,13 @@ export function applyLocalSetupWorkspaceConfig(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function applySkipBootstrapConfig(cfg: OpenClawConfig): OpenClawConfig {
|
||||
const next = structuredClone(cfg);
|
||||
setConfigValueAtPath(
|
||||
next as Record<string, unknown>,
|
||||
["agents", "defaults", "skipBootstrap"],
|
||||
true,
|
||||
);
|
||||
return next;
|
||||
}
|
||||
|
||||
@@ -353,6 +353,38 @@ describe("onboard (non-interactive): gateway and remote auth", () => {
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("persists skipBootstrap and skips workspace bootstrap creation", async () => {
|
||||
ensureWorkspaceAndSessionsMock.mockClear();
|
||||
await withStateDir("state-skip-bootstrap-", async (stateDir) => {
|
||||
const workspace = path.join(stateDir, "openclaw");
|
||||
|
||||
await runNonInteractiveSetup(
|
||||
{
|
||||
nonInteractive: true,
|
||||
mode: "local",
|
||||
workspace,
|
||||
authChoice: "skip",
|
||||
skipBootstrap: true,
|
||||
skipSkills: true,
|
||||
skipHealth: true,
|
||||
installDaemon: false,
|
||||
gatewayBind: "loopback",
|
||||
},
|
||||
runtime,
|
||||
);
|
||||
|
||||
const cfg = readTestConfig();
|
||||
|
||||
expect(cfg.agents?.defaults?.workspace).toBe(workspace);
|
||||
expect(cfg.agents?.defaults?.skipBootstrap).toBe(true);
|
||||
expect(ensureWorkspaceAndSessionsMock).toHaveBeenCalledWith(
|
||||
workspace,
|
||||
runtime,
|
||||
expect.objectContaining({ skipBootstrap: true }),
|
||||
);
|
||||
});
|
||||
}, 60_000);
|
||||
|
||||
it("writes gateway.remote url/token", async () => {
|
||||
await withStateDir("state-remote-", async (_stateDir) => {
|
||||
const port = getPseudoPort(30_000);
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { resolveGatewayAuthToken } from "../../gateway/auth-token-resolution.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { DEFAULT_GATEWAY_DAEMON_RUNTIME } from "../daemon-runtime.js";
|
||||
import { applyLocalSetupWorkspaceConfig } from "../onboard-config.js";
|
||||
import { applyLocalSetupWorkspaceConfig, applySkipBootstrapConfig } from "../onboard-config.js";
|
||||
import {
|
||||
applyWizardMetadata,
|
||||
DEFAULT_WORKSPACE,
|
||||
@@ -136,6 +136,9 @@ export async function runNonInteractiveLocalSetup(params: {
|
||||
});
|
||||
|
||||
let nextConfig: OpenClawConfig = applyLocalSetupWorkspaceConfig(baseConfig, workspaceDir);
|
||||
if (opts.skipBootstrap) {
|
||||
nextConfig = applySkipBootstrapConfig(nextConfig);
|
||||
}
|
||||
|
||||
const inferredAuthChoice = opts.authChoice
|
||||
? undefined
|
||||
|
||||
@@ -4,6 +4,7 @@ import { logConfigUpdated } from "../../config/logging.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { type RuntimeEnv, writeRuntimeJson } from "../../runtime.js";
|
||||
import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { applySkipBootstrapConfig } from "../onboard-config.js";
|
||||
import { applyWizardMetadata } from "../onboard-helpers.js";
|
||||
import type { OnboardOptions } from "../onboard-types.js";
|
||||
|
||||
@@ -34,6 +35,9 @@ export async function runNonInteractiveRemoteSetup(params: {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (opts.skipBootstrap) {
|
||||
nextConfig = applySkipBootstrapConfig(nextConfig);
|
||||
}
|
||||
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
||||
await replaceConfigFile({
|
||||
nextConfig,
|
||||
|
||||
@@ -76,6 +76,7 @@ export type OnboardOptions = OnboardDynamicProviderOptions & {
|
||||
/** @deprecated Legacy alias for `skipChannels`. */
|
||||
skipProviders?: boolean;
|
||||
skipSkills?: boolean;
|
||||
skipBootstrap?: boolean;
|
||||
skipSearch?: boolean;
|
||||
skipHealth?: boolean;
|
||||
skipUi?: boolean;
|
||||
|
||||
@@ -431,6 +431,49 @@ describe("runSetupWizard", () => {
|
||||
expect(runTui).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("persists skipBootstrap and skips workspace bootstrap creation when requested", async () => {
|
||||
ensureWorkspaceAndSessions.mockClear();
|
||||
writeConfigFile.mockClear();
|
||||
|
||||
const workspaceDir = await makeCaseDir("skip-bootstrap-");
|
||||
const prompter = buildWizardPrompter({});
|
||||
const runtime = createRuntime();
|
||||
|
||||
await runSetupWizard(
|
||||
{
|
||||
acceptRisk: true,
|
||||
flow: "quickstart",
|
||||
authChoice: "skip",
|
||||
installDaemon: false,
|
||||
skipBootstrap: true,
|
||||
skipChannels: true,
|
||||
skipSkills: true,
|
||||
skipSearch: true,
|
||||
skipHealth: true,
|
||||
skipUi: true,
|
||||
workspace: workspaceDir,
|
||||
},
|
||||
runtime,
|
||||
prompter,
|
||||
);
|
||||
|
||||
expect(writeConfigFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
agents: expect.objectContaining({
|
||||
defaults: expect.objectContaining({
|
||||
skipBootstrap: true,
|
||||
workspace: workspaceDir,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(ensureWorkspaceAndSessions).toHaveBeenCalledWith(
|
||||
workspaceDir,
|
||||
runtime,
|
||||
expect.objectContaining({ skipBootstrap: true }),
|
||||
);
|
||||
});
|
||||
|
||||
it("fails fast if the auth choice prompt returns nothing", async () => {
|
||||
promptAuthChoiceGrouped.mockImplementationOnce(async () => undefined as never);
|
||||
const prompter = buildWizardPrompter();
|
||||
|
||||
@@ -462,10 +462,14 @@ export async function runSetupWizard(
|
||||
|
||||
if (mode === "remote") {
|
||||
const { promptRemoteGatewayConfig } = await import("../commands/onboard-remote.js");
|
||||
const { applySkipBootstrapConfig } = await import("../commands/onboard-config.js");
|
||||
const { logConfigUpdated } = await loadConfigLoggingModule();
|
||||
let nextConfig = await promptRemoteGatewayConfig(baseConfig, prompter, {
|
||||
secretInputMode: opts.secretInputMode,
|
||||
});
|
||||
if (opts.skipBootstrap) {
|
||||
nextConfig = applySkipBootstrapConfig(nextConfig);
|
||||
}
|
||||
nextConfig = onboardHelpers.applyWizardMetadata(nextConfig, { command: "onboard", mode });
|
||||
await writeConfigFile(nextConfig);
|
||||
logConfigUpdated(runtime);
|
||||
@@ -484,8 +488,12 @@ export async function runSetupWizard(
|
||||
|
||||
const workspaceDir = resolveUserPath(workspaceInput.trim() || onboardHelpers.DEFAULT_WORKSPACE);
|
||||
|
||||
const { applyLocalSetupWorkspaceConfig } = await import("../commands/onboard-config.js");
|
||||
const { applyLocalSetupWorkspaceConfig, applySkipBootstrapConfig } =
|
||||
await import("../commands/onboard-config.js");
|
||||
let nextConfig: OpenClawConfig = applyLocalSetupWorkspaceConfig(baseConfig, workspaceDir);
|
||||
if (opts.skipBootstrap) {
|
||||
nextConfig = applySkipBootstrapConfig(nextConfig);
|
||||
}
|
||||
|
||||
const authChoiceFromPrompt = opts.authChoice === undefined;
|
||||
let authChoice: AuthChoice | undefined = opts.authChoice;
|
||||
|
||||
Reference in New Issue
Block a user