@cad0p/pi-usage 0.11.5 โ 0.11.6-20261107.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/CHANGELOG.md +22 -0
- package/README.md +102 -52
- package/bin/piquota.js +13 -3
- package/extensions/quota-panel.ts +567 -204
- package/omarchy/cad0p.pi-usage/UsageContent.qml +17 -1
- package/omarchy/cad0p.pi-usage/UsageLogic.js +13 -3
- package/omarchy/cad0p.pi-usage/assets/mistral.svg +18 -0
- package/package.json +3 -2
- package/src/auth/pi-auth.js +13 -0
- package/src/cache.js +128 -31
- package/src/cli/commands/omarchy.js +21 -3
- package/src/cli/explain.js +2 -0
- package/src/engine.js +105 -9
- package/src/local/history.js +29 -8
- package/src/local/pi-sessions.js +30 -3
- package/src/local/stats-worker.js +27 -0
- package/src/local/worker.js +157 -0
- package/src/model.js +39 -5
- package/src/omarchy/assets/mistral.svg +18 -0
- package/src/omarchy/publish.js +9 -8
- package/src/omarchy/record.js +24 -19
- package/src/overlay-keys.js +257 -0
- package/src/providers/antigravity-oauth.js +43 -3
- package/src/providers/antigravity.js +44 -5
- package/src/providers/claude.js +26 -0
- package/src/providers/codex.js +14 -0
- package/src/providers/mistral.js +251 -0
- package/src/providers/opencode-go.js +3 -3
- package/src/refresh.js +4 -1
- package/src/render/panel.js +23 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.11.6] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
<!-- USER-EDITABLE SECTION START -->
|
|
8
|
+
`/usage` refreshes inside pi with no CLI child, each provider on its own, and every key works under the kitty keyboard protocol.
|
|
9
|
+
|
|
10
|
+
- **In-process refresh:** `/usage` runs the engine inside pi instead of spawning `piquota`, so it paints in about 0.35 s and `r` finishes in under a second. Each provider updates on its own, so a slow one never holds the others back. The token-stats scan runs in a worker thread and never blocks the TUI. The systemd timer still uses the read-only CLI.
|
|
11
|
+
- **Logins stay valid:** inside pi, Claude and Codex tokens come from pi's own `getProviderAuth`, which renews them under pi's lock exactly as when you use the model. Antigravity, which pi 1.5 no longer resolves, keeps its renewed token in memory for its lifetime; nothing is written back.
|
|
12
|
+
- **Kitty keyboard protocol (Ghostty, kitty, foot):** arrows, Esc, Enter, Tab, Space and every legend key work in their CSI-u form on both tabs. Key releases never act, repeats do. A drift test compares the decoder with the installed pi-tui matcher.
|
|
13
|
+
- **History all-time counts today once**, and an all-time figure that lags behind the month files is shown dimmed with a note instead of disappearing.
|
|
14
|
+
- **Antigravity header:** no repeated project, and no false "token expired" while the account works.
|
|
15
|
+
<!-- USER-EDITABLE SECTION END -->
|
|
16
|
+
|
|
17
|
+
### ๐ Features
|
|
18
|
+
|
|
19
|
+
- In-process /usage refresh, each provider on its own (closes #26)
|
|
20
|
+
|
|
21
|
+
### ๐ Bug Fixes
|
|
22
|
+
|
|
23
|
+
- History all-time counts today once; Antigravity header without the false expiry (closes #29)
|
|
24
|
+
- *(extension)* /usage keys work under the kitty keyboard protocol (closes #33)
|
|
25
|
+
|
|
26
|
+
|
|
5
27
|
## [0.11.5] - 2026-11-03
|
|
6
28
|
|
|
7
29
|
<!-- USER-EDITABLE SECTION START -->
|
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ One source of truth for quota: **the credentials Pi already owns**.
|
|
|
6
6
|
> `@cad0p/pi-usage` is a fork of [J3pf/piQuota](https://github.com/J3pf/piQuota) (`@j3pf/piquota`). It adds:
|
|
7
7
|
>
|
|
8
8
|
> * **OpenCode Go from the API key**: `GET https://opencode.ai/zen/go/v1/usage` returns the rolling (5h), weekly and monthly windows for the key Pi already stores, so no browser session is needed.
|
|
9
|
+
> * **Mistral from the API key**: `GET https://api.mistral.ai/v1/billing/budget` and `/v1/billing/subscription`, the two reads the Vibe CLI makes for its usage gauge, return the monthly Vibe Code and API budgets and the plan for the key Pi already stores (`mistral`, and siblings such as `mistral-1-api`). They are read-only and cost no tokens.
|
|
9
10
|
> * **Numbered accounts**: `auth.json` entries such as `opencode-go-2` โฆ `opencode-go-14` (the [pi-fallback-provider](https://github.com/cad0p/pi-fallback-provider) sibling convention) are read as extra accounts of their base provider. The accounts of one provider are queried in parallel (8 at a time, `PI_QUOTA_ACCOUNT_CONCURRENCY` from 1 to 32), so 14 OpenCode Go keys cost about one round trip instead of fourteen. Every request carries `User-Agent: pi-usage/<version> (+https://github.com/cad0p/pi-usage)` unless a provider needs its own client string: OpenCode Go asks clients not to use a generic HTTP-library name, and Node's default is `node`.
|
|
10
11
|
> * **One Omarchy tab per provider**: a multi-account provider shows each account's binding window (`#2 ยท Monthly`) in a single tab, and summarises failed accounts.
|
|
11
12
|
> * **Tokens by day and by model**: the tokens Pi's own session files recorded are folded per provider and shown in `/usage`, in `--json` (`localStats`) and in the Omarchy records, at a bounded scan cost.
|
|
@@ -32,15 +33,22 @@ shuvquota, and it never writes to a credential file.
|
|
|
32
33
|
|
|
33
34
|
## Hard guarantees
|
|
34
35
|
|
|
35
|
-
* `~/.pi/agent/auth.json`
|
|
36
|
-
|
|
36
|
+
* pi-usage opens `~/.pi/agent/auth.json` with `flag: "r"` and never writes it.
|
|
37
|
+
CLI and systemd-timer runs are strictly read-only. In the Pi extension only,
|
|
38
|
+
`getProviderAuth` may ask Pi to renew Anthropic/Codex OAuth under Pi's own
|
|
39
|
+
cross-process auth lock; pi-usage never writes auth storage itself. Check the
|
|
40
|
+
auth.json checksum around live runs.
|
|
37
41
|
* No token, refresh token, API key, session cookie or `sessionKey` is printed,
|
|
38
|
-
logged,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
token
|
|
42
|
+
logged, or serialized to a file. Tokens are sent only to their provider; the
|
|
43
|
+
extension holds resolved access tokens in memory for the request. Antigravity's
|
|
44
|
+
renewed access token stays in a process-local cache until shortly before expiry.
|
|
45
|
+
E-mail identities are redacted in every artifact.
|
|
46
|
+
* Antigravity may be renewed in memory because Google does not rotate its refresh
|
|
47
|
+
token. The CLI and timer never refresh Claude/Codex; inside Pi, the extension
|
|
48
|
+
calls Pi's `getProviderAuth`, which returns fresh OAuth directly or lets Pi
|
|
49
|
+
renew and persist it under Pi's auth-store lock. pi-usage itself never writes
|
|
50
|
+
auth.json. Every surface reports the token the run *used*: an Antigravity
|
|
51
|
+
renewal clears the stale stored-expiry claim; only a failed renewal leaves it.
|
|
44
52
|
* The Claude Code store (`~/.claude/.credentials.json`) is opened read-only too, and
|
|
45
53
|
its refresh token is never even read into memory, so nothing here can rotate a
|
|
46
54
|
token the installed `claude` owns. `~/.claude.json` is read only for the account
|
|
@@ -60,7 +68,7 @@ shuvquota, and it never writes to a credential file.
|
|
|
60
68
|
|
|
61
69
|
| Path | What |
|
|
62
70
|
| --- | --- |
|
|
63
|
-
| `~/.cache/pi-quota/usage.json` | the report cache |
|
|
71
|
+
| `~/.cache/pi-quota/usage.json` | the report cache, with each family's own fetch time |
|
|
64
72
|
| `~/.cache/pi-quota/local-sessions.json` | per-session-file offsets and open token days |
|
|
65
73
|
| `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage/totals.json`, `tokens-YYYY-MM.json` | frozen token history, plus `.corrupt-<ts>` backups for corrupt files |
|
|
66
74
|
| `~/.cache/pi-quota/refresh-state.json` | each family's own refresh clock |
|
|
@@ -72,10 +80,11 @@ shuvquota, and it never writes to a credential file.
|
|
|
72
80
|
|
|
73
81
|
| Provider | Source | Endpoint | Result |
|
|
74
82
|
| --- | --- | --- | --- |
|
|
75
|
-
| Claude | Claude Code CLI store, else `anthropic.access` | `GET api.anthropic.com/api/oauth/usage` | โ
5h + weekly + plan |
|
|
76
|
-
| Codex | `openai-codex.access` + `accountId` | `GET chatgpt.com/backend-api/wham/usage` | โ
5h + weekly + plan |
|
|
83
|
+
| Claude | Claude Code CLI store, else `anthropic.access` | `GET api.anthropic.com/api/oauth/usage` | โ
5h + weekly + plan; an `api_key` sibling (`anthropic-1-api`) is a working pay-as-you-go account: no windows, a note, and its key is never sent (Anthropic serves no balance to API keys) |
|
|
84
|
+
| Codex | `openai-codex.access` + `accountId` | `GET chatgpt.com/backend-api/wham/usage` | โ
5h + weekly + plan; an `api_key` sibling (`openai-codex-1-api`) is likewise a working pay-as-you-go account whose key is never sent |
|
|
77
85
|
| Antigravity | `google-antigravity.access` (or legacy `antigravity`) + `projectId` | `POST cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary` | โ
Gemini + Claude/GPT buckets, **with in-memory refresh** |
|
|
78
86
|
| OpenCode Go | `opencode-go.key`, numbered siblings included | `GET opencode.ai/zen/go/v1/usage` | โ
5h + weekly + monthly per account; the session cookie and SSR dashboard are a base-account fallback |
|
|
87
|
+
| Mistral | `mistral.key`, numbered siblings included | `GET api.mistral.ai/v1/billing/budget` + `/v1/billing/subscription` | โ
Vibe monthly + API monthly + plan per key; without the `see_org_inference_usage` permission the plan alone, with a note. Keys of one organisation share its budgets, so they show the same numbers |
|
|
79
88
|
| GitHub Actions (opt-in) | Authenticated `gh` CLI | `gh api /organizations/<org>/settings/billing/usage/summary?product=Actions` | โ
org-wide monthly Linux minutes, plan-derived allowance |
|
|
80
89
|
|
|
81
90
|
Four findings worth recording, each of which cost a wrong hypothesis:
|
|
@@ -107,14 +116,17 @@ There are two ways to be signed in to Claude, and a user may have either or both
|
|
|
107
116
|
| Claude Code CLI | `~/.claude/.credentials.json` (honours `CLAUDE_CONFIG_DIR`); on macOS, when that file is absent, the `Claude Code-credentials` login Keychain item (read-only, via `security find-generic-password`; macOS may ask once to allow access) | What `npm:pi-claude-code-provider` drives. A subscription with usage credits disabled answers `400` on Pi's own entry for every request, and this is the source that still works. |
|
|
108
117
|
| Pi's own entry | `~/.pi/agent/auth.json` โ `anthropic` | `/login anthropic` in Pi, without the plugin installed. |
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
choice is a *preference, not a fallback*:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
For `piquota` CLI and timer runs, the Claude Code CLI wins when both are
|
|
120
|
+
present and its token is still valid. The choice is a *preference, not a fallback*:
|
|
121
|
+
only the selected source is tried, so a broken preferred source is reported rather
|
|
122
|
+
than silently masked by the other one. An expired Claude Code token next to a fresh
|
|
123
|
+
Pi login is passed over in `auto` mode with a warning. Override CLI selection with
|
|
124
|
+
`PI_QUOTA_CLAUDE_SOURCE=pi` or `=claude-code`; `--explain` prints what was used.
|
|
125
|
+
|
|
126
|
+
Inside Pi, the extension asks Pi's `getProviderAuth("anthropic")` for OAuth first.
|
|
127
|
+
A fresh token is returned as-is; when it is near expiry, Pi renews and persists it
|
|
128
|
+
under its auth-store lock, then the extension uses that returned token in memory.
|
|
129
|
+
If Pi has no OAuth login, the configured source preference applies.
|
|
118
130
|
|
|
119
131
|
The Claude Code store is opened read-only, and its refresh token is deliberately
|
|
120
132
|
**never carried into memory**: Anthropic rotates refresh tokens, and a rotation
|
|
@@ -174,10 +186,10 @@ When you also run `install.sh` for the terminal CLI, pass
|
|
|
174
186
|
| `piquota` on your `PATH` for terminal use | no | yes, symlinked |
|
|
175
187
|
| Omarchy sync while Pi is closed | no | yes, with `--omarchy-timer` |
|
|
176
188
|
|
|
177
|
-
If you live inside Pi and use Omarchy, the
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
sync while Pi is not running.
|
|
189
|
+
If you live inside Pi and use Omarchy, the extension publishes each
|
|
190
|
+
provider's record in-process as soon as that family finishes, then prunes stale
|
|
191
|
+
records once the complete refresh settles. Choose `install.sh` when you want the
|
|
192
|
+
CLI in a terminal, or background sync while Pi is not running.
|
|
181
193
|
|
|
182
194
|
### Manual Install / Development
|
|
183
195
|
|
|
@@ -266,11 +278,19 @@ refresh (and a gentle refresh after each agent turn). `/usage hide` gives the ro
|
|
|
266
278
|
back. `/usage panel` still pins the same panel flat above the editor.
|
|
267
279
|
|
|
268
280
|
The overlay paints on the same keystroke: it renders the report the extension
|
|
269
|
-
already holds, or the
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
281
|
+
already holds, or the on-disk report cache (`~/.cache/pi-quota/usage.json`) when
|
|
282
|
+
memory is empty. That cache is keyed by the Claude source the numbers in it came
|
|
283
|
+
from, so a report fetched with Pi's own OAuth is stored under Pi's key and a
|
|
284
|
+
reader that asked for Claude Code is not served it. It never waits for network work before the first frame. Opening
|
|
285
|
+
`/usage` refreshes only families older than their own clock (60 seconds by
|
|
286
|
+
default; Claude's 300-second throttle-safe interval is preserved); `r` forces
|
|
287
|
+
all enabled families. Families run independently and repaint as they finish. A
|
|
288
|
+
failed family is a line in the view โ one line per family, kept until that family
|
|
289
|
+
succeeds again, so an out-of-order success cannot hide another one's failure โ
|
|
290
|
+
never a thrown error. The local session
|
|
291
|
+
scan runs in a Node worker thread, not on the TUI event loop, and a shutdown or
|
|
292
|
+
`/reload` stops it mid-scan: it releases its history lock and writes nothing.
|
|
293
|
+
Every provider is a section:
|
|
274
294
|
the header is the brand-coloured name line, the body lists every window with its
|
|
275
295
|
used-fraction bar, reset countdown and the token rows for today, the last seven
|
|
276
296
|
days, the prompts and the heaviest models. All sections start expanded; the fold
|
|
@@ -288,6 +308,13 @@ resumes them, and no file is written.
|
|
|
288
308
|
| `tab`, `1` / `2` | switch between Now and History |
|
|
289
309
|
| `esc` / `q` | close |
|
|
290
310
|
|
|
311
|
+
Every key works in a terminal that sends the legacy escape sequences and in one
|
|
312
|
+
that negotiates the kitty keyboard protocol (Ghostty among them): the decoder
|
|
313
|
+
reads both encodings, plus kitty press, repeat and release events, so a press
|
|
314
|
+
moves once and its release never moves again. A repeat is a press โ a held arrow
|
|
315
|
+
keeps moving, exactly as it does in tmux, where the OS repeat is the same bytes
|
|
316
|
+
as the first press.
|
|
317
|
+
|
|
291
318
|
The legend at the bottom lists every key the current view answers, and wraps
|
|
292
319
|
into a second or third row on a narrow pane instead of dropping pairs โ as few
|
|
293
320
|
rows as fit, with the pairs balanced across them. The frame
|
|
@@ -300,13 +327,20 @@ dependencies to wrap the body in a `ScrollView`.
|
|
|
300
327
|
|
|
301
328
|
`tab` (or `2`) opens **History**, the second page: the durable token history for
|
|
302
329
|
one month, with the same foldable sections. A provider section lists the month's
|
|
303
|
-
days, the
|
|
304
|
-
|
|
305
|
-
|
|
330
|
+
days, the prompt count for that month and for all time, the token split by
|
|
331
|
+
model, and โ for a multi-account provider โ one row per account with its month
|
|
332
|
+
and all-time totals (every account the family has in the month *or* all-time, so
|
|
333
|
+
one busy key does not hide the others). `โ`/`โ` (or
|
|
306
334
|
`[`/`]`) walk the browsable months โ those with a file, plus the live month,
|
|
307
335
|
which is where History opens even before its first freeze โ and `tab` (or `1`)
|
|
308
336
|
comes back to Now. The all-time
|
|
309
|
-
figure is `totals.json` (frozen through its watermark)
|
|
337
|
+
figure is `totals.json` (frozen through its watermark) plus, when it is merged,
|
|
338
|
+
the same open day the month counts, so today is never missing from it. A rollup
|
|
339
|
+
that cannot cover the month on screen โ a freeze whose totals write failed, a
|
|
340
|
+
month file restored behind `totals.json` โ is still printed, dimmed, with
|
|
341
|
+
`~ all-time lags this month; run piquota stats --rebuild-totals` above the rows:
|
|
342
|
+
the figure is the only answer to "how much has this burned ever", so it is
|
|
343
|
+
explained rather than dropped. A family's open day
|
|
310
344
|
(today) is merged from the live report only when the report was *generated* today
|
|
311
345
|
and the frozen month has no row for that family's day โ per family, so one family
|
|
312
346
|
frozen early never hides another's live row โ and a day is counted exactly once.
|
|
@@ -314,21 +348,25 @@ A report generated before midnight, or the older report cache painted on the fir
|
|
|
314
348
|
frame, is skipped: its `todayTotalTokens` belong to another day, and merging them
|
|
315
349
|
under today would count a frozen yesterday twice. The tail then names the report's
|
|
316
350
|
day and says today is not merged. A missing or quarantined month is a note instead
|
|
317
|
-
of rows, a failed
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
351
|
+
of rows, a failed history read is redacted and shown with a retry hint, and a slow
|
|
352
|
+
read never moves the page off the month the last keypress asked for.
|
|
353
|
+
|
|
354
|
+
The extension reads the same `historyJson` payload in-process; the CLI exposes
|
|
355
|
+
it as `piquota stats --json [--month YYYY-MM]`: the month list, one month's days
|
|
356
|
+
per family/slot/model, the all-time rollup and notes. The read path never takes
|
|
357
|
+
the writer lock and never writes, so it is safe while a scan is freezing a day. Everything it reports is frozen: `lastFrozenDay` is its
|
|
358
|
+
watermark, and neither `piquota stats` nor its JSON moves an open day into a
|
|
359
|
+
total. The History page merges the live day itself, for the month and for the
|
|
360
|
+
all-time figures alike, and prints the same "comes from the live report" note
|
|
361
|
+
under both.
|
|
325
362
|
|
|
326
363
|
A provider with several accounts is shown as one header (`OP-Go ยท 7 of 14 accounts
|
|
327
364
|
ok`) plus one line per account with every window and its reset, e.g.
|
|
328
365
|
`#2 โ session 2% (10m) ยท week 10% (6d) ยท month 90% (19d)`, or the reason it failed
|
|
329
|
-
(`key rejected`, `no Go plan`). The base entry in `auth.json` (`opencode-go`)
|
|
330
|
-
|
|
331
|
-
|
|
366
|
+
(`key rejected`, `no Go plan`). The base entry in `auth.json` (`opencode-go`) is the
|
|
367
|
+
family's first account, `#1`; sibling slots (`opencode-go-2` โฆ) are numbered
|
|
368
|
+
`#2` โฆ `#14`, and a lone account prints no number. A `<base>-1` sibling
|
|
369
|
+
beside the base would be a second `#1`, so the report warns to renumber it. The leading glyph is the fullest window's band. The
|
|
332
370
|
overlay and the pinned panel are rendered as components, so Pi's ten-line cap on
|
|
333
371
|
plain widgets never truncates them.
|
|
334
372
|
|
|
@@ -349,7 +387,8 @@ right rail instead, under Status and TODO, and the above-editor box stays quiet
|
|
|
349
387
|
the numbers are not shown twice. See [the rail slot](#the-gentle-pi-rail-slot).
|
|
350
388
|
|
|
351
389
|
* each name is painted with its **own brand colour** โ Claude `#D97757`, Codex
|
|
352
|
-
`#10A37F`, Antigravity `#4285F4`, OpenCode `#007AFF
|
|
390
|
+
`#10A37F`, Antigravity `#4285F4`, OpenCode `#007AFF`, Mistral `#FA520F` (its row,
|
|
391
|
+
the Vibe budget, appears only when a Mistral key is configured);
|
|
353
392
|
* the numbers are **used**, not remaining, and the semaphore is both colour and
|
|
354
393
|
shape (`โ` plenty left โ `โ` โ `โ` โ `โ` nearly spent), so it still reads on a
|
|
355
394
|
colourblind or mono terminal;
|
|
@@ -439,6 +478,12 @@ Rp suite) without external network daemons or closed-source intermediaries:
|
|
|
439
478
|
* **Percentages and Countdown**: `usedPercent` and `remainingPercent` report
|
|
440
479
|
percentages (0..100); `resetsInSec` provides the exact integer seconds until
|
|
441
480
|
reset, immune to local clock skew.
|
|
481
|
+
* **`expiresInMin` and `tokenRenewedInMemory`**: the freshness of the credential the
|
|
482
|
+
run actually used. Antigravity's token is renewed in memory, so its result
|
|
483
|
+
carries `tokenRenewedInMemory: true` and `expiresInMin: null` instead of Pi's
|
|
484
|
+
stale stored countdown; a renewal that failed keeps the stored value, which is
|
|
485
|
+
what the panel's `token EXPIRED` badge and the warning line describe. The field
|
|
486
|
+
is only present when a renewal happened.
|
|
442
487
|
* **`notConfigured` vs `ok`**: unconfigured providers report `notConfigured: true`,
|
|
443
488
|
allowing consumers to cleanly omit them without displaying false-positive
|
|
444
489
|
error indicators.
|
|
@@ -486,6 +531,10 @@ Rp suite) without external network daemons or closed-source intermediaries:
|
|
|
486
531
|
}
|
|
487
532
|
```
|
|
488
533
|
|
|
534
|
+
A provider may also carry a top-level `note` when it answered but has no windows
|
|
535
|
+
to show, such as a Mistral key without the `see_org_inference_usage` permission
|
|
536
|
+
(`"ok": true`, `"windows": []`, the plan, and the reason in `note`).
|
|
537
|
+
|
|
489
538
|
The token part of the same report is abridged here (`recentDays` always carries
|
|
490
539
|
seven entries, oldest first, today last):
|
|
491
540
|
|
|
@@ -522,7 +571,7 @@ stale without triggering rate limits:
|
|
|
522
571
|
| Family | Clock |
|
|
523
572
|
| --- | --- |
|
|
524
573
|
| Claude | **300 s** (`--claude-ttl`) |
|
|
525
|
-
| Codex, Antigravity, OpenCode Go | **60 s** (`--fetch-ttl`) |
|
|
574
|
+
| Codex, Antigravity, OpenCode Go, Mistral | **60 s** (`--fetch-ttl`) |
|
|
526
575
|
|
|
527
576
|
`src/refresh.js` keeps a timestamp per family, requests only what is past its own
|
|
528
577
|
clock, and merges the result back into the canonical report. `src/sticky.js`
|
|
@@ -535,7 +584,7 @@ credential) are never masked.
|
|
|
535
584
|
Omarchy 4.0.4's **Agents** bar panel draws every `*.json` record in
|
|
536
585
|
`${XDG_STATE_HOME:-~/.local/state}/omarchy/agents/usage/`, whoever wrote it.
|
|
537
586
|
`piquota omarchy` (opt-in, one shot) writes one record per provider piQuota can
|
|
538
|
-
read, so Claude, Codex, Antigravity
|
|
587
|
+
read, so Claude, Codex, Antigravity, OpenCode Go and Mistral all get a tab:
|
|
539
588
|
|
|
540
589
|
```bash
|
|
541
590
|
piquota omarchy # write pi-claude.json, pi-codex.json, ... once
|
|
@@ -554,9 +603,8 @@ piquota omarchy # write pi-claude.json, pi-codex.json, ... once
|
|
|
554
603
|
`auth.json`) still gets **one** record: every healthy account contributes only
|
|
555
604
|
the window that constrains it, titled `#<slot> ยท Session|Weekly|Monthly`, and
|
|
556
605
|
failed accounts add no rows but are summarised by reason, e.g.
|
|
557
|
-
`#8โ#10 key rejected ยท #11โ#14 no Go plan`. The
|
|
558
|
-
|
|
559
|
-
`#<slot>`. The same record also carries
|
|
606
|
+
`#8โ#10 key rejected ยท #11โ#14 no Go plan`. The base entry's `id` is `#1`
|
|
607
|
+
and its limits are titled like any other slot's. The same record also carries
|
|
560
608
|
`accounts[{id, ok, status, limits}]` with every window of every account
|
|
561
609
|
(`status` is a short reason such as `Key rejected` for a failed one), which
|
|
562
610
|
Omarchy's built-in panel ignores and this package's own plugin draws.
|
|
@@ -658,7 +706,7 @@ What counts: one assistant message with a non-zero token count is one prompt,
|
|
|
658
706
|
and its tokens are `input + output + cacheRead + cacheWrite` โ the same sum the
|
|
659
707
|
Omarchy panel renders. Messages are bucketed by **local** date (the panel calls
|
|
660
708
|
the last row "Today"). Only providers piQuota can bill are counted:
|
|
661
|
-
`anthropic`, `openai-codex`, `antigravity`, `opencode-go` and their numbered
|
|
709
|
+
`anthropic`, `openai-codex`, `antigravity`, `opencode-go`, `mistral` and their numbered
|
|
662
710
|
siblings, so `opencode-go-7` is account `#7`. Providers it does not manage
|
|
663
711
|
(`deepinfra`, `xai`, ...) are ignored, and so is the custom `claude-bridge`
|
|
664
712
|
provider: its tokens are not a Pi subscription.
|
|
@@ -793,6 +841,7 @@ src/local/history.js frozen local-day history: month files + all-time
|
|
|
793
841
|
src/model.js window normalization, percent and reset parsing
|
|
794
842
|
src/render/{theme,panel}.js colors, thresholds, rings, bars, boxed panel
|
|
795
843
|
src/cache.js report cache at ~/.cache/pi-quota/usage.json
|
|
844
|
+
src/overlay-keys.js raw key decoding for /usage: legacy and kitty keyboard encodings
|
|
796
845
|
src/omarchy/record.js pure mapping from the report to Omarchy usage records
|
|
797
846
|
src/omarchy/publish.js atomic writer for pi-*.json, and stale-record cleanup
|
|
798
847
|
src/refresh.js per-family refresh clocks, and the merge back into one report
|
|
@@ -826,12 +875,13 @@ extensions/quota-panel.ts Pi TUI line and /usage
|
|
|
826
875
|
## Tests
|
|
827
876
|
|
|
828
877
|
```bash
|
|
829
|
-
node --test tests/*.test.mjs #
|
|
878
|
+
node --test tests/*.test.mjs # 489 tests, fake tokens only, no network
|
|
830
879
|
```
|
|
831
880
|
|
|
832
881
|
Modules covered: `auth.json` parsing and de-duplication, the Claude Code store
|
|
833
882
|
(including that the refresh token never leaves it and that reading leaves the file
|
|
834
|
-
byte-identical), Claude source precedence, the
|
|
883
|
+
byte-identical), Claude source precedence, the six providers (including Mistral's two budgets,
|
|
884
|
+
the missing usage permission and a rejected key, GitHub
|
|
835
885
|
Actions plan resolution, opt-in gating and degradation, the two Antigravity
|
|
836
886
|
failures and the OpenCode degradation), the dashboard parser's three
|
|
837
887
|
strategies, the Firefox cookie reader against a synthetic SQLite database, the
|
package/bin/piquota.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* piquota โ independent, read-only quota for the credentials Pi already owns.
|
|
4
4
|
*
|
|
5
|
-
* piquota panel for Claude, Codex, Antigravity, OpenCode Go
|
|
5
|
+
* piquota panel for Claude, Codex, Antigravity, OpenCode Go, Mistral
|
|
6
6
|
* piquota --json normalized report (used by rp-host / tools)
|
|
7
7
|
* piquota --compact | --status | --explain
|
|
8
8
|
* piquota auth opencode open the OpenCode login and capture the session
|
|
@@ -177,10 +177,20 @@ async function main() {
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
if (command === "omarchy") {
|
|
180
|
-
const load = async () => {
|
|
180
|
+
const load = async ({ onFamily } = {}) => {
|
|
181
181
|
const everyFamily = enabledFamilies({ env: process.env });
|
|
182
|
+
const publishFamily = onFamily
|
|
183
|
+
? async (family, fragment) => onFamily(family, mergeLastGood(fragment, {}).report)
|
|
184
|
+
: undefined;
|
|
182
185
|
const loadFresh = () =>
|
|
183
|
-
collectQuota({
|
|
186
|
+
collectQuota({
|
|
187
|
+
families: everyFamily,
|
|
188
|
+
timeoutMs: args.timeoutMs,
|
|
189
|
+
refresh,
|
|
190
|
+
force: args.force,
|
|
191
|
+
localStats: LOCAL_STATS,
|
|
192
|
+
onFamily: publishFamily,
|
|
193
|
+
});
|
|
184
194
|
const fresh = args.noCache
|
|
185
195
|
? await loadFresh()
|
|
186
196
|
: (await withCache({ ttlMs: args.ttlMs, force: args.force, families: everyFamily, key: CACHE_KEY }, loadFresh)).report;
|