pi-minimal-footer 0.3.3 → 0.4.1
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/CHANGELOG.md +25 -0
- package/README.md +34 -11
- package/VISION.md +3 -3
- package/extensions/index.ts +16 -6
- package/extensions/lib/usage-limits.ts +7 -3
- package/extensions/lib/usage-providers.ts +3 -0
- package/media/github-preview.png +0 -0
- package/media/preview.png +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.1] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Restructure README with separate left/right feature lists, clearer context and subscription usage guidance, explicit adapter verification limits, and source installation under development
|
|
15
|
+
- Refresh the vision contract for the two-cell baseline, bracketed bars, and nine-second timeout
|
|
16
|
+
- Explain cache retention, refresh jitter, exact-model quota selection, and readability thresholds with rationale comments; update the source footer example to show brackets
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Restore reset-label-only usage on terminals too narrow for a bracketed cell, with regression coverage for reset-only, hidden-usage, and one-cell width boundaries
|
|
21
|
+
- Align usage detection and regression expectations with bracketed bars, the two-cell baseline, bracket-aware width allocation, and ten-cell rounding for small percentages
|
|
22
|
+
|
|
23
|
+
## [0.4.0] - 2026-10-07
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- `minFooter.maxUsageBarCells` (default `10`) to cap the subscription usage bar length; use a positive integer such as `4` for a compact bar, excluding brackets and reset time
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Reduce the compact subscription usage bar baseline from five to two cells before expanding into spare space up to the configured cap
|
|
32
|
+
- Add one space at each footer edge, reserving those columns when fitting narrow terminals
|
|
33
|
+
- Refresh README and package-gallery PNG previews with the current footer, edge padding, and live OpenAI Codex subscription usage
|
|
34
|
+
|
|
10
35
|
## [0.3.3] - 2026-10-07
|
|
11
36
|
|
|
12
37
|
### Changed
|
package/README.md
CHANGED
|
@@ -9,18 +9,26 @@
|
|
|
9
9
|
|
|
10
10
|
An opinionated, compact one-line footer for [Pi](https://github.com/earendil-works/pi).
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
![Footer preview: ~/git/project main 🧠 Karpathy 🪽 Icarus model · 42/200k · [⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀]↻1d8h](media/github-preview.png)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
**Left**
|
|
15
|
+
|
|
16
|
+
- Working directory: `~/git/project`
|
|
17
|
+
- Git branch: ` main`
|
|
18
|
+
|
|
19
|
+
**Right**
|
|
20
|
+
|
|
21
|
+
- Extension statuses: ` 🧠 Karpathy 🪽 Icarus`
|
|
22
|
+
- Model: `model`
|
|
23
|
+
- Context usage: `42/200k`
|
|
24
|
+
- Subscription usage bar with reset time: `[⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀]↻1d8h`
|
|
15
25
|
|
|
16
26
|
## Install
|
|
17
27
|
|
|
18
|
-
Requires Node.js >=22.19.0.
|
|
28
|
+
Requires Node.js >=22.19.0. Interactive terminal UI only.
|
|
19
29
|
|
|
20
30
|
```bash
|
|
21
31
|
pi install npm:pi-minimal-footer
|
|
22
|
-
# Or install from source:
|
|
23
|
-
pi install git:github.com/Ryu-CZ/pi-minimal-footer
|
|
24
32
|
```
|
|
25
33
|
|
|
26
34
|
## Commands
|
|
@@ -44,27 +52,42 @@ Edit `minFooter` in Pi's agent `settings.json` (normally `~/.pi/agent/settings.j
|
|
|
44
52
|
"showPath": true,
|
|
45
53
|
"showModel": true,
|
|
46
54
|
"showContext": true,
|
|
47
|
-
"powerlineSeparator": true
|
|
55
|
+
"powerlineSeparator": true,
|
|
56
|
+
"maxUsageBarCells": 10
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
```
|
|
51
60
|
|
|
52
61
|
- `showSkills` shows **extension status text**, not installed skills. The footer does not add the example statuses itself.
|
|
53
62
|
- Set `powerlineSeparator` to `false` for plain spaces instead of `` / `` if your font lacks those glyphs. The git icon `` also needs a compatible font; hide it with `showGitBranch: false` if needed.
|
|
54
|
-
-
|
|
63
|
+
- `maxUsageBarCells` caps the Braille bar length (excluding brackets and reset time). Use a positive integer, e.g. `4` for a compact bar; invalid values fall back to `10`.
|
|
64
|
+
|
|
65
|
+
## Reading the footer
|
|
66
|
+
|
|
67
|
+
### Context
|
|
55
68
|
|
|
56
|
-
|
|
69
|
+
`42/200k` means tokens used / context window. `?` means Pi has not reported usage yet, including immediately after compaction.
|
|
57
70
|
|
|
58
|
-
|
|
71
|
+
### Subscription usage
|
|
59
72
|
|
|
60
|
-
|
|
73
|
+
`[⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀]↻1d8h` shows **subscription allowance used**, not context usage.
|
|
74
|
+
|
|
75
|
+
- **Bar:** expands into spare space, using 2–10 cells by default, capped by `maxUsageBarCells`. Smaller caps and very narrow terminals may show fewer cells or only the reset label.
|
|
76
|
+
- **Colors:** green below 85%, amber from 85%, red from 92%.
|
|
77
|
+
- **Cached data:** dim after a refresh failure.
|
|
61
78
|
- Reset: local `↻HH:mm` through 24 hours; whole days/hours above 24 hours (`↻1d8h`); whole days above 10 days (`↻12d`). Remaining durations round down. No reset reported means bar only.
|
|
62
79
|
- Shows the shortest available usage window for the selected provider. Missing credentials, unsupported/local models, or absent limits hide the usage bar. Custom proxy endpoints are not polled.
|
|
63
80
|
|
|
64
|
-
|
|
81
|
+
Implemented adapters: **OpenAI Codex, Claude OAuth, GitHub Copilot, Gemini CLI, MiniMax, Kimi Coding, and OpenCode Go**. Uses Pi's existing credentials; ordinary Claude API keys do not expose subscription usage. Authenticated endpoints remain unverified live; see [verification limits](VISION.md).
|
|
65
82
|
|
|
66
83
|
## Development & design
|
|
67
84
|
|
|
85
|
+
Install from source:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pi install git:github.com/Ryu-CZ/pi-minimal-footer
|
|
89
|
+
```
|
|
90
|
+
|
|
68
91
|
See [VISION.md](VISION.md) for design constraints, local development, provider details, and verification limits. Release history: [CHANGELOG.md](CHANGELOG.md).
|
|
69
92
|
|
|
70
93
|
## Credits
|
package/VISION.md
CHANGED
|
@@ -18,9 +18,9 @@ The [README](README.md) is the user manual. This document records current design
|
|
|
18
18
|
|
|
19
19
|
Reserve the usage bar and model/context before allocating location and statuses. Statuses and location may shorten or disappear; on sufficiently narrow terminals, model/context can also shorten or disappear.
|
|
20
20
|
|
|
21
|
-
Lay out other fields with a
|
|
21
|
+
Lay out other fields with a two-cell usage bar first. Grow the bar only into leftover columns, up to ten cells—never truncate another field solely to enlarge it. At extreme widths, shrink to one cell, show only the reset label, or hide usage if neither can fit. Brackets keep an empty bar's edges visible and count toward its width, but not its cell cap.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Two cells give 6.25% steps; ten give 1.25%. Fill rounds to the nearest eighth-cell step. Only the final partially filled cell uses an intermediate shade:
|
|
24
24
|
|
|
25
25
|
| Cell | Fill |
|
|
26
26
|
|---|---|
|
|
@@ -57,7 +57,7 @@ Reuse Pi's selected-provider credentials. Copilot uses the GitHub login token fr
|
|
|
57
57
|
- Consume passive Codex response headers/stream events and Claude quota headers when available.
|
|
58
58
|
- Otherwise fetch on startup/provider switch, then roughly every four minutes without a fresh response update. Sample independent uniform ±⅛ jitter (3.5–4.5 minutes) for each attempt or fresh displayed-window update to spread fallback requests across sessions. Keep that deadline across unrelated window updates; a reported reset time still triggers an earlier refresh.
|
|
59
59
|
- Merge partial windows. A weekly-only signal must not replace a cached shorter window or postpone its refresh.
|
|
60
|
-
- Bound authentication, fetch, and response-body parsing by a shared
|
|
60
|
+
- Bound authentication, fetch, and response-body parsing by a shared nine-second timeout. Release stalled work so polling can recover.
|
|
61
61
|
- Provider switches clear usage immediately, cancel pending work, and invalidate late results. Gemini model switches also refresh model-specific usage.
|
|
62
62
|
- Missing credentials and explicit empty/unlimited responses clear usage. Temporary authentication, network, or parsing failures retain only the current provider's cached usage, dimmed; without cache, hide it.
|
|
63
63
|
- Invalid optional reset metadata must not discard otherwise valid usage. Zero usage is an empty bar, not an absent usage bar.
|
package/extensions/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal footer — replaces pi's default footer with a clean status line:
|
|
3
3
|
*
|
|
4
|
-
* ~/path/to/dir main status1 status2 sonnet · 12/128k ·
|
|
4
|
+
* ~/path/to/dir main status1 status2 sonnet · 12/128k · [⣿⣶⠀⠀⠀]↻16:40
|
|
5
5
|
*
|
|
6
6
|
* Settings are persisted in the agent directory (usually ~/.pi/agent)
|
|
7
7
|
* settings.json under "minFooter".
|
|
@@ -30,6 +30,7 @@ interface Settings {
|
|
|
30
30
|
showModel?: boolean;
|
|
31
31
|
showContext?: boolean;
|
|
32
32
|
powerlineSeparator?: boolean;
|
|
33
|
+
maxUsageBarCells?: number;
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -43,6 +44,7 @@ const DEFAULT_SETTINGS: FooterSettings = {
|
|
|
43
44
|
showModel: true,
|
|
44
45
|
showContext: true,
|
|
45
46
|
powerlineSeparator: true,
|
|
47
|
+
maxUsageBarCells: MAX_BAR_CELLS,
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
function settingsPath(): string {
|
|
@@ -74,7 +76,11 @@ function writeSettings(patch: Partial<Settings>): void {
|
|
|
74
76
|
|
|
75
77
|
function readConfig(): FooterSettings {
|
|
76
78
|
const s = readSettings();
|
|
77
|
-
|
|
79
|
+
const config = { ...DEFAULT_SETTINGS, ...s.minFooter };
|
|
80
|
+
if (typeof config.maxUsageBarCells !== "number" || !Number.isSafeInteger(config.maxUsageBarCells) || config.maxUsageBarCells < 1) {
|
|
81
|
+
config.maxUsageBarCells = MAX_BAR_CELLS;
|
|
82
|
+
}
|
|
83
|
+
return config;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function writeEnabled(v: boolean): void {
|
|
@@ -135,18 +141,21 @@ function updateState(ctx: ExtensionContext, state: FooterState): void {
|
|
|
135
141
|
const MODEL_GAP = " · ";
|
|
136
142
|
const USAGE_GAP = " · ";
|
|
137
143
|
const LOCATION_GAP_WIDTH = 3;
|
|
144
|
+
// Below these budgets, ellipses dominate text and status tabs stop being useful at a glance.
|
|
138
145
|
const MIN_TEXT_WIDTH = 4;
|
|
139
146
|
const MIN_STATUS_WIDTH = 12;
|
|
140
147
|
|
|
141
148
|
function buildLine(
|
|
142
149
|
width: number, path: string, statuses: string, branch: string, model: string, context: string,
|
|
143
150
|
usage: (available: number, maxCells?: number) => string | null,
|
|
144
|
-
statusSeparator: string, locationSeparator: string,
|
|
151
|
+
statusSeparator: string, locationSeparator: string, maxUsageBarCells: number,
|
|
145
152
|
): string {
|
|
146
153
|
if (width <= 0) return "";
|
|
154
|
+
if (width <= 2) return " ".repeat(width);
|
|
155
|
+
width -= 2;
|
|
147
156
|
let core = [model, context].filter(Boolean).join(MODEL_GAP);
|
|
148
157
|
// Budget the compact bar first; its expanded size must not drive truncation decisions.
|
|
149
|
-
const usageBar = usage(width) ?? "";
|
|
158
|
+
const usageBar = usage(width, Math.min(BASE_BAR_CELLS, maxUsageBarCells)) ?? "";
|
|
150
159
|
const usageGapWidth = usageBar && core ? USAGE_GAP.length : 0;
|
|
151
160
|
const coreBudget = Math.max(0, width - visibleWidth(usageBar) - usageGapWidth);
|
|
152
161
|
if (usageBar && coreBudget < MIN_TEXT_WIDTH) {
|
|
@@ -182,11 +191,11 @@ function buildLine(
|
|
|
182
191
|
// A dropped left group needs no divider; those columns belong to the usage bar instead.
|
|
183
192
|
const interGroupGapWidth = left && right ? LOCATION_GAP_WIDTH : 0;
|
|
184
193
|
const spareWidth = Math.max(0, width - visibleWidth(left) - visibleWidth(right) - interGroupGapWidth);
|
|
185
|
-
const expandedUsageBar = usage(visibleWidth(usageBar) + spareWidth,
|
|
194
|
+
const expandedUsageBar = usage(visibleWidth(usageBar) + spareWidth, maxUsageBarCells) ?? usageBar;
|
|
186
195
|
const expandedCore = [core, expandedUsageBar].filter(Boolean).join(USAGE_GAP);
|
|
187
196
|
right = [fittedStatuses, expandedCore].filter(Boolean).join(statusSeparator);
|
|
188
197
|
}
|
|
189
|
-
return left + " ".repeat(Math.max(0, width - visibleWidth(left) - visibleWidth(right))) + right;
|
|
198
|
+
return " " + left + " ".repeat(Math.max(0, width - visibleWidth(left) - visibleWidth(right))) + right + " ";
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
// ── Extension ─────────────────────────────────────────────────────────
|
|
@@ -251,6 +260,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
251
260
|
(available, maxCells = BASE_BAR_CELLS) => usageLimits.line(available, theme, maxCells),
|
|
252
261
|
statusSeparator,
|
|
253
262
|
config.powerlineSeparator ? theme.fg("dim", " ") : " ",
|
|
263
|
+
config.maxUsageBarCells ?? MAX_BAR_CELLS,
|
|
254
264
|
);
|
|
255
265
|
return [line];
|
|
256
266
|
},
|
|
@@ -14,7 +14,7 @@ const CELL_STEPS = 8;
|
|
|
14
14
|
const PARTIAL_CELL_FILLS = ["", "⡀", "⣀", "⣄", "⣤", "⣦", "⣶", "⣷"];
|
|
15
15
|
|
|
16
16
|
// Start compact so extra precision never steals space from the other footer fields.
|
|
17
|
-
export const BASE_BAR_CELLS =
|
|
17
|
+
export const BASE_BAR_CELLS = 2;
|
|
18
18
|
export const MAX_BAR_CELLS = 10;
|
|
19
19
|
|
|
20
20
|
function formatResetLabel(resetAt: number | null, now: number): string {
|
|
@@ -186,6 +186,7 @@ export class UsageLimits {
|
|
|
186
186
|
this.render();
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// Independent deadlines spread fallback requests across sessions sharing a subscription.
|
|
189
190
|
private randomizeRefresh(): void {
|
|
190
191
|
this.refreshDelay = REFRESH_MS + (Math.random() * 2 - 1) * REFRESH_JITTER_MS;
|
|
191
192
|
}
|
|
@@ -246,8 +247,11 @@ export class UsageLimits {
|
|
|
246
247
|
if (!this.ctx || !window) return null;
|
|
247
248
|
const now = Date.now();
|
|
248
249
|
const resetLabel = formatResetLabel(window.resetAt, now);
|
|
249
|
-
//
|
|
250
|
-
if (width <
|
|
250
|
+
// Keep the reset useful when the terminal cannot fit even one bracketed cell.
|
|
251
|
+
if (width < resetLabel.length + 3) {
|
|
252
|
+
return resetLabel && width >= resetLabel.length ? theme.fg("dim", resetLabel) : null;
|
|
253
|
+
}
|
|
254
|
+
// Brackets keep the edges of a zero-usage bar visible.
|
|
251
255
|
const cells = Math.min(maxCells, Math.max(1, width - resetLabel.length - 2));
|
|
252
256
|
const steps = Math.round(window.used / 100 * cells * CELL_STEPS);
|
|
253
257
|
const filled = "⣿".repeat(Math.floor(steps / CELL_STEPS)) + PARTIAL_CELL_FILLS[steps % CELL_STEPS];
|
|
@@ -136,6 +136,8 @@ export function parseUsageWindows(provider: string, model: string, payload: unkn
|
|
|
136
136
|
if (window) windows[key] = window;
|
|
137
137
|
else malformed = true;
|
|
138
138
|
};
|
|
139
|
+
// Valid siblings remain useful; explicit absence clears cache, while malformed
|
|
140
|
+
// snapshots throw so a temporary provider failure retains dimmed cached usage.
|
|
139
141
|
const finish = () => {
|
|
140
142
|
if (Object.keys(windows).length) return windows;
|
|
141
143
|
if (recognized && !malformed) return null;
|
|
@@ -247,6 +249,7 @@ export function parseUsageWindows(provider: string, model: string, payload: unkn
|
|
|
247
249
|
if (!ok) malformed = true;
|
|
248
250
|
return ok;
|
|
249
251
|
});
|
|
252
|
+
// A malformed exact match must not substitute another model family's quota.
|
|
250
253
|
const hasExact = buckets.some((b) => b.modelId === model);
|
|
251
254
|
const family = model.toLowerCase().includes("flash") ? "flash" : model.toLowerCase().includes("pro") ? "pro" : null;
|
|
252
255
|
const selected = valid.filter((b) => hasExact ? b.modelId === model : family && String(b.modelId).toLowerCase().includes(family));
|
package/media/github-preview.png
CHANGED
|
Binary file
|
package/media/preview.png
CHANGED
|
Binary file
|
package/package.json
CHANGED