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.
Files changed (298) hide show
  1. package/README.md +30 -9
  2. package/dist/adapters/grok.js +2 -2
  3. package/dist/adapters/index.js +3 -0
  4. package/dist/adapters/muse.d.ts +8 -0
  5. package/dist/adapters/muse.js +96 -0
  6. package/dist/adapters/pty.d.ts +53 -0
  7. package/dist/adapters/pty.js +402 -74
  8. package/dist/advisory/provider-names.d.ts +14 -0
  9. package/dist/advisory/provider-names.js +98 -0
  10. package/dist/advisory/snapshot.d.ts +7 -0
  11. package/dist/advisory/snapshot.js +7 -0
  12. package/dist/advisory/types.d.ts +12 -0
  13. package/dist/advisory/validation.d.ts +9 -0
  14. package/dist/advisory/validation.js +39 -0
  15. package/dist/cli/advise.js +74 -7
  16. package/dist/cli/clients.d.ts +10 -0
  17. package/dist/cli/clients.js +2 -0
  18. package/dist/cli/index.js +13 -8
  19. package/dist/cli/init.d.ts +2 -0
  20. package/dist/cli/init.js +29 -0
  21. package/dist/cli/providers.d.ts +3 -0
  22. package/dist/cli/providers.js +174 -0
  23. package/dist/cli/runtime.d.ts +21 -0
  24. package/dist/cli/runtime.js +169 -42
  25. package/dist/cli/snapshot-source.d.ts +4 -1
  26. package/dist/cli/snapshot-source.js +52 -4
  27. package/dist/cli/status.js +78 -4
  28. package/dist/cli/takeover.d.ts +64 -0
  29. package/dist/cli/takeover.js +175 -0
  30. package/dist/cli/update.d.ts +61 -0
  31. package/dist/cli/update.js +392 -0
  32. package/dist/config.d.ts +18 -0
  33. package/dist/config.js +106 -1
  34. package/dist/diagnostics/failure.d.ts +26 -0
  35. package/dist/diagnostics/failure.js +417 -0
  36. package/dist/format/issues.d.ts +2 -0
  37. package/dist/format/issues.js +17 -0
  38. package/dist/format/markdown.js +1 -1
  39. package/dist/format/rows.js +2 -0
  40. package/dist/format/terminal.js +6 -6
  41. package/dist/http/server.d.ts +10 -0
  42. package/dist/http/server.js +115 -2
  43. package/dist/mcp/server.js +2 -0
  44. package/dist/runtime/client.d.ts +1 -0
  45. package/dist/runtime/client.js +21 -1
  46. package/dist/runtime/poll.d.ts +14 -4
  47. package/dist/runtime/poll.js +56 -39
  48. package/dist/runtime/service.js +26 -3
  49. package/dist/runtime/spawn.js +14 -4
  50. package/dist/runtime/updates.d.ts +74 -0
  51. package/dist/runtime/updates.js +445 -0
  52. package/dist/runtime/versions.d.ts +2 -0
  53. package/dist/runtime/versions.js +26 -0
  54. package/dist/service/index.d.ts +7 -2
  55. package/dist/service/index.js +67 -11
  56. package/dist/service/macos.d.ts +4 -0
  57. package/dist/service/macos.js +62 -11
  58. package/dist/service/systemd.d.ts +40 -0
  59. package/dist/service/systemd.js +387 -0
  60. package/dist/store/attempts.d.ts +6 -0
  61. package/dist/store/attempts.js +27 -20
  62. package/dist/store/db.js +6 -1
  63. package/dist/version.js +1 -1
  64. package/dist/web/src/App.js +1 -1
  65. package/dist/web/src/api.d.ts +2 -0
  66. package/dist/web/src/api.js +20 -0
  67. package/dist/web/src/components/AdviceDrawer.js +3 -4
  68. package/dist/web/src/components/FaultBanner.js +2 -3
  69. package/dist/web/src/components/Header.js +5 -1
  70. package/dist/web/src/components/ProviderCard.js +1 -2
  71. package/dist/web/src/components/ProviderDrawer.d.ts +8 -1
  72. package/dist/web/src/components/ProviderDrawer.js +103 -7
  73. package/dist/web/src/components/ProviderIcon.js +1 -0
  74. package/dist/web/src/components/ProviderRow.js +1 -2
  75. package/dist/web/src/components/Recommendation.js +2 -3
  76. package/dist/web/src/components/ResetRail.js +4 -5
  77. package/dist/web/src/components/SubscriptionList.js +1 -2
  78. package/dist/web/src/components/settings/ProvidersPanel.js +1 -2
  79. package/dist/web/src/pages/Onboarding.js +4 -5
  80. package/dist/web/src/state.d.ts +18 -0
  81. package/dist/web/src/state.js +18 -0
  82. package/dist/webAssets.js +1 -1
  83. package/dist/webHtml.js +1 -1
  84. package/package.json +4 -3
  85. package/web/dist/assets/index-CzObzbxb.js +12 -0
  86. package/web/dist/assets/index-VioQmN7J.css +1 -0
  87. package/web/dist/index.html +2 -2
  88. package/dist/src/adapters/agy.d.ts +0 -3
  89. package/dist/src/adapters/agy.js +0 -81
  90. package/dist/src/adapters/claude.d.ts +0 -9
  91. package/dist/src/adapters/claude.js +0 -30
  92. package/dist/src/adapters/codex.d.ts +0 -7
  93. package/dist/src/adapters/codex.js +0 -140
  94. package/dist/src/adapters/grok.d.ts +0 -7
  95. package/dist/src/adapters/grok.js +0 -126
  96. package/dist/src/adapters/index.d.ts +0 -21
  97. package/dist/src/adapters/index.js +0 -66
  98. package/dist/src/adapters/kimi.d.ts +0 -7
  99. package/dist/src/adapters/kimi.js +0 -76
  100. package/dist/src/adapters/manual.d.ts +0 -8
  101. package/dist/src/adapters/manual.js +0 -28
  102. package/dist/src/adapters/parse.d.ts +0 -1
  103. package/dist/src/adapters/parse.js +0 -81
  104. package/dist/src/adapters/pty.d.ts +0 -23
  105. package/dist/src/adapters/pty.js +0 -487
  106. package/dist/src/adapters/types.d.ts +0 -21
  107. package/dist/src/adapters/types.js +0 -1
  108. package/dist/src/advisory/engine.d.ts +0 -28
  109. package/dist/src/advisory/engine.js +0 -149
  110. package/dist/src/advisory/snapshot.d.ts +0 -16
  111. package/dist/src/advisory/snapshot.js +0 -213
  112. package/dist/src/advisory/types.d.ts +0 -56
  113. package/dist/src/advisory/types.js +0 -1
  114. package/dist/src/advisory/validate.d.ts +0 -5
  115. package/dist/src/advisory/validate.js +0 -18
  116. package/dist/src/cli/advise.d.ts +0 -3
  117. package/dist/src/cli/advise.js +0 -62
  118. package/dist/src/cli/clients.d.ts +0 -18
  119. package/dist/src/cli/clients.js +0 -11
  120. package/dist/src/cli/index.d.ts +0 -2
  121. package/dist/src/cli/index.js +0 -22
  122. package/dist/src/cli/ingest.d.ts +0 -3
  123. package/dist/src/cli/ingest.js +0 -31
  124. package/dist/src/cli/runtime.d.ts +0 -12
  125. package/dist/src/cli/runtime.js +0 -125
  126. package/dist/src/cli/snapshot-source.d.ts +0 -25
  127. package/dist/src/cli/snapshot-source.js +0 -223
  128. package/dist/src/cli/status.d.ts +0 -3
  129. package/dist/src/cli/status.js +0 -84
  130. package/dist/src/config.d.ts +0 -40
  131. package/dist/src/config.js +0 -157
  132. package/dist/src/daemon.d.ts +0 -4
  133. package/dist/src/daemon.js +0 -15
  134. package/dist/src/format/markdown.d.ts +0 -3
  135. package/dist/src/format/markdown.js +0 -24
  136. package/dist/src/format/rows.d.ts +0 -27
  137. package/dist/src/format/rows.js +0 -241
  138. package/dist/src/format/terminal.d.ts +0 -12
  139. package/dist/src/format/terminal.js +0 -144
  140. package/dist/src/http/server.d.ts +0 -25
  141. package/dist/src/http/server.js +0 -274
  142. package/dist/src/mcp/server.d.ts +0 -46
  143. package/dist/src/mcp/server.js +0 -163
  144. package/dist/src/runtime/client.d.ts +0 -24
  145. package/dist/src/runtime/client.js +0 -72
  146. package/dist/src/runtime/owner.d.ts +0 -35
  147. package/dist/src/runtime/owner.js +0 -393
  148. package/dist/src/runtime/poll.d.ts +0 -48
  149. package/dist/src/runtime/poll.js +0 -222
  150. package/dist/src/runtime/service.d.ts +0 -24
  151. package/dist/src/runtime/service.js +0 -251
  152. package/dist/src/runtime/spawn.d.ts +0 -17
  153. package/dist/src/runtime/spawn.js +0 -97
  154. package/dist/src/runtime/token.d.ts +0 -3
  155. package/dist/src/runtime/token.js +0 -64
  156. package/dist/src/service/index.d.ts +0 -4
  157. package/dist/src/service/index.js +0 -47
  158. package/dist/src/service/macos.d.ts +0 -96
  159. package/dist/src/service/macos.js +0 -551
  160. package/dist/src/store/attempts.d.ts +0 -12
  161. package/dist/src/store/attempts.js +0 -29
  162. package/dist/src/store/db.d.ts +0 -2
  163. package/dist/src/store/db.js +0 -74
  164. package/dist/src/store/quotas.d.ts +0 -6
  165. package/dist/src/store/quotas.js +0 -98
  166. package/dist/src/version.d.ts +0 -1
  167. package/dist/src/version.js +0 -2
  168. package/dist/src/webAssets.d.ts +0 -1
  169. package/dist/src/webAssets.js +0 -2
  170. package/dist/src/webHtml.d.ts +0 -1
  171. package/dist/src/webHtml.js +0 -2
  172. package/dist/tests/adapters/agy.test.d.ts +0 -1
  173. package/dist/tests/adapters/agy.test.js +0 -337
  174. package/dist/tests/adapters/claude.test.d.ts +0 -1
  175. package/dist/tests/adapters/claude.test.js +0 -83
  176. package/dist/tests/adapters/codex.test.d.ts +0 -1
  177. package/dist/tests/adapters/codex.test.js +0 -246
  178. package/dist/tests/adapters/credential-free.test.d.ts +0 -1
  179. package/dist/tests/adapters/credential-free.test.js +0 -211
  180. package/dist/tests/adapters/grok.test.d.ts +0 -1
  181. package/dist/tests/adapters/grok.test.js +0 -259
  182. package/dist/tests/adapters/kimi.test.d.ts +0 -1
  183. package/dist/tests/adapters/kimi.test.js +0 -235
  184. package/dist/tests/adapters/manual.test.d.ts +0 -1
  185. package/dist/tests/adapters/manual.test.js +0 -28
  186. package/dist/tests/adapters/pty.test.d.ts +0 -1
  187. package/dist/tests/adapters/pty.test.js +0 -240
  188. package/dist/tests/adapters/registry.test.d.ts +0 -1
  189. package/dist/tests/adapters/registry.test.js +0 -25
  190. package/dist/tests/advisory/engine.test.d.ts +0 -1
  191. package/dist/tests/advisory/engine.test.js +0 -222
  192. package/dist/tests/advisory/pace.test.d.ts +0 -1
  193. package/dist/tests/advisory/pace.test.js +0 -60
  194. package/dist/tests/advisory/snapshot.test.d.ts +0 -1
  195. package/dist/tests/advisory/snapshot.test.js +0 -65
  196. package/dist/tests/advisory/validate.test.d.ts +0 -1
  197. package/dist/tests/advisory/validate.test.js +0 -19
  198. package/dist/tests/bootstrap.test.d.ts +0 -1
  199. package/dist/tests/bootstrap.test.js +0 -14
  200. package/dist/tests/cli/advise.test.d.ts +0 -1
  201. package/dist/tests/cli/advise.test.js +0 -294
  202. package/dist/tests/cli/cli.test.d.ts +0 -1
  203. package/dist/tests/cli/cli.test.js +0 -46
  204. package/dist/tests/cli/helpers.d.ts +0 -9
  205. package/dist/tests/cli/helpers.js +0 -93
  206. package/dist/tests/cli/ingest.test.d.ts +0 -1
  207. package/dist/tests/cli/ingest.test.js +0 -188
  208. package/dist/tests/cli/parity.test.d.ts +0 -1
  209. package/dist/tests/cli/parity.test.js +0 -225
  210. package/dist/tests/cli/snapshot-source.test.d.ts +0 -1
  211. package/dist/tests/cli/snapshot-source.test.js +0 -305
  212. package/dist/tests/cli/status.test.d.ts +0 -1
  213. package/dist/tests/cli/status.test.js +0 -423
  214. package/dist/tests/daemon.test.d.ts +0 -1
  215. package/dist/tests/daemon.test.js +0 -164
  216. package/dist/tests/fixtures/stable-state.d.ts +0 -22
  217. package/dist/tests/fixtures/stable-state.js +0 -152
  218. package/dist/tests/fixtures/stable-state.test.d.ts +0 -1
  219. package/dist/tests/fixtures/stable-state.test.js +0 -17
  220. package/dist/tests/format/markdown.test.d.ts +0 -1
  221. package/dist/tests/format/markdown.test.js +0 -49
  222. package/dist/tests/format/terminal.test.d.ts +0 -1
  223. package/dist/tests/format/terminal.test.js +0 -476
  224. package/dist/tests/http/api.test.d.ts +0 -1
  225. package/dist/tests/http/api.test.js +0 -236
  226. package/dist/tests/integration.test.d.ts +0 -1
  227. package/dist/tests/integration.test.js +0 -61
  228. package/dist/tests/mcp/protocol.test.d.ts +0 -1
  229. package/dist/tests/mcp/protocol.test.js +0 -134
  230. package/dist/tests/mcp/quotas.test.d.ts +0 -1
  231. package/dist/tests/mcp/quotas.test.js +0 -56
  232. package/dist/tests/mcp/server.test.d.ts +0 -1
  233. package/dist/tests/mcp/server.test.js +0 -7
  234. package/dist/tests/mcp/tools.test.d.ts +0 -1
  235. package/dist/tests/mcp/tools.test.js +0 -291
  236. package/dist/tests/runtime/cancel.test.d.ts +0 -1
  237. package/dist/tests/runtime/cancel.test.js +0 -264
  238. package/dist/tests/runtime/cli.test.d.ts +0 -1
  239. package/dist/tests/runtime/cli.test.js +0 -226
  240. package/dist/tests/runtime/config.test.d.ts +0 -1
  241. package/dist/tests/runtime/config.test.js +0 -144
  242. package/dist/tests/runtime/http.test.d.ts +0 -1
  243. package/dist/tests/runtime/http.test.js +0 -267
  244. package/dist/tests/runtime/lifecycle.test.d.ts +0 -1
  245. package/dist/tests/runtime/lifecycle.test.js +0 -303
  246. package/dist/tests/runtime/ownership.test.d.ts +0 -1
  247. package/dist/tests/runtime/ownership.test.js +0 -355
  248. package/dist/tests/runtime/poll.test.d.ts +0 -1
  249. package/dist/tests/runtime/poll.test.js +0 -410
  250. package/dist/tests/runtime/status.test.d.ts +0 -1
  251. package/dist/tests/runtime/status.test.js +0 -232
  252. package/dist/tests/service/macos.test.d.ts +0 -1
  253. package/dist/tests/service/macos.test.js +0 -349
  254. package/dist/tests/store/attempts.test.d.ts +0 -1
  255. package/dist/tests/store/attempts.test.js +0 -29
  256. package/dist/tests/store/db.test.d.ts +0 -1
  257. package/dist/tests/store/db.test.js +0 -96
  258. package/dist/tests/store/migration.test.d.ts +0 -1
  259. package/dist/tests/store/migration.test.js +0 -41
  260. package/dist/tests/web/advice-drawer.test.d.ts +0 -1
  261. package/dist/tests/web/advice-drawer.test.js +0 -45
  262. package/dist/tests/web/build.test.d.ts +0 -1
  263. package/dist/tests/web/build.test.js +0 -8
  264. package/dist/tests/web/dashboard.e2e.d.ts +0 -1
  265. package/dist/tests/web/dashboard.e2e.js +0 -670
  266. package/dist/tests/web/header.test.d.ts +0 -1
  267. package/dist/tests/web/header.test.js +0 -56
  268. package/dist/tests/web/names.test.d.ts +0 -1
  269. package/dist/tests/web/names.test.js +0 -19
  270. package/dist/tests/web/onboarding.test.d.ts +0 -1
  271. package/dist/tests/web/onboarding.test.js +0 -21
  272. package/dist/tests/web/pacing.test.d.ts +0 -1
  273. package/dist/tests/web/pacing.test.js +0 -62
  274. package/dist/tests/web/playwright.config.d.ts +0 -2
  275. package/dist/tests/web/playwright.config.js +0 -10
  276. package/dist/tests/web/provider-drawer.test.d.ts +0 -1
  277. package/dist/tests/web/provider-drawer.test.js +0 -68
  278. package/dist/tests/web/recommendation.test.d.ts +0 -1
  279. package/dist/tests/web/recommendation.test.js +0 -38
  280. package/dist/tests/web/reset-rail.test.d.ts +0 -1
  281. package/dist/tests/web/reset-rail.test.js +0 -63
  282. package/dist/tests/web/responsive.test.d.ts +0 -1
  283. package/dist/tests/web/responsive.test.js +0 -21
  284. package/dist/tests/web/settings.test.d.ts +0 -1
  285. package/dist/tests/web/settings.test.js +0 -32
  286. package/dist/tests/web/state-catalog.test.d.ts +0 -1
  287. package/dist/tests/web/state-catalog.test.js +0 -206
  288. package/dist/tests/web/state.test.d.ts +0 -1
  289. package/dist/tests/web/state.test.js +0 -27
  290. package/dist/tests/web/stub-server.d.ts +0 -37
  291. package/dist/tests/web/stub-server.js +0 -132
  292. package/dist/tests/web/theme.test.d.ts +0 -1
  293. package/dist/tests/web/theme.test.js +0 -12
  294. package/dist/web/src/names.d.ts +0 -3
  295. package/dist/web/src/names.js +0 -19
  296. package/scripts/fix-pty-perms.mjs +0 -21
  297. package/web/dist/assets/index-CyUkWdwJ.css +0 -1
  298. 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: `agy` Gemini, `agy:3p` 3p) | Live |
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
- # Terminal 1: leave this running
48
- quotacap init # writes ~/.quotacap/config.json
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
- `web` stays in the foreground and starts the daemon.
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.
@@ -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+(\d{1,2}):(\d{2})$/);
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+\d+:\d+)/i);
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) {
@@ -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
+ };
@@ -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>;