120 lines
3.6 KiB
TypeScript
120 lines
3.6 KiB
TypeScript
import type { AuthProfileStore } from "../agents/auth-profiles.js";
|
|
import {
|
|
CLAUDE_CLI_PROFILE_ID,
|
|
CODEX_CLI_PROFILE_ID,
|
|
} from "../agents/auth-profiles.js";
|
|
import { colorize, isRich, theme } from "../terminal/theme.js";
|
|
import type { AuthChoice } from "./onboard-types.js";
|
|
|
|
export type AuthChoiceOption = {
|
|
value: AuthChoice;
|
|
label: string;
|
|
hint?: string;
|
|
};
|
|
|
|
function formatOAuthHint(
|
|
expires?: number,
|
|
opts?: { allowStale?: boolean },
|
|
): string {
|
|
const rich = isRich();
|
|
if (!expires) {
|
|
return colorize(rich, theme.muted, "token unavailable");
|
|
}
|
|
const now = Date.now();
|
|
const remaining = expires - now;
|
|
if (remaining <= 0) {
|
|
if (opts?.allowStale) {
|
|
return colorize(rich, theme.warn, "token present · refresh on use");
|
|
}
|
|
return colorize(rich, theme.error, "token expired");
|
|
}
|
|
const minutes = Math.round(remaining / (60 * 1000));
|
|
const duration =
|
|
minutes >= 120
|
|
? `${Math.round(minutes / 60)}h`
|
|
: minutes >= 60
|
|
? "1h"
|
|
: `${Math.max(minutes, 1)}m`;
|
|
const label = `token ok · expires in ${duration}`;
|
|
if (minutes <= 10) {
|
|
return colorize(rich, theme.warn, label);
|
|
}
|
|
return colorize(rich, theme.success, label);
|
|
}
|
|
|
|
export function buildAuthChoiceOptions(params: {
|
|
store: AuthProfileStore;
|
|
includeSkip: boolean;
|
|
includeClaudeCliIfMissing?: boolean;
|
|
platform?: NodeJS.Platform;
|
|
}): AuthChoiceOption[] {
|
|
const options: AuthChoiceOption[] = [];
|
|
const platform = params.platform ?? process.platform;
|
|
|
|
const codexCli = params.store.profiles[CODEX_CLI_PROFILE_ID];
|
|
if (codexCli?.type === "oauth") {
|
|
options.push({
|
|
value: "codex-cli",
|
|
label: "OpenAI Codex OAuth (Codex CLI)",
|
|
hint: formatOAuthHint(codexCli.expires, { allowStale: true }),
|
|
});
|
|
}
|
|
|
|
const claudeCli = params.store.profiles[CLAUDE_CLI_PROFILE_ID];
|
|
if (claudeCli?.type === "oauth" || claudeCli?.type === "token") {
|
|
options.push({
|
|
value: "claude-cli",
|
|
label: "Anthropic token (Claude CLI)",
|
|
hint: formatOAuthHint(claudeCli.expires),
|
|
});
|
|
} else if (params.includeClaudeCliIfMissing && platform === "darwin") {
|
|
options.push({
|
|
value: "claude-cli",
|
|
label: "Anthropic token (Claude CLI)",
|
|
hint: "requires Keychain access",
|
|
});
|
|
}
|
|
|
|
options.push({
|
|
value: "setup-token",
|
|
label: "Anthropic token (run setup-token)",
|
|
hint: "Runs `claude setup-token`",
|
|
});
|
|
|
|
options.push({
|
|
value: "token",
|
|
label: "Anthropic token (paste setup-token)",
|
|
hint: "Run `claude setup-token`, then paste the token",
|
|
});
|
|
|
|
options.push({
|
|
value: "openai-codex",
|
|
label: "OpenAI Codex (ChatGPT OAuth)",
|
|
});
|
|
options.push({ value: "openai-api-key", label: "OpenAI API key" });
|
|
options.push({
|
|
value: "antigravity",
|
|
label: "Google Antigravity (Claude Opus 4.5, Gemini 3, etc.)",
|
|
});
|
|
options.push({ value: "gemini-api-key", label: "Google Gemini API key" });
|
|
options.push({ value: "zai-api-key", label: "Z.AI (GLM) API key" });
|
|
options.push({ value: "apiKey", label: "Anthropic API key" });
|
|
// Token flow is currently Anthropic-only; use CLI for advanced providers.
|
|
options.push({
|
|
value: "opencode-zen",
|
|
label: "OpenCode Zen (multi-model proxy)",
|
|
hint: "Claude, GPT, Gemini via opencode.ai/zen",
|
|
});
|
|
options.push({ value: "minimax-cloud", label: "MiniMax M2.1 (minimax.io)" });
|
|
options.push({ value: "minimax", label: "Minimax M2.1 (LM Studio)" });
|
|
options.push({
|
|
value: "minimax-api",
|
|
label: "MiniMax API (platform.minimax.io)",
|
|
});
|
|
if (params.includeSkip) {
|
|
options.push({ value: "skip", label: "Skip for now" });
|
|
}
|
|
|
|
return options;
|
|
}
|