@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 CHANGED
@@ -1,10 +1,14 @@
1
1
  # Pi Footer
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@zigai/pi-footer.svg?color=blue)](https://www.npmjs.com/package/@zigai/pi-footer)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@zigai/pi-footer.svg)](https://www.npmjs.com/package/@zigai/pi-footer)
5
+ [![license](https://img.shields.io/npm/l/@zigai/pi-footer.svg)](../../LICENSE)
6
+
3
7
  This Pi extension replaces Pi's footer with a single compact plain-text status line.
4
8
 
5
9
  ![Pi Footer screenshot](assets/footer.png)
6
10
 
7
- The footer keeps key session information visible without taking much space:
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.6",
4
- "description": "Pi package for a custom footer and worked-for token speed widget.",
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
@@ -1,5 +1,3 @@
1
- export const WIDGET_KEY = "worked-for-widget";
2
-
3
1
  export const ACTIVE_FOOTER_VARIANT = "plain" as const;
4
2
  export const BRANCH_ICON = "";
5
3
 
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
  }
@@ -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
- }