agentbox-app 0.1.12 → 0.1.16

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 (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1,54 @@
1
+ // An npm Claude Code from before the package shipped a native binary is a
2
+ // `#!/usr/bin/env node` script. Opened from the Dock, the app's PATH is
3
+ // launchd's four folders, and with nvm lazy-loaded no profile adds node, so the
4
+ // app found that Claude Code and every worker died with
5
+ // `env: node: No such file or directory`
6
+ // (tests/an-old-npm-claude-code-starts-as-a-worker-from-the-dock).
7
+ //
8
+ // Narrower than main/codex-launch-env.mjs on purpose. A worker's PATH is the
9
+ // PATH its agent's own commands run under, so it is changed only when this
10
+ // binary needs node and none is on it, and the node found is added at the end,
11
+ // where it cannot shadow anything the person's own PATH chose.
12
+ import fs from 'node:fs';
13
+ import os from 'node:os';
14
+ import path from 'node:path';
15
+ import { candidatePaths, versionedPaths } from './claude-bin.mjs';
16
+
17
+ function executable(file) {
18
+ try {
19
+ fs.accessSync(file, fs.constants.X_OK);
20
+ return fs.statSync(file).isFile();
21
+ } catch { return false; }
22
+ }
23
+
24
+ /** Whether `bin`, followed through its links, is a script run by node. */
25
+ function runsOnNode(bin) {
26
+ try {
27
+ const fd = fs.openSync(fs.realpathSync(bin), 'r');
28
+ const head = Buffer.alloc(128);
29
+ const read = fs.readSync(fd, head, 0, head.length, 0);
30
+ fs.closeSync(fd);
31
+ const first = head.subarray(0, read).toString('utf8').split('\n')[0];
32
+ return /^#!.*\bnode\b/.test(first);
33
+ } catch { return false; }
34
+ }
35
+
36
+ export function claudeLaunchEnv(bin, source = process.env, {
37
+ home = source.HOME || os.homedir(), exists = executable, readdir,
38
+ } = {}) {
39
+ if (!bin || !runsOnNode(bin)) return { ...source };
40
+ const inherited = (source.PATH || '').split(path.delimiter).filter(Boolean);
41
+ if (inherited.some(dir => exists(path.join(dir, 'node')))) return { ...source };
42
+ // The node npm installed it with sits beside the link, or beside the real
43
+ // file's package; then the places a Node install usually lives.
44
+ let real = bin;
45
+ try { real = fs.realpathSync(bin); } catch { /* keep the link */ }
46
+ const directories = [...new Set([
47
+ path.dirname(bin), path.dirname(real),
48
+ ...candidatePaths(home).map(path.dirname),
49
+ ...versionedPaths(home, readdir).map(path.dirname),
50
+ ])];
51
+ const nodeDir = directories.find(dir => exists(path.join(dir, 'node')));
52
+ if (!nodeDir) return { ...source };
53
+ return { ...source, PATH: [...inherited, nodeDir].join(path.delimiter) };
54
+ }
@@ -39,8 +39,35 @@
39
39
  // at all.
40
40
 
41
41
  import fs from 'node:fs';
42
+ import path from 'node:path';
43
+ import { fileURLToPath } from 'node:url';
42
44
  import { CLAUDE_MODELS, READ_FROM } from '../shared/claude-models.generated.mjs';
43
45
 
46
+ // WHAT THE LAST BUILD ON THIS MACHINE READ, which is a better floor than the
47
+ // committed table and is not a file anybody edits.
48
+ //
49
+ // The committed table is tracked, so a build that wrote over it made every
50
+ // folder that had run the app look like it held work (w-6bbb709b1f, 2026-10-07).
51
+ // The build writes this instead. It is gitignored, it is absent on a machine
52
+ // that has never built, and it is read with `fs` rather than imported, for the
53
+ // one reason that decides the whole shape: this module is node, but
54
+ // shared/claude-commands.mjs next door is shared with the renderer, where an
55
+ // import of a file that may not exist cannot be made to resolve. A read is a
56
+ // read in node and nothing at all in a bundle.
57
+ const LOCAL = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'shared', 'claude-models.local.json');
58
+
59
+ /** The table this machine read at build time, or null if there is none to trust. */
60
+ function readLocal(at) {
61
+ try {
62
+ const table = JSON.parse(fs.readFileSync(at, 'utf8'));
63
+ if (!Array.isArray(table.models) || !table.models.length) return null;
64
+ // Every row has to carry what the picker draws. A half-written file is a
65
+ // worse answer than the committed table, not a better one.
66
+ if (!table.models.every((m) => m && typeof m.alias === 'string' && typeof m.label === 'string')) return null;
67
+ return table;
68
+ } catch { return null; }
69
+ }
70
+
44
71
  /**
45
72
  * HER ORDER, AND ONLY HER ORDER. Not a filter: an alias missing from this list
46
73
  * is still offered, after these. Her three are from and Fable is the one she
@@ -203,8 +230,10 @@ function stampOf(binPath) {
203
230
  * WHAT MAY SHE BE OFFERED FOR CLAUDE CODE, and what version said so.
204
231
  *
205
232
  * `{ models: [{ alias, id, label }], readFrom, source }` where `source` is
206
- * `'cli'` when it came off this Mac's Claude Code and `'built'` when it is the
207
- * table committed at build time. Never empty: the committed table is the floor.
233
+ * `'cli'` when it came off this Mac's Claude Code at the moment it was asked,
234
+ * `'read'` when it is what the last build on this machine read out of it, and
235
+ * `'built'` when it is the table committed to the repository. Never empty: the
236
+ * committed table is the floor under both of them.
208
237
  *
209
238
  * THE PATH IS HANDED IN, NEVER SEARCHED FOR HERE. `config.claudeBin` is already
210
239
  * resolved once at startup (main/config.mjs), and `findClaudeBin` falls back to
@@ -216,15 +245,18 @@ function stampOf(binPath) {
216
245
  * `--version` so the Settings screen could say which one it read. That is one
217
246
  * `execFileSync` on the path a settings read takes, and no label on a screen is
218
247
  * worth a process started while she waits. `readFrom` is the committed table's
219
- * version, and it is meaningful only when `source` is `'built'`.
248
+ * version, and it is meaningful whenever `source` is not `'cli'`.
220
249
  */
221
- export function claudeModels({ bin = null } = {}) {
250
+ export function claudeModels({ bin = null, local = LOCAL } = {}) {
251
+ // THE FLOOR IS THE FRESHER OF THE TWO TABLES NOBODY HAS TO ASK FOR: what the
252
+ // last build on this machine read, and the one committed to the repository.
253
+ const read = readLocal(local);
222
254
  const built = {
223
- models: CLAUDE_MODELS.map((m) => ({
255
+ models: (read?.models ?? CLAUDE_MODELS).map((m) => ({
224
256
  alias: m.alias, id: m.id, label: m.label, defaultLevel: m.defaultLevel ?? null,
225
257
  })),
226
- readFrom: READ_FROM,
227
- source: 'built',
258
+ readFrom: read ? read.readFrom ?? null : READ_FROM,
259
+ source: read ? 'read' : 'built',
228
260
  };
229
261
  if (!bin) return built;
230
262
  const stamp = stampOf(bin);
@@ -71,6 +71,9 @@ export function planLabel(raw) {
71
71
  if (!s) return null;
72
72
  const bare = s.replace(/^default_/, '').replace(/^claude_/, '');
73
73
  if (!bare) return null;
74
+ // Claude's saved Enterprise tier includes an internal name. Keep the raw
75
+ // tier for capacity decisions, but show only the subscription name.
76
+ if (bare === 'raven_enterprise') return 'Enterprise';
74
77
  return bare
75
78
  .split('_')
76
79
  .filter(Boolean)
@@ -93,27 +96,35 @@ export function isMaxTier(raw) {
93
96
  * Claude Code has not fetched a profile for yet: all of them come back
94
97
  * `known: false`, which every caller here treats as "leave everything exactly
95
98
  * as it was". */
99
+ const planCache = new Map();
100
+
96
101
  export function readPlan(profile, { home = os.homedir(), file = null } = {}) {
97
102
  const at = file ?? planFile(profile, home);
103
+ // Keyed on the file's mtime AND its size, as accountIdentity is keyed on the
104
+ // mtime alone: the cloud tiles ask on every snapshot.
105
+ //
106
+ // THE SIZE IS THERE BECAUSE AN MTIME CAN STAND STILL (w-dac993a554). Two
107
+ // writes inside one tick of the filesystem's clock carry the same mtime, and
108
+ // the cache then served the first one's plan for ever. A subscription
109
+ // renewed in that window read as ended until the file was written again, so
110
+ // `Supervisor#_forgetRenewedSubscriptions` never let the account back in.
111
+ let stat = null;
112
+ try { stat = fs.statSync(at); } catch { /* read below says none */ }
113
+ const hit = planCache.get(at);
114
+ if (stat && hit?.mtime === stat.mtimeMs && hit?.size === stat.size) return hit.plan;
115
+ const plan = readPlanNow(at);
116
+ if (stat) planCache.set(at, { mtime: stat.mtimeMs, size: stat.size, plan });
117
+ return plan;
118
+ }
119
+
120
+ function readPlanNow(at) {
121
+ const none = { known: false, max: false, label: null, tier: null, from: null, file: at };
98
122
  let account = null;
99
123
  try {
100
124
  account = JSON.parse(fs.readFileSync(at, 'utf8'))?.oauthAccount ?? null;
101
125
  } catch {
102
- return planOfAccount(null, at);
126
+ return none;
103
127
  }
104
- return planOfAccount(account, at);
105
- }
106
-
107
- /**
108
- * THE SAME ANSWER OFF AN `oauthAccount` SOMEBODY ALREADY HAS IN HAND.
109
- *
110
- * Lifted out of `readPlan` so a caller that has read the file for its own
111
- * reasons (main/runs-on.mjs, which also wants to know whether there is a login
112
- * in there at all) does not carry a second copy of the tier ordering. One copy
113
- * of that rule, and the file read stays in one place per caller.
114
- */
115
- export function planOfAccount(account, at = null) {
116
- const none = { known: false, max: false, label: null, tier: null, from: null, file: at };
117
128
  if (!account || typeof account !== 'object') return none;
118
129
  // THE ORDER IS MOST SPECIFIC FIRST. A rate limit tier names the plan AND its
119
130
  // size (`max_20x` against `max_5x`), which is the thing worth showing; the
@@ -126,7 +137,43 @@ export function planOfAccount(account, at = null) {
126
137
  const from = tier === account.organizationRateLimitTier
127
138
  ? 'org-tier'
128
139
  : tier === account.userRateLimitTier ? 'user-tier' : 'org-type';
129
- return { known: true, max: isMaxTier(tier), label: planLabel(tier), tier, from, file: at };
140
+ // A SUBSCRIPTION THAT HAS ENDED STILL NAMES ITS OLD TIER. Measured
141
+ // 2026-10-11 on a lapsed personal Max login: `billingType` had gone to
142
+ // "none" (a paying one says "stripe_subscription") while the tier still said
143
+ // `default_claude_max_20x`, so the page went on saying "Max 20x". Only the
144
+ // exact word counts; a file that names no billing at all is read as before.
145
+ if (account.billingType === 'none') {
146
+ return { known: true, ended: true, max: false, label: 'Subscription ended', tier, from, file: at };
147
+ }
148
+ return { known: true, ended: false, max: isMaxTier(tier), label: planLabel(tier), tier, from, file: at };
149
+ }
150
+
151
+ /**
152
+ * WHICH ACCOUNT AND PLAN ONE LOGIN IS ON, as a string that changes only when
153
+ * one of them does: an upgrade, extra usage switched on, a renewal, or a
154
+ * /login as somebody else. Claude Code rewrites this file all day for other
155
+ * reasons, so only these fields count (named as Claude Code 2.1.305 writes
156
+ * them). Null when the file names no account or cannot be read.
157
+ */
158
+ const PLAN_FIELDS = ['accountUuid', 'organizationUuid', 'organizationType', 'organizationRateLimitTier',
159
+ 'userRateLimitTier', 'seatTier', 'billingType', 'hasExtraUsageEnabled', 'planDisplayName', 'subscriptionCreatedAt'];
160
+ const stampCache = new Map();
161
+
162
+ export function planStamp(profile, { home = os.homedir(), file = null } = {}) {
163
+ const at = file ?? planFile(profile, home);
164
+ let mtime;
165
+ try { mtime = fs.statSync(at).mtimeMs; } catch { return null; }
166
+ const hit = stampCache.get(at);
167
+ if (hit?.mtime === mtime) return hit.stamp;
168
+ let stamp = null;
169
+ try {
170
+ const account = JSON.parse(fs.readFileSync(at, 'utf8'))?.oauthAccount;
171
+ if (account?.accountUuid) {
172
+ stamp = JSON.stringify(PLAN_FIELDS.map((k) => (k === 'hasExtraUsageEnabled' ? account[k] === true : account[k] ?? null)));
173
+ }
174
+ } catch { /* half written: the next write has a new mtime and is read again */ }
175
+ stampCache.set(at, { mtime, stamp });
176
+ return stamp;
130
177
  }
131
178
 
132
179
  /**
@@ -154,7 +201,8 @@ export function readPlans(profiles, { home = os.homedir() } = {}) {
154
201
  * AND AN UNREADABLE ACCOUNT NEVER LOWERS IT. Somebody whose config file we
155
202
  * cannot parse gets exactly the app they had before this existed. */
156
203
  export function slotsForPlans(plans, fallback = 3) {
157
- const known = (plans ?? []).filter((p) => p.known);
204
+ // An ended subscription runs nothing, so it has no say in how many run.
205
+ const known = (plans ?? []).filter((p) => p.known && !p.ended);
158
206
  if (!known.length) return fallback;
159
207
  return known.every((p) => p.max) ? fallback : 1;
160
208
  }
@@ -0,0 +1,28 @@
1
+ // Whether Claude Code still has a session open somewhere, read off the state
2
+ // file it keeps for each open session (`<home>/sessions/<pid>.json`, with the
3
+ // session id and the pid holding it). A state file whose process has gone is a
4
+ // session somebody closed.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+
8
+ function alive(pid) {
9
+ try { process.kill(pid, 0); return true; } catch (err) { return err?.code === 'EPERM'; }
10
+ }
11
+
12
+ export function claudeSessionOpen(sessionId, homes = []) {
13
+ const id = String(sessionId ?? '').trim();
14
+ if (!id) return false;
15
+ for (const home of new Set(homes.filter(Boolean))) {
16
+ const dir = path.join(home, 'sessions');
17
+ let names = [];
18
+ try { names = fs.readdirSync(dir); } catch { continue; }
19
+ for (const name of names) {
20
+ if (!name.endsWith('.json')) continue;
21
+ let rec = null;
22
+ try { rec = JSON.parse(fs.readFileSync(path.join(dir, name), 'utf8')); } catch { continue; }
23
+ const pid = Number(rec?.pid);
24
+ if (rec?.sessionId === id && pid > 0 && alive(pid)) return true;
25
+ }
26
+ }
27
+ return false;
28
+ }
@@ -0,0 +1,40 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { attachClaudeInput } from './claude-input.mjs';
3
+
4
+ // No user message is submitted. The CLI owns authentication and the provider request.
5
+ export function runClaudeUsage(bin, args, opts, done) {
6
+ let child, timer, killTimer, finished = false, bytes = 0;
7
+ const finish = (error, report = '') => {
8
+ if (finished) return;
9
+ finished = true;
10
+ clearTimeout(timer);
11
+ if (child?.exitCode == null && child?.pid) {
12
+ child.kill('SIGTERM');
13
+ killTimer = setTimeout(() => child.kill('SIGKILL'), 1000);
14
+ killTimer.unref();
15
+ }
16
+ done(error, report);
17
+ };
18
+ try {
19
+ child = attachClaudeInput(spawn(bin, args, { cwd: opts.cwd, env: opts.env, stdio: ['pipe', 'pipe', 'ignore'] }));
20
+ child.holdInput(true);
21
+ child.once('error', error => finish(error));
22
+ child.once('exit', () => {
23
+ clearTimeout(killTimer);
24
+ finish(Error('Claude Code closed before reporting usage.'));
25
+ });
26
+ child.stdout.on('data', data => {
27
+ bytes += data.length;
28
+ if (bytes > opts.maxBuffer) finish(Error('Claude Code sent too much usage output.'));
29
+ });
30
+ timer = setTimeout(() => finish(Error('Claude Code did not report usage in time.')), opts.timeout);
31
+ void (async () => {
32
+ try {
33
+ await child.control({ subtype: 'initialize' }, opts.timeout);
34
+ const report = await child.control({ subtype: 'get_usage', skip_behaviors: true }, opts.timeout);
35
+ finish(null, JSON.stringify(report));
36
+ } catch (error) { finish(error); }
37
+ })();
38
+ } catch (error) { finish(error); }
39
+ return child;
40
+ }
@@ -1,39 +1,13 @@
1
- // RUNNING `/usage`, WHICH TAKES TWENTY-FIVE SECONDS AND MUST NEVER BE WAITED ON.
2
- //
3
- // The pure half is shared/claude-usage.mjs: the text in, the limits out, the
4
- // words the pill prints. This is the half that shells out, and everything about
5
- // its shape comes from one measurement.
6
- //
7
- // TIMED TWICE ON HER MAC, 2026-08-31: 23.2s and 28.0s for `claude -p '/usage'`.
8
- // The percentage did not move between the two, which is the other half of the
9
- // answer: this is not a number that changes second to second, so nothing is lost
10
- // by reading it every few minutes and everything is lost by reading it on a
11
- // timer the window waits for.
12
- //
13
- // SO IT IS A CACHE WITH A BACKGROUND REFRESH, and never a call anything awaits:
14
- //
15
- // - `read` answers instantly, always, from the last measurement or with null.
16
- // It never blocks and never throws.
17
- // - A refresh is kicked off when the cached figure is older than STALE_MS,
18
- // and only ever one at a time.
19
- // - The window is told when a new one lands, the same way everything else in
20
- // this app tells it: a push.
21
- //
22
- // WHY NOT A SESSION. Agentbox spawns real workers through the supervisor and could
23
- // have asked one of them. It must not: a worker is a unit of her work, it lands
24
- // in her inbox and burns a slot in the fleet, and this is a status reading. It
25
- // gets its own short-lived child process with no store, no brief and no row.
26
- //
27
- // WHAT IS NOT SENT ANYWHERE. The output carries her usage figures and nothing
28
- // else leaves this machine because of it; `main/analytics.mjs` is the only file
29
- // that sends, and none of its eight counts is fed from here.
1
+ // Cached provider usage, read through Claude Code without starting a model turn.
2
+ // Snapshots never wait; only an explicit refresh waits for the CLI's answer.
30
3
 
31
- import { execFile } from 'node:child_process';
32
4
  import fs from 'node:fs';
33
5
  import os from 'node:os';
34
6
  import path from 'node:path';
35
- import { readUsage } from '../shared/claude-usage.mjs';
36
- import { localZone } from '../shared/usage.mjs';
7
+ import { readUsageReport } from '../shared/claude-usage.mjs';
8
+ import { localZone, USAGE_UNREADABLE } from '../shared/usage.mjs';
9
+ import { runClaudeUsage } from './claude-usage-process.mjs';
10
+ import { NO_METERED_KEY } from './harnesses/claude.mjs';
37
11
 
38
12
  // WHERE THE READING RUNS, AND WHY IT IS NOT "WHEREVER THE APP IS". An app
39
13
  // opened from Finder has `/` as its working directory, and this used to spawn
@@ -61,9 +35,7 @@ export function usageCwd() {
61
35
  // than the number itself is reported to.
62
36
  export const STALE_MS = 5 * 60_000;
63
37
 
64
- // Long enough for the slowest run measured plus most of the same again. Past
65
- // this the reading is not late, it is not coming.
66
- const TIMEOUT_MS = 90_000;
38
+ const TIMEOUT_MS = 30_000;
67
39
 
68
40
  // After a failure, how long before it is worth trying again. A machine that is
69
41
  // signed out, rate limited, or has no Claude Code at all would otherwise be
@@ -76,20 +48,36 @@ export class ClaudeUsage {
76
48
  * is, asked each time rather than captured: it can be installed while the
77
49
  * app is running, and the settings screen already promises that works.
78
50
  * @param { => void} [how.onChange] Told when a new reading lands, so the
79
- * window redraws. Never called for a refresh that changed nothing.
51
+ * window redraws, including a new checked time on unchanged percentages.
52
+ * @param { => { profile: string, env?: object }} [how.account] WHOSE LIMIT
53
+ * IS READ, asked each time. It used to be nobody's in particular: the
54
+ * command ran with the app's own environment, which is the default login,
55
+ * so a Mac with a second account picked as In use showed "No usage reading
56
+ * yet" for good, because the default one was at its limit and Claude Code
57
+ * printed no limits for it (2026-10-08, Claude Code 2.1.293). `profile` is
58
+ * the account's name ('default' or its CLAUDE_CONFIG_DIR) and `env` is the
59
+ * environment to run it in. Supervisor.usageAccount is the real one.
80
60
  */
81
- constructor({ bin, onChange = () => {}, run = execFile, now = () => Date.now(), where = usageCwd } = {}) {
61
+ constructor({ bin, onChange = () => {}, run = runClaudeUsage, now = () => Date.now(), where = usageCwd, account = () => ({ profile: 'default' }) } = {}) {
82
62
  this._bin = bin;
63
+ this._selectedProfile = null;
64
+ this._generation = 0;
65
+ this._pending = null;
83
66
  this._onChange = onChange;
84
67
  this._run = run;
85
68
  this._now = now;
86
69
  this._where = where;
87
- /** @type {{ limits: any[], at: number, zone: string|null }|null} */
70
+ this._account = account;
71
+ /** @type {{ limits: any[], at: number, zone: string|null, profile: string }|null} */
88
72
  this._last = null;
89
73
  this._busy = false;
90
74
  this._missedUntil = 0;
91
75
  }
92
76
 
77
+ _who() {
78
+ try { return this._account() ?? { profile: 'default' }; } catch { return { profile: 'default' }; }
79
+ }
80
+
93
81
  /**
94
82
  * THE LAST READING, INSTANTLY, and a refresh started if it has gone stale.
95
83
  *
@@ -98,13 +86,28 @@ export class ClaudeUsage {
98
86
  * than a pill full of dashes.
99
87
  */
100
88
  read() {
89
+ this._syncProfile();
101
90
  this._maybeRefresh();
102
- return this._last ? { limits: this._last.limits, at: this._last.at } : null;
91
+ return this.peek();
103
92
  }
104
93
 
105
94
  // The secondary subscription can show its last reading without starting
106
95
  // another coding process merely to populate the sidebar.
107
- peek() { return this._last ? { limits: this._last.limits, at: this._last.at } : null; }
96
+ peek() {
97
+ this._syncProfile();
98
+ return this._last ? { limits: this._last.limits, at: this._last.at, profile: this._last.profile } : null;
99
+ }
100
+
101
+ _syncProfile() {
102
+ const account = this._who().profile || 'default';
103
+ if (account !== this._selectedProfile) {
104
+ this._selectedProfile = account;
105
+ this._generation++;
106
+ this._last = null;
107
+ this._missedUntil = 0;
108
+ }
109
+ return account;
110
+ }
108
111
 
109
112
  _maybeRefresh() {
110
113
  const now = this._now();
@@ -114,33 +117,65 @@ export class ClaudeUsage {
114
117
  }
115
118
 
116
119
  /** Ask again now, whatever the cache says. The settings screen's own button. */
117
- refreshNow() {
120
+ async refreshNow() {
121
+ this._syncProfile();
118
122
  this._missedUntil = 0;
119
- if (!this._busy) this._refresh();
123
+ // Join an existing reading, including one begun by the background poll.
124
+ // If it belongs to the previous login, wait for it and then ask this one.
125
+ const generation = this._generation;
126
+ const pendingGeneration = this._pendingGeneration;
127
+ const result = await (this._pending ?? this._refresh());
128
+ this._syncProfile();
129
+ if (generation !== this._generation) return { ok: false, error: 'The account changed. Refresh usage again.' };
130
+ if (pendingGeneration != null && pendingGeneration !== generation) return this.refreshNow();
131
+ return result;
120
132
  }
121
133
 
122
134
  _refresh() {
135
+ const profile = this._syncProfile();
136
+ const generation = this._generation;
123
137
  const bin = (() => { try { return this._bin(); } catch { return null; } })();
124
- if (!bin) { this._missed(); return; }
138
+ const failure = (error = USAGE_UNREADABLE) => ({ ok: false, error });
139
+ if (!bin) { this._missed(); return Promise.resolve(failure()); }
125
140
  this._busy = true;
126
- this._run(
141
+ this._pendingGeneration = generation;
142
+ let resolve;
143
+ const pending = new Promise(done => { resolve = done; });
144
+ this._pending = pending;
145
+ const finish = (err, stdout) => {
146
+ this._busy = false;
147
+ this._pending = null;
148
+ this._pendingGeneration = null;
149
+ this._syncProfile();
150
+ if (generation !== this._generation) { resolve(failure()); this._onChange(); return; }
151
+ // Failed or unreadable output cannot acquire a fresh checked time.
152
+ const limits = err ? [] : readUsageReport(stdout, this._now());
153
+ if (!limits.length) {
154
+ this._missed();
155
+ const unsupported = /get_usage.*(?:not supported|unknown)|unknown.*get_usage/i.test(err?.message ?? '');
156
+ resolve(failure(unsupported ? 'Update Claude Code to read usage for this account.' : USAGE_UNREADABLE));
157
+ return;
158
+ }
159
+ this._last = { limits, at: this._now(), zone: localZone(), profile };
160
+ resolve({ ok: true, reading: this.peek() });
161
+ // Even unchanged percentages now have a newly checked time to draw.
162
+ this._onChange();
163
+ };
164
+ const env = { ...(this._who().env ?? process.env) };
165
+ for (const key of Object.keys(env)) {
166
+ if (/^ANTHROPIC_|^CLAUDE_CODE_/.test(key) || ['CLAUDECODE', 'CLAUDE_PID', 'CLAUDE_EFFORT', 'CLAUDE_CONFIG_DIR'].includes(key)) delete env[key];
167
+ }
168
+ if (profile !== 'default') env.CLAUDE_CONFIG_DIR = profile;
169
+ try { this._run(
127
170
  bin,
128
- ['-p', '/usage', '--output-format', 'text'],
171
+ ['-p', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose',
172
+ '--no-session-persistence', '--strict-mcp-config', '--mcp-config', '{"mcpServers":{}}', '--tools', '',
173
+ '--settings', JSON.stringify({ ...NO_METERED_KEY, disableAllHooks: true })],
129
174
  // `cwd` is the whole fix for the permission panels; see usageCwd above.
130
- { cwd: this._where(), timeout: TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, encoding: 'utf8' },
131
- (err, stdout) => {
132
- this._busy = false;
133
- // A COMMAND THAT FAILED LEAVES THE OLD READING ALONE. It is stale rather
134
- // than wrong, and a corner that empties itself every time the network
135
- // hiccups is worse than one that is five minutes behind.
136
- if (err && !stdout) { this._missed(); return; }
137
- const limits = readUsage(stdout, this._now(), localZone());
138
- if (!limits.length) { this._missed(); return; }
139
- const before = JSON.stringify(this._last?.limits ?? null);
140
- this._last = { limits, at: this._now(), zone: localZone() };
141
- if (JSON.stringify(limits) !== before) this._onChange();
142
- },
143
- );
175
+ { cwd: this._where(), env, timeout: TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, encoding: 'utf8' },
176
+ finish,
177
+ ); } catch (err) { finish(err, ''); }
178
+ return pending;
144
179
  }
145
180
 
146
181
  _missed() {