@cad0p/pi-usage 0.11.4 → 0.11.5-20261103.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 +21 -0
- package/README.md +140 -35
- package/bin/piquota.js +25 -4
- package/extensions/quota-panel.ts +1879 -194
- package/omarchy/cad0p.pi-usage/UsageLogic.js +9 -4
- package/package.json +1 -1
- package/src/cache.js +128 -31
- package/src/cli/args.js +13 -0
- package/src/cli/commands/omarchy.js +21 -3
- package/src/cli/commands/stats.js +149 -1
- package/src/engine.js +178 -29
- package/src/http.js +34 -1
- package/src/local/history.js +53 -7
- package/src/local/pi-sessions.js +48 -5
- package/src/local/stats-worker.js +27 -0
- package/src/local/worker.js +157 -0
- package/src/model.js +39 -5
- package/src/omarchy/publish.js +9 -8
- package/src/omarchy/record.js +18 -11
- package/src/providers/antigravity-oauth.js +43 -3
- package/src/providers/antigravity.js +44 -5
- package/src/providers/opencode-go.js +4 -3
- package/src/refresh.js +4 -1
- package/src/render/panel.js +28 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.11.5] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
<!-- USER-EDITABLE SECTION START -->
|
|
8
|
+
A fullscreen `/usage` with browsable history, and a refresh about three times faster.
|
|
9
|
+
|
|
10
|
+
- **Fullscreen `/usage`:** bare `/usage` opens a view like `/context`. It paints at once from the last report (`refreshing …` until the new one lands, then `updated HH:MM`), keeps an always-visible key legend, folds provider sections, and scrolls with the arrows, `j`/`k`, PgUp/PgDn, `g`/`G` and the mouse wheel. Rows wrap instead of being clipped, so resets stay visible at 64 columns. Folds, page and month persist for the pi session; `esc`/`q` leaves a clean editor.
|
|
11
|
+
- **History:** `tab` (or `1`/`2`) switches to month history: days, models and per-account tokens, `←`/`→` and `[`/`]` walk the months, and today merges from the live report exactly once. Backed by the new read-only `piquota stats --json [--month YYYY-MM]`.
|
|
12
|
+
- **No invented `#1`:** the unnumbered base account carries no suffix on any surface.
|
|
13
|
+
- **Faster refresh:** a provider's accounts are queried in parallel (default 8, `PI_QUOTA_ACCOUNT_CONCURRENCY` 1–32): a forced refresh went from ~2.9 s to ~1.0 s, OpenCode Go's 14 keys from ~3.8 s to ~0.9 s.
|
|
14
|
+
- **User-Agent:** every request sends `pi-usage/<version> (+https://github.com/cad0p/pi-usage)` instead of Node's generic `node` (Antigravity keeps its CLI agent).
|
|
15
|
+
<!-- USER-EDITABLE SECTION END -->
|
|
16
|
+
|
|
17
|
+
### 🚀 Features
|
|
18
|
+
|
|
19
|
+
- Fullscreen /usage with month history; no invented "#1" (closes #21, closes #22)
|
|
20
|
+
|
|
21
|
+
### ⚡ Performance
|
|
22
|
+
|
|
23
|
+
- Query a provider's accounts in parallel; send the package User-Agent (closes #23)
|
|
24
|
+
|
|
25
|
+
|
|
5
26
|
## [0.11.4] - 2026-11-03
|
|
6
27
|
|
|
7
28
|
<!-- USER-EDITABLE SECTION START -->
|
package/README.md
CHANGED
|
@@ -6,7 +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
|
-
> * **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.
|
|
9
|
+
> * **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
10
|
> * **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
11
|
> * **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.
|
|
12
12
|
> * **`/usage` on demand**: the Pi command is `/usage` only, and nothing is pinned above the editor or in the footer unless you ask for it. A multi-account provider gets one line per account in the panel.
|
|
@@ -32,15 +32,22 @@ shuvquota, and it never writes to a credential file.
|
|
|
32
32
|
|
|
33
33
|
## Hard guarantees
|
|
34
34
|
|
|
35
|
-
* `~/.pi/agent/auth.json`
|
|
36
|
-
|
|
35
|
+
* pi-usage opens `~/.pi/agent/auth.json` with `flag: "r"` and never writes it.
|
|
36
|
+
CLI and systemd-timer runs are strictly read-only. In the Pi extension only,
|
|
37
|
+
`getProviderAuth` may ask Pi to renew Anthropic/Codex OAuth under Pi's own
|
|
38
|
+
cross-process auth lock; pi-usage never writes auth storage itself. Check the
|
|
39
|
+
auth.json checksum around live runs.
|
|
37
40
|
* No token, refresh token, API key, session cookie or `sessionKey` is printed,
|
|
38
|
-
logged,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
token
|
|
41
|
+
logged, or serialized to a file. Tokens are sent only to their provider; the
|
|
42
|
+
extension holds resolved access tokens in memory for the request. Antigravity's
|
|
43
|
+
renewed access token stays in a process-local cache until shortly before expiry.
|
|
44
|
+
E-mail identities are redacted in every artifact.
|
|
45
|
+
* Antigravity may be renewed in memory because Google does not rotate its refresh
|
|
46
|
+
token. The CLI and timer never refresh Claude/Codex; inside Pi, the extension
|
|
47
|
+
calls Pi's `getProviderAuth`, which returns fresh OAuth directly or lets Pi
|
|
48
|
+
renew and persist it under Pi's auth-store lock. pi-usage itself never writes
|
|
49
|
+
auth.json. Every surface reports the token the run *used*: an Antigravity
|
|
50
|
+
renewal clears the stale stored-expiry claim; only a failed renewal leaves it.
|
|
44
51
|
* The Claude Code store (`~/.claude/.credentials.json`) is opened read-only too, and
|
|
45
52
|
its refresh token is never even read into memory, so nothing here can rotate a
|
|
46
53
|
token the installed `claude` owns. `~/.claude.json` is read only for the account
|
|
@@ -60,7 +67,7 @@ shuvquota, and it never writes to a credential file.
|
|
|
60
67
|
|
|
61
68
|
| Path | What |
|
|
62
69
|
| --- | --- |
|
|
63
|
-
| `~/.cache/pi-quota/usage.json` | the report cache |
|
|
70
|
+
| `~/.cache/pi-quota/usage.json` | the report cache, with each family's own fetch time |
|
|
64
71
|
| `~/.cache/pi-quota/local-sessions.json` | per-session-file offsets and open token days |
|
|
65
72
|
| `${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
73
|
| `~/.cache/pi-quota/refresh-state.json` | each family's own refresh clock |
|
|
@@ -107,14 +114,17 @@ There are two ways to be signed in to Claude, and a user may have either or both
|
|
|
107
114
|
| 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
115
|
| Pi's own entry | `~/.pi/agent/auth.json` → `anthropic` | `/login anthropic` in Pi, without the plugin installed. |
|
|
109
116
|
|
|
110
|
-
|
|
111
|
-
choice is a *preference, not a fallback*:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
For `piquota` CLI and timer runs, the Claude Code CLI wins when both are
|
|
118
|
+
present and its token is still valid. The choice is a *preference, not a fallback*:
|
|
119
|
+
only the selected source is tried, so a broken preferred source is reported rather
|
|
120
|
+
than silently masked by the other one. An expired Claude Code token next to a fresh
|
|
121
|
+
Pi login is passed over in `auto` mode with a warning. Override CLI selection with
|
|
122
|
+
`PI_QUOTA_CLAUDE_SOURCE=pi` or `=claude-code`; `--explain` prints what was used.
|
|
123
|
+
|
|
124
|
+
Inside Pi, the extension asks Pi's `getProviderAuth("anthropic")` for OAuth first.
|
|
125
|
+
A fresh token is returned as-is; when it is near expiry, Pi renews and persists it
|
|
126
|
+
under its auth-store lock, then the extension uses that returned token in memory.
|
|
127
|
+
If Pi has no OAuth login, the configured source preference applies.
|
|
118
128
|
|
|
119
129
|
The Claude Code store is opened read-only, and its refresh token is deliberately
|
|
120
130
|
**never carried into memory**: Anthropic rotates refresh tokens, and a rotation
|
|
@@ -174,10 +184,10 @@ When you also run `install.sh` for the terminal CLI, pass
|
|
|
174
184
|
| `piquota` on your `PATH` for terminal use | no | yes, symlinked |
|
|
175
185
|
| Omarchy sync while Pi is closed | no | yes, with `--omarchy-timer` |
|
|
176
186
|
|
|
177
|
-
If you live inside Pi and use Omarchy, the
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
sync while Pi is not running.
|
|
187
|
+
If you live inside Pi and use Omarchy, the extension publishes each
|
|
188
|
+
provider's record in-process as soon as that family finishes, then prunes stale
|
|
189
|
+
records once the complete refresh settles. Choose `install.sh` when you want the
|
|
190
|
+
CLI in a terminal, or background sync while Pi is not running.
|
|
181
191
|
|
|
182
192
|
### Manual Install / Development
|
|
183
193
|
|
|
@@ -223,6 +233,9 @@ piquota auth opencode --wait 300
|
|
|
223
233
|
|
|
224
234
|
piquota omarchy # write pi-*.json records for Omarchy's Agents panel
|
|
225
235
|
piquota stats # durable token history: last frozen day, families, totals
|
|
236
|
+
piquota stats --json --month 2026-11
|
|
237
|
+
# read-only history feed: months, one month's days
|
|
238
|
+
# per family/slot/model, all-time totals, notes
|
|
226
239
|
piquota stats --rebuild-totals # recompute totals.json from the frozen month files
|
|
227
240
|
```
|
|
228
241
|
|
|
@@ -242,7 +255,7 @@ Inside Pi:
|
|
|
242
255
|
|
|
243
256
|
| Command | Effect |
|
|
244
257
|
| --- | --- |
|
|
245
|
-
| `/usage` |
|
|
258
|
+
| `/usage` | open the fullscreen quota overlay: every provider, window, account and token row, folded by section, refreshed in the background; `tab` opens the month history; `esc` closes it |
|
|
246
259
|
| `/usage box` | pin the five-row box above the editor |
|
|
247
260
|
| `/usage line` | pin the compact line above the editor, in its own row |
|
|
248
261
|
| `/usage panel` | full panel above the editor, pinned across prompts |
|
|
@@ -253,18 +266,99 @@ Inside Pi:
|
|
|
253
266
|
|
|
254
267
|
### The Pi surface
|
|
255
268
|
|
|
256
|
-
Nothing is pinned by default: `/usage`
|
|
257
|
-
above the editor and leaves the footer alone.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
`/usage
|
|
269
|
+
Nothing is pinned by default: `/usage` opens a **fullscreen overlay** that takes
|
|
270
|
+
the keyboard, so it claims no row above the editor and leaves the footer alone.
|
|
271
|
+
It is the same `ctx.ui.custom` overlay pattern pi's `/context` uses: it fills the
|
|
272
|
+
terminal, scrolls, folds and closes on Esc (or `q`), leaving nothing behind.
|
|
273
|
+
Turn a persistent surface on with `/usage box`, `/usage line`, `/usage panel` or
|
|
274
|
+
`/usage status`; only then does the extension keep it fresh with a background
|
|
275
|
+
refresh (and a gentle refresh after each agent turn). `/usage hide` gives the row
|
|
276
|
+
back. `/usage panel` still pins the same panel flat above the editor.
|
|
277
|
+
|
|
278
|
+
The overlay paints on the same keystroke: it renders the report the extension
|
|
279
|
+
already holds, or the on-disk report cache (`~/.cache/pi-quota/usage.json`) when
|
|
280
|
+
memory is empty. That cache is keyed by the Claude source the numbers in it came
|
|
281
|
+
from, so a report fetched with Pi's own OAuth is stored under Pi's key and a
|
|
282
|
+
reader that asked for Claude Code is not served it. It never waits for network work before the first frame. Opening
|
|
283
|
+
`/usage` refreshes only families older than their own clock (60 seconds by
|
|
284
|
+
default; Claude's 300-second throttle-safe interval is preserved); `r` forces
|
|
285
|
+
all enabled families. Families run independently and repaint as they finish. A
|
|
286
|
+
failed family is a line in the view — one line per family, kept until that family
|
|
287
|
+
succeeds again, so an out-of-order success cannot hide another one's failure —
|
|
288
|
+
never a thrown error. The local session
|
|
289
|
+
scan runs in a Node worker thread, not on the TUI event loop, and a shutdown or
|
|
290
|
+
`/reload` stops it mid-scan: it releases its history lock and writes nothing.
|
|
291
|
+
Every provider is a section:
|
|
292
|
+
the header is the brand-coloured name line, the body lists every window with its
|
|
293
|
+
used-fraction bar, reset countdown and the token rows for today, the last seven
|
|
294
|
+
days, the prompts and the heaviest models. All sections start expanded; the fold
|
|
295
|
+
state and the page you left live for the pi session, so reopening `/usage`
|
|
296
|
+
resumes them, and no file is written.
|
|
297
|
+
|
|
298
|
+
| Key | Action |
|
|
299
|
+
| --- | --- |
|
|
300
|
+
| `↑`/`↓`, `j`/`k` | move the header cursor; a section taller than the viewport scrolls line by line first |
|
|
301
|
+
| `enter` / `space` | fold or unfold the section under the cursor |
|
|
302
|
+
| `→`/`l`, `←`/`h` | expand, collapse |
|
|
303
|
+
| `PgUp`/`PgDn`, `g`/`Home`, `G`/`End` | page, first section, last section |
|
|
304
|
+
| mouse wheel | scroll |
|
|
305
|
+
| `r` | refresh in place (on History: the month and the live report) |
|
|
306
|
+
| `tab`, `1` / `2` | switch between Now and History |
|
|
307
|
+
| `esc` / `q` | close |
|
|
308
|
+
|
|
309
|
+
The legend at the bottom lists every key the current view answers, and wraps
|
|
310
|
+
into a second or third row on a narrow pane instead of dropping pairs — as few
|
|
311
|
+
rows as fit, with the pairs balanced across them. The frame
|
|
312
|
+
fits from about 60 columns up: a line wider than the terminal wraps at its
|
|
313
|
+
` · ` separators with a hanging indent instead of being cut, so a row never
|
|
314
|
+
loses the datum it ends with, and a resize rebuilds it. The wheel is handled by the component itself, like
|
|
315
|
+
pi-context-view: pi routes fullscreen wheel events to the focused component but
|
|
316
|
+
has no scrolling API for a custom screen, and this extension has no runtime
|
|
317
|
+
dependencies to wrap the body in a `ScrollView`.
|
|
318
|
+
|
|
319
|
+
`tab` (or `2`) opens **History**, the second page: the durable token history for
|
|
320
|
+
one month, with the same foldable sections. A provider section lists the month's
|
|
321
|
+
days, the prompt count for that month and for all time, the token split by
|
|
322
|
+
model, and — for a multi-account provider — one row per account with its month
|
|
323
|
+
and all-time totals (every account the family has in the month *or* all-time, so
|
|
324
|
+
one busy key does not hide the others). `←`/`→` (or
|
|
325
|
+
`[`/`]`) walk the browsable months — those with a file, plus the live month,
|
|
326
|
+
which is where History opens even before its first freeze — and `tab` (or `1`)
|
|
327
|
+
comes back to Now. The all-time
|
|
328
|
+
figure is `totals.json` (frozen through its watermark) plus, when it is merged,
|
|
329
|
+
the same open day the month counts, so today is never missing from it. A rollup
|
|
330
|
+
that cannot cover the month on screen — a freeze whose totals write failed, a
|
|
331
|
+
month file restored behind `totals.json` — is still printed, dimmed, with
|
|
332
|
+
`~ all-time lags this month; run piquota stats --rebuild-totals` above the rows:
|
|
333
|
+
the figure is the only answer to "how much has this burned ever", so it is
|
|
334
|
+
explained rather than dropped. A family's open day
|
|
335
|
+
(today) is merged from the live report only when the report was *generated* today
|
|
336
|
+
and the frozen month has no row for that family's day — per family, so one family
|
|
337
|
+
frozen early never hides another's live row — and a day is counted exactly once.
|
|
338
|
+
A report generated before midnight, or the older report cache painted on the first
|
|
339
|
+
frame, is skipped: its `todayTotalTokens` belong to another day, and merging them
|
|
340
|
+
under today would count a frozen yesterday twice. The tail then names the report's
|
|
341
|
+
day and says today is not merged. A missing or quarantined month is a note instead
|
|
342
|
+
of rows, a failed history read is redacted and shown with a retry hint, and a slow
|
|
343
|
+
read never moves the page off the month the last keypress asked for.
|
|
344
|
+
|
|
345
|
+
The extension reads the same `historyJson` payload in-process; the CLI exposes
|
|
346
|
+
it as `piquota stats --json [--month YYYY-MM]`: the month list, one month's days
|
|
347
|
+
per family/slot/model, the all-time rollup and notes. The read path never takes
|
|
348
|
+
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
|
|
349
|
+
watermark, and neither `piquota stats` nor its JSON moves an open day into a
|
|
350
|
+
total. The History page merges the live day itself, for the month and for the
|
|
351
|
+
all-time figures alike, and prints the same "comes from the live report" note
|
|
352
|
+
under both.
|
|
261
353
|
|
|
262
354
|
A provider with several accounts is shown as one header (`OP-Go · 7 of 14 accounts
|
|
263
355
|
ok`) plus one line per account with every window and its reset, e.g.
|
|
264
356
|
`#2 ● session 2% (10m) · week 10% (6d) · month 90% (19d)`, or the reason it failed
|
|
265
|
-
(`key rejected`, `no Go plan`). The
|
|
266
|
-
|
|
267
|
-
|
|
357
|
+
(`key rejected`, `no Go plan`). The base entry in `auth.json` (`opencode-go`) has
|
|
358
|
+
no number, so its label column is blank; only real sibling slots (`opencode-go-2`
|
|
359
|
+
…) are numbered `#2` … `#14`. The leading glyph is the fullest window's band. The
|
|
360
|
+
overlay and the pinned panel are rendered as components, so Pi's ten-line cap on
|
|
361
|
+
plain widgets never truncates them.
|
|
268
362
|
|
|
269
363
|
`/usage box` pins the **five-row box above the editor**:
|
|
270
364
|
|
|
@@ -373,6 +467,12 @@ Rp suite) without external network daemons or closed-source intermediaries:
|
|
|
373
467
|
* **Percentages and Countdown**: `usedPercent` and `remainingPercent` report
|
|
374
468
|
percentages (0..100); `resetsInSec` provides the exact integer seconds until
|
|
375
469
|
reset, immune to local clock skew.
|
|
470
|
+
* **`expiresInMin` and `tokenRenewedInMemory`**: the freshness of the credential the
|
|
471
|
+
run actually used. Antigravity's token is renewed in memory, so its result
|
|
472
|
+
carries `tokenRenewedInMemory: true` and `expiresInMin: null` instead of Pi's
|
|
473
|
+
stale stored countdown; a renewal that failed keeps the stored value, which is
|
|
474
|
+
what the panel's `token EXPIRED` badge and the warning line describe. The field
|
|
475
|
+
is only present when a renewal happened.
|
|
376
476
|
* **`notConfigured` vs `ok`**: unconfigured providers report `notConfigured: true`,
|
|
377
477
|
allowing consumers to cleanly omit them without displaying false-positive
|
|
378
478
|
error indicators.
|
|
@@ -440,7 +540,7 @@ seven entries, oldest first, today last):
|
|
|
440
540
|
"activeDays": 11,
|
|
441
541
|
"activeDates": ["2026-10-27"],
|
|
442
542
|
"modelUsage": { "gpt-5.1-codex": { "inputTokens": 4, "outputTokens": 425, "cacheReadInputTokens": 6838, "cacheCreationInputTokens": 6905 } },
|
|
443
|
-
"byAccount": { "
|
|
543
|
+
"byAccount": { "": { "todayTokens": 14172, "weekTokens": 118422, "todayPrompts": 3, "totalPrompts": 1200, "totalTokens": 48812210 } }
|
|
444
544
|
}
|
|
445
545
|
},
|
|
446
546
|
"localStatsMeta": { "files": 604, "scanned": 1, "reused": 603, "bytesRead": 3042, "durationMs": 9, "partial": false, "budgetMs": 12000, "frozenDays": 0, "locked": false }
|
|
@@ -488,7 +588,9 @@ piquota omarchy # write pi-claude.json, pi-codex.json, ... once
|
|
|
488
588
|
`auth.json`) still gets **one** record: every healthy account contributes only
|
|
489
589
|
the window that constrains it, titled `#<slot> · Session|Weekly|Monthly`, and
|
|
490
590
|
failed accounts add no rows but are summarised by reason, e.g.
|
|
491
|
-
`#8–#10 key rejected · #11–#14 no Go plan`. The
|
|
591
|
+
`#8–#10 key rejected · #11–#14 no Go plan`. The unnumbered base entry keeps a
|
|
592
|
+
blank `id` and its limits are untitled; only real sibling slots carry
|
|
593
|
+
`#<slot>`. The same record also carries
|
|
492
594
|
`accounts[{id, ok, status, limits}]` with every window of every account
|
|
493
595
|
(`status` is a short reason such as `Key rejected` for a failed one), which
|
|
494
596
|
Omarchy's built-in panel ignores and this package's own plugin draws.
|
|
@@ -510,7 +612,8 @@ is a collapsible section of one scrolling panel.
|
|
|
510
612
|
with its window and reset (`Weekly · 1d`), a chip when something is wrong
|
|
511
613
|
(`Unavailable`, `7 down`), and the headline meter while folded. The headline is
|
|
512
614
|
the fullest window; for a multi-account provider it is the healthy account
|
|
513
|
-
with the most room left
|
|
615
|
+
with the most room left. `/usage` does not collapse a family like that: its
|
|
616
|
+
panel lists one line per account, so this headline is the bar's own summary.
|
|
514
617
|
- **Expanded**: the status card, LIMITS with the built-in meters and countdowns,
|
|
515
618
|
then for a multi-account record one row per account with every window
|
|
516
619
|
(`#2 session 2% 10m · week 10% 6d · month 90% 19d`, failed accounts dimmed
|
|
@@ -582,6 +685,8 @@ shows them next to the quota:
|
|
|
582
685
|
| `/usage` panel | a `tokens` row per provider: today, 7 days, prompts, then the top 3 models of the week |
|
|
583
686
|
| `piquota omarchy` | the optional fields Omarchy's Agents panel draws as "Tokens by day" and "Tokens by model" |
|
|
584
687
|
| `piquota stats` | the durable history: last frozen day, families, prompts, session-days |
|
|
688
|
+
| `/usage` → `tab` | History: per provider, the month's days and models, account totals for a multi-account family, and the all-time rollup |
|
|
689
|
+
| `piquota stats --json` | the same history as a read-only JSON feed (`--month YYYY-MM` selects a month) |
|
|
585
690
|
|
|
586
691
|
What counts: one assistant message with a non-zero token count is one prompt,
|
|
587
692
|
and its tokens are `input + output + cacheRead + cacheWrite` — the same sum the
|
|
@@ -755,7 +860,7 @@ extensions/quota-panel.ts Pi TUI line and /usage
|
|
|
755
860
|
## Tests
|
|
756
861
|
|
|
757
862
|
```bash
|
|
758
|
-
node --test tests/*.test.mjs #
|
|
863
|
+
node --test tests/*.test.mjs # 441 tests, fake tokens only, no network
|
|
759
864
|
```
|
|
760
865
|
|
|
761
866
|
Modules covered: `auth.json` parsing and de-duplication, the Claude Code store
|
package/bin/piquota.js
CHANGED
|
@@ -57,7 +57,7 @@ Usage:
|
|
|
57
57
|
piquota auth <opencode|status> [flags]
|
|
58
58
|
piquota gentle-pi <status|apply|revert>
|
|
59
59
|
piquota omarchy
|
|
60
|
-
piquota stats [--rebuild-totals]
|
|
60
|
+
piquota stats [--json] [--month YYYY-MM] [--rebuild-totals]
|
|
61
61
|
|
|
62
62
|
Quota:
|
|
63
63
|
--json Emit the normalized report as JSON (for rp-host and automations)
|
|
@@ -73,6 +73,7 @@ Quota:
|
|
|
73
73
|
--explain Show which stores/fields are read (names only)
|
|
74
74
|
--clear-cache Delete the local cache and exit
|
|
75
75
|
--rebuild-totals With \`stats\`: rebuild the token-history totals from its month files
|
|
76
|
+
--month YYYY-MM With \`stats\`: read one month (default: the current local month)
|
|
76
77
|
--version Print the version and exit
|
|
77
78
|
|
|
78
79
|
Auth (OpenCode Go session, the only credential Pi does not store):
|
|
@@ -86,6 +87,9 @@ Auth (OpenCode Go session, the only credential Pi does not store):
|
|
|
86
87
|
Stats:
|
|
87
88
|
piquota stats summarise the durable token history
|
|
88
89
|
(dir: \${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage)
|
|
90
|
+
piquota stats --json [--month] read-only JSON: months (the live month always),
|
|
91
|
+
one month's days per family/slot/model, all-time
|
|
92
|
+
totals, notes
|
|
89
93
|
piquota stats --rebuild-totals recompute totals.json from the frozen month files
|
|
90
94
|
|
|
91
95
|
Omarchy:
|
|
@@ -96,6 +100,7 @@ Omarchy:
|
|
|
96
100
|
Environment:
|
|
97
101
|
PI_QUOTA_LOCAL_STATS=0 skip the token scan of Pi's session files
|
|
98
102
|
PI_QUOTA_LOCAL_STATS_BUDGET_MS wall-clock budget for that scan (default 12000)
|
|
103
|
+
PI_QUOTA_ACCOUNT_CONCURRENCY accounts of one provider queried at once (1-32, default 8)
|
|
99
104
|
|
|
100
105
|
Guarantees:
|
|
101
106
|
* ~/.pi/agent/auth.json is opened read-only. Never written, synced or refreshed.
|
|
@@ -161,15 +166,31 @@ async function main() {
|
|
|
161
166
|
return 2;
|
|
162
167
|
}
|
|
163
168
|
|
|
169
|
+
if (args.month && command !== "stats") {
|
|
170
|
+
err("--month only works with \`piquota stats\`\n");
|
|
171
|
+
process.stdout.write(HELP);
|
|
172
|
+
return 2;
|
|
173
|
+
}
|
|
174
|
+
|
|
164
175
|
if (command === "stats") {
|
|
165
|
-
return runStatsCommand({ rebuild: args.rebuildTotals, env: process.env });
|
|
176
|
+
return runStatsCommand({ rebuild: args.rebuildTotals, json: args.json, month: args.month, env: process.env });
|
|
166
177
|
}
|
|
167
178
|
|
|
168
179
|
if (command === "omarchy") {
|
|
169
|
-
const load = async () => {
|
|
180
|
+
const load = async ({ onFamily } = {}) => {
|
|
170
181
|
const everyFamily = enabledFamilies({ env: process.env });
|
|
182
|
+
const publishFamily = onFamily
|
|
183
|
+
? async (family, fragment) => onFamily(family, mergeLastGood(fragment, {}).report)
|
|
184
|
+
: undefined;
|
|
171
185
|
const loadFresh = () =>
|
|
172
|
-
collectQuota({
|
|
186
|
+
collectQuota({
|
|
187
|
+
families: everyFamily,
|
|
188
|
+
timeoutMs: args.timeoutMs,
|
|
189
|
+
refresh,
|
|
190
|
+
force: args.force,
|
|
191
|
+
localStats: LOCAL_STATS,
|
|
192
|
+
onFamily: publishFamily,
|
|
193
|
+
});
|
|
173
194
|
const fresh = args.noCache
|
|
174
195
|
? await loadFresh()
|
|
175
196
|
: (await withCache({ ttlMs: args.ttlMs, force: args.force, families: everyFamily, key: CACHE_KEY }, loadFresh)).report;
|