import { join } from "node:path"; import { CONFIG_DIR_NAME, getAgentDir, SettingsManager, type ExtensionAPI, type ExtensionContext, } from "@earendil-works/pi-coding-agent"; import { Text, type KeyId } from "@earendil-works/pi-tui"; import { loadConfig } from "../src/config.js"; import { createFooterComponent, type ThemeLike } from "../src/footer.js"; import { openBarMenu } from "../src/menu.js"; import { BarRuntime } from "../src/state.js"; import type { BarState } from "../src/types.js"; import { formatWorkedLine, readWorkedVerbs } from "../src/worked-line.js"; const WORKED_ENTRY_TYPE = "bar:worked-line"; export default function barExtension(pi: ExtensionAPI): void { let runtime: BarRuntime | undefined; let currentContext: ExtensionContext | undefined; let requestRender: () => void = () => undefined; let enabled = true; let shortcutRegistered = false; let workStartedAt: number | undefined; let workedTurnSeed = 0; pi.registerEntryRenderer(WORKED_ENTRY_TYPE, (entry, _options, theme) => { const data = entry.data as { line?: unknown }; return new Text(theme.fg("muted", typeof data.line === "string" ? data.line : "")); }); const markWorkStart = (): void => { if (workStartedAt === undefined) workStartedAt = Date.now(); }; async function openMenu(ctx: ExtensionContext): Promise { if (!runtime) { ctx.ui.notify("Pi Bar is not active in this session", "warning"); return; } await openBarMenu(pi, ctx, runtime, join(getAgentDir(), "pi-bar.json")); } function installFooter(ctx: ExtensionContext): void { if (!runtime || ctx.mode !== "tui") return; ctx.ui.setFooter((tui, theme, footerData) => { requestRender = () => tui.requestRender(); return createFooterComponent({ getState: (): BarState => { const state = runtime?.getState(); if (!state) throw new Error("Pi Bar runtime unavailable"); const branch = footerData.getGitBranch(); return { ...state, ...(branch ? { branch } : {}), extensionStatuses: Array.from(footerData.getExtensionStatuses().values()), }; }, getConfig: () => runtime?.getConfig() ?? (() => { throw new Error("Pi Bar config unavailable"); })(), colorEnabled: !("NO_COLOR" in process.env), requestRender, onBranchChange: (callback) => footerData.onBranchChange(() => { void runtime?.refreshGitDirty(); callback(); }), theme: theme as unknown as ThemeLike, }); }); } pi.registerCommand("bar", { description: "Open or control the Pi Bar status menu", handler: async (args, ctx) => { const action = args.trim().toLowerCase(); if (action === "disable") { enabled = false; ctx.ui.setFooter(undefined); ctx.ui.notify("Pi Bar disabled", "info"); return; } if (action === "enable") { enabled = true; installFooter(ctx); ctx.ui.notify("Pi Bar enabled", "info"); return; } await openMenu(ctx); }, }); pi.on("session_start", async (_event, ctx) => { if (ctx.mode !== "tui") return; ctx.ui.setWorkingVisible(false); currentContext = ctx; try { const userPath = join(getAgentDir(), "pi-bar.json"); const projectPath = join(ctx.cwd, CONFIG_DIR_NAME, "pi-bar.json"); const loaded = await loadConfig({ userPath, projectPath, projectTrusted: ctx.isProjectTrusted(), }); for (const warning of loaded.warnings) ctx.ui.notify(warning, "warning"); let autoCompact: boolean | null = null; try { autoCompact = SettingsManager.create( ctx.isProjectTrusted() ? ctx.cwd : getAgentDir(), ).getCompactionSettings().enabled; } catch { ctx.ui.notify("Could not read Pi compaction settings; compaction mode is unavailable", "warning"); } runtime?.dispose(); runtime = new BarRuntime({ pi, ctx, config: loaded.config, autoCompact, requestRender: () => requestRender(), }); await runtime.refreshGitDirty(); if (!shortcutRegistered) { try { pi.registerShortcut(loaded.config.shortcut as KeyId, { description: "Open Pi Bar", handler: async (shortcutContext) => openMenu(shortcutContext), }); } catch { pi.registerShortcut("alt+a" as KeyId, { description: "Open Pi Bar", handler: async (shortcutContext) => openMenu(shortcutContext), }); ctx.ui.notify(`Invalid Bar shortcut "${loaded.config.shortcut}"; using alt+a`, "warning"); } shortcutRegistered = true; } if (enabled) installFooter(ctx); } catch (error) { runtime?.dispose(); runtime = undefined; ctx.ui.setFooter(undefined); ctx.ui.notify( `Pi Bar could not start: ${error instanceof Error ? error.message : String(error)}`, "error", ); } }); pi.on("before_agent_start", () => markWorkStart()); pi.on("agent_start", () => { markWorkStart(); runtime?.setActivity("working"); }); pi.on("message_start", (event) => { if (event.message.role === "user") markWorkStart(); }); pi.on("message_end", (event) => { const message = event.message; if (message.role !== "assistant" || message.stopReason === "toolUse") return; const startedAt = workStartedAt; workStartedAt = undefined; if (startedAt === undefined) return; const hasText = Array.isArray(message.content) && message.content.some( (block) => block.type === "text" && typeof block.text === "string" && block.text.trim().length > 0, ); if (!hasText) return; pi.appendEntry(WORKED_ENTRY_TYPE, { line: formatWorkedLine(Date.now() - startedAt, workedTurnSeed++, readWorkedVerbs()), }); }); pi.on("agent_settled", () => { workStartedAt = undefined; runtime?.setActivity("ready"); }); pi.on("turn_end", async () => { runtime?.refreshUsage(); await runtime?.refreshGitDirty(); }); pi.on("model_select", () => runtime?.refreshUsage()); pi.on("thinking_level_select", () => runtime?.refreshUsage()); pi.on("session_compact", () => runtime?.refreshUsage()); pi.on("session_info_changed", () => runtime?.refreshUsage()); pi.on("session_shutdown", () => { workStartedAt = undefined; runtime?.dispose(); runtime = undefined; currentContext?.ui.setFooter(undefined); currentContext = undefined; requestRender = () => undefined; }); }