@ravshansbox/pi-quota 0.8.1 → 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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.ts +30 -28
  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% 3d13h`, 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;
@@ -106,8 +106,10 @@ function formatResetTime(reset: Date): string {
106
106
  const hours = Math.floor((diff % 86400000) / 3600000);
107
107
  const minutes = Math.floor((diff % 3600000) / 60000);
108
108
 
109
- if (days > 0) return `${days}d${hours}h`;
110
- if (hours > 0) return `${hours}h${minutes}m`;
109
+ if (days === 1) return `${days}d${hours}h`;
110
+ if (days > 1) return `${Math.round(diff / 86400000)}d`;
111
+ if (hours === 1) return `${hours}h${minutes}m`;
112
+ if (hours > 1) return `${Math.round(diff / 3600000)}h`;
111
113
  if (minutes > 0) return `${minutes}m`;
112
114
  return 'now';
113
115
  }
@@ -116,7 +118,7 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
116
118
  const utilization = headers[`anthropic-ratelimit-unified-${name}-utilization`];
117
119
  const reset = headers[`anthropic-ratelimit-unified-${name}-reset`];
118
120
  return {
119
- remaining: utilization === undefined ? null : clampPercent(100 - Number(utilization) * 100),
121
+ used: utilization === undefined ? null : clampPercent(Number(utilization) * 100),
120
122
  reset: reset === undefined ? null : new Date(Number(reset) * 1000),
121
123
  };
122
124
  }
@@ -124,12 +126,12 @@ function parseAnthropicHeaderWindow(headers: Record<string, string>, name: strin
124
126
  function parseAnthropicHeaders(headers: Record<string, string>): QuotaState | null {
125
127
  const fiveHour = parseAnthropicHeaderWindow(headers, '5h');
126
128
  const sevenDay = parseAnthropicHeaderWindow(headers, '7d');
127
- if (fiveHour.remaining === null && sevenDay.remaining === null) return null;
129
+ if (fiveHour.used === null && sevenDay.used === null) return null;
128
130
  return {
129
131
  provider: 'anthropic',
130
- fiveHourRemaining: fiveHour.remaining,
132
+ fiveHourUsed: fiveHour.used,
131
133
  fiveHourReset: fiveHour.reset,
132
- sevenDayRemaining: sevenDay.remaining,
134
+ sevenDayUsed: sevenDay.used,
133
135
  sevenDayReset: sevenDay.reset,
134
136
  resetsAvailable: 0,
135
137
  resetSoonestExpiry: null,
@@ -211,13 +213,13 @@ export default function (pi: ExtensionAPI) {
211
213
  if (!state) return undefined;
212
214
 
213
215
  const parts: string[] = [];
214
- if (state.fiveHourRemaining !== null) {
216
+ if (state.fiveHourUsed !== null) {
215
217
  const resetStr = state.fiveHourReset ? formatResetTime(state.fiveHourReset) : '?';
216
- parts.push(`${state.fiveHourRemaining}% ${resetStr}`);
218
+ parts.push(`${state.fiveHourUsed}% ${resetStr}`);
217
219
  }
218
- if (state.sevenDayRemaining !== null) {
220
+ if (state.sevenDayUsed !== null) {
219
221
  const resetStr = state.sevenDayReset ? formatResetTime(state.sevenDayReset) : '?';
220
- parts.push(`${state.sevenDayRemaining}% ${resetStr}`);
222
+ parts.push(`${state.sevenDayUsed}% ${resetStr}`);
221
223
  }
222
224
  if (state.resetsAvailable > 0) {
223
225
  const expiryStr = state.resetSoonestExpiry
@@ -240,13 +242,13 @@ export default function (pi: ExtensionAPI) {
240
242
  function windowResetDetected(
241
243
  previousReset: Date | null,
242
244
  nextReset: Date | null,
243
- previousRemaining: number | null,
244
- nextRemaining: number | null,
245
+ previousUsed: number | null,
246
+ nextUsed: number | null,
245
247
  ): boolean {
246
248
  if (!previousReset || !nextReset) return false;
247
249
  if (nextReset.getTime() <= previousReset.getTime()) return false;
248
- if (previousRemaining === null || nextRemaining === null) return false;
249
- return nextRemaining > previousRemaining;
250
+ if (previousUsed === null || nextUsed === null) return false;
251
+ return nextUsed < previousUsed;
250
252
  }
251
253
 
252
254
  function shouldPingOnReset(previous: QuotaState, next: QuotaState): boolean {
@@ -254,14 +256,14 @@ export default function (pi: ExtensionAPI) {
254
256
  windowResetDetected(
255
257
  previous.fiveHourReset,
256
258
  next.fiveHourReset,
257
- previous.fiveHourRemaining,
258
- next.fiveHourRemaining,
259
+ previous.fiveHourUsed,
260
+ next.fiveHourUsed,
259
261
  ) ||
260
262
  windowResetDetected(
261
263
  previous.sevenDayReset,
262
264
  next.sevenDayReset,
263
- previous.sevenDayRemaining,
264
- next.sevenDayRemaining,
265
+ previous.sevenDayUsed,
266
+ next.sevenDayUsed,
265
267
  )
266
268
  );
267
269
  }
@@ -319,9 +321,9 @@ export default function (pi: ExtensionAPI) {
319
321
 
320
322
  const shouldPing = parsed.provider !== 'anthropic' && shouldPingOnReset(existing, parsed);
321
323
 
322
- existing.fiveHourRemaining = parsed.fiveHourRemaining;
324
+ existing.fiveHourUsed = parsed.fiveHourUsed;
323
325
  existing.fiveHourReset = parsed.fiveHourReset;
324
- existing.sevenDayRemaining = parsed.sevenDayRemaining;
326
+ existing.sevenDayUsed = parsed.sevenDayUsed;
325
327
  existing.sevenDayReset = parsed.sevenDayReset;
326
328
  existing.resetsAvailable = parsed.resetsAvailable;
327
329
  existing.resetSoonestExpiry = parsed.resetSoonestExpiry;
@@ -498,9 +500,9 @@ export default function (pi: ExtensionAPI) {
498
500
  const sevenDay = data.seven_day;
499
501
  updateState({
500
502
  provider: 'anthropic',
501
- fiveHourRemaining: fiveHour ? clampPercent(100 - (fiveHour.utilization ?? 0)) : null,
503
+ fiveHourUsed: fiveHour ? clampPercent(fiveHour.utilization ?? 0) : null,
502
504
  fiveHourReset: fiveHour?.resets_at ? new Date(fiveHour.resets_at) : null,
503
- sevenDayRemaining: sevenDay ? clampPercent(100 - (sevenDay.utilization ?? 0)) : null,
505
+ sevenDayUsed: sevenDay ? clampPercent(sevenDay.utilization ?? 0) : null,
504
506
  sevenDayReset: sevenDay?.resets_at ? new Date(sevenDay.resets_at) : null,
505
507
  resetsAvailable: 0,
506
508
  resetSoonestExpiry: null,
@@ -534,9 +536,9 @@ export default function (pi: ExtensionAPI) {
534
536
  resetsAvailable !== undefined ? Math.max(0, Math.trunc(resetsAvailable)) : 0;
535
537
  updateState({
536
538
  provider: 'openai-codex',
537
- fiveHourRemaining: fiveHour ? clampPercent(100 - (fiveHour.used_percent ?? 0)) : null,
539
+ fiveHourUsed: fiveHour ? clampPercent(fiveHour.used_percent ?? 0) : null,
538
540
  fiveHourReset: fiveHour?.reset_at ? new Date(fiveHour.reset_at * 1000) : null,
539
- sevenDayRemaining: sevenDay ? clampPercent(100 - (sevenDay.used_percent ?? 0)) : null,
541
+ sevenDayUsed: sevenDay ? clampPercent(sevenDay.used_percent ?? 0) : null,
540
542
  sevenDayReset: sevenDay?.reset_at ? new Date(sevenDay.reset_at * 1000) : null,
541
543
  resetsAvailable: resetCount,
542
544
  resetSoonestExpiry:
@@ -560,11 +562,11 @@ export default function (pi: ExtensionAPI) {
560
562
  const codexState = states.find((s) => s.provider === 'openai-codex');
561
563
  if (!codexState) return;
562
564
 
563
- if (codexState.sevenDayRemaining !== null && codexState.sevenDayRemaining > 0) {
565
+ if (codexState.sevenDayUsed !== null && codexState.sevenDayUsed < 100) {
564
566
  codexRedeemAttempted = false;
565
567
  }
566
568
 
567
- if (codexState.sevenDayRemaining !== 0) return;
569
+ if (codexState.sevenDayUsed !== 100) return;
568
570
  if (codexState.resetsAvailable === 0) return;
569
571
  if (codexRedeemAttempted) return;
570
572
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ravshansbox/pi-quota",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "Anthropic and OpenAI Codex quota status extension for pi",
5
5
  "license": "MIT",
6
6
  "type": "module",