CLI: add OpenRouter auth choice
This commit is contained in:
committed by
Peter Steinberger
parent
cffec07329
commit
b6982236a6
@@ -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 () => {
|
it("passes zai api key to onboard", async () => {
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
await program.parseAsync(
|
await program.parseAsync(
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export function buildProgram() {
|
|||||||
.option("--mode <mode>", "Wizard mode: local|remote")
|
.option("--mode <mode>", "Wizard mode: local|remote")
|
||||||
.option(
|
.option(
|
||||||
"--auth-choice <choice>",
|
"--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(
|
.option(
|
||||||
"--token-provider <id>",
|
"--token-provider <id>",
|
||||||
@@ -269,6 +269,7 @@ export function buildProgram() {
|
|||||||
)
|
)
|
||||||
.option("--anthropic-api-key <key>", "Anthropic API key")
|
.option("--anthropic-api-key <key>", "Anthropic API key")
|
||||||
.option("--openai-api-key <key>", "OpenAI 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("--gemini-api-key <key>", "Gemini API key")
|
||||||
.option("--zai-api-key <key>", "Z.AI API key")
|
.option("--zai-api-key <key>", "Z.AI API key")
|
||||||
.option("--minimax-api-key <key>", "MiniMax API key")
|
.option("--minimax-api-key <key>", "MiniMax API key")
|
||||||
@@ -315,6 +316,7 @@ export function buildProgram() {
|
|||||||
| "token"
|
| "token"
|
||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "openai-api-key"
|
| "openai-api-key"
|
||||||
|
| "openrouter-api-key"
|
||||||
| "codex-cli"
|
| "codex-cli"
|
||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "gemini-api-key"
|
| "gemini-api-key"
|
||||||
@@ -332,6 +334,7 @@ export function buildProgram() {
|
|||||||
tokenExpiresIn: opts.tokenExpiresIn as string | undefined,
|
tokenExpiresIn: opts.tokenExpiresIn as string | undefined,
|
||||||
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
anthropicApiKey: opts.anthropicApiKey as string | undefined,
|
||||||
openaiApiKey: opts.openaiApiKey as string | undefined,
|
openaiApiKey: opts.openaiApiKey as string | undefined,
|
||||||
|
openrouterApiKey: opts.openrouterApiKey as string | undefined,
|
||||||
geminiApiKey: opts.geminiApiKey as string | undefined,
|
geminiApiKey: opts.geminiApiKey as string | undefined,
|
||||||
zaiApiKey: opts.zaiApiKey as string | undefined,
|
zaiApiKey: opts.zaiApiKey as string | undefined,
|
||||||
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
minimaxApiKey: opts.minimaxApiKey as string | undefined,
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export function buildAuthChoiceOptions(params: {
|
|||||||
label: "OpenAI Codex (ChatGPT OAuth)",
|
label: "OpenAI Codex (ChatGPT OAuth)",
|
||||||
});
|
});
|
||||||
options.push({ value: "openai-api-key", label: "OpenAI API key" });
|
options.push({ value: "openai-api-key", label: "OpenAI API key" });
|
||||||
|
options.push({ value: "openrouter-api-key", label: "OpenRouter API key" });
|
||||||
options.push({
|
options.push({
|
||||||
value: "antigravity",
|
value: "antigravity",
|
||||||
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
||||||
|
|||||||
@@ -42,13 +42,17 @@ import {
|
|||||||
applyMinimaxHostedConfig,
|
applyMinimaxHostedConfig,
|
||||||
applyMinimaxHostedProviderConfig,
|
applyMinimaxHostedProviderConfig,
|
||||||
applyMinimaxProviderConfig,
|
applyMinimaxProviderConfig,
|
||||||
|
applyOpenrouterConfig,
|
||||||
|
applyOpenrouterProviderConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
applyOpencodeZenProviderConfig,
|
applyOpencodeZenProviderConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
MINIMAX_HOSTED_MODEL_REF,
|
MINIMAX_HOSTED_MODEL_REF,
|
||||||
|
OPENROUTER_DEFAULT_MODEL_REF,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
|
setOpenrouterApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
writeOAuthCredentials,
|
writeOAuthCredentials,
|
||||||
@@ -366,6 +370,28 @@ export async function applyAuthChoice(params: {
|
|||||||
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
`Saved OPENAI_API_KEY to ${result.path} for launchd compatibility.`,
|
||||||
"OpenAI API key",
|
"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") {
|
} else if (params.authChoice === "openai-codex") {
|
||||||
const isRemote = isRemoteEnvironment();
|
const isRemote = isRemoteEnvironment();
|
||||||
await params.prompter.note(
|
await params.prompter.note(
|
||||||
@@ -745,6 +771,8 @@ export function resolvePreferredProviderForAuthChoice(
|
|||||||
return "openai-codex";
|
return "openai-codex";
|
||||||
case "openai-api-key":
|
case "openai-api-key":
|
||||||
return "openai";
|
return "openai";
|
||||||
|
case "openrouter-api-key":
|
||||||
|
return "openrouter";
|
||||||
case "gemini-api-key":
|
case "gemini-api-key":
|
||||||
return "google";
|
return "google";
|
||||||
case "antigravity":
|
case "antigravity":
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export async function setMinimaxApiKey(key: string, agentDir?: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ZAI_DEFAULT_MODEL_REF = "zai/glm-4.7";
|
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) {
|
export async function setZaiApiKey(key: string, agentDir?: string) {
|
||||||
// Write to the multi-agent path so gateway finds credentials on startup
|
// 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 {
|
export function applyZaiConfig(cfg: ClawdbotConfig): ClawdbotConfig {
|
||||||
const models = { ...cfg.agents?.defaults?.models };
|
const models = { ...cfg.agents?.defaults?.models };
|
||||||
models[ZAI_DEFAULT_MODEL_REF] = {
|
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(
|
export function applyAuthProfileConfig(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ import {
|
|||||||
applyMinimaxApiConfig,
|
applyMinimaxApiConfig,
|
||||||
applyMinimaxConfig,
|
applyMinimaxConfig,
|
||||||
applyMinimaxHostedConfig,
|
applyMinimaxHostedConfig,
|
||||||
|
applyOpenrouterConfig,
|
||||||
applyOpencodeZenConfig,
|
applyOpencodeZenConfig,
|
||||||
applyZaiConfig,
|
applyZaiConfig,
|
||||||
setAnthropicApiKey,
|
setAnthropicApiKey,
|
||||||
setGeminiApiKey,
|
setGeminiApiKey,
|
||||||
setMinimaxApiKey,
|
setMinimaxApiKey,
|
||||||
|
setOpenrouterApiKey,
|
||||||
setOpencodeZenApiKey,
|
setOpencodeZenApiKey,
|
||||||
setZaiApiKey,
|
setZaiApiKey,
|
||||||
} from "./onboard-auth.js";
|
} from "./onboard-auth.js";
|
||||||
@@ -264,6 +266,25 @@ export async function runNonInteractiveOnboarding(
|
|||||||
});
|
});
|
||||||
process.env.OPENAI_API_KEY = key;
|
process.env.OPENAI_API_KEY = key;
|
||||||
runtime.log(`Saved OPENAI_API_KEY to ${result.path}`);
|
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") {
|
} else if (authChoice === "minimax-cloud") {
|
||||||
const resolved = await resolveNonInteractiveApiKey({
|
const resolved = await resolveNonInteractiveApiKey({
|
||||||
provider: "minimax",
|
provider: "minimax",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export type AuthChoice =
|
|||||||
| "token"
|
| "token"
|
||||||
| "openai-codex"
|
| "openai-codex"
|
||||||
| "openai-api-key"
|
| "openai-api-key"
|
||||||
|
| "openrouter-api-key"
|
||||||
| "codex-cli"
|
| "codex-cli"
|
||||||
| "antigravity"
|
| "antigravity"
|
||||||
| "apiKey"
|
| "apiKey"
|
||||||
@@ -43,6 +44,7 @@ export type OnboardOptions = {
|
|||||||
tokenExpiresIn?: string;
|
tokenExpiresIn?: string;
|
||||||
anthropicApiKey?: string;
|
anthropicApiKey?: string;
|
||||||
openaiApiKey?: string;
|
openaiApiKey?: string;
|
||||||
|
openrouterApiKey?: string;
|
||||||
geminiApiKey?: string;
|
geminiApiKey?: string;
|
||||||
zaiApiKey?: string;
|
zaiApiKey?: string;
|
||||||
minimaxApiKey?: string;
|
minimaxApiKey?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user