import type { Command } from "commander"; import { formatAuthChoiceChoicesForCli } from "../../commands/auth-choice-options.js"; import type { GatewayDaemonRuntime } from "../../commands/daemon-runtime.js"; import { ONBOARD_PROVIDER_AUTH_FLAGS } from "../../commands/onboard-provider-auth-flags.js"; import type { AuthChoice, GatewayAuthChoice, GatewayBind, NodeManagerChoice, TailscaleMode, } from "../../commands/onboard-types.js"; import { onboardCommand } from "../../commands/onboard.js"; import { defaultRuntime } from "../../runtime.js"; import { formatDocsLink } from "../../terminal/links.js"; import { theme } from "../../terminal/theme.js"; import { runCommandWithRuntime } from "../cli-utils.js"; function resolveInstallDaemonFlag( command: unknown, opts: { installDaemon?: boolean }, ): boolean | undefined { if (!command || typeof command !== "object") { return undefined; } const getOptionValueSource = "getOptionValueSource" in command ? command.getOptionValueSource : undefined; if (typeof getOptionValueSource !== "function") { return undefined; } // Commander doesn't support option conflicts natively; keep original behavior. // If --skip-daemon is explicitly passed, it wins. if (getOptionValueSource.call(command, "skipDaemon") === "cli") { return false; } if (getOptionValueSource.call(command, "installDaemon") === "cli") { return Boolean(opts.installDaemon); } return undefined; } const AUTH_CHOICE_HELP = formatAuthChoiceChoicesForCli({ includeLegacyAliases: true, includeSkip: true, }); 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`, ) .option("--workspace ", "Agent workspace directory (default: ~/.openclaw/workspace)") .option("--reset", "Reset config + credentials + sessions + workspace before running wizard") .option("--non-interactive", "Run without prompts", false) .option( "--accept-risk", "Acknowledge that agents are powerful and full system access is risky (required for --non-interactive)", false, ) .option("--flow ", "Wizard flow: quickstart|advanced|manual") .option("--mode ", "Wizard mode: local|remote") .option("--auth-choice ", `Auth: ${AUTH_CHOICE_HELP}`) .option( "--token-provider ", "Token provider id (non-interactive; used with --auth-choice token)", ) .option("--token ", "Token value (non-interactive; used with --auth-choice token)") .option( "--token-profile-id ", "Auth profile id (non-interactive; default: :manual)", ) .option("--token-expires-in ", "Optional token expiry duration (e.g. 365d, 12h)") .option("--cloudflare-ai-gateway-account-id ", "Cloudflare Account ID") .option("--cloudflare-ai-gateway-gateway-id ", "Cloudflare AI Gateway ID"); for (const providerFlag of ONBOARD_PROVIDER_AUTH_FLAGS) { command.option(providerFlag.cliOption, providerFlag.description); } command .option("--custom-base-url ", "Custom provider base URL") .option("--custom-api-key ", "Custom provider API key (optional)") .option("--custom-model-id ", "Custom provider model ID") .option("--custom-provider-id ", "Custom provider ID (optional; auto-derived by default)") .option( "--custom-compatibility ", "Custom provider API compatibility: openai|anthropic (default: openai)", ) .option("--gateway-port ", "Gateway port") .option("--gateway-bind ", "Gateway bind: loopback|tailnet|lan|auto|custom") .option("--gateway-auth ", "Gateway auth: token|password") .option("--gateway-token ", "Gateway token (token auth)") .option("--gateway-password ", "Gateway password (password auth)") .option("--remote-url ", "Remote Gateway WebSocket URL") .option("--remote-token ", "Remote Gateway token (optional)") .option("--tailscale ", "Tailscale: off|serve|funnel") .option("--tailscale-reset-on-exit", "Reset tailscale serve/funnel on exit") .option("--install-daemon", "Install gateway service") .option("--no-install-daemon", "Skip gateway service install") .option("--skip-daemon", "Skip gateway service install") .option("--daemon-runtime ", "Daemon runtime: node|bun") .option("--skip-channels", "Skip channel setup") .option("--skip-skills", "Skip skills setup") .option("--skip-health", "Skip health check") .option("--skip-ui", "Skip Control UI/TUI prompts") .option("--node-manager ", "Node manager for skills: npm|pnpm|bun") .option("--json", "Output JSON summary", false); command.action(async (opts, commandRuntime) => { await runCommandWithRuntime(defaultRuntime, async () => { const installDaemon = resolveInstallDaemonFlag(commandRuntime, { installDaemon: Boolean(opts.installDaemon), }); const gatewayPort = typeof opts.gatewayPort === "string" ? Number.parseInt(opts.gatewayPort, 10) : undefined; await onboardCommand( { workspace: opts.workspace as string | undefined, nonInteractive: Boolean(opts.nonInteractive), acceptRisk: Boolean(opts.acceptRisk), flow: opts.flow as "quickstart" | "advanced" | "manual" | undefined, mode: opts.mode as "local" | "remote" | undefined, authChoice: opts.authChoice as AuthChoice | undefined, tokenProvider: opts.tokenProvider as string | undefined, token: opts.token as string | undefined, tokenProfileId: opts.tokenProfileId as string | undefined, tokenExpiresIn: opts.tokenExpiresIn as string | undefined, anthropicApiKey: opts.anthropicApiKey as string | undefined, openaiApiKey: opts.openaiApiKey as string | undefined, openrouterApiKey: opts.openrouterApiKey as string | undefined, aiGatewayApiKey: opts.aiGatewayApiKey as string | undefined, cloudflareAiGatewayAccountId: opts.cloudflareAiGatewayAccountId as string | undefined, cloudflareAiGatewayGatewayId: opts.cloudflareAiGatewayGatewayId as string | undefined, cloudflareAiGatewayApiKey: opts.cloudflareAiGatewayApiKey as string | undefined, moonshotApiKey: opts.moonshotApiKey as string | undefined, kimiCodeApiKey: opts.kimiCodeApiKey as string | undefined, geminiApiKey: opts.geminiApiKey as string | undefined, zaiApiKey: opts.zaiApiKey as string | undefined, xiaomiApiKey: opts.xiaomiApiKey as string | undefined, qianfanApiKey: opts.qianfanApiKey as string | undefined, minimaxApiKey: opts.minimaxApiKey as string | undefined, syntheticApiKey: opts.syntheticApiKey as string | undefined, veniceApiKey: opts.veniceApiKey as string | undefined, togetherApiKey: opts.togetherApiKey as string | undefined, huggingfaceApiKey: opts.huggingfaceApiKey as string | undefined, opencodeZenApiKey: opts.opencodeZenApiKey as string | undefined, xaiApiKey: opts.xaiApiKey as string | undefined, litellmApiKey: opts.litellmApiKey as string | undefined, customBaseUrl: opts.customBaseUrl as string | undefined, customApiKey: opts.customApiKey as string | undefined, customModelId: opts.customModelId as string | undefined, customProviderId: opts.customProviderId as string | undefined, customCompatibility: opts.customCompatibility as "openai" | "anthropic" | undefined, gatewayPort: typeof gatewayPort === "number" && Number.isFinite(gatewayPort) ? gatewayPort : undefined, gatewayBind: opts.gatewayBind as GatewayBind | undefined, gatewayAuth: opts.gatewayAuth as GatewayAuthChoice | undefined, gatewayToken: opts.gatewayToken as string | undefined, gatewayPassword: opts.gatewayPassword as string | undefined, remoteUrl: opts.remoteUrl as string | undefined, remoteToken: opts.remoteToken as string | undefined, tailscale: opts.tailscale as TailscaleMode | undefined, tailscaleResetOnExit: Boolean(opts.tailscaleResetOnExit), reset: Boolean(opts.reset), installDaemon, daemonRuntime: opts.daemonRuntime as GatewayDaemonRuntime | undefined, skipChannels: Boolean(opts.skipChannels), skipSkills: Boolean(opts.skipSkills), skipHealth: Boolean(opts.skipHealth), skipUi: Boolean(opts.skipUi), nodeManager: opts.nodeManager as NodeManagerChoice | undefined, json: Boolean(opts.json), }, defaultRuntime, ); }); }); }