/** * Reader for ~/.a2a/credentials.json — same file the Python `a2a` CLI writes. * * We deliberately *only read* this file (and overwrite it on `a2amcp login`) * so a single login flow is shared between the Python and Node tooling. */ import { promises as fs } from "node:fs"; import os from "node:os"; import path from "node:path"; export const DEFAULT_API_URL = "https://api.a2acloud.io"; export interface Credentials { apiUrl: string; token: string; email: string; userId?: number; bucket?: string; } const credsDir = () => path.join(os.homedir(), ".a2a"); const credsFile = () => path.join(credsDir(), "credentials.json"); export async function loadCredentials(): Promise { try { const raw = await fs.readFile(credsFile(), "utf8"); const data = JSON.parse(raw); if (!data?.token) return null; return { apiUrl: data.api_url ?? DEFAULT_API_URL, token: data.token, email: data.email ?? "", userId: typeof data.user_id === "number" ? data.user_id : undefined, bucket: typeof data.bucket === "string" ? data.bucket : undefined, }; } catch (err: any) { if (err?.code === "ENOENT") return null; throw err; } } export async function saveCredentials(c: Credentials): Promise { await fs.mkdir(credsDir(), { recursive: true }); const out: Record = { api_url: c.apiUrl, token: c.token, email: c.email, }; if (c.userId !== undefined) out.user_id = c.userId; if (c.bucket !== undefined) out.bucket = c.bucket; await fs.writeFile(credsFile(), JSON.stringify(out), { mode: 0o600 }); } export async function clearCredentials(): Promise { try { await fs.unlink(credsFile()); return true; } catch (err: any) { if (err?.code === "ENOENT") return false; throw err; } } export function resolveApiUrl(override?: string): string { if (override) return override; if (process.env.A2A_API_URL) return process.env.A2A_API_URL; return DEFAULT_API_URL; }