UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

381 lines (380 loc) • 22.2 kB
import { o as resolveUserPath } from "./home-dir-Dc6nN6HB.mjs"; import { r as defaultRuntime } from "./runtime-DlO3PTAd.mjs"; import { l as isValidEnvSecretRefId } from "./ref-contract-D0kWy5jy.mjs"; import "./types.secrets-DsPRn9Dz.mjs"; import "./utils-DgZn2dLu.mjs"; import { r as resolveStateDir } from "./state-dir-BHJORNly.mjs"; import { b as resolveGatewayPort } from "./paths-DGIwWbpu.mjs"; import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { t as formatCliCommand } from "./command-format-DwEdrDKL.mjs"; import { t as assertSupportedRuntime } from "./runtime-guard-BoZCdLcu.mjs"; import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs"; import "./session-key-C09ow4h0.mjs"; import { c as readConfigFileSnapshot } from "./io.runtime-BsUXSsD4.mjs"; import "./config-DLycWFvB.mjs"; import { i as resolveManifestProviderAuthChoices } from "./provider-auth-choices-DMYmVIgL.mjs"; import { n as resolveProviderInstallCatalogEntries } from "./provider-install-catalog-CCgeBGs_.mjs"; import { r as t } from "./i18n-Cuh2pEdZ.mjs"; import { o as handleReset, t as DEFAULT_WORKSPACE } from "./onboard-helpers-CMSgvi-w.mjs"; import { l as resolveOnboardingSetupTarget } from "./onboard-agent-target-C1NZC0Wr.mjs"; import { r as isTerminalInteractive } from "./terminal-interactivity-DXUXAq5U.mjs"; import "./onboard-interactive-runner-ChJuru4K.mjs"; import { r as resolveProviderMatch } from "./provider-auth-choice-helpers-gVHdr-sl.mjs"; import { o as normalizeTokenProviderInput } from "./provider-auth-input-CpNkpIyx.mjs"; import { a as GENERIC_PROVIDER_AUTH_CHOICES, r as formatAuthChoiceChoicesForCli } from "./auth-choice-options-fF-ZM1DL.mjs"; import { t as resolveLegacyOnboardAuthChoice } from "./auth-choice-legacy-BxqOc5gX.mjs"; import { n as validateGatewayWebSocketUrl } from "./onboard-remote-B3Cxe6-M.mjs"; import { u as withSetupMigrationTargetLock } from "./setup.migration-snapshot-Bw2NZurp.mjs"; import { n as validateOnboardingChoiceOptions, t as rejectOnboardingOption } from "./onboard-options-CNC8ppU5.mjs"; import { t as applyNonInteractiveGatewayConfig } from "./gateway-config--8xln9bh.mjs"; import { t as inferAuthChoiceFromFlags } from "./auth-choice-inference-CBZ-jsHw.mjs"; import path from "node:path"; //#region src/commands/onboard.ts /** * Top-level `openclaw onboard` command entrypoint. * * It validates global setup flags, performs optional reset handling, and then * routes to interactive or non-interactive onboarding. */ const VALID_RESET_SCOPES = /* @__PURE__ */ new Set([ "config", "config+creds+sessions", "full" ]); function validatePreflightOptions(opts, runtime) { if (opts.mode !== void 0 && opts.mode !== "local" && opts.mode !== "remote") return rejectOnboardingOption(opts, runtime, `Invalid --mode "${String(opts.mode)}". Use "local" or "remote", or run ${formatCliCommand("openclaw onboard")} for interactive setup.`); const remoteOnlyFlags = [ opts.remoteUrl !== void 0 ? "--remote-url" : void 0, opts.remoteToken !== void 0 ? "--remote-token" : void 0, opts.remotePassword !== void 0 ? "--remote-password" : void 0 ].filter((flag) => flag !== void 0); if (opts.nonInteractive && (opts.mode ?? "local") === "local" && remoteOnlyFlags.length > 0) return rejectOnboardingOption(opts, runtime, `${remoteOnlyFlags.join(" and ")} ${remoteOnlyFlags.length === 1 ? "requires" : "require"} --mode remote in non-interactive setup.`); for (const [flag, value] of [["--remote-token", opts.remoteToken], ["--remote-password", opts.remotePassword]]) if (value !== void 0 && !value.trim()) return rejectOnboardingOption(opts, runtime, `Invalid ${flag}: value cannot be empty.`); if (opts.remoteToken !== void 0 && opts.remotePassword !== void 0) return rejectOnboardingOption(opts, runtime, "Use either --remote-token or --remote-password, not both."); if (opts.mode === "remote") { const localGatewayCredentials = [ [ "--gateway-password", opts.gatewayPassword, "--remote-password" ], [ "--gateway-token", opts.gatewayToken, "--remote-token" ], [ "--gateway-token-ref-env", opts.gatewayTokenRefEnv, "--remote-token with --secret-input-mode ref" ] ]; for (const [flag, value, remoteFlag] of localGatewayCredentials) if (value !== void 0) return rejectOnboardingOption(opts, runtime, `${flag} configures local gateway auth. Use ${remoteFlag} in remote mode.`); } if (opts.nonInteractive && opts.secretInputMode === "ref") { const gatewayCredentials = [ [ "--gateway-password", opts.gatewayPassword, "OPENCLAW_GATEWAY_PASSWORD" ], [ "--remote-token", opts.remoteToken, "OPENCLAW_GATEWAY_TOKEN" ], [ "--remote-password", opts.remotePassword, "OPENCLAW_GATEWAY_PASSWORD" ] ]; for (const [flag, value, envName] of gatewayCredentials) { if (value === void 0) continue; const envValue = process.env[envName]?.trim(); if (!envValue) return rejectOnboardingOption(opts, runtime, `${flag} requires ${envName} to be set when --secret-input-mode ref is used.`); if (value.trim() !== envValue) return rejectOnboardingOption(opts, runtime, `${flag} does not match ${envName}. Set the environment variable to the same value or omit the flag.`); } } if (!validateOnboardingChoiceOptions(opts, runtime)) return false; if (opts.gatewayTokenRefEnv !== void 0) { const gatewayTokenRefEnv = opts.gatewayTokenRefEnv.trim(); if (!isValidEnvSecretRefId(gatewayTokenRefEnv)) return rejectOnboardingOption(opts, runtime, "Invalid --gateway-token-ref-env. Use an environment variable name like OPENCLAW_GATEWAY_TOKEN."); if (opts.gatewayToken !== void 0) return rejectOnboardingOption(opts, runtime, "Use either --gateway-token or --gateway-token-ref-env, not both. Prefer --gateway-token-ref-env to avoid writing plaintext tokens."); if (!process.env[gatewayTokenRefEnv]?.trim()) return rejectOnboardingOption(opts, runtime, `Environment variable "${gatewayTokenRefEnv}" is missing or empty. Export it first, then rerun ${formatCliCommand("openclaw onboard")}.`); } if (opts.nonInteractive && opts.mode === "remote" && !opts.remoteUrl?.trim()) return rejectOnboardingOption(opts, runtime, `Missing --remote-url for remote mode. Example: ${formatCliCommand("openclaw onboard --non-interactive --accept-risk --mode remote --remote-url ws://127.0.0.1:3000")}.`); if (opts.nonInteractive && opts.mode === "remote" && opts.remoteUrl?.trim()) { const remoteUrlError = validateGatewayWebSocketUrl(opts.remoteUrl); if (remoteUrlError) return rejectOnboardingOption(opts, runtime, remoteUrlError); } if (opts.nonInteractive && (opts.flow === "import" || opts.importSource || opts.importSecrets) && !opts.importFrom?.trim()) return rejectOnboardingOption(opts, runtime, `--import-from is required for non-interactive migration import. Run ${formatCliCommand("openclaw migrate list")} to choose a provider.`); return true; } async function validateResetAuthChoice(params) { const inferredAuthChoice = params.opts.authChoice || params.opts.mode === "remote" || !params.opts.nonInteractive && !wantsClassicInteractiveSetup(params.opts) ? void 0 : inferAuthChoiceFromFlags(params.opts, { config: params.baseConfig, workspaceDir: params.workspaceDir, env: process.env }); if (inferredAuthChoice && inferredAuthChoice.matches.length > 1) return rejectOnboardingOption(params.opts, params.runtime, [ `Multiple ${params.opts.nonInteractive ? "API key" : "provider credential"} flags were provided for ${params.opts.nonInteractive ? "non-interactive" : "interactive"} setup.`, "Use a single provider flag or pass --auth-choice explicitly.", `Flags: ${inferredAuthChoice.matches.map((match) => match.label).join(", ")}` ].join("\n")); if (!params.opts.nonInteractive && inferredAuthChoice) return true; const authChoice = params.opts.authChoice ?? inferredAuthChoice?.choice; if (!authChoice) return true; if (!new Set(formatAuthChoiceChoicesForCli({ config: params.baseConfig, workspaceDir: params.workspaceDir, env: process.env }).split("|")).has(authChoice)) return rejectOnboardingOption(params.opts, params.runtime, `Auth choice "${authChoice}" was not matched to a provider setup flow. Run ${formatCliCommand("openclaw onboard")} to choose interactively.`); const providerAuthChoices = [...resolveManifestProviderAuthChoices({ config: params.baseConfig, workspaceDir: params.workspaceDir, env: process.env, includeUntrustedWorkspacePlugins: false }), ...resolveProviderInstallCatalogEntries({ config: params.baseConfig, workspaceDir: params.workspaceDir, env: process.env, includeUntrustedWorkspacePlugins: false })]; const isGenericProviderChoice = GENERIC_PROVIDER_AUTH_CHOICES.includes(authChoice); const normalizedTokenProvider = normalizeTokenProviderInput(params.opts.tokenProvider); const inferredOptionKey = inferredAuthChoice?.matches[0]?.optionKey; const providerAuthChoice = isGenericProviderChoice ? providerAuthChoices.find((choice) => { const providerMatches = normalizedTokenProvider ? normalizeTokenProviderInput(choice.providerId) === normalizedTokenProvider || choice.providerAliases?.some((alias) => normalizeTokenProviderInput(alias) === normalizedTokenProvider) : inferredOptionKey !== void 0 && choice.optionKey === inferredOptionKey; const methodId = choice.methodId.toLowerCase(); const supportsAuthKind = authChoice === "apiKey" ? methodId.includes("api") && methodId.includes("key") : authChoice === "setup-token" ? methodId === "setup-token" : methodId.includes("token"); return providerMatches && supportsAuthKind; }) : providerAuthChoices.find((choice) => choice.choiceId === authChoice); if (params.opts.nonInteractive && isGenericProviderChoice && !normalizedTokenProvider && !inferredOptionKey) return rejectOnboardingOption(params.opts, params.runtime, `Auth choice "${authChoice}" requires --token-provider in non-interactive setup.`); if (params.opts.nonInteractive && (authChoice === "token" || authChoice === "setup-token") && !params.opts.token?.trim()) return rejectOnboardingOption(params.opts, params.runtime, `Auth choice "${authChoice}" requires --token in non-interactive setup.`); if (params.opts.nonInteractive && isGenericProviderChoice && !providerAuthChoice) return rejectOnboardingOption(params.opts, params.runtime, `Auth choice "${authChoice}" was not matched to provider "${params.opts.tokenProvider?.trim()}".`); if (!params.opts.nonInteractive || authChoice === "skip") return true; const { resolveNonInteractiveApiKey: resolveNonInteractiveCredential } = await import("./api-keys-C-3STY9N.mjs"); const target = resolveOnboardingSetupTarget(params.baseConfig, params.opts.agentName || params.opts.team ? { name: params.opts.agentName ?? "coordinator", workspaceDir: params.opts.team ? path.join(params.workspaceDir, normalizeAgentId(params.opts.agentName ?? "coordinator")) : params.workspaceDir } : void 0); if (authChoice === "custom-api-key") { const { applyCustomApiConfig, CustomApiError, parseNonInteractiveCustomApiFlags, resolveCustomProviderId } = await import("./onboard-custom-config-Calipnzt.mjs"); try { const custom = parseNonInteractiveCustomApiFlags({ baseUrl: params.opts.customBaseUrl, modelId: params.opts.customModelId, compatibility: params.opts.customCompatibility, apiKey: void 0, providerId: params.opts.customProviderId, supportsImageInput: params.opts.customImageInput }); const customProviderId = resolveCustomProviderId({ config: params.baseConfig, baseUrl: custom.baseUrl, providerId: custom.providerId }).providerId; const customCredential = await resolveNonInteractiveCredential({ provider: customProviderId, cfg: params.baseConfig, flagValue: params.opts.customApiKey, flagName: "--custom-api-key", envVar: "CUSTOM_API_KEY", runtime: params.runtime, agentDir: target.agentDir, workspaceDir: params.workspaceDir, allowProfile: params.resetScope === "config", required: false, secretInputMode: params.opts.secretInputMode, json: params.opts.json }); if (params.opts.customApiKey?.trim() && !customCredential) return false; applyCustomApiConfig({ ...custom, config: params.baseConfig, apiKey: void 0 }); } catch (error) { const message = error instanceof CustomApiError && (error.code === "missing_required" || error.code === "invalid_compatibility") ? error.message : `Invalid custom provider config: ${formatErrorMessage(error)}`; return rejectOnboardingOption(params.opts, params.runtime, message); } } else { const runtimeMethod = (providerAuthChoice ? resolveProviderMatch((await import("./provider-auth-choice.runtime.js")).resolvePluginProviders({ config: params.baseConfig, workspaceDir: params.workspaceDir, mode: "setup", includeUntrustedWorkspacePlugins: false, providerRefs: [providerAuthChoice.providerId], activate: true }), providerAuthChoice.providerId) : null)?.auth.find((method) => method.id === providerAuthChoice?.methodId || method.wizard?.choiceId === providerAuthChoice?.choiceId); if (!runtimeMethod?.runNonInteractive || !runtimeMethod.validateNonInteractive) { const reason = !runtimeMethod ? "provider unavailable" : !runtimeMethod.runNonInteractive ? "non-interactive setup unsupported" : "reset validation unavailable"; return rejectOnboardingOption(params.opts, params.runtime, `Auth choice "${authChoice}" cannot be safely preflighted with --reset (${reason}). Choose a provider method that supports non-interactive reset validation, or run setup without --reset.`); } if (!await runtimeMethod.validateNonInteractive({ authChoice, config: params.baseConfig, baseConfig: params.baseConfig, opts: params.opts, runtime: params.runtime, agentDir: target.agentDir, workspaceDir: params.workspaceDir, resolveApiKey: async (input) => await resolveNonInteractiveCredential({ ...input, cfg: params.baseConfig, runtime: params.runtime, agentDir: target.agentDir, workspaceDir: params.workspaceDir, allowProfile: input.allowProfile === false ? false : params.resetScope === "config", secretInputMode: params.opts.secretInputMode, json: params.opts.json }) })) return false; } return true; } /** * Interactive onboarding defaults to guided setup. Any explicit * setup flag beyond this allowlist keeps the classic wizard — those flags are * a public automation contract and guided setup does not honor them. * Most false booleans mean "not passed" because the command layer normalizes * them with Boolean(). False-valued explicit choices preserve undefined when * omitted, so daemon, Tailscale-reset, and custom-model input overrides are * special-cased. `--modern` never reaches this dispatch; the command layer * routes it through the inference-gated OpenClaw. */ const GUIDED_SAFE_ONBOARD_KEYS = /* @__PURE__ */ new Set([ "workspace", "acceptRisk", "reset", "resetScope", "nonInteractive", "agentName", "team", "tui", "skipUi", "suppressGatewayTokenOutput" ]); function wantsClassicInteractiveSetup(opts) { if (opts.classic === true) return true; if (opts.installDaemon !== void 0 || opts.customImageInput !== void 0) return true; return Object.entries(opts).some(([key, value]) => !GUIDED_SAFE_ONBOARD_KEYS.has(key) && value !== void 0 && value !== false); } /** Runs the onboard command after normalizing legacy flags and setup mode. */ async function setupWizardCommand(opts, runtime = defaultRuntime) { await assertSupportedRuntime(runtime); const { authChoice: normalizedAuthChoice, deprecated } = resolveLegacyOnboardAuthChoice(opts.authChoice, { env: process.env }); if (opts.nonInteractive && deprecated) { rejectOnboardingOption(opts, runtime, deprecated.nonInteractiveError); return; } if (deprecated) runtime.log(deprecated.message); const flow = opts.flow === "manual" ? "advanced" : opts.flow; const normalizedOpts = normalizedAuthChoice === opts.authChoice && flow === opts.flow ? opts : { ...opts, authChoice: normalizedAuthChoice, flow }; if (normalizedOpts.agentName !== void 0) { const { validateFirstOnboardingAgentName } = await import("./onboard-agent-EB6075nc.mjs"); const error = validateFirstOnboardingAgentName(normalizedOpts.agentName); if (error) { rejectOnboardingOption(normalizedOpts, runtime, `Invalid --agent-name: ${error}`); return; } } if (!validatePreflightOptions(normalizedOpts, runtime)) return; if (normalizedOpts.workspace?.trim()) { const { validateSetupWorkspacePath } = await import("./setup.workspace-Di_3xh9u.mjs"); const error = validateSetupWorkspacePath(normalizedOpts.workspace.trim()); if (error) { rejectOnboardingOption(normalizedOpts, runtime, `Invalid --workspace: ${error}`); return; } } if (normalizedOpts.team && (normalizedOpts.mode === "remote" || normalizedOpts.importFrom || normalizedOpts.importSource || normalizedOpts.flow === "import" || !normalizedOpts.nonInteractive && wantsClassicInteractiveSetup(normalizedOpts))) { rejectOnboardingOption(normalizedOpts, runtime, "--team supports local guided or non-interactive onboarding. Remove classic, remote, or import options."); return; } if (normalizedOpts.classic && normalizedOpts.nonInteractive) { rejectOnboardingOption(normalizedOpts, runtime, "--classic cannot be combined with --non-interactive. Remove --non-interactive to open the classic wizard, or remove --classic for automated setup."); return; } if (normalizedOpts.tui && normalizedOpts.nonInteractive) { rejectOnboardingOption(normalizedOpts, runtime, "--tui cannot be combined with --non-interactive. Remove --tui for automation, or remove --non-interactive to open the terminal hatch."); return; } if (normalizedOpts.secretInputMode && normalizedOpts.secretInputMode !== "plaintext" && normalizedOpts.secretInputMode !== "ref") { rejectOnboardingOption(normalizedOpts, runtime, `Invalid --secret-input-mode. Use "plaintext" or "ref", or run ${formatCliCommand("openclaw onboard")} for the interactive setup.`); return; } if (normalizedOpts.resetScope && !VALID_RESET_SCOPES.has(normalizedOpts.resetScope)) { rejectOnboardingOption(normalizedOpts, runtime, `Invalid --reset-scope. Use "config", "config+creds+sessions", or "full". Run ${formatCliCommand("openclaw onboard --reset --reset-scope config")} for a config-only reset.`); return; } if (normalizedOpts.resetScope && !normalizedOpts.reset) { rejectOnboardingOption(normalizedOpts, runtime, `--reset-scope requires --reset. Re-run with ${formatCliCommand(`openclaw onboard --reset --reset-scope ${normalizedOpts.resetScope}`)}.`); return; } if (normalizedOpts.nonInteractive && normalizedOpts.acceptRisk !== true) { rejectOnboardingOption(normalizedOpts, runtime, [ "Non-interactive setup requires explicit risk acknowledgement.", "Read: https://docs.openclaw.ai/security", `Re-run with: ${formatCliCommand("openclaw onboard --non-interactive --accept-risk ...")}` ].join("\n")); return; } if (!normalizedOpts.nonInteractive && !isTerminalInteractive()) { rejectOnboardingOption(normalizedOpts, runtime, t("wizard.guided.ttyRequired")); return; } if (process.platform === "win32") runtime.log([ "Windows detected - OpenClaw runs great on WSL2!", "Native Windows might be trickier.", "Quick setup: wsl --install (one command, one reboot)", "Guide: https://docs.openclaw.ai/windows" ].join("\n")); const runSetup = normalizedOpts.nonInteractive ? (await import("./onboard-non-interactive-BdLwu44n.mjs")).runNonInteractiveSetup : wantsClassicInteractiveSetup(normalizedOpts) ? (await import("./onboard-interactive-CjnObit9.mjs")).runInteractiveSetup : (await import("./commands/onboard-guided.js")).runGuidedOnboarding; const runSetupAfterOptionalReset = async () => { if (normalizedOpts.reset) { const snapshot = await readConfigFileSnapshot(); const baseConfig = snapshot.sourceConfig ?? (snapshot.valid ? snapshot.config : {}); const resetScope = normalizedOpts.resetScope ?? "config+creds+sessions"; const setupBaseConfig = {}; const setupWorkspaceDir = resolveUserPath(normalizedOpts.workspace ?? DEFAULT_WORKSPACE); const configuredWorkspace = normalizedOpts.workspace ?? baseConfig.agents?.defaults?.workspace; if (resetScope === "full" && normalizedOpts.workspace === void 0 && snapshot.exists && !snapshot.valid && snapshot.readError !== void 0) { rejectOnboardingOption(normalizedOpts, runtime, "Cannot determine the configured workspace from an unreadable config. Pass --workspace with the workspace to remove, or use a narrower --reset-scope."); return; } if (resetScope === "full" && configuredWorkspace !== void 0 && (typeof configuredWorkspace !== "string" || !configuredWorkspace.trim())) { rejectOnboardingOption(normalizedOpts, runtime, "Configured workspace is invalid. Pass --workspace with the workspace to remove, or use a narrower --reset-scope."); return; } const workspaceDir = resolveUserPath(typeof configuredWorkspace === "string" && configuredWorkspace.trim() ? configuredWorkspace : DEFAULT_WORKSPACE); if (!await validateResetAuthChoice({ opts: normalizedOpts, runtime, baseConfig: setupBaseConfig, workspaceDir: setupWorkspaceDir, resetScope })) return; if (normalizedOpts.nonInteractive && (normalizedOpts.mode ?? "local") !== "remote" && !await applyNonInteractiveGatewayConfig({ nextConfig: setupBaseConfig, opts: normalizedOpts, runtime, defaultPort: resolveGatewayPort(setupBaseConfig) })) return; if (normalizedOpts.importFrom || normalizedOpts.importSource || normalizedOpts.importSecrets || normalizedOpts.flow === "import") { rejectOnboardingOption(normalizedOpts, runtime, "Migration import cannot be combined with --reset because provider input must be planned before any state is removed. Run the import without --reset."); return; } await handleReset(resetScope, workspaceDir, runtime); } await runSetup(normalizedOpts, runtime); }; await withSetupMigrationTargetLock(resolveStateDir(), runSetupAfterOptionalReset); } //#endregion export { setupWizardCommand };