quota-axi 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -65
- package/dist/src/advice.d.ts +2 -0
- package/dist/src/advice.js +49 -0
- package/dist/src/advice.js.map +1 -1
- package/dist/src/args.d.ts +19 -0
- package/dist/src/args.js +55 -4
- package/dist/src/args.js.map +1 -1
- package/dist/src/cache.d.ts +54 -4
- package/dist/src/cache.js +349 -58
- package/dist/src/cache.js.map +1 -1
- package/dist/src/cli.js +4 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands.d.ts +11 -1
- package/dist/src/commands.js +150 -20
- package/dist/src/commands.js.map +1 -1
- package/dist/src/interpretation.js +40 -0
- package/dist/src/interpretation.js.map +1 -1
- package/dist/src/lib/fetch-lock.d.ts +59 -0
- package/dist/src/lib/fetch-lock.js +207 -0
- package/dist/src/lib/fetch-lock.js.map +1 -0
- package/dist/src/lib/fs.d.ts +9 -0
- package/dist/src/lib/fs.js +25 -0
- package/dist/src/lib/fs.js.map +1 -1
- package/dist/src/lib/input-trace.d.ts +23 -0
- package/dist/src/lib/input-trace.js +69 -0
- package/dist/src/lib/input-trace.js.map +1 -0
- package/dist/src/lib/reuse-context.d.ts +16 -0
- package/dist/src/lib/reuse-context.js +80 -0
- package/dist/src/lib/reuse-context.js.map +1 -0
- package/dist/src/lib/source-attempts.d.ts +4 -2
- package/dist/src/lib/source-attempts.js +3 -2
- package/dist/src/lib/source-attempts.js.map +1 -1
- package/dist/src/pace.d.ts +4 -1
- package/dist/src/pace.js +30 -2
- package/dist/src/pace.js.map +1 -1
- package/dist/src/providers/claude.js +4 -0
- package/dist/src/providers/claude.js.map +1 -1
- package/dist/src/providers/codex.js +76 -31
- package/dist/src/providers/codex.js.map +1 -1
- package/dist/src/providers/common.d.ts +13 -0
- package/dist/src/providers/common.js +26 -0
- package/dist/src/providers/common.js.map +1 -1
- package/dist/src/providers/copilot-cli-credential.js +8 -1
- package/dist/src/providers/copilot-cli-credential.js.map +1 -1
- package/dist/src/providers/copilot.js +10 -5
- package/dist/src/providers/copilot.js.map +1 -1
- package/dist/src/providers/cursor-cli-credential.js +6 -1
- package/dist/src/providers/cursor-cli-credential.js.map +1 -1
- package/dist/src/providers/cursor.js +34 -5
- package/dist/src/providers/cursor.js.map +1 -1
- package/dist/src/providers/devin.js +4 -1
- package/dist/src/providers/devin.js.map +1 -1
- package/dist/src/providers/grok.js +6 -5
- package/dist/src/providers/grok.js.map +1 -1
- package/dist/src/providers/index.js +2 -0
- package/dist/src/providers/index.js.map +1 -1
- package/dist/src/providers/kimi-code-cli-credential.js +2 -0
- package/dist/src/providers/kimi-code-cli-credential.js.map +1 -1
- package/dist/src/providers/minimax.js +2 -0
- package/dist/src/providers/minimax.js.map +1 -1
- package/dist/src/providers/muse-cache-context.d.ts +17 -0
- package/dist/src/providers/muse-cache-context.js +43 -0
- package/dist/src/providers/muse-cache-context.js.map +1 -0
- package/dist/src/providers/muse-keychain-credential.d.ts +44 -0
- package/dist/src/providers/muse-keychain-credential.js +150 -0
- package/dist/src/providers/muse-keychain-credential.js.map +1 -0
- package/dist/src/providers/muse-read-gate.d.ts +62 -0
- package/dist/src/providers/muse-read-gate.js +199 -0
- package/dist/src/providers/muse-read-gate.js.map +1 -0
- package/dist/src/providers/muse.d.ts +84 -0
- package/dist/src/providers/muse.js +975 -0
- package/dist/src/providers/muse.js.map +1 -0
- package/dist/src/providers/pi-codex-credential.js +2 -0
- package/dist/src/providers/pi-codex-credential.js.map +1 -1
- package/dist/src/providers/pi-kimi-credential.js +2 -0
- package/dist/src/providers/pi-kimi-credential.js.map +1 -1
- package/dist/src/providers/pi-xai-credential.js +2 -0
- package/dist/src/providers/pi-xai-credential.js.map +1 -1
- package/dist/src/render.js +13 -1
- package/dist/src/render.js.map +1 -1
- package/dist/src/skill.js +3 -2
- package/dist/src/skill.js.map +1 -1
- package/dist/src/tui-live.d.ts +6 -2
- package/dist/src/tui-live.js +6 -1
- package/dist/src/tui-live.js.map +1 -1
- package/dist/src/tui.js +66 -29
- package/dist/src/tui.js.map +1 -1
- package/dist/src/types.d.ts +11 -4
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -1
- package/package.json +1 -1
- package/skills/quota-axi/SKILL.md +3 -2
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ Quota CLI for agents - designed with [AXI](https://axi.md) (Agent eXperience Int
|
|
|
15
15
|
Agents need quota state before they choose where work can safely run.
|
|
16
16
|
Vendor dashboards are not shaped for shell automation, and local CLIs expose different windows, resets, and auth sources.
|
|
17
17
|
|
|
18
|
-
quota-axi reports local Claude, Codex, Cursor, GitHub Copilot, Grok, Kimi, Z.AI, Alibaba, OpenCode Go, Antigravity (`agy`), Command Code, MiniMax, MiMo, DeepSeek, OpenRouter, ElevenLabs, and
|
|
19
|
-
It is data only: it never routes, recommends a provider, model, harness, credential, or route, proxies, intercepts, logs in, imports browser cookies, or mints or rotates a credential. When the same stored access token is expired, carries a refresh token, and is definitively rejected, quota-axi may delegate renewal to that vendor's own non-interactive CLI command and re-read the result ([Delegated credential refresh](#delegated-credential-refresh)). Default output has no ordering preference. The opt-in `models --sort runway` surface applies only its documented deterministic comparator to quota evidence, preserves all evidence and explicit ties, and is not a recommendation. It publishes one derived per-scope comparative selection signal, [`selection`](#per-scope-selection-signal), as data computed from figures it already reports; the consumer, not quota-axi, does any routing or ranking with it.
|
|
18
|
+
quota-axi reports local Claude, Codex, Cursor, GitHub Copilot, Grok, Kimi, Z.AI, Alibaba, OpenCode Go, Antigravity (`agy`), Command Code, MiniMax, MiMo, DeepSeek, OpenRouter, ElevenLabs, Devin, and Muse quota windows in one [AXI](https://axi.md)-shaped call.
|
|
19
|
+
It is data only: it never routes, recommends a provider, model, harness, credential, or route, proxies, intercepts, logs in, imports browser cookies, or mints or rotates a credential. Muse's only quota read also issues an API key server-side, which quota-axi discards unread and rate-bounds ([Muse provider notes](#provider-notes)). When the same stored access token is expired, carries a refresh token, and is definitively rejected, quota-axi may delegate renewal to that vendor's own non-interactive CLI command and re-read the result ([Delegated credential refresh](#delegated-credential-refresh)). Default output has no ordering preference. The opt-in `models --sort runway` surface applies only its documented deterministic comparator to quota evidence, preserves all evidence and explicit ties, and is not a recommendation. It publishes one derived per-scope comparative selection signal, [`selection`](#per-scope-selection-signal), as data computed from figures it already reports; the consumer, not quota-axi, does any routing or ranking with it.
|
|
20
20
|
|
|
21
21
|
- **Official sources** - quota-axi reads local provider auth sources and calls first-party quota, usage, billing, entitlement, local loopback, or read-only credential-liveness endpoints used by the local agents, with read-only CLI probes where applicable. Vendor-command boundaries and the explicit inference exception are documented under [Safety guarantees](#safety-guarantees).
|
|
22
22
|
- **Local first** - quota and auth reports run on the machine that holds the credentials; their network calls go to first-party provider endpoints, never a third-party relay.
|
|
@@ -25,8 +25,8 @@ It is data only: it never routes, recommends a provider, model, harness, credent
|
|
|
25
25
|
|
|
26
26
|
## Quick Start
|
|
27
27
|
|
|
28
|
-
**Credential-source note:** Claude Code, the Cursor CLI (`cursor-agent`),
|
|
29
|
-
quota-axi does not read native secure-store values until the user grants permission, so Claude quota, CLI-only Cursor auth,
|
|
28
|
+
**Credential-source note:** Claude Code, the Cursor CLI (`cursor-agent`), Copilot CLI, and Muse Code keep live tokens in native secure stores on supported platforms. Linux `cursor-agent` stores its access token in `~/.config/cursor/auth.json` (or the XDG/`$CURSOR_CLI_CONFIG` override); Copilot CLI uses Windows Credential Manager on Windows and the macOS Keychain on macOS; Muse Code on macOS stores the OAuth token in the login Keychain item its `auth.json` `storage: "keychain"` records point to.
|
|
29
|
+
quota-axi does not read native secure-store values until the user grants permission, so Claude quota, CLI-only Cursor auth, Copilot CLI auth, or Muse quota can stay stale or appear unavailable when no other usable credential exists. On Linux it reads only the Cursor auth file's `accessToken` and never its refresh token.
|
|
30
30
|
Run `quota-axi --allow-keychain-prompt` once and approve macOS Keychain access with "Always Allow"; on Windows the same flag permits the Copilot CLI Credential Manager read without an OS prompt.
|
|
31
31
|
After a successful read, future non-interactive quota calls reuse the corresponding account-scoped grant without requiring the flag. Claude grants are also profile-scoped; legacy Claude markers created before account-pinned lookup are not reused.
|
|
32
32
|
|
|
@@ -274,7 +274,7 @@ pnpm run dev
|
|
|
274
274
|
## Agent Skill
|
|
275
275
|
|
|
276
276
|
The npm package includes `skills/quota-axi/SKILL.md`, the same installable skill recommended above.
|
|
277
|
-
It is generated from `src/skill.ts
|
|
277
|
+
It is generated from `src/skill.ts` as a minimal stub: discovery frontmatter, what quota-axi is, when to reach for it, and pointers to the live CLI (`npx -y quota-axi`, `--help`, `--json` / `--full`). CLI output is the source of truth. Do not restate CLI instructions or claim that quota-axi recommends or ranks. Update the skill with `pnpm run build:skill` and verify it with `pnpm run build:skill -- --check`.
|
|
278
278
|
|
|
279
279
|
## How It Works
|
|
280
280
|
|
|
@@ -304,10 +304,10 @@ It is generated from `src/skill.ts`; update it with `pnpm run build:skill` and v
|
|
|
304
304
|
```
|
|
305
305
|
|
|
306
306
|
- **Live first** - provider HTTP calls and Antigravity's structured print command use 15 second timeouts, Codex JSON-RPC and Antigravity loopback reads use shorter per-call timeouts, and stale cache fallback is per provider.
|
|
307
|
-
- **Host network policy** - Claude, Codex, Copilot, Cursor, Grok, Z.AI, Command Code, MiniMax, DeepSeek, OpenRouter, ElevenLabs, and
|
|
308
|
-
- **Opt-in secure-store access** - macOS Claude, Cursor CLI,
|
|
307
|
+
- **Host network policy** - Claude, Codex, Copilot, Cursor, Grok, Z.AI, Command Code, MiniMax, DeepSeek, OpenRouter, ElevenLabs, Devin, and Muse outbound HTTP calls honor standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables (including lowercase forms). This only follows the user's configured egress path; quota-axi does not expose a proxy service or print proxy URLs. New remote fetches go through `src/lib/http.ts` rather than global `fetch`. The proxy path pairs the installed `undici` build's `ProxyAgent` with that same build's `fetch`, because Node's global fetch only accepts a dispatcher from the undici build Node bundles.
|
|
308
|
+
- **Opt-in secure-store access** - macOS Claude, Cursor CLI, Copilot CLI, and Muse Keychain value reads, plus Windows Copilot Credential Manager reads, are skipped on plain calls until `--allow-keychain-prompt` succeeds once for that source, then future quota calls reuse the corresponding grant.
|
|
309
309
|
- **Delegated refresh, never minted** - when the same stored access token is expired, carries a refresh token, and is definitively rejected, quota-axi may run that vendor CLI's own smallest non-interactive refresh command and re-read the store the CLI rewrote. quota-axi never performs a refresh-token exchange itself. See [Delegated credential refresh](#delegated-credential-refresh).
|
|
310
|
-
- **Partial success is success** - one provider can fail while another returns fresh or stale data, and the process still exits 0. Exit code 1 means every provider failed
|
|
310
|
+
- **Partial success is success** - one provider can fail while another returns fresh or stale data, and the process still exits 0. Exit code 1 means every provider failed and the report is still rendered. Exit code 2 is a usage error (`VALIDATION_ERROR`). `quota` is the implicit default command: a bare call or a flag-first call is read as `quota`, while `auth`, `update`, a single-token `--help`, and a version flag stay with the SDK. Slow-path routing, help, exit framing, and `update` come from `axi-sdk-js` `runAxiCli`; machine TOON/JSON stays in `src/render.ts` and command bodies in `src/commands.ts`. A bare `-v`/`-V`/`--version` is answered by `axi-sdk-js/fast-path` plus the leaf `src/version.ts` (node builtins only), which imports the CLI only on the slow path so the provider graph never loads. Keep `src/version.ts` leaf-clean.
|
|
311
311
|
- **No token equivalence** - quota-axi does not claim that one provider percentage equals another provider percentage.
|
|
312
312
|
|
|
313
313
|
## CLI Reference
|
|
@@ -322,23 +322,24 @@ It is generated from `src/skill.ts`; update it with `pnpm run build:skill` and v
|
|
|
322
322
|
|
|
323
323
|
### Flags
|
|
324
324
|
|
|
325
|
-
| Flag
|
|
326
|
-
|
|
|
327
|
-
| `--provider claude,codex,cursor,copilot,grok,kimi,zai,agy,alibaba,opencode-go,commandcode,minimax,mimo,deepseek,openrouter,elevenlabs,devin` | Scope providers; repeat to union in first-seen order (`--provider zai --provider codex` equals `--provider zai,codex`) |
|
|
328
|
-
| `--json`
|
|
329
|
-
| `--full`
|
|
330
|
-
| `--tui`
|
|
331
|
-
| `--refresh 30s\|5m\|1h`
|
|
332
|
-
| `--once`
|
|
333
|
-
| `--all`
|
|
334
|
-
| `--allow-keychain-prompt`
|
|
335
|
-
| `--allow-claude-inference`
|
|
336
|
-
| `--no-credential-refresh`
|
|
337
|
-
| `--
|
|
338
|
-
| `--
|
|
339
|
-
| `--
|
|
340
|
-
|
|
|
341
|
-
| `-
|
|
325
|
+
| Flag | Description |
|
|
326
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
327
|
+
| `--provider claude,codex,cursor,copilot,grok,kimi,zai,agy,alibaba,opencode-go,commandcode,minimax,mimo,deepseek,openrouter,elevenlabs,devin,muse` | Scope providers; repeat to union in first-seen order (`--provider zai --provider codex` equals `--provider zai,codex`) |
|
|
328
|
+
| `--json` | Emit normalized JSON instead of TOON for quota, auth, or models |
|
|
329
|
+
| `--full` | Include audit and derivation details |
|
|
330
|
+
| `--tui` | Render the live human terminal report instead of TOON (quota only) |
|
|
331
|
+
| `--refresh 30s\|5m\|1h` | Live `--tui` refresh interval, default 5m (30s-24h) |
|
|
332
|
+
| `--once` | Render one `--tui` frame and exit instead of staying live |
|
|
333
|
+
| `--all` | Draw not-set-up providers as full `--tui` cards |
|
|
334
|
+
| `--allow-keychain-prompt` | Permit native secure-store access that could prompt (macOS or Windows) |
|
|
335
|
+
| `--allow-claude-inference` | Spend one bounded native Claude inference to read env-token quota headers |
|
|
336
|
+
| `--no-credential-refresh` | Never run a vendor CLI's own non-interactive credential refresh |
|
|
337
|
+
| `--max-age 0\|90s\|2m` | Opt in to reusing a fresh reading up to this old instead of asking the vendor; `0` always asks |
|
|
338
|
+
| `--profile-only` | Read one explicitly selected Claude or Codex credential file (quota only) |
|
|
339
|
+
| `--intelligence high\|medium\|low` | Filter `models` by editorial intelligence bucket |
|
|
340
|
+
| `--sort runway` | Explicitly sort `models` by documented usable-runway evidence |
|
|
341
|
+
| `-h`, `--help` | Print terse [AXI](https://axi.md) help |
|
|
342
|
+
| `-v`, `-V`, `--version` | Print version |
|
|
342
343
|
|
|
343
344
|
### Profile-only quota reads
|
|
344
345
|
|
|
@@ -351,6 +352,54 @@ CLAUDE_CONFIG_DIR=/path/to/claude-profile quota-axi --provider claude --profile-
|
|
|
351
352
|
CODEX_HOME=/path/to/codex-profile quota-axi --provider codex --profile-only --full --json
|
|
352
353
|
```
|
|
353
354
|
|
|
355
|
+
### Fresh reuse
|
|
356
|
+
|
|
357
|
+
A burst of back-to-back reads (a dispatcher that takes a fresh reading per decision, a test run) can trip a vendor's usage-endpoint rate limit and leave the provider unmeasured until the limit clears.
|
|
358
|
+
Fresh reuse lets such a caller serve the last successful reading instead of asking the vendor again.
|
|
359
|
+
It is opt-in: with neither `--max-age` nor `QUOTA_AXI_MAX_AGE`, every `quota` and `models` read asks the vendor.
|
|
360
|
+
`--max-age <duration>` enables it for one call, and `QUOTA_AXI_MAX_AGE=<duration>` enables it for a host; the flag wins, so `--max-age 0` always asks the vendor.
|
|
361
|
+
Both accept `0`, bare seconds, or a whole-unit duration up to one hour, and a `QUOTA_AXI_MAX_AGE` that does not parse fails the read with a validation error instead of being ignored.
|
|
362
|
+
Ninety seconds absorbs a burst and moves a five-hour window's elapsed time by half a percent.
|
|
363
|
+
|
|
364
|
+
A reading is reused only when all of these hold:
|
|
365
|
+
|
|
366
|
+
- It came from the same credential selection: every environment variable a provider consults to choose a profile, store, credential, CLI, or deployment (`CREDENTIAL_SELECTION_ENV` in `src/lib/reuse-context.ts`) has the value it had, plus the same home directory and user. The values are hashed together and never stored.
|
|
367
|
+
- No local file the reading was derived from has changed. Every file quota-axi opened for that provider, present or absent, is recorded with its identity, size, and modification times (never its contents), so a login that rewrites a credential store, or a Kimi Code configuration that now names another deployment, is read again. A store read through a vendor tool (the macOS Keychain, Cursor's SQLite database, a vendor CLI) is identified by the selection that names it, the same boundary the stale-cache context uses, so an in-place Keychain login switch can be reused for at most `--max-age`. Muse is excluded from this path: its macOS credential lives in a Keychain item that is not a traced file, so a login switch cannot be detected here, and `--max-age` never serves a Muse reading. The five-minute key-endpoint ledger remains the bound on those vendor calls.
|
|
368
|
+
- Every lane the provider reported in that reading was a fresh reading with windows, so part of an account-expanded report is never served, and a failed, stale, or window-less lane makes the next read ask the vendor.
|
|
369
|
+
- It is younger than `--max-age`, and no window has reached its own reported `resetsAt`, because a number that has stopped being true is never served ([#257](https://github.com/kunchenguid/quota-axi/issues/257)).
|
|
370
|
+
|
|
371
|
+
The cache keeps one slot per provider lane, so two profiles that alternate each read the vendor; each still reuses its own reading between switches.
|
|
372
|
+
`--full` is the audit tier and account identity and source attempts are never cached, so `QUOTA_AXI_MAX_AGE` does not reach it; it reuses only when `--max-age` is passed explicitly.
|
|
373
|
+
`--profile-only` never reuses, and readings the cache excludes (Claude native inference and Copilot native secure-store readings) are never reused.
|
|
374
|
+
With reuse enabled, a live `--tui` reuses on its first frame; a scheduled refresh reuses only a reading younger than the refresh interval, so it never repeats its own previous frame and still reads the vendor at any `--refresh`, unless `--max-age` is passed explicitly.
|
|
375
|
+
Pressing `r` always reads the vendor, because it is an operator asking for a new reading now.
|
|
376
|
+
|
|
377
|
+
With reuse enabled, processes that miss the cache together make one vendor read per provider and credential selection.
|
|
378
|
+
The first creates a lock file under the cache directory, reads the vendor, and caches that provider's reading before releasing it; the others poll the cache and are answered by that reading through the same checks as any reuse.
|
|
379
|
+
A waiter gives up after 30 seconds and reads the vendor itself, and a lock whose holder has exited, or that is older than two minutes, is taken over, so a crashed or wedged holder never blocks a read.
|
|
380
|
+
If the holder's reading is not reusable (it failed, is stale, has no windows, or is a reading the cache excludes), nothing is cached and the waiters read the vendor together rather than one at a time, so a burst takes about one vendor round trip beyond the holder's, not one per waiter.
|
|
381
|
+
Leaders of different providers merge their readings into the one cache file under a short lock of their own, so one leader's write never drops another's reading and leaves its waiters to read the vendor; if that lock is not free within five seconds the write goes ahead unlocked.
|
|
382
|
+
The lock only saves vendor calls: a lost race costs an extra read, never a wrong one.
|
|
383
|
+
|
|
384
|
+
A new environment variable a provider reads to choose a credential must be listed in `CREDENTIAL_SELECTION_ENV` (`src/lib/reuse-context.ts`) or declared non-selecting in `test/reuse-context.test.ts`. A new provider file read goes through the `src/lib/fs.ts` readers or `traceInput` (`src/lib/input-trace.ts`). quota-axi's own cache is read untraced, because every write changes it. `QUOTA_AXI_SNAPSHOT` is the fixture surface on the same path.
|
|
385
|
+
|
|
386
|
+
A reused reading is a fresh reading, not a stale one: `state.status` stays `fresh`, `stale` stays `false`, and quota rows, pace, runway, and selection are derived as usual at the report's `generatedAt`.
|
|
387
|
+
It is marked honestly on every surface: `--json` adds `state.reused: true` and keeps its `state.refreshedAt` (the time the vendor answered) in the default tier, default TOON adds a `reused` attention row naming that time, and the `--tui` card title says `reused 42s`.
|
|
388
|
+
Reusing never rewrites the cache record, so its age keeps counting from the vendor's answer.
|
|
389
|
+
Serving it as fresh is honest because the caller chose the bound: a reused reading is the vendor's own answer at `refreshedAt`, no older than the `--max-age` the caller asked for, and never past a window's reset, so a consumer that needs a newer number reads `refreshedAt` or passes a smaller `--max-age`.
|
|
390
|
+
|
|
391
|
+
#### Snapshot fixtures
|
|
392
|
+
|
|
393
|
+
`QUOTA_AXI_SNAPSHOT=<file>` makes `quota` and `models` answer every provider from that file instead of any credential or vendor, for tests and fixtures of tools that consume quota-axi output.
|
|
394
|
+
The file uses the cache file format (`{"schemaVersion": 3, "providers": [...]}`), so a cache file captured from a real run is a valid fixture.
|
|
395
|
+
Each provider's snapshots are served as reused readings with `sourcesTried: ["snapshot"]`, whatever their age; a provider the file does not name reports `unavailable` with `not_in_snapshot`, and one with a window past its own reset reports `unavailable` with `snapshot_expired`.
|
|
396
|
+
A file that is missing, does not parse as that format, or holds a provider record that does not parse fails with `VALIDATION_ERROR` (exit 2) rather than reporting that provider as `not_in_snapshot`.
|
|
397
|
+
Nothing is written to the cache, and the variable cannot be combined with `--profile-only`.
|
|
398
|
+
|
|
399
|
+
```sh
|
|
400
|
+
QUOTA_AXI_SNAPSHOT=test/fixtures/quota.json quota-axi --json
|
|
401
|
+
```
|
|
402
|
+
|
|
354
403
|
### Human terminal report (`--tui`)
|
|
355
404
|
|
|
356
405
|
`quota-axi --tui` renders the same redacted report as a live human terminal view instead of TOON: a two-up provider card grid with thin quota bars and a `┃` linear-pace marker whenever pace is known. It is presentation only and is not part of the machine-readable contract.
|
|
@@ -358,7 +407,7 @@ CODEX_HOME=/path/to/codex-profile quota-axi --provider codex --profile-only --fu
|
|
|
358
407
|
- On an interactive terminal the report stays up and refreshes every 5 minutes until you press `q` (or Ctrl+C). Press `r` to refresh immediately; the footer lists both controls. `--refresh` sets the interval (30s-24h) and `--once` renders a single frame. A non-TTY stdout or stdin (pipes, CI, screenshots) always renders one frame and exits.
|
|
359
408
|
- Every refresh re-runs the same quota read as a bare `quota-axi`, including [delegated credential refresh](#delegated-credential-refresh) when a stored session has expired in the meantime. Run `quota-axi --tui --no-credential-refresh` to keep the live report strictly read-only.
|
|
360
409
|
- Live frames paint on the alternate screen and repaint immediately on terminal resize; quitting restores the screen and prints the final frame so the last report stays in scrollback.
|
|
361
|
-
- Height comes from the terminal too. When the report is taller than the terminal, the live view windows it instead of letting the alternate screen (which has no scrollback) push the header and first cards out of reach. The viewport accounts for physical rows after terminal-width wrapping: a full-width visible line can consume its own row without wrapping the optional scroll affordance, which is omitted when it cannot fit. At five or more rows the header stays pinned; when there is room, the last row carries a scroll affordance naming how many report lines are above and below. Below five rows the header scrolls with the other report content, and at one row the scroll affordance is omitted so content still remains visible. Use `j`/`k`, the arrow keys, `PgUp`/`PgDn`, `Space`/`b`, or `g`/`G` to move the window. Scrolling clamps at both ends, survives a refresh, and re-clamps on resize; growing the terminal back past the report's height restores the whole frame and the ordinary `Press r to refresh · q to quit` footer. `--once`, non-TTY output, the final frame echoed on quit, and the TOON and JSON surfaces are all unaffected by terminal height.
|
|
410
|
+
- Height comes from the terminal too. When the report is taller than the terminal, the live view windows it instead of letting the alternate screen (which has no scrollback) push the header and first cards out of reach. The viewport accounts for physical rows after terminal-width wrapping: a full-width visible line can consume its own row without wrapping the optional scroll affordance, which is omitted when it cannot fit. At five or more rows the header stays pinned; when there is room, the last row carries a scroll affordance naming how many report lines are above and below. Below five rows the header scrolls with the other report content, and at one row the scroll affordance is omitted so content still remains visible. Use `j`/`k`, the arrow keys, `PgUp`/`PgDn`, `Space`/`b`, or `g`/`G` to move the window. `renderQuotaTui` lays cards out for the width and returns every line that takes; `scrollFrame` in `src/tui-viewport.ts` windows that frame to the terminal, pins the first line, and clips oversized lines. The closing hint is composed outside `renderQuotaTui`, and the live paint has no trailing newline, so a frame that exactly fills the terminal does not scroll itself. Fold a provider only on positive evidence of absence. Declare an uncertain skip on the adapter that owns the source (`isUncertainSkip`) and a sibling-tool login as incidental (`incidentalSources`); do not special-case those in the TUI. The `tui.show` preference stays on the human path in `src/commands.ts` and `src/tui.ts` and never changes the model or machine output. Scrolling clamps at both ends, survives a refresh, and re-clamps on resize; growing the terminal back past the report's height restores the whole frame and the ordinary `Press r to refresh · q to quit` footer. `--once`, non-TTY output, the final frame echoed on quit, and the TOON and JSON surfaces are all unaffected by terminal height.
|
|
362
411
|
- Each live card with a combinable bound leads with the effective-availability rollup (min across bounding windows), colored by headroom: >=50% healthy, 20-50% tight, <20% critical. Per-window rows, including per-model breakouts, are the supporting detail.
|
|
363
412
|
- Percentages and bars show what is left by default. To have every provider's headline, window rows, and bars show what has been used instead, set `tui.show` to `used` in the user config file `~/.config/quota-axi/config.json` (`$XDG_CONFIG_HOME/quota-axi/config.json` when `XDG_CONFIG_HOME` is set): `{ "tui": { "show": "used" } }`. That file is the only place the preference is read - there is no environment variable or flag for it - and it is read once when `--tui` starts, so restart a live report to pick up a change. Only the exact values `used` and `remaining` are recognized; a missing, unreadable, or malformed file, or any other value, keeps the default remaining view. The used view rounds the raw consumed percentage independently of the remaining view, so rounded figures need not sum to 100% (48.5% used / 51.5% remaining displays 49% used / 52% remaining). It labels each headline `49% used · week`. Its bar fill is consumption, the `┃` marker moves to the elapsed share of the window, and fill running past the marker means burning ahead of the reset clock. Health colors, runway verdicts, used-share rows, raw credit balances, and `?` rows read the same in both views. It is a human display preference only: quota-axi never reads the file outside `--tui`, so the default TOON, `--json`, `--full`, and the cache are byte-identical whatever it says.
|
|
364
413
|
- A window with `shareOf` is a used-share, not an independent remaining pool: the row prints `N% of <parent>` (or `share of <parent>` when `percentUsed` is absent) instead of a remaining bar or `?`, so it cannot be read as missing data or as its own headroom.
|
|
@@ -366,7 +415,7 @@ CODEX_HOME=/path/to/codex-profile quota-axi --provider codex --profile-only --fu
|
|
|
366
415
|
- In the default view, the bar fill is current headroom; the `┃` marker sits at the binding window's `pace.timeRemainingPercent`, the fill position of exactly linear burn. The headline marker therefore matches the corresponding `limitingWindowIds` sub-bar even when another window supplies the finite-runway `empty in` verdict. Fill ending left of the marker means burning faster than the reset clock. The marker is omitted when that window's pace is unknown.
|
|
367
416
|
- Pace is shown by the bar and marker alone, never as a numeric burn multiple. The runway verdict on the headline reads `on pace ✓` for `through_reset` and `empty in 7h 21m` for `projected_exhaustion`. Two-up rows keep both card bottoms aligned by padding the shorter card inside its border. The TUI does not display the per-scope selection signal; that signal remains on the JSON and TOON machine surfaces. Those surfaces also keep the `through_reset` vocabulary, while `--full --json` exposes the complete `pace` object. The TUI renders from the complete in-memory model, so `--json` tiering never removes anything it draws.
|
|
368
417
|
- A provider whose window relationships are wholly unknown (Copilot or Antigravity, with every window unresolved) has no combined effective percentage, pace, or runway to show, so its card replaces the headline block with a single `per-window usage · no combined bound` line and leads straight into its real per-window rows. Partially understood providers keep the effective-unknown headline. No combined headroom, pace, or runway number is invented.
|
|
369
|
-
- Providers are grouped by what quota-axi found on this machine, and the header always counts all
|
|
418
|
+
- Providers are grouped by what quota-axi found on this machine, and the header always counts all four groups in that order, for example `4 live · 1 stale · 2 need attention · 10 not set up`, including the ones that are empty. Every count is kept even in the narrowest supported terminal: a header that would not fit gives up its timestamp - the time zone first, then the date, then the clock - never a count. A fresh reading is live: an accented `●` and a full border. A stale cached reading is its own lane, counted apart from live. Its card uses a `◐` glyph and a dim border, and notes name when the snapshot was last refreshed and the error from the live read that failed, including `fetch failed` when the failure was the usage fetch. A provider without a reading whose sources found something (a credential that is expired, rejected, or waiting on a Keychain prompt, an installed tool that failed, or a request that failed) needs attention: it stays a dimmed full card right after the live and stale cards, with its remedy, and is excluded from the live total. A provider is not set up only when every credential source it checks was skipped as absent (no file, environment variable, CLI, or running app). A provider that recorded no sources at all still needs attention, so absence is never assumed, and so does one whose skip could not establish absence either way, such as a Copilot CLI account whose Keychain value still awaits `--allow-keychain-prompt` or a configuration that names no account quota-axi can confirm, a Kimi Code configuration quota-axi could not read to the environment it names, or an Antigravity CLI that is installed but timed out. A GitHub CLI login alone, or one Copilot refused, is not evidence of Copilot access and leaves Copilot not set up rather than broken; a `gh` store that could not be read, or a request through it that failed transiently, keeps Copilot in view.
|
|
370
419
|
- Providers that are not set up fold into one dim footer line that names each of them and points at `quota-axi auth` for where each one is read. Press `a` in the live report to draw them as dimmed full cards under a `○ not set up · N` label, and press it again to fold them; while any are folded or expanded the closing line names the key, including when the report scrolls. `--all` starts the report expanded and is how a `--once` frame shows them. A provider named with `--provider` is never folded.
|
|
371
420
|
- Width comes from the terminal, clamped to 80-120 columns; below the two-up width the grid reflows to one column. Color honors `NO_COLOR`, `TERM=dumb`, and non-TTY stdout (the glyph skeleton is kept), re-enables with `FORCE_COLOR`, and uses truecolor when `COLORTERM` advertises it, falling back to 256-color then ANSI-16.
|
|
372
421
|
- `--tui` composes with `--provider` scoping, `--all`, and `--full` (account identity and source-attempt footers, including for folded providers). It is mutually exclusive with `--json` and only supported by the `quota` command; `--all` is only supported with `--tui`.
|
|
@@ -405,7 +454,7 @@ A failed CLI reading is shown as stale or unavailable only when `account/read` c
|
|
|
405
454
|
A native login (from `auth.json` or the CLI) for the same account as a Pi lane is not a second lane.
|
|
406
455
|
The account is compared by the vendor `accountId` a fresh reading reports, or else the stored one; email, tokens, and key names are never used as identity.
|
|
407
456
|
That Pi lane keeps its own reading when fresh, and shows the native reading when its own is expired, rejected, or stale and the native one is fresh, or when only the native one has stale cached windows.
|
|
408
|
-
A proven sign-out
|
|
457
|
+
A proven sign-out retires only cached Codex snapshots whose stored account identity matches the rejected credential; a native login that coalesces into a Pi lane no longer removes the shared `codex-home` slot by name, so another account's snapshot cannot be discarded.
|
|
409
458
|
`--profile-only` still reads one native Codex file and never opens Pi auth.
|
|
410
459
|
|
|
411
460
|
### Account keys and compatibility
|
|
@@ -467,7 +516,7 @@ A `quota[]` row whose `runway` is `projected_exhaustion` or `exhausted_now` has
|
|
|
467
516
|
| `kind` | `scope` | Meaning |
|
|
468
517
|
| ------------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
469
518
|
| `stale` | `all` | The report is stale diagnostic data. `detail` names the last refresh, `fetch failed` plus `state.error` when a usage fetch failed, and any `state.reason`; no scope gets a `quota[]` row. |
|
|
470
|
-
| `auth_required`, `rate_limited`, `unavailable`, `error` | `all` | The provider state status. `detail` is `state.error`, any `state.reason`, plus the retry-after instant
|
|
519
|
+
| `auth_required`, `rate_limited`, `unavailable`, `error` | `all` | The provider state status. `detail` is `state.error`, any `state.reason`, plus the retry-after instant when `state.retryAfter` is set. |
|
|
471
520
|
| `no_quota` | `all` | The provider reported no measurable scope and no raw credit balance. Emitted when nothing else names it or when needed to preserve `state.authStatus`. |
|
|
472
521
|
| `credits` | `all` | The provider reported a raw credit balance but no measurable scope. `detail` states that balance verbatim; no percentage or bound is derived from it. |
|
|
473
522
|
| `unresolved_windows` | `all` | `quotaSemantics.unresolvedWindowIds`: unfamiliar vendor windows not folded into any bound. |
|
|
@@ -477,6 +526,7 @@ A `quota[]` row whose `runway` is `projected_exhaustion` or `exhausted_now` has
|
|
|
477
526
|
| `bound_conflict` | scope | A window the scope only inherits reads zero while the scope's own windows still report allowance. `detail` names both sides. The scope gets no `quota[]` row and no `exhaustion[]` row. |
|
|
478
527
|
| `unmeasurable` | scope | Headroom is known but a bound blocks `runway`, `spendPriority`, or both. `detail` names which. |
|
|
479
528
|
| `degraded_source` | `all` | A credential source was superseded: it was broken or unreadable while a sibling source answered. `detail` is `<source> · <error>`. One row per source, only on a fresh reading. |
|
|
529
|
+
| `reused` | `all` | A fresh reading was reused instead of asking the vendor again. `detail` is `last refreshed <refreshedAt>`. See [Fresh reuse](#fresh-reuse). |
|
|
480
530
|
|
|
481
531
|
`remedy` carries `state.remedyCommand` when one exists, and situational agent-directed advice is still prepended to `help`.
|
|
482
532
|
|
|
@@ -509,11 +559,11 @@ An unknown or stale scope deliberately gets **no** `quota[]` row: the absence of
|
|
|
509
559
|
| `effectiveAvailability[].pace.behindWindowIds`, `onPaceWindowIds` |
|
|
510
560
|
| Account identity (`account`) and per-source `attempts` |
|
|
511
561
|
|
|
512
|
-
Everything a consumer branches on stays in the default tier: `accountKey` and `accountKeys` on an account row; `state.status`, `stale`, `authStatus`, `error`, `reason`, `remedyCommand`, `retryAfter`, `untrustedWindowIds`, and `degradedSources`; window `pace.status`, `reason`, `reservePercentPoints`, `burnMultiple`, and `shareOf` together with that share window's `percentUsed`; `quotaSemantics.status` and `unresolvedWindowIds`; every scope's `effectivePercentRemaining`, `boundedBy`, `limitingWindowIds`, `boundConflict`, `runway`, `selection`, and pace `aheadWindowIds` / `unknownWindowIds` / `worstReservePercentPoints`; and sparse `notSetUp`. `credits` also stays, so a consumer can avoid misreading it as exhaustion.
|
|
562
|
+
Everything a consumer branches on stays in the default tier: `accountKey` and `accountKeys` on an account row; `state.status`, `stale`, `authStatus`, `error`, `reason`, `remedyCommand`, `retryAfter`, `untrustedWindowIds`, and `degradedSources`, plus `reused` and a reused reading's `refreshedAt`; window `pace.status`, `reason`, `reservePercentPoints`, `burnMultiple`, and `shareOf` together with that share window's `percentUsed`; `quotaSemantics.status` and `unresolvedWindowIds`; every scope's `effectivePercentRemaining`, `boundedBy`, `limitingWindowIds`, `boundConflict`, `runway`, `selection`, and pace `aheadWindowIds` / `unknownWindowIds` / `worstReservePercentPoints`; and sparse `notSetUp`. `credits` also stays, so a consumer can avoid misreading it as exhaustion.
|
|
513
563
|
|
|
514
564
|
`notSetUp: true` is present only on a provider lane that has positive evidence of absence (the same `providerPresence` classification the human report uses). Every provider stays in `providers[]` on both default `--json` and `--full`; omitting entries would change downstream consumers that look a provider up by id. The field is additive, so `schemaVersion` stays 5, or 6 when a provider expands to multiple accounts. Default TOON is the only output that omits these providers.
|
|
515
565
|
|
|
516
|
-
`--tui` renders from the complete in-memory model, so demotion never changes what the human report draws.
|
|
566
|
+
`--tui` renders from the complete in-memory model, so demotion never changes what the human report draws. Demotion happens only in the renderer (`quotaJsonReport` and `renderQuotaToon` in `src/render.ts`). Presence for an absence marker and for a TOON omission is classified in `quotaCommand` before redaction.
|
|
517
567
|
|
|
518
568
|
### Quota report shape
|
|
519
569
|
|
|
@@ -542,13 +592,21 @@ Claude `identityStatus` is `verified` only when Anthropic returns an authoritati
|
|
|
542
592
|
| `untrustedWindowIds` | Optional identifiers for limits that could not be parsed authoritatively |
|
|
543
593
|
| `authStatus` | Optional machine-readable local auth usability: `usable`, `expired_refreshable`, or `unusable`. Distinct from quota freshness and from human `error` prose. |
|
|
544
594
|
| `degradedSources` | Optional `{ source, error? }` list of credential sources a working sibling superseded on a fresh reading |
|
|
595
|
+
| `reused` | Optional `true` when this fresh reading was reused from the cache; `refreshedAt` then stays in default `--json` |
|
|
545
596
|
|
|
546
|
-
When stale or unavailable quota is likely fixable by a one-time native secure-store grant, `state.reason` is `keychain_access_required`, `state.remedyCommand` supplies the runnable grant command (Copilot's includes `--provider copilot`), and for Claude and
|
|
547
|
-
When no Grok credential establishes usability but at least one still has a valid literal refresh token, `state.authStatus` is `expired_refreshable` and `state.status` is `unavailable` (not `auth_required`). Stored-expired bearers are first tested with bounded read-only liveness attempts; this classification stands only after the credential's own model catalog definitively rejects it or cannot decide, and an empirically live bearer reports fresh quota or `usable` instead. The `grok` remedy (`state.reason: credentials_expired`, `state.remedyCommand: grok`, plus an agent-directed JSON `help` entry telling the user to run the Grok CLI once) is emitted only when the same refreshable CLI candidate is rejected by both consumer quota and the official Grok Build model catalog. It survives the delegated `grok models` refresh, so it names the case where that delegate could not run or did not recover the session (see [Delegated credential refresh](#delegated-credential-refresh)). A transient failure does not trigger the remedy. If Grok CLI OIDC is refreshable and Pi is not usable, `state.error` is `Grok access token expired`. If only Pi `xai` OAuth is refreshable, `state.error` is `Pi xAI access token expired` and no Grok CLI remedy is emitted because Grok cannot refresh Pi-owned credentials. Default JSON exposes `authStatus`; for a provider shown in compact TOON without a `quota[]` row, a defined auth status appears as `(auth <status>)` in `attention[]`. Providers omitted because they are not set up follow [Default report blocks](#default-report-blocks). Source-appropriate advice is included only when a remedy exists. Full output shows the attempts: `model_auth_probe_live` means the credential's official model catalog accepted it but exposed no numeric quota. `attempts[].error: credentials_expired` marks a stored-expired credential that was not attempted.
|
|
597
|
+
When stale or unavailable quota is likely fixable by a one-time native secure-store grant, `state.reason` is `keychain_access_required`, `state.remedyCommand` supplies the runnable grant command (Copilot's includes `--provider copilot`), and for Claude, Cursor, and Muse JSON also includes an agent-directed `help` entry. That remedy is offered only for `keychain_prompt_required`; backend-specific consent behavior is described in [Provider notes](#provider-notes). A denied or timed-out Keychain outcome is reported as `keychain_access_denied` or `keychain_prompt_timeout` and stays silent no longer, but it does not advertise `--allow-keychain-prompt`. For Claude, macOS `security` exit 44 is cannot-reach (`keychain_unreachable`), not item-absent: it is not signed-out, and it does not retire the Claude cache. A successful metadata listing covering every keychain in the search list, with no matching or unrecognized Claude item, is the only evidence that establishes a missing Claude Keychain source; an empty, partial, or failed listing proves nothing and falls back to the exact profile-and-account read.
|
|
598
|
+
When no Grok credential establishes usability but at least one still has a valid literal refresh token, `state.authStatus` is `expired_refreshable` and `state.status` is `unavailable` (not `auth_required`). Stored-expired bearers are first tested with bounded read-only liveness attempts; this classification stands only after the credential's own model catalog definitively rejects it or cannot decide, and an empirically live bearer reports fresh quota or `usable` instead. The `grok` remedy (`state.reason: credentials_expired`, `state.remedyCommand: grok`, plus an agent-directed JSON `help` entry telling the user to run the Grok CLI once) is emitted only when the same refreshable CLI candidate is rejected by both consumer quota and the official Grok Build model catalog. It survives the delegated `grok models` refresh, so it names the case where that delegate could not run or did not recover the session (see [Delegated credential refresh](#delegated-credential-refresh)). A transient failure does not trigger the remedy. If Grok CLI OIDC is refreshable and Pi is not usable, `state.error` is `Grok access token expired`. If only Pi `xai` OAuth is refreshable, `state.error` is `Pi xAI access token expired` and no Grok CLI remedy is emitted because Grok cannot refresh Pi-owned credentials. A Kimi soft expiry uses the same `credentials_expired` reason; its `pi` or `kimi` remedy is described in the [Kimi provider notes](#provider-notes). Default JSON exposes `authStatus`; for a provider shown in compact TOON without a `quota[]` row, a defined auth status appears as `(auth <status>)` in `attention[]`. Providers omitted because they are not set up follow [Default report blocks](#default-report-blocks). Source-appropriate advice is included only when a remedy exists. Full output shows the attempts: `model_auth_probe_live` means the credential's official model catalog accepted it but exposed no numeric quota. `attempts[].error: credentials_expired` marks a stored-expired credential that was not attempted.
|
|
548
599
|
True Grok sign-out or definitive remote rejection uses `state.authStatus: unusable` with `state.status: auth_required` and `state.error: Grok sign-in required` (no `credentials_expired` reason). For official Grok Build OIDC and Pi OAuth, a rejection from the consumer Usage-page operation alone is not that verdict because those access tokens can have a different audience; the matching first-party model catalog must reject the same bearer too. `authStatus: unusable` by itself only means that no source established usability; for example, a Pi credential-resolution failure instead has `state.status: error`. Callers must branch on `authStatus`, `status`, and `reason`, not on human error prose alone, and must not treat `expired_refreshable` as logged out.
|
|
549
|
-
When Grok Build or Pi `xai` OAuth passes its model-catalog probe after consumer-quota rejection, or a local Pi `xai` API key establishes model-only usability, and no sibling supplies consumer quota, `state.authStatus` is `usable`, windows stay empty, and `state.error` is `Grok model access available; quota unavailable` rather than sign-in required. Only transient consumer-quota failures remain eligible for same-source stale fallback;
|
|
600
|
+
When Grok Build or Pi `xai` OAuth passes its model-catalog probe after consumer-quota rejection, or a local Pi `xai` API key establishes model-only usability, and no sibling supplies consumer quota, `state.authStatus` is `usable`, windows stay empty, and `state.error` is `Grok model access available; quota unavailable` rather than sign-in required. Only transient consumer-quota failures and `expired_refreshable` soft expiry remain eligible for same-source stale fallback; a definitive sign-out (`auth_required`) retires the cached snapshot and reads the same with or without a cache. Model-only success does not inherit old quota windows. This is authenticated-but-unmeasurable, not a percentage or evidence of unlimited capacity.
|
|
601
|
+
|
|
602
|
+
A provider with more than one credential source consults them in its fixed declared order and reports the one that works, so a broken store never speaks for a provider whose sibling store still answers. A store's own expiry field is advisory within that source, never a verdict or a reason to reorder sources: a stored-expired credential is still probed in its source's declared position because only the provider's own endpoint can establish that a credential is dead. The superseded source is not discarded: `state.degradedSources` names it on the fresh reading, and default TOON emits a `degraded_source` attention row for it. Pi auth readers reserve `missing` for an absent file or provider property; malformed stores and present non-object or structurally invalid entries are `invalid`. Handover is limited to credential problems - a transport, decoding, or server failure is about the request rather than the credential, so it is reported as-is instead of being retried on a second credential. When no source works the provider still reports its auth problem, and a source that only failed transiently outranks a definitive rejection so a rejected credential is never reported as a sign-out while a sibling's outage is unresolved. New or migrated adapters route multi-credential selection through `src/providers/credential-selection.ts` instead of skipping a stored-expired credential. Codex, Kimi, and Copilot call it once per source so each provider's ownership-stability order stays authoritative. An `expired` resolution carries the stored token for the probe only; it is never logged or rendered. Pi entries are classified through `classifyPiAuthEntry` in `src/lib/pi-auth-store.ts`: only an absent file or provider property is `missing`, and a present non-object entry is `invalid`. Literal secrets for a new adapter go through `usableLiteralSecret` in `src/lib/secret.ts`. Claude's `env` source has distinct handover and failure-priority rules; see [Claude provider notes](#provider-notes).
|
|
603
|
+
|
|
604
|
+
Adding a credential source:
|
|
550
605
|
|
|
551
|
-
|
|
606
|
+
1. Enumerate every source in an ownership-stability order declared by a named constant. Keep `absent`, `structurally_invalid`, `unsupported`, `read_error`, and `resolved` (stored-valid or stored-expired) distinct, and derive `credentialPresent` once from that resolution.
|
|
607
|
+
2. Separate local resolution from the bounded read-only liveness probe. Enroll stored-expired credentials instead of skipping them, never infer liveness from presence, and classify probe outcomes as `usable`, `live_no_quota`, `definitively_rejected`, or `transient`. Only a first-party 401/403 is an auth verdict.
|
|
608
|
+
3. Hand over only after definitive credential failure. An absent source is never degraded. A present-but-broken source superseded by a working sibling is degraded on fresh readings only. Inspect refresh-token presence only, and never read or exchange its value, except the Pi usability boolean described under [Safety guarantees](#safety-guarantees).
|
|
609
|
+
4. Add adapter regressions for primary healthy, stored-expired plus live sibling, structurally invalid present, absent source, all rejected, refreshable expired, and transient stops handover. Extend the cross-provider invariant table in `test/credential-contract.test.ts`. Env-plus-Pi adapters route through `src/providers/env-pi-credential.ts`.
|
|
552
610
|
|
|
553
611
|
Claude credential failures without a usable access token preserve the precise `credentials_missing` or `credentials_invalid` error. A usage response with HTTP 401 reports `Claude sign-in required` only when it definitively establishes account authentication failure; the [Claude provider notes](#provider-notes) own the withheld-Keychain exception and cache effects. HTTP 403 is not sufficient authentication evidence because network policy and WAF denials use it too; it stays unavailable or stale rather than claiming sign-out.
|
|
554
612
|
|
|
@@ -579,12 +637,12 @@ Kimi documents the cycle itself: Kimi Code shares the Kimi membership's monthly
|
|
|
579
637
|
So `month_total` gets a `startsAt` one calendar month before its reported reset, with the day of month clamped into a shorter month (a 31 March reset starts on 28 or 29 February), which makes its pace, and with it the `all_models` runway and `spendPriority`, measurable.
|
|
580
638
|
A `month_total` with no reported reset gets no start, so its pace stays `missing_cycle`; `month_code` is a share and gets no cycle either.
|
|
581
639
|
The monthly code window is the code-typed share of that monthly total as the vendor serves it, not a cap of its own, so it is reported as a used share with no `percentRemaining` and bounds no scope: monthly headroom for code work is `month_total`'s.
|
|
582
|
-
That window carries `shareOf: month_total` so default TOON can name it as an `attention[]` `share` row and `--tui` can print the used percent of the parent instead of a remaining bar or `?`.
|
|
640
|
+
That window carries `shareOf: month_total` so default TOON can name it as an `attention[]` `share` row and `--tui` can print the used percent of the parent instead of a remaining bar or `?`. `shareOf` is the single share rule for interpretation, TOON, TUI, and `--json`: keep `percentUsed` because it is that window's only figure, never derive a remaining from a share, and never special-case a window id.
|
|
583
641
|
`share` is an additive attention kind; `schemaVersion` is unchanged.
|
|
584
642
|
An unfamiliar legacy limit, and a declared map entry that carries no usable ratio, both stay named in `unresolvedWindowIds` - as `limit:<index>` or `usages:<key>` - and turn the provider's semantics `partial`.
|
|
585
643
|
An unrecognized `usages` key is not reported as a window.
|
|
586
644
|
|
|
587
|
-
Alibaba's account `weekly` window is reported at `all_models` scope, while each `model:*` limit is kept only at its named model scope; a model limit never becomes an account-wide bound. OpenCode Go's rolling, weekly, and monthly windows are stacked plan caps ($12 per rolling 5 hours, $30 per week, $60 per month) that jointly bound Go-plan usage at `all_models` scope, so effective remaining is the minimum across them. A zeroed plan window blocks Go-plan requests, but the vendor's free-model fallback or an opted-in Zen balance may still serve past it, which the usage endpoint does not report. Command Code's `five_hour` and `weekly` windows jointly pace **included monthly credits** and are reported as one `included_credits` scope only when both windows are present and trusted; extra pay-as-you-go credits can bypass those windows, so they are never an unconditional `all_models` bound. Devin's `daily` and `weekly` windows bound `included_quota` the same way: paid extra usage continues past them, the daily window reuses the `session` kind, and only an explicit `hideDailyQuota: true` omits the daily cap. A missing flag leaves the bound unresolved. `limited:false` is credit-only: leftover five-hour or weekly fields are omitted rather than treated as a bound. An unfamiliar or untrusted Command Code limit, including a non-empty organization spend-limit list whose schema is not independently established, stays named in `unresolvedWindowIds`, turns the provider's semantics `partial`, and withholds the `included_credits` scalar. MiniMax model-remains windows are kept at their named model scopes and never become an account-wide bound. MiMo has no quota windows or effective model scope until a provider-owned quota source exists. DeepSeek and OpenRouter expose balances or key caps as raw credit evidence; quota-axi does not infer model headroom or effective remaining from those amounts. ElevenLabs' single `characters` window is the subscription plan's **included character allowance** and is reported as an `included_characters` scope, never `all_models` and never a model scope: plans whose `can_extend_character_limit` is set bill usage past that allowance, so a zeroed window means the included allowance is spent rather than that requests are refused.
|
|
645
|
+
Alibaba's account `weekly` window is reported at `all_models` scope, while each `model:*` limit is kept only at its named model scope; a model limit never becomes an account-wide bound. OpenCode Go's rolling, weekly, and monthly windows are stacked plan caps ($12 per rolling 5 hours, $30 per week, $60 per month) that jointly bound Go-plan usage at `all_models` scope, so effective remaining is the minimum across them. A zeroed plan window blocks Go-plan requests, but the vendor's free-model fallback or an opted-in Zen balance may still serve past it, which the usage endpoint does not report. quota-axi reports no Zen balance, so that zeroed window still reads plan-exhausted and names the fallback only in prose. Only a payload-supplied 18,000-second rolling duration promotes the window to `five_hour`; a plan-declared duration stays `rolling`. Cursor, OpenCode Go, ElevenLabs, and Kimi renewal dates all step back through `calendarMonthsBefore` in `src/lib/time.ts`. Command Code's `five_hour` and `weekly` windows jointly pace **included monthly credits** and are reported as one `included_credits` scope only when both windows are present and trusted; extra pay-as-you-go credits can bypass those windows, so they are never an unconditional `all_models` bound. Devin's `daily` and `weekly` windows bound `included_quota` the same way: paid extra usage continues past them, the daily window reuses the `session` kind, and only an explicit `hideDailyQuota: true` omits the daily cap. A missing flag leaves the bound unresolved. `limited:false` is credit-only: leftover five-hour or weekly fields are omitted rather than treated as a bound. An unfamiliar or untrusted Command Code limit, including a non-empty organization spend-limit list whose schema is not independently established, stays named in `unresolvedWindowIds`, turns the provider's semantics `partial`, and withholds the `included_credits` scalar. MiniMax model-remains windows are kept at their named model scopes and never become an account-wide bound. MiMo has no quota windows or effective model scope until a provider-owned quota source exists. DeepSeek and OpenRouter expose balances or key caps as raw credit evidence; quota-axi does not infer model headroom or effective remaining from those amounts. ElevenLabs' single `characters` window is the subscription plan's **included character allowance** and is reported as an `included_characters` scope, never `all_models` and never a model scope: plans whose `can_extend_character_limit` is set bill usage past that allowance, so a zeroed window means the included allowance is spent rather than that requests are refused. Muse's `five_hour` and `weekly` subscription windows jointly bound every model at `all_models`, so effective remaining is the minimum across them. If either expected window is absent while the other remains - including a five-hour reset that has already passed, which is dropped rather than published live - that missing bound stays named in `unresolvedWindowIds` and the remaining window does not publish a definitive `all_models` percentage. A window of any other length, or an entry that carried no usable percentage, is likewise unresolved and turns the provider's semantics `partial` rather than being folded in. An omitted or null `subs_usage` on an active subscription is an established empty reading (plan, no windows), not an untrusted field. An inactive subscription reports no window.
|
|
588
646
|
|
|
589
647
|
Antigravity groups its Gemini windows (`gemini_5h`, `gemini_weekly`) and its Claude/GPT windows (`claude_gpt_5h`, `claude_gpt_weekly`) into two independent scopes, `gemini` and `claude_gpt`. Within each group the 5-hour and weekly windows jointly bound that group, so its effective remaining is the minimum across its named windows, and neither group's usage lowers the other's headroom. An unfamiliar Antigravity window - including a recognized group's unrecognized bucket such as `gemini_unknown` - is not folded into either bound: it stays named in `unresolvedWindowIds` and turns the provider's semantics `partial`, while a reading that exposes only model-config `model:*` windows resolves no group and stays `unknown`. Antigravity v1 snapshots carry no cycle history, so each group reports headroom while its pace, `runway`, and `selection` stay unmeasurable.
|
|
590
648
|
|
|
@@ -654,11 +712,11 @@ A bounding window with no `resetsAt` at all has not been triggered yet (e.g. a C
|
|
|
654
712
|
|
|
655
713
|
In default TOON the scalar is the `spendPriority` column of the scope's `quota[]` row - there is no separate `selection[]` block, at any tier, because the column already carries it. An unmeasurable scalar renders the literal `unknown`, never `0`: `0` is exact utilization, a completely different claim.
|
|
656
714
|
|
|
657
|
-
| Field | Meaning
|
|
658
|
-
| ----------------------- |
|
|
659
|
-
| `status` | `known` when
|
|
660
|
-
| `spendPriority` | The clamped scope scalar. Present only when `status` is `known`
|
|
661
|
-
| `unmeasurableWindowIds` | The bounding windows that blocked the scalar. Present
|
|
715
|
+
| Field | Meaning |
|
|
716
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
717
|
+
| `status` | `known` when at least one bounding window is measurable, every other bound is an untriggered zero-use window, and no [`boundConflict`](#quota-windows) is disclosed; otherwise `unknown` |
|
|
718
|
+
| `spendPriority` | The clamped scope scalar. Present only when `status` is `known` |
|
|
719
|
+
| `unmeasurableWindowIds` | The bounding windows that blocked the scalar. Present only when a named bound made the scope `unknown` |
|
|
662
720
|
|
|
663
721
|
For each bounding window `w` of the scope:
|
|
664
722
|
|
|
@@ -680,7 +738,9 @@ A higher `spendPriority` therefore marks the scope where spending recovers the m
|
|
|
680
738
|
|
|
681
739
|
Any bounding window without usable pace makes the **whole scope** unmeasurable: `status` is `unknown`, no scalar is emitted, and `unmeasurableWindowIds` names the blockers. An unknown window is never assumed healthy and never treated as zero. A window whose remaining cycle time has effectively run out is unmeasurable rather than infinite. The one case where an absent `burnMultiple` is not a gap is a window with zero elapsed cycle time and zero usage: nothing can have been consumed yet, so its observed burn is `0` and the scope stays measurable.
|
|
682
740
|
|
|
683
|
-
`
|
|
741
|
+
A bounding window with no `resetsAt` at all has not been triggered yet (e.g. a Z.AI `five_hour` window before its first request this window, whose vendor omits the reset while the window is idle) rather than being a data gap. Under the same rule as the runway aggregate, when that untriggered window also reports zero usage (100% remaining, 0% used) it is treated as fully available and excluded from the weighted mean instead of blocking the scalar, so the scope's other bounding windows still determine `spendPriority`. When every bounding window is untriggered there is no measurable cycle to weight, so the scope publishes no scalar and `spendPriority` renders the literal `unknown`. A missing `resetsAt` paired with any other usage shape (unknown usage, or nonzero usage without an active clock), and a present-but-unparseable `resetsAt`, remain real data gaps that fail closed into `unmeasurableWindowIds`.
|
|
742
|
+
|
|
743
|
+
`selection` is derived per report from the same `generatedAt` clock as `pace` and `runway`, and is not cached. `summarizeEffectiveSelection` in `src/pace.ts` is its only implementation, and `SELECTION_SCALAR_KEY` in `src/types.ts` is the only spelling of the published field name. `worstReservePercentPoints` is a single-window diagnostic and is not this scalar.
|
|
684
744
|
|
|
685
745
|
**This is data, not routing.** quota-axi still never routes, ranks a winner, orders providers preferentially, proxies, logs in, or changes provider quota state. `selection` is a derived comparative _data_ signal computed entirely from figures quota-axi already reports; any routing, ranking, or preference is the consumer's decision. It is also advisory only: it never overrides `runway`, which remains the hard completion-risk evidence a consumer checks against its task horizon.
|
|
686
746
|
|
|
@@ -727,10 +787,11 @@ Source attempts can include `credentialPresent` when a source is not genuinely a
|
|
|
727
787
|
| ElevenLabs | Reads the deliberately exported `ELEVENLABS_API_KEY` and reports one `characters` window from the subscription plan's included allowance (`character_count` / `character_limit`) via the documented first-party `GET /v1/user/subscription`. `resetsAt` comes from `next_character_count_reset_unix`; the cycle start is that reset stepped back by the vendor's declared `character_refresh_period`, and an unrecognized period leaves the cycle - and therefore pace, runway, and selection - unknown rather than assuming one. Only `monthly_period` maps onto the `monthly` window kind; the longer declared periods stay `unknown`. A zero or absent `character_limit` reports no window instead of a percentage, and so does a response whose reported reset has already passed: those counters describe a finished cycle, so the live reading fails closed the same way the stale-cache path does. The window bounds `included_characters`, never `all_models`. |
|
|
728
788
|
| Devin | Reads `WINDSURF_API_KEY` when it is non-blank, otherwise `$XDG_DATA_HOME/devin/credentials.toml` (`~/.local/share/devin/credentials.toml`, or `%APPDATA%\devin\credentials.toml` on Windows). One read-only Connect-JSON `POST` to `https://server.codeium.com/exa.seat_management_pb.SeatManagementService/GetUserStatus` reports `weekly` and, when `planInfo.hideDailyQuota` is explicitly false, `daily` remaining percents with the vendor's reset timestamps. An absent flag leaves the daily cap unresolved, not omitted as on Max. The daily window reuses the existing `session` kind, so the published window-kind enum is unchanged: its id is `daily`, its label is `day`, and `windowSeconds` is 86,400. Weekly is `weekly` / `week` / 604,800. Both bound `included_quota`, because paid extra usage continues past a zeroed window. `overageBalanceMicros` is the extra-usage USD balance and is never turned into a percentage. A body whose `billingStrategy` is not `BILLING_STRATEGY_QUOTA` publishes no windows. An unrecognized `api_server_url` is not contacted. |
|
|
729
789
|
| Devin proto3 zero | For `GetUserStatus` only, an omitted `dailyQuotaRemainingPercent` or `weeklyQuotaRemainingPercent` is 0 remaining when that window's reset timestamp is present, because proto3 JSON omits zero-valued scalars. When both the percent and reset are missing on a quota plan with an expected cap, that window is untrusted and the combined bound is withheld. |
|
|
790
|
+
| Muse | Reads the Muse CLI's sign-in credential - `auth.json`'s `providers.meta.access_token` on Linux, the macOS Keychain item its `storage: "keychain"` records point to - then an exported `META_API_KEY`, and reports the subscription's `five_hour` (`subs_usage.window` when `window_duration_mins` is 300) and `weekly` (`subs_usage.weekly`) windows from `used_percent` and epoch-second `resets_at`, via `POST https://api.meta.ai/muse-code/key` - the request the Muse CLI makes at startup. Their effective-availability interpretation is documented in [Quota windows](#quota-windows). A window of any other length is named `window` with its own duration; an entry without a usable `used_percent` is named in `untrustedWindowIds`; an omitted or null `subs_usage` on an active subscription stays absent (plan still published) rather than untrusted or filled with 0%; an inactive subscription reports no window. Because that endpoint also issues an API key, requests are rate-bounded per credential; see [Muse provider notes](#provider-notes). |
|
|
730
791
|
|
|
731
792
|
### Model catalog and `models`
|
|
732
793
|
|
|
733
|
-
`quota-axi models [--intelligence high|medium|low] [--sort runway] [--provider ...] [--json|--full]` joins a reviewed catalog of native Claude, Codex, Grok, Kimi, MiniMax, and MiMo models to the provider's effective quota evidence. MiMo has catalog names for discovery, but no quota windows are established, so its model rows remain unevidenced. It queries catalog-backed providers by default and accepts only those providers in an explicit models scope. Cursor, Copilot, Z.AI, Alibaba, OpenCode Go, DeepSeek, OpenRouter, Antigravity, Command Code, and
|
|
794
|
+
`quota-axi models [--intelligence high|medium|low] [--sort runway] [--provider ...] [--json|--full]` joins a reviewed catalog of native Claude, Codex, Grok, Kimi, MiniMax, and MiMo models to the provider's effective quota evidence. MiMo has catalog names for discovery, but no quota windows are established, so its model rows remain unevidenced. It queries catalog-backed providers by default and accepts only those providers in an explicit models scope. Cursor, Copilot, Z.AI, Alibaba, OpenCode Go, DeepSeek, OpenRouter, Antigravity, Command Code, Devin, and Muse report quota but have no reviewed catalog entries yet, so they are not `models` providers. ElevenLabs meters speech characters rather than model usage, so it is not a `models` provider by construction.
|
|
734
795
|
Catalog buckets are coarse editorial classifications relative to the current frontier, not scores. They are curated from public provider material and public leaderboards, including [Artificial Analysis](https://artificialanalysis.ai/) as an informing source, with a documented provenance string so a stale catalog is detectable rather than silently refreshed. quota-axi does not reproduce Artificial Analysis scores, has no runtime Artificial Analysis dependency, and never commits an Artificial Analysis key. `scripts/refresh-model-kb.ts` is a maintainer-only review aid: it may use a private `AA_API_KEY` to suggest changes, but it never writes the catalog.
|
|
735
796
|
|
|
736
797
|
Every models response includes `catalog.version` and `catalog.provenance`; callers must treat catalog freshness and unmapped `unmatchedWindowIds` as explicit unknowns, never as missing evidence. A model row exposes the applicable effective quota scope and provider state. When no model-specific scope is known, the provider account scope remains the evidence rather than an invented model limit.
|
|
@@ -747,10 +808,10 @@ Default model order is deterministic and non-preferential: provider, then `accou
|
|
|
747
808
|
|
|
748
809
|
Auth source entries can include `credentialPresent` when a source is not genuinely absent, including when a read failure prevents a more precise classification.
|
|
749
810
|
|
|
750
|
-
| Name | Values
|
|
751
|
-
| -------------------- |
|
|
752
|
-
| Auth source statuses | `available`, `missing`, `invalid`, `expired`, `skipped`, or `error`
|
|
753
|
-
| Auth source names | `env`, `oauth-file`, `keychain`, `auth-json`, `auth-env`, `apps-json`, `copilot-cli:keychain`, `gh:hosts.yml`, `state-vscdb`, `cli-keychain`, `cli-authfile`, `cli-rpc`, `pi:openai-codex`, `pi:openai-codex-*` sibling keys, `pi:kimi-coding`, `pi:xai`, `pi:zai`, `pi:commandcode`, `kimi-code-cli`, `pi:opencode-go`, `opencode:auth.json`, `minimax:config.json`, `pi:minimax`, `env:MINIMAX_API_KEY`, `env:MIMO_API_KEY`, `pi:deepseek`, `env:DEEPSEEK_API_KEY`, `pi:openrouter`, `env:OPENROUTER_API_KEY`, `bl-cli`, `loopback`, `env:COMMAND_CODE_API_KEY`, `env:COMMANDCODE_API_KEY`, `commandcode-cli`, `omp:commandcode`, and `env:
|
|
811
|
+
| Name | Values |
|
|
812
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
813
|
+
| Auth source statuses | `available`, `missing`, `invalid`, `expired`, `skipped`, or `error` |
|
|
814
|
+
| Auth source names | `env`, `oauth-file`, `keychain`, `auth-json`, `auth-env`, `apps-json`, `copilot-cli:keychain`, `gh:hosts.yml`, `state-vscdb`, `cli-keychain`, `cli-authfile`, `cli-rpc`, `pi:openai-codex`, `pi:openai-codex-*` sibling keys, `pi:kimi-coding`, `pi:xai`, `pi:zai`, `pi:commandcode`, `kimi-code-cli`, `pi:opencode-go`, `opencode:auth.json`, `minimax:config.json`, `pi:minimax`, `env:MINIMAX_API_KEY`, `env:MIMO_API_KEY`, `pi:deepseek`, `env:DEEPSEEK_API_KEY`, `pi:openrouter`, `env:OPENROUTER_API_KEY`, `bl-cli`, `loopback`, `env:COMMAND_CODE_API_KEY`, `env:COMMANDCODE_API_KEY`, `commandcode-cli`, `omp:commandcode`, `env:ELEVENLABS_API_KEY`, `muse:auth.json`, and `env:META_API_KEY` |
|
|
754
815
|
|
|
755
816
|
## Security Posture
|
|
756
817
|
|
|
@@ -776,6 +837,7 @@ Auth source entries can include `credentialPresent` when a source is not genuine
|
|
|
776
837
|
| OpenRouter | `OPENROUTER_API_KEY` or `$PI_CODING_AGENT_DIR/auth.json` (default `~/.pi/agent/auth.json`) for a literal `openrouter` key |
|
|
777
838
|
| ElevenLabs | `$ELEVENLABS_API_KEY` only, when it holds a usable literal key. That is the credential ElevenLabs' own CLI documents for non-interactive use and sends as the `xi-api-key` header. Stored expiry does not apply: the credential is a non-expiring API key. |
|
|
778
839
|
| Devin | `$WINDSURF_API_KEY` when it is non-blank (optional `$WINDSURF_API_SERVER_URL`), otherwise `$XDG_DATA_HOME/devin/credentials.toml`, `~/.local/share/devin/credentials.toml`, or `%APPDATA%\devin\credentials.toml` on Windows. The token does not expire and has no refresh token. A blank variable selects nothing. The token is sent only to `https://server.codeium.com`. |
|
|
840
|
+
| Muse | The Muse CLI's `${XDG_CONFIG_HOME:-~/.config}/muse/auth.json` `providers.meta.access_token` first, then - on macOS, where the file records `storage: "keychain"` - the login Keychain item `ai.meta.dev.credentials` / `meta`, then `$META_API_KEY`, each only when it holds a usable literal credential. Only the access token is read: an `auth.json` sibling `refresh_token` and the Keychain bundle's minted `api_key` are dropped while parsed, a bundle `refresh_token` is checked for presence only. The Keychain value read follows the `--allow-keychain-prompt` gate (presence checks never prompt). `quota-axi auth` never fetches the bundle unless that flag is on, even when a grant marker exists. Neither store carries a stored expiry quota-axi reads, so each is probed in declared order. |
|
|
779
841
|
|
|
780
842
|
The Claude and Codex rows describe default discovery; [`--profile-only`](#profile-only-quota-reads) narrows each to the one selected credential file.
|
|
781
843
|
|
|
@@ -833,9 +895,9 @@ The Claude and Codex rows describe default discovery; [`--profile-only`](#profil
|
|
|
833
895
|
|
|
834
896
|
- The Cursor editor and the Cursor CLI keep credentials in different stores, so both are independent sources and Cursor auth is usable when either one is. For quota fetching, the editor `state-vscdb` source is tried first because it never prompts; the platform CLI source is tried when the editor has no usable token or its token is rejected. The `auth` command reports both sources.
|
|
835
897
|
- Cursor Desktop is not required. On macOS, a CLI-only machine can refresh from the CLI Keychain token after the one-time Keychain grant described below; that quota attempt is named `cli-keychain` in `sourcesTried`. On Linux, `cursor-agent` quota uses the read-only `cli-authfile` source from `auth.json`; its `accessToken` is only a bearer for the existing dashboard RPCs. The editor-credential fetch keeps its historical `api` attempt name. When credential discovery cannot produce a token, an unavailable source known to hold a credential takes precedence over a merely absent store, so a signed-in `cursor-agent` user sees the applicable source state rather than `Cursor sign-in required`.
|
|
836
|
-
- Editor source: it uses `sqlite3 -readonly` to read `cursorAuth` values and calls Cursor's first-party dashboard RPCs. If `sqlite3` is unavailable, that source is reported as skipped with `sqlite3_unavailable
|
|
898
|
+
- Editor source: it uses `sqlite3 -readonly` to read `cursorAuth` values and calls Cursor's first-party dashboard RPCs. If `sqlite3` is unavailable, that source is reported as skipped with `sqlite3_unavailable` when the state database exists or its path cannot be checked, and as missing only when it does not exist, so an absent editor store never holds back a sign-out verdict from the CLI source. Do not install `sqlite3` or any other system package to fill that gap.
|
|
837
899
|
- CLI source: on macOS, `cli-config.json` holds sign-in identity only and is never a token; its `authInfo` supplies the reported account email, and the access token is read from the login Keychain item `cursor-access-token` / `cursor-user` only under `--allow-keychain-prompt` or an existing account-scoped non-secret access marker. On Linux, `cli-authfile` reads only `accessToken` from `$CURSOR_CLI_CONFIG` or `${XDG_CONFIG_HOME:-~/.config}/cursor/auth.json`; missing, unreadable, malformed, or empty files are unavailable. The sibling refresh token is never read.
|
|
838
|
-
- quota-axi never refreshes Cursor credentials, and Cursor has no delegated refresh. Neither the Linux auth-file refresh token nor the macOS `cursor-refresh-token` Keychain item is read, and no non-interactive `cursor-agent` command was observed to rotate the stored session, so an expired or rejected CLI access token falls through to stale
|
|
900
|
+
- quota-axi never refreshes Cursor credentials, and Cursor has no delegated refresh. Neither the Linux auth-file refresh token nor the macOS `cursor-refresh-token` Keychain item is read, and no non-interactive `cursor-agent` command was observed to rotate the stored session, so an expired or rejected CLI access token is a sign-out: `auth_required`, and any cached snapshot is retired. A transient probe failure still falls through to stale reporting. Recovery requires `cursor-agent login` outside quota-axi. Cursor never classifies `expired_refreshable` because it does not read refresh tokens. Cursor CLI session tokens are long-lived (about sixty days observed), so this costs little in practice. This is a known limitation, not a silent gap.
|
|
839
901
|
- The token value is used only as the bearer of Cursor's read-only dashboard RPCs (`GetCurrentPeriodUsage`, `GetPlanInfo`, and `GetSandUsageStatus`). It is never logged, cached, or included in any output. quota-axi does not call Grok Bot trial, banked-reset, or machine-registration methods.
|
|
840
902
|
|
|
841
903
|
**GitHub Copilot**
|
|
@@ -876,7 +938,7 @@ The Claude and Codex rows describe default discovery; [`--profile-only`](#profil
|
|
|
876
938
|
- A credential quota-axi cannot reach is never reported as one the user does not have: a `keyring` slot is `kimi_code_cli_credential_storage_unsupported`, endpoints that are not one of Kimi Code's own deployments are `kimi_code_cli_region_unrecognized`, and configuration naming an unusable slot, or a suffixed slot with no deployment left to place it, is `kimi_code_cli_config_invalid`.
|
|
877
939
|
- The Pi source always has priority. Ambient API-key environment variables are not a credential source. The CLI source is consulted whenever Pi cannot yield quota windows - no supported credential, an unreadable store, a Pi credential the provider definitively rejected, or an authenticated established-empty response as described under [Provider windows](#provider-windows) - so a broken or quota-empty Pi source does not hide a working Kimi Code CLI session. Transport, decoding, timeout, cancellation, and server failures do not trigger credential switching.
|
|
878
940
|
- It sends one redirect-disabled `GET` to `<base URL>/usages` with a 15 second total deadline and a 262,144-byte decoded-body cap. That deadline also bounds how long quota-axi waits for the one reading of `config.toml` taken before the request, so a configuration file that never answers cannot outlast the operation; a run that cannot read it in time reports through the Pi source with no Kimi Code cache identity to reuse or stamp. The base URL travels with the credential: a Kimi Code CLI token is sent only to the deployment its own environment names, and a Pi-brokered credential, which records no deployment, keeps `https://api.kimi.com/coding/v1`. A configured environment is accepted only when the endpoints it records - its OAuth host, its base URL, or whichever of the two it names - identify one of Kimi Code's two published deployments (`.com` mainland China, `.ai` global). Slot and deployment must then agree in both directions, because Kimi Code derives the unsuffixed `kimi-code` slot for the default deployment and a suffixed slot for every other: neither may appear with the other's counterpart. Configuration therefore cannot redirect the bearer to an arbitrary origin or pair one deployment's token with another's host, and an unrecognized environment sends no request at all.
|
|
879
|
-
- It checks `refresh_token` presence only and never reads, renders, or exchanges its value. It never accepts an arbitrary Kimi origin, launches Pi or Kimi, makes a model request, refreshes or writes credentials, creates a device ID, imports cookies, sends device identity, retains raw responses, or exposes account, plan, token, or fingerprint data. Kimi has no delegated refresh: no non-interactive Pi or Kimi Code CLI command was established as renewing either store, so an expired record stays read-only with
|
|
941
|
+
- It checks `refresh_token` presence only and never reads, renders, or exchanges its value. It never accepts an arbitrary Kimi origin, launches Pi or Kimi, makes a model request, refreshes or writes credentials, creates a device ID, imports cookies, sends device identity, retains raw responses, or exposes account, plan, token, or fingerprint data. Kimi has no delegated refresh: no non-interactive Pi or Kimi Code CLI command was established as renewing either store, so an expired record stays read-only. A soft expiry (`authStatus: expired_refreshable`, status `unavailable` or `stale`) publishes `state.reason: credentials_expired`, `state.remedyCommand` of `pi` when Pi's `kimi-coding` store defines the reading or `kimi` when the Kimi Code CLI store does, and an agent-directed `help` entry telling the user to use a Kimi model in `pi` once, or to run a Kimi Code session with `kimi` once, because each CLI refreshes its own Kimi token only when that token is used. quota-axi does not launch either command. A hard sign-out stays `auth_required` with no remedy.
|
|
880
942
|
- Definitive credential absence or rejection retires Kimi cache data, but only when it is the verdict that speaks for the provider: a sibling source that failed transiently outranks it, so the cache survives, and a refreshable soft-expired verdict (`expired_refreshable`) is never definitive, so it also survives. Transient fallback serves only windows the shared [stale fallback bound](#cache) still allows.
|
|
881
943
|
- Because a reading can come from either Kimi Code deployment, or from a Pi credential that names neither, Kimi snapshots are cache-scoped like Claude's: each carries an opaque SHA-256 identifier for what produced it, and stale fallback reuses a snapshot only when the source whose failure is being reported asks for the identity it would have read under. A Kimi Code CLI reading is identified by the environment it was taken in; a Pi-brokered reading, which records no deployment and always asks `https://api.kimi.com/coding/v1`, is identified by that source and endpoint, so its numbers are never served back as a Kimi Code deployment's stale reading. An environment quota-axi assumed rather than established is its own identity, distinct from the confirmed environment naming the same slot and host, so an unreadable or unparseable `config.toml` reuses no snapshot at all: a mainland reading is never served to a global login that fell back to the default slot. That state withholds the cached numbers without retiring them, so the environment that produced them keeps them. Legacy context-less Kimi records are not reused. The environment identifier is derived from the resolved slot and base URL, never from `config.toml`'s bytes, so no credential in that file contributes to it and an edit that selects the same environment keeps the cache usable. It also comes from the one reading of `config.toml` a run takes before it requests anything, never from a second reading taken afterwards, so a login that switches deployments mid-run cannot file one deployment's numbers under the other's identity. Both identifiers discriminate configuration, source, and endpoint rather than accounts, so neither distinguishes two Pi credentials.
|
|
882
944
|
|
|
@@ -914,7 +976,7 @@ The Claude and Codex rows describe default discovery; [`--profile-only`](#profil
|
|
|
914
976
|
|
|
915
977
|
**Antigravity**
|
|
916
978
|
|
|
917
|
-
- It never restarts, signs in to, configures, or mutates Antigravity or `agy`. It reads no credential store, so it has no delegated refresh. It prefers running `agy -p "/quota" --output-format json` once with a 15 second bound; this is the vendor's noninteractive read-only usage command (a bounded read, not an agent session or credential operation). quota-axi resolves the CLI executable first, then prepends controlled denials for the desktop openers (`xdg-open` and `open`) plus the current Node runtime to that invocation's otherwise unchanged command search path: authenticated `/quota` launchers still resolve Node even under a restricted inherited path, while the signed-out CLI's PATH-based desktop-opener attempt is denied before it can launch an interactive sign-in browser. When the CLI is unavailable or fails, it falls back to existing loopback access. Loopback HTTP 401 carrying `missing CSRF token` is treated as `unavailable` (not signed-out) and retains the cached record. Any reading whose `resetsAt` is in the past is marked stale.
|
|
979
|
+
- It never restarts, signs in to, configures, or mutates Antigravity or `agy`. It reads no credential store and no process memory, and it never contacts an unowned port, so it has no delegated refresh. It prefers running `agy -p "/quota" --output-format json` once with a 15 second bound; this is the vendor's noninteractive read-only usage command (a bounded read, not an agent session or credential operation). quota-axi resolves the CLI executable first, then prepends controlled denials for the desktop openers (`xdg-open` and `open`) plus the current Node runtime to that invocation's otherwise unchanged command search path: authenticated `/quota` launchers still resolve Node even under a restricted inherited path, while the signed-out CLI's PATH-based desktop-opener attempt is denied before it can launch an interactive sign-in browser. When the CLI is unavailable or fails, it falls back to existing loopback access. Loopback HTTP 401 carrying `missing CSRF token` is treated as `unavailable` (not signed-out) and retains the cached record. Any reading whose `resetsAt` is in the past is marked stale.
|
|
918
980
|
- On macOS and Linux, it discovers only the current user's processes and their owned listening ports, then sends read-only POST requests with operation-specific bodies to local endpoints on `127.0.0.1`. On macOS, an app language server qualifies only when the executable path itself is inside an `.app` bundle whose name contains `Antigravity`, including bundle names with spaces; wrapper or prompt text that merely mentions such a path does not qualify. An app-advertised extension port is eligible only when the same process owns its listener.
|
|
919
981
|
- For loopback access, it uses `RetrieveUserQuotaSummary` with the vendor's read-only `request: {}` / `forceRefresh: false` envelope, forwards a runtime CSRF token only when an app process advertises one, uses `GetUserStatus` for plan identity and account identity exposed only behind `--full`, and can fall back to model quota data from `GetUserStatus` / `GetCommandModelConfigs` when grouped quota summary is unavailable. Conversation text, token counters, and subprocess error output are discarded.
|
|
920
982
|
- Percent remaining and reset times come only from vendor `remainingFraction`/`resetTime` fields. It does not invent windows, resets, or percentages.
|
|
@@ -950,18 +1012,27 @@ The Claude and Codex rows describe default discovery; [`--profile-only`](#profil
|
|
|
950
1012
|
- The two windows bound `included_quota` jointly when both are trusted and `hideDailyQuota` is false, or weekly alone when `hideDailyQuota` is explicitly true. Free models do not draw on them, and paid extra usage continues past a zeroed window, so they are not an `all_models` bound. Organization and administrator limits are not in these fields.
|
|
951
1013
|
- Snapshots are cache-scoped by an opaque SHA-256 of the answering source, host, and a one-way digest of the token. A new login is a cache miss. A reading with no resolved credential neither caches nor clears the record.
|
|
952
1014
|
|
|
1015
|
+
**Muse**
|
|
1016
|
+
|
|
1017
|
+
- Muse (Meta's Muse Code CLI) exposes subscription usage only on `POST https://api.meta.ai/muse-code/key` with `{"onboard": false}` and `x-api-version: 1.0.0`, the request the Muse CLI itself sends at every startup. quota-axi sends that same request with the same bearer and reads `subs_usage.window`, `subs_usage.weekly`, `is_subs_active`, and `subs_tier_name` (as `plan`). An active subscription whose mint body omits `subs_usage` or returns it as `null` keeps that plan and publishes no windows: the field is absent, not untrusted, and 0% is not invented. A live Muse Code 1.4.0-R4161.1 `Muse Code Power Usage` mint body (2026-09-25, JSON key paths only, no values) carried `is_subs_active` and `subs_tier_name` and omitted `subs_usage`, `window`, `weekly`, `used_percent`, and `resets_at`.
|
|
1018
|
+
- That endpoint also returns an API key in its response. quota-axi never keeps it: a JSON reviver drops every response key outside the usage fields while the body is parsed, so the returned key and any account fields (email, name, payment method) never reach an object, an error, the cache, or any output mode. No response body is ever quoted in an error. quota-axi never writes that key, or anything else, to Muse's `auth.json`.
|
|
1019
|
+
- Verified against a live Muse Code 1.3.0 subscription (2026-09-24): two consecutive calls returned the identical `api_key`, and that key equals the one the CLI minted at a session startup, so repeated reads do not mint or rotate a fresh credential - the endpoint serves the account's standing Model API key - and a CLI session started before those calls kept working through and after them (a multi-round headless session straddling both calls completed normally).
|
|
1020
|
+
- Because the endpoint issues a key on first use and its rotation cadence is not documented, requests are still bounded per credential: at most one request per credential per five minutes, whatever its outcome, across every quota-axi process sharing the cache directory. The request is recorded as `pending` in the [key-endpoint ledger](#cache) before it leaves, so a timeout or crash still counts. Inside the interval nothing is sent: the reading that request produced is served from its own context-matched snapshot as `status: fresh` with `source: cache` and its original `refreshedAt`; an empty successful observation (omitted or null `subs_usage`, an inactive subscription, or every reported `resets_at` already passed) is replayed from a non-secret ledger marker as the same `status: fresh` with `source: cache`, the same `plan` if present, `windows: []`, and its original `refreshedAt`; a rejection is replayed as a rejection; and anything else reads status `unavailable` with error `muse_read_deferred` and a `retryAfter` naming when the next request may leave, falling back to that credential's older snapshot as stale when one is still inside its windows. A cache miss after a windowed quota outcome is not turned into an empty reading. The interval equals the `--tui` default refresh, so a live report sends at most one request per refresh, and a burst of agent calls sends one in total. `quota-axi auth` never contacts the endpoint. The ledger check and `pending` claim are one locked step, so two processes sharing the cache directory cannot both POST inside the interval. If that claim cannot be recorded (unwritable cache directory, or the lock cannot be taken), the request does not leave and the report is `muse_read_unrecorded`.
|
|
1021
|
+
- `auth.json` is read for `providers.meta.access_token` only; a sibling `refresh_token` is checked for presence and its value is dropped while the file is parsed. An unreadable or malformed file with no working fallback is an operational failure (`status: error`), not `auth_required`. On macOS, records with `storage: "keychain"` point at the login Keychain item (service `ai.meta.dev.credentials`, account `meta`) whose JSON bundle holds the OAuth `access_token` beside the CLI's minted Model API key; the bundle is parsed with its own key allowlist, so the minted key never survives parsing, and the value read is gated like the Claude and Cursor CLI Keychain sources (presence check only on plain quota calls, `--allow-keychain-prompt` or the recorded grant marker for the value). `quota-axi auth` never fetches that bundle unless `--allow-keychain-prompt` is on, even when a grant marker exists, because the report only emits status and the bundle carries the minted `api_key`. HTTP 401 on a credential stored beside a refresh token is soft expiry (`authStatus: expired_refreshable`, status `unavailable`, stale fallback served); without one it is sign-out. A present Keychain source skipped without being probed (`keychain_prompt_required`, `keychain_access_denied`, or `keychain_prompt_timeout`) is never a sign-out: the blocked read is reported with that error and status `error`, and the cache is preserved even when a later sibling is rejected. HTTP 403 is not sign-out evidence. Muse has no delegated refresh, and `ProviderOptions.refreshCredentials` is ignored.
|
|
1022
|
+
- Snapshots are cache-scoped by an opaque SHA-256 of the answering source plus a one-way digest of its credential. That identity holds on the adapter path: interval replay and stale fallback require it to match, so a token the Muse CLI has since refreshed is a cache miss, never a cross-attribution. Muse is excluded from `--max-age` / `QUOTA_AXI_MAX_AGE` fresh reuse, because a macOS Keychain login switch does not change a traced file and must not be served under the new identity; the five-minute key-endpoint ledger remains the bound on vendor calls.
|
|
1023
|
+
|
|
953
1024
|
### Delegated credential refresh
|
|
954
1025
|
|
|
955
1026
|
quota-axi reports quota; it is not an auth app. It never mints a credential, never rotates one, and never performs a refresh-token exchange over HTTP. Those refresh tokens rotate on use, so a second holder performing the exchange would spend the vendor's own single-use token and sign the user out of the harness being measured.
|
|
956
1027
|
|
|
957
|
-
Instead, when the same stored access token is expired, carries a refresh token, **and** is definitively rejected, quota-axi may run the vendor CLI's own smallest non-interactive command that already owns rotation, then re-read the store that CLI rewrote and retry the same read-only quota request once. Rotation is always the vendor's; quota-axi only reads the result.
|
|
1028
|
+
Instead, when the same stored access token is expired, carries a refresh token, **and** is definitively rejected, quota-axi may run the vendor CLI's own smallest non-interactive command that already owns rotation, then re-read the store that CLI rewrote and retry the same read-only quota request once. Rotation is always the vendor's; quota-axi only reads the result. A provider gets a delegate only when that command's rotation is established from the vendor's own CLI. Leaving a provider read-only is always allowed. Route any new delegate through `src/providers/delegated-refresh.ts` rather than spawning ad hoc. `ProviderOptions.refreshCredentials` gates it: `--no-credential-refresh` turns it off, and the read-only `auth` command always passes `false`. Tests pass it explicitly so no test can spawn a vendor CLI by accident.
|
|
958
1029
|
|
|
959
|
-
| Provider
|
|
960
|
-
|
|
|
961
|
-
| Claude
|
|
962
|
-
| Codex
|
|
963
|
-
| Grok
|
|
964
|
-
| Cursor, GitHub Copilot, Kimi, Z.AI, Alibaba, OpenCode Go, Antigravity, Command Code, MiniMax, MiMo, DeepSeek, OpenRouter, ElevenLabs, Devin | none | read-only; see the per-provider notes below |
|
|
1030
|
+
| Provider | Vendor-owned recovery path | Store the vendor rewrites |
|
|
1031
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------- |
|
|
1032
|
+
| Claude | `claude doctor` delegate | the Claude Code Keychain item, or `.credentials.json` |
|
|
1033
|
+
| Codex | existing `app-server` quota probe | `$CODEX_HOME/auth.json` |
|
|
1034
|
+
| Grok | `grok models` delegate | `$GROK_HOME/auth.json` |
|
|
1035
|
+
| Cursor, GitHub Copilot, Kimi, Z.AI, Alibaba, OpenCode Go, Antigravity, Command Code, MiniMax, MiMo, DeepSeek, OpenRouter, ElevenLabs, Devin, Muse | none | read-only; see the per-provider notes below |
|
|
965
1036
|
|
|
966
1037
|
The Claude and Grok delegated runs are bounded the same way:
|
|
967
1038
|
|
|
@@ -971,6 +1042,7 @@ The Claude and Grok delegated runs are bounded the same way:
|
|
|
971
1042
|
- At most one delegated refresh per credential source per quota read. That bound is per read rather than per process, so a long-running `--tui` still recovers from a session that expires while it is up.
|
|
972
1043
|
- Vendor output is discarded at the operating system, never read. A credential is never parsed out of a vendor's stdout; the refreshed value only ever comes from re-reading the vendor's own store.
|
|
973
1044
|
- It runs only for soft expiry: a stored-expired credential that carries a refresh token and was definitively rejected. Transient failures, missing or malformed stores, stored-valid credentials the server revoked, and relocated stores the vendor would not rewrite all stay read-only.
|
|
1045
|
+
- Process-table discovery (Antigravity loopback endpoints and this Claude check) shells out to `ps` through `currentUserProcessListArgs` in `src/lib/process.ts`. Linux procps rejects the BSD `-x` selector alongside `-u`, so a new process-table read goes through that helper. Tests mock `src/lib/running-processes.ts` so the machine's real process table never decides a test.
|
|
974
1046
|
- Claude adds a best-effort check before delegating: the process list must show no Claude Code process. Claude Code owns that session and refreshes it on its own schedule, so `claude doctor` alongside a live session is at best redundant and at worst a second holder racing a single-use refresh token. This also means a detached `claude doctor` that outlives quota-axi's wait is visible to the next read, which stays read-only instead of stacking another refresh on it. Not knowing counts as not safe: where the process list cannot be read (Windows, no effective uid, no `ps`), quota-axi stays read-only rather than guessing. The check and spawn are not atomic, so a Claude Code session starting after the check or another concurrent quota-axi read can still overlap the delegate. This narrows the common repeated five-minute `--tui` versus live-session collision and, together with never signaling the delegate, is strictly safer than force-killing without adding a failure mode beyond the pre-existing vendor-owned race.
|
|
975
1047
|
- `--no-credential-refresh` disables it entirely, the read-only `auth` command never delegates a refresh, and neither does [`--profile-only`](#profile-only-quota-reads).
|
|
976
1048
|
|
|
@@ -987,7 +1059,7 @@ A Claude or Grok delegated run appears in `--full` output as its own attempt (`c
|
|
|
987
1059
|
|
|
988
1060
|
A `refresh_timed_out` run is never treated as a credential verdict. Claude reports that read as unmeasured (`claude_refresh_unconfirmed`), falling back to a stale cached snapshot when one applies, and keeps the cached snapshot rather than retiring it. On Windows, a resolved `.cmd` or `.bat` command shim runs through the platform command interpreter without enabling Node's shell mode, preserving the no-shell argument boundary. Quota accuracy and the no-shell safety guarantee are unchanged. Codex needs no extra spawn: its existing read-only `cli-rpc` app-server probe both refreshes `auth.json` and returns the rate limits, so an expired Codex token already reports live quota through the vendor CLI.
|
|
989
1061
|
|
|
990
|
-
Providers with no established non-interactive rotation command stay read-only on purpose. That is a documented limitation rather than a reason to force an unsafe path: Cursor's CLI token is long-lived and no non-interactive `cursor-agent` command was observed to rotate it, GitHub Copilot has no established non-interactive rotation command for its supported sources, Z.AI uses a non-expiring API key, Alibaba is accessed through the read-only `bl` usage command, OpenCode Go has no vendor-owned rotation command, Pi-owned OAuth entries (`openai-codex`, `xai`, `kimi-coding`, `commandcode`) have no non-interactive Pi refresh command, Command Code API keys do not expire, ElevenLabs API keys do not expire either, Devin's session token does not expire and has no refresh token, and Antigravity exposes no credential store at all.
|
|
1062
|
+
Providers with no established non-interactive rotation command stay read-only on purpose. That is a documented limitation rather than a reason to force an unsafe path: Cursor's CLI token is long-lived and no non-interactive `cursor-agent` command was observed to rotate it, GitHub Copilot has no established non-interactive rotation command for its supported sources, Z.AI uses a non-expiring API key, Alibaba is accessed through the read-only `bl` usage command, OpenCode Go has no vendor-owned rotation command, Pi-owned OAuth entries (`openai-codex`, `xai`, `kimi-coding`, `commandcode`) have no non-interactive Pi refresh command, Command Code API keys do not expire, ElevenLabs API keys do not expire either, Devin's session token does not expire and has no refresh token, Muse exposes no rotation command beyond the key endpoint its quota read already uses, and Antigravity exposes no credential store at all.
|
|
991
1063
|
|
|
992
1064
|
### Safety guarantees
|
|
993
1065
|
|
|
@@ -995,9 +1067,9 @@ Providers with no established non-interactive rotation command stay read-only on
|
|
|
995
1067
|
- The user-initiated `update` command is the only outbound non-provider network surface, and it is not part of quota measurement.
|
|
996
1068
|
- It sends credential values only to the first-party provider request they authenticate.
|
|
997
1069
|
- It never prints, logs, or caches credential values.
|
|
998
|
-
- It never mints, rotates, or writes a credential, and never performs a refresh-token exchange. Credential renewal is always delegated to the vendor CLI that owns the store (see [Delegated credential refresh](#delegated-credential-refresh)).
|
|
1070
|
+
- It never mints, rotates, or writes a credential, and never performs a refresh-token exchange. Credential renewal is always delegated to the vendor CLI that owns the store (see [Delegated credential refresh](#delegated-credential-refresh)). Muse is the one provider whose only quota read, the Muse CLI's own startup request, also returns an API key; that read is verified to return the account's standing key rather than mint a fresh one, quota-axi discards the key while parsing, never stores or prints it, and bounds how often it sends the request (see [Muse provider notes](#provider-notes)).
|
|
999
1071
|
- It never retains, prints, logs, renders, caches, sends, or exchanges a refresh token's value. The Pi credential brokers read a stored refresh value only to derive a usability boolean - whether it is a usable literal secret rather than absent or an environment, template, or command reference - and discard it immediately; elsewhere only its presence is checked, as evidence that the vendor can still recover.
|
|
1000
|
-
- It never launches the Cursor, GitHub (`gh`), Copilot, Pi, Kimi, MiniMax, OpenCode, Command Code, ElevenLabs, or
|
|
1072
|
+
- It never launches the Cursor, GitHub (`gh`), Copilot, Pi, Kimi, MiniMax, OpenCode, Command Code, ElevenLabs, Devin, or Muse CLIs. It runs the read-only Alibaba `bl` usage command, the declared read-only Codex app-server probe, Antigravity's noninteractive structured `/quota` read (`agy -p "/quota"`), preferred ahead of its loopback access, and the two declared refresh delegates (`claude doctor`, `grok models`); none starts an agent session or spends the quota being measured. The one explicit exception is `--allow-claude-inference`, whose fixed native Claude request spends inference quota and is documented above.
|
|
1001
1073
|
- It never signals or kills a delegated refresh. A vendor that outruns quota-axi's wait is left to finish its own token exchange, and quota-axi reports an unconfirmed refresh instead of a credential verdict.
|
|
1002
1074
|
- It never routes, ranks a winner, or orders providers preferentially. Derived comparative signals, including `effectiveAvailability[].selection`, are published as data for the consumer to act on.
|
|
1003
1075
|
|
|
@@ -1010,18 +1082,24 @@ Providers with no established non-interactive rotation command stay read-only on
|
|
|
1010
1082
|
| Quota cache contents | Stores normalized non-secret snapshots only. |
|
|
1011
1083
|
| Claude Keychain access marker | Lives alongside the quota cache as `claude-keychain-access-granted-<service-hash>-account-<account-hash>`, where the service hash is eight hexadecimal characters and the account hash is sixteen. It uses `0600` file permissions, contains no credential material, raw account name, or raw service name, and markers written by earlier versions are ignored rather than deleted. |
|
|
1012
1084
|
| Cursor CLI Keychain access marker | Lives alongside the quota cache as `cursor-cli-keychain-access-granted-account-<account-hash>`, where the account hash is sixteen hexadecimal characters. It uses `0600` file permissions and contains no credential material or raw account identity. |
|
|
1013
|
-
|
|
|
1014
|
-
|
|
|
1085
|
+
| Muse Keychain access marker | Lives alongside the quota cache as `muse-keychain-access-granted-<hash>`, where the hash is the SHA-256 of the JSON array `[service, account]`. It uses `0600` file permissions and contains no credential material, raw account name, or raw service name. |
|
|
1086
|
+
| Cached reports | Only fresh provider snapshots with windows are cached. Cache schema 3 slots one snapshot per `provider` plus `accountKey`; a snapshot with no key holds that provider's `default` slot, and the filler `default` key an expanded report stamps on single-account providers is never written, so a stale reading can never expand a report in which nothing expanded. Each lane reads its own slot, except the `codex-home` compatibility lane may also read the legacy keyless Codex slot, and a slot name alone cannot say which account signed in there, so a provider whose slot can be rebound stamps the snapshot with its own account identity (see Codex cache identities below). Schema 1 and 2 files without `accountKey` remain readable. A context-scoped snapshot for Claude, Kimi, Command Code, MiniMax, ElevenLabs, Devin, or Muse is written only when the current reading supplies its context identity. A reading served from the cache is never written back. |
|
|
1087
|
+
| Fresh provider reports with no windows | Clear any cached snapshot for that provider/account lane, so entitlement-only reports do not leave stale quota windows behind. Claude, Kimi, Command Code, MiniMax, ElevenLabs, Devin, and Muse skip both cache write and clear when the current reading has no context identity. |
|
|
1015
1088
|
| Reports and details not cached | Failed providers, stale providers, account identity, and source attempts are not cached. Claude native inference readings and Copilot native secure-store readings (`source: cli`) are excluded from both cache writes and clears, leaving existing stored-session snapshots untouched. |
|
|
1089
|
+
| Definitive sign-out | A definitive sign-out reports `auth_required` whether or not a snapshot exists, and retires that snapshot so a later failure cannot serve it. Transient failures and `expired_refreshable` soft expiry keep the stale fallback, and so does a sign-in verdict reached while a present store was skipped without being probed (unreadable, malformed, or awaiting a secure-store grant): only a credential the vendor rejected, or no credential at all, retires the snapshot. A Copilot sign-out still retires its snapshot beside a GitHub CLI login that is keyring-stored or unreadable, because that login is not evidence of Copilot access. Grok, Cursor, Copilot, and Codex share `staleUnlessSignOut` for that split. Cursor and Copilot have no `expired_refreshable` verdict. A Codex credential the endpoint rejects is a sign-out, and retires every Codex snapshot stamped with that stored account id, unless the rejected credential was stored-expired and its store holds a refresh token (presence only, never its value): that is `expired_refreshable` with status `unavailable`, and the account's stale snapshot stays. Unstamped snapshots and snapshots stamped for another account remain. A Codex CLI fallback that then reports the account signed out does not replace that sign-out, and neither does one that cannot read the limits after the endpoint rejected the `auth.json` login the CLI shares; after a rejected Pi entry, the CLI reads a different login, so its failure to read limits is its own error and keeps the stale fallback. |
|
|
1090
|
+
| Fresh reuse stamp | A snapshot from a provider reading whose every lane was cacheable also carries a `reuse` stamp for [Fresh reuse](#fresh-reuse), except Muse (see Muse cache provenance below): a hashed credential-selection identifier, the paths of the local files the reading was derived from and a hash of their state (never their contents), the reading time and lane position, and that reading's `accountKeys`, `authStatus`, and `degradedSources`. Stale fallback never reads the stamp, so a stale report carries none of that state forward. |
|
|
1016
1091
|
| Stale fallback bound | Every provider with a stale cache fallback serves only cached windows that can still be true, through one shared rule. A window whose own `resetsAt` is at or before the moment of the fallback is dropped, because the vendor has already started a new cycle. A window without a usable reset is kept only while the snapshot is younger than the shortest cycle it admits, because after that long a reset has certainly happened: its own `windowSeconds`, else five hours for `session`, seven days for `weekly`, and 28 days (the shortest month) for `monthly`. A resetless `credits`, `model`, or `unknown` window with no declared duration has no such bound and is never served, and neither is a resetless window from a snapshot whose `refreshedAt` cannot be read. A snapshot whose `refreshedAt` is later than the moment of the fallback (written while the clock ran ahead) serves no window at all, because its age cannot be known. A stale report keeps a cached `untrustedWindowIds` entry only when it names a window that survived or never named a cached window. When no window survives, the transient failure is reported as the failed read, with the same status, windows, and exit code as a run with no cache. Provider rows below add provenance and identity checks on top, and a provider's own shorter resetless bound still applies to the windows the shared rule keeps: Claude also withholds a snapshot older than seven days and caps a resetless `session` or `monthly` window at five hours and a resetless `weekly` or `model` window at seven days, Kimi caps every resetless window except `weekly` at five hours, and ElevenLabs never serves a resetless window. |
|
|
1017
1092
|
| Claude cache fallback | Follows the [Claude provider rules](#provider-notes), including the denied-Keychain exception. Eligible fallback uses a formerly fresh snapshot from the same selected Claude configuration context, with a seven-day provider bound on top of the shared stale fallback bound. Its opaque SHA-256 context identifier includes the configuration directory and the selected Keychain service, which already encodes any secure-storage selector. Legacy context-less records, snapshots from the earlier broad suffix-discovery context, and Claude snapshots written by 0.1.50 with inverted usage percentages are withheld without deleting them. |
|
|
1018
|
-
| Codex cache identities | Cached Codex windows are accepted only when ID, label, kind, duration, and duplicate suffix order agree; stale snapshots with mismatched identities are rejected. A snapshot also records an opaque SHA-256 identifier of the ChatGPT account id the credential that produced it stored, and is withheld when the credentials a failed reading actually tried store only other accounts' ids, because a slot name alone cannot say whose windows it holds: a credential that reading never tried does not vouch for the snapshot, and a probe that fails transiently is vouched for only by its own credential. The vendor's response id is not the stamp: it can differ from the store while the same token is live, and a failed probe only has the store. A snapshot without that identifier, or a run whose credentials name no account,
|
|
1093
|
+
| Codex cache identities | Cached Codex windows are accepted only when ID, label, kind, duration, and duplicate suffix order agree; stale snapshots with mismatched identities are rejected. A snapshot also records an opaque SHA-256 identifier of the ChatGPT account id the credential that produced it stored, and is withheld when the credentials a failed reading actually tried store only other accounts' ids, because a slot name alone cannot say whose windows it holds: a credential that reading never tried does not vouch for the snapshot, and a probe that fails transiently is vouched for only by its own credential. The vendor's response id is not the stamp: it can differ from the store while the same token is live, and a failed probe only has the store. A snapshot without that identifier, or a run whose tried credentials name no account, cannot establish ownership and is withheld from stale fallback. |
|
|
1019
1094
|
| Grok cache provenance | Only snapshots produced by the current `web` consumer operation can be used as Grok stale fallback; legacy `api` billing-proxy snapshots are rejected. |
|
|
1020
1095
|
| Kimi cache provenance | Follows the [Kimi provider rules](#provider-notes): a snapshot is reused only when its opaque SHA-256 identifier still matches the source that produced it - the selected Kimi Code environment, or the Pi source and its default endpoint - and legacy context-less Kimi records are not reused. A reusable snapshot written before `shareOf` existed stores `month_code` with `percentUsed` alone, so reading it restores the `shareOf: month_total` marker; a stale fallback after upgrading therefore still presents that window as a used share rather than as missing data. |
|
|
1021
1096
|
| Command Code cache provenance | Follows the [Command Code provider rules](#provider-notes): a snapshot is reused only after the current `whoami` identifies the account and the opaque SHA-256 source-plus-account identifier still matches; a failed or unidentified `whoami` cannot select stale data from an unknown account and does not cache or clear the existing record. |
|
|
1022
1097
|
| MiniMax cache provenance | Follows the [MiniMax provider rules](#provider-notes): a snapshot is reused only when its opaque SHA-256 identifier still matches the credential source and deployment host that produced it, and legacy context-less MiniMax records are not reused. |
|
|
1023
1098
|
| ElevenLabs cache provenance | Follows the [ElevenLabs provider rules](#provider-notes): each snapshot carries an opaque SHA-256 of the answering source plus a one-way digest of the key that produced it, and stale fallback requires that identity to match. A reading with no usable key neither caches nor clears the record, and a cached window whose own reported reset has already passed is never served. As a deliberate exception to the shared stale fallback bound, a resetless ElevenLabs window is never served at all: the character allowance is not given a shelf life the vendor never reported. |
|
|
1099
|
+
| Context-scoped identity | A provider whose slot name cannot fix the account, or whose local configuration selects the account, stamps snapshots through `CONTEXT_SCOPED_PROVIDERS` in `src/cache.ts` rather than a per-provider field. The stamp describes the source that answered. A context read from a file the vendor rewrites is taken before the request and is not re-derived at write time. Kimi publishes that identity through `src/providers/kimi-cache-context.ts`. |
|
|
1024
1100
|
| Devin cache provenance | Follows the [Devin provider rules](#provider-notes): each snapshot carries an opaque SHA-256 of the answering source, host, and a one-way digest of the token that produced it, and stale fallback requires that identity to match. A reading with no resolved credential neither caches nor clears the record. A definitive rejection retires only a snapshot whose identity matches the rejected token. |
|
|
1101
|
+
| Muse cache provenance | Follows the [Muse provider rules](#provider-notes): each snapshot carries an opaque SHA-256 of the answering source plus a one-way digest of the credential that produced it, and both interval reuse and stale fallback require that identity to match. Muse is not stamped or served through `--max-age` fresh reuse. A reading with no usable credential neither caches nor clears the record. |
|
|
1102
|
+
| Muse key-endpoint ledger | Lives alongside the quota cache as `muse-key-reads.json` (`0600`). It maps each Muse credential's opaque context digest to the time and outcome class (`pending`, `quota`, `rejected`, `transient`) of the last key-endpoint request. A successful reading with no windows also stores a non-secret empty-quota marker (`plan` if present, `untrustedWindowIds` if any, original `refreshedAt`) so interval replay can return that observation; it never stores an `api_key`, token, email, payment field, or quota figure. The ledger holds no credential material or account data, and drops entries older than the five-minute interval on each write. |
|
|
1025
1103
|
|
|
1026
1104
|
## Development
|
|
1027
1105
|
|
|
@@ -1035,6 +1113,8 @@ pnpm run build:skill -- --check # Verify the generated skill is current
|
|
|
1035
1113
|
pnpm run dev # Run the CLI with tsx
|
|
1036
1114
|
```
|
|
1037
1115
|
|
|
1116
|
+
Provider tests use synthetic credential stores and mock Keychain, vendor-process, and HTTP boundaries, including Copilot `CredReadW` and the Claude process list. Never validate against a developer's live credentials, invoke the real `security` binary, refresh authentication, or call provider APIs. Adapter behavior is clean-room from each vendor's own OAuth/HTTP behavior. quota-axi carries no vendored third-party adapter code. The Z.AI adapter is the one attribution exception (derived from opencode-glm-quota, MIT), credited in `src/providers/zai.ts`; no third-party code is copied. The committed `pnpm-lock.yaml` is Prettier-formatted. After changing dependencies, run `pnpm exec prettier --write pnpm-lock.yaml`.
|
|
1117
|
+
|
|
1038
1118
|
## Contributing
|
|
1039
1119
|
|
|
1040
1120
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for the no-mistakes PR workflow, generated-file rules, and release-please conventions.
|