refactor(onboard): unify auth-choice aliases and provider flags

This commit is contained in:
Peter Steinberger
2026-02-14 05:58:14 +01:00
parent 2f4cef2021
commit d8beddc8b7
9 changed files with 351 additions and 143 deletions

View File

@@ -8,6 +8,7 @@ import type {
TailscaleMode,
} from "../../commands/onboard-types.js";
import { formatAuthChoiceChoicesForCli } from "../../commands/auth-choice-options.js";
import { ONBOARD_PROVIDER_AUTH_FLAGS } from "../../commands/onboard-provider-auth-flags.js";
import { onboardCommand } from "../../commands/onboard.js";
import { defaultRuntime } from "../../runtime.js";
import { formatDocsLink } from "../../terminal/links.js";
@@ -44,7 +45,7 @@ const AUTH_CHOICE_HELP = formatAuthChoiceChoicesForCli({
});
export function registerOnboardCommand(program: Command) {
program
const command = program
.command("onboard")
.description("Interactive wizard to set up the gateway, workspace, and skills")
.addHelpText(
@@ -73,27 +74,14 @@ export function registerOnboardCommand(program: Command) {
"Auth profile id (non-interactive; default: <provider>:manual)",
)
.option("--token-expires-in <duration>", "Optional token expiry duration (e.g. 365d, 12h)")
.option("--anthropic-api-key <key>", "Anthropic API key")
.option("--openai-api-key <key>", "OpenAI API key")
.option("--openrouter-api-key <key>", "OpenRouter API key")
.option("--ai-gateway-api-key <key>", "Vercel AI Gateway API key")
.option("--cloudflare-ai-gateway-account-id <id>", "Cloudflare Account ID")
.option("--cloudflare-ai-gateway-gateway-id <id>", "Cloudflare AI Gateway ID")
.option("--cloudflare-ai-gateway-api-key <key>", "Cloudflare AI Gateway API key")
.option("--moonshot-api-key <key>", "Moonshot API key")
.option("--kimi-code-api-key <key>", "Kimi Coding API key")
.option("--gemini-api-key <key>", "Gemini API key")
.option("--zai-api-key <key>", "Z.AI API key")
.option("--xiaomi-api-key <key>", "Xiaomi API key")
.option("--minimax-api-key <key>", "MiniMax API key")
.option("--synthetic-api-key <key>", "Synthetic API key")
.option("--venice-api-key <key>", "Venice API key")
.option("--together-api-key <key>", "Together AI API key")
.option("--huggingface-api-key <key>", "Hugging Face API key (HF token)")
.option("--opencode-zen-api-key <key>", "OpenCode Zen API key")
.option("--xai-api-key <key>", "xAI API key")
.option("--litellm-api-key <key>", "LiteLLM API key")
.option("--qianfan-api-key <key>", "QIANFAN API key")
.option("--cloudflare-ai-gateway-gateway-id <id>", "Cloudflare AI Gateway ID");
for (const providerFlag of ONBOARD_PROVIDER_AUTH_FLAGS) {
command.option(providerFlag.cliOption, providerFlag.description);
}
command
.option("--custom-base-url <url>", "Custom provider base URL")
.option("--custom-api-key <key>", "Custom provider API key (optional)")
.option("--custom-model-id <id>", "Custom provider model ID")
@@ -120,76 +108,77 @@ export function registerOnboardCommand(program: Command) {
.option("--skip-health", "Skip health check")
.option("--skip-ui", "Skip Control UI/TUI prompts")
.option("--node-manager <name>", "Node manager for skills: npm|pnpm|bun")
.option("--json", "Output JSON summary", false)
.action(async (opts, command) => {
await runCommandWithRuntime(defaultRuntime, async () => {
const installDaemon = resolveInstallDaemonFlag(command, {
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,
);
.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,
);
});
});
}

View File

@@ -0,0 +1,28 @@
import type { AuthChoice } from "./onboard-types.js";
export const AUTH_CHOICE_LEGACY_ALIASES_FOR_CLI: ReadonlyArray<AuthChoice> = [
"setup-token",
"oauth",
"claude-cli",
"codex-cli",
"minimax-cloud",
"minimax",
];
export function normalizeLegacyOnboardAuthChoice(
authChoice: AuthChoice | undefined,
): AuthChoice | undefined {
if (authChoice === "oauth" || authChoice === "claude-cli") {
return "setup-token";
}
if (authChoice === "codex-cli") {
return "openai-codex";
}
return authChoice;
}
export function isDeprecatedAuthChoice(
authChoice: AuthChoice | undefined,
): authChoice is "claude-cli" | "codex-cli" {
return authChoice === "claude-cli" || authChoice === "codex-cli";
}

View File

@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import { buildAuthChoiceOptions, formatAuthChoiceChoicesForCli } from "./auth-choice-options.js";
import {
buildAuthChoiceGroups,
buildAuthChoiceOptions,
formatAuthChoiceChoicesForCli,
} from "./auth-choice-options.js";
describe("buildAuthChoiceOptions", () => {
it("includes GitHub Copilot", () => {
@@ -172,4 +176,16 @@ describe("buildAuthChoiceOptions", () => {
expect(cliChoices).toContain("claude-cli");
expect(cliChoices).toContain("codex-cli");
});
it("shows Chutes in grouped provider selection", () => {
const store: AuthProfileStore = { version: 1, profiles: {} };
const { groups } = buildAuthChoiceGroups({
store,
includeSkip: false,
});
const chutesGroup = groups.find((group) => group.value === "chutes");
expect(chutesGroup).toBeDefined();
expect(chutesGroup?.options.some((opt) => opt.value === "chutes")).toBe(true);
});
});

View File

@@ -1,5 +1,6 @@
import type { AuthProfileStore } from "../agents/auth-profiles.js";
import type { AuthChoice, AuthChoiceGroupId } from "./onboard-types.js";
import { AUTH_CHOICE_LEGACY_ALIASES_FOR_CLI } from "./auth-choice-legacy.js";
export type { AuthChoiceGroupId };
@@ -33,6 +34,12 @@ const AUTH_CHOICE_GROUP_DEFS: {
hint: "setup-token + API key",
choices: ["token", "apiKey"],
},
{
value: "chutes",
label: "Chutes",
hint: "OAuth",
choices: ["chutes"],
},
{
value: "vllm",
label: "vLLM",
@@ -287,15 +294,6 @@ const BASE_AUTH_CHOICE_OPTIONS: ReadonlyArray<AuthChoiceOption> = [
{ value: "custom-api-key", label: "Custom Provider" },
];
const LEGACY_AUTH_CHOICE_ALIASES: ReadonlyArray<AuthChoice> = [
"setup-token",
"oauth",
"claude-cli",
"codex-cli",
"minimax-cloud",
"minimax",
];
export function formatAuthChoiceChoicesForCli(params?: {
includeSkip?: boolean;
includeLegacyAliases?: boolean;
@@ -308,7 +306,7 @@ export function formatAuthChoiceChoicesForCli(params?: {
values.push("skip");
}
if (includeLegacyAliases) {
values.push(...LEGACY_AUTH_CHOICE_ALIASES);
values.push(...AUTH_CHOICE_LEGACY_ALIASES_FOR_CLI);
}
return values.join("|");

View File

@@ -480,6 +480,36 @@ describe("onboard (non-interactive): provider auth", () => {
});
}, 60_000);
it("infers QIANFAN auth choice from --qianfan-api-key and sets default model", async () => {
await withOnboardEnv("openclaw-onboard-qianfan-infer-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
qianfanApiKey: "qianfan-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
agents?: { defaults?: { model?: { primary?: string } } };
}>(configPath);
expect(cfg.auth?.profiles?.["qianfan:default"]?.provider).toBe("qianfan");
expect(cfg.auth?.profiles?.["qianfan:default"]?.mode).toBe("api_key");
expect(cfg.agents?.defaults?.model?.primary).toBe("qianfan/deepseek-v3.2");
await expectApiKeyProfile({
profileId: "qianfan:default",
provider: "qianfan",
key: "qianfan-test-key",
});
});
}, 60_000);
it("configures a custom provider from non-interactive flags", async () => {
await withOnboardEnv("openclaw-onboard-custom-provider-", async ({ configPath, runtime }) => {
await runNonInteractive(

View File

@@ -1,7 +1,8 @@
import type { AuthChoice, OnboardOptions } from "../../onboard-types.js";
import { ONBOARD_PROVIDER_AUTH_FLAGS } from "../../onboard-provider-auth-flags.js";
type AuthChoiceFlag = {
flag: keyof AuthChoiceFlagOptions;
optionKey: keyof AuthChoiceFlagOptions;
authChoice: AuthChoice;
label: string;
};
@@ -26,36 +27,12 @@ type AuthChoiceFlagOptions = Pick<
| "opencodeZenApiKey"
| "xaiApiKey"
| "litellmApiKey"
| "qianfanApiKey"
| "customBaseUrl"
| "customModelId"
| "customApiKey"
>;
const AUTH_CHOICE_FLAG_MAP = [
{ flag: "anthropicApiKey", authChoice: "apiKey", label: "--anthropic-api-key" },
{ flag: "geminiApiKey", authChoice: "gemini-api-key", label: "--gemini-api-key" },
{ flag: "openaiApiKey", authChoice: "openai-api-key", label: "--openai-api-key" },
{ flag: "openrouterApiKey", authChoice: "openrouter-api-key", label: "--openrouter-api-key" },
{ flag: "aiGatewayApiKey", authChoice: "ai-gateway-api-key", label: "--ai-gateway-api-key" },
{
flag: "cloudflareAiGatewayApiKey",
authChoice: "cloudflare-ai-gateway-api-key",
label: "--cloudflare-ai-gateway-api-key",
},
{ flag: "moonshotApiKey", authChoice: "moonshot-api-key", label: "--moonshot-api-key" },
{ flag: "kimiCodeApiKey", authChoice: "kimi-code-api-key", label: "--kimi-code-api-key" },
{ flag: "syntheticApiKey", authChoice: "synthetic-api-key", label: "--synthetic-api-key" },
{ flag: "veniceApiKey", authChoice: "venice-api-key", label: "--venice-api-key" },
{ flag: "togetherApiKey", authChoice: "together-api-key", label: "--together-api-key" },
{ flag: "zaiApiKey", authChoice: "zai-api-key", label: "--zai-api-key" },
{ flag: "xiaomiApiKey", authChoice: "xiaomi-api-key", label: "--xiaomi-api-key" },
{ flag: "xaiApiKey", authChoice: "xai-api-key", label: "--xai-api-key" },
{ flag: "minimaxApiKey", authChoice: "minimax-api", label: "--minimax-api-key" },
{ flag: "opencodeZenApiKey", authChoice: "opencode-zen", label: "--opencode-zen-api-key" },
{ flag: "huggingfaceApiKey", authChoice: "huggingface-api-key", label: "--huggingface-api-key" },
{ flag: "litellmApiKey", authChoice: "litellm-api-key", label: "--litellm-api-key" },
] satisfies ReadonlyArray<AuthChoiceFlag>;
export type AuthChoiceInference = {
choice?: AuthChoice;
matches: AuthChoiceFlag[];
@@ -67,9 +44,13 @@ function hasStringValue(value: unknown): boolean {
// Infer auth choice from explicit provider API key flags.
export function inferAuthChoiceFromFlags(opts: OnboardOptions): AuthChoiceInference {
const matches: AuthChoiceFlag[] = AUTH_CHOICE_FLAG_MAP.filter(({ flag }) =>
hasStringValue(opts[flag]),
);
const matches: AuthChoiceFlag[] = ONBOARD_PROVIDER_AUTH_FLAGS.filter(({ optionKey }) =>
hasStringValue(opts[optionKey]),
).map((flag) => ({
optionKey: flag.optionKey,
authChoice: flag.authChoice,
label: flag.cliFlag,
}));
if (
hasStringValue(opts.customBaseUrl) ||
@@ -77,7 +58,7 @@ export function inferAuthChoiceFromFlags(opts: OnboardOptions): AuthChoiceInfere
hasStringValue(opts.customApiKey)
) {
matches.push({
flag: "customBaseUrl",
optionKey: "customBaseUrl",
authChoice: "custom-api-key",
label: "--custom-base-url/--custom-model-id/--custom-api-key",
});

View File

@@ -0,0 +1,169 @@
import type { AuthChoice, OnboardOptions } from "./onboard-types.js";
type OnboardProviderAuthOptionKey = keyof Pick<
OnboardOptions,
| "anthropicApiKey"
| "openaiApiKey"
| "openrouterApiKey"
| "aiGatewayApiKey"
| "cloudflareAiGatewayApiKey"
| "moonshotApiKey"
| "kimiCodeApiKey"
| "geminiApiKey"
| "zaiApiKey"
| "xiaomiApiKey"
| "minimaxApiKey"
| "syntheticApiKey"
| "veniceApiKey"
| "togetherApiKey"
| "huggingfaceApiKey"
| "opencodeZenApiKey"
| "xaiApiKey"
| "litellmApiKey"
| "qianfanApiKey"
>;
export type OnboardProviderAuthFlag = {
optionKey: OnboardProviderAuthOptionKey;
authChoice: AuthChoice;
cliFlag: `--${string}`;
cliOption: `--${string} <key>`;
description: string;
};
// Shared source for provider API-key flags used by CLI registration + non-interactive inference.
export const ONBOARD_PROVIDER_AUTH_FLAGS: ReadonlyArray<OnboardProviderAuthFlag> = [
{
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",
},
];

View File

@@ -50,6 +50,7 @@ export type AuthChoice =
export type AuthChoiceGroupId =
| "openai"
| "anthropic"
| "chutes"
| "vllm"
| "google"
| "copilot"

View File

@@ -5,33 +5,29 @@ import { readConfigFileSnapshot } from "../config/config.js";
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js";
import { isDeprecatedAuthChoice, normalizeLegacyOnboardAuthChoice } from "./auth-choice-legacy.js";
import { DEFAULT_WORKSPACE, handleReset } from "./onboard-helpers.js";
import { runInteractiveOnboarding } from "./onboard-interactive.js";
import { runNonInteractiveOnboarding } from "./onboard-non-interactive.js";
export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv = defaultRuntime) {
assertSupportedRuntime(runtime);
const authChoice = opts.authChoice === "oauth" ? ("setup-token" as const) : opts.authChoice;
const normalizedAuthChoice =
authChoice === "claude-cli"
? ("setup-token" as const)
: authChoice === "codex-cli"
? ("openai-codex" as const)
: authChoice;
if (opts.nonInteractive && (authChoice === "claude-cli" || authChoice === "codex-cli")) {
const originalAuthChoice = opts.authChoice;
const normalizedAuthChoice = normalizeLegacyOnboardAuthChoice(originalAuthChoice);
if (opts.nonInteractive && isDeprecatedAuthChoice(originalAuthChoice)) {
runtime.error(
[
`Auth choice "${authChoice}" is deprecated.`,
`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 (authChoice === "claude-cli") {
if (originalAuthChoice === "claude-cli") {
runtime.log('Auth choice "claude-cli" is deprecated; using setup-token flow instead.');
}
if (authChoice === "codex-cli") {
if (originalAuthChoice === "codex-cli") {
runtime.log('Auth choice "codex-cli" is deprecated; using OpenAI Codex OAuth instead.');
}
const flow = opts.flow === "manual" ? ("advanced" as const) : opts.flow;