120 lines
3.3 KiB
JavaScript
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;
|
|
});
|
|
}
|