Commit c8c42236c058
Changed files (1)
dots
pi
agent
extensions
dots/pi/agent/extensions/custom-header.ts
@@ -0,0 +1,216 @@
+/**
+ * Custom Header Extension
+ *
+ * Displays a custom header with:
+ * - Pi (π) mathematical symbol in ASCII art
+ * - Version information
+ * - Extension issues (if any) displayed in header
+ * - /resources command to show loaded extensions/skills/prompts inline
+ */
+
+import type { ExtensionAPI, Theme } from "@mariozechner/pi-coding-agent";
+import { VERSION } from "@mariozechner/pi-coding-agent";
+
+// Pi symbol ASCII art (using Braille patterns) with green-to-blue gradient
+function getPiArt(theme: Theme): string[] {
+ // Green to blue gradient colors - from bright green at top to blue at bottom
+ const color1 = "\x1b[38;2;0;255;100m"; // Bright green
+ const color2 = "\x1b[38;2;0;240;110m"; // Green
+ const color3 = "\x1b[38;2;0;220;125m"; // Green-cyan
+ const color4 = "\x1b[38;2;0;195;145m"; // Cyan-green
+ const color5 = "\x1b[38;2;0;165;170m"; // Cyan
+ const color6 = "\x1b[38;2;0;130;195m"; // Cyan-blue
+ const color7 = "\x1b[38;2;0;95;220m"; // Blue-cyan
+ const color8 = "\x1b[38;2;0;60;240m"; // Blue
+
+ return [
+ "",
+ color1 + "⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⠀",
+ color2 + "⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄",
+ color2 + "⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀",
+ color3 + "⠀⠀⣴⣿⣿⣿⣿⣿⡟⠉⠀⠀⠈⢻⣿⣿⣿⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color3 + "⢀⣾⣿⣿⣿⣿⡿⣻⡇⠀⠀⠄⠕⢹⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color4 + "⠈⢿⣿⣿⡿⠋⡃⢸⣿⠓⠄⠄⠀⢸⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color4 + "⠀⠀⠈⠁⠀⠀⠌⠀⣿⡇⠀⠐⠀⣾⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color5 + "⠀⠀⠀⠀⠀⠀⠘⠀⡇⡇⠀⠈⠀⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color5 + "⠀⠀⠀⠀⠀⠀⡎⠐⣷⣷⠀⠀⠀⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
+ color6 + "⠀⠀⠀⠀⠀⠀⡏⠀⣷⡇⠈⠀⠀⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀",
+ color6 + "⠀⠀⠀⠀⢈⠀⡎⠸⣿⡇⠀⠀⠀⣿⣿⣿⣿⣿⡠⠀⠀⠀⣰⣿⣿⣿⣦⠀⠀⠀",
+ color7 + "⠀⠀⠀⠀⠁⠀⢹⢰⣿⡇⠀⠀⠀⣿⣿⣿⣿⣿⣄⡀⠀⣰⣿⣿⣿⣿⡿⠀⠀⠀",
+ color7 + "⠀⠀⠀⠀⠀⠀⠼⢿⣿⡇⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀",
+ color8 + "⠀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠈⠙⢿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀",
+ color8 + "⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀",
+ "",
+ ];
+}
+
+// Track extension issues from pi's startup
+let extensionIssues: string[] = [];
+
+export default function (pi: ExtensionAPI) {
+ // Capture extension issues from the console/logs if available
+ // This is a placeholder - we'd need pi to expose these via an event or API
+ pi.on("session_start", async (_event, ctx) => {
+ // TODO: Check if pi exposes extension errors/warnings
+ // For now, we'll show issues in the header if they exist
+ });
+
+ // Set custom header on load
+ pi.on("session_start", async (_event, ctx) => {
+ if (ctx.hasUI) {
+ ctx.ui.setHeader((_tui, theme) => {
+ return {
+ render(_width: number): string[] {
+ const artLines = getPiArt(theme);
+ const versionLine = ` ${theme.fg("muted", "shitty coding agent")} ${theme.fg("dim", `v${VERSION}`)}`;
+ const hintLine = ` ${theme.fg("dim", "Type /resources to view loaded extensions")}`;
+
+ const lines = [...artLines, versionLine, hintLine];
+
+ // Add extension issues if any
+ if (extensionIssues.length > 0) {
+ lines.push("");
+ lines.push(` ${theme.fg("warning", "⚠️ Extension Issues:")}`);
+ for (const issue of extensionIssues) {
+ lines.push(` ${theme.fg("warning", " • " + issue)}`);
+ }
+ }
+
+ return lines;
+ },
+ invalidate() {},
+ };
+ });
+ }
+ });
+
+ // Command to show resources inline (like /todos)
+ pi.registerCommand("resources", {
+ description: "Show loaded extensions, skills, prompts, themes, and context files",
+ handler: async (_args, ctx) => {
+ // Get extension info from commands
+ const commands = pi.getCommands();
+
+ const extensions = commands
+ .filter(cmd => cmd.source === "extension")
+ .map(cmd => ({
+ name: cmd.name,
+ description: cmd.description,
+ path: cmd.path,
+ }));
+
+ const skills = commands
+ .filter(cmd => cmd.source === "skill")
+ .map(cmd => ({
+ name: cmd.name,
+ location: cmd.location,
+ path: cmd.path,
+ }));
+
+ const prompts = commands
+ .filter(cmd => cmd.source === "prompt")
+ .map(cmd => ({
+ name: cmd.name,
+ location: cmd.location,
+ path: cmd.path,
+ }));
+
+ // Get themes
+ const themes = ctx.ui.getAllThemes();
+ const currentTheme = ctx.ui.theme;
+
+ // Build markdown content
+ const lines: string[] = [];
+
+ lines.push("## 🔧 Loaded Resources");
+ lines.push("");
+
+ // Context files section
+ lines.push("### 📄 Context Files");
+ lines.push("");
+ const systemPrompt = ctx.getSystemPrompt();
+ // Try to extract AGENTS.md file references from the system prompt
+ // This is a heuristic - check if prompt contains common patterns
+ if (systemPrompt.includes("AGENTS.md") || systemPrompt.includes("Project-specific instructions")) {
+ // Common locations for context files
+ const contextFiles = [
+ "~/.pi/agent/AGENTS.md",
+ `${ctx.cwd}/AGENTS.md`,
+ ];
+ for (const file of contextFiles) {
+ lines.push(`- \`${file}\``);
+ }
+ } else {
+ lines.push("*No AGENTS.md files detected*");
+ }
+ lines.push("");
+
+ if (extensions.length > 0) {
+ lines.push(`### 🔌 Extensions (${extensions.length})`);
+ lines.push("");
+ for (const ext of extensions) {
+ const desc = ext.description ? ` — ${ext.description}` : "";
+ const path = ext.path ? ` \`${ext.path}\`` : "";
+ lines.push(`- **/${ext.name}**${desc}${path}`);
+ }
+ lines.push("");
+ }
+
+ if (skills.length > 0) {
+ lines.push(`### 🎯 Skills (${skills.length})`);
+ lines.push("");
+ for (const skill of skills) {
+ const loc = skill.location ? ` *(${skill.location})*` : "";
+ const path = skill.path ? ` \`${skill.path}\`` : "";
+ lines.push(`- **/skill:${skill.name}**${loc}${path}`);
+ }
+ lines.push("");
+ }
+
+ if (prompts.length > 0) {
+ lines.push(`### 📝 Prompt Templates (${prompts.length})`);
+ lines.push("");
+ for (const prompt of prompts) {
+ const loc = prompt.location ? ` *(${prompt.location})*` : "";
+ const path = prompt.path ? ` \`${prompt.path}\`` : "";
+ lines.push(`- **/${prompt.name}**${loc}${path}`);
+ }
+ lines.push("");
+ }
+
+ if (themes.length > 0) {
+ lines.push(`### 🎨 Themes (${themes.length})`);
+ lines.push("");
+ for (const theme of themes) {
+ const isCurrent = currentTheme && theme.name === currentTheme.name;
+ const marker = isCurrent ? "**→** " : " ";
+ const path = theme.path ? ` \`${theme.path}\`` : " *(built-in)*";
+ lines.push(`${marker}**${theme.name}**${path}`);
+ }
+ lines.push("");
+ }
+
+ // Empty state
+ if (extensions.length === 0 && skills.length === 0 && prompts.length === 0) {
+ lines.push("*No extensions, skills, or prompts loaded.*");
+ lines.push("");
+ }
+
+ // Send as inline message
+ pi.sendMessage({
+ customType: "resources",
+ content: lines.join("\n"),
+ display: true,
+ });
+ },
+ });
+
+ // Command to restore built-in header
+ pi.registerCommand("builtin-header", {
+ description: "Restore built-in header",
+ handler: async (_args, ctx) => {
+ ctx.ui.setHeader(undefined);
+ ctx.ui.notify("Built-in header restored. Use /reload to re-enable custom header.", "info");
+ },
+ });
+}