/** * SF-TUI — Unified TUI enhancements for Singularity Forge * * Features: * - Powerline footer: git branch, diff stats, last commit, model, cost, context * - Header: project name + branch + model * - Prompt history: Ctrl+Alt+H overlay */ import { randomUUID } from "node:crypto"; import { Key } from "@singularity-forge/pi-tui"; import { isAutoActive } from "../sf/auto.js"; import { projectRoot } from "../sf/commands/context.js"; import { registerSessionColor } from "./color-band.js"; import { registerSessionEmoji } from "./emoji.js"; import { renderFooter } from "./footer.js"; import { invalidateGitStatus } from "./git.js"; import { renderHeader } from "./header.js"; import { openMarketplaceOverlay } from "./marketplace.js"; import { appendPromptHistory, openPromptHistoryOverlay, pushPromptHistory, readPromptHistory, } from "./prompt-history.js"; function installHeader(ctx) { if (!ctx.hasUI) return; ctx.ui.setHeader((_tui, theme) => { return { render: (width) => { if (isAutoActive()) return []; return renderHeader(theme, ctx, width); }, invalidate: () => {}, dispose: () => {}, }; }); } function installFooter(ctx) { if (!ctx.hasUI) return; ctx.ui.setFooter((_tui, theme, footerData) => { return { render: (width) => { if (isAutoActive()) return []; return renderFooter(theme, footerData, ctx, width); }, invalidate: () => {}, dispose: () => {}, }; }); } export default function sfTui(pi) { registerSessionEmoji(pi); registerSessionColor(pi); const promptHistory = readPromptHistory(); const promptHistorySessionId = randomUUID(); let projectBasePath = null; let wasAutoActive = false; pi.on("session_start", async (_event, ctx) => { if (!ctx.hasUI) return; try { projectBasePath = projectRoot(); const projectPromptHistory = readPromptHistory(projectBasePath); promptHistory.splice(0, promptHistory.length, ...projectPromptHistory); } catch { projectBasePath = null; } installHeader(ctx); installFooter(ctx); const openProjectPromptHistory = (overlayCtx) => openPromptHistoryOverlay(overlayCtx, projectBasePath ?? undefined); pi.registerShortcut(Key.ctrlAlt("h"), { description: "Open prompt history", handler: openProjectPromptHistory, }); pi.registerShortcut(Key.ctrlShift("h"), { description: "Open prompt history (fallback)", handler: openProjectPromptHistory, }); pi.registerShortcut(Key.ctrlAlt("m"), { description: "Open marketplace browser", handler: openMarketplaceOverlay, }); wasAutoActive = isAutoActive(); }); pi.on("before_agent_start", async (event) => { const prompt = event.prompt?.trim(); if (prompt) { pushPromptHistory(promptHistory, prompt); appendPromptHistory( prompt, projectBasePath ?? undefined, promptHistorySessionId, ); pi.appendEntry("sf-prompt-history", { prompt, projectRoot: projectBasePath, sessionId: promptHistorySessionId, timestamp: Date.now(), }); } }); pi.on("tool_result", async (_event, ctx) => { invalidateGitStatus(); const autoNow = isAutoActive(); if (!autoNow && wasAutoActive) { installHeader(ctx); installFooter(ctx); } wasAutoActive = autoNow; }); pi.on("agent_end", async (_event, ctx) => { const autoNow = isAutoActive(); if (!autoNow) { installHeader(ctx); installFooter(ctx); } wasAutoActive = autoNow; }); }