import { readFile } from "node:fs/promises"; import type { ThinkingLevel } from "@earendil-works/pi-ai"; export type PermissionMode = "approve-for-me" | "full-access"; export interface ReviewerConfig { model: string; reasoning: ThinkingLevel; } export interface ApproveForMeConfig { mode: PermissionMode; reviewer: ReviewerConfig; alwaysAllowTools: string[]; } export type ConfigResult = | { ok: true; config: ApproveForMeConfig } | { ok: false; error: string }; const REASONING_LEVELS = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]); function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } export function parseModelReference(reference: string): { provider: string; modelId: string } | undefined { const slash = reference.indexOf("/"); if (slash <= 0 || slash === reference.length - 1) return undefined; return { provider: reference.slice(0, slash), modelId: reference.slice(slash + 1) }; } export function parseSettingsJson(text: string): ConfigResult { let root: unknown; try { root = JSON.parse(text) as unknown; } catch (error) { return { ok: false, error: `settings.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}` }; } if (!isRecord(root)) return { ok: false, error: "settings.json must contain a JSON object" }; const namespace = root["approveForMe"]; if (!isRecord(namespace)) return { ok: false, error: "settings.json is missing the approveForMe object" }; const mode = namespace["mode"]; if (mode !== "approve-for-me" && mode !== "full-access") { return { ok: false, error: "approveForMe.mode must be approve-for-me or full-access" }; } const reviewer = namespace["reviewer"]; if (!isRecord(reviewer)) return { ok: false, error: "approveForMe.reviewer must be an object" }; const model = reviewer["model"]; if (typeof model !== "string" || parseModelReference(model) === undefined) { return { ok: false, error: "approveForMe.reviewer.model must be a fully-qualified provider/model id" }; } const reasoning = reviewer["reasoning"]; if (typeof reasoning !== "string" || !REASONING_LEVELS.has(reasoning as ThinkingLevel)) { return { ok: false, error: "approveForMe.reviewer.reasoning is not a supported Pi reasoning level" }; } const configuredValue = namespace["alwaysAllowTools"]; const configuredTools = configuredValue === undefined ? [] : configuredValue; if (!Array.isArray(configuredTools)) { return { ok: false, error: "approveForMe.alwaysAllowTools must be an array" }; } const alwaysAllowTools: string[] = []; const seen = new Set(); for (const name of configuredTools) { if (typeof name !== "string" || name.length === 0) { return { ok: false, error: "approveForMe.alwaysAllowTools must contain only non-empty strings" }; } if (name === "bash") { return { ok: false, error: "approveForMe.alwaysAllowTools cannot contain bash" }; } if (seen.has(name)) { return { ok: false, error: `approveForMe.alwaysAllowTools contains duplicate tool name: ${name}` }; } seen.add(name); alwaysAllowTools.push(name); } return { ok: true, config: { mode, reviewer: { model, reasoning: reasoning as ThinkingLevel }, alwaysAllowTools }, }; } export async function loadGlobalConfig(settingsPath: string): Promise { try { return parseSettingsJson(await readFile(settingsPath, "utf8")); } catch (error) { return { ok: false, error: `Could not read settings.json: ${error instanceof Error ? error.message : String(error)}` }; } }