/** * Obnoxious Working Indicator * * pi's default streaming status is a static "Working..." with a plain braille * spinner. This replaces it, per agent run, with a randomly-picked loud spinner * and a rotating stream of quirky status messages. * * How it works (all via the official UI hooks): * - setWorkingIndicator({ frames, intervalMs }) — custom spinner. Frames are * rendered VERBATIM, so we bake truecolor into them (rainbow spinners etc.). * - setWorkingMessage(text) — the status line. pi tints it "muted", but our * embedded ANSI still shows through. * - Both update the LIVE indicator, so we rotate the message (and sometimes the * spinner) on a timer while streaming. * * Lifecycle: pick on agent_start, rotate on a timer, restore defaults on * agent_settled / session_shutdown. No-op without a TUI. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; // ── color helpers ─────────────────────────────────────────────────────────── function fg(hex: string, s: string): string { const h = hex.replace("#", ""); return `\x1b[38;2;${parseInt(h.slice(0, 2), 16)};${parseInt(h.slice(2, 4), 16)};${parseInt(h.slice(4, 6), 16)}m${s}\x1b[39m`; } // vivid 6-stop rainbow: pink · orange · yellow · green · cyan · violet const HUES = ["#ff2e88", "#ff8a1e", "#ffe000", "#7ee787", "#2ed6ff", "#b05cff"]; // Color each glyph by cycling the palette → a spinner that spins AND shifts hue. const rainbow = (glyphs: string[]): string[] => glyphs.map((g, i) => fg(HUES[i % HUES.length], g)); // Color every glyph a single fixed hue. const tint = (hex: string, glyphs: string[]): string[] => glyphs.map((g) => fg(hex, g)); type Spinner = { frames: string[]; intervalMs: number }; // ── the obnoxious spinner pool ────────────────────────────────────────────── const SPINNERS: Spinner[] = [ { frames: rainbow(["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]), intervalMs: 70 }, { frames: rainbow(["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]), intervalMs: 80 }, { frames: ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"], intervalMs: 120 }, { frames: ["🕛", "🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕚"], intervalMs: 90 }, { frames: ["🌍", "🌎", "🌏"], intervalMs: 180 }, { frames: ["🔥", "✨", "💥", "⚡", "🌟", "💫"], intervalMs: 140 }, { frames: ["❤️", "🧡", "💛", "💚", "💙", "💜"], intervalMs: 110 }, { frames: rainbow(["▏", "▎", "▍", "▌", "▋", "▊", "▉", "█", "▉", "▊", "▋", "▌", "▍", "▎"]), intervalMs: 60 }, { frames: rainbow(["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"]), intervalMs: 90 }, { frames: tint("#2ed6ff", ["◜", "◠", "◝", "◞", "◡", "◟"]), intervalMs: 90 }, { frames: tint("#ff2e88", ["◐", "◓", "◑", "◒"]), intervalMs: 100 }, { frames: ["🙈", "🙉", "🙊"], intervalMs: 200 }, { frames: ["💀", "☠️", "💀", "🕺"], intervalMs: 160 }, { frames: rainbow(["⢹", "⢺", "⢼", "⣸", "⣇", "⡧", "⡗", "⡏"]), intervalMs: 70 }, { frames: ["🚀", "🛸", "✨", "🌌", "🪐"], intervalMs: 150 }, { frames: tint("#7ee787", [". ", ".. ", "...", " ..", " .", " "]), intervalMs: 130 }, ]; // ── the message pool (sl0p voice: goofy swag, light leet, max attitude) ──── const MESSAGES: string[] = [ "warming up the h4x…", "sl0pmaxxing…", "he’s so back…", "big brain loading…", "w4rez incoming…", "0day o’clock…", "hax0r things…", "sl0p.foo, we outside…", "built for this, frfr…", "loading the sauce…", "computering extremely hard…", ]; function pick(a: T[]): T { return a[Math.floor(Math.random() * a.length)]; } export default function (pi: ExtensionAPI) { let timer: ReturnType | undefined; const stop = () => { if (timer) { clearInterval(timer); timer = undefined; } }; const start = (ctx: any) => { if (!ctx?.hasUI) return; stop(); const spin = pick(SPINNERS); try { ctx.ui.setWorkingIndicator({ frames: spin.frames, intervalMs: spin.intervalMs }); ctx.ui.setWorkingMessage(pick(MESSAGES)); } catch { /* ignore */ } timer = setInterval(() => { try { ctx.ui.setWorkingMessage(pick(MESSAGES)); // ~1 in 4 rotations, swap to a different obnoxious spinner too. if (Math.random() < 0.25) { const s = pick(SPINNERS); ctx.ui.setWorkingIndicator({ frames: s.frames, intervalMs: s.intervalMs }); } } catch { /* ignore */ } }, 2200); timer.unref?.(); }; const settle = (ctx: any) => { stop(); try { ctx?.ui?.setWorkingMessage(undefined); // back to "Working..." ctx?.ui?.setWorkingIndicator(undefined); // back to default spinner } catch { /* ignore */ } }; pi.on("agent_start", (_e, ctx) => start(ctx)); pi.on("agent_settled", (_e, ctx) => settle(ctx)); pi.on("session_shutdown", () => stop()); }