@zigai/pi-footer 0.1.6 → 0.1.7
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 +9 -2
- package/package.json +2 -2
- package/src/constants.ts +0 -2
- package/src/index.ts +0 -79
- package/src/worked-for-widget.ts +0 -43
package/README.md
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# Pi Footer
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@zigai/pi-footer)
|
|
4
|
+
[](https://www.npmjs.com/package/@zigai/pi-footer)
|
|
5
|
+
[](../../LICENSE)
|
|
6
|
+
|
|
3
7
|
This Pi extension replaces Pi's footer with a single compact plain-text status line.
|
|
4
8
|
|
|
5
9
|

|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
Footer contents:
|
|
8
12
|
|
|
9
13
|
- current working directory
|
|
10
14
|
- git branch
|
|
@@ -12,10 +16,13 @@ The footer keeps key session information visible without taking much space:
|
|
|
12
16
|
- thinking level
|
|
13
17
|
- MCP status
|
|
14
18
|
- context usage
|
|
15
|
-
- a short post-run summary with total agent time and output token speed
|
|
16
19
|
|
|
17
20
|
## Install
|
|
18
21
|
|
|
19
22
|
```sh
|
|
20
23
|
pi install npm:@zigai/pi-footer
|
|
21
24
|
```
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zigai/pi-footer",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Pi package
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "Pi package that replaces Pi's footer with a compact status line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"footer",
|
|
7
7
|
"pi",
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
import { createFooterComponent } from "./footer-rendering.ts";
|
|
4
|
-
import { formatDuration, setWorkedForWidget } from "./worked-for-widget.ts";
|
|
5
4
|
|
|
6
5
|
export default function uiEnhancements(pi: ExtensionAPI) {
|
|
7
|
-
let agentStartedAt: number | undefined;
|
|
8
|
-
let messageStart: number | undefined;
|
|
9
|
-
let streamStart: number | undefined;
|
|
10
|
-
let estimatedStreamedTokens = 0;
|
|
11
|
-
let totalOutputTokens = 0;
|
|
12
|
-
let totalStreamMs = 0;
|
|
13
|
-
|
|
14
6
|
const installFooter = (ctx: ExtensionContext) => {
|
|
15
7
|
ctx.ui.setFooter((tui, _theme, footerData) =>
|
|
16
8
|
createFooterComponent(
|
|
@@ -24,80 +16,9 @@ export default function uiEnhancements(pi: ExtensionAPI) {
|
|
|
24
16
|
|
|
25
17
|
pi.on("session_start", async (_event, ctx) => {
|
|
26
18
|
installFooter(ctx);
|
|
27
|
-
setWorkedForWidget(ctx, undefined);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
pi.on("agent_start", async (_event, ctx) => {
|
|
31
|
-
agentStartedAt = Date.now();
|
|
32
|
-
messageStart = undefined;
|
|
33
|
-
streamStart = undefined;
|
|
34
|
-
estimatedStreamedTokens = 0;
|
|
35
|
-
totalOutputTokens = 0;
|
|
36
|
-
totalStreamMs = 0;
|
|
37
|
-
setWorkedForWidget(ctx, undefined);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
pi.on("message_start", async (event) => {
|
|
41
|
-
if (event.message.role !== "assistant") return;
|
|
42
|
-
messageStart = Date.now();
|
|
43
|
-
streamStart = undefined;
|
|
44
|
-
estimatedStreamedTokens = 0;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
pi.on("message_update", async (event) => {
|
|
48
|
-
if (event.message.role !== "assistant") return;
|
|
49
|
-
|
|
50
|
-
const streamEvent = event.assistantMessageEvent;
|
|
51
|
-
const isOutputDelta =
|
|
52
|
-
streamEvent.type === "text_delta" ||
|
|
53
|
-
streamEvent.type === "thinking_delta" ||
|
|
54
|
-
streamEvent.type === "toolcall_delta";
|
|
55
|
-
if (!isOutputDelta) return;
|
|
56
|
-
|
|
57
|
-
streamStart ??= Date.now();
|
|
58
|
-
estimatedStreamedTokens += Math.max(0, streamEvent.delta.length / 4);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
pi.on("message_end", async (event) => {
|
|
62
|
-
if (event.message.role !== "assistant") return;
|
|
63
|
-
|
|
64
|
-
const outputTokens = event.message.usage.output;
|
|
65
|
-
const timingStart = streamStart ?? messageStart;
|
|
66
|
-
if (timingStart === undefined || outputTokens <= 0) {
|
|
67
|
-
messageStart = undefined;
|
|
68
|
-
streamStart = undefined;
|
|
69
|
-
estimatedStreamedTokens = 0;
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
totalOutputTokens += outputTokens;
|
|
74
|
-
totalStreamMs += Math.max(0, Date.now() - timingStart);
|
|
75
|
-
|
|
76
|
-
messageStart = undefined;
|
|
77
|
-
streamStart = undefined;
|
|
78
|
-
estimatedStreamedTokens = 0;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
pi.on("agent_end", async (_event, ctx) => {
|
|
82
|
-
if (agentStartedAt === undefined) return;
|
|
83
|
-
const duration = Date.now() - agentStartedAt;
|
|
84
|
-
const elapsedSeconds = totalStreamMs / 1000;
|
|
85
|
-
let tokensPerSecond: number | undefined;
|
|
86
|
-
if (totalOutputTokens > 0 && elapsedSeconds > 0) {
|
|
87
|
-
tokensPerSecond = Math.round(totalOutputTokens / elapsedSeconds);
|
|
88
|
-
}
|
|
89
|
-
agentStartedAt = undefined;
|
|
90
|
-
setWorkedForWidget(ctx, formatDuration(duration), tokensPerSecond);
|
|
91
19
|
});
|
|
92
20
|
|
|
93
21
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
94
|
-
agentStartedAt = undefined;
|
|
95
|
-
messageStart = undefined;
|
|
96
|
-
streamStart = undefined;
|
|
97
|
-
estimatedStreamedTokens = 0;
|
|
98
|
-
totalOutputTokens = 0;
|
|
99
|
-
totalStreamMs = 0;
|
|
100
22
|
ctx.ui.setFooter(undefined);
|
|
101
|
-
setWorkedForWidget(ctx, undefined);
|
|
102
23
|
});
|
|
103
24
|
}
|
package/src/worked-for-widget.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
-
|
|
4
|
-
import { WIDGET_KEY } from "./constants.ts";
|
|
5
|
-
|
|
6
|
-
export function formatDuration(ms: number): string {
|
|
7
|
-
const wholeSeconds = Math.max(0, Math.round(ms / 1000));
|
|
8
|
-
if (wholeSeconds < 60) return `${wholeSeconds}s`;
|
|
9
|
-
|
|
10
|
-
const minutes = Math.floor(wholeSeconds / 60);
|
|
11
|
-
const remainingSeconds = wholeSeconds % 60;
|
|
12
|
-
if (minutes < 60) return `${minutes}m ${remainingSeconds.toString().padStart(2, "0")}s`;
|
|
13
|
-
|
|
14
|
-
const hours = Math.floor(minutes / 60);
|
|
15
|
-
const remainingMinutes = minutes % 60;
|
|
16
|
-
return `${hours}h ${remainingMinutes.toString().padStart(2, "0")}m`;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function setWorkedForWidget(
|
|
20
|
-
ctx: ExtensionContext,
|
|
21
|
-
workedForText?: string,
|
|
22
|
-
tokensPerSecond?: number,
|
|
23
|
-
): void {
|
|
24
|
-
if (ctx.hasUI !== true) return;
|
|
25
|
-
|
|
26
|
-
if (workedForText === undefined || workedForText.length === 0) {
|
|
27
|
-
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({
|
|
32
|
-
render(width: number): string[] {
|
|
33
|
-
if (width <= 0) return [""];
|
|
34
|
-
let text = `Worked for ${workedForText}.`;
|
|
35
|
-
if (tokensPerSecond !== undefined && tokensPerSecond > 0) {
|
|
36
|
-
text = `${text} [${tokensPerSecond} tok/s]`;
|
|
37
|
-
}
|
|
38
|
-
const truncated = truncateToWidth(text, Math.max(0, width - 1), "");
|
|
39
|
-
return [theme.fg("dim", ` ${truncated}`)];
|
|
40
|
-
},
|
|
41
|
-
invalidate() {},
|
|
42
|
-
}));
|
|
43
|
-
}
|