@cad0p/pi-usage 0.11.3 โ 0.11.4-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 +28 -0
- package/README.md +353 -20
- package/bin/piquota.js +60 -4
- package/extensions/quota-panel.ts +1594 -109
- package/omarchy/cad0p.pi-usage/Agent.qml +33 -0
- package/omarchy/cad0p.pi-usage/Main.qml +333 -0
- package/omarchy/cad0p.pi-usage/Panel.qml +136 -0
- package/omarchy/cad0p.pi-usage/README.md +40 -0
- package/omarchy/cad0p.pi-usage/UsageContent.qml +1001 -0
- package/omarchy/cad0p.pi-usage/UsageLogic.js +608 -0
- package/omarchy/cad0p.pi-usage/assets/antigravity.svg +3 -0
- package/omarchy/cad0p.pi-usage/assets/claude.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/codex-light.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/codex.svg +1 -0
- package/omarchy/cad0p.pi-usage/assets/fireworks.svg +7 -0
- package/omarchy/cad0p.pi-usage/assets/github-actions.svg +3 -0
- package/omarchy/cad0p.pi-usage/assets/opencode-go.svg +3 -0
- package/omarchy/cad0p.pi-usage/manifest.json +56 -0
- package/package.json +2 -1
- package/src/auth/pi-auth.js +3 -0
- package/src/cli/args.js +18 -0
- package/src/cli/commands/stats.js +224 -0
- package/src/engine.js +125 -23
- package/src/http.js +34 -1
- package/src/local/history.js +1206 -0
- package/src/local/pi-sessions.js +1527 -0
- package/src/omarchy/publish.js +1 -65
- package/src/omarchy/record.js +100 -18
- package/src/providers/opencode-go.js +4 -3
- package/src/render/panel.js +28 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.11.4] - 2026-11-03
|
|
6
|
+
|
|
7
|
+
<!-- USER-EDITABLE SECTION START -->
|
|
8
|
+
Token history, a scrolling bar panel, and Antigravity from Pi's own login.
|
|
9
|
+
|
|
10
|
+
- **Token stats from Pi's sessions:** tokens by day and by model per provider and account, read from `~/.pi/agent/sessions` with no extra credential. Incremental (a warm run reads only appended bytes) and durable: finished days are frozen into `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage/` (`totals.json` + `tokens-YYYY-MM.json`) and survive session deletion and cache loss. Shown in `/usage` and in the Omarchy records; `piquota stats` inspects the history.
|
|
11
|
+
- **AI Usage bar plugin (`cad0p.pi-usage`):** looks like Omarchy's Agents panel, but every provider is a collapsible section of one scrolling panel, multi-account providers list every window of every account, and tokens by day and by model are drawn as in the built-in. `install.sh --omarchy-plugin` links it from the pi package, so `pi update` updates it.
|
|
12
|
+
- **No more writes into other Omarchy plugins:** `piquota omarchy` stopped copying provider marks into every `~/.config/omarchy/plugins/*/assets`.
|
|
13
|
+
- **`/usage` glance:** bare `/usage` closes on the next prompt or a second `/usage` (`/usage panel` still pins), and each account line shows session, week and month.
|
|
14
|
+
- **Antigravity:** read from Pi's `google-antigravity` login (the legacy `antigravity` key still works).
|
|
15
|
+
- `docs/verification.md`: the checklist every change follows.
|
|
16
|
+
<!-- USER-EDITABLE SECTION END -->
|
|
17
|
+
|
|
18
|
+
### ๐ Features
|
|
19
|
+
|
|
20
|
+
- *(local)* Token stats by day and by model from Pi's session files (closes #16)
|
|
21
|
+
- *(omarchy)* AI Usage bar plugin, every provider a collapsible section (closes #18)
|
|
22
|
+
|
|
23
|
+
### ๐ Bug Fixes
|
|
24
|
+
|
|
25
|
+
- *(extension)* /usage glance closes on the next prompt; every window per account (closes #9)
|
|
26
|
+
- *(auth)* Read Antigravity from Pi's google-antigravity login (closes #12)
|
|
27
|
+
|
|
28
|
+
### ๐ Documentation
|
|
29
|
+
|
|
30
|
+
- Add the verification checklist every change follows (closes #14)
|
|
31
|
+
|
|
32
|
+
|
|
5
33
|
## [0.11.3] - 2026-11-02
|
|
6
34
|
|
|
7
35
|
<!-- USER-EDITABLE SECTION START -->
|
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@ 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
|
+
> * **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.
|
|
11
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.
|
|
12
13
|
> * **No dead Claude store**: `auto` passes over an expired Claude Code token when Pi's own `anthropic` login is fresh, and says so in the warnings.
|
|
13
14
|
>
|
|
@@ -17,7 +18,7 @@ One source of truth for quota: **the credentials Pi already owns**.
|
|
|
17
18
|
shuvquota, and it never writes to a credential file.
|
|
18
19
|
|
|
19
20
|
> [!TIP]
|
|
20
|
-
> **Working with AI Agents?** See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for architectural boundaries, test runner commands, and hard invariants for AI coding agents.
|
|
21
|
+
> **Working with AI Agents?** See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for architectural boundaries, test runner commands, and hard invariants for AI coding agents, and [docs/verification.md](docs/verification.md) for the verification checklist every change follows.
|
|
21
22
|
|
|
22
23
|
| Surface | Command |
|
|
23
24
|
| --- | --- |
|
|
@@ -49,11 +50,19 @@ shuvquota, and it never writes to a credential file.
|
|
|
49
50
|
endpoint is gone and no session cookie is configured.
|
|
50
51
|
* `/usage` never injects quota into the LLM context: the extension only calls
|
|
51
52
|
`ui.setStatus`, `ui.setWidget` and `ui.notify`.
|
|
53
|
+
* Pi's session files (`~/.pi/agent/sessions/**/*.jsonl`) are opened read-only, to
|
|
54
|
+
count tokens per day and per model. The scan cache keys each file by its path,
|
|
55
|
+
size and byte offset and stores open days; the durable history under
|
|
56
|
+
`~/.pi/agent/pi-usage/` stores frozen days. Both hold only aggregate counts and
|
|
57
|
+
model names โ no prompt text or tool output reaches a cache, a record or the
|
|
58
|
+
report.
|
|
52
59
|
* Nothing is written outside these paths:
|
|
53
60
|
|
|
54
61
|
| Path | What |
|
|
55
62
|
| --- | --- |
|
|
56
63
|
| `~/.cache/pi-quota/usage.json` | the report cache |
|
|
64
|
+
| `~/.cache/pi-quota/local-sessions.json` | per-session-file offsets and open token days |
|
|
65
|
+
| `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage/totals.json`, `tokens-YYYY-MM.json` | frozen token history, plus `.corrupt-<ts>` backups for corrupt files |
|
|
57
66
|
| `~/.cache/pi-quota/refresh-state.json` | each family's own refresh clock |
|
|
58
67
|
| `~/.cache/pi-quota/last-published.json`, `last-good.json`, `backoff.json` | sticky snapshots and throttle state |
|
|
59
68
|
| `~/.config/pi-quota/opencode-cookie` | only after `piquota auth opencode`, mode `600` |
|
|
@@ -65,7 +74,7 @@ shuvquota, and it never writes to a credential file.
|
|
|
65
74
|
| --- | --- | --- | --- |
|
|
66
75
|
| Claude | Claude Code CLI store, else `anthropic.access` | `GET api.anthropic.com/api/oauth/usage` | โ
5h + weekly + plan |
|
|
67
76
|
| Codex | `openai-codex.access` + `accountId` | `GET chatgpt.com/backend-api/wham/usage` | โ
5h + weekly + plan |
|
|
68
|
-
| Antigravity | `antigravity.access` + `projectId` | `POST cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary` | โ
Gemini + Claude/GPT buckets, **with in-memory refresh** |
|
|
77
|
+
| Antigravity | `google-antigravity.access` (or legacy `antigravity`) + `projectId` | `POST cloudcode-pa.googleapis.com/v1internal:retrieveUserQuotaSummary` | โ
Gemini + Claude/GPT buckets, **with in-memory refresh** |
|
|
69
78
|
| 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 |
|
|
70
79
|
| 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 |
|
|
71
80
|
|
|
@@ -177,6 +186,7 @@ git clone https://github.com/cad0p/pi-usage.git
|
|
|
177
186
|
cd pi-usage
|
|
178
187
|
./install.sh # --link for development, --uninstall to remove
|
|
179
188
|
./install.sh --no-extension # CLI only; keep the Pi extension installed as a package
|
|
189
|
+
./install.sh --omarchy-plugin # also link the AI Usage bar plugin (see Omarchy integration)
|
|
180
190
|
```
|
|
181
191
|
|
|
182
192
|
No sudo. It validates Node, Pi and Gentle AI, reports which Claude source it found,
|
|
@@ -212,6 +222,11 @@ piquota auth opencode --no-browser
|
|
|
212
222
|
piquota auth opencode --wait 300
|
|
213
223
|
|
|
214
224
|
piquota omarchy # write pi-*.json records for Omarchy's Agents panel
|
|
225
|
+
piquota stats # durable token history: last frozen day, families, totals
|
|
226
|
+
piquota stats --json --month 2026-11
|
|
227
|
+
# read-only history feed: months, one month's days
|
|
228
|
+
# per family/slot/model, all-time totals, notes
|
|
229
|
+
piquota stats --rebuild-totals # recompute totals.json from the frozen month files
|
|
215
230
|
```
|
|
216
231
|
|
|
217
232
|
Colours: green above 50% remaining, yellow 20โ50%, red below 20%.
|
|
@@ -230,10 +245,10 @@ Inside Pi:
|
|
|
230
245
|
|
|
231
246
|
| Command | Effect |
|
|
232
247
|
| --- | --- |
|
|
233
|
-
| `/usage` |
|
|
248
|
+
| `/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 |
|
|
234
249
|
| `/usage box` | pin the five-row box above the editor |
|
|
235
250
|
| `/usage line` | pin the compact line above the editor, in its own row |
|
|
236
|
-
| `/usage panel` | full
|
|
251
|
+
| `/usage panel` | full panel above the editor, pinned across prompts |
|
|
237
252
|
| `/usage hide` | hide the panel/line and stop background refresh until a surface is on again |
|
|
238
253
|
| `/usage status` / `nostatus` | enable/disable the footer status |
|
|
239
254
|
| `/usage refresh` | refresh only |
|
|
@@ -241,17 +256,81 @@ Inside Pi:
|
|
|
241
256
|
|
|
242
257
|
### The Pi surface
|
|
243
258
|
|
|
244
|
-
Nothing is pinned by default: `/usage`
|
|
245
|
-
above the editor and leaves the footer alone.
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
`/usage
|
|
259
|
+
Nothing is pinned by default: `/usage` opens a **fullscreen overlay** that takes
|
|
260
|
+
the keyboard, so it claims no row above the editor and leaves the footer alone.
|
|
261
|
+
It is the same `ctx.ui.custom` overlay pattern pi's `/context` uses: it fills the
|
|
262
|
+
terminal, scrolls, folds and closes on Esc (or `q`), leaving nothing behind.
|
|
263
|
+
Turn a persistent surface on with `/usage box`, `/usage line`, `/usage panel` or
|
|
264
|
+
`/usage status`; only then does the extension keep it fresh with a background
|
|
265
|
+
refresh (and a gentle refresh after each agent turn). `/usage hide` gives the row
|
|
266
|
+
back. `/usage panel` still pins the same panel flat above the editor.
|
|
267
|
+
|
|
268
|
+
The overlay paints on the same keystroke: it renders the report the extension
|
|
269
|
+
already holds, or the CLI's on-disk report cache (`~/.cache/pi-quota/usage.json`,
|
|
270
|
+
read-only) when memory is empty, and never waits for the CLI before the first
|
|
271
|
+
frame. The same forced refresh bare `/usage` always ran then runs behind it: the
|
|
272
|
+
header says `refreshing โฆ` while it does, and the update time when it lands. A
|
|
273
|
+
failed refresh is a line in the view, never a thrown error. Every provider is a section:
|
|
274
|
+
the header is the brand-coloured name line, the body lists every window with its
|
|
275
|
+
used-fraction bar, reset countdown and the token rows for today, the last seven
|
|
276
|
+
days, the prompts and the heaviest models. All sections start expanded; the fold
|
|
277
|
+
state and the page you left live for the pi session, so reopening `/usage`
|
|
278
|
+
resumes them, and no file is written.
|
|
279
|
+
|
|
280
|
+
| Key | Action |
|
|
281
|
+
| --- | --- |
|
|
282
|
+
| `โ`/`โ`, `j`/`k` | move the header cursor; a section taller than the viewport scrolls line by line first |
|
|
283
|
+
| `enter` / `space` | fold or unfold the section under the cursor |
|
|
284
|
+
| `โ`/`l`, `โ`/`h` | expand, collapse |
|
|
285
|
+
| `PgUp`/`PgDn`, `g`/`Home`, `G`/`End` | page, first section, last section |
|
|
286
|
+
| mouse wheel | scroll |
|
|
287
|
+
| `r` | refresh in place (on History: the month and the live report) |
|
|
288
|
+
| `tab`, `1` / `2` | switch between Now and History |
|
|
289
|
+
| `esc` / `q` | close |
|
|
290
|
+
|
|
291
|
+
The legend at the bottom lists every key the current view answers, and wraps
|
|
292
|
+
into a second or third row on a narrow pane instead of dropping pairs โ as few
|
|
293
|
+
rows as fit, with the pairs balanced across them. The frame
|
|
294
|
+
fits from about 60 columns up: a line wider than the terminal wraps at its
|
|
295
|
+
` ยท ` separators with a hanging indent instead of being cut, so a row never
|
|
296
|
+
loses the datum it ends with, and a resize rebuilds it. The wheel is handled by the component itself, like
|
|
297
|
+
pi-context-view: pi routes fullscreen wheel events to the focused component but
|
|
298
|
+
has no scrolling API for a custom screen, and this extension has no runtime
|
|
299
|
+
dependencies to wrap the body in a `ScrollView`.
|
|
300
|
+
|
|
301
|
+
`tab` (or `2`) opens **History**, the second page: the durable token history for
|
|
302
|
+
one month, with the same foldable sections. A provider section lists the month's
|
|
303
|
+
days, the token split by model, and โ for a multi-account provider โ one row per
|
|
304
|
+
account with its month and all-time totals (every account the family has in the
|
|
305
|
+
month *or* all-time, so one busy key does not hide the others). `โ`/`โ` (or
|
|
306
|
+
`[`/`]`) walk the browsable months โ those with a file, plus the live month,
|
|
307
|
+
which is where History opens even before its first freeze โ and `tab` (or `1`)
|
|
308
|
+
comes back to Now. The all-time
|
|
309
|
+
figure is `totals.json` (frozen through its watermark). A family's open day
|
|
310
|
+
(today) is merged from the live report only when the report was *generated* today
|
|
311
|
+
and the frozen month has no row for that family's day โ per family, so one family
|
|
312
|
+
frozen early never hides another's live row โ and a day is counted exactly once.
|
|
313
|
+
A report generated before midnight, or the older report cache painted on the first
|
|
314
|
+
frame, is skipped: its `todayTotalTokens` belong to another day, and merging them
|
|
315
|
+
under today would count a frozen yesterday twice. The tail then names the report's
|
|
316
|
+
day and says today is not merged. A missing or quarantined month is a note instead
|
|
317
|
+
of rows, a failed `stats` run shows the CLI's own stderr line (redacted) or its
|
|
318
|
+
exit status with a retry hint, and a slow fetch never moves the page off the month
|
|
319
|
+
the last keypress asked for.
|
|
320
|
+
|
|
321
|
+
History reads `piquota stats --json [--month YYYY-MM]`: the month list, one
|
|
322
|
+
month's days per family/slot/model, the all-time rollup and notes. That command
|
|
323
|
+
never takes the writer lock and never writes, so it is safe to run while a scan
|
|
324
|
+
is freezing a day.
|
|
249
325
|
|
|
250
326
|
A provider with several accounts is shown as one header (`OP-Go ยท 7 of 14 accounts
|
|
251
|
-
ok`) plus one line per account
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
327
|
+
ok`) plus one line per account with every window and its reset, e.g.
|
|
328
|
+
`#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`) has
|
|
330
|
+
no number, so its label column is blank; only real sibling slots (`opencode-go-2`
|
|
331
|
+
โฆ) are numbered `#2` โฆ `#14`. The leading glyph is the fullest window's band. The
|
|
332
|
+
overlay and the pinned panel are rendered as components, so Pi's ten-line cap on
|
|
333
|
+
plain widgets never truncates them.
|
|
255
334
|
|
|
256
335
|
`/usage box` pins the **five-row box above the editor**:
|
|
257
336
|
|
|
@@ -363,6 +442,10 @@ Rp suite) without external network daemons or closed-source intermediaries:
|
|
|
363
442
|
* **`notConfigured` vs `ok`**: unconfigured providers report `notConfigured: true`,
|
|
364
443
|
allowing consumers to cleanly omit them without displaying false-positive
|
|
365
444
|
error indicators.
|
|
445
|
+
* **`localStats`**: the tokens Pi's own session files recorded, per family,
|
|
446
|
+
shaped exactly like the fields Omarchy's Agents panel reads (today, the last
|
|
447
|
+
seven local days, all-time per-model buckets) plus a `byAccount` breakdown for
|
|
448
|
+
numbered siblings. `localStatsMeta` says how the scan went.
|
|
366
449
|
|
|
367
450
|
```json
|
|
368
451
|
{
|
|
@@ -403,6 +486,33 @@ Rp suite) without external network daemons or closed-source intermediaries:
|
|
|
403
486
|
}
|
|
404
487
|
```
|
|
405
488
|
|
|
489
|
+
The token part of the same report is abridged here (`recentDays` always carries
|
|
490
|
+
seven entries, oldest first, today last):
|
|
491
|
+
|
|
492
|
+
```json
|
|
493
|
+
{
|
|
494
|
+
"localStats": {
|
|
495
|
+
"codex": {
|
|
496
|
+
"hasLocalStats": true,
|
|
497
|
+
"hasPromptStats": true,
|
|
498
|
+
"todayTotalTokens": 14172,
|
|
499
|
+
"todayPrompts": 3,
|
|
500
|
+
"todaySessions": 1,
|
|
501
|
+
"todayTokensByModel": { "gpt-5.1-codex": 14172 },
|
|
502
|
+
"weekTokensByModel": { "gpt-5.1-codex": 118422 },
|
|
503
|
+
"recentDays": [{ "date": "2026-10-27", "messageCount": 0 }],
|
|
504
|
+
"totalPrompts": 1200,
|
|
505
|
+
"totalSessions": 42,
|
|
506
|
+
"activeDays": 11,
|
|
507
|
+
"activeDates": ["2026-10-27"],
|
|
508
|
+
"modelUsage": { "gpt-5.1-codex": { "inputTokens": 4, "outputTokens": 425, "cacheReadInputTokens": 6838, "cacheCreationInputTokens": 6905 } },
|
|
509
|
+
"byAccount": { "": { "todayTokens": 14172, "weekTokens": 118422, "todayPrompts": 3, "totalPrompts": 1200, "totalTokens": 48812210 } }
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
"localStatsMeta": { "files": 604, "scanned": 1, "reused": 603, "bytesRead": 3042, "durationMs": 9, "partial": false, "budgetMs": 12000, "frozenDays": 0, "locked": false }
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
406
516
|
### Each provider refreshes on its own clock
|
|
407
517
|
|
|
408
518
|
Anthropic's usage endpoint answers `429` when polled every minute; Codex,
|
|
@@ -434,7 +544,9 @@ piquota omarchy # write pi-claude.json, pi-codex.json, ... once
|
|
|
434
544
|
|
|
435
545
|
- Records are named `pi-<family>.json`, so they never overwrite Omarchy's own
|
|
436
546
|
`claude.json` / `codex.json`. They hold plan names, used percentages, window
|
|
437
|
-
labels
|
|
547
|
+
labels, reset times, and โ when the token scan has run โ aggregate token counts
|
|
548
|
+
per local day and per model (`hasLocalStats`, `recentDays`, `modelUsage`).
|
|
549
|
+
No prompt text, session path, account or e-mail address is copied.
|
|
438
550
|
- A provider with a failing sign-in shows a status and a help line instead of
|
|
439
551
|
limits. A provider that is not configured gets no record, and a stale
|
|
440
552
|
`pi-*.json` is removed on the next run.
|
|
@@ -442,13 +554,224 @@ piquota omarchy # write pi-claude.json, pi-codex.json, ... once
|
|
|
442
554
|
`auth.json`) still gets **one** record: every healthy account contributes only
|
|
443
555
|
the window that constrains it, titled `#<slot> ยท Session|Weekly|Monthly`, and
|
|
444
556
|
failed accounts add no rows but are summarised by reason, e.g.
|
|
445
|
-
`#8โ#10 key rejected ยท #11โ#14 no Go plan`.
|
|
557
|
+
`#8โ#10 key rejected ยท #11โ#14 no Go plan`. The unnumbered base entry keeps a
|
|
558
|
+
blank `id` and its limits are untitled; only real sibling slots carry
|
|
559
|
+
`#<slot>`. The same record also carries
|
|
560
|
+
`accounts[{id, ok, status, limits}]` with every window of every account
|
|
561
|
+
(`status` is a short reason such as `Key rejected` for a failed one), which
|
|
562
|
+
Omarchy's built-in panel ignores and this package's own plugin draws.
|
|
446
563
|
- Omarchy's native collector already tracks Claude with richer local statistics, so
|
|
447
564
|
`piquota omarchy` automatically skips `claude` and cleans up duplicate `pi-claude.json`
|
|
448
565
|
files when `claude.json` exists in the usage directory (override with `PI_QUOTA_OMARCHY_SKIP`).
|
|
449
566
|
- `PI_QUOTA_OMARCHY_DIR` overrides the output directory. `./install.sh --uninstall`
|
|
450
567
|
removes the timer and the `pi-*.json` records.
|
|
451
568
|
|
|
569
|
+
### The AI Usage bar plugin
|
|
570
|
+
|
|
571
|
+
The package also ships its own Omarchy bar widget, `cad0p.pi-usage` ("AI
|
|
572
|
+
Usage", in [`omarchy/cad0p.pi-usage/`](omarchy/cad0p.pi-usage/)). It is derived
|
|
573
|
+
from Omarchy's built-in Agents plugin (MIT, by Omarchy) and reuses the shell's
|
|
574
|
+
own panel components, so it looks the same, but it has no tabs: every provider
|
|
575
|
+
is a collapsible section of one scrolling panel.
|
|
576
|
+
|
|
577
|
+
- **Header row**: the provider's mark, name and plan, the headline percentage
|
|
578
|
+
with its window and reset (`Weekly ยท 1d`), a chip when something is wrong
|
|
579
|
+
(`Unavailable`, `7 down`), and the headline meter while folded. The headline is
|
|
580
|
+
the fullest window; for a multi-account provider it is the healthy account
|
|
581
|
+
with the most room left. `/usage` does not collapse a family like that: its
|
|
582
|
+
panel lists one line per account, so this headline is the bar's own summary.
|
|
583
|
+
- **Expanded**: the status card, LIMITS with the built-in meters and countdowns,
|
|
584
|
+
then for a multi-account record one row per account with every window
|
|
585
|
+
(`#2 session 2% 10m ยท week 10% 6d ยท month 90% 19d`, failed accounts dimmed
|
|
586
|
+
with their reason), then TOKENS BY DAY and TOKENS BY MODEL whenever the record
|
|
587
|
+
carries local statistics, drawn exactly as the built-in draws them.
|
|
588
|
+
- **Which section is open**: on first use, only the provider closest to a limit
|
|
589
|
+
(ties go to the earliest reset). Every expand or collapse you make sticks,
|
|
590
|
+
across popup opens and shell restarts, in
|
|
591
|
+
`~/.local/state/omarchy/settings/cad0p.pi-usage.json`.
|
|
592
|
+
- **Keys**: `j`/`k` or the arrows walk the section headers (a tall open section
|
|
593
|
+
scrolls first), Enter or Space toggles, `l`/`h` expand/collapse, `r` refreshes,
|
|
594
|
+
Tab moves to the neighbouring bar panel, Esc closes. The mouse wheel scrolls;
|
|
595
|
+
a click on a header toggles it.
|
|
596
|
+
- **Records**: every `*.json` in the usage directory, Omarchy's own included.
|
|
597
|
+
A provider is hidden by `providers.<id>.enabled = false` in the widget's
|
|
598
|
+
settings, the same switch the built-in uses. The plugin does **not** support
|
|
599
|
+
the built-in Agents widget's synced cross-device aggregation
|
|
600
|
+
(`syncMode`, `syncDir`, `syncFileName`, `syncDeviceId`). Users who rely on
|
|
601
|
+
synced aggregation should keep the built-in widget; this plugin is not a full
|
|
602
|
+
replacement for it.
|
|
603
|
+
- **Refresh**: the systemd timer and the Pi extension already publish the
|
|
604
|
+
`pi-*.json` records, so the plugin never schedules `piquota`. `r`, a middle
|
|
605
|
+
click or `omarchy-shell cad0p.pi-usage refresh` starts
|
|
606
|
+
`piquota-omarchy.service` (or runs the package's `piquota omarchy` when there is
|
|
607
|
+
no unit). Omarchy's collectors (`omarchy-agent-usage-update`) run on the
|
|
608
|
+
`refreshIntervalSec` timer and for a record's `retryAdvised` retry, and only
|
|
609
|
+
while the built-in Agents widget is off the bar (`omarchyCollectors`: `Auto`,
|
|
610
|
+
`On` or `Off`, where `Off` never runs them), so keeping both never doubles the
|
|
611
|
+
work.
|
|
612
|
+
|
|
613
|
+
Install it by linking it into Omarchy's plugin directory; the link points into
|
|
614
|
+
the pi package when there is one, so `pi update` updates the plugin too:
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
./install.sh --omarchy-plugin # symlink ~/.config/omarchy/plugins/cad0p.pi-usage (idempotent)
|
|
618
|
+
omarchy plugin enable cad0p.pi-usage # put it on the bar (right section)
|
|
619
|
+
omarchy bar put cad0p.pi-usage --after omarchy.agents # or: right next to the built-in Agents icon
|
|
620
|
+
omarchy plugin disable omarchy.agents # optional: drop the built-in widget
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
Without the installer, the link is
|
|
624
|
+
`ln -s ~/.pi/agent/npm/node_modules/@cad0p/pi-usage/omarchy/cad0p.pi-usage ~/.config/omarchy/plugins/cad0p.pi-usage`
|
|
625
|
+
followed by `omarchy-shell shell rescanPlugins`. To hide providers, pass the whole
|
|
626
|
+
object, as with the built-in:
|
|
627
|
+
`omarchy bar set cad0p.pi-usage providers '{"fireworks":{"enabled":false}}' --json`.
|
|
628
|
+
Remove it with `omarchy plugin disable cad0p.pi-usage` and
|
|
629
|
+
`rm ~/.config/omarchy/plugins/cad0p.pi-usage` (`./install.sh --uninstall` removes
|
|
630
|
+
the link too); `omarchy plugin enable omarchy.agents` brings the built-in back.
|
|
631
|
+
|
|
632
|
+
IPC: `omarchy-shell cad0p.pi-usage <open|close|toggle|refresh>`, plus
|
|
633
|
+
`expand <id>` / `collapse <id>` (e.g. `expand pi-opencode-go`).
|
|
634
|
+
|
|
635
|
+
`tests/omarchy-harness/render.sh [out-dir] [--light]` renders the panel offscreen
|
|
636
|
+
to PNGs against fixture records, with Omarchy's real components and the current
|
|
637
|
+
theme, next to the built-in panel for comparison. It never touches the running
|
|
638
|
+
shell.
|
|
639
|
+
|
|
640
|
+
## Token stats from Pi's own sessions
|
|
641
|
+
|
|
642
|
+
Pi writes every assistant message's `usage` block into
|
|
643
|
+
`~/.pi/agent/sessions/**.jsonl` (plus `~/.omp/agent/sessions` when the omp fork
|
|
644
|
+
is installed; `PI_CODING_AGENT_DIR` relocates the Pi agent directory). piQuota
|
|
645
|
+
folds those messages into tokens per local day and per model, per provider, and
|
|
646
|
+
shows them next to the quota:
|
|
647
|
+
|
|
648
|
+
| Where | What |
|
|
649
|
+
| --- | --- |
|
|
650
|
+
| `piquota --json` | `localStats.<family>` โ the Omarchy-shaped counters plus `byAccount` |
|
|
651
|
+
| `/usage` panel | a `tokens` row per provider: today, 7 days, prompts, then the top 3 models of the week |
|
|
652
|
+
| `piquota omarchy` | the optional fields Omarchy's Agents panel draws as "Tokens by day" and "Tokens by model" |
|
|
653
|
+
| `piquota stats` | the durable history: last frozen day, families, prompts, session-days |
|
|
654
|
+
| `/usage` โ `tab` | History: per provider, the month's days and models, account totals for a multi-account family, and the all-time rollup |
|
|
655
|
+
| `piquota stats --json` | the same history as a read-only JSON feed (`--month YYYY-MM` selects a month) |
|
|
656
|
+
|
|
657
|
+
What counts: one assistant message with a non-zero token count is one prompt,
|
|
658
|
+
and its tokens are `input + output + cacheRead + cacheWrite` โ the same sum the
|
|
659
|
+
Omarchy panel renders. Messages are bucketed by **local** date (the panel calls
|
|
660
|
+
the last row "Today"). Only providers piQuota can bill are counted:
|
|
661
|
+
`anthropic`, `openai-codex`, `antigravity`, `opencode-go` and their numbered
|
|
662
|
+
siblings, so `opencode-go-7` is account `#7`. Providers it does not manage
|
|
663
|
+
(`deepinfra`, `xai`, ...) are ignored, and so is the custom `claude-bridge`
|
|
664
|
+
provider: its tokens are not a Pi subscription.
|
|
665
|
+
|
|
666
|
+
**Two stores, two lifetimes.** Open days (normally just today) live in the
|
|
667
|
+
disposable scan cache. Once a local day is over it is frozen into a durable
|
|
668
|
+
history that no rescan, no cache wipe and no session deletion can rewrite. The
|
|
669
|
+
history is Pi data, so it lives next to Pi's agent directory:
|
|
670
|
+
|
|
671
|
+
| Path | What |
|
|
672
|
+
| --- | --- |
|
|
673
|
+
| `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage/totals.json` | all-time rollup per family and account (models, prompts, session-days, active dates) plus `lastFrozenDay` |
|
|
674
|
+
| `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage/tokens-YYYY-MM.json` | the frozen days of one local month, keyed day โ family โ slot โ model |
|
|
675
|
+
| `~/.cache/pi-quota/local-sessions.json` | per-session-file size, mtime, byte offset and copy fingerprint, open days, and pending late deltas for unreadable months |
|
|
676
|
+
|
|
677
|
+
> `~/.pi` is often a git repository. Add `agent/pi-usage/` to that repo's
|
|
678
|
+
> `.gitignore` if you do not want your token counts committed.
|
|
679
|
+
|
|
680
|
+
Freezing: every run moves each day before today that is not frozen yet into its
|
|
681
|
+
month file and into `totals.json`, then drops it from the per-file cache. The
|
|
682
|
+
month file is written first and the totals second; the `lastFrozenDay` watermark
|
|
683
|
+
makes a restart re-freeze a half-written day idempotently (the month write is a
|
|
684
|
+
SET per day) instead of adding it twice. A late write to an already-frozen day โ
|
|
685
|
+
a session that appends just past midnight โ increments both stores directly; if
|
|
686
|
+
its month is temporarily unreadable, the delta stays in the cache until a later
|
|
687
|
+
run can write it. Such a held delta lives only in the disposable cache: if the
|
|
688
|
+
cache is lost before the month becomes writable, that delta is dropped, because
|
|
689
|
+
the bytes of a frozen day are never read again. A crash between the month and totals writes can double that
|
|
690
|
+
one small delta. A budget-truncated scan never freezes: history is only written
|
|
691
|
+
from complete runs.
|
|
692
|
+
|
|
693
|
+
Consequences, all covered by tests: history survives session deletion and cache
|
|
694
|
+
loss. A lost or corrupt `totals.json` is moved aside as
|
|
695
|
+
`totals.json.corrupt-<ts>` and **rebuilt from the month files**, which are the
|
|
696
|
+
authoritative record of frozen days โ not from the session files, because a
|
|
697
|
+
session deleted since then would otherwise shrink history. Frozen days are never
|
|
698
|
+
recomputed, so a replaced session file cannot rewrite them either.
|
|
699
|
+
|
|
700
|
+
Damaged month files are handled without rebuilding frozen days from sessions. A
|
|
701
|
+
month that cannot be *read* (permissions, I/O) is left alone: the run warns, keeps
|
|
702
|
+
its open days in the cache, and does not freeze them until the month is readable.
|
|
703
|
+
Other history proceeds normally, and this alone does not force a full session
|
|
704
|
+
rescan. A rollup rebuild never writes `totals.json` from an incomplete month set.
|
|
705
|
+
|
|
706
|
+
A *corrupt* or foreign-version month is moved aside by the lock owner (never by a
|
|
707
|
+
read-only run) to `.corrupt-<ts>`. Its warning names the file and directs you to copy the
|
|
708
|
+
`.corrupt-<ts>` backup back to the original month path if recoverable, or to delete
|
|
709
|
+
the backup to abandon those counts. Until one of the two happens, freezing stops at
|
|
710
|
+
that month for *every* later day too (the frozen watermark only moves forward), so
|
|
711
|
+
later days stay open in the cache and each run repeats the warning; once the backup
|
|
712
|
+
is restored or deleted, the held days freeze on the next run. Frozen days are never rebuilt from sessions: their
|
|
713
|
+
daily rows are missing (shown as 0) in `recentDays`, while `totals.json` retains
|
|
714
|
+
the all-time counts, so the two views may disagree. `piquota stats --rebuild-totals`
|
|
715
|
+
refuses while a quarantined month has not been restored. A lost *month* file
|
|
716
|
+
loses that month's daily detail; normal runs keep the all-time totals, and a
|
|
717
|
+
rebuild can recover counts from every remaining month file even if sessions are
|
|
718
|
+
gone. Rebuild refuses when there is no month file at all while `totals.json`
|
|
719
|
+
still has data, because replacing that rollup with an empty one would turn a
|
|
720
|
+
missing directory into permanently lost history. A normal run reads only
|
|
721
|
+
`totals.json` plus the month files covering the last seven days (current month,
|
|
722
|
+
and the previous one early in a month), so years of history never slow a run: on
|
|
723
|
+
this machine 45 frozen days cost ~28 KB in total โ `totals.json` 6.9 KB and two
|
|
724
|
+
month files of 5.4 KB and 16 KB. A day inside that window is read from its month
|
|
725
|
+
file once frozen, so late writes appear in "tokens by day" without waiting for
|
|
726
|
+
the week to roll over.
|
|
727
|
+
|
|
728
|
+
Cost: the first run streams each session file once โ 1.56 GB across 608 files
|
|
729
|
+
in 3.0 s here; later runs read only the bytes appended since the previous scan
|
|
730
|
+
(59 ms of scanning for the whole store, ~20 KB read), and every byte is parsed
|
|
731
|
+
once in normal operation. The CLI's own wall time is dominated by the provider
|
|
732
|
+
calls either way.
|
|
733
|
+
|
|
734
|
+
What forces a file to be read from its first byte (a *rescan*, in which frozen
|
|
735
|
+
days are skipped and only open days are rebuilt): the cache missing or corrupt,
|
|
736
|
+
the file replaced or now shorter, a path the cache has never seen (a copy, a
|
|
737
|
+
move, a restored backup โ its old days were already frozen under its previous
|
|
738
|
+
path), or the same size with a new mtime (an in-place rewrite of the same
|
|
739
|
+
length). An append only reads the new bytes. The one rewrite this cannot see is
|
|
740
|
+
one that restores both the size *and* the mtime, which no stat-based check can
|
|
741
|
+
detect. The cache is atomic and `0600`.
|
|
742
|
+
|
|
743
|
+
Two writers are routine here (the systemd timer and `/usage`), so a run takes an
|
|
744
|
+
exclusive lock โ `local-sessions.json.lock`, with a 2-minute stale timeout โ
|
|
745
|
+
before it reads the history. The file holds an owner token, and a live run
|
|
746
|
+
refreshes its mtime as it walks the store, so a slow scan is never mistaken for a
|
|
747
|
+
dead one. A run that cannot take the lock reports the numbers already on disk and
|
|
748
|
+
writes nothing; a run that is taken over mid-scan notices on its next refresh and
|
|
749
|
+
also writes nothing (it never deletes its successor's lock).
|
|
750
|
+
|
|
751
|
+
Duplicate *sessions* are handled two ways. A byte-identical copy of a session
|
|
752
|
+
file (a copy, a restored backup, a sync tool) is recognised by a (size, first
|
|
753
|
+
32 KB, last 32 KB) fingerprint and contributes nothing; anything appended to it
|
|
754
|
+
later is counted normally, and if the original is deleted the copy is adopted and
|
|
755
|
+
read on the next run. A fingerprint can collide โ two same-size sessions that
|
|
756
|
+
differ only in the middle โ so a candidate is verified by streaming both files
|
|
757
|
+
before it is called a copy; that full comparison runs only on the rare duplicate.
|
|
758
|
+
A *fork* that copies a prefix and then diverges has its own size and tail, so it
|
|
759
|
+
is counted as a session of its own, duplicated prefix included: on this machine
|
|
760
|
+
(611 files, 186,443 assistant messages) only 11 message ids appeared in more than
|
|
761
|
+
one file (0.006%), none of them on today, so message-id de-duplication would cost
|
|
762
|
+
megabytes to correct a handful of tokens and is not implemented.
|
|
763
|
+
|
|
764
|
+
The scan has a wall-clock budget (12 s, or `PI_QUOTA_LOCAL_STATS_BUDGET_MS`) so
|
|
765
|
+
the first run cannot push `/usage` past its 30 s exec timeout; when it runs out
|
|
766
|
+
the report carries `localStatsMeta.partial`, the panel marks the numbers
|
|
767
|
+
`scan partial`, and nothing is frozen that run. `PI_QUOTA_LOCAL_STATS=0` turns
|
|
768
|
+
the scan off.
|
|
769
|
+
|
|
770
|
+
Session counts are **session-days**, not distinct session files: a day is frozen
|
|
771
|
+
as aggregate rows, without the list of files that fed it, so a session that runs
|
|
772
|
+
past midnight counts once on each day it appears in. `todaySessions` is exact
|
|
773
|
+
(those files are still open); `totalSessions` is the sum of the per-day counts.
|
|
774
|
+
|
|
452
775
|
## Layout
|
|
453
776
|
|
|
454
777
|
```
|
|
@@ -465,6 +788,8 @@ src/providers/antigravity-oauth.js in-memory refresh with Google's public clien
|
|
|
465
788
|
src/providers/backoff.js per-family throttle state
|
|
466
789
|
src/sticky.js last published / last good snapshots and throttle recovery
|
|
467
790
|
src/engine.js collectQuota() -> one normalized report
|
|
791
|
+
src/local/pi-sessions.js token counts from Pi's session files, incremental scan
|
|
792
|
+
src/local/history.js frozen local-day history: month files + all-time rollup
|
|
468
793
|
src/model.js window normalization, percent and reset parsing
|
|
469
794
|
src/render/{theme,panel}.js colors, thresholds, rings, bars, boxed panel
|
|
470
795
|
src/cache.js report cache at ~/.cache/pi-quota/usage.json
|
|
@@ -472,9 +797,12 @@ src/omarchy/record.js pure mapping from the report to Omarchy usage rec
|
|
|
472
797
|
src/omarchy/publish.js atomic writer for pi-*.json, and stale-record cleanup
|
|
473
798
|
src/refresh.js per-family refresh clocks, and the merge back into one report
|
|
474
799
|
src/cli/args.js argument parsing, and the flag/positional split
|
|
800
|
+
src/cli/commands/stats.js `piquota stats` and `--rebuild-totals`
|
|
475
801
|
src/exec.js the one place that spawns gh CLI binary
|
|
476
802
|
src/http.js fetch wrapper: timeouts, JSON, redaction
|
|
477
803
|
contrib/systemd/ user service and timer behind `install.sh --omarchy-timer`
|
|
804
|
+
omarchy/cad0p.pi-usage/ the AI Usage Omarchy bar plugin (QML; UsageLogic.js is its pure logic)
|
|
805
|
+
tests/omarchy-harness/ offscreen renderer for the plugin, fixtures included
|
|
478
806
|
bin/piquota.js the only CLI
|
|
479
807
|
extensions/quota-panel.ts Pi TUI line and /usage
|
|
480
808
|
```
|
|
@@ -498,7 +826,7 @@ extensions/quota-panel.ts Pi TUI line and /usage
|
|
|
498
826
|
## Tests
|
|
499
827
|
|
|
500
828
|
```bash
|
|
501
|
-
node --test tests/*.test.mjs #
|
|
829
|
+
node --test tests/*.test.mjs # 401 tests, fake tokens only, no network
|
|
502
830
|
```
|
|
503
831
|
|
|
504
832
|
Modules covered: `auth.json` parsing and de-duplication, the Claude Code store
|
|
@@ -508,9 +836,14 @@ Actions plan resolution, opt-in gating and degradation, the two Antigravity
|
|
|
508
836
|
failures and the OpenCode degradation), the dashboard parser's three
|
|
509
837
|
strategies, the Firefox cookie reader against a synthetic SQLite database, the
|
|
510
838
|
Antigravity refresh (in-memory only), sticky and last-good snapshots resilience,
|
|
511
|
-
the Omarchy record mapping and atomic writer, the
|
|
512
|
-
|
|
513
|
-
|
|
839
|
+
the Omarchy record mapping and atomic writer, the Omarchy plugin's logic,
|
|
840
|
+
manifest and marks, the local token scan (family and slot mapping, incremental
|
|
841
|
+
append, shrink, deletion, local-day bucketing, cache corruption), the durable
|
|
842
|
+
history (month partitioning, freezing at midnight, late appends to frozen days,
|
|
843
|
+
cache loss, a crash between the month and totals writes, corrupt-history
|
|
844
|
+
quarantine, rebuild-totals), the renderers, the Pi extension contract, the
|
|
845
|
+
per-family refresh clocks, argument parsing, and the structure of these
|
|
846
|
+
documents themselves (tables, fences, links, anchors).
|
|
514
847
|
|
|
515
848
|
## Acknowledgments & Prior Art
|
|
516
849
|
|