quotacap 0.0.22 → 0.0.24
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 +30 -9
- package/dist/adapters/grok.js +2 -2
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/muse.d.ts +8 -0
- package/dist/adapters/muse.js +96 -0
- package/dist/adapters/pty.d.ts +53 -0
- package/dist/adapters/pty.js +402 -74
- package/dist/advisory/provider-names.d.ts +14 -0
- package/dist/advisory/provider-names.js +98 -0
- package/dist/advisory/snapshot.d.ts +7 -0
- package/dist/advisory/snapshot.js +7 -0
- package/dist/advisory/types.d.ts +12 -0
- package/dist/advisory/validation.d.ts +9 -0
- package/dist/advisory/validation.js +39 -0
- package/dist/cli/advise.js +74 -7
- package/dist/cli/clients.d.ts +10 -0
- package/dist/cli/clients.js +2 -0
- package/dist/cli/index.js +13 -8
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +29 -0
- package/dist/cli/providers.d.ts +3 -0
- package/dist/cli/providers.js +174 -0
- package/dist/cli/runtime.d.ts +21 -0
- package/dist/cli/runtime.js +169 -42
- package/dist/cli/snapshot-source.d.ts +4 -1
- package/dist/cli/snapshot-source.js +52 -4
- package/dist/cli/status.js +78 -4
- package/dist/cli/takeover.d.ts +64 -0
- package/dist/cli/takeover.js +175 -0
- package/dist/cli/update.d.ts +61 -0
- package/dist/cli/update.js +392 -0
- package/dist/config.d.ts +18 -0
- package/dist/config.js +106 -1
- package/dist/diagnostics/failure.d.ts +26 -0
- package/dist/diagnostics/failure.js +417 -0
- package/dist/format/issues.d.ts +2 -0
- package/dist/format/issues.js +17 -0
- package/dist/format/markdown.js +1 -1
- package/dist/format/rows.js +2 -0
- package/dist/format/terminal.js +6 -6
- package/dist/http/server.d.ts +10 -0
- package/dist/http/server.js +115 -2
- package/dist/mcp/server.js +2 -0
- package/dist/runtime/client.d.ts +1 -0
- package/dist/runtime/client.js +21 -1
- package/dist/runtime/poll.d.ts +14 -4
- package/dist/runtime/poll.js +56 -39
- package/dist/runtime/service.js +26 -3
- package/dist/runtime/spawn.js +14 -4
- package/dist/runtime/updates.d.ts +74 -0
- package/dist/runtime/updates.js +445 -0
- package/dist/runtime/versions.d.ts +2 -0
- package/dist/runtime/versions.js +26 -0
- package/dist/service/index.d.ts +7 -2
- package/dist/service/index.js +67 -11
- package/dist/service/macos.d.ts +4 -0
- package/dist/service/macos.js +62 -11
- package/dist/service/systemd.d.ts +40 -0
- package/dist/service/systemd.js +387 -0
- package/dist/store/attempts.d.ts +6 -0
- package/dist/store/attempts.js +27 -20
- package/dist/store/db.js +6 -1
- package/dist/version.js +1 -1
- package/dist/web/src/App.js +1 -1
- package/dist/web/src/api.d.ts +2 -0
- package/dist/web/src/api.js +20 -0
- package/dist/web/src/components/AdviceDrawer.js +3 -4
- package/dist/web/src/components/FaultBanner.js +2 -3
- package/dist/web/src/components/Header.js +5 -1
- package/dist/web/src/components/ProviderCard.js +1 -2
- package/dist/web/src/components/ProviderDrawer.d.ts +8 -1
- package/dist/web/src/components/ProviderDrawer.js +103 -7
- package/dist/web/src/components/ProviderIcon.js +1 -0
- package/dist/web/src/components/ProviderRow.js +1 -2
- package/dist/web/src/components/Recommendation.js +2 -3
- package/dist/web/src/components/ResetRail.js +4 -5
- package/dist/web/src/components/SubscriptionList.js +1 -2
- package/dist/web/src/components/settings/ProvidersPanel.js +1 -2
- package/dist/web/src/pages/Onboarding.js +4 -5
- package/dist/web/src/state.d.ts +18 -0
- package/dist/web/src/state.js +18 -0
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +4 -3
- package/web/dist/assets/index-CzObzbxb.js +12 -0
- package/web/dist/assets/index-VioQmN7J.css +1 -0
- package/web/dist/index.html +2 -2
- package/dist/src/adapters/agy.d.ts +0 -3
- package/dist/src/adapters/agy.js +0 -81
- package/dist/src/adapters/claude.d.ts +0 -9
- package/dist/src/adapters/claude.js +0 -30
- package/dist/src/adapters/codex.d.ts +0 -7
- package/dist/src/adapters/codex.js +0 -140
- package/dist/src/adapters/grok.d.ts +0 -7
- package/dist/src/adapters/grok.js +0 -126
- package/dist/src/adapters/index.d.ts +0 -21
- package/dist/src/adapters/index.js +0 -66
- package/dist/src/adapters/kimi.d.ts +0 -7
- package/dist/src/adapters/kimi.js +0 -76
- package/dist/src/adapters/manual.d.ts +0 -8
- package/dist/src/adapters/manual.js +0 -28
- package/dist/src/adapters/parse.d.ts +0 -1
- package/dist/src/adapters/parse.js +0 -81
- package/dist/src/adapters/pty.d.ts +0 -23
- package/dist/src/adapters/pty.js +0 -487
- package/dist/src/adapters/types.d.ts +0 -21
- package/dist/src/adapters/types.js +0 -1
- package/dist/src/advisory/engine.d.ts +0 -28
- package/dist/src/advisory/engine.js +0 -149
- package/dist/src/advisory/snapshot.d.ts +0 -16
- package/dist/src/advisory/snapshot.js +0 -213
- package/dist/src/advisory/types.d.ts +0 -56
- package/dist/src/advisory/types.js +0 -1
- package/dist/src/advisory/validate.d.ts +0 -5
- package/dist/src/advisory/validate.js +0 -18
- package/dist/src/cli/advise.d.ts +0 -3
- package/dist/src/cli/advise.js +0 -62
- package/dist/src/cli/clients.d.ts +0 -18
- package/dist/src/cli/clients.js +0 -11
- package/dist/src/cli/index.d.ts +0 -2
- package/dist/src/cli/index.js +0 -22
- package/dist/src/cli/ingest.d.ts +0 -3
- package/dist/src/cli/ingest.js +0 -31
- package/dist/src/cli/runtime.d.ts +0 -12
- package/dist/src/cli/runtime.js +0 -125
- package/dist/src/cli/snapshot-source.d.ts +0 -25
- package/dist/src/cli/snapshot-source.js +0 -223
- package/dist/src/cli/status.d.ts +0 -3
- package/dist/src/cli/status.js +0 -84
- package/dist/src/config.d.ts +0 -40
- package/dist/src/config.js +0 -157
- package/dist/src/daemon.d.ts +0 -4
- package/dist/src/daemon.js +0 -15
- package/dist/src/format/markdown.d.ts +0 -3
- package/dist/src/format/markdown.js +0 -24
- package/dist/src/format/rows.d.ts +0 -27
- package/dist/src/format/rows.js +0 -241
- package/dist/src/format/terminal.d.ts +0 -12
- package/dist/src/format/terminal.js +0 -144
- package/dist/src/http/server.d.ts +0 -25
- package/dist/src/http/server.js +0 -274
- package/dist/src/mcp/server.d.ts +0 -46
- package/dist/src/mcp/server.js +0 -163
- package/dist/src/runtime/client.d.ts +0 -24
- package/dist/src/runtime/client.js +0 -72
- package/dist/src/runtime/owner.d.ts +0 -35
- package/dist/src/runtime/owner.js +0 -393
- package/dist/src/runtime/poll.d.ts +0 -48
- package/dist/src/runtime/poll.js +0 -222
- package/dist/src/runtime/service.d.ts +0 -24
- package/dist/src/runtime/service.js +0 -251
- package/dist/src/runtime/spawn.d.ts +0 -17
- package/dist/src/runtime/spawn.js +0 -97
- package/dist/src/runtime/token.d.ts +0 -3
- package/dist/src/runtime/token.js +0 -64
- package/dist/src/service/index.d.ts +0 -4
- package/dist/src/service/index.js +0 -47
- package/dist/src/service/macos.d.ts +0 -96
- package/dist/src/service/macos.js +0 -551
- package/dist/src/store/attempts.d.ts +0 -12
- package/dist/src/store/attempts.js +0 -29
- package/dist/src/store/db.d.ts +0 -2
- package/dist/src/store/db.js +0 -74
- package/dist/src/store/quotas.d.ts +0 -6
- package/dist/src/store/quotas.js +0 -98
- package/dist/src/version.d.ts +0 -1
- package/dist/src/version.js +0 -2
- package/dist/src/webAssets.d.ts +0 -1
- package/dist/src/webAssets.js +0 -2
- package/dist/src/webHtml.d.ts +0 -1
- package/dist/src/webHtml.js +0 -2
- package/dist/tests/adapters/agy.test.d.ts +0 -1
- package/dist/tests/adapters/agy.test.js +0 -337
- package/dist/tests/adapters/claude.test.d.ts +0 -1
- package/dist/tests/adapters/claude.test.js +0 -83
- package/dist/tests/adapters/codex.test.d.ts +0 -1
- package/dist/tests/adapters/codex.test.js +0 -246
- package/dist/tests/adapters/credential-free.test.d.ts +0 -1
- package/dist/tests/adapters/credential-free.test.js +0 -211
- package/dist/tests/adapters/grok.test.d.ts +0 -1
- package/dist/tests/adapters/grok.test.js +0 -259
- package/dist/tests/adapters/kimi.test.d.ts +0 -1
- package/dist/tests/adapters/kimi.test.js +0 -235
- package/dist/tests/adapters/manual.test.d.ts +0 -1
- package/dist/tests/adapters/manual.test.js +0 -28
- package/dist/tests/adapters/pty.test.d.ts +0 -1
- package/dist/tests/adapters/pty.test.js +0 -240
- package/dist/tests/adapters/registry.test.d.ts +0 -1
- package/dist/tests/adapters/registry.test.js +0 -25
- package/dist/tests/advisory/engine.test.d.ts +0 -1
- package/dist/tests/advisory/engine.test.js +0 -222
- package/dist/tests/advisory/pace.test.d.ts +0 -1
- package/dist/tests/advisory/pace.test.js +0 -60
- package/dist/tests/advisory/snapshot.test.d.ts +0 -1
- package/dist/tests/advisory/snapshot.test.js +0 -65
- package/dist/tests/advisory/validate.test.d.ts +0 -1
- package/dist/tests/advisory/validate.test.js +0 -19
- package/dist/tests/bootstrap.test.d.ts +0 -1
- package/dist/tests/bootstrap.test.js +0 -14
- package/dist/tests/cli/advise.test.d.ts +0 -1
- package/dist/tests/cli/advise.test.js +0 -294
- package/dist/tests/cli/cli.test.d.ts +0 -1
- package/dist/tests/cli/cli.test.js +0 -46
- package/dist/tests/cli/helpers.d.ts +0 -9
- package/dist/tests/cli/helpers.js +0 -93
- package/dist/tests/cli/ingest.test.d.ts +0 -1
- package/dist/tests/cli/ingest.test.js +0 -188
- package/dist/tests/cli/parity.test.d.ts +0 -1
- package/dist/tests/cli/parity.test.js +0 -225
- package/dist/tests/cli/snapshot-source.test.d.ts +0 -1
- package/dist/tests/cli/snapshot-source.test.js +0 -305
- package/dist/tests/cli/status.test.d.ts +0 -1
- package/dist/tests/cli/status.test.js +0 -423
- package/dist/tests/daemon.test.d.ts +0 -1
- package/dist/tests/daemon.test.js +0 -164
- package/dist/tests/fixtures/stable-state.d.ts +0 -22
- package/dist/tests/fixtures/stable-state.js +0 -152
- package/dist/tests/fixtures/stable-state.test.d.ts +0 -1
- package/dist/tests/fixtures/stable-state.test.js +0 -17
- package/dist/tests/format/markdown.test.d.ts +0 -1
- package/dist/tests/format/markdown.test.js +0 -49
- package/dist/tests/format/terminal.test.d.ts +0 -1
- package/dist/tests/format/terminal.test.js +0 -476
- package/dist/tests/http/api.test.d.ts +0 -1
- package/dist/tests/http/api.test.js +0 -236
- package/dist/tests/integration.test.d.ts +0 -1
- package/dist/tests/integration.test.js +0 -61
- package/dist/tests/mcp/protocol.test.d.ts +0 -1
- package/dist/tests/mcp/protocol.test.js +0 -134
- package/dist/tests/mcp/quotas.test.d.ts +0 -1
- package/dist/tests/mcp/quotas.test.js +0 -56
- package/dist/tests/mcp/server.test.d.ts +0 -1
- package/dist/tests/mcp/server.test.js +0 -7
- package/dist/tests/mcp/tools.test.d.ts +0 -1
- package/dist/tests/mcp/tools.test.js +0 -291
- package/dist/tests/runtime/cancel.test.d.ts +0 -1
- package/dist/tests/runtime/cancel.test.js +0 -264
- package/dist/tests/runtime/cli.test.d.ts +0 -1
- package/dist/tests/runtime/cli.test.js +0 -226
- package/dist/tests/runtime/config.test.d.ts +0 -1
- package/dist/tests/runtime/config.test.js +0 -144
- package/dist/tests/runtime/http.test.d.ts +0 -1
- package/dist/tests/runtime/http.test.js +0 -267
- package/dist/tests/runtime/lifecycle.test.d.ts +0 -1
- package/dist/tests/runtime/lifecycle.test.js +0 -303
- package/dist/tests/runtime/ownership.test.d.ts +0 -1
- package/dist/tests/runtime/ownership.test.js +0 -355
- package/dist/tests/runtime/poll.test.d.ts +0 -1
- package/dist/tests/runtime/poll.test.js +0 -410
- package/dist/tests/runtime/status.test.d.ts +0 -1
- package/dist/tests/runtime/status.test.js +0 -232
- package/dist/tests/service/macos.test.d.ts +0 -1
- package/dist/tests/service/macos.test.js +0 -349
- package/dist/tests/store/attempts.test.d.ts +0 -1
- package/dist/tests/store/attempts.test.js +0 -29
- package/dist/tests/store/db.test.d.ts +0 -1
- package/dist/tests/store/db.test.js +0 -96
- package/dist/tests/store/migration.test.d.ts +0 -1
- package/dist/tests/store/migration.test.js +0 -41
- package/dist/tests/web/advice-drawer.test.d.ts +0 -1
- package/dist/tests/web/advice-drawer.test.js +0 -45
- package/dist/tests/web/build.test.d.ts +0 -1
- package/dist/tests/web/build.test.js +0 -8
- package/dist/tests/web/dashboard.e2e.d.ts +0 -1
- package/dist/tests/web/dashboard.e2e.js +0 -670
- package/dist/tests/web/header.test.d.ts +0 -1
- package/dist/tests/web/header.test.js +0 -56
- package/dist/tests/web/names.test.d.ts +0 -1
- package/dist/tests/web/names.test.js +0 -19
- package/dist/tests/web/onboarding.test.d.ts +0 -1
- package/dist/tests/web/onboarding.test.js +0 -21
- package/dist/tests/web/pacing.test.d.ts +0 -1
- package/dist/tests/web/pacing.test.js +0 -62
- package/dist/tests/web/playwright.config.d.ts +0 -2
- package/dist/tests/web/playwright.config.js +0 -10
- package/dist/tests/web/provider-drawer.test.d.ts +0 -1
- package/dist/tests/web/provider-drawer.test.js +0 -68
- package/dist/tests/web/recommendation.test.d.ts +0 -1
- package/dist/tests/web/recommendation.test.js +0 -38
- package/dist/tests/web/reset-rail.test.d.ts +0 -1
- package/dist/tests/web/reset-rail.test.js +0 -63
- package/dist/tests/web/responsive.test.d.ts +0 -1
- package/dist/tests/web/responsive.test.js +0 -21
- package/dist/tests/web/settings.test.d.ts +0 -1
- package/dist/tests/web/settings.test.js +0 -32
- package/dist/tests/web/state-catalog.test.d.ts +0 -1
- package/dist/tests/web/state-catalog.test.js +0 -206
- package/dist/tests/web/state.test.d.ts +0 -1
- package/dist/tests/web/state.test.js +0 -27
- package/dist/tests/web/stub-server.d.ts +0 -37
- package/dist/tests/web/stub-server.js +0 -132
- package/dist/tests/web/theme.test.d.ts +0 -1
- package/dist/tests/web/theme.test.js +0 -12
- package/dist/web/src/names.d.ts +0 -3
- package/dist/web/src/names.js +0 -19
- package/scripts/fix-pty-perms.mjs +0 -21
- package/web/dist/assets/index-CyUkWdwJ.css +0 -1
- package/web/dist/assets/index-PN4RpMKG.js +0 -12
package/README.md
CHANGED
|
@@ -19,10 +19,11 @@ QuotaCap helps you get more from the AI coding subscriptions you already pay for
|
|
|
19
19
|
| Provider | Mechanism | Source |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| Claude Code | `exec` — `claude -p /usage --output-format json` | Live |
|
|
22
|
-
| Antigravity | `exec` — `agy -p /usage --output-format json` (two rows:
|
|
22
|
+
| Antigravity | `exec` — `agy -p /usage --output-format json` (two rows: Antigravity for Gemini, Antigravity 3P for third-party models) | Live |
|
|
23
23
|
| Codex | `pty` — `codex --no-alt-screen` then `/status`, parse `Weekly/5h limit: X% left` | Live |
|
|
24
24
|
| Kimi Code | `pty` — `kimi` then `/usage`, parse `Weekly/5h limit: Y% used` | Live |
|
|
25
25
|
| Grok | `pty` — `grok` then `/usage`, parse `Weekly limit (plan)` + `Credits: $X` | Live |
|
|
26
|
+
| Muse Code | `pty` — `muse --trust-workspace` then `/usage`, parse `Subscription · Muse Code <plan>` + `Weekly/Current N% used` | Live |
|
|
26
27
|
|
|
27
28
|
Exec adapters run via `execFile` with an argv list. PTY adapters run via `node-pty` (`src/adapters/pty.ts`). They are TUI-fragile: a vendor text change breaks the parser and the row degrades fail-closed until the regex is fixed. Poll latency is 2–10 s per PTY provider (settle plus completion). It dominates `POST /api/refresh` and the first poll, not the steady-state 15 m timer.
|
|
28
29
|
|
|
@@ -39,21 +40,22 @@ npm install -g quotacap
|
|
|
39
40
|
```
|
|
40
41
|
|
|
41
42
|
The binary is a self-contained executable from GitHub Releases (shipped as `quotacap-<os>-<arch>.tar.gz` with a `pty` sidecar for Kimi/Codex/Grok); `install.sh` handles the tarball and sidecar transparently.
|
|
43
|
+
One command takes you from zero to a running tracker: the installer verifies checksums, provisions config, registers the background login service, and opens the dashboard. Re-running it upgrades in place with no flags. Pass `--no-service` (foreground only) or `--no-open` (SSH sessions) to skip steps.
|
|
42
44
|
The npm package runs the same CLI on Node. To run without a global install, replace `quotacap` with `npx quotacap` in any command, for example `npx quotacap web`.
|
|
43
45
|
|
|
44
46
|
## Quick start
|
|
45
47
|
|
|
46
48
|
```bash
|
|
47
|
-
#
|
|
48
|
-
quotacap
|
|
49
|
-
quotacap web # dashboard at http://localhost:8787
|
|
50
|
-
|
|
51
|
-
# Terminal 2: after the dashboard table fills
|
|
52
|
-
quotacap status
|
|
49
|
+
quotacap # opens the dashboard, starting the service if needed
|
|
50
|
+
quotacap status # summary table (pass --verbose for adapter failure guidance)
|
|
53
51
|
quotacap advise
|
|
52
|
+
quotacap update # upgrade to the latest release
|
|
53
|
+
quotacap providers list # view built-in and effective provider names
|
|
54
|
+
quotacap providers rename claude "Work" # customize provider display name
|
|
55
|
+
quotacap providers reset claude # restore built-in name (--all resets all)
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
`
|
|
58
|
+
No setup ceremony: config is provisioned on first use, and bare `quotacap` is the dashboard launcher. `quotacap status --verbose` displays actionable remediation guidance and sanitized failure details for failing adapters. `status` remains strictly read-only and never triggers background polling. `quotacap providers` manages custom display names (persisted to `~/.quotacap/config.json` under `providerNames`). `quotacap web --foreground` and `quotacap daemon` keep the old hold-the-terminal mode for SSH, containers, and supervisors.
|
|
57
59
|
|
|
58
60
|
## MCP
|
|
59
61
|
|
|
@@ -80,6 +82,7 @@ The local web server listens on `127.0.0.1:8787` (configured via `QUOTACAP_URL`
|
|
|
80
82
|
| `GET` | `/api/recommendation` | None | Current advisory on which provider to use next |
|
|
81
83
|
| `GET` | `/api/token` | Same-origin | Shared secret token for the dashboard |
|
|
82
84
|
| `POST` | `/api/refresh` | `X-QuotaCap-Token` | Trigger an immediate adapter poll (debounced to 60s) |
|
|
85
|
+
| `PATCH` | `/api/providers/:id` | `X-QuotaCap-Token` | Set or clear custom provider display name override |
|
|
83
86
|
|
|
84
87
|
### Curl examples
|
|
85
88
|
|
|
@@ -96,11 +99,27 @@ curl -X POST http://localhost:8787/api/refresh \
|
|
|
96
99
|
-H "X-QuotaCap-Token: $(cat ~/.quotacap/token)"
|
|
97
100
|
```
|
|
98
101
|
|
|
102
|
+
Set or clear a provider display name override:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Set custom display name
|
|
106
|
+
curl -X PATCH http://localhost:8787/api/providers/claude \
|
|
107
|
+
-H "X-QuotaCap-Token: $(cat ~/.quotacap/token)" \
|
|
108
|
+
-H "Content-Type: application/json" \
|
|
109
|
+
-d '{"displayName": "Work Claude"}'
|
|
110
|
+
|
|
111
|
+
# Reset to built-in name
|
|
112
|
+
curl -X PATCH http://localhost:8787/api/providers/claude \
|
|
113
|
+
-H "X-QuotaCap-Token: $(cat ~/.quotacap/token)" \
|
|
114
|
+
-H "Content-Type: application/json" \
|
|
115
|
+
-d '{"displayName": null}'
|
|
116
|
+
```
|
|
117
|
+
|
|
99
118
|
## Security
|
|
100
119
|
|
|
101
120
|
QuotaCap is a local daemon. It binds to `127.0.0.1` only (`src/cli/index.ts:56`). It does not listen on `0.0.0.0`. There is no LAN surface.
|
|
102
121
|
|
|
103
|
-
It owns no tokens. It never reads `~/.codex/auth.json`, `~/.kimi-code/credentials/kimi-code.json`, `~/.kimi/credentials/kimi-code.json`, `~/.grok/auth.json`, or `~/.gemini/oauth_creds.json`. It never uses `refresh_token` or `grant_type=refresh_token`. It has no hardcoded client ids. Those OAuth paths and the `.qc-bak` and `.qc-lock` helpers were removed in #14. This is asserted by `tests/adapters/credential-free.test.ts`. Each CLI owns its own session. QuotaCap only spawns the CLI and reads its stdout via `exec` (`claude`, `agy`) or PTY (`codex`, `kimi`, `grok`).
|
|
122
|
+
It owns no tokens. It never reads `~/.codex/auth.json`, `~/.kimi-code/credentials/kimi-code.json`, `~/.kimi/credentials/kimi-code.json`, `~/.grok/auth.json`, or `~/.gemini/oauth_creds.json`. It never uses `refresh_token` or `grant_type=refresh_token`. It has no hardcoded client ids. Those OAuth paths and the `.qc-bak` and `.qc-lock` helpers were removed in #14. This is asserted by `tests/adapters/credential-free.test.ts`. Each CLI owns its own session. It never reads `~/.config/muse/auth.json`, `~/.local/share/muse/sessions/`, or `~/.config/muse/tui-history.jsonl`. Each CLI owns its own session. QuotaCap only spawns the CLI and reads its stdout via `exec` (`claude`, `agy`) or PTY (`codex`, `kimi`, `grok`, `muse`).
|
|
104
123
|
|
|
105
124
|
It stores no `raw` provider payload. The `raw` column was dropped and migrated in `src/store/db.ts:37-48`. `GET /api/quotas` and MCP `get_quotas` never return `raw` (`tests/http/api.test.ts`). History and the token live under `~/.quotacap/` with `0700` on the directory and `0600` on files.
|
|
106
125
|
|
|
@@ -136,6 +155,8 @@ The same code is an npm package and a compiled binary.
|
|
|
136
155
|
|
|
137
156
|
The Kimi adapter spawns `kimi` in your home directory so the quota modal does not depend on the project path. If Kimi shows `Trust this folder?`, QuotaCap fails closed with `untrusted workspace — run \`kimi\` there and select Trust this folder` and does not auto-trust; trust remains an explicit interactive decision.
|
|
138
157
|
|
|
158
|
+
The Muse adapter never trusts a directory of yours. It runs `muse --trust-workspace` with its working directory set to an empty folder QuotaCap owns (`~/.quotacap/muse-probe/`), so the flag has no project-local skills, rules, hooks or plugin config to load, and Muse's own flag does not persist trust. If a future Muse ignores the flag and shows the prompt anyway, the adapter aborts fail-closed rather than answering it.
|
|
159
|
+
|
|
139
160
|
The compiled binaries are built with `bun build --compile`.
|
|
140
161
|
Each `dist-bin/quotacap-<os>-<arch>.tar.gz` bundles a `pty` sidecar.
|
|
141
162
|
The sidecar is `pty/node-pty` with the matching prebuild.
|
package/dist/adapters/grok.js
CHANGED
|
@@ -10,7 +10,7 @@ const ABBR_MONTHS = {
|
|
|
10
10
|
jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
|
|
11
11
|
};
|
|
12
12
|
function parseGrokReset(raw, now) {
|
|
13
|
-
const m = raw.trim().match(/^([A-Za-z]+)\s+(\d{1,2}),\s
|
|
13
|
+
const m = raw.trim().match(/^([A-Za-z]+)\s+(\d{1,2}),\s*(\d{1,2}):(\d{2})$/);
|
|
14
14
|
if (!m)
|
|
15
15
|
return null;
|
|
16
16
|
const monStr = m[1].toLowerCase();
|
|
@@ -73,7 +73,7 @@ export function parseGrokTui(text, now = new Date()) {
|
|
|
73
73
|
if (Number.isFinite(v) && v >= 0)
|
|
74
74
|
creditsUsd = v;
|
|
75
75
|
}
|
|
76
|
-
const mReset = cleaned.match(/Resets:\s*([A-Za-z]+\s+\d+,\s
|
|
76
|
+
const mReset = cleaned.match(/Resets:\s*([A-Za-z]+\s+\d+,\s*\d+:\d+)/i);
|
|
77
77
|
let resetsAt = null;
|
|
78
78
|
let estimated = false;
|
|
79
79
|
if (mReset) {
|
package/dist/adapters/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { codexAdapter } from "./codex.js";
|
|
|
4
4
|
import { kimiAdapter } from "./kimi.js";
|
|
5
5
|
import { grokAdapter } from "./grok.js";
|
|
6
6
|
import { agyAdapter } from "./agy.js";
|
|
7
|
+
import { museAdapter } from "./muse.js";
|
|
7
8
|
import { installAdapterSignal, clearAdapterSignal } from "../runtime/spawn.js";
|
|
8
9
|
export const adapters = {
|
|
9
10
|
claude: claudeAdapter,
|
|
@@ -12,6 +13,7 @@ export const adapters = {
|
|
|
12
13
|
kimi: kimiAdapter,
|
|
13
14
|
grok: grokAdapter,
|
|
14
15
|
agy: agyAdapter,
|
|
16
|
+
muse: museAdapter,
|
|
15
17
|
};
|
|
16
18
|
const ADAPTER_TIMEOUTS = {
|
|
17
19
|
claude: 8000,
|
|
@@ -19,6 +21,7 @@ const ADAPTER_TIMEOUTS = {
|
|
|
19
21
|
kimi: 8000,
|
|
20
22
|
grok: 14000,
|
|
21
23
|
agy: 20000,
|
|
24
|
+
muse: 14000,
|
|
22
25
|
};
|
|
23
26
|
export async function pollAll(enabled, opts) {
|
|
24
27
|
const rawJobs = enabled.map(id => {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ParsedQuota } from "./types.js";
|
|
2
|
+
export declare function museProbeDir(): string;
|
|
3
|
+
export declare function parseMuseTui(text: string, now?: Date): ParsedQuota;
|
|
4
|
+
export declare const museAdapter: {
|
|
5
|
+
id: string;
|
|
6
|
+
requiresAuth: string;
|
|
7
|
+
poll(): Promise<ParsedQuota>;
|
|
8
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseResetText } from "./parse.js";
|
|
5
|
+
import { runPty, stripAnsi } from "./pty.js";
|
|
6
|
+
import { adapterSignal } from "../runtime/spawn.js";
|
|
7
|
+
// QuotaCap-owned empty probe dir, beside the config and database. Muse loads
|
|
8
|
+
// project-local skills, rules, hooks and plugin config from cwd, so the probe
|
|
9
|
+
// never runs in $HOME or a user project — an empty dir grants nothing.
|
|
10
|
+
export function museProbeDir() {
|
|
11
|
+
return path.join(process.env.QUOTACAP_HOME ?? os.homedir(), ".quotacap", "muse-probe");
|
|
12
|
+
}
|
|
13
|
+
export function parseMuseTui(text, now = new Date()) {
|
|
14
|
+
// After ANSI stripping the panel is ONE line (cursor-addressed, no
|
|
15
|
+
// newlines), so no pattern here may anchor on ^, $ or \n.
|
|
16
|
+
const cleaned = stripAnsi(text);
|
|
17
|
+
const weekly = cleaned.match(/Weekly\s+(\d+)%\s+used/i);
|
|
18
|
+
if (!weekly)
|
|
19
|
+
throw new Error("muse: weekly usage not found in TUI output");
|
|
20
|
+
const current = cleaned.match(/Current\s+(\d+)%\s+used/i);
|
|
21
|
+
if (!current)
|
|
22
|
+
throw new Error("muse: current-window usage not found in TUI output");
|
|
23
|
+
const usedPct = parseInt(weekly[1], 10);
|
|
24
|
+
const sessionPct = parseInt(current[1], 10);
|
|
25
|
+
if (!Number.isFinite(usedPct) || usedPct < 0 || usedPct > 100)
|
|
26
|
+
throw new Error("muse: bad weekly pct");
|
|
27
|
+
if (!Number.isFinite(sessionPct) || sessionPct < 0 || sessionPct > 100)
|
|
28
|
+
throw new Error("muse: bad current pct");
|
|
29
|
+
// First match wins; overdrawn transcripts carry two identical copies.
|
|
30
|
+
// Vendor casing preserved (grok precedent: SuperGrok); the Muse Code
|
|
31
|
+
// product prefix is stripped because the product is already the row label.
|
|
32
|
+
let plan = "unknown";
|
|
33
|
+
const planMatch = cleaned.match(/Subscription\s*·\s*Muse Code\s+(.+?)\s{2,}/i);
|
|
34
|
+
if (planMatch)
|
|
35
|
+
plan = planMatch[1].trim();
|
|
36
|
+
// The Current-window reset ("Resets at 3:51 PM") is a bare clock time with
|
|
37
|
+
// no date, and Quota has no session-reset field — discarded, as claude and
|
|
38
|
+
// kimi already do. A missing or unparseable Weekly reset throws rather than
|
|
39
|
+
// estimating: an invented reset would feed the advisory a false deadline.
|
|
40
|
+
const resetMatch = cleaned.match(/Weekly[^\n]*?Resets\s+([A-Za-z]{3}\s+\d{1,2}\s+at\s+\d{1,2}:\d{2}\s*[AP]M)/i);
|
|
41
|
+
if (!resetMatch)
|
|
42
|
+
throw new Error("muse: bad weekly reset");
|
|
43
|
+
const resetRaw = resetMatch[1].trim();
|
|
44
|
+
const resetsAt = parseResetText(`resets ${resetRaw}`, now);
|
|
45
|
+
if (!resetsAt)
|
|
46
|
+
throw new Error(`muse: bad weekly reset "${resetRaw}"`);
|
|
47
|
+
const periodStart = new Date(new Date(resetsAt).getTime() - 7 * 86400000).toISOString();
|
|
48
|
+
return {
|
|
49
|
+
provider: "muse",
|
|
50
|
+
plan,
|
|
51
|
+
usedPct,
|
|
52
|
+
sessionPct,
|
|
53
|
+
resetsAt,
|
|
54
|
+
periodStart,
|
|
55
|
+
source: "tui",
|
|
56
|
+
fetchedAt: now.toISOString(),
|
|
57
|
+
raw: cleaned.slice(0, 4096),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export const museAdapter = {
|
|
61
|
+
id: "muse",
|
|
62
|
+
requiresAuth: "muse login (CLI owns credentials)",
|
|
63
|
+
async poll() {
|
|
64
|
+
const cwd = museProbeDir();
|
|
65
|
+
fs.mkdirSync(cwd, { recursive: true });
|
|
66
|
+
const transcript = await runPty({
|
|
67
|
+
file: "muse",
|
|
68
|
+
args: ["--trust-workspace"],
|
|
69
|
+
cwd,
|
|
70
|
+
// Without this, a missing binary triggers a foreground 248 MB download
|
|
71
|
+
// on every poll; with it the launcher dies fast with a clear message.
|
|
72
|
+
env: { MUSE_NO_AUTO_UPDATE: "1" },
|
|
73
|
+
cols: 140,
|
|
74
|
+
rows: 50,
|
|
75
|
+
readyRegex: /muse-spark|Muse Code \d/,
|
|
76
|
+
readyTimeoutMs: 8000,
|
|
77
|
+
settleDelayMs: 1000,
|
|
78
|
+
// Two-phase submit: "/usage" first, CR 1500ms later. A single combined
|
|
79
|
+
// write is swallowed by the slash-command autocomplete and never runs.
|
|
80
|
+
input: "/usage",
|
|
81
|
+
submitInput: "\r",
|
|
82
|
+
submitAfterMs: 1500,
|
|
83
|
+
completionRegex: /Subscription/i,
|
|
84
|
+
// Trust prompt (in case a future Muse ignores the flag), an accidental
|
|
85
|
+
// model turn from a mistimed Enter, and the unavailable-subscription
|
|
86
|
+
// fast-fail — every one aborts to a degraded row, never a 14s timeout.
|
|
87
|
+
abortOn: /Do you trust this workspace|Working \(\d+s|Subscriptions aren't currently available|subscription_unavailable/,
|
|
88
|
+
timeoutMs: 14000,
|
|
89
|
+
respondToQueries: true,
|
|
90
|
+
maxBytes: 256 * 1024,
|
|
91
|
+
signal: adapterSignal("muse"),
|
|
92
|
+
label: "muse",
|
|
93
|
+
});
|
|
94
|
+
return parseMuseTui(transcript);
|
|
95
|
+
},
|
|
96
|
+
};
|
package/dist/adapters/pty.d.ts
CHANGED
|
@@ -8,8 +8,26 @@ export interface PtyRunOptions {
|
|
|
8
8
|
rows?: number;
|
|
9
9
|
readyRegex?: RegExp;
|
|
10
10
|
readyTimeoutMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Settle delay after spawn (no readyRegex) or after readiness (with
|
|
13
|
+
* readyRegex). The post-ready default stays 200ms; pass an explicit value
|
|
14
|
+
* (e.g. muse's 1000ms) only when the TUI needs longer after signalling.
|
|
15
|
+
*/
|
|
11
16
|
settleDelayMs?: number;
|
|
12
17
|
input: string;
|
|
18
|
+
/**
|
|
19
|
+
* Second-phase input, written `submitAfterMs` after `input`. Both must be
|
|
20
|
+
* set for the second write to happen; absent by default, which keeps the
|
|
21
|
+
* historical single-write behaviour for existing adapters.
|
|
22
|
+
*/
|
|
23
|
+
submitInput?: string;
|
|
24
|
+
submitAfterMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Opt-in terminal-query responder: answers DSR, DA1 and OSC 4/10/11
|
|
27
|
+
* capability queries the child emits. Off by default; TUIs that never
|
|
28
|
+
* query (codex, kimi, grok) are unaffected either way.
|
|
29
|
+
*/
|
|
30
|
+
respondToQueries?: boolean;
|
|
13
31
|
completionRegex?: RegExp;
|
|
14
32
|
/** If matched, abort immediately with a clear error (e.g. trust prompts). */
|
|
15
33
|
abortOn?: RegExp;
|
|
@@ -20,4 +38,39 @@ export interface PtyRunOptions {
|
|
|
20
38
|
/** Diagnostic label for the tracked child (adapter id). */
|
|
21
39
|
label?: string;
|
|
22
40
|
}
|
|
41
|
+
export interface TerminalQueryRule {
|
|
42
|
+
/** Non-global pattern; must match the complete query including terminator. */
|
|
43
|
+
match: RegExp;
|
|
44
|
+
reply: (m: RegExpMatchArray) => string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Canned answers to terminal capability queries, table-driven so a future
|
|
48
|
+
* query type is a one-line row. Replies are static: colour answers never
|
|
49
|
+
* affect parsing because styling is stripped before matching.
|
|
50
|
+
*/
|
|
51
|
+
export declare const TERMINAL_QUERY_RESPONSES: TerminalQueryRule[];
|
|
52
|
+
/**
|
|
53
|
+
* Stateful feeder for `TERMINAL_QUERY_RESPONSES`. Consumes each matched query
|
|
54
|
+
* from an internal buffer, so a query split across chunks is answered once it
|
|
55
|
+
* completes and never answered twice. Plain output passes through silently.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createTerminalQueryResponder(write: (s: string) => void): (chunk: string) => void;
|
|
58
|
+
export interface SpawnHelperRepair {
|
|
59
|
+
helper: string;
|
|
60
|
+
ok: boolean;
|
|
61
|
+
error?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Lazy replacement for the removed postinstall: best-effort `chmod 0o755`
|
|
65
|
+
* over every prebuilds entry `spawn-helper` under the given node-pty roots.
|
|
66
|
+
* Missing roots, missing prebuild dirs, and missing helpers are skipped
|
|
67
|
+
* silently; only attempted repairs are recorded.
|
|
68
|
+
*/
|
|
69
|
+
export declare function repairSpawnHelpers(roots: string[], chmod?: (p: string, mode: number) => void): SpawnHelperRepair[];
|
|
70
|
+
/**
|
|
71
|
+
* Human diagnostic for failed repairs, or null when nothing failed.
|
|
72
|
+
* Load-bearing for root-owned trees (e.g. `sudo` global installs) where the
|
|
73
|
+
* invoking user cannot repair: without it the failure would go opaque again.
|
|
74
|
+
*/
|
|
75
|
+
export declare function describeSpawnHelperRepairs(outcomes: SpawnHelperRepair[]): string | null;
|
|
23
76
|
export declare function runPty(opts: PtyRunOptions): Promise<string>;
|