/ extensions / titlebar-spinner.ts
titlebar-spinner.ts
 1  /**
 2   * Titlebar Spinner Extension
 3   *
 4   * Shows a braille spinner animation in the terminal title while the agent is working.
 5   * Uses `ctx.ui.setTitle()` to update the terminal title via the extension API.
 6   *
 7   * Usage:
 8   *   pi --extension examples/extensions/titlebar-spinner.ts
 9   */
10  
11  import path from "node:path";
12  import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
13  
14  const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
15  
16  function getBaseTitle(pi: ExtensionAPI): string {
17  	const cwd = path.basename(process.cwd());
18  	const session = pi.getSessionName();
19  	return session ? `π - ${session} - ${cwd}` : `π - ${cwd}`;
20  }
21  
22  export default function (pi: ExtensionAPI) {
23  	let timer: ReturnType<typeof setInterval> | null = null;
24  	let frameIndex = 0;
25  
26  	function stopAnimation(ctx: ExtensionContext) {
27  		if (timer) {
28  			clearInterval(timer);
29  			timer = null;
30  		}
31  		frameIndex = 0;
32  		ctx.ui.setTitle(getBaseTitle(pi));
33  	}
34  
35  	function startAnimation(ctx: ExtensionContext) {
36  		stopAnimation(ctx);
37  		timer = setInterval(() => {
38  			const frame = BRAILLE_FRAMES[frameIndex % BRAILLE_FRAMES.length];
39  			const cwd = path.basename(process.cwd());
40  			const session = pi.getSessionName();
41  			const title = session ? `${frame} π - ${session} - ${cwd}` : `${frame} π - ${cwd}`;
42  			ctx.ui.setTitle(title);
43  			frameIndex++;
44  		}, 80);
45  	}
46  
47  	pi.on("agent_start", async (_event, ctx) => {
48  		startAnimation(ctx);
49  	});
50  
51  	pi.on("agent_end", async (_event, ctx) => {
52  		stopAnimation(ctx);
53  	});
54  
55  	pi.on("session_shutdown", async (_event, ctx) => {
56  		stopAnimation(ctx);
57  	});
58  }