pi-idle 1.0.4 → 1.0.6
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 +6 -0
- package/assets/idle.png +0 -0
- package/assets/spinner.png +0 -0
- package/assets/spinner.svg +5 -0
- package/package.json +11 -6
- package/pi-idle.ts +35 -12
package/README.md
CHANGED
|
@@ -16,6 +16,12 @@ A [pi](https://github.com/earendil-works/pi-coding-agent) extension that puts a
|
|
|
16
16
|
- `≤ 50 %` → percentage hidden; `> 50 %` → `[N%]`; `≥ 90 %` → `![N%]!` (high-usage warning).
|
|
17
17
|
- ANSI colours don't work inside terminal-title OSC sequences, so everything is plain text.
|
|
18
18
|
|
|
19
|
+
## Screenshots
|
|
20
|
+
|
|
21
|
+
| Working | Idle |
|
|
22
|
+
|---|---|
|
|
23
|
+
|  |  |
|
|
24
|
+
|
|
19
25
|
## Install
|
|
20
26
|
|
|
21
27
|
From npm:
|
package/assets/idle.png
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
|
2
|
+
<text x="64" y="90" font-size="96" font-family="sans-serif" fill="#bd93f9" text-anchor="middle">
|
|
3
|
+
<animate attributeName="textContent" values="◰;◳;◲;◱;◰" keyTimes="0;0.25;0.5;0.75;1" dur="1s" repeatCount="indefinite" calcMode="discrete" />
|
|
4
|
+
</text>
|
|
5
|
+
</svg>
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-idle",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Pi extension: shows ✓ in terminal title when idle, spinner (◰◳◲◱) while working, with context-usage percentage beside the checkmark",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"idle",
|
|
7
|
+
"done",
|
|
8
|
+
"spinner",
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"pi-package"
|
|
11
|
+
],
|
|
6
12
|
"type": "module",
|
|
7
13
|
"license": "MIT",
|
|
8
14
|
"repository": {
|
|
@@ -17,10 +23,9 @@
|
|
|
17
23
|
"node": ">=22.0.0"
|
|
18
24
|
},
|
|
19
25
|
"pi": {
|
|
20
|
-
"extensions": [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
26
|
+
"extensions": [
|
|
27
|
+
"./pi-idle.ts"
|
|
28
|
+
]
|
|
24
29
|
},
|
|
25
30
|
"devDependencies": {
|
|
26
31
|
"vitest": "^3.2.4"
|
package/pi-idle.ts
CHANGED
|
@@ -57,6 +57,8 @@ function getContextIndicator(ctx: ExtensionContext): string {
|
|
|
57
57
|
export default function (pi: ExtensionAPI) {
|
|
58
58
|
let timer: ReturnType<typeof setInterval> | null = null;
|
|
59
59
|
let frameIndex = 0;
|
|
60
|
+
let spinnerActive = false;
|
|
61
|
+
let currentCtx: ExtensionContext | null = null;
|
|
60
62
|
|
|
61
63
|
// ── Internal helpers ───────────────────────────────────────
|
|
62
64
|
|
|
@@ -65,7 +67,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
65
67
|
clearInterval(timer);
|
|
66
68
|
timer = null;
|
|
67
69
|
}
|
|
70
|
+
spinnerActive = false;
|
|
68
71
|
frameIndex = 0;
|
|
72
|
+
currentCtx = null;
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
/** Write the idle title (plain text — colours don't work in OSC titles). */
|
|
@@ -77,16 +81,35 @@ export default function (pi: ExtensionAPI) {
|
|
|
77
81
|
ctx.ui.setTitle(`✓${spacer}${indicator} ${baseTitle}`);
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function showSpinnerFrame(ctx: ExtensionContext) {
|
|
85
|
+
const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
|
|
86
|
+
const baseTitle = getBaseTitle(pi);
|
|
87
|
+
ctx.ui.setTitle(`${frame} ${baseTitle}`);
|
|
88
|
+
frameIndex++;
|
|
89
|
+
}
|
|
90
|
+
|
|
80
91
|
/** Start the spinner in the title. No context percentage — it only appears with the checkmark. */
|
|
81
92
|
function startSpinner(ctx: ExtensionContext) {
|
|
82
|
-
|
|
93
|
+
// Don't restart if already spinning — avoids race conditions and reduces CPU
|
|
94
|
+
if (spinnerActive) {
|
|
95
|
+
frameIndex = 0;
|
|
96
|
+
currentCtx = ctx;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
spinnerActive = true;
|
|
101
|
+
currentCtx = ctx;
|
|
102
|
+
frameIndex = 0;
|
|
103
|
+
|
|
104
|
+
// Show first frame immediately so user sees spinner right away.
|
|
105
|
+
showSpinnerFrame(ctx);
|
|
83
106
|
|
|
84
107
|
timer = setInterval(() => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
if (currentCtx) {
|
|
109
|
+
showSpinnerFrame(currentCtx);
|
|
110
|
+
}
|
|
111
|
+
}, 2000);
|
|
112
|
+
(timer as ReturnType<typeof setInterval> & { unref?: () => void }).unref?.();
|
|
90
113
|
}
|
|
91
114
|
|
|
92
115
|
// ── Lifecycle hooks ────────────────────────────────────────
|
|
@@ -98,29 +121,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
98
121
|
showDone(ctx);
|
|
99
122
|
});
|
|
100
123
|
|
|
101
|
-
pi.on("input",
|
|
124
|
+
pi.on("input", (event, ctx) => {
|
|
102
125
|
if (event.source === "interactive") {
|
|
103
126
|
startSpinner(ctx);
|
|
104
127
|
}
|
|
105
128
|
});
|
|
106
129
|
|
|
107
|
-
pi.on("agent_start",
|
|
130
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
108
131
|
// agent_start always fires after input for every user prompt;
|
|
109
132
|
// backstop in case the input handler missed a non-interactive source.
|
|
110
133
|
startSpinner(ctx);
|
|
111
134
|
});
|
|
112
135
|
|
|
113
|
-
pi.on("turn_start",
|
|
136
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
114
137
|
// Multi-turn agent: keep spinner running between turns.
|
|
115
138
|
startSpinner(ctx);
|
|
116
139
|
});
|
|
117
140
|
|
|
118
|
-
pi.on("agent_end",
|
|
141
|
+
pi.on("agent_end", (_event, ctx) => {
|
|
119
142
|
showDone(ctx);
|
|
120
143
|
});
|
|
121
144
|
|
|
122
|
-
pi.on("session_shutdown",
|
|
145
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
123
146
|
stopSpinner();
|
|
124
147
|
ctx.ui.setTitle(getBaseTitle(pi));
|
|
125
|
-
})
|
|
148
|
+
});
|
|
126
149
|
}
|