import * as fs from "node:fs/promises"; import * as path from "node:path"; import { isEnoent, logger } from "@oh-my-pi/pi-utils"; import { getAgentDir } from "@oh-my-pi/pi-utils/dirs"; const SMITHERY_AUTH_FILENAME = "smithery.json"; const SMITHERY_URL = process.env.SMITHERY_URL || "https://smithery.ai"; type SmitheryCliAuthSession = { sessionId: string; authUrl: string; }; type SmitheryCliPollResponse = { status: "pending" | "success" | "error"; apiKey?: string; message?: string; }; type SmitheryAuthPayload = { apiKey?: string; }; function getSmitheryAuthPath(): string { return path.join(getAgentDir(), SMITHERY_AUTH_FILENAME); } function normalizeApiKey(value: string | undefined): string | undefined { if (!value) return undefined; const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : undefined; } export function getSmitheryLoginUrl(): string { return SMITHERY_URL; } export async function createSmitheryCliAuthSession(): Promise { const response = await fetch(`${SMITHERY_URL}/api/auth/cli/session`, { method: "POST", }); if (!response.ok) { throw new Error(`Failed to create Smithery auth session: ${response.status} ${response.statusText}`); } return (await response.json()) as SmitheryCliAuthSession; } export async function pollSmitheryCliAuthSession( sessionId: string, signal?: AbortSignal, ): Promise { const response = await fetch(`${SMITHERY_URL}/api/auth/cli/poll/${sessionId}`, { signal, }); if (!response.ok) { if (response.status === 404 || response.status === 410) { throw new Error("Smithery login session expired. Please try again."); } throw new Error(`Smithery auth polling failed: ${response.status} ${response.statusText}`); } return (await response.json()) as SmitheryCliPollResponse; } export async function getSmitheryApiKey(): Promise { const envKey = normalizeApiKey(process.env.SMITHERY_API_KEY); if (envKey) return envKey; const authPath = getSmitheryAuthPath(); try { const payload = (await Bun.file(authPath).json()) as SmitheryAuthPayload; return normalizeApiKey(payload.apiKey); } catch (error) { if (isEnoent(error)) return undefined; logger.warn("Failed to read Smithery auth file, treating as missing", { path: authPath, error }); return undefined; } } export async function saveSmitheryApiKey(apiKey: string): Promise { const normalized = normalizeApiKey(apiKey); if (!normalized) { throw new Error("Smithery API key cannot be empty."); } const authPath = getSmitheryAuthPath(); const payload: SmitheryAuthPayload = { apiKey: normalized }; await Bun.write(authPath, `${JSON.stringify(payload, null, 2)}\n`); try { await fs.chmod(authPath, 0o600); } catch (error) { logger.warn("Could not set restrictive permissions on Smithery auth file", { path: authPath, error }); } } export async function clearSmitheryApiKey(): Promise { const authPath = getSmitheryAuthPath(); try { await fs.rm(authPath); return true; } catch (error) { if (isEnoent(error)) return false; throw error; } }