@henryqw/pi-footer 0.2.0 → 0.2.2

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
@@ -22,18 +22,18 @@ pi install npm:@henryqw/pi-footer
22
22
  | Footer | UI | Show checkout, usage, model, thinking, and extension statuses. |
23
23
 
24
24
  ```text
25
- pi-packages · clear-field-f8d2
25
+ pi-packages · clear-field-f8d2 · PR #123 · approved
26
26
  ↑ 12.4k · ↓ 2.1k · ↺ 84.3% · $ 0.127 · ◔ 36.8% gpt-5.6-luna • high
27
27
  Codex #1 · 50% · 7d 1d 1h 22m
28
28
  ```
29
29
 
30
- First line shows repository and branch. Linked-worktree branches drop generated `worktree/` prefix.
30
+ First line shows repository, branch, and pull request status (`pi-pr`) after clickable checkout link. Linked-worktree branches drop generated `worktree/` prefix.
31
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
33
 
34
34
  `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
35
 
36
- Final line renders every non-empty status emitted through `ctx.ui.setStatus()`, sorted by status key. Producer text, spacing, colors, links, and glyphs are preserved, including Ponytail mode, GitHub PR state, and Codex quota.
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
37
 
38
38
  ## Clickable checkout
39
39
 
@@ -53,40 +53,58 @@ export default function footerExtension(pi: ExtensionAPI): void {
53
53
  const commonName = commonDir && basename(commonDir) === ".git" ? basename(dirname(commonDir)) : undefined;
54
54
  const repo = git.code === 0 ? commonName && commonName !== rootName ? commonName : rootName : basename(ctx.cwd);
55
55
 
56
+ // ponytail: keyed on length + last entry (sessions are append-only); revisit if entries ever mutate in place.
57
+ let usageKey: string | undefined;
58
+ let input = 0;
59
+ let output = 0;
60
+ let cost = 0;
61
+ let cacheRate: number | undefined;
62
+ const computeUsage = () => {
63
+ input = 0;
64
+ output = 0;
65
+ cost = 0;
66
+ cacheRate = undefined;
67
+ const add = (usage: Usage | undefined) => {
68
+ if (!usage) return;
69
+ input += usage.input;
70
+ output += usage.output;
71
+ cost += usage.cost.total;
72
+ };
73
+
74
+ for (const entry of ctx.sessionManager.getEntries()) {
75
+ if (entry.type === "message" && entry.message.role === "assistant") {
76
+ const usage = entry.message.usage;
77
+ const prompt = usage.input + usage.cacheRead + usage.cacheWrite;
78
+ cacheRate = prompt ? usage.cacheRead / prompt * 100 : 0;
79
+ add(usage);
80
+ } else if (entry.type === "message" && entry.message.role === "toolResult") {
81
+ add(entry.message.usage);
82
+ } else if (entry.type === "branch_summary" || entry.type === "compaction") {
83
+ add(entry.usage);
84
+ }
85
+ }
86
+ };
87
+
56
88
  ctx.ui.setFooter((tui, theme, data) => {
57
89
  const unsubscribe = data.onBranchChange(() => tui.requestRender());
58
90
  return {
59
91
  dispose: unsubscribe,
60
92
  invalidate() { },
61
93
  render(width: number): string[] {
62
- let input = 0;
63
- let output = 0;
64
- let cost = 0;
65
- let cacheRate: number | undefined;
66
- const add = (usage: Usage | undefined) => {
67
- if (!usage) return;
68
- input += usage.input;
69
- output += usage.output;
70
- cost += usage.cost.total;
71
- };
72
-
73
- for (const entry of ctx.sessionManager.getEntries()) {
74
- if (entry.type === "message" && entry.message.role === "assistant") {
75
- const usage = entry.message.usage;
76
- const prompt = usage.input + usage.cacheRead + usage.cacheWrite;
77
- cacheRate = prompt ? usage.cacheRead / prompt * 100 : 0;
78
- add(usage);
79
- } else if (entry.type === "message" && entry.message.role === "toolResult") {
80
- add(entry.message.usage);
81
- } else if (entry.type === "branch_summary" || entry.type === "compaction") {
82
- add(entry.usage);
83
- }
94
+ const entries = ctx.sessionManager.getEntries();
95
+ const key = `${entries.length}:${entries.at(-1)?.type}`;
96
+ if (key !== usageKey) {
97
+ usageKey = key;
98
+ computeUsage();
84
99
  }
85
100
 
86
101
  const branch = data.getGitBranch()?.replace(/^worktree\//, "");
87
102
  const context = ctx.getContextUsage()?.percent;
88
103
  const openUri = configuredOpenUri(ctx.cwd);
89
- const statuses = [...data.getExtensionStatuses()]
104
+ const extensionStatuses = data.getExtensionStatuses();
105
+ const prStatus = sanitizeStatus(extensionStatuses.get("pi-pr") ?? "");
106
+ const statuses = [...extensionStatuses]
107
+ .filter(([key]) => key !== "pi-pr")
90
108
  .sort(([a], [b]) => a.localeCompare(b))
91
109
  .map(([, text]) => sanitizeStatus(text))
92
110
  .filter(Boolean);
@@ -106,8 +124,10 @@ export default function footerExtension(pi: ExtensionAPI): void {
106
124
  const model = theme.fg("dim", `${ctx.model?.id ?? "no-model"} • `) + thinkingText;
107
125
  const identity = branch ? theme.fg("dim", `${repo} · `) : "";
108
126
  const checkout = branch ?? repo;
127
+ const checkoutLink = openUri ? hyperlink(theme.fg("accent", checkout), openUri) : theme.fg("dim", checkout);
128
+ const firstLine = prStatus ? `${identity}${checkoutLink} · ${prStatus}` : `${identity}${checkoutLink}`;
109
129
  const lines = [
110
- identity + (openUri ? hyperlink(theme.fg("accent", checkout), openUri) : theme.fg("dim", checkout)),
130
+ firstLine,
111
131
  align(usage, model, width, ellipsis),
112
132
  ];
113
133
  if (statuses.length) lines.push(statuses.join(" "));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-footer",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Show concise repository, branch, and usage details in the Pi footer.",
5
5
  "keywords": [
6
6
  "pi-package",