@henryqw/pi-footer 0.2.2 → 0.4.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 CHANGED
@@ -23,17 +23,18 @@ pi install npm:@henryqw/pi-footer
23
23
 
24
24
  ```text
25
25
  pi-packages · clear-field-f8d2 · PR #123 · approved
26
- ↑ 12.4k · ↓ 2.1k · ↺ 84.3% · $ 0.127 · ◔ 36.8% gpt-5.6-luna • high
26
+ ↑ 12.4k · ↓ 2.1k · ↺ 84.3% · ⚡ 87.4 t/s · $ 0.127 · ◔ 36.8% gpt-5.6-luna • high
27
27
  Codex #1 · 50% · 7d 1d 1h 22m
28
+ ● 🐴 ponytail: ⚡ FULL
28
29
  ```
29
30
 
30
31
  First line shows repository, branch, and pull request status (`pi-pr`) after clickable checkout link. Linked-worktree branches drop generated `worktree/` prefix.
31
32
 
32
- Second line shows cumulative input tokens, output tokens, latest cache-hit rate, estimated cost, and context usage. Unavailable values render as `—` without a misleading percent sign. Active model and thinking level are right-aligned.
33
+ Second line shows cumulative input tokens, output tokens, latest cache-hit rate, tokens per second of the most recent assistant response, estimated cost, and context usage. Unavailable values render as `—` without a misleading percent sign. Active model and thinking level are right-aligned.
33
34
 
34
35
  `off` uses the same dim grey as the model name. Active levels use an ANSI-256 gradient: green `minimal`, yellow-green `low`, lime `medium`, yellow `high`, orange `xhigh`, and red `max`. `ultra` renders as a rainbow when the runtime supplies it. Pi 0.84.2 does not yet accept `ultra`, so that footer path remains unreachable until Pi adds it.
35
36
 
36
- Final line renders every non-empty status emitted through `ctx.ui.setStatus()`, sorted by status key (excluding `pi-pr`, which appears on the first line). Producer text, spacing, colors, links, and glyphs are preserved, including Ponytail mode and Codex quota.
37
+ Third line is reserved for non-empty statuses from `@henryqw` extensions, currently Codex quota. Fourth line renders statuses from all other extensions, including Ponytail and `pi-rewind`. If only external statuses exist, third line stays blank so external output remains on line four. Statuses are sorted by key; producer text, spacing, colors, links, and glyphs are preserved.
37
38
 
38
39
  ## Clickable checkout
39
40
 
@@ -12,6 +12,7 @@ const THINKING_COLORS = {
12
12
  xhigh: 208,
13
13
  max: 196,
14
14
  } as const;
15
+ const HENRY_STATUS_KEYS = new Set(["pi-multi-codex"]);
15
16
 
16
17
  function formatTokens(count: number): string {
17
18
  if (count < 1_000) return `${count}`;
@@ -53,6 +54,19 @@ export default function footerExtension(pi: ExtensionAPI): void {
53
54
  const commonName = commonDir && basename(commonDir) === ".git" ? basename(dirname(commonDir)) : undefined;
54
55
  const repo = git.code === 0 ? commonName && commonName !== rootName ? commonName : rootName : basename(ctx.cwd);
55
56
 
57
+ let tps: number | undefined;
58
+ let assistantStartedAt: number | undefined;
59
+ pi.on("message_start", async (event) => {
60
+ if (event.message.role === "assistant") assistantStartedAt = performance.now();
61
+ });
62
+ pi.on("message_end", async (event) => {
63
+ if (event.message.role !== "assistant") return;
64
+ const output = event.message.usage?.output ?? 0;
65
+ const seconds = assistantStartedAt === undefined ? 0 : (performance.now() - assistantStartedAt) / 1000;
66
+ assistantStartedAt = undefined;
67
+ tps = seconds > 0 ? output / seconds : undefined;
68
+ });
69
+
56
70
  // ponytail: keyed on length + last entry (sessions are append-only); revisit if entries ever mutate in place.
57
71
  let usageKey: string | undefined;
58
72
  let input = 0;
@@ -106,8 +120,14 @@ export default function footerExtension(pi: ExtensionAPI): void {
106
120
  const statuses = [...extensionStatuses]
107
121
  .filter(([key]) => key !== "pi-pr")
108
122
  .sort(([a], [b]) => a.localeCompare(b))
109
- .map(([, text]) => sanitizeStatus(text))
110
- .filter(Boolean);
123
+ .map(([key, text]) => [key, sanitizeStatus(text)] as const)
124
+ .filter(([, text]) => Boolean(text));
125
+ const henryStatuses = statuses
126
+ .filter(([key]) => HENRY_STATUS_KEYS.has(key))
127
+ .map(([, text]) => text);
128
+ const externalStatuses = statuses
129
+ .filter(([key]) => !HENRY_STATUS_KEYS.has(key))
130
+ .map(([, text]) => text);
111
131
  const thinking = String(ctx.thinkingLevel ?? "off");
112
132
  const thinkingColor = THINKING_COLORS[thinking as keyof typeof THINKING_COLORS];
113
133
  const ellipsis = theme.fg("dim", "…");
@@ -115,7 +135,8 @@ export default function footerExtension(pi: ExtensionAPI): void {
115
135
  `↑ ${formatTokens(input)}`,
116
136
  `↓ ${formatTokens(output)}`,
117
137
  `↺ ${cacheRate === undefined ? "—" : `${cacheRate.toFixed(1)}%`}`,
118
- `$ ${cost.toFixed(3)}`,
138
+ `⚡ ${tps === undefined ? "—" : `${tps.toFixed(1)} t/s`}`,
139
+ `$ ${cost.toFixed(3)}`,
119
140
  `◔ ${context == null ? "—" : `${context.toFixed(1)}%`}`,
120
141
  ].join(" · "));
121
142
  const thinkingText = thinking === "ultra"
@@ -130,7 +151,8 @@ export default function footerExtension(pi: ExtensionAPI): void {
130
151
  firstLine,
131
152
  align(usage, model, width, ellipsis),
132
153
  ];
133
- if (statuses.length) lines.push(statuses.join(" "));
154
+ if (henryStatuses.length || externalStatuses.length) lines.push(henryStatuses.join(" "));
155
+ if (externalStatuses.length) lines.push(externalStatuses.join(" "));
134
156
  return lines.map((line) => truncateToWidth(line, width, ellipsis));
135
157
  },
136
158
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-footer",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "description": "Show concise repository, branch, and usage details in the Pi footer.",
5
5
  "keywords": [
6
6
  "pi-package",