Agent-JAE/default-extensions/dashboard.ts
jae a1b6e22c01 feat: add 16 default extensions for Agent JAE
New extensions shipping with Agent JAE:
- jae-rules: Per-project .jae/rules.md injection
- cost-tracker: Token/cost tracking with budget limits
- project-dna: Auto-analyze project fingerprint
- teach-mode: Pedagogical explanation mode
- bookmarks: File bookmark management
- replay: Session recording and asciicast export
- checkpoints: Git-based undo/time-travel
- pr-review: Autonomous PR diff review
- auto-docs: Auto-generate README and docs
- screenshot-context: Vision analysis for screenshots
- deploy: One-command deploy (Docker/Vercel/rsync/static)
- pair-programming: Real-time file watching with feedback
- dashboard: Terminal HUD with live stats
- swarm: Multi-agent parallel task execution
- skill-marketplace: Search/install/publish skills
- widget-api-demo: TUI widget API demonstration
2026-03-23 20:14:41 +01:00

109 lines
3.2 KiB
TypeScript

import type { ExtensionAPI } from "@jaeswift/jae-coding-agent";
interface DashboardState {
gitBranch: string;
modelName: string;
sessionTokens: number;
filesModified: Set<string>;
toolCalls: number;
startTime: number;
}
export default function (pi: ExtensionAPI) {
const state: DashboardState = {
gitBranch: "",
modelName: "",
sessionTokens: 0,
filesModified: new Set(),
toolCalls: 0,
startTime: Date.now(),
};
function formatUptime(): string {
const elapsed = Math.floor((Date.now() - state.startTime) / 1000);
const mins = Math.floor(elapsed / 60);
const secs = elapsed % 60;
return mins > 0 ? `${mins}m${secs}s` : `${secs}s`;
}
function updateDashboard(ctx: any): void {
const tokens = (state.sessionTokens / 1000).toFixed(1);
const branch = state.gitBranch || "n/a";
const model = state.modelName || "unknown";
const files = state.filesModified.size;
const tools = state.toolCalls;
const uptime = formatUptime();
ctx.ui.setFooter(
"dashboard",
`\u{1F4CA} ${model} | \u{1F333} ${branch} | ${tokens}k tok | ${files} files | ${tools} tools | ${uptime}`,
);
}
pi.on("session_start", async (_event, ctx) => {
state.startTime = Date.now();
// Detect git branch
try {
const result = await pi.exec("git", ["branch", "--show-current"], { cwd: ctx.cwd });
if (result.code === 0 && result.stdout.trim()) {
state.gitBranch = result.stdout.trim();
}
} catch { /* not a git repo */ }
// Get model name
if (ctx.model) {
state.modelName = ctx.model.name || ctx.model.id || "unknown";
}
updateDashboard(ctx);
});
pi.on("message_end", async (event, ctx) => {
if (event.message?.usage) {
const u = event.message.usage;
state.sessionTokens += (u.inputTokens || 0) + (u.outputTokens || 0);
}
if (ctx.model) {
state.modelName = ctx.model.name || ctx.model.id || state.modelName;
}
updateDashboard(ctx);
});
pi.on("tool_execution_end", async (event, ctx) => {
state.toolCalls++;
// Track file modifications
if (["write", "edit", "write_file", "edit_file"].includes(event.toolName)) {
const filePath = (event.input as any)?.path || (event.input as any)?.file_path || "";
if (filePath) state.filesModified.add(filePath);
}
// Refresh git branch (might have changed)
try {
const result = await pi.exec("git", ["branch", "--show-current"], { cwd: ctx.cwd });
if (result.code === 0 && result.stdout.trim()) {
state.gitBranch = result.stdout.trim();
}
} catch { /* ignore */ }
updateDashboard(ctx);
});
pi.registerCommand("dashboard", {
description: "Show session dashboard stats",
handler: async (_args, ctx) => {
const uptime = formatUptime();
ctx.ui.notify(
`\u{1F4CA} Session Dashboard:
Model: ${state.modelName || "unknown"}
Git Branch: ${state.gitBranch || "n/a"}
Session Tokens: ${state.sessionTokens.toLocaleString()}
Files Modified: ${state.filesModified.size} (${Array.from(state.filesModified).slice(0, 5).join(", ")}${state.filesModified.size > 5 ? "..." : ""})
Tool Calls: ${state.toolCalls}
Uptime: ${uptime}`,
"info",
);
},
});
}