CLI: add OpenRouter auth choice

This commit is contained in:
Matthew
2026-01-10 20:21:15 -05:00
committed by Peter Steinberger
parent cffec07329
commit b6982236a6
7 changed files with 136 additions and 1 deletions

View File

@@ -156,6 +156,29 @@ describe("cli program", () => {
);
});
it("passes openrouter api key to onboard", async () => {
const program = buildProgram();
await program.parseAsync(
[
"onboard",
"--non-interactive",
"--auth-choice",
"openrouter-api-key",
"--openrouter-api-key",
"sk-openrouter-test",
],
{ from: "user" },
);
expect(onboardCommand).toHaveBeenCalledWith(
expect.objectContaining({
nonInteractive: true,
authChoice: "openrouter-api-key",
openrouterApiKey: "sk-openrouter-test",
}),
runtime,
);
});
it("passes zai api key to onboard", async () => {
const program = buildProgram();
await program.parseAsync(

View File

@@ -249,7 +249,7 @@ export function buildProgram() {
.option("--mode <mode>", "Wizard mode: local|remote")
.option(
"--auth-choice <choice>",
"Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip",
"Auth: setup-token|claude-cli|token|openai-codex|openai-api-key|openrouter-api-key|codex-cli|antigravity|gemini-api-key|zai-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip",
)
.option(
"--token-provider <id>",
@@ -269,6 +269,7 @@ export function buildProgram() {
)
.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("--gemini-api-key <key>", "Gemini API key")
.option("--zai-api-key <key>", "Z.AI API key")
.option("--minimax-api-key <key>", "MiniMax API key")
@@ -315,6 +316,7 @@ export function buildProgram() {
| "token"
| "openai-codex"
| "openai-api-key"
| "openrouter-api-key"
| "codex-cli"
| "antigravity"
| "gemini-api-key"
@@ -332,6 +334,7 @@ export function buildProgram() {
tokenExpiresIn: opts.tokenExpiresIn as string | undefined,
anthropicApiKey: opts.anthropicApiKey as string | undefined,
openaiApiKey: opts.openaiApiKey as string | undefined,
openrouterApiKey: opts.openrouterApiKey as string | undefined,
geminiApiKey: opts.geminiApiKey as string | undefined,
zaiApiKey: opts.zaiApiKey as string | undefined,
minimaxApiKey: opts.minimaxApiKey as string | undefined,

View File

@@ -92,6 +92,7 @@ export function buildAuthChoiceOptions(params: {
label: "OpenAI Codex (ChatGPT OAuth)",
});
options.push({ value: "openai-api-key", label: "OpenAI API key" });
options.push({ value: "openrouter-api-key", label: "OpenRouter API key" });
options.push({
value: "antigravity",
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",

View File

@@ -42,13 +42,17 @@ import {
applyMinimaxHostedConfig,
applyMinimaxHostedProviderConfig,
applyMinimaxProviderConfig,
applyOpenrouterConfig,
applyOpenrouterProviderConfig,
applyOpencodeZenConfig,
applyOpencodeZenProviderConfig,
applyZaiConfig,
MINIMAX_HOSTED_MODEL_REF,
OPENROUTER_DEFAULT_MODEL_REF,
setAnthropicApiKey,
setGeminiApiKey,
setMinimaxApiKey,
setOpenrouterApiKey,
setOpencodeZenApiKey,
setZaiApiKey,
writeOAuthCredentials,
@@ -366,6 +370,28 @@ export async function applyAuthChoice(params: {
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
"OpenAI API key",
);
} else if (params.authChoice === "openrouter-api-key") {
const key = await params.prompter.text({
message: "Enter OpenRouter API key",
validate: (value) => (value?.trim() ? undefined : "Required"),
});
await setOpenrouterApiKey(String(key).trim(), params.agentDir);
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "openrouter:default",
provider: "openrouter",
mode: "api_key",
});
if (params.setDefaultModel) {
nextConfig = applyOpenrouterConfig(nextConfig);
await params.prompter.note(
`Default model set to ${OPENROUTER_DEFAULT_MODEL_REF}`,
"Model configured",
);
} else {
nextConfig = applyOpenrouterProviderConfig(nextConfig);
agentModelOverride = OPENROUTER_DEFAULT_MODEL_REF;
await noteAgentModel(OPENROUTER_DEFAULT_MODEL_REF);
}
} else if (params.authChoice === "openai-codex") {
const isRemote = isRemoteEnvironment();
await params.prompter.note(
@@ -745,6 +771,8 @@ export function resolvePreferredProviderForAuthChoice(
return "openai-codex";
case "openai-api-key":
return "openai";
case "openrouter-api-key":
return "openrouter";
case "gemini-api-key":
return "google";
case "antigravity":

View File

@@ -131,6 +131,7 @@ export async function setMinimaxApiKey(key: string, agentDir?: string) {
}
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
export const OPENROUTER_DEFAULT_MODEL_REF = "openrouter/auto";
export async function setZaiApiKey(key: string, agentDir?: string) {
// Write to the multi-agent path so gateway finds credentials on startup
@@ -145,6 +146,18 @@ export async function setZaiApiKey(key: string, agentDir?: string) {
});
}
export async function setOpenrouterApiKey(key: string, agentDir?: string) {
upsertAuthProfile({
profileId: "openrouter:default",
credential: {
type: "api_key",
provider: "openrouter",
key,
},
agentDir: agentDir ?? resolveDefaultAgentDir(),
});
}
export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
const models = { ...cfg.agents?.defaults?.models };
models[ZAI_DEFAULT_MODEL_REF] = {
@@ -175,6 +188,50 @@ export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
};
}
export function applyOpenrouterProviderConfig(
cfg: ClawdbotConfig,
): ClawdbotConfig {
const models = { ...cfg.agents?.defaults?.models };
models[OPENROUTER_DEFAULT_MODEL_REF] = {
...models[OPENROUTER_DEFAULT_MODEL_REF],
alias: models[OPENROUTER_DEFAULT_MODEL_REF]?.alias ?? "OpenRouter",
};
return {
...cfg,
agents: {
...cfg.agents,
defaults: {
...cfg.agents?.defaults,
models,
},
},
};
}
export function applyOpenrouterConfig(cfg: ClawdbotConfig): ClawdbotConfig {
const next = applyOpenrouterProviderConfig(cfg);
const existingModel = next.agents?.defaults?.model;
return {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
model: {
...(existingModel &&
"fallbacks" in (existingModel as Record<string, unknown>)
? {
fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks,
}
: undefined),
primary: OPENROUTER_DEFAULT_MODEL_REF,
},
},
},
};
}
export function applyAuthProfileConfig(
cfg: ClawdbotConfig,
params: {

View File

@@ -35,11 +35,13 @@ import {
applyMinimaxApiConfig,
applyMinimaxConfig,
applyMinimaxHostedConfig,
applyOpenrouterConfig,
applyOpencodeZenConfig,
applyZaiConfig,
setAnthropicApiKey,
setGeminiApiKey,
setMinimaxApiKey,
setOpenrouterApiKey,
setOpencodeZenApiKey,
setZaiApiKey,
} from "./onboard-auth.js";
@@ -264,6 +266,25 @@ export async function runNonInteractiveOnboarding(
});
process.env.OPENAI_API_KEY = key;
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
} else 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;
if (resolved.source !== "profile") {
await setOpenrouterApiKey(resolved.key);
}
nextConfig = applyAuthProfileConfig(nextConfig, {
profileId: "openrouter:default",
provider: "openrouter",
mode: "api_key",
});
nextConfig = applyOpenrouterConfig(nextConfig);
} else if (authChoice === "minimax-cloud") {
const resolved = await resolveNonInteractiveApiKey({
provider: "minimax",

View File

@@ -10,6 +10,7 @@ export type AuthChoice =
| "token"
| "openai-codex"
| "openai-api-key"
| "openrouter-api-key"
| "codex-cli"
| "antigravity"
| "apiKey"
@@ -43,6 +44,7 @@ export type OnboardOptions = {
tokenExpiresIn?: string;
anthropicApiKey?: string;
openaiApiKey?: string;
openrouterApiKey?: string;
geminiApiKey?: string;
zaiApiKey?: string;
minimaxApiKey?: string;