@latentminds/pi-quotas 0.2.1 → 0.2.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @latentminds/pi-quotas
2
2
 
3
- Quota monitoring for the [Pi coding agent](https://github.com/mariozechner/pi). Shows remaining usage and rate limits for Anthropic, OpenAI Codex, GitHub Copilot, and OpenRouter — directly in your Pi session.
3
+ Quota monitoring for the [Pi coding agent](https://github.com/mariozechner/pi). Shows remaining usage and rate limits for Anthropic, OpenAI Codex, GitHub Copilot, OpenRouter, and Synthetic — directly in your Pi session.
4
4
 
5
5
  ## Screenshots
6
6
 
@@ -38,6 +38,7 @@ pi -e npm:@latentminds/pi-quotas
38
38
  | `/codex:quotas` | OpenAI Codex quotas only |
39
39
  | `/github:quotas` | GitHub Copilot quotas only |
40
40
  | `/openrouter:quotas` | OpenRouter quotas only |
41
+ | `/synthetic:quotas` | Synthetic quotas only |
41
42
  | `/quotas:settings` | Toggle individual features on or off |
42
43
 
43
44
  ## Features
@@ -58,7 +59,7 @@ Automatic notifications when projected usage is on track to exceed limits before
58
59
 
59
60
  Use `/quotas:settings` to enable or disable:
60
61
  - Combined `/quotas` command
61
- - Per-provider commands (`/anthropic:quotas`, `/codex:quotas`, `/github:quotas`, `/openrouter:quotas`)
62
+ - Per-provider commands (`/anthropic:quotas`, `/codex:quotas`, `/github:quotas`, `/openrouter:quotas`, `/synthetic:quotas`)
62
63
  - Footer status widget
63
64
  - Quota warning notifications
64
65
 
@@ -72,6 +73,7 @@ Settings can be saved globally (`~/.pi/agent/extensions/quotas.json`) or per-pro
72
73
  | OpenAI Codex | 5h, 7d, credits, spend cap | Rate-limit percentages; credit balance; spend-cap reached/OK |
73
74
  | GitHub Copilot | Premium/chat/completions per month | Remaining/entitlement counts with overage indicators |
74
75
  | OpenRouter | Monthly budget, daily/weekly/monthly usage | USD spending tracking with cents precision; optional per-key budget limits; UTC-based period resets |
76
+ | Synthetic | Subscription, search/hour, free tools, weekly tokens, 5h limit | Request counts and token budgets; rolling five-hour rate limit; weekly token regen |
75
77
 
76
78
  ## Credentials
77
79
 
@@ -81,8 +83,9 @@ pi-quotas reads existing Pi auth entries from `~/.pi/agent/auth.json`:
81
83
  - `openai-codex` — Codex access token (also reads `~/.codex/auth.json` for the account ID)
82
84
  - `github-copilot` — GitHub Copilot OAuth token (falls back to `gh auth token` if needed)
83
85
  - `openrouter` — OpenRouter API key (Bearer token)
86
+ - `synthetic` — Synthetic API key (set the `SYNTHETIC_API_KEY` environment variable)
84
87
 
85
- No additional setup is required - if Pi can use the provider, pi-quotas can check its quotas.
88
+ No additional setup is required - if Pi can use the provider, pi-quotas can check its quotas. For Synthetic, export `SYNTHETIC_API_KEY` in your shell or Pi environment.
86
89
 
87
90
  ## Requirements
88
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@latentminds/pi-quotas",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,7 +1,7 @@
1
1
  import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { formatWindowStatus, type WindowStatus } from "./format-status.js";
3
3
  import type { SupportedQuotaProvider } from "../../types/quotas.js";
4
- import { formatStatus, toWindowStatus } from "./index.js";
4
+ import { formatStatus, formatStatusForFooter, toStatusWindows, toWindowStatus } from "./index.js";
5
5
 
6
6
  // Minimal fake theme that just returns text with markers for color assertions
7
7
  function fakeTheme() {
@@ -183,6 +183,46 @@ describe("formatWindowStatus", () => {
183
183
  }
184
184
  });
185
185
 
186
+ it("clears the footer status when filtering removes all windows", () => {
187
+ expect(formatStatusForFooter({ ui: { theme } } as any, [])).toBeUndefined();
188
+ });
189
+
190
+ it("filters Anthropic subscription windows from footer status while keeping extra usage", () => {
191
+ const windows = toStatusWindows([
192
+ {
193
+ provider: "anthropic",
194
+ label: "5h",
195
+ usedPercent: 10,
196
+ resetsAt: new Date("2026-05-06T07:47:37Z"),
197
+ windowSeconds: 5 * 60 * 60,
198
+ usedValue: 10,
199
+ limitValue: 100,
200
+ },
201
+ {
202
+ provider: "anthropic",
203
+ label: "7d Sonnet",
204
+ usedPercent: 20,
205
+ resetsAt: new Date("2026-05-06T07:47:37Z"),
206
+ windowSeconds: 7 * 24 * 60 * 60,
207
+ usedValue: 20,
208
+ limitValue: 100,
209
+ },
210
+ {
211
+ provider: "anthropic",
212
+ label: "Extra (USD)",
213
+ usedPercent: 30,
214
+ resetsAt: new Date("2026-06-01T00:00:00Z"),
215
+ windowSeconds: 30 * 24 * 60 * 60,
216
+ usedValue: 30,
217
+ limitValue: 100,
218
+ isCurrency: true,
219
+ },
220
+ ]);
221
+
222
+ expect(windows).toHaveLength(1);
223
+ expect(windows[0]).toMatchObject({ label: "Extra (USD)" });
224
+ });
225
+
186
226
  it("does not prefix elapsed reset times with in", () => {
187
227
  vi.useFakeTimers();
188
228
  vi.setSystemTime(new Date("2026-05-06T05:28:37Z"));
@@ -39,6 +39,21 @@ export function formatStatus(ctx: Pick<ExtensionContext, "ui">, windows: WindowS
39
39
  .join(" ");
40
40
  }
41
41
 
42
+ const ANTHROPIC_SUBSCRIPTION_WINDOW_LABELS = new Set([
43
+ "5h",
44
+ "7d",
45
+ "7d Sonnet",
46
+ "7d Opus",
47
+ "7d Opus (legacy)",
48
+ ]);
49
+
50
+ function shouldShowInStatus(window: QuotaWindow): boolean {
51
+ return !(
52
+ window.provider === "anthropic" &&
53
+ ANTHROPIC_SUBSCRIPTION_WINDOW_LABELS.has(window.label)
54
+ );
55
+ }
56
+
42
57
  export function toWindowStatus(window: QuotaWindow): WindowStatus {
43
58
  return {
44
59
  label: window.label,
@@ -52,6 +67,18 @@ export function toWindowStatus(window: QuotaWindow): WindowStatus {
52
67
  };
53
68
  }
54
69
 
70
+ export function toStatusWindows(windows: QuotaWindow[]): WindowStatus[] {
71
+ return windows.filter(shouldShowInStatus).map(toWindowStatus);
72
+ }
73
+
74
+ export function formatStatusForFooter(
75
+ ctx: Pick<ExtensionContext, "ui">,
76
+ windows: WindowStatus[],
77
+ ): string | undefined {
78
+ if (windows.length === 0) return undefined;
79
+ return formatStatus(ctx, windows);
80
+ }
81
+
55
82
  function createStatusRefresher() {
56
83
  let refreshTimer: ReturnType<typeof setInterval> | undefined;
57
84
  let activeContext: ExtensionContext | undefined;
@@ -73,9 +100,10 @@ function createStatusRefresher() {
73
100
  ctx.ui.setStatus(EXTENSION_ID, ctx.ui.theme.fg("warning", "usage unavailable"));
74
101
  return;
75
102
  }
76
- const windows: WindowStatus[] = result.data.windows.map(toWindowStatus);
77
- lastStatus = windows;
78
- ctx.ui.setStatus(EXTENSION_ID, formatStatus(ctx, windows));
103
+ const windows: WindowStatus[] = toStatusWindows(result.data.windows);
104
+ const status = formatStatusForFooter(ctx, windows);
105
+ lastStatus = status === undefined ? undefined : windows;
106
+ ctx.ui.setStatus(EXTENSION_ID, status);
79
107
  } catch {
80
108
  ctx.ui.setStatus(EXTENSION_ID, ctx.ui.theme.fg("warning", "usage unavailable"));
81
109
  } finally {
@@ -114,7 +142,7 @@ function createStatusRefresher() {
114
142
  },
115
143
  renderLast(ctx: ExtensionContext): boolean {
116
144
  if (!lastStatus || !ctx.hasUI) return false;
117
- ctx.ui.setStatus(EXTENSION_ID, formatStatus(ctx, lastStatus));
145
+ ctx.ui.setStatus(EXTENSION_ID, formatStatusForFooter(ctx, lastStatus));
118
146
  return true;
119
147
  },
120
148
  };
@@ -48,6 +48,8 @@ describe("parseAnthropicUsage", () => {
48
48
  const extra = windows.find((w) => w.label === "Extra (AUD)");
49
49
  expect(extra).toBeDefined();
50
50
  expect(extra).toMatchObject({
51
+ provider: "anthropic",
52
+ label: "Extra (AUD)",
51
53
  isCurrency: true,
52
54
  usedPercent: 71.83,
53
55
  usedValue: 215.48,