import type { OAuthCredentials } from "@earendil-works/pi-ai"; import { isRecord, stringValue } from "./utils.js"; import { resolveApiBase, WORKOS_TOKEN_PREFIX } from "./env.js"; import { walkAuthPaths, type AuthKeyOptions } from "./config-store.js"; export { WORKOS_TOKEN_PREFIX }; export const CLINE_REFRESH_ENDPOINT = "/api/v1/auth/refresh"; export const WORKOS_TOKEN_LIFETIME_MS = 55 * 60 * 1000; export const WORKOS_REFRESH_MARGIN_MS = 5 * 60 * 1000; export const WORKOS_REFRESH_TIMEOUT_MS = 15_000; export interface ClineAuthCredentials { accessToken: string; refreshToken: string; expiresAt: number; } export function isWorkosToken(token: string): boolean { return token.startsWith(WORKOS_TOKEN_PREFIX); } export function credentialsFromWorkos( accessToken: string, refreshToken: string, expiresAt: number, ): OAuthCredentials { return { access: accessToken, refresh: refreshToken, expires: expiresAt, }; } export interface WorkosRefreshOptions { fetch?: typeof globalThis.fetch; apiBase?: string; } export async function refreshWorkosToken( credentials: OAuthCredentials, options: WorkosRefreshOptions = {}, ): Promise { const fetchFn = options.fetch ?? globalThis.fetch; const apiBase = options.apiBase ?? resolveApiBase(); let response; try { response = await fetchFn(`${apiBase}${CLINE_REFRESH_ENDPOINT}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ granttype: "refresh_token", refreshToken: credentials.refresh, }), signal: AbortSignal.timeout(WORKOS_REFRESH_TIMEOUT_MS), }); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") { throw new Error( "Cline token refresh timed out — check your network or try a static API key.", { cause: err }, ); } throw err; } if (!response.ok) { const text = await response.text().catch(() => "unknown error"); throw new Error( `Cline token refresh failed (${response.status}): ${text}` + " — try running `cline auth` to re-login, or use a static API key.", ); } const data = (await response.json()) as { data?: { accessToken?: string; refreshToken?: string }; accessToken?: string; refreshToken?: string; }; const tokens = data.data ?? data; const newAccessToken = tokens.accessToken; const newRefreshToken = tokens.refreshToken; if (!newAccessToken || !newRefreshToken) { throw new Error("Cline token refresh returned unexpected response format"); } const prefixedToken = isWorkosToken(newAccessToken) ? newAccessToken : `workos:${newAccessToken}`; return credentialsFromWorkos( prefixedToken, newRefreshToken, Date.now() + WORKOS_TOKEN_LIFETIME_MS - WORKOS_REFRESH_MARGIN_MS, ); } function resolveExpiresAt(expiresField: unknown): number { if (typeof expiresField === "number" && Number.isFinite(expiresField)) { return expiresField; } return 0; } function extractWorkosAuthFromRecord( auth: Record, ): ClineAuthCredentials | undefined { const accessToken = stringValue(auth.accessToken); const refreshToken = stringValue(auth.refreshToken); if (!accessToken || !refreshToken || !isWorkosToken(accessToken)) return undefined; return { accessToken, refreshToken, expiresAt: resolveExpiresAt(auth.expiresAt), }; } function extractPiProviderAuth(parsed: Record): ClineAuthCredentials | undefined { for (const field of ["clinefree", "clinepass"] as const) { const providerField = parsed[field]; if (!isRecord(providerField)) continue; const accessToken = stringValue(providerField.access); const refreshToken = stringValue(providerField.refresh); if (!accessToken || !refreshToken || !isWorkosToken(accessToken)) continue; return { accessToken, refreshToken, expiresAt: resolveExpiresAt(providerField.expires), }; } return undefined; } function collectClineProviderAuths(parsed: Record): ClineAuthCredentials[] { const providers = isRecord(parsed.providers) ? parsed.providers : undefined; if (!providers) return []; const results: ClineAuthCredentials[] = []; for (const key of ["cline-pass", "cline"]) { const provider = isRecord(providers[key]) ? providers[key] : undefined; if (!provider) continue; const settings = isRecord(provider.settings) ? provider.settings : undefined; if (!settings) continue; const auth = isRecord(settings.auth) ? settings.auth : undefined; if (!auth) continue; const cred = extractWorkosAuthFromRecord(auth); if (cred) results.push(cred); } return results; } function pickFreshestAuth(candidates: ClineAuthCredentials[]): ClineAuthCredentials | undefined { if (candidates.length === 0) return undefined; return candidates.reduce((best, current) => current.expiresAt > best.expiresAt ? current : best, ); } export function resolveClineAuthCredentials( options: AuthKeyOptions = {}, ): ClineAuthCredentials | undefined { const candidates: ClineAuthCredentials[] = []; walkAuthPaths(options, (parsed) => { const piAuth = extractPiProviderAuth(parsed); if (piAuth) candidates.push(piAuth); candidates.push(...collectClineProviderAuths(parsed)); return undefined; }); return pickFreshestAuth(candidates); }