rinco-pi-hud 1.1.1 → 1.2.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/docs/footer.md CHANGED
@@ -149,7 +149,7 @@ $separator → $sep
149
149
 
150
150
  The status line automatically switches quota display based on model provider:
151
151
 
152
- - **openai-codex**: Uses Pi Model Registry's Codex Auth to query ChatGPT usage endpoint; falls back to `codex app-server --listen stdio://`. Shows weekly limit percentage, e.g. `codex 75% wk`.
152
+ - **openai-codex**: Uses Pi Model Registry's Codex Auth to query ChatGPT usage endpoint; falls back to `codex app-server --listen stdio://`. Shows the remaining 5h window with its reset time plus the weekly limit percentage, e.g. `codex 60% 5h (14:30) 75% wk` (the reset stamp becomes `14:30 12 Feb` when the window rolls over on a later day).
153
153
  - **token-switch**: Uses `TOKEN_SWITCH_API_KEY` environment variable to query billing subscription and usage. Shows available balance, e.g. `token-switch $750.00`.
154
154
 
155
155
  Both queries use 15-second timeout, 5-minute cache, and auto-refresh. Switching models via `/model` forces a refresh.
@@ -54,31 +54,48 @@ export function formatCodexUsageStatusline(
54
54
  const snapshot = selectSnapshotForModel(report, model);
55
55
  if (!snapshot) return "usage unavailable";
56
56
 
57
- const weekly = selectWeeklyWindow(snapshot);
58
- if (weekly) return formatStatuslineWindow(snapshot, weekly);
59
-
60
- // A model-specific bucket may omit the long window; fall back to the account-wide one.
57
+ // A model-specific bucket may omit a window; fall back to the account-wide one.
61
58
  const codexSnapshot = report.snapshots.find(isPrimaryCodexSnapshot);
62
- if (codexSnapshot && codexSnapshot !== snapshot) {
63
- const fallbackWeekly = selectWeeklyWindow(codexSnapshot);
64
- if (fallbackWeekly) return formatStatuslineWindow(codexSnapshot, fallbackWeekly);
65
- }
59
+ const fallbackSnapshot = codexSnapshot !== snapshot ? codexSnapshot : undefined;
60
+ const short = selectShortWindow(snapshot) ?? selectShortWindow(fallbackSnapshot);
61
+ const weekly = selectWeeklyWindow(snapshot) ?? selectWeeklyWindow(fallbackSnapshot);
62
+
63
+ const ownsWindow = Boolean(selectShortWindow(snapshot) ?? selectWeeklyWindow(snapshot));
64
+ const labelSnapshot = ownsWindow ? snapshot : (fallbackSnapshot ?? snapshot);
65
+ const parts = [formatStatuslinePrefix(labelSnapshot)];
66
+
67
+ if (short) parts.push(formatStatuslineWindow(short, "5h", true));
68
+ if (weekly) parts.push(formatStatuslineWindow(weekly, "weekly", false));
69
+ if (!short && !weekly) parts.push("limits unavailable");
66
70
 
67
- return `${formatStatuslinePrefix(snapshot)} weekly unavailable`;
71
+ return parts.join(" ");
68
72
  }
69
73
 
70
74
  function formatStatuslineWindow(
71
- snapshot: NormalizedRateLimitSnapshot,
72
75
  window: NormalizedRateLimitWindow,
76
+ fallback: "5h" | "weekly",
77
+ withReset: boolean,
73
78
  ): string {
74
- return `${formatStatuslinePrefix(snapshot)} ${formatRemainingPercent(window)} ${formatWindowLabel(window, "weekly", true)}`;
79
+ const label = formatWindowLabel(window, fallback, true);
80
+ const reset = withReset && window.resetsAt ? ` (${formatCompactReset(window.resetsAt)})` : "";
81
+ return `${formatRemainingPercent(window)} ${label}${reset}`;
82
+ }
83
+
84
+ // The short (rolling) window is the one shorter than a day; accounts that only
85
+ // report a single long window have no short window at all.
86
+ function selectShortWindow(
87
+ snapshot: NormalizedRateLimitSnapshot | undefined,
88
+ ): NormalizedRateLimitWindow | undefined {
89
+ if (!snapshot?.primary) return undefined;
90
+ return isLongWindow(snapshot.primary) ? undefined : snapshot.primary;
75
91
  }
76
92
 
77
93
  // Prefer the secondary (long) window, but some accounts only report a single
78
94
  // long window as `primary` — treat any window of a day or more as the weekly one.
79
95
  function selectWeeklyWindow(
80
- snapshot: NormalizedRateLimitSnapshot,
96
+ snapshot: NormalizedRateLimitSnapshot | undefined,
81
97
  ): NormalizedRateLimitWindow | undefined {
98
+ if (!snapshot) return undefined;
82
99
  if (snapshot.secondary) return snapshot.secondary;
83
100
  if (snapshot.primary && isLongWindow(snapshot.primary)) return snapshot.primary;
84
101
  return undefined;
@@ -256,6 +273,20 @@ function formatReset(epochSeconds: number): string {
256
273
  return `${time} on ${day} ${month}`;
257
274
  }
258
275
 
276
+ // Compact reset stamp for the footer usage row: `14:30`, or `14:30 12 Feb`
277
+ // when the window rolls over on a later day.
278
+ function formatCompactReset(epochSeconds: number): string {
279
+ const reset = new Date(epochSeconds * 1000);
280
+ if (Number.isNaN(reset.getTime())) return "unknown";
281
+
282
+ const time = `${reset.getHours().toString().padStart(2, "0")}:${reset
283
+ .getMinutes()
284
+ .toString()
285
+ .padStart(2, "0")}`;
286
+ if (reset.toDateString() === new Date().toDateString()) return time;
287
+ return `${time} ${reset.getDate()} ${reset.toLocaleDateString(undefined, { month: "short" })}`;
288
+ }
289
+
259
290
  export function formatQueryErrors(errors: UsageQueryError[]): string {
260
291
  const lines = ["Unable to read Codex usage."];
261
292
  for (const error of errors) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rinco-pi-hud",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "A dynamic HUD footer for Pi, ported from Sakura Zentui Footer.",
5
5
  "type": "module",
6
6
  "license": "MIT",