import { readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs'; import { resolve } from 'path'; import { homedir } from 'os'; const AUTH_DIR = resolve(homedir(), '.config/flynn'); const AUTH_FILE = resolve(AUTH_DIR, 'auth.json'); export interface ZaiAuthInfo { /** Z.AI API key (used as Bearer token). */ api_key: string; created_at: string; } interface AuthStore { // Keep other provider entries untyped to avoid cross-module coupling. github?: unknown; openai?: unknown; /** Preferred key for Z.AI credentials. */ zai?: ZaiAuthInfo; /** Back-compat: older naming that matches the model provider id. */ zhipuai?: ZaiAuthInfo; } function safeJsonParse(raw: string): T | null { try { return JSON.parse(raw) as T; } catch { return null; } } function readAuthStore(): AuthStore { try { const raw = readFileSync(AUTH_FILE, 'utf-8'); const parsed = safeJsonParse(raw); return parsed ?? {}; } catch { return {}; } } function writeAuthStore(store: AuthStore): void { mkdirSync(AUTH_DIR, { recursive: true }); writeFileSync(AUTH_FILE, JSON.stringify(store, null, 2) + '\n', 'utf-8'); chmodSync(AUTH_FILE, 0o600); } export function loadStoredZaiAuth(): ZaiAuthInfo | null { const store = readAuthStore(); return store.zai ?? store.zhipuai ?? null; } export function storeZaiAuth(apiKey: string): void { const trimmed = apiKey.trim(); if (!trimmed) { throw new Error('Z.AI API key is empty'); } const store = readAuthStore(); store.zai = { api_key: trimmed, created_at: new Date().toISOString() }; // Also write the provider-id alias for compatibility. store.zhipuai = store.zai; writeAuthStore(store); } export function clearZaiAuth(): void { const store = readAuthStore(); delete store.zai; delete store.zhipuai; writeAuthStore(store); } /** * Get a Z.AI credential from any available source. * Priority: ZAI_API_KEY → ZHIPUAI_API_KEY → ZHIPUAI_AUTH_TOKEN → stored auth.json. */ export function getZaiApiKey(): string | null { const raw = process.env.ZAI_API_KEY ?? process.env.ZHIPUAI_API_KEY ?? process.env.ZHIPUAI_AUTH_TOKEN ?? loadStoredZaiAuth()?.api_key; if (!raw) {return null;} return raw.startsWith('Bearer ') ? raw.slice('Bearer '.length) : raw; }