@llblab/pi-codex-usage 0.8.2 → 0.9.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/AGENTS.md CHANGED
@@ -8,10 +8,10 @@
8
8
  - Trigger: Updating quota polling or error handling.
9
9
  - Action: Do not collapse the bar while a request is in flight; only show `n/a` or `error` after repeated failures or no usable quota.
10
10
 
11
- - `Compact dual bar`: Encode the 5-hour and weekly quota windows in the ten-character separated-sextant bar.
11
+ - `Adaptive compact status`: Match the status representation to the server-provided quota windows.
12
12
  - Trigger: Changing statusline formatting.
13
- - Action: Keep a fixed-width `xxxxxxxxxx` bar where top sextants represent the 5-hour window and bottom sextants represent the weekly window, with 20 steps per window.
13
+ - Action: When both windows exist, keep the classic dual bar with 20 top steps for the 5-hour window and 20 bottom steps for the weekly window. When only one weekly window exists, show its rounded remaining percentage directly instead of using a bar.
14
14
 
15
- - `Weekly reset countdown`: Append the weekly reset countdown only when the secondary window exposes a reset time.
15
+ - `Weekly reset countdown`: Append the weekly reset countdown whenever the available weekly window exposes a reset time.
16
16
  - Trigger: Changing reset-time normalization or statusline refresh cadence.
17
- - Action: Keep `d` labels rounded upward in 144-minute day-tenth steps above 24h, show 24h..1h labels in upward-rounded 6-minute hour-tenth steps, keep `m`/`s` labels floored, and hold `0s` until a successful quota refresh reports the next window.
17
+ - Action: Treat the secondary window as weekly in dual-window responses and the sole window as weekly in single-window responses. Keep `d` labels rounded upward in 144-minute day-tenth steps above 24h, show 24h..1h labels in upward-rounded 6-minute hour-tenth steps, keep `m`/`s` labels floored, and hold `0s` until a successful quota refresh reports the next window.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.9.1: Clear Weekly Quota Status
6
+
7
+ - Replaced the unified single-window bar with an explicit remaining-quota percentage. Impact: weekly-only OpenAI responses now render as compact text such as `codex 67% 7d`, making both the remaining quota and reset time immediately readable without decoding a bar.
8
+ - Preserved the classic dual-tier bar for responses that still expose both 5-hour and weekly windows. Impact: older or account-specific server response shapes retain the dense two-limit display while the status adapts directly to available information.
9
+
10
+ ## 0.9.0: Adaptive Weekly Quota Bar
11
+
12
+ - Added server-driven quota-window display selection. Impact: responses containing both 5-hour and weekly windows keep the classic dual-tier bar, while responses containing only the new weekly window use all 40 bar steps as one unified quota scale instead of leaving one tier empty.
13
+ - Added single-window reset countdown and redraw scheduling. Impact: the weekly reset time remains visible and updates at the correct countdown boundaries regardless of whether OpenAI returns the weekly limit as the primary or secondary window.
14
+ - Extended provisional full-quota stabilization to single-window responses. Impact: transient 0%-used weekly reports remain behind the loading indicator until confirmed, matching the existing dual-window safety behavior.
15
+
5
16
  ## 0.8.2: Quota Loading Hotfix
6
17
 
7
18
  - Replaced the static empty request placeholder with fixed-width upper and lower markers that move in opposite directions through their respective quota bars, reverse at the ends, and randomly start from either mirrored endpoint phase, using the normal themed bar background. Impact: motion distinguishes startup and model-bucket loading from 100% available quota while preserving the dual-window visual language.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ![Codex Usage](./banner.jpg)
6
6
 
7
- This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-codex-usage`](https://github.com/narumiruna/pi-extensions/tree/main/extensions/pi-codex-usage). It keeps the auth and quota-fetching path, but intentionally narrows the interface to the primary Codex 5-hour and weekly limits.
7
+ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-codex-usage`](https://github.com/narumiruna/pi-extensions/tree/main/extensions/pi-codex-usage). It keeps the auth and quota-fetching path, but intentionally narrows the interface to the Codex quota windows returned by OpenAI.
8
8
 
9
9
  ## Start Here
10
10
 
@@ -16,7 +16,7 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
16
16
 
17
17
  - Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every 30 seconds
18
18
  - Keeps the last usable active-bucket bar visible during ordinary refreshes instead of replacing known quota with a loading state
19
- - Statusline output stays compact, with the active usage label accented and the quota bar plus weekly reset countdown drawn on a themed background
19
+ - Statusline output adapts to the response: weekly-only limits show an explicit remaining percentage and reset countdown, while dual-window limits keep the compact themed bar
20
20
  - Regular Codex subscription models show the primary `codex` quota bucket
21
21
  - `GPT-5.3-Codex-Spark` shows the parallel Spark quota bucket with the `spark` label
22
22
  - When `pi-telegram` is available, the same compact value appears as `codex: <value>` or `spark: <value>` in the `/start` menu status text for active OpenAI Codex subscription models
@@ -56,11 +56,17 @@ Spark model usage:
56
56
  spark ██████████ 7d
57
57
  ```
58
58
 
59
- The ten-character bar encodes two twenty-step limits at once: 40 total bits of quota state in 10 terminal cells. Each step is 5%: the top quadrants are the 5-hour limit, and the bottom quadrants are the weekly limit. If either quota window is exhausted, the bar keeps the same shape but switches to the error background color.
59
+ When OpenAI returns only the weekly window, the status shows the exact rounded remaining percentage and reset countdown directly:
60
+
61
+ ```text
62
+ codex 67% 7d
63
+ ```
64
+
65
+ When both windows exist, the ten-character bar encodes two twenty-step limits at once: the top quadrants show the 5-hour limit and the bottom quadrants show the weekly limit, with each step representing 5%. If either quota window is exhausted, the bar keeps its shape but switches to the error background color.
60
66
 
61
67
  Before the first usable report for the active quota bucket arrives, two half-height markers move through the same fixed-width themed bar. The upper 5-hour marker travels opposite the lower weekly marker; both reverse smoothly at the ends, and each loader run randomly starts from one of the two mirrored endpoint phases. Their motion distinguishes loading from 100% remaining quota while preserving the normal bar background. A first report claiming both windows are completely unused is treated as provisional for 15 seconds and retried every second, because providers can briefly emit zeroed windows while initializing. Once a usable report exists, refresh requests preserve that last good bar.
62
68
 
63
- When the weekly reset time is available, the bar is followed by a countdown. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `6.6d`, `5.1d`, `5d`, `3.7d`, `3d`, `2d`, `1.9d`, `1.5d`, and `1.1d`, rounded upward to the next tenth. At 24 hours and below it switches to upward-rounded 6-minute hour-tenth steps such as `24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, `1.4h`, `1.3h`, `1.2h`, `1.1h`, and `1h`. Under an hour it switches to floored minutes, and under a minute to seconds. After the reset timestamp passes, `0s` is held until the next successful quota refresh reports the new weekly window.
69
+ When the weekly reset time is available, it follows either the single-window percentage or the dual-window bar. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `6.6d`, `5.1d`, `5d`, `3.7d`, `3d`, `2d`, `1.9d`, `1.5d`, and `1.1d`, rounded upward to the next tenth. At 24 hours and below it switches to upward-rounded 6-minute hour-tenth steps such as `24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, `1.4h`, `1.3h`, `1.2h`, `1.1h`, and `1h`. Under an hour it switches to floored minutes, and under a minute to seconds. After the reset timestamp passes, `0s` is held until the next successful quota refresh reports the new weekly window.
64
70
 
65
71
  When the 5-hour window is exhausted and exposes its own reset time, the statusline adds the 5-hour reset before the weekly reset:
66
72
 
package/index.ts CHANGED
@@ -1071,6 +1071,9 @@ export function formatCodexUsageStatusline(
1071
1071
  ): string {
1072
1072
  const value = formatCodexUsageStatusValue(report, model);
1073
1073
  if (!value) return formatStatuslineText(ctx, "n/a", model);
1074
+ const snapshot = selectActiveUsageSnapshot(report, model);
1075
+ if (!snapshot?.primary || !snapshot.secondary)
1076
+ return formatStatuslineText(ctx, value, model);
1074
1077
  const [bar, countdown] = value.split(" ", 2);
1075
1078
  const barText = formatStatuslineBarText(
1076
1079
  ctx,
@@ -1098,6 +1101,15 @@ export function formatCodexUsageStatusValue(
1098
1101
  const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
1099
1102
  const snapshot = selectActiveUsageSnapshot(report, model);
1100
1103
  if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
1104
+ if (!snapshot.primary || !snapshot.secondary) {
1105
+ const window = snapshot.primary ?? snapshot.secondary;
1106
+ if (!window) return undefined;
1107
+ const percentage = `${Math.round(remainingPercent(window))}%`;
1108
+ const countdown = window.resetAt
1109
+ ? formatResetCountdown(window.resetAt, capturedNow)
1110
+ : undefined;
1111
+ return countdown ? `${percentage} ${countdown}` : percentage;
1112
+ }
1101
1113
  const bar = formatDualLimitBar(snapshot.primary, snapshot.secondary);
1102
1114
  if (isQuotaWindowExhausted(snapshot.primary) && snapshot.primary?.resetAt) {
1103
1115
  const primaryCountdown = formatResetCountdown(
@@ -1122,7 +1134,8 @@ export function formatWeeklyResetCountdown(
1122
1134
  ): string | undefined {
1123
1135
  const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
1124
1136
  const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
1125
- const resetAt = selectActiveUsageSnapshot(report, model)?.secondary?.resetAt;
1137
+ const snapshot = selectActiveUsageSnapshot(report, model);
1138
+ const resetAt = weeklyWindow(snapshot)?.resetAt;
1126
1139
  if (resetAt === undefined) return undefined;
1127
1140
  return formatResetCountdown(resetAt, capturedNow);
1128
1141
  }
@@ -1151,9 +1164,9 @@ export function nextResetCountdownDelayMs(
1151
1164
  model?: CodexUsageModel,
1152
1165
  ): number | undefined {
1153
1166
  const snapshot = selectActiveUsageSnapshot(report, model);
1154
- const resetTimes = [snapshot?.secondary?.resetAt];
1155
- if (isQuotaWindowExhausted(snapshot?.primary)) {
1156
- resetTimes.push(snapshot?.primary?.resetAt);
1167
+ const resetTimes = [weeklyWindow(snapshot)?.resetAt];
1168
+ if (snapshot?.secondary && isQuotaWindowExhausted(snapshot.primary)) {
1169
+ resetTimes.push(snapshot.primary?.resetAt);
1157
1170
  }
1158
1171
  const delays = resetTimes
1159
1172
  .filter((resetAt): resetAt is number => resetAt !== undefined)
@@ -1197,7 +1210,7 @@ function formatReportBar(
1197
1210
  ): string | undefined {
1198
1211
  const snapshot = selectActiveUsageSnapshot(report, model);
1199
1212
  if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
1200
- return formatDualLimitBar(snapshot.primary, snapshot.secondary);
1213
+ return formatAdaptiveLimitBar(snapshot.primary, snapshot.secondary);
1201
1214
  }
1202
1215
 
1203
1216
  function formatStatuslineText(
@@ -1291,9 +1304,32 @@ function normalizedUsageKey(value: string | undefined): string | undefined {
1291
1304
  return key || undefined;
1292
1305
  }
1293
1306
 
1294
- function formatDualLimitBar(
1307
+ function formatAdaptiveLimitBar(
1295
1308
  primary: NormalizedRateLimitWindow | undefined,
1296
1309
  secondary: NormalizedRateLimitWindow | undefined,
1310
+ ): string {
1311
+ if (!primary || !secondary) return formatSingleLimitBar(primary ?? secondary);
1312
+ return formatDualLimitBar(primary, secondary);
1313
+ }
1314
+
1315
+ function formatSingleLimitBar(
1316
+ window: NormalizedRateLimitWindow | undefined,
1317
+ ): string {
1318
+ const filled = filledParts(window, DUAL_BAR_WIDTH * 4);
1319
+ const partMasks = [1, 4, 2, 8];
1320
+ let value = "";
1321
+ for (let index = 0; index < DUAL_BAR_WIDTH; index++) {
1322
+ const cellParts = Math.min(4, Math.max(0, filled - index * 4));
1323
+ let mask = 0;
1324
+ for (let part = 0; part < cellParts; part++) mask |= partMasks[part] ?? 0;
1325
+ value += DUAL_BAR_CHARS[mask];
1326
+ }
1327
+ return value;
1328
+ }
1329
+
1330
+ function formatDualLimitBar(
1331
+ primary: NormalizedRateLimitWindow,
1332
+ secondary: NormalizedRateLimitWindow,
1297
1333
  ): string {
1298
1334
  const primaryParts = filledTwentieths(primary);
1299
1335
  const secondaryParts = filledTwentieths(secondary);
@@ -1360,14 +1396,21 @@ export function isFullyAvailableReport(
1360
1396
  model?: CodexUsageModel,
1361
1397
  ): boolean {
1362
1398
  const snapshot = selectActiveUsageSnapshot(report, model);
1399
+ const windows = [snapshot?.primary, snapshot?.secondary].filter(
1400
+ (window): window is NormalizedRateLimitWindow => window !== undefined,
1401
+ );
1363
1402
  return (
1364
- snapshot?.primary !== undefined &&
1365
- snapshot.secondary !== undefined &&
1366
- clampPercent(snapshot.primary.usedPercent) === 0 &&
1367
- clampPercent(snapshot.secondary.usedPercent) === 0
1403
+ windows.length > 0 &&
1404
+ windows.every((window) => clampPercent(window.usedPercent) === 0)
1368
1405
  );
1369
1406
  }
1370
1407
 
1408
+ function weeklyWindow(
1409
+ snapshot: NormalizedRateLimitSnapshot | undefined,
1410
+ ): NormalizedRateLimitWindow | undefined {
1411
+ return snapshot?.secondary ?? snapshot?.primary;
1412
+ }
1413
+
1371
1414
  function selectActiveUsageSnapshot(
1372
1415
  report: CodexUsageReport,
1373
1416
  model: CodexUsageModel | undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "Minimal Pi extension that shows primary Codex ChatGPT subscription usage limits.",
6
6
  "keywords": [