pi-minimal-footer 0.6.0 → 0.7.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 CHANGED
@@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-10-16
11
+
12
+ ### Fixed
13
+
14
+ - Use the correct Copilot account credentials when displaying quota usage
15
+ - Keep extension statuses on one footer line when they contain newlines or tabs
16
+
17
+ - Cover Pi 1.3.0 in compatibility checks
18
+
19
+ ## [0.7.0] - 2026-10-13
20
+
21
+ ### Fixed
22
+
23
+ - Remove an unused, completed implementation plan from the documentation
24
+
25
+ ### Added
26
+
27
+ - Programmatically enable or disable the footer for the current session without changing saved settings:
28
+ ```typescript
29
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: false }); // disable
30
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: true }); // enable
31
+ ```
32
+
33
+ ### Changed
34
+
35
+ - Strengthen TypeScript checks and make provider endpoint lookups explicit
36
+ - Refine the package description and discovery keywords
37
+ - Shorten the README introduction and use a thinner footer preview; keep the Pi package preview unchanged
38
+
10
39
  ## [0.6.0] - 2026-10-08
11
40
 
12
41
  ### Fixed
@@ -234,7 +263,9 @@ First npm-ready release.
234
263
 
235
264
  - Project scaffold — `extensions/index.ts` with basic footer structure, `package.json` with pi extension manifest, `README.md`, `LICENSE` (MIT)
236
265
 
237
- [Unreleased]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.6.0...HEAD
266
+ [Unreleased]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.7.1...HEAD
267
+ [0.7.1]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.7.0...v0.7.1
268
+ [0.7.0]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.6.0...v0.7.0
238
269
  [0.6.0]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.5.3...v0.6.0
239
270
  [0.5.3]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.5.2...v0.5.3
240
271
  [0.5.2]: https://github.com/Ryu-CZ/pi-minimal-footer/compare/v0.5.1...v0.5.2
package/README.md CHANGED
@@ -7,9 +7,9 @@
7
7
  <a href="https://pi.dev/packages/pi-minimal-footer"><img src="https://img.shields.io/badge/pi-package-1a1a2e" alt="pi package"></a>
8
8
  </p>
9
9
 
10
- Pi minimal footer is an opinionated, compact, one-line status bar for [Pi](https://github.com/earendil-works/pi).
10
+ An opinionated, compact, one-line status bar for [Pi](https://github.com/earendil-works/pi).
11
11
 
12
- ![Footer preview: ~/git/project   main  🧠 Karpathy  🪽 Icarus  model · 42/200k · [⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀]↻1d8h](media/github-preview.png)
12
+ ![Footer preview: ~/git/project   main  🧠 Karpathy  🪽 Icarus  model · 42/200k · [⣿⣿⣿⣤⠀⠀⠀⠀⠀⠀]↻1d8h](media/preview.png)
13
13
 
14
14
  **Left**
15
15
 
@@ -116,6 +116,22 @@ Provided by other extensions; hidden with `showSkills: false`. Narrow terminals
116
116
 
117
117
  Only one footer extension can be active. For plugin integration, see [VISION.md](VISION.md#plugin-integration).
118
118
 
119
+ ## Programmatic control
120
+
121
+ Other Pi extensions can enable or disable the footer for the current session without changing saved settings:
122
+
123
+ ```typescript
124
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: false }); // disable
125
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: true }); // enable
126
+ ```
127
+
128
+ - Events work only while this extension's session is active.
129
+ - The latest command or event wins.
130
+ - `/minfooter on/off` saves your preference into config; events don't.
131
+ - Enabling may replace another footer.
132
+ - Disabling restores Pi's default footer if this footer is active; it leaves another extension's active footer untouched.
133
+ - New sessions and `/reload` use your saved preference.
134
+
119
135
  ## Development & design
120
136
 
121
137
  Install from source:
package/VISION.md CHANGED
@@ -113,6 +113,21 @@ Use a unique key. Pi requests a redraw when statuses change; no footer-specific
113
113
 
114
114
  Footer ownership is exclusive: another extension calling `ctx.ui.setFooter()` replaces this footer. Usage requests, new Git queries, and footer-owned timers stop on disposal; an in-flight Git query may complete, but lifecycle state refreshes alone never start one.
115
115
 
116
+ #### Session-only footer control
117
+
118
+ Extensions can temporarily override footer visibility without changing the user's saved setting:
119
+
120
+ ```typescript
121
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: false });
122
+ pi.events.emit("pi-minimal-footer:set-enabled", { enabled: true });
123
+ ```
124
+
125
+ Emit after this extension's `session_start` handler has run; events before startup or after shutdown are ignored. The payload must contain a boolean `enabled`; invalid payloads are ignored. Changes are silent and do not write settings.
126
+
127
+ The latest explicit action wins: `/minfooter on/off` changes the current footer and saves the preference, while this event changes only the current session. Normal refreshes never undo the choice. A new session or extension reload starts from the saved preference.
128
+
129
+ Disabling restores Pi's default footer only if we still own the active footer; otherwise it leaves the active footer untouched. Enabling explicitly installs our footer and may replace another extension's footer; repeated enables while we own it do nothing. Neither action restores a previous extension's footer. A coordinating extension must choose the active footer—there is no priority stack or automatic reactivation.
130
+
116
131
  ### Code map
117
132
 
118
133
  - `extensions/index.ts`: settings, plain state updates, active-footer refresh gate, layout, footer lifecycle, and `/minfooter`.
@@ -262,6 +262,7 @@ function buildLine(
262
262
  export default function (pi: ExtensionAPI) {
263
263
  let config = readConfig();
264
264
  let enabled = config.enabled !== false;
265
+ let currentCtx: ExtensionContext | undefined;
265
266
  const state: FooterState = {
266
267
  cwd: process.cwd(), model: "no-model", context: "?", gitSync: "",
267
268
  gitInitialized: false, gitHighlighted: false, gitRequest: 0,
@@ -294,7 +295,6 @@ export default function (pi: ExtensionAPI) {
294
295
 
295
296
  function install(ctx: ExtensionContext): void {
296
297
  config = readConfig();
297
- enabled = config.enabled !== false;
298
298
  if (ctx.mode !== "tui") { usageLimits.stop(); return; }
299
299
 
300
300
  if (!enabled) {
@@ -384,7 +384,7 @@ export default function (pi: ExtensionAPI) {
384
384
  return {
385
385
  render(width: number): string[] {
386
386
  const statuses = config.showSkills
387
- ? [...footerData.getExtensionStatuses().values()].filter((s) => s.trim())
387
+ ? [...footerData.getExtensionStatuses().values()].map((s) => s.replace(/[\r\n\t]/g, " ")).filter((s) => s.trim())
388
388
  : [];
389
389
  const branch = config.showGitBranch ? footerData.getGitBranch() : null;
390
390
  const branchText = branch ? ` ${branch}${state.gitSync}` : "";
@@ -426,7 +426,19 @@ export default function (pi: ExtensionAPI) {
426
426
 
427
427
  // ── Lifecycle events (passive; no footer re-install) ────────────────
428
428
 
429
+ pi.events.on("pi-minimal-footer:set-enabled", (data) => {
430
+ if (!currentCtx || typeof data !== "object" || data === null ||
431
+ !("enabled" in data) || typeof data.enabled !== "boolean") return;
432
+ if (enabled === data.enabled && (!enabled || disposeFooter !== null)) return;
433
+ enabled = data.enabled;
434
+ updateState(currentCtx, state);
435
+ install(currentCtx);
436
+ refreshFooter();
437
+ });
438
+
429
439
  pi.on("session_start", async (_event, ctx) => {
440
+ currentCtx = ctx;
441
+ enabled = readConfig().enabled !== false;
430
442
  usageLimits.stop();
431
443
  updateState(ctx, state);
432
444
  install(ctx);
@@ -467,6 +479,7 @@ export default function (pi: ExtensionAPI) {
467
479
  });
468
480
 
469
481
  pi.on("session_shutdown", async () => {
482
+ currentCtx = undefined;
470
483
  usageLimits.stop();
471
484
  disposeFooter?.();
472
485
  });
@@ -197,7 +197,7 @@ export class UsageLimits {
197
197
  if (!this.ctx || this.request) return;
198
198
  const selected = this.selectedWindow();
199
199
  // Keep the sampled deadline when unrelated windows reschedule the timer.
200
- let next = Math.max(this.attemptedAt, selected ? this.updatedAt[selected[0]] : 0) + this.refreshDelay;
200
+ let next = Math.max(this.attemptedAt, selected ? this.updatedAt[selected[0]] ?? 0 : 0) + this.refreshDelay;
201
201
  const resetAt = selected?.[1].resetAt;
202
202
  if (resetAt != null && resetAt > this.attemptedAt) next = Math.min(next, resetAt);
203
203
  this.timer = setTimeout(() => { this.timer = null; void this.poll(); }, Math.max(1, next - Date.now()));
@@ -73,6 +73,8 @@ function storedOAuth(provider: string): Record<string, unknown> {
73
73
 
74
74
  export async function usageRequest(ctx: ExtensionContext, model: NonNullable<ExtensionContext["model"]>): Promise<{ url: string; init: RequestInit } | null> {
75
75
  const provider = model.provider;
76
+ const url = ENDPOINTS[provider];
77
+ if (!url) return null;
76
78
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
77
79
  if (!auth.ok) {
78
80
  // Pi exposes missing credentials as a message, alongside transient OAuth errors.
@@ -96,7 +98,9 @@ export async function usageRequest(ctx: ExtensionContext, model: NonNullable<Ext
96
98
  } else if (provider === "github-copilot") {
97
99
  // Quota API needs the GitHub login token, not the exchanged inference token.
98
100
  const refresh = storedOAuth(provider).refresh;
99
- token = auth.env?.COPILOT_GITHUB_TOKEN ?? process.env.COPILOT_GITHUB_TOKEN ?? (typeof refresh === "string" ? refresh : undefined);
101
+ if (ctx.modelRegistry.isUsingOAuth?.(model) ?? typeof refresh === "string") {
102
+ token = typeof refresh === "string" ? refresh : undefined;
103
+ }
100
104
  Object.assign(headers, { "Editor-Version": "vscode/1.96.2", "User-Agent": "GitHubCopilotChat/0.26.7", "X-Github-Api-Version": "2025-04-01" });
101
105
  } else if (provider === "google-gemini-cli" && token?.startsWith("{")) {
102
106
  const credentials = record(JSON.parse(token));
@@ -110,7 +114,7 @@ export async function usageRequest(ctx: ExtensionContext, model: NonNullable<Ext
110
114
  init.body = "{}";
111
115
  headers["Content-Type"] = "application/json";
112
116
  }
113
- return { url: ENDPOINTS[provider], init };
117
+ return { url, init };
114
118
  }
115
119
 
116
120
  function isObject(value: unknown): value is Record<string, unknown> {
@@ -148,7 +152,7 @@ export function parseUsageWindows(provider: string, model: string, payload: unkn
148
152
  recognized = true;
149
153
  if (data.rate_limit !== null && !isObject(data.rate_limit)) malformed = true;
150
154
  const limits = record(data.rate_limit);
151
- for (const [key, name] of [["primary", "primary_window"], ["secondary", "secondary_window"]]) {
155
+ for (const [key, name] of [["primary", "primary_window"], ["secondary", "secondary_window"]] as const) {
152
156
  add(key, limits[name], (w) => windowFrom(number(w.used_percent),
153
157
  Object.hasOwn(w, "limit_window_seconds") ? number(w.limit_window_seconds) ?? NaN : undefined,
154
158
  resetSeconds(w.reset_at) ?? relativeReset(w.reset_after_seconds)));
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "pi-minimal-footer",
3
- "version": "0.6.0",
4
- "description": "An opinionated minimal footer extension for Pi — clean status line with path, extension statuses, model, and context usage.",
3
+ "version": "0.7.1",
4
+ "description": "An opinionated minimal Pi footer - one-line status bar showing subscription usage, path, status, model and context.",
5
5
  "keywords": [
6
+ "footer",
7
+ "minimal",
8
+ "usage",
9
+ "limits",
10
+ "status-bar",
11
+ "one-line",
6
12
  "pi-package",
7
13
  "pi-extension",
8
- "pi",
9
- "footer",
10
- "minimal"
14
+ "pi"
11
15
  ],
12
16
  "homepage": "https://github.com/Ryu-CZ/pi-minimal-footer#readme",
13
17
  "bugs": {