@gguf/claw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,155 lines (1,140 loc) • 38.4 kB
JavaScript
import { u as resolveGatewayPort } from "./paths-B4BZAPZh.js";
import { S as shortenHomePath, y as resolveUserPath } from "./utils-CP9YLh6M.js";
import { f as defaultRuntime, p as restoreTerminalState } from "./subsystem-BCQGGxdd.js";
import { Dt as upsertAuthProfile, J as resolveEnvApiKey, Rt as normalizeOptionalSecretInput, _t as resolveApiKeyForProfile, dt as resolveAuthProfileOrder, kt as ensureAuthProfileStore, l as normalizeProviderId, zt as normalizeSecretInput } from "./model-selection-CqaTAlhy.js";
import { t as formatCliCommand } from "./command-format-DEKzLnLg.js";
import { C as parseDurationMs, l as writeConfigFile, o as readConfigFileSnapshot } from "./config-PQiujvsf.js";
import { a as ensureWorkspaceAndSessions, b as waitForGatewayReachable, c as handleReset, g as resolveControlUiLinks, h as randomToken, n as applyWizardMetadata, t as DEFAULT_WORKSPACE } from "./onboard-helpers-DbjfBSMX.js";
import { t as assertSupportedRuntime } from "./runtime-guard-DlT3NecY.js";
import { t as WizardCancelledError } from "./prompts-xO_GDgJd.js";
import { t as createClackPrompter } from "./clack-prompter-C8qX15zH.js";
import { a as gatewayInstallErrorHint, i as buildGatewayInstallPlan, r as isGatewayDaemonRuntime, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-CZuP7tS4.js";
import { r as isSystemdUserServiceAvailable } from "./systemd-B6S4ND7H.js";
import { t as resolveGatewayService } from "./service-Bw9h81em.js";
import { r as healthCommand } from "./health-Bt3xmMjK.js";
import { t as runOnboardingWizard } from "./onboarding-DeU1kIgp.js";
import { $ as setHuggingfaceApiKey, A as applyXaiConfig, B as applyVercelAiGatewayConfig, E as applyTogetherConfig, I as applyLitellmConfig, M as applyXiaomiConfig, O as applyVeniceConfig, P as applyZaiConfig, Q as setGeminiApiKey, R as applyCloudflareAiGatewayConfig, S as applyQianfanConfig, X as setAnthropicApiKey, Z as setCloudflareAiGatewayConfig, _ as applyMoonshotConfigCn, a as applyMinimaxApiConfig, at as setOpenrouterApiKey, b as applyOpenrouterConfig, ct as setTogetherApiKey, d as applyAuthProfileConfig, dt as setXaiApiKey, et as setKimiCodingApiKey, f as applyHuggingfaceConfig, ft as setXiaomiApiKey, g as applyMoonshotConfig, it as setOpencodeZenApiKey, l as applyMinimaxConfig, lt as setVeniceApiKey, m as applyKimiCodeConfig, n as validateAnthropicSetupToken, nt as setMinimaxApiKey, o as applyMinimaxApiConfigCn, ot as setQianfanApiKey, pt as setZaiApiKey, r as applyOpencodeZenConfig, rt as setMoonshotApiKey, st as setSyntheticApiKey, t as buildTokenProfileId, tt as setLitellmApiKey, ut as setVercelAiGatewayApiKey, w as applySyntheticConfig } from "./auth-token-CliHE70g.js";
import { n as logConfigUpdated } from "./logging-Cl6-FWdR.js";
import { n as isDeprecatedAuthChoice, r as normalizeLegacyOnboardAuthChoice } from "./auth-choice-legacy-Ch38-Kbr.js";
import { a as detectZaiEndpoint, i as upsertSharedEnvVar, n as applyOpenAIConfig, s as applyGoogleGeminiModelDefault } from "./openai-model-default-8jds2v9m.js";
import { n as ensureSystemdUserLingerNonInteractive } from "./systemd-linger-C3Tnphf9.js";
import { i as parseNonInteractiveCustomApiFlags, n as applyCustomApiConfig, o as resolveCustomProviderId, t as CustomApiError } from "./onboard-custom-DQu1PmgP.js";
import { t as applyOnboardingLocalWorkspaceConfig } from "./onboard-config-B9jTl83d.js";
//#region src/commands/onboard-provider-auth-flags.ts
const ONBOARD_PROVIDER_AUTH_FLAGS = [
{
optionKey: "anthropicApiKey",
authChoice: "apiKey",
cliFlag: "--anthropic-api-key",
cliOption: "--anthropic-api-key <key>",
description: "Anthropic API key"
},
{
optionKey: "openaiApiKey",
authChoice: "openai-api-key",
cliFlag: "--openai-api-key",
cliOption: "--openai-api-key <key>",
description: "OpenAI API key"
},
{
optionKey: "openrouterApiKey",
authChoice: "openrouter-api-key",
cliFlag: "--openrouter-api-key",
cliOption: "--openrouter-api-key <key>",
description: "OpenRouter API key"
},
{
optionKey: "aiGatewayApiKey",
authChoice: "ai-gateway-api-key",
cliFlag: "--ai-gateway-api-key",
cliOption: "--ai-gateway-api-key <key>",
description: "Vercel AI Gateway API key"
},
{
optionKey: "cloudflareAiGatewayApiKey",
authChoice: "cloudflare-ai-gateway-api-key",
cliFlag: "--cloudflare-ai-gateway-api-key",
cliOption: "--cloudflare-ai-gateway-api-key <key>",
description: "Cloudflare AI Gateway API key"
},
{
optionKey: "moonshotApiKey",
authChoice: "moonshot-api-key",
cliFlag: "--moonshot-api-key",
cliOption: "--moonshot-api-key <key>",
description: "Moonshot API key"
},
{
optionKey: "kimiCodeApiKey",
authChoice: "kimi-code-api-key",
cliFlag: "--kimi-code-api-key",
cliOption: "--kimi-code-api-key <key>",
description: "Kimi Coding API key"
},
{
optionKey: "geminiApiKey",
authChoice: "gemini-api-key",
cliFlag: "--gemini-api-key",
cliOption: "--gemini-api-key <key>",
description: "Gemini API key"
},
{
optionKey: "zaiApiKey",
authChoice: "zai-api-key",
cliFlag: "--zai-api-key",
cliOption: "--zai-api-key <key>",
description: "Z.AI API key"
},
{
optionKey: "xiaomiApiKey",
authChoice: "xiaomi-api-key",
cliFlag: "--xiaomi-api-key",
cliOption: "--xiaomi-api-key <key>",
description: "Xiaomi API key"
},
{
optionKey: "minimaxApiKey",
authChoice: "minimax-api",
cliFlag: "--minimax-api-key",
cliOption: "--minimax-api-key <key>",
description: "MiniMax API key"
},
{
optionKey: "syntheticApiKey",
authChoice: "synthetic-api-key",
cliFlag: "--synthetic-api-key",
cliOption: "--synthetic-api-key <key>",
description: "Synthetic API key"
},
{
optionKey: "veniceApiKey",
authChoice: "venice-api-key",
cliFlag: "--venice-api-key",
cliOption: "--venice-api-key <key>",
description: "Venice API key"
},
{
optionKey: "togetherApiKey",
authChoice: "together-api-key",
cliFlag: "--together-api-key",
cliOption: "--together-api-key <key>",
description: "Together AI API key"
},
{
optionKey: "huggingfaceApiKey",
authChoice: "huggingface-api-key",
cliFlag: "--huggingface-api-key",
cliOption: "--huggingface-api-key <key>",
description: "Hugging Face API key (HF token)"
},
{
optionKey: "opencodeZenApiKey",
authChoice: "opencode-zen",
cliFlag: "--opencode-zen-api-key",
cliOption: "--opencode-zen-api-key <key>",
description: "OpenCode Zen API key"
},
{
optionKey: "xaiApiKey",
authChoice: "xai-api-key",
cliFlag: "--xai-api-key",
cliOption: "--xai-api-key <key>",
description: "xAI API key"
},
{
optionKey: "litellmApiKey",
authChoice: "litellm-api-key",
cliFlag: "--litellm-api-key",
cliOption: "--litellm-api-key <key>",
description: "LiteLLM API key"
},
{
optionKey: "qianfanApiKey",
authChoice: "qianfan-api-key",
cliFlag: "--qianfan-api-key",
cliOption: "--qianfan-api-key <key>",
description: "QIANFAN API key"
}
];
//#endregion
//#region src/commands/onboard-interactive.ts
async function runInteractiveOnboarding(opts, runtime = defaultRuntime) {
const prompter = createClackPrompter();
let exitCode = null;
try {
await runOnboardingWizard(opts, runtime, prompter);
} catch (err) {
if (err instanceof WizardCancelledError) {
exitCode = 1;
return;
}
throw err;
} finally {
restoreTerminalState("onboarding finish", { resumeStdinIfPaused: false });
if (exitCode !== null) runtime.exit(exitCode);
}
}
//#endregion
//#region src/commands/onboard-non-interactive/local/auth-choice-inference.ts
function hasStringValue(value) {
return typeof value === "string" ? value.trim().length > 0 : Boolean(value);
}
function inferAuthChoiceFromFlags(opts) {
const matches = ONBOARD_PROVIDER_AUTH_FLAGS.filter(({ optionKey }) => hasStringValue(opts[optionKey])).map((flag) => ({
optionKey: flag.optionKey,
authChoice: flag.authChoice,
label: flag.cliFlag
}));
if (hasStringValue(opts.customBaseUrl) || hasStringValue(opts.customModelId) || hasStringValue(opts.customApiKey)) matches.push({
optionKey: "customBaseUrl",
authChoice: "custom-api-key",
label: "--custom-base-url/--custom-model-id/--custom-api-key"
});
return {
choice: matches[0]?.authChoice,
matches
};
}
//#endregion
//#region src/commands/onboard-non-interactive/api-keys.ts
async function resolveApiKeyFromProfiles(params) {
const store = ensureAuthProfileStore(params.agentDir);
const order = resolveAuthProfileOrder({
cfg: params.cfg,
store,
provider: params.provider
});
for (const profileId of order) {
if (store.profiles[profileId]?.type !== "api_key") continue;
const resolved = await resolveApiKeyForProfile({
cfg: params.cfg,
store,
profileId,
agentDir: params.agentDir
});
if (resolved?.apiKey) return resolved.apiKey;
}
return null;
}
async function resolveNonInteractiveApiKey(params) {
const flagKey = normalizeOptionalSecretInput(params.flagValue);
if (flagKey) return {
key: flagKey,
source: "flag"
};
const envResolved = resolveEnvApiKey(params.provider);
if (envResolved?.apiKey) return {
key: envResolved.apiKey,
source: "env"
};
const explicitEnvVar = params.envVarName?.trim();
if (explicitEnvVar) {
const explicitEnvKey = normalizeOptionalSecretInput(process.env[explicitEnvVar]);
if (explicitEnvKey) return {
key: explicitEnvKey,
source: "env"
};
}
if (params.allowProfile ?? true) {
const profileKey = await resolveApiKeyFromProfiles({
provider: params.provider,
cfg: params.cfg,
agentDir: params.agentDir
});
if (profileKey) return {
key: profileKey,
source: "profile"
};
}
if (params.required === false) return null;
const profileHint = params.allowProfile === false ? "" : `, or existing ${params.provider} API-key profile`;
params.runtime.error(`Missing ${params.flagName} (or ${params.envVar} in env${profileHint}).`);
params.runtime.exit(1);
return null;
}
//#endregion
//#region src/commands/onboard-non-interactive/local/auth-choice.ts
async function applyNonInteractiveAuthChoice(params) {
const { authChoice, opts, runtime, baseConfig } = params;
let nextConfig = params.nextConfig;
if (authChoice === "claude-cli" || authChoice === "codex-cli") {
runtime.error([`Auth choice "${authChoice}" is deprecated.`, "Use \"--auth-choice token\" (Anthropic setup-token) or \"--auth-choice openai-codex\"."].join("\n"));
runtime.exit(1);
return null;
}
if (authChoice === "setup-token") {
runtime.error(["Auth choice \"setup-token\" requires interactive mode.", "Use \"--auth-choice token\" with --token and --token-provider anthropic."].join("\n"));
runtime.exit(1);
return null;
}
if (authChoice === "vllm") {
runtime.error(["Auth choice \"vllm\" requires interactive mode.", "Use interactive onboard/configure to enter base URL, API key, and model ID."].join("\n"));
runtime.exit(1);
return null;
}
if (authChoice === "apiKey") {
const resolved = await resolveNonInteractiveApiKey({
provider: "anthropic",
cfg: baseConfig,
flagValue: opts.anthropicApiKey,
flagName: "--anthropic-api-key",
envVar: "ANTHROPIC_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setAnthropicApiKey(resolved.key);
return applyAuthProfileConfig(nextConfig, {
profileId: "anthropic:default",
provider: "anthropic",
mode: "api_key"
});
}
if (authChoice === "token") {
const providerRaw = opts.tokenProvider?.trim();
if (!providerRaw) {
runtime.error("Missing --token-provider for --auth-choice token.");
runtime.exit(1);
return null;
}
const provider = normalizeProviderId(providerRaw);
if (provider !== "anthropic") {
runtime.error("Only --token-provider anthropic is supported for --auth-choice token.");
runtime.exit(1);
return null;
}
const tokenRaw = normalizeSecretInput(opts.token);
if (!tokenRaw) {
runtime.error("Missing --token for --auth-choice token.");
runtime.exit(1);
return null;
}
const tokenError = validateAnthropicSetupToken(tokenRaw);
if (tokenError) {
runtime.error(tokenError);
runtime.exit(1);
return null;
}
let expires;
const expiresInRaw = opts.tokenExpiresIn?.trim();
if (expiresInRaw) try {
expires = Date.now() + parseDurationMs(expiresInRaw, { defaultUnit: "d" });
} catch (err) {
runtime.error(`Invalid --token-expires-in: ${String(err)}`);
runtime.exit(1);
return null;
}
const profileId = opts.tokenProfileId?.trim() || buildTokenProfileId({
provider,
name: ""
});
upsertAuthProfile({
profileId,
credential: {
type: "token",
provider,
token: tokenRaw.trim(),
...expires ? { expires } : {}
}
});
return applyAuthProfileConfig(nextConfig, {
profileId,
provider,
mode: "token"
});
}
if (authChoice === "gemini-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "google",
cfg: baseConfig,
flagValue: opts.geminiApiKey,
flagName: "--gemini-api-key",
envVar: "GEMINI_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setGeminiApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "google:default",
provider: "google",
mode: "api_key"
});
return applyGoogleGeminiModelDefault(nextConfig).next;
}
if (authChoice === "zai-api-key" || authChoice === "zai-coding-global" || authChoice === "zai-coding-cn" || authChoice === "zai-global" || authChoice === "zai-cn") {
const resolved = await resolveNonInteractiveApiKey({
provider: "zai",
cfg: baseConfig,
flagValue: opts.zaiApiKey,
flagName: "--zai-api-key",
envVar: "ZAI_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setZaiApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "zai:default",
provider: "zai",
mode: "api_key"
});
let endpoint;
let modelIdOverride;
if (authChoice === "zai-coding-global") endpoint = "coding-global";
else if (authChoice === "zai-coding-cn") endpoint = "coding-cn";
else if (authChoice === "zai-global") endpoint = "global";
else if (authChoice === "zai-cn") endpoint = "cn";
else {
const detected = await detectZaiEndpoint({ apiKey: resolved.key });
if (detected) {
endpoint = detected.endpoint;
modelIdOverride = detected.modelId;
} else endpoint = "global";
}
return applyZaiConfig(nextConfig, {
endpoint,
...modelIdOverride ? { modelId: modelIdOverride } : {}
});
}
if (authChoice === "xiaomi-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "xiaomi",
cfg: baseConfig,
flagValue: opts.xiaomiApiKey,
flagName: "--xiaomi-api-key",
envVar: "XIAOMI_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setXiaomiApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "xiaomi:default",
provider: "xiaomi",
mode: "api_key"
});
return applyXiaomiConfig(nextConfig);
}
if (authChoice === "xai-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "xai",
cfg: baseConfig,
flagValue: opts.xaiApiKey,
flagName: "--xai-api-key",
envVar: "XAI_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") setXaiApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "xai:default",
provider: "xai",
mode: "api_key"
});
return applyXaiConfig(nextConfig);
}
if (authChoice === "qianfan-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "qianfan",
cfg: baseConfig,
flagValue: opts.qianfanApiKey,
flagName: "--qianfan-api-key",
envVar: "QIANFAN_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") setQianfanApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "qianfan:default",
provider: "qianfan",
mode: "api_key"
});
return applyQianfanConfig(nextConfig);
}
if (authChoice === "openai-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "openai",
cfg: baseConfig,
flagValue: opts.openaiApiKey,
flagName: "--openai-api-key",
envVar: "OPENAI_API_KEY",
runtime,
allowProfile: false
});
if (!resolved) return null;
const key = resolved.key;
const result = upsertSharedEnvVar({
key: "OPENAI_API_KEY",
value: key
});
process.env.OPENAI_API_KEY = key;
runtime.log(`Saved OPENAI_API_KEY to ${shortenHomePath(result.path)}`);
return applyOpenAIConfig(nextConfig);
}
if (authChoice === "openrouter-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "openrouter",
cfg: baseConfig,
flagValue: opts.openrouterApiKey,
flagName: "--openrouter-api-key",
envVar: "OPENROUTER_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setOpenrouterApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "openrouter:default",
provider: "openrouter",
mode: "api_key"
});
return applyOpenrouterConfig(nextConfig);
}
if (authChoice === "litellm-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "litellm",
cfg: baseConfig,
flagValue: opts.litellmApiKey,
flagName: "--litellm-api-key",
envVar: "LITELLM_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setLitellmApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "litellm:default",
provider: "litellm",
mode: "api_key"
});
return applyLitellmConfig(nextConfig);
}
if (authChoice === "ai-gateway-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "vercel-ai-gateway",
cfg: baseConfig,
flagValue: opts.aiGatewayApiKey,
flagName: "--ai-gateway-api-key",
envVar: "AI_GATEWAY_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setVercelAiGatewayApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "vercel-ai-gateway:default",
provider: "vercel-ai-gateway",
mode: "api_key"
});
return applyVercelAiGatewayConfig(nextConfig);
}
if (authChoice === "cloudflare-ai-gateway-api-key") {
const accountId = opts.cloudflareAiGatewayAccountId?.trim() ?? "";
const gatewayId = opts.cloudflareAiGatewayGatewayId?.trim() ?? "";
if (!accountId || !gatewayId) {
runtime.error(["Auth choice \"cloudflare-ai-gateway-api-key\" requires Account ID and Gateway ID.", "Use --cloudflare-ai-gateway-account-id and --cloudflare-ai-gateway-gateway-id."].join("\n"));
runtime.exit(1);
return null;
}
const resolved = await resolveNonInteractiveApiKey({
provider: "cloudflare-ai-gateway",
cfg: baseConfig,
flagValue: opts.cloudflareAiGatewayApiKey,
flagName: "--cloudflare-ai-gateway-api-key",
envVar: "CLOUDFLARE_AI_GATEWAY_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setCloudflareAiGatewayConfig(accountId, gatewayId, resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "cloudflare-ai-gateway:default",
provider: "cloudflare-ai-gateway",
mode: "api_key"
});
return applyCloudflareAiGatewayConfig(nextConfig, {
accountId,
gatewayId
});
}
const applyMoonshotApiKeyChoice = async (applyConfig) => {
const resolved = await resolveNonInteractiveApiKey({
provider: "moonshot",
cfg: baseConfig,
flagValue: opts.moonshotApiKey,
flagName: "--moonshot-api-key",
envVar: "MOONSHOT_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setMoonshotApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "moonshot:default",
provider: "moonshot",
mode: "api_key"
});
return applyConfig(nextConfig);
};
if (authChoice === "moonshot-api-key") return await applyMoonshotApiKeyChoice(applyMoonshotConfig);
if (authChoice === "moonshot-api-key-cn") return await applyMoonshotApiKeyChoice(applyMoonshotConfigCn);
if (authChoice === "kimi-code-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "kimi-coding",
cfg: baseConfig,
flagValue: opts.kimiCodeApiKey,
flagName: "--kimi-code-api-key",
envVar: "KIMI_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setKimiCodingApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "kimi-coding:default",
provider: "kimi-coding",
mode: "api_key"
});
return applyKimiCodeConfig(nextConfig);
}
if (authChoice === "synthetic-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "synthetic",
cfg: baseConfig,
flagValue: opts.syntheticApiKey,
flagName: "--synthetic-api-key",
envVar: "SYNTHETIC_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setSyntheticApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "synthetic:default",
provider: "synthetic",
mode: "api_key"
});
return applySyntheticConfig(nextConfig);
}
if (authChoice === "venice-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "venice",
cfg: baseConfig,
flagValue: opts.veniceApiKey,
flagName: "--venice-api-key",
envVar: "VENICE_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setVeniceApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "venice:default",
provider: "venice",
mode: "api_key"
});
return applyVeniceConfig(nextConfig);
}
if (authChoice === "minimax-cloud" || authChoice === "minimax-api" || authChoice === "minimax-api-key-cn" || authChoice === "minimax-api-lightning") {
const isCn = authChoice === "minimax-api-key-cn";
const providerId = isCn ? "minimax-cn" : "minimax";
const profileId = `${providerId}:default`;
const resolved = await resolveNonInteractiveApiKey({
provider: providerId,
cfg: baseConfig,
flagValue: opts.minimaxApiKey,
flagName: "--minimax-api-key",
envVar: "MINIMAX_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setMinimaxApiKey(resolved.key, void 0, profileId);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId,
provider: providerId,
mode: "api_key"
});
const modelId = authChoice === "minimax-api-lightning" ? "MiniMax-M2.5-Lightning" : "MiniMax-M2.5";
return isCn ? applyMinimaxApiConfigCn(nextConfig, modelId) : applyMinimaxApiConfig(nextConfig, modelId);
}
if (authChoice === "minimax") return applyMinimaxConfig(nextConfig);
if (authChoice === "opencode-zen") {
const resolved = await resolveNonInteractiveApiKey({
provider: "opencode",
cfg: baseConfig,
flagValue: opts.opencodeZenApiKey,
flagName: "--opencode-zen-api-key",
envVar: "OPENCODE_API_KEY (or OPENCODE_ZEN_API_KEY)",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setOpencodeZenApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "opencode:default",
provider: "opencode",
mode: "api_key"
});
return applyOpencodeZenConfig(nextConfig);
}
if (authChoice === "together-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "together",
cfg: baseConfig,
flagValue: opts.togetherApiKey,
flagName: "--together-api-key",
envVar: "TOGETHER_API_KEY",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setTogetherApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "together:default",
provider: "together",
mode: "api_key"
});
return applyTogetherConfig(nextConfig);
}
if (authChoice === "huggingface-api-key") {
const resolved = await resolveNonInteractiveApiKey({
provider: "huggingface",
cfg: baseConfig,
flagValue: opts.huggingfaceApiKey,
flagName: "--huggingface-api-key",
envVar: "HF_TOKEN",
runtime
});
if (!resolved) return null;
if (resolved.source !== "profile") await setHuggingfaceApiKey(resolved.key);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "huggingface:default",
provider: "huggingface",
mode: "api_key"
});
return applyHuggingfaceConfig(nextConfig);
}
if (authChoice === "custom-api-key") try {
const customAuth = parseNonInteractiveCustomApiFlags({
baseUrl: opts.customBaseUrl,
modelId: opts.customModelId,
compatibility: opts.customCompatibility,
apiKey: opts.customApiKey,
providerId: opts.customProviderId
});
const resolvedCustomApiKey = await resolveNonInteractiveApiKey({
provider: resolveCustomProviderId({
config: nextConfig,
baseUrl: customAuth.baseUrl,
providerId: customAuth.providerId
}).providerId,
cfg: baseConfig,
flagValue: customAuth.apiKey,
flagName: "--custom-api-key",
envVar: "CUSTOM_API_KEY",
envVarName: "CUSTOM_API_KEY",
runtime,
required: false
});
const result = applyCustomApiConfig({
config: nextConfig,
baseUrl: customAuth.baseUrl,
modelId: customAuth.modelId,
compatibility: customAuth.compatibility,
apiKey: resolvedCustomApiKey?.key,
providerId: customAuth.providerId
});
if (result.providerIdRenamedFrom && result.providerId) runtime.log(`Custom provider ID "${result.providerIdRenamedFrom}" already exists for a different base URL. Using "${result.providerId}".`);
return result.config;
} catch (err) {
if (err instanceof CustomApiError) {
switch (err.code) {
case "missing_required":
case "invalid_compatibility":
runtime.error(err.message);
break;
default:
runtime.error(`Invalid custom provider config: ${err.message}`);
break;
}
runtime.exit(1);
return null;
}
const reason = err instanceof Error ? err.message : String(err);
runtime.error(`Invalid custom provider config: ${reason}`);
runtime.exit(1);
return null;
}
if (authChoice === "oauth" || authChoice === "chutes" || authChoice === "openai-codex" || authChoice === "qwen-portal" || authChoice === "minimax-portal") {
runtime.error("OAuth requires interactive mode.");
runtime.exit(1);
return null;
}
return nextConfig;
}
//#endregion
//#region src/commands/onboard-non-interactive/local/daemon-install.ts
async function installGatewayDaemonNonInteractive(params) {
const { opts, runtime, port, gatewayToken } = params;
if (!opts.installDaemon) return;
const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
const systemdAvailable = process.platform === "linux" ? await isSystemdUserServiceAvailable() : true;
if (process.platform === "linux" && !systemdAvailable) {
runtime.log("Systemd user services are unavailable; skipping service install.");
return;
}
if (!isGatewayDaemonRuntime(daemonRuntimeRaw)) {
runtime.error("Invalid --daemon-runtime (use node or bun)");
runtime.exit(1);
return;
}
const service = resolveGatewayService();
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
env: process.env,
port,
token: gatewayToken,
runtime: daemonRuntimeRaw,
warn: (message) => runtime.log(message),
config: params.nextConfig
});
try {
await service.install({
env: process.env,
stdout: process.stdout,
programArguments,
workingDirectory,
environment
});
} catch (err) {
runtime.error(`Gateway service install failed: ${String(err)}`);
runtime.log(gatewayInstallErrorHint());
return;
}
await ensureSystemdUserLingerNonInteractive({ runtime });
}
//#endregion
//#region src/commands/onboard-non-interactive/local/gateway-config.ts
function applyNonInteractiveGatewayConfig(params) {
const { opts, runtime } = params;
const hasGatewayPort = opts.gatewayPort !== void 0;
if (hasGatewayPort && (!Number.isFinite(opts.gatewayPort) || (opts.gatewayPort ?? 0) <= 0)) {
runtime.error("Invalid --gateway-port");
runtime.exit(1);
return null;
}
const port = hasGatewayPort ? opts.gatewayPort : params.defaultPort;
let bind = opts.gatewayBind ?? "loopback";
const authModeRaw = opts.gatewayAuth ?? "token";
if (authModeRaw !== "token" && authModeRaw !== "password") {
runtime.error("Invalid --gateway-auth (use token|password).");
runtime.exit(1);
return null;
}
let authMode = authModeRaw;
const tailscaleMode = opts.tailscale ?? "off";
const tailscaleResetOnExit = Boolean(opts.tailscaleResetOnExit);
if (tailscaleMode !== "off" && bind !== "loopback") bind = "loopback";
if (tailscaleMode === "funnel" && authMode !== "password") authMode = "password";
let nextConfig = params.nextConfig;
let gatewayToken = opts.gatewayToken?.trim() || void 0;
if (authMode === "token") {
if (!gatewayToken) gatewayToken = randomToken();
nextConfig = {
...nextConfig,
gateway: {
...nextConfig.gateway,
auth: {
...nextConfig.gateway?.auth,
mode: "token",
token: gatewayToken
}
}
};
}
if (authMode === "password") {
const password = opts.gatewayPassword?.trim();
if (!password) {
runtime.error("Missing --gateway-password for password auth.");
runtime.exit(1);
return null;
}
nextConfig = {
...nextConfig,
gateway: {
...nextConfig.gateway,
auth: {
...nextConfig.gateway?.auth,
mode: "password",
password
}
}
};
}
nextConfig = {
...nextConfig,
gateway: {
...nextConfig.gateway,
port,
bind,
tailscale: {
...nextConfig.gateway?.tailscale,
mode: tailscaleMode,
resetOnExit: tailscaleResetOnExit
}
}
};
return {
nextConfig,
port,
bind,
authMode,
tailscaleMode,
tailscaleResetOnExit,
gatewayToken
};
}
//#endregion
//#region src/commands/onboard-non-interactive/local/output.ts
function logNonInteractiveOnboardingJson(params) {
if (!params.opts.json) return;
params.runtime.log(JSON.stringify({
mode: params.mode,
workspace: params.workspaceDir,
authChoice: params.authChoice,
gateway: params.gateway,
installDaemon: Boolean(params.installDaemon),
daemonRuntime: params.daemonRuntime,
skipSkills: Boolean(params.skipSkills),
skipHealth: Boolean(params.skipHealth)
}, null, 2));
}
//#endregion
//#region src/commands/onboard-non-interactive/local/skills-config.ts
function applyNonInteractiveSkillsConfig(params) {
const { nextConfig, opts, runtime } = params;
if (opts.skipSkills) return nextConfig;
const nodeManager = opts.nodeManager ?? "npm";
if (![
"npm",
"pnpm",
"bun"
].includes(nodeManager)) {
runtime.error("Invalid --node-manager (use npm, pnpm, or bun)");
runtime.exit(1);
return nextConfig;
}
return {
...nextConfig,
skills: {
...nextConfig.skills,
install: {
...nextConfig.skills?.install,
nodeManager
}
}
};
}
//#endregion
//#region src/commands/onboard-non-interactive/local/workspace.ts
function resolveNonInteractiveWorkspaceDir(params) {
return resolveUserPath((params.opts.workspace ?? params.baseConfig.agents?.defaults?.workspace ?? params.defaultWorkspaceDir).trim());
}
//#endregion
//#region src/commands/onboard-non-interactive/local.ts
async function runNonInteractiveOnboardingLocal(params) {
const { opts, runtime, baseConfig } = params;
const mode = "local";
const workspaceDir = resolveNonInteractiveWorkspaceDir({
opts,
baseConfig,
defaultWorkspaceDir: DEFAULT_WORKSPACE
});
let nextConfig = applyOnboardingLocalWorkspaceConfig(baseConfig, workspaceDir);
const inferredAuthChoice = inferAuthChoiceFromFlags(opts);
if (!opts.authChoice && inferredAuthChoice.matches.length > 1) {
runtime.error([
"Multiple API key flags were provided for non-interactive onboarding.",
"Use a single provider flag or pass --auth-choice explicitly.",
`Flags: ${inferredAuthChoice.matches.map((match) => match.label).join(", ")}`
].join("\n"));
runtime.exit(1);
return;
}
const authChoice = opts.authChoice ?? inferredAuthChoice.choice ?? "skip";
const nextConfigAfterAuth = await applyNonInteractiveAuthChoice({
nextConfig,
authChoice,
opts,
runtime,
baseConfig
});
if (!nextConfigAfterAuth) return;
nextConfig = nextConfigAfterAuth;
const gatewayBasePort = resolveGatewayPort(baseConfig);
const gatewayResult = applyNonInteractiveGatewayConfig({
nextConfig,
opts,
runtime,
defaultPort: gatewayBasePort
});
if (!gatewayResult) return;
nextConfig = gatewayResult.nextConfig;
nextConfig = applyNonInteractiveSkillsConfig({
nextConfig,
opts,
runtime
});
nextConfig = applyWizardMetadata(nextConfig, {
command: "onboard",
mode
});
await writeConfigFile(nextConfig);
logConfigUpdated(runtime);
await ensureWorkspaceAndSessions(workspaceDir, runtime, { skipBootstrap: Boolean(nextConfig.agents?.defaults?.skipBootstrap) });
await installGatewayDaemonNonInteractive({
nextConfig,
opts,
runtime,
port: gatewayResult.port,
gatewayToken: gatewayResult.gatewayToken
});
const daemonRuntimeRaw = opts.daemonRuntime ?? DEFAULT_GATEWAY_DAEMON_RUNTIME;
if (!opts.skipHealth) {
await waitForGatewayReachable({
url: resolveControlUiLinks({
bind: gatewayResult.bind,
port: gatewayResult.port,
customBindHost: nextConfig.gateway?.customBindHost,
basePath: void 0
}).wsUrl,
token: gatewayResult.gatewayToken,
deadlineMs: 15e3
});
await healthCommand({
json: false,
timeoutMs: 1e4
}, runtime);
}
logNonInteractiveOnboardingJson({
opts,
runtime,
mode,
workspaceDir,
authChoice,
gateway: {
port: gatewayResult.port,
bind: gatewayResult.bind,
authMode: gatewayResult.authMode,
tailscaleMode: gatewayResult.tailscaleMode
},
installDaemon: Boolean(opts.installDaemon),
daemonRuntime: opts.installDaemon ? daemonRuntimeRaw : void 0,
skipSkills: Boolean(opts.skipSkills),
skipHealth: Boolean(opts.skipHealth)
});
if (!opts.json) runtime.log(`Tip: run \`${formatCliCommand("openclaw configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.openclaw.ai/tools/web`);
}
//#endregion
//#region src/commands/onboard-non-interactive/remote.ts
async function runNonInteractiveOnboardingRemote(params) {
const { opts, runtime, baseConfig } = params;
const mode = "remote";
const remoteUrl = opts.remoteUrl?.trim();
if (!remoteUrl) {
runtime.error("Missing --remote-url for remote mode.");
runtime.exit(1);
return;
}
let nextConfig = {
...baseConfig,
gateway: {
...baseConfig.gateway,
mode: "remote",
remote: {
url: remoteUrl,
token: opts.remoteToken?.trim() || void 0
}
}
};
nextConfig = applyWizardMetadata(nextConfig, {
command: "onboard",
mode
});
await writeConfigFile(nextConfig);
logConfigUpdated(runtime);
const payload = {
mode,
remoteUrl,
auth: opts.remoteToken ? "token" : "none"
};
if (opts.json) runtime.log(JSON.stringify(payload, null, 2));
else {
runtime.log(`Remote gateway: ${remoteUrl}`);
runtime.log(`Auth: ${payload.auth}`);
runtime.log(`Tip: run \`${formatCliCommand("openclaw configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.openclaw.ai/tools/web`);
}
}
//#endregion
//#region src/commands/onboard-non-interactive.ts
async function runNonInteractiveOnboarding(opts, runtime = defaultRuntime) {
const snapshot = await readConfigFileSnapshot();
if (snapshot.exists && !snapshot.valid) {
runtime.error(`Config invalid. Run \`${formatCliCommand("openclaw doctor")}\` to repair it, then re-run onboarding.`);
runtime.exit(1);
return;
}
const baseConfig = snapshot.valid ? snapshot.config : {};
const mode = opts.mode ?? "local";
if (mode !== "local" && mode !== "remote") {
runtime.error(`Invalid --mode "${String(mode)}" (use local|remote).`);
runtime.exit(1);
return;
}
if (mode === "remote") {
await runNonInteractiveOnboardingRemote({
opts,
runtime,
baseConfig
});
return;
}
await runNonInteractiveOnboardingLocal({
opts,
runtime,
baseConfig
});
}
//#endregion
//#region src/commands/onboard.ts
async function onboardCommand(opts, runtime = defaultRuntime) {
assertSupportedRuntime(runtime);
const originalAuthChoice = opts.authChoice;
const normalizedAuthChoice = normalizeLegacyOnboardAuthChoice(originalAuthChoice);
if (opts.nonInteractive && isDeprecatedAuthChoice(originalAuthChoice)) {
runtime.error([`Auth choice "${String(originalAuthChoice)}" is deprecated.`, "Use \"--auth-choice token\" (Anthropic setup-token) or \"--auth-choice openai-codex\"."].join("\n"));
runtime.exit(1);
return;
}
if (originalAuthChoice === "claude-cli") runtime.log("Auth choice \"claude-cli\" is deprecated; using setup-token flow instead.");
if (originalAuthChoice === "codex-cli") runtime.log("Auth choice \"codex-cli\" is deprecated; using OpenAI Codex OAuth instead.");
const flow = opts.flow === "manual" ? "advanced" : opts.flow;
const normalizedOpts = normalizedAuthChoice === opts.authChoice && flow === opts.flow ? opts : {
...opts,
authChoice: normalizedAuthChoice,
flow
};
if (normalizedOpts.nonInteractive && normalizedOpts.acceptRisk !== true) {
runtime.error([
"Non-interactive onboarding requires explicit risk acknowledgement.",
"Read: https://docs.openclaw.ai/security",
`Re-run with: ${formatCliCommand("openclaw onboard --non-interactive --accept-risk ...")}`
].join("\n"));
runtime.exit(1);
return;
}
if (normalizedOpts.reset) {
const snapshot = await readConfigFileSnapshot();
const baseConfig = snapshot.valid ? snapshot.config : {};
await handleReset("full", resolveUserPath(normalizedOpts.workspace ?? baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE), runtime);
}
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"));
if (normalizedOpts.nonInteractive) {
await runNonInteractiveOnboarding(normalizedOpts, runtime);
return;
}
await runInteractiveOnboarding(normalizedOpts, runtime);
}
//#endregion
export { ONBOARD_PROVIDER_AUTH_FLAGS as n, onboardCommand as t };