@ravshansbox/pi-quota 0.9.0 → 0.10.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.ts +28 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,12 +12,12 @@ Add `-l` to install it in project settings.
12
12
 
13
13
  ## Usage
14
14
 
15
- Pi loads the extension from `./index.ts` and shows the remaining quota for the provider backing the active model in the footer.
15
+ Pi loads the extension from `./index.ts` and shows the used quota for the provider backing the active model in the footer.
16
16
 
17
17
  - Calls the Anthropic usage endpoint once on session start, then reads the Anthropic quota from the rate limit headers of each Anthropic response
18
18
  - Polls the OpenAI Codex usage endpoint on session start, then at every 10-minute wall-clock mark (`HH:00`, `HH:10`, `HH:20`, `HH:30`, `HH:40`, `HH:50`)
19
19
  - Shows a footer status for the provider backing the **active model**, and only when quota data for that provider has been polled; switching models re-renders it immediately
20
- - The text stays compact, e.g. `93% 1h29m · 67% 4d`, where the 5-hour window comes first and the 7-day window second, and each window shows its remaining percentage followed by time until reset
20
+ - The text stays compact, e.g. `7% 1h29m · 33% 4d`, where the 5-hour window comes first and the 7-day window second, and each window shows its used percentage followed by time until reset
21
21
  - Available resets, when present, are appended as `· 2x 5h` (count, then time until the soonest expires)
22
22
  - Both providers are checked on session start whenever their credentials exist, so quota is already known when the active model changes
23
23
  - Refreshes Anthropic and OpenAI Codex OAuth access tokens from `~/.pi/agent/auth.json` when needed, writes updated credentials back (re-reading the file first to avoid clobbering concurrent updates), and notifies on the first successful refresh per provider each session
package/index.ts CHANGED
@@ -13,9 +13,9 @@ interface QuotaConfig {
13
13
 
14
14
  interface QuotaState {
15
15
  provider: 'anthropic' | 'openai-codex';
16
- fiveHourRemaining: number | null;
16
+ fiveHourUsed: number | null;
17
17
  fiveHourReset: Date | null;
18
- sevenDayRemaining: number | null;
18
+ sevenDayUsed: number | null;
19
19
  sevenDayReset: Date | null;
20
20
  resetsAvailable: number;
21
21
  resetSoonestExpiry: Date | null;
@@ -118,7 +118,7 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
118
118
  const utilization = headers[`anthropic-ratelimit-unified-${name}-utilization`];
119
119
  const reset = headers[`anthropic-ratelimit-unified-${name}-reset`];
120
120
  return {
121
- remaining: utilization === undefined ? null : clampPercent(100 - Number(utilization) * 100),
121
+ used: utilization === undefined ? null : clampPercent(Number(utilization) * 100),
122
122
  reset: reset === undefined ? null : new Date(Number(reset) * 1000),
123
123
  };
124
124
  }
@@ -126,12 +126,12 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
126
126
  function parseAnthropicHeaders(headers: Record<string, string>): QuotaState | null {
127
127
  const fiveHour = parseAnthropicHeaderWindow(headers, '5h');
128
128
  const sevenDay = parseAnthropicHeaderWindow(headers, '7d');
129
- if (fiveHour.remaining === null && sevenDay.remaining === null) return null;
129
+ if (fiveHour.used === null && sevenDay.used === null) return null;
130
130
  return {
131
131
  provider: 'anthropic',
132
- fiveHourRemaining: fiveHour.remaining,
132
+ fiveHourUsed: fiveHour.used,
133
133
  fiveHourReset: fiveHour.reset,
134
- sevenDayRemaining: sevenDay.remaining,
134
+ sevenDayUsed: sevenDay.used,
135
135
  sevenDayReset: sevenDay.reset,
136
136
  resetsAvailable: 0,
137
137
  resetSoonestExpiry: null,
@@ -213,13 +213,13 @@ export default function (pi: ExtensionAPI) {
213
213
  if (!state) return undefined;
214
214
 
215
215
  const parts: string[] = [];
216
- if (state.fiveHourRemaining !== null) {
216
+ if (state.fiveHourUsed !== null) {
217
217
  const resetStr = state.fiveHourReset ? formatResetTime(state.fiveHourReset) : '?';
218
- parts.push(`${state.fiveHourRemaining}% ${resetStr}`);
218
+ parts.push(`${state.fiveHourUsed}% ${resetStr}`);
219
219
  }
220
- if (state.sevenDayRemaining !== null) {
220
+ if (state.sevenDayUsed !== null) {
221
221
  const resetStr = state.sevenDayReset ? formatResetTime(state.sevenDayReset) : '?';
222
- parts.push(`${state.sevenDayRemaining}% ${resetStr}`);
222
+ parts.push(`${state.sevenDayUsed}% ${resetStr}`);
223
223
  }
224
224
  if (state.resetsAvailable > 0) {
225
225
  const expiryStr = state.resetSoonestExpiry
@@ -242,13 +242,13 @@ export default function (pi: ExtensionAPI) {
242
242
  function windowResetDetected(
243
243
  previousReset: Date | null,
244
244
  nextReset: Date | null,
245
- previousRemaining: number | null,
246
- nextRemaining: number | null,
245
+ previousUsed: number | null,
246
+ nextUsed: number | null,
247
247
  ): boolean {
248
248
  if (!previousReset || !nextReset) return false;
249
249
  if (nextReset.getTime() <= previousReset.getTime()) return false;
250
- if (previousRemaining === null || nextRemaining === null) return false;
251
- return nextRemaining > previousRemaining;
250
+ if (previousUsed === null || nextUsed === null) return false;
251
+ return nextUsed < previousUsed;
252
252
  }
253
253
 
254
254
  function shouldPingOnReset(previous: QuotaState, next: QuotaState): boolean {
@@ -256,14 +256,14 @@ export default function (pi: ExtensionAPI) {
256
256
  windowResetDetected(
257
257
  previous.fiveHourReset,
258
258
  next.fiveHourReset,
259
- previous.fiveHourRemaining,
260
- next.fiveHourRemaining,
259
+ previous.fiveHourUsed,
260
+ next.fiveHourUsed,
261
261
  ) ||
262
262
  windowResetDetected(
263
263
  previous.sevenDayReset,
264
264
  next.sevenDayReset,
265
- previous.sevenDayRemaining,
266
- next.sevenDayRemaining,
265
+ previous.sevenDayUsed,
266
+ next.sevenDayUsed,
267
267
  )
268
268
  );
269
269
  }
@@ -312,6 +312,7 @@ export default function (pi: ExtensionAPI) {
312
312
  }
313
313
 
314
314
  function updateState(parsed: QuotaState) {
315
+ if (!ctxRef) return;
315
316
  if (parsed.provider === 'anthropic') scheduleAnthropicPing(parsed);
316
317
  const existing = states.find((s) => s.provider === parsed.provider);
317
318
  if (!existing) {
@@ -321,9 +322,9 @@ export default function (pi: ExtensionAPI) {
321
322
 
322
323
  const shouldPing = parsed.provider !== 'anthropic' && shouldPingOnReset(existing, parsed);
323
324
 
324
- existing.fiveHourRemaining = parsed.fiveHourRemaining;
325
+ existing.fiveHourUsed = parsed.fiveHourUsed;
325
326
  existing.fiveHourReset = parsed.fiveHourReset;
326
- existing.sevenDayRemaining = parsed.sevenDayRemaining;
327
+ existing.sevenDayUsed = parsed.sevenDayUsed;
327
328
  existing.sevenDayReset = parsed.sevenDayReset;
328
329
  existing.resetsAvailable = parsed.resetsAvailable;
329
330
  existing.resetSoonestExpiry = parsed.resetSoonestExpiry;
@@ -500,9 +501,9 @@ export default function (pi: ExtensionAPI) {
500
501
  const sevenDay = data.seven_day;
501
502
  updateState({
502
503
  provider: 'anthropic',
503
- fiveHourRemaining: fiveHour ? clampPercent(100 - (fiveHour.utilization ?? 0)) : null,
504
+ fiveHourUsed: fiveHour ? clampPercent(fiveHour.utilization ?? 0) : null,
504
505
  fiveHourReset: fiveHour?.resets_at ? new Date(fiveHour.resets_at) : null,
505
- sevenDayRemaining: sevenDay ? clampPercent(100 - (sevenDay.utilization ?? 0)) : null,
506
+ sevenDayUsed: sevenDay ? clampPercent(sevenDay.utilization ?? 0) : null,
506
507
  sevenDayReset: sevenDay?.resets_at ? new Date(sevenDay.resets_at) : null,
507
508
  resetsAvailable: 0,
508
509
  resetSoonestExpiry: null,
@@ -536,9 +537,9 @@ export default function (pi: ExtensionAPI) {
536
537
  resetsAvailable !== undefined ? Math.max(0, Math.trunc(resetsAvailable)) : 0;
537
538
  updateState({
538
539
  provider: 'openai-codex',
539
- fiveHourRemaining: fiveHour ? clampPercent(100 - (fiveHour.used_percent ?? 0)) : null,
540
+ fiveHourUsed: fiveHour ? clampPercent(fiveHour.used_percent ?? 0) : null,
540
541
  fiveHourReset: fiveHour?.reset_at ? new Date(fiveHour.reset_at * 1000) : null,
541
- sevenDayRemaining: sevenDay ? clampPercent(100 - (sevenDay.used_percent ?? 0)) : null,
542
+ sevenDayUsed: sevenDay ? clampPercent(sevenDay.used_percent ?? 0) : null,
542
543
  sevenDayReset: sevenDay?.reset_at ? new Date(sevenDay.reset_at * 1000) : null,
543
544
  resetsAvailable: resetCount,
544
545
  resetSoonestExpiry:
@@ -562,11 +563,11 @@ export default function (pi: ExtensionAPI) {
562
563
  const codexState = states.find((s) => s.provider === 'openai-codex');
563
564
  if (!codexState) return;
564
565
 
565
- if (codexState.sevenDayRemaining !== null && codexState.sevenDayRemaining > 0) {
566
+ if (codexState.sevenDayUsed !== null && codexState.sevenDayUsed < 100) {
566
567
  codexRedeemAttempted = false;
567
568
  }
568
569
 
569
- if (codexState.sevenDayRemaining !== 0) return;
570
+ if (codexState.sevenDayUsed !== 100) return;
570
571
  if (codexState.resetsAvailable === 0) return;
571
572
  if (codexRedeemAttempted) return;
572
573
 
@@ -666,5 +667,6 @@ export default function (pi: ExtensionAPI) {
666
667
 
667
668
  pi.on('session_shutdown', () => {
668
669
  stopTimers();
670
+ ctxRef = null;
669
671
  });
670
672
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ravshansbox/pi-quota",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Anthropic and OpenAI Codex quota status extension for pi",
5
5
  "license": "MIT",
6
6
  "type": "module",