1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
/**
* 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<T>(a: T[]): T {
return a[Math.floor(Math.random() * a.length)];
}
export default function (pi: ExtensionAPI) {
let timer: ReturnType<typeof setInterval> | 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());
}
|