refactor: make setup the primary wizard surface

This commit is contained in:
Peter Steinberger
2026-03-15 21:59:51 -07:00
parent 98877dc413
commit 5c120cb36c
37 changed files with 115 additions and 495 deletions

View File

@@ -169,7 +169,7 @@ describe("registerPreActionHooks", () => {
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "all" });
});
it("keeps onboarding and channels add manifest-first", async () => {
it("keeps setup alias and channels add manifest-first", async () => {
await runPreAction({
parseArgv: ["onboard"],
processArgv: ["node", "openclaw", "onboard"],

View File

@@ -78,7 +78,7 @@ function shouldLoadPluginsForCommand(commandPath: string[], argv: string[]): boo
if ((primary === "status" || primary === "health") && hasFlag(argv, "--json")) {
return false;
}
// Onboarding/setup should stay manifest-first and load selected plugins on demand.
// Setup wizard and channels add should stay manifest-first and load selected plugins on demand.
if (primary === "onboard" || (primary === "channels" && secondary === "add")) {
return false;
}

View File

@@ -1,7 +1,7 @@
import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const onboardCommandMock = vi.fn();
const setupWizardCommandMock = vi.fn();
const runtime = {
log: vi.fn(),
@@ -23,7 +23,7 @@ vi.mock("../../commands/onboard-provider-auth-flags.js", () => ({
}));
vi.mock("../../commands/onboard.js", () => ({
onboardCommand: onboardCommandMock,
setupWizardCommand: setupWizardCommandMock,
}));
vi.mock("../../runtime.js", () => ({
@@ -45,13 +45,13 @@ describe("registerOnboardCommand", () => {
beforeEach(() => {
vi.clearAllMocks();
onboardCommandMock.mockResolvedValue(undefined);
setupWizardCommandMock.mockResolvedValue(undefined);
});
it("defaults installDaemon to undefined when no daemon flags are provided", async () => {
await runCli(["onboard"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
installDaemon: undefined,
}),
@@ -61,7 +61,7 @@ describe("registerOnboardCommand", () => {
it("sets installDaemon from explicit install flags and prioritizes --skip-daemon", async () => {
await runCli(["onboard", "--install-daemon"]);
expect(onboardCommandMock).toHaveBeenNthCalledWith(
expect(setupWizardCommandMock).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
installDaemon: true,
@@ -70,7 +70,7 @@ describe("registerOnboardCommand", () => {
);
await runCli(["onboard", "--no-install-daemon"]);
expect(onboardCommandMock).toHaveBeenNthCalledWith(
expect(setupWizardCommandMock).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
installDaemon: false,
@@ -79,7 +79,7 @@ describe("registerOnboardCommand", () => {
);
await runCli(["onboard", "--install-daemon", "--skip-daemon"]);
expect(onboardCommandMock).toHaveBeenNthCalledWith(
expect(setupWizardCommandMock).toHaveBeenNthCalledWith(
3,
expect.objectContaining({
installDaemon: false,
@@ -90,7 +90,7 @@ describe("registerOnboardCommand", () => {
it("parses numeric gateway port and drops invalid values", async () => {
await runCli(["onboard", "--gateway-port", "18789"]);
expect(onboardCommandMock).toHaveBeenNthCalledWith(
expect(setupWizardCommandMock).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
gatewayPort: 18789,
@@ -99,7 +99,7 @@ describe("registerOnboardCommand", () => {
);
await runCli(["onboard", "--gateway-port", "nope"]);
expect(onboardCommandMock).toHaveBeenNthCalledWith(
expect(setupWizardCommandMock).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
gatewayPort: undefined,
@@ -108,9 +108,9 @@ describe("registerOnboardCommand", () => {
);
});
it("forwards --reset-scope to onboard command options", async () => {
it("forwards --reset-scope to setup wizard options", async () => {
await runCli(["onboard", "--reset", "--reset-scope", "full"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
reset: true,
resetScope: "full",
@@ -121,7 +121,7 @@ describe("registerOnboardCommand", () => {
it("parses --mistral-api-key and forwards mistralApiKey", async () => {
await runCli(["onboard", "--mistral-api-key", "sk-mistral-test"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
mistralApiKey: "sk-mistral-test", // pragma: allowlist secret
}),
@@ -131,7 +131,7 @@ describe("registerOnboardCommand", () => {
it("forwards --gateway-token-ref-env", async () => {
await runCli(["onboard", "--gateway-token-ref-env", "OPENCLAW_GATEWAY_TOKEN"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
gatewayTokenRefEnv: "OPENCLAW_GATEWAY_TOKEN",
}),
@@ -139,12 +139,12 @@ describe("registerOnboardCommand", () => {
);
});
it("reports errors via runtime on onboard command failures", async () => {
onboardCommandMock.mockRejectedValueOnce(new Error("onboard failed"));
it("reports errors via runtime on setup wizard command failures", async () => {
setupWizardCommandMock.mockRejectedValueOnce(new Error("setup failed"));
await runCli(["onboard"]);
expect(runtime.error).toHaveBeenCalledWith("Error: onboard failed");
expect(runtime.error).toHaveBeenCalledWith("Error: setup failed");
expect(runtime.exit).toHaveBeenCalledWith(1);
});
});

View File

@@ -1,4 +1,5 @@
import type { Command } from "commander";
import { formatCliCommand } from "../../cli/command-format.js";
import { formatStaticAuthChoiceChoicesForCli } from "../../commands/auth-choice-options.static.js";
import type { GatewayDaemonRuntime } from "../../commands/daemon-runtime.js";
import { ONBOARD_PROVIDER_AUTH_FLAGS } from "../../commands/onboard-provider-auth-flags.js";
@@ -11,7 +12,7 @@ import type {
SecretInputMode,
TailscaleMode,
} from "../../commands/onboard-types.js";
import { onboardCommand } from "../../commands/onboard.js";
import { setupWizardCommand } from "../../commands/onboard.js";
import { defaultRuntime } from "../../runtime.js";
import { formatDocsLink } from "../../terminal/links.js";
import { theme } from "../../terminal/theme.js";
@@ -49,11 +50,14 @@ const AUTH_CHOICE_HELP = formatStaticAuthChoiceChoicesForCli({
export function registerOnboardCommand(program: Command) {
const command = program
.command("onboard")
.description("Interactive wizard to set up the gateway, workspace, and skills")
.addHelpText(
"after",
() =>
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/onboard", "docs.openclaw.ai/cli/onboard")}\n`,
.description('Legacy alias for "openclaw setup --wizard"')
.addHelpText("after", () =>
[
"",
`${theme.muted("Docs:")} ${formatDocsLink("/cli/setup", "docs.openclaw.ai/cli/setup")}`,
`${theme.muted("Prefer:")} ${formatCliCommand("openclaw setup --wizard")}`,
"",
].join("\n"),
)
.option("--workspace <dir>", "Agent workspace directory (default: ~/.openclaw/workspace)")
.option(
@@ -132,7 +136,7 @@ export function registerOnboardCommand(program: Command) {
});
const gatewayPort =
typeof opts.gatewayPort === "string" ? Number.parseInt(opts.gatewayPort, 10) : undefined;
await onboardCommand(
await setupWizardCommand(
{
workspace: opts.workspace as string | undefined,
nonInteractive: Boolean(opts.nonInteractive),

View File

@@ -2,7 +2,7 @@ import { Command } from "commander";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const setupCommandMock = vi.fn();
const onboardCommandMock = vi.fn();
const setupWizardCommandMock = vi.fn();
const runtime = {
log: vi.fn(),
error: vi.fn(),
@@ -14,7 +14,7 @@ vi.mock("../../commands/setup.js", () => ({
}));
vi.mock("../../commands/onboard.js", () => ({
onboardCommand: onboardCommandMock,
setupWizardCommand: setupWizardCommandMock,
}));
vi.mock("../../runtime.js", () => ({
@@ -37,7 +37,7 @@ describe("registerSetupCommand", () => {
beforeEach(() => {
vi.clearAllMocks();
setupCommandMock.mockResolvedValue(undefined);
onboardCommandMock.mockResolvedValue(undefined);
setupWizardCommandMock.mockResolvedValue(undefined);
});
it("runs setup command by default", async () => {
@@ -49,13 +49,13 @@ describe("registerSetupCommand", () => {
}),
runtime,
);
expect(onboardCommandMock).not.toHaveBeenCalled();
expect(setupWizardCommandMock).not.toHaveBeenCalled();
});
it("runs onboard command when --wizard is set", async () => {
it("runs setup wizard command when --wizard is set", async () => {
await runCli(["setup", "--wizard", "--mode", "remote", "--remote-url", "wss://example"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
mode: "remote",
remoteUrl: "wss://example",
@@ -65,10 +65,10 @@ describe("registerSetupCommand", () => {
expect(setupCommandMock).not.toHaveBeenCalled();
});
it("runs onboard command when wizard-only flags are passed explicitly", async () => {
it("runs setup wizard command when wizard-only flags are passed explicitly", async () => {
await runCli(["setup", "--mode", "remote", "--non-interactive"]);
expect(onboardCommandMock).toHaveBeenCalledWith(
expect(setupWizardCommandMock).toHaveBeenCalledWith(
expect.objectContaining({
mode: "remote",
nonInteractive: true,

View File

@@ -1,5 +1,5 @@
import type { Command } from "commander";
import { onboardCommand } from "../../commands/onboard.js";
import { setupWizardCommand } from "../../commands/onboard.js";
import { setupCommand } from "../../commands/setup.js";
import { defaultRuntime } from "../../runtime.js";
import { formatDocsLink } from "../../terminal/links.js";
@@ -10,7 +10,7 @@ import { hasExplicitOptions } from "../command-options.js";
export function registerSetupCommand(program: Command) {
program
.command("setup")
.description("Initialize ~/.openclaw/openclaw.json and the agent workspace")
.description("Initialize config/workspace or run the setup wizard")
.addHelpText(
"after",
() =>
@@ -20,8 +20,8 @@ export function registerSetupCommand(program: Command) {
"--workspace <dir>",
"Agent workspace directory (default: ~/.openclaw/workspace; stored as agents.defaults.workspace)",
)
.option("--wizard", "Run the interactive setup wizard", false)
.option("--non-interactive", "Run the wizard without prompts", false)
.option("--wizard", "Run the guided setup wizard", false)
.option("--non-interactive", "Run the setup wizard without prompts", false)
.option("--mode <mode>", "Wizard mode: local|remote")
.option("--remote-url <url>", "Remote Gateway WebSocket URL")
.option("--remote-token <token>", "Remote Gateway token (optional)")
@@ -35,7 +35,7 @@ export function registerSetupCommand(program: Command) {
"remoteToken",
]);
if (opts.wizard || hasWizardFlags) {
await onboardCommand(
await setupWizardCommand(
{
workspace: opts.workspace as string | undefined,
nonInteractive: Boolean(opts.nonInteractive),