import { spawnSync } from "node:child_process"; import { confirm, select, text } from "@clack/prompts"; import { CLAUDE_CLI_PROFILE_ID, ensureAuthProfileStore, upsertAuthProfile, } from "../../agents/auth-profiles.js"; import { normalizeProviderId } from "../../agents/model-selection.js"; import { parseDurationMs } from "../../cli/parse-duration.js"; import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js"; import type { RuntimeEnv } from "../../runtime.js"; import { applyAuthProfileConfig } from "../onboard-auth.js"; import { updateConfig } from "./shared.js"; type TokenProvider = "anthropic"; function resolveTokenProvider(raw?: string): TokenProvider | "custom" | null { const trimmed = raw?.trim(); if (!trimmed) return null; const normalized = normalizeProviderId(trimmed); if (normalized === "anthropic") return "anthropic"; return "custom"; } function resolveDefaultTokenProfileId(provider: string): string { return `${normalizeProviderId(provider)}:manual`; } export async function modelsAuthSetupTokenCommand( opts: { provider?: string; yes?: boolean }, runtime: RuntimeEnv, ) { const provider = resolveTokenProvider(opts.provider ?? "anthropic"); if (provider !== "anthropic") { throw new Error( "Only --provider anthropic is supported for setup-token (uses `claude setup-token`).", ); } if (!process.stdin.isTTY) { throw new Error("setup-token requires an interactive TTY."); } if (!opts.yes) { const proceed = await confirm({ message: "Run `claude setup-token` now?", initialValue: true, }); if (!proceed) return; } const res = spawnSync("claude", ["setup-token"], { stdio: "inherit" }); if (res.error) throw res.error; if (typeof res.status === "number" && res.status !== 0) { throw new Error(`claude setup-token failed (exit ${res.status})`); } const store = ensureAuthProfileStore(undefined, { allowKeychainPrompt: true, }); const synced = store.profiles[CLAUDE_CLI_PROFILE_ID]; if (!synced) { throw new Error( `No Claude CLI credentials found after setup-token. Expected auth profile ${CLAUDE_CLI_PROFILE_ID}.`, ); } await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId: CLAUDE_CLI_PROFILE_ID, provider: "anthropic", mode: "token", }), ); runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`); runtime.log(`Auth profile: ${CLAUDE_CLI_PROFILE_ID} (anthropic/token)`); } export async function modelsAuthPasteTokenCommand( opts: { provider?: string; profileId?: string; expiresIn?: string; }, runtime: RuntimeEnv, ) { const rawProvider = opts.provider?.trim(); if (!rawProvider) { throw new Error("Missing --provider."); } const provider = normalizeProviderId(rawProvider); const profileId = opts.profileId?.trim() || resolveDefaultTokenProfileId(provider); const tokenInput = await text({ message: `Paste token for ${provider}`, validate: (value) => (value?.trim() ? undefined : "Required"), }); const token = String(tokenInput).trim(); const expires = opts.expiresIn?.trim() && opts.expiresIn.trim().length > 0 ? Date.now() + parseDurationMs(String(opts.expiresIn).trim(), { defaultUnit: "d" }) : undefined; upsertAuthProfile({ profileId, credential: { type: "token", provider, token, ...(expires ? { expires } : {}), }, }); await updateConfig((cfg) => applyAuthProfileConfig(cfg, { profileId, provider, mode: "token" }), ); runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`); runtime.log(`Auth profile: ${profileId} (${provider}/token)`); } export async function modelsAuthAddCommand( _opts: Record, runtime: RuntimeEnv, ) { const provider = (await select({ message: "Token provider", options: [ { value: "anthropic", label: "anthropic" }, { value: "custom", label: "custom (type provider id)" }, ], })) as TokenProvider | "custom"; const providerId = provider === "custom" ? normalizeProviderId( String( await text({ message: "Provider id", validate: (value) => (value?.trim() ? undefined : "Required"), }), ), ) : provider; const method = (await select({ message: "Token method", options: [ ...(providerId === "anthropic" ? [ { value: "setup-token", label: "setup-token (claude)", hint: "Runs `claude setup-token` (recommended)", }, ] : []), { value: "paste", label: "paste token" }, ], })) as "setup-token" | "paste"; if (method === "setup-token") { await modelsAuthSetupTokenCommand({ provider: providerId }, runtime); return; } const profileIdDefault = resolveDefaultTokenProfileId(providerId); const profileId = String( await text({ message: "Profile id", initialValue: profileIdDefault, validate: (value) => (value?.trim() ? undefined : "Required"), }), ).trim(); const wantsExpiry = await confirm({ message: "Does this token expire?", initialValue: false, }); const expiresIn = wantsExpiry ? String( await text({ message: "Expires in (duration)", initialValue: "365d", validate: (value) => { try { parseDurationMs(String(value ?? ""), { defaultUnit: "d" }); return undefined; } catch { return "Invalid duration (e.g. 365d, 12h, 30m)"; } }, }), ).trim() : undefined; await modelsAuthPasteTokenCommand( { provider: providerId, profileId, expiresIn }, runtime, ); }