import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import type { HudConfig, HudElement } from "./types.js"; export const CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "state", "universal-hud.json"); export const DEFAULT_ELEMENT_ORDER: HudElement[] = [ "project", "identity", "usage", "tools", "agents", "session", ]; export const DEFAULT_CONFIG: HudConfig = { enabled: true, surface: "statusline", lineLayout: "expanded", showSeparators: false, pathLevels: 1, elementOrder: [...DEFAULT_ELEMENT_ORDER], display: { showProject: true, showContextBar: true, showUsage: true, showLimits: true, showTools: true, showAgents: true, showSession: true, showSessionTokens: true, showSpeed: true, showCost: true, }, }; export function loadConfig(): HudConfig { try { const raw = fs.readFileSync(CONFIG_PATH, "utf8"); return normalizeConfig(JSON.parse(raw)); } catch { return normalizeConfig(DEFAULT_CONFIG); } } export function saveConfig(next: HudConfig): void { try { fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true }); fs.writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}\n`, "utf8"); } catch { // UI config persistence is best-effort. } } export function normalizeConfig(input: unknown): HudConfig { const raw = input && typeof input === "object" ? input as Partial : {}; const display: Record = raw.display && typeof raw.display === "object" ? raw.display as Record : {}; const elementOrder = Array.isArray(raw.elementOrder) ? raw.elementOrder.filter((item): item is HudElement => typeof item === "string" && DEFAULT_ELEMENT_ORDER.includes(item as HudElement)) : DEFAULT_ELEMENT_ORDER; return { enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_CONFIG.enabled, surface: raw.surface === "footer" ? "footer" : "statusline", lineLayout: raw.lineLayout === "compact" ? "compact" : "expanded", showSeparators: typeof raw.showSeparators === "boolean" ? raw.showSeparators : DEFAULT_CONFIG.showSeparators, pathLevels: raw.pathLevels === 1 || raw.pathLevels === 2 || raw.pathLevels === 3 ? raw.pathLevels : DEFAULT_CONFIG.pathLevels, elementOrder: elementOrder.length > 0 ? dedupeElements(elementOrder) : [...DEFAULT_ELEMENT_ORDER], display: { showProject: booleanValue(display.showProject, true), showContextBar: booleanValue(display.showContextBar, true), showUsage: booleanValue(display.showUsage, true), showLimits: booleanValue(display.showLimits, true), showTools: booleanValue(display.showTools, true), showAgents: booleanValue(display.showAgents, true), showSession: booleanValue(display.showSession, true), showSessionTokens: booleanValue(display.showSessionTokens, true), showSpeed: booleanValue(display.showSpeed, true), showCost: booleanValue(display.showCost, true), }, }; } function dedupeElements(elements: HudElement[]): HudElement[] { const seen = new Set(); const result: HudElement[] = []; for (const element of elements) { if (seen.has(element)) continue; seen.add(element); result.push(element); } return result; } function booleanValue(value: unknown, fallback: boolean): boolean { return typeof value === "boolean" ? value : fallback; }