@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 +3 -3
- package/extensions/footer.ts +44 -24
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
package/extensions/footer.ts
CHANGED
|
@@ -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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
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
|
-
|
|
130
|
+
firstLine,
|
|
111
131
|
align(usage, model, width, ellipsis),
|
|
112
132
|
];
|
|
113
133
|
if (statuses.length) lines.push(statuses.join(" "));
|