@llblab/pi-codex-usage 0.9.3 → 0.10.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/BACKLOG.md CHANGED
@@ -1,8 +1,3 @@
1
1
  # Backlog
2
2
 
3
- ## Pending Release Verification
4
-
5
- - [x] Fix stale extension context usage after session replacement or reload. Source: GitHub issue #1. Timers must not keep using captured stale `ctx` after Pi session lifecycle changes.
6
- - [x] Make usage refresh cache/in-flight state model-aware for Codex vs Spark. A regular Codex cache or request must not temporarily render Spark as `n/a` after model switching.
7
- - [x] Preserve the active `codex`/`spark` label for failure states. Spark failures should render `spark n/a` or `spark error`, not `codex ...`.
8
- - [x] Replace the visually full startup/request placeholder with counter-moving upper/lower in-bar loading markers. Keep the last usable active-bucket bar visible during ordinary refreshes, and hold provisional zero-usage reports behind the loader until confirmed stable.
3
+ No open work.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.0: Business Credit Usage
4
+
5
+ - Added Business-account credit usage support for responses that do not expose Codex rate-limit windows. Impact: the statusline now shows rounded remaining credit percentage and an optional reset countdown while preserving existing rate-limit displays when both forms are available.
6
+ - Scheduled credit reset countdown redraws at their exact display boundaries and retained credits-only reports in the active cache. Impact: credit status remains locally responsive between API refreshes without displacing model-specific quota windows.
7
+
8
+ ## 0.9.4: Responsive Quota Polling
9
+
10
+ - Increased the automatic Codex quota refresh interval from 30 to 60 seconds. Impact: status values remain responsive down to the locally rendered seconds countdown while reducing repeated OpenAI requests, especially when several Pi instances are open.
11
+
3
12
  ## 0.9.3: Trusted Release Automation
4
13
 
5
14
  - Added tag-gated release automation with exact package/lock/tag identity checks, full package validation, npm Trusted Publisher provenance, public registry verification, and workflow-owned GitHub Release creation through `.github/workflows/release.yml`.
package/README.md CHANGED
@@ -14,7 +14,7 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
14
14
 
15
15
  ## Features
16
16
 
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
17
+ - Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every 60 seconds
18
18
  - Keeps the last usable active-bucket bar visible during ordinary refreshes instead of replacing known quota with a loading state
19
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
@@ -68,6 +68,12 @@ Before the first usable report for the active quota bucket arrives, two half-hei
68
68
 
69
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.
70
70
 
71
+ Business accounts that return credits instead of Codex rate-limit windows show the rounded remaining percentage. When the response includes a credit reset cycle, its countdown is shown after the percentage:
72
+
73
+ ```text
74
+ codex 81% 2.8d
75
+ ```
76
+
71
77
  When the 5-hour window is exhausted and exposes its own reset time, the statusline adds the 5-hour reset before the weekly reset:
72
78
 
73
79
  ```text
package/index.ts CHANGED
@@ -16,7 +16,7 @@ const HOUR_MS = 60 * MINUTE_MS;
16
16
  const HOUR_TENTH_MS = 6 * MINUTE_MS;
17
17
  const DAY_MS = 24 * HOUR_MS;
18
18
  const DAY_TENTH_MS = 144 * MINUTE_MS;
19
- const REFRESH_INTERVAL_MS = 30 * SECOND_MS;
19
+ const REFRESH_INTERVAL_MS = 60 * SECOND_MS;
20
20
  const PROVISIONAL_RETRY_MS = SECOND_MS;
21
21
  const FULL_AVAILABILITY_CONFIRMATION_MS = 15 * SECOND_MS;
22
22
  const LOADING_FRAME_MS = 30;
@@ -90,6 +90,12 @@ export type UsageQueryError = {
90
90
 
91
91
  export type CodexUsageReport = {
92
92
  snapshots: NormalizedRateLimitSnapshot[];
93
+ credits?: NormalizedCreditUsage;
94
+ };
95
+
96
+ export type NormalizedCreditUsage = {
97
+ remainingPercent: number;
98
+ resetAt?: number;
93
99
  };
94
100
 
95
101
  export type NormalizedRateLimitSnapshot = {
@@ -106,6 +112,8 @@ export type NormalizedRateLimitWindow = {
106
112
  type RateLimitStatusPayload = {
107
113
  rate_limit?: unknown;
108
114
  additional_rate_limits?: unknown;
115
+ credits?: unknown;
116
+ spend_control?: unknown;
109
117
  };
110
118
 
111
119
  type BackendAdditionalRateLimit = {
@@ -593,7 +601,10 @@ async function queryUsage(
593
601
  source === "pi-auth"
594
602
  ? await queryViaPiAuth(ctx, options.timeoutMs)
595
603
  : await queryViaCodexAppServer(options.timeoutMs);
596
- if (selectUsageSnapshot(report, activeUsageLimitId(model))) {
604
+ if (
605
+ selectUsageSnapshot(report, activeUsageLimitId(model)) ||
606
+ report.credits
607
+ ) {
597
608
  return { ok: true, report };
598
609
  }
599
610
  errors.push({
@@ -920,12 +931,56 @@ export function normalizeBackendPayload(
920
931
  }
921
932
  }
922
933
 
923
- if (snapshots.length === 0) {
934
+ const credits = normalizeBackendCredits(payload, _capturedAt);
935
+ if (snapshots.length === 0 && !credits) {
924
936
  throw new Error(
925
- "Codex usage endpoint returned no displayable rate-limit windows.",
937
+ "Codex usage endpoint returned no displayable rate-limit windows or credits.",
926
938
  );
927
939
  }
928
- return { snapshots };
940
+ return credits ? { snapshots, credits } : { snapshots };
941
+ }
942
+
943
+ function normalizeBackendCredits(
944
+ payload: RateLimitStatusPayload,
945
+ capturedAt: number,
946
+ ): NormalizedCreditUsage | undefined {
947
+ const spendControl = payload.spend_control as
948
+ | Record<string, unknown>
949
+ | undefined;
950
+ const individualLimit = spendControl?.individual_limit as
951
+ | Record<string, unknown>
952
+ | undefined;
953
+ const credits = payload.credits as Record<string, unknown> | undefined;
954
+ const limit = asNumber(individualLimit?.limit);
955
+ const used = asNumber(individualLimit?.used);
956
+ const remaining = asNumber(individualLimit?.remaining);
957
+ const resetAt = asResetTime(
958
+ [
959
+ individualLimit?.reset_at,
960
+ individualLimit?.resets_at,
961
+ individualLimit?.reset_time,
962
+ spendControl?.reset_at,
963
+ spendControl?.resets_at,
964
+ credits?.reset_at,
965
+ credits?.resets_at,
966
+ ],
967
+ individualLimit?.reset_after_seconds ?? spendControl?.reset_after_seconds,
968
+ capturedAt,
969
+ );
970
+ if (
971
+ limit === undefined ||
972
+ used === undefined ||
973
+ remaining === undefined ||
974
+ limit <= 0
975
+ )
976
+ return undefined;
977
+ const remainingPercent = Math.min(
978
+ 100,
979
+ Math.max(0, (remaining / limit) * 100),
980
+ );
981
+ return resetAt === undefined
982
+ ? { remainingPercent }
983
+ : { remainingPercent, resetAt };
929
984
  }
930
985
 
931
986
  function backendAdditionalLimitId(limit: BackendAdditionalRateLimit): string {
@@ -1103,7 +1158,9 @@ export function formatCodexUsageStatusValue(
1103
1158
  const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
1104
1159
  const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
1105
1160
  const snapshot = selectActiveUsageSnapshot(report, model);
1106
- if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
1161
+ if (!snapshot || (!snapshot.primary && !snapshot.secondary)) {
1162
+ return formatCreditUsage(report.credits, capturedNow);
1163
+ }
1107
1164
  if (!snapshot.primary || !snapshot.secondary) {
1108
1165
  const window = snapshot.primary ?? snapshot.secondary;
1109
1166
  if (!window) return undefined;
@@ -1130,6 +1187,17 @@ export function formatCodexUsageStatusValue(
1130
1187
  return countdown ? `${bar} ${countdown}` : bar;
1131
1188
  }
1132
1189
 
1190
+ function formatCreditUsage(
1191
+ credits: NormalizedCreditUsage | undefined,
1192
+ now: number,
1193
+ ): string | undefined {
1194
+ if (!credits) return undefined;
1195
+ const percentage = `${Math.round(credits.remainingPercent)}%`;
1196
+ return credits.resetAt
1197
+ ? `${percentage} ${formatResetCountdown(credits.resetAt, now)}`
1198
+ : percentage;
1199
+ }
1200
+
1133
1201
  export function formatWeeklyResetCountdown(
1134
1202
  report: CodexUsageReport,
1135
1203
  modelOrNow?: CodexUsageModel | number,
@@ -1167,7 +1235,10 @@ export function nextResetCountdownDelayMs(
1167
1235
  model?: CodexUsageModel,
1168
1236
  ): number | undefined {
1169
1237
  const snapshot = selectActiveUsageSnapshot(report, model);
1170
- const resetTimes = [weeklyWindow(snapshot)?.resetAt];
1238
+ const hasRateLimitWindow = Boolean(snapshot?.primary || snapshot?.secondary);
1239
+ const resetTimes = [
1240
+ hasRateLimitWindow ? weeklyWindow(snapshot)?.resetAt : report.credits?.resetAt,
1241
+ ];
1171
1242
  if (snapshot?.secondary && isQuotaWindowExhausted(snapshot.primary)) {
1172
1243
  resetTimes.push(snapshot.primary?.resetAt);
1173
1244
  }
@@ -1391,7 +1462,10 @@ export function canReuseCachedReport(
1391
1462
  report: CodexUsageReport,
1392
1463
  model: CodexUsageModel | undefined,
1393
1464
  ): boolean {
1394
- return selectActiveUsageSnapshot(report, model) !== undefined;
1465
+ return (
1466
+ selectActiveUsageSnapshot(report, model) !== undefined ||
1467
+ report.credits !== undefined
1468
+ );
1395
1469
  }
1396
1470
 
1397
1471
  export function isFullyAvailableReport(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "private": false,
5
5
  "description": "Minimal Pi extension that shows primary Codex ChatGPT subscription usage limits",
6
6
  "keywords": [