singularity-forge/src/resources/extensions/sf-tui/index.js

120 lines
3.3 KiB
JavaScript

/**
* 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;
});
}