/** * Passthrough backend -- delegates to Pi's native auth.json. * * This is the fallback backend when the configured backend is unavailable. * It reads and writes directly to auth.json, preserving Pi's default behavior. */ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs"; import { dirname, join } from "node:path"; import { homedir } from "node:os"; import type { BackendStatus, CredentialBackend, CredentialEntry, } from "../types.js"; function getAuthPath(): string { return join(homedir(), ".pi", "agent", "auth.json"); } function readAuthFile(): Record { const authPath = getAuthPath(); if (!existsSync(authPath)) { return {}; } const raw = readFileSync(authPath, "utf-8"); const parsed: unknown = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return {}; } return parsed as Record; } function writeAuthFile(data: Record): void { const authPath = getAuthPath(); const dir = dirname(authPath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true, mode: 0o700 }); } writeFileSync(authPath, JSON.stringify(data, null, 2), "utf-8"); chmodSync(authPath, 0o600); } function toCredentialEntry(raw: unknown): CredentialEntry | undefined { if (!raw || typeof raw !== "object" || Array.isArray(raw)) { return undefined; } const entry = raw as Record; if (entry.type === "api_key" && typeof entry.key === "string") { return { type: "api_key", key: entry.key }; } if ( entry.type === "oauth" && typeof entry.access === "string" && typeof entry.refresh === "string" && typeof entry.expires === "number" ) { return { type: "oauth", access: entry.access, refresh: entry.refresh, expires: entry.expires, accountId: typeof entry.accountId === "string" ? entry.accountId : undefined, }; } return undefined; } export class PassthroughBackend implements CredentialBackend { readonly name = "passthrough"; async get(provider: string): Promise { const data = readAuthFile(); return toCredentialEntry(data[provider]); } async set(provider: string, entry: CredentialEntry): Promise { const data = readAuthFile(); data[provider] = { ...entry }; writeAuthFile(data); } async remove(provider: string): Promise { const data = readAuthFile(); delete data[provider]; writeAuthFile(data); } async list(): Promise { const data = readAuthFile(); return Object.keys(data); } async check(): Promise { const authPath = getAuthPath(); if (existsSync(authPath)) { return { available: true }; } return { available: true, detail: "auth.json does not exist yet; will be created on first write" }; } }