@underactive/pi-topping 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -38
- package/index.ts +1 -1
- package/package.json +3 -3
- package/{activity-meter.ts → src/activity-meter.ts} +9 -2
- package/{format.ts → src/format.ts} +26 -11
- package/src/menu.ts +497 -0
- package/src/preview.ts +61 -0
- package/src/prompt-decorator.ts +77 -0
- package/src/session.ts +370 -0
- package/src/settings.ts +174 -0
- package/menu.ts +0 -355
- package/preview.ts +0 -47
- package/session.ts +0 -43
- package/settings.ts +0 -215
- /package/{words.ts → src/words.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
1
|
# pi-topping
|
|
2
2
|
|
|
3
|
-
We garnish our pies. It seemed rude not to extend Pi the same courtesy. This is a [Pi](https://github.com/earendil-works/pi-coding-agent) extension that
|
|
3
|
+
We garnish our pies. It seemed rude not to extend Pi the same courtesy. This is a [Pi](https://github.com/earendil-works/pi-coding-agent) extension that adds some quality of life improvements to Pi's UX, while looking good in the process:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**User Prompt** — high-vis bordered prompt box with configurable border color, pi icon toggle, and timestamp.
|
|
6
|
+
|
|
7
|
+

|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
**Working Loader Text** — animated spinner (with color choice), randomized activity word, shimmer (with direction), token activity meter (with color, direction, and dim toggle), elapsed timer, and output token display.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
- Sweeps a theme-aware shimmer across the word while it's showing.
|
|
11
|
-
- Shows an elapsed timer (`Xm YYs`) that counts up from when you submitted the prompt.
|
|
12
|
-
- Shows a live output-token estimate (`↓ N tokens`), reconciled against the real usage total once each assistant message finishes.
|
|
13
|
-
- Resets everything — word, timer, token count — on each new prompt, and restores Pi's normal loader between turns.
|
|
14
|
-
- Leaves a durable completion marker in the transcript after each finished turn, e.g. `π Baked for 6m 41s` — a fresh random word and the total time for that turn, rendered with `π` in the theme's primary text color and the rest dimmed. It's TUI-only display content and never enters LLM context.
|
|
11
|
+

|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
**Completion Marker** — end-of-turn marker with icon, randomized verb, and token consumption display. Hooks into Pi's `agent_settled` event.
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+

|
|
19
16
|
|
|
20
17
|
## Settings
|
|
21
18
|
|
|
22
|
-
Run `/topping-settings` (TUI only) to
|
|
19
|
+
Run `/topping-settings` (TUI only) to customize your toppings:
|
|
23
20
|
|
|
24
21
|
```
|
|
25
|
-
|
|
26
|
-
╟─ Preview
|
|
27
|
-
║
|
|
28
|
-
║
|
|
29
|
-
║
|
|
30
|
-
╟─
|
|
31
|
-
║
|
|
32
|
-
║ [■]
|
|
33
|
-
║ [■]
|
|
34
|
-
|
|
35
|
-
║
|
|
36
|
-
|
|
37
|
-
║ [■]
|
|
38
|
-
║
|
|
39
|
-
║ [
|
|
40
|
-
|
|
41
|
-
║
|
|
42
|
-
|
|
22
|
+
╔═┥ Pi Topping: Settings ┝═══════════════════════════════════╗
|
|
23
|
+
╟─ Preview ──────────────────────────────────────────────────╢
|
|
24
|
+
║ ║
|
|
25
|
+
║ Crafting (0m 03s · ↓ 84 tokens) ║
|
|
26
|
+
║ ║
|
|
27
|
+
╟─ User Prompt ──────────────────────────────────────────────╢
|
|
28
|
+
║ [■] High-vis prompt ON ║
|
|
29
|
+
║ [■] Border color ‹ accent › ║
|
|
30
|
+
║ [■] Pi icon ON ║
|
|
31
|
+
║ [■] Timestamp ON ║
|
|
32
|
+
║ ║
|
|
33
|
+
╟─ Working Loader Text ──────────────────────────────────────╢
|
|
34
|
+
║ [■] Animated spinner ON ║
|
|
35
|
+
║ ▸ [■] Animated spinner color ‹ accent › ║
|
|
36
|
+
║ [■] Randomize "Working" text ON ║
|
|
37
|
+
║ [■] Text shimmer ON ║
|
|
38
|
+
║ [■] Text shimmer direction ‹ Left to Right › ║
|
|
39
|
+
║ [■] Token activity monitor ON ║
|
|
40
|
+
║ [■] Token activity monitor color ‹ accent › ║
|
|
41
|
+
║ [■] Token activity monitor direction ‹ Left to Right › ║
|
|
42
|
+
║ [■] Token activity monitor dimmed OFF ║
|
|
43
|
+
║ [■] Elapsed time since prompt ON ║
|
|
44
|
+
║ [■] Show output tokens ON ║
|
|
45
|
+
║ ║
|
|
46
|
+
╟─ Completion Marker ────────────────────────────────────────╢
|
|
47
|
+
║ [■] Show completion marker ON ║
|
|
48
|
+
║ [■] Pi icon ON ║
|
|
49
|
+
║ [■] Randomize "Worked" text ON ║
|
|
50
|
+
║ [■] Tokens spent ON ║
|
|
51
|
+
║ ║
|
|
52
|
+
╟─ Options ──────────────────────────────────────────────────╢
|
|
53
|
+
║ [■] Use NerdFont icons ON ║
|
|
54
|
+
╟────────────────────────────────────────────────────────────╢
|
|
55
|
+
║ ↑↓ move ←→ select ␣ toggle ⏎ apply esc cancel ║
|
|
56
|
+
╚═════════════════════════════════════════════════════┥ 1/9 ┝╝
|
|
43
57
|
```
|
|
44
58
|
|
|
45
|
-
**Decorations** — animated spinner, word shimmer, activity meter.
|
|
46
|
-
|
|
47
|
-
**Options** — whether to substitute the random word at all, the elapsed timer, the token count, the completion marker, and the meter scrolling direction.
|
|
48
|
-
|
|
49
|
-
The menu has a live preview so you can see changes as you make them. Everything is on by default, so nothing changes unless you open the menu. Use `↑↓` to move, `␣` to toggle, `⏎` to save, `esc` to cancel. Settings are saved to `~/.pi/agent/pi-topping/settings.json`.
|
|
50
|
-
|
|
51
|
-
The toggle-menu component itself lives in `menu.ts` and is reusable by other extensions if you want a similar settings UI.
|
|
52
|
-
|
|
53
59
|
## Install
|
|
54
60
|
|
|
55
61
|
```bash
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** pi-topping extension entry point. */
|
|
2
2
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { SessionManager } from "./session.ts";
|
|
3
|
+
import { SessionManager } from "./src/session.ts";
|
|
4
4
|
|
|
5
5
|
export default function (pi: ExtensionAPI): void {
|
|
6
6
|
new SessionManager(pi).install();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@underactive/pi-topping",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A Pi extension that replaces the default \"Working...\" loader with a shimmering, randomized activity word, live elapsed timer, and output-token activity meter",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A Pi extension that decorates user prompts and replaces the default \"Working...\" loader with a shimmering, randomized activity word, live elapsed timer, and output-token activity meter",
|
|
5
5
|
"keywords": ["pi-package", "pi-extension"],
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Eric Sison",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
-
"files": ["
|
|
23
|
+
"files": ["index.ts", "src/**/*.ts", "README.md", "LICENSE"],
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "node --test tests/*.test.ts",
|
|
26
26
|
"typecheck": "tsc --noEmit"
|
|
@@ -117,12 +117,19 @@ export class ActivityMeter {
|
|
|
117
117
|
this.#levels.fill(0);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
/** Colorize a meter cell using the default theme mapping (dim at IDLE,
|
|
120
|
+
/** Colorize a meter cell using the default theme mapping (dim at IDLE, `color` otherwise). */
|
|
121
121
|
static colorizeCell(
|
|
122
122
|
level: ActivityMeterLevel,
|
|
123
123
|
char: string,
|
|
124
124
|
theme: { fg: (style: ThemeColor, s: string) => string },
|
|
125
|
+
color: ThemeColor = "accent",
|
|
126
|
+
dimmed?: boolean,
|
|
125
127
|
): string {
|
|
126
|
-
|
|
128
|
+
if (level === ActivityMeterLevel.IDLE) return theme.fg("dim", char);
|
|
129
|
+
const colored = theme.fg(color, char);
|
|
130
|
+
// ANSI dim (SGR 2) reduces color brightness; SGR 22 resets dim/bold.
|
|
131
|
+
// This is necessary because theme.fg("dim", ...) sets a gray color instead
|
|
132
|
+
// of applying the ANSI dim attribute, so nesting would overwrite the accent color.
|
|
133
|
+
return dimmed ? `\x1b[2m${colored}\x1b[22m` : colored;
|
|
127
134
|
}
|
|
128
135
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
/** Pi's default working-indicator frames (same braille spinner as pi-tui's Loader). */
|
|
4
|
+
export const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
5
|
+
|
|
3
6
|
/** Format an output-token count for the working indicator. */
|
|
4
7
|
export function formatTokens(count: number): string {
|
|
5
8
|
if (count < 1000) return count.toString();
|
|
@@ -67,29 +70,41 @@ export function buildWorkingMessage(
|
|
|
67
70
|
export function shimmerString(
|
|
68
71
|
text: string,
|
|
69
72
|
elapsedMs: number,
|
|
70
|
-
theme: Pick<Theme, "
|
|
73
|
+
theme: Pick<Theme, "getFgAnsi">,
|
|
74
|
+
direction: "ltr" | "rtl" = "ltr",
|
|
71
75
|
): string {
|
|
72
76
|
const chars = [...text];
|
|
73
77
|
if (chars.length === 0) return "";
|
|
74
78
|
const SHIMMER_SWEEP_S = 2.0;
|
|
75
79
|
const SHIMMER_BAND_HALF = 5.0;
|
|
76
80
|
const SHIMMER_PADDING = 10;
|
|
81
|
+
const SHIMMER_BASE = ansiToRgb(theme.getFgAnsi("dim"));
|
|
82
|
+
const SHIMMER_HIGHLIGHT = ansiToRgb(theme.getFgAnsi("text"));
|
|
77
83
|
const period = chars.length + SHIMMER_PADDING * 2;
|
|
78
84
|
const elapsedS = elapsedMs / 1000;
|
|
79
|
-
const
|
|
85
|
+
const sweep = ((elapsedS % SHIMMER_SWEEP_S) / SHIMMER_SWEEP_S) * period;
|
|
86
|
+
const pos = direction === "rtl" ? period - sweep : sweep;
|
|
80
87
|
|
|
81
88
|
return chars
|
|
82
89
|
.map((ch, i) => {
|
|
83
|
-
const dist = Math.abs(i + SHIMMER_PADDING - pos);
|
|
84
|
-
const
|
|
85
|
-
dist
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
const dist = Math.abs((i + SHIMMER_PADDING) - pos);
|
|
91
|
+
const t = dist <= SHIMMER_BAND_HALF
|
|
92
|
+
? 0.5 * (1 + Math.cos(Math.PI * dist / SHIMMER_BAND_HALF))
|
|
93
|
+
: 0;
|
|
94
|
+
const alpha = t * 0.9;
|
|
95
|
+
const r = Math.round(SHIMMER_HIGHLIGHT[0] * alpha + SHIMMER_BASE[0] * (1 - alpha));
|
|
96
|
+
const g = Math.round(SHIMMER_HIGHLIGHT[1] * alpha + SHIMMER_BASE[1] * (1 - alpha));
|
|
97
|
+
const b = Math.round(SHIMMER_HIGHLIGHT[2] * alpha + SHIMMER_BASE[2] * (1 - alpha));
|
|
98
|
+
const bold = t > 0.2 ? "\x1b[1m" : "";
|
|
99
|
+
return `${bold}\x1b[38;2;${r};${g};${b}m${ch}\x1b[22m`;
|
|
91
100
|
})
|
|
92
|
-
.join("");
|
|
101
|
+
.join("") + "\x1b[0m";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function ansiToRgb(ansi: string): [number, number, number] {
|
|
105
|
+
const match = ansi.match(/^\x1b\[38;2;(\d+);(\d+);(\d+)m$/);
|
|
106
|
+
if (!match) throw new Error(`Shimmer requires truecolor theme colors, received ${JSON.stringify(ansi)}`);
|
|
107
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
/**
|