agentbox-app 0.1.13 → 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 (278) 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/agent-sessions.mjs +282 -268
  9. package/main/agents.mjs +179 -466
  10. package/main/analytics.mjs +12 -2
  11. package/main/answer-modes.mjs +103 -37
  12. package/main/approval-prompt-core.mjs +197 -0
  13. package/main/approval-prompt-server.mjs +4 -138
  14. package/main/approvals.mjs +101 -297
  15. package/main/artifact-path.mjs +47 -18
  16. package/main/claude-input.mjs +48 -4
  17. package/main/claude-plan.mjs +48 -0
  18. package/main/claude-session-open.mjs +28 -0
  19. package/main/claude-usage-process.mjs +40 -0
  20. package/main/claude-usage.mjs +18 -40
  21. package/main/cloud/account.mjs +179 -0
  22. package/main/cloud/aws.mjs +127 -0
  23. package/main/cloud/browser-return.mjs +58 -0
  24. package/main/cloud/clients.mjs +15 -0
  25. package/main/cloud/codex-cloud.mjs +53 -0
  26. package/main/cloud/config.mjs +107 -0
  27. package/main/cloud/docs.mjs +219 -0
  28. package/main/cloud/exec.mjs +27 -0
  29. package/main/cloud/host.mjs +165 -0
  30. package/main/cloud/kit.mjs +80 -0
  31. package/main/cloud/logins.mjs +543 -0
  32. package/main/cloud/managed.mjs +115 -0
  33. package/main/cloud/marker.mjs +104 -0
  34. package/main/cloud/org.mjs +53 -0
  35. package/main/cloud/reuse.mjs +208 -0
  36. package/main/cloud/run-cli.mjs +177 -0
  37. package/main/cloud/run.mjs +1934 -0
  38. package/main/cloud/usage.mjs +76 -0
  39. package/main/cloud-permission.mjs +51 -0
  40. package/main/codex-account.mjs +78 -0
  41. package/main/codex-app-server.mjs +276 -409
  42. package/main/codex-approvals.mjs +148 -433
  43. package/main/codex-models.mjs +20 -2
  44. package/main/codex-session.mjs +212 -507
  45. package/main/codex-threads-worker.mjs +12 -0
  46. package/main/codex-threads.mjs +71 -3
  47. package/main/codex-usage-file.mjs +5 -3
  48. package/main/codex-usage.mjs +56 -7
  49. package/main/codex-watch.mjs +17 -3
  50. package/main/codex.mjs +95 -363
  51. package/main/config.mjs +11 -1
  52. package/main/corner-tag.mjs +340 -19
  53. package/main/dev-window.mjs +1 -1
  54. package/main/engine-bin.mjs +232 -282
  55. package/main/engine-setup.mjs +21 -9
  56. package/main/feedback.mjs +3 -25
  57. package/main/folder-pictures.mjs +2 -2
  58. package/main/freeze-recorder-thread.mjs +86 -0
  59. package/main/freeze-recorder.mjs +146 -0
  60. package/main/fresh-user.mjs +20 -9
  61. package/main/git-change-offthread.mjs +17 -3
  62. package/main/git-change-thread.mjs +5 -2
  63. package/main/harnesses/claude.mjs +32 -11
  64. package/main/harnesses/codex.mjs +9 -2
  65. package/main/importable-threads.mjs +64 -6
  66. package/main/ipc.mjs +362 -111
  67. package/main/leftovers.mjs +117 -25
  68. package/main/liquid-glass.mjs +26 -0
  69. package/main/live-replies.mjs +10 -4
  70. package/main/main-went-red.mjs +330 -0
  71. package/main/main.mjs +199 -84
  72. package/main/memory-gate-history.mjs +36 -10
  73. package/main/memory-gate-server.mjs +62 -0
  74. package/main/memory-gate.mjs +14 -1
  75. package/main/menu.mjs +4 -2
  76. package/main/message-priority.mjs +2 -2
  77. package/main/mic.mjs +38 -0
  78. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  79. package/main/new-chats-watch.mjs +110 -0
  80. package/main/new-user-window.mjs +25 -0
  81. package/main/notify.mjs +24 -8
  82. package/main/phone-alerts.mjs +117 -0
  83. package/main/phone-inbox.mjs +625 -0
  84. package/main/phone-link.mjs +29 -9
  85. package/main/phone-uploads.mjs +476 -0
  86. package/main/picture-shape.mjs +114 -0
  87. package/main/quit-sequence.mjs +143 -0
  88. package/main/refresh-agent-models.mjs +15 -2
  89. package/main/relay-client.mjs +69 -1
  90. package/main/repeats.mjs +10 -4
  91. package/main/row-label.mjs +12 -41
  92. package/main/run-change.mjs +13 -0
  93. package/main/run-ledger.mjs +135 -0
  94. package/main/save-attachments.mjs +41 -0
  95. package/main/serve.mjs +45 -3
  96. package/main/settings.mjs +220 -44
  97. package/main/shell-path.mjs +185 -37
  98. package/main/ship-conflict-brief.mjs +119 -0
  99. package/main/ship-queue.mjs +115 -19
  100. package/main/source-updater.mjs +18 -10
  101. package/main/stage-attachment.mjs +68 -0
  102. package/main/stall-watch.mjs +50 -0
  103. package/main/store/home.mjs +87 -232
  104. package/main/store/paths.mjs +11 -0
  105. package/main/store/teammates-files.mjs +99 -0
  106. package/main/store/work-items.mjs +231 -13
  107. package/main/store.mjs +226 -52
  108. package/main/supervisor.mjs +3103 -341
  109. package/main/task-folders.mjs +430 -30
  110. package/main/task-terminals.mjs +110 -34
  111. package/main/team/index.mjs +114 -10
  112. package/main/team/memory-cloud.mjs +183 -1
  113. package/main/team/room-hours.mjs +48 -0
  114. package/main/team/room-setup.mjs +31 -0
  115. package/main/team/rooms-memory.mjs +454 -0
  116. package/main/team/rooms-shape.mjs +42 -0
  117. package/main/team/rooms-supabase.mjs +324 -0
  118. package/main/team/rooms.mjs +575 -0
  119. package/main/team/session.mjs +96 -26
  120. package/main/team/supabase-backend.mjs +180 -0
  121. package/main/team/sync.mjs +109 -1
  122. package/main/teammates.mjs +580 -0
  123. package/main/terminal-host.mjs +143 -0
  124. package/main/terminal-hosts.mjs +261 -0
  125. package/main/transcript-search.mjs +80 -0
  126. package/main/webview-guard.mjs +45 -0
  127. package/main/worktree-include.mjs +90 -219
  128. package/mcp/core/documents.mjs +3 -1
  129. package/mcp/core/repeats.mjs +88 -0
  130. package/mcp/core/teammates.mjs +222 -0
  131. package/mcp/core/work.mjs +76 -4
  132. package/mcp/stdio.mjs +24 -11
  133. package/mcp/tools.mjs +116 -8
  134. package/package.json +21 -5
  135. package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
  136. package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
  137. package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
  138. package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
  139. package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
  140. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  141. package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
  142. package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
  143. package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
  144. package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
  145. package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
  146. package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
  147. package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
  148. package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
  149. package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
  150. package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
  151. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  152. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  153. package/renderer/dist/assets/{cose-bilkent-JH36ORCC-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
  154. package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
  155. package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
  156. package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
  157. package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
  158. package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
  159. package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
  160. package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
  161. package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
  162. package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
  163. package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
  164. package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
  165. package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
  166. package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
  167. package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
  168. package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
  169. package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
  170. package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
  171. package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
  172. package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
  173. package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
  174. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  175. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  176. package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
  177. package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
  178. package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
  179. package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
  180. package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
  181. package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
  182. package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
  183. package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
  184. package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
  185. package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
  186. package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
  187. package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
  188. package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
  189. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  190. package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
  191. package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
  192. package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
  193. package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
  194. package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
  195. package/renderer/dist/corner-tag.html +3 -3
  196. package/renderer/dist/index.html +3 -3
  197. package/scripts/after-install.mjs +30 -8
  198. package/scripts/cloud-shim.pl +57 -0
  199. package/shared/account-names.d.mts +6 -0
  200. package/shared/account-names.mjs +15 -0
  201. package/shared/agent-hours.mjs +136 -0
  202. package/shared/agent-import.mjs +60 -34
  203. package/shared/agents.mjs +126 -522
  204. package/shared/analytics-events.mjs +12 -0
  205. package/shared/approval-card.mjs +36 -0
  206. package/shared/bridge-map.mjs +27 -1
  207. package/shared/claude-code-shape.mjs +6 -0
  208. package/shared/claude-usage.d.mts +1 -0
  209. package/shared/claude-usage.mjs +42 -1
  210. package/shared/cloud-hand-back.d.mts +7 -0
  211. package/shared/cloud-hand-back.mjs +20 -0
  212. package/shared/cloud-kit.d.mts +219 -0
  213. package/shared/cloud-kit.mjs +341 -0
  214. package/shared/cloud-login-name.mjs +4 -0
  215. package/shared/cloud-start.mjs +17 -0
  216. package/shared/cloud-versions.mjs +23 -0
  217. package/shared/codex-import.mjs +2 -2
  218. package/shared/codex-modes.d.mts +22 -9
  219. package/shared/codex-modes.mjs +67 -11
  220. package/shared/codex-usage.mjs +108 -2
  221. package/shared/corner-tag.d.mts +23 -1
  222. package/shared/corner-tag.mjs +284 -29
  223. package/shared/dead-run-trace.mjs +19 -2
  224. package/shared/engines.mjs +70 -309
  225. package/shared/feedback.d.mts +7 -0
  226. package/shared/feedback.mjs +25 -0
  227. package/shared/first-run-practice.mjs +17 -24
  228. package/shared/harness-definitions.mjs +8 -0
  229. package/shared/instruction-defaults.json +2 -2
  230. package/shared/key-caps.d.mts +14 -0
  231. package/shared/key-caps.mjs +68 -0
  232. package/shared/message-edits.d.mts +7 -0
  233. package/shared/message-edits.mjs +35 -0
  234. package/shared/move-thread.d.mts +24 -0
  235. package/shared/move-thread.mjs +112 -0
  236. package/shared/named-threads.mjs +72 -0
  237. package/shared/not-shipped.d.mts +22 -0
  238. package/shared/not-shipped.mjs +89 -0
  239. package/shared/notify-rules.d.mts +6 -0
  240. package/shared/notify-rules.mjs +36 -8
  241. package/shared/phone-files.mjs +28 -0
  242. package/shared/phone-settings.mjs +118 -0
  243. package/shared/phone-snapshot.d.mts +16 -0
  244. package/shared/phone-snapshot.mjs +348 -0
  245. package/shared/picture-formats.d.mts +1 -0
  246. package/shared/picture-formats.mjs +13 -0
  247. package/shared/rank.d.mts +2 -1
  248. package/shared/rank.mjs +32 -19
  249. package/shared/repeats.d.mts +8 -0
  250. package/shared/repeats.mjs +33 -2
  251. package/shared/room-cards.mjs +167 -0
  252. package/shared/room-links.d.mts +4 -0
  253. package/shared/room-links.mjs +21 -0
  254. package/shared/runs-on.d.mts +3 -3
  255. package/shared/runs-on.mjs +8 -5
  256. package/shared/shipped-note.d.mts +4 -0
  257. package/shared/shipped-note.mjs +6 -0
  258. package/shared/shortcuts.mjs +14 -6
  259. package/shared/spawn-trouble.d.mts +4 -0
  260. package/shared/spawn-trouble.mjs +443 -40
  261. package/shared/team-rules.mjs +9 -2
  262. package/shared/teammates.d.mts +19 -0
  263. package/shared/teammates.mjs +756 -0
  264. package/shared/terminal-focus.d.mts +1 -0
  265. package/shared/terminal-focus.mjs +13 -0
  266. package/shared/thread-cards.mjs +4 -1
  267. package/shared/usage.d.mts +9 -0
  268. package/shared/usage.mjs +49 -3
  269. package/shared/websites.d.mts +15 -0
  270. package/shared/websites.mjs +61 -0
  271. package/shared/work-items.mjs +127 -30
  272. package/shared/work-lines.mjs +92 -244
  273. package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
  274. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
  275. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
  276. package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
  277. package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
  278. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
package/README.md CHANGED
@@ -6,11 +6,12 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>Give your agents an inbox.</strong><br>
9
- Run dozens of coding agents at once and only hear from the ones that need you.
9
+ Run dozens of Claude Code and Codex agents in parallel, each in its own git worktree,<br>
10
+ and only hear from the ones that need you.
10
11
  </p>
11
12
 
12
13
  <p align="center">
13
- <a href="#get-it">Get it</a> ·
14
+ <a href="#install">Get it</a> ·
14
15
  <a href="#how-it-works">How it works</a> ·
15
16
  <a href="#what-matters-most-goes-first">Priorities</a> ·
16
17
  <a href="#built-for-the-keyboard">Keys</a> ·
@@ -23,8 +24,8 @@
23
24
  </p>
24
25
 
25
26
  <p align="center">
26
- Free and open source. Runs in your browser or as a Mac app, on the Claude Code or Codex plan you already have.<br>
27
- No server, no account, no extra subscription.
27
+ Free and open source. A Mac app, or in your browser on any platform with <code>npx agentbox-app</code>,<br>
28
+ on the Claude Code or Codex plan you already have. No server, no account, no extra subscription.
28
29
  </p>
29
30
 
30
31
  ---
@@ -41,7 +42,7 @@ npx agentbox-app
41
42
 
42
43
  It starts on your machine and opens a tab. On Linux that open goes through `xdg-open`. Nothing is hosted: it listens only on `127.0.0.1`, and the address carries a key that is new every run. Add `--help` to see the options. `--no-open` prints the address and leaves the browser to you.
43
44
 
44
- From a checkout, build the screen once, then start it the same way:
45
+ From a checkout, build the screen once, then start it the same way. The install links the checkout's own command, so `npx` runs this copy rather than looking for `agentbox` on your PATH:
45
46
 
46
47
  ```
47
48
  npm install
@@ -58,7 +59,9 @@ npm install
58
59
  npm start
59
60
  ```
60
61
 
61
- On Linux, `npm install` puts `agentbox` on `~/.local/bin` and adds the app to the launcher. The entry is `$XDG_DATA_HOME/applications/agentbox.desktop` (`~/.local/share/applications` when that variable is unset), and it runs `agentbox`. That command starts this checkout. Open the app from the launcher.
62
+ On Linux, `npm install` puts `agentbox` on `~/.local/bin` and adds the app to the launcher. The entry is `$XDG_DATA_HOME/applications/agentbox.desktop` (`~/.local/share/applications` when that variable is unset), and it runs `agentbox`. That command starts this checkout, and it is written before Electron has downloaded its binary, so the first launch off the launcher does that download. Open the app from the launcher.
63
+
64
+ Two of this repo's dependencies need an install script to work: `node-pty`, which is the terminal, and `esbuild`. `package.json` names them in `allowScripts`, by name rather than pinned to a version, so a dependency bump does not need re-approving. `electron-winstaller` is denied there: it is only for packing a Windows installer, which this repo does not do. npm 11 and newer print anything in neither list on every install, and `npm approve-scripts --allow-scripts-pending` lists them.
62
65
 
63
66
  `npm start` builds the app and opens it. On first run it walks you through connecting Claude Code or Codex and choosing a folder to work in.
64
67
 
@@ -84,7 +87,8 @@ than you could ever watch and only deal with one when it actually needs you.
84
87
  ### 1. File the work
85
88
 
86
89
  Paste a list, send over a board of issues, or type one task. Each item becomes
87
- its own row, and each row is its own agent, already working.
90
+ its own row, and each row is its own agent, already working in its own git
91
+ worktree and on its own branch, so no two agents touch the same files.
88
92
 
89
93
  <img src="docs/readme/file.webp" alt="Twelve issues from a project board go over in one message and appear as twelve agents working" width="100%">
90
94
 
@@ -231,7 +235,7 @@ Nothing, unless you are running a build that was signed and released with a
231
235
  PostHog key baked into it. A build made from this repo has no key, so a clone
232
236
  and a fork are silent, and with no key the whole path is off at every send.
233
237
 
234
- A released build counts twelve things, and they are counts:
238
+ A released build counts eighteen things, and they are counts:
235
239
 
236
240
  - the app was opened
237
241
  - the app was used today, once a day
@@ -245,6 +249,12 @@ A released build counts twelve things, and they are counts:
245
249
  - a task finished
246
250
  - an agent started work
247
251
  - an agent stopped work
252
+ - the app was quit
253
+ - the corner tag was hidden
254
+ - the corner tag was turned off
255
+ - setup offered agents to import
256
+ - setup imported agents
257
+ - counts were turned off
248
258
 
249
259
  Each carries a random install id, the app and platform version, and at most
250
260
  seven other things: how long something took, how many of a thing there were,
@@ -262,6 +272,11 @@ accident. Crash reports travel the same way and are scrubbed first
262
272
  - In the app: Settings, then the "Counts and crash reports" switch.
263
273
  - In `zero.config.json`: `"diagnostics": false`.
264
274
 
275
+ Either way nothing is sent from that moment, with one exception: turning the
276
+ switch off in the app sends one last count saying counts were turned off, so
277
+ you are not read as someone who stopped using Agentbox. Editing the file sends
278
+ nothing at all.
279
+
265
280
  The update check is separate and is not routed through any of this. It is a
266
281
  request to github.com for the latest release, it carries no identity and no
267
282
  key, and it only happens in a packaged build.
@@ -320,7 +335,7 @@ or `?engines=codex` give two.
320
335
 
321
336
  To open the app as somebody who has never seen it, with its own throwaway home
322
337
  and nothing of yours inside it, press `⌘K` or `Ctrl+K` and choose "Open Agentbox as a new
323
- user". `npm run fresh` does the same from a terminal. `ZERO_NO_SUPERVISOR=1` applies to `npm start` and to `npx agentbox-app`.
338
+ user". `npm run fresh` does the same from a terminal: a packaged app when there is one, this checkout through Electron when there is not, which is the way in on Linux. `ZERO_NO_SUPERVISOR=1` applies to `npm start` and to `npx agentbox-app`.
324
339
 
325
340
  The app is named in exactly one place, `shared/product-name.mjs`. Change it
326
341
  there and run `node scripts/product-name.mjs --write`, and everything else
package/bin/agentbox.mjs CHANGED
@@ -15,8 +15,11 @@ import fs from 'node:fs';
15
15
  import { spawn } from 'node:child_process';
16
16
  import { fileURLToPath } from 'node:url';
17
17
 
18
- import { bootHeadless, createServer, newToken } from '../main/serve.mjs';
18
+ import { bootHeadless, createServer, newToken, picturesTheAppOwns } from '../main/serve.mjs';
19
19
  import { appHome } from '../main/store/home.mjs';
20
+ import { learnShellDataDirs } from '../main/shell-path.mjs';
21
+ import { startFreezeRecorder } from '../main/freeze-recorder.mjs';
22
+ import { watchNewChatsFor } from '../main/new-chats-watch.mjs';
20
23
 
21
24
  const here = path.dirname(fileURLToPath(import.meta.url));
22
25
  const appDir = path.dirname(here);
@@ -59,10 +62,22 @@ if (!fs.existsSync(path.join(dist, 'index.html'))) {
59
62
  process.exit(1);
60
63
  }
61
64
 
65
+ // The login folders the shell profile names, read before the first worker can
66
+ // start, as main/main.mjs does. The worker scrub keeps CLAUDE_CONFIG_DIR and
67
+ // CODEX_HOME from reaching a worker by inheritance, so without this a copy's
68
+ // workers signed in to ~/.claude and ~/.codex (w-6d8c543a43).
69
+ await learnShellDataDirs();
62
70
  const booted = await bootHeadless({ dataDir, appDir, userDir: dataDir });
71
+ // What held the thread each time it froze, as the desktop records it.
72
+ startFreezeRecorder({
73
+ dir: path.join(dataDir, 'memory-log'),
74
+ appDir,
75
+ onFreeze: (f) => console.warn(`zero: the app froze for ${f.ms} ms in ${f.where}`),
76
+ });
63
77
  // ZERO_NO_SUPERVISOR: the desktop already honors this (main/main.mjs). This
64
78
  // entry is the same inbox, so a launch that asks for no agents must not start
65
79
  // them here either.
80
+ let newChatsWatch = null;
66
81
  if (!process.env.ZERO_NO_SUPERVISOR) {
67
82
  // Whoever was signed in to the team last time is signed in again.
68
83
  booted.team?.start().then(() => booted.supervisor.wake?.()).catch((err) => console.warn(`team: ${err.message}`));
@@ -70,6 +85,8 @@ if (!process.env.ZERO_NO_SUPERVISOR) {
70
85
  // tab only moved work when something woke the supervisor: a queued thread sat
71
86
  // queued and a scheduled one never fired (found 2026-10-01 on a test copy).
72
87
  booted.supervisor.start();
88
+ // The chats setup's "Keep bringing in new chats" tick asks for.
89
+ newChatsWatch = watchNewChatsFor({ store: booted.store, config: booted.config, supervisor: booted.supervisor });
73
90
  }
74
91
  const token = newToken();
75
92
  const server = createServer({
@@ -77,6 +94,10 @@ const server = createServer({
77
94
  token,
78
95
  listeners: booted.window.listeners,
79
96
  dist,
97
+ // The pictures in a thread: a tab cannot open `agentbox-img://`, which only
98
+ // the desktop window answers, so without this door every local picture is
99
+ // drawn as missing.
100
+ files: picturesTheAppOwns(booted),
80
101
  });
81
102
 
82
103
  const wanted = Number(flag('port', 0)) || 0;
@@ -99,6 +120,7 @@ server.on('error', (err) => {
99
120
  for (const signal of ['SIGINT', 'SIGTERM']) {
100
121
  process.on(signal, () => {
101
122
  console.log('\n Stopping.');
123
+ newChatsWatch?.stop();
102
124
  booted.host.app.quit();
103
125
  server.close(() => process.exit(0));
104
126
  setTimeout(() => process.exit(0), 3000).unref();
@@ -1,3 +1,23 @@
1
+ <!-- autonomy-rules:start -->
2
+ These rules format a final answer; they do not tell you when to stop working.
3
+ Explicit autonomy applies to the agreed objective, not to unrelated work.
4
+ When the person delegates a plan and asks you to keep going, carry out its
5
+ authorized steps without asking for another go-ahead. A completed step is a checkpoint, not a reason to end the run.
6
+ Give progress updates while continuing. Earlier autonomy remains in force until they change it;
7
+ a status question during that work gets a brief update, then work continues.
8
+ Do not ask them to approve a step they already authorized.
9
+
10
+ If one step is blocked, continue other independent authorized work first.
11
+ Stop when the objective is complete, they ask you to stop,
12
+ you need a decision or resource only they can supply, or repeated attempts produce no progress.
13
+ Follow narrower instructions immediately.
14
+ Autonomy does not bypass permission checks or specific approval requirements.
15
+ An approval covers only the thing it approved.
16
+ Without explicit delegation, finish the requested task and return; a question
17
+ does not authorize a project, and "continue" alone does not authorize unrelated work.
18
+ Authorized work continues until a real stopping point, even if older defaults ask for proposals or options.
19
+ <!-- autonomy-rules:end -->
20
+
1
21
  This is about the LAST message you send, the one that ends your run. Whoever
2
22
  asked for it reads it in a list beside twenty others and usually decides what to
3
23
  do next without opening anything. Ten seconds is the real budget.
@@ -73,7 +93,10 @@ WHAT THE APP CAN SHOW, so you can lay out what you made the way it reads best:
73
93
  - A sound or a film plays where it is named; a localhost address opens beside
74
94
  the message. Headings, lists, tables and bold all draw.
75
95
  The arrangement is yours. The 200 words are for your report, not for the words
76
- that help them read what you show.
96
+ that help them read what you show. A suggestion when they choose between
97
+ designs: show each one stacked one under the next, in the order of your options,
98
+ with its option's number and a line over or under it saying what differs, so
99
+ each is big enough to judge without opening it.
77
100
 
78
101
  Steps they must take are a numbered list, five at most, the first one doable now.
79
102
 
@@ -91,19 +114,24 @@ ended up with, and read them back together: if someone said option one
91
114
  out loud, it has to answer line one. When it does not, they have two questions
92
115
  stacked on each other and must open the message to see which is which.
93
116
 
117
+ OFFER CHOICES WHENEVER THEIR NEXT REPLY IS CLEAR. A number pressed is quicker
118
+ than a reply typed, and options are drawn as numbered choices. So when you end
119
+ with steps for them to take, offer the reply they would send once the steps are
120
+ done, such as "Done, check it again", and when a decision has two or three
121
+ sensible answers, offer them. Leave options out only when there is truly
122
+ nothing to pick.
123
+
94
124
  Offer one to four options; two or three is usual, and one is fine. Mark exactly
95
125
  one recommended. Every option has to be something you would start on without asking
96
126
  another question first. Name each choice in your prose too, because the list is
97
127
  often drawn elsewhere on the screen and a sentence pointing at "option two"
98
128
  then points at nothing.
99
129
 
100
- IF YOUR RUN CHANGED ANYTHING, THERE IS SOMETHING TO DECIDE. Edited files, a
101
- branch, a draft, a page you built: none of it is finished until the person
102
- reading you says what happens to it. So the options are what to do with the
103
- thing, in their words: ship it, change this part first, throw it away. Do not
104
- call the run settled to save yourself the asking, or make them type "merge it"
105
- because you did not offer it. The only run that ends with no options
106
- is one that changed nothing.
130
+ IF YOUR RUN LEAVES AN UNAPPROVED CHANGE, THERE IS SOMETHING TO DECIDE. Edited
131
+ files, a branch, a draft, a page you built: offer what still needs their decision,
132
+ in their words: ship it, change this part first, throw it away. Already authorized actions do not create another decision.
133
+ Do not manufacture a pick to continue an agreed plan. When an approval is
134
+ still required, offer it explicitly so they can choose it.
107
135
 
108
136
  NEVER OFFER WHAT YOUR RUN ALREADY DID. Shipped, merged or sent: line one says
109
137
  so, and no option offers it again. When only closing is left, offer the one
package/briefs/worker.md CHANGED
@@ -46,7 +46,9 @@ sentences, 45 words at most, that bring the thread back to someone who has
46
46
  forgotten it, what was asked, what was found and the facts needed to decide.
47
47
  `progress` is shown as DONE: the steps taken so far, one per line, oldest first
48
48
  and newest last, six lines at most of 15 words each; send the whole list each
49
- time. A longer one is refused. Leave `solution` out; it is no longer shown. If
49
+ time. A longer one is refused. EVERY LINE IS A STEP THAT FINISHED: one you are
50
+ about to take goes on after it happens, never before, because a DONE list
51
+ claiming a commit that never ran is how the work gets thrown away. Leave `solution` out; it is no longer shown. If
50
52
  it waits on another thread, pass that thread's id in `blockedBy`. The person may have edited the summary; read it
51
53
  before you rewrite it, and keep what they said unless it is no longer true.
52
54
  <!-- store-tools:end -->
@@ -91,6 +93,8 @@ are a good general assistant first and this brief is scaffolding around that.
91
93
  If following a rule here would make your answer worse than a plain Claude Code
92
94
  session's, you are misapplying it. Answer well, then apply what actually fits.
93
95
 
96
+ Finish the authorized objective, not merely the current step.
97
+
94
98
  ## Doing the work
95
99
 
96
100
  <!-- store-tools:start -->
@@ -101,14 +105,13 @@ session's, you are misapplying it. Answer well, then apply what actually fits.
101
105
  the only one that changes it.
102
106
  - Actions outside your standing grants do not silently fail any more: the
103
107
  permission system asks the founder LIVE (an approval card in their inbox app)
104
- and blocks you until they answer, up to 15 minutes. Spend these asks like
105
- interruptions, because they are: exhaust what your grants already allow,
106
- batch the gated part into as few commands as possible, and if they deny
107
- or the ask times out, do not retry it; finish what you can and file the
108
+ and blocks you until they answer, with no deadline on it: you may wait hours.
109
+ Spend these asks like interruptions, because they are: exhaust what your
110
+ grants already allow, batch the gated part into as few commands as possible,
111
+ and if they deny, do not retry it; finish what you can and file the
108
112
  remainder as a review with the exact commands.
109
- - EVERYTHING YOU FILE IS A PROPOSAL: nothing you create runs until the founder
110
- approves it from the inbox, so write it for them to decide rather than as a
111
- note to another agent.
113
+ - EVERYTHING YOU FILE IS A PROPOSAL: nothing you file runs until they approve
114
+ it from the inbox; do already authorized work in this run instead of filing it.
112
115
  - Code changes happen in the product's repo (your cwd), on a branch, committed
113
116
  as you go. Name the branch the way this repo already names branches; read a
114
117
  few with `git branch` and follow them. Never commit to main; the founder
@@ -157,14 +160,14 @@ session's, you are misapplying it. Answer well, then apply what actually fits.
157
160
  you alone before it started you, and it takes it back when you exit.
158
161
  - Actions outside your standing grants do not silently fail any more: the
159
162
  permission system asks the founder LIVE (an approval card in their inbox app)
160
- and blocks you until they answer, up to 15 minutes. Spend these asks like
161
- interruptions, because they are: exhaust what your grants already allow,
162
- batch the gated part into as few commands as possible, and if they deny
163
- or the ask times out, do not retry it; finish what you can and say what is
163
+ and blocks you until they answer, with no deadline on it: you may wait hours.
164
+ Spend these asks like interruptions, because they are: exhaust what your
165
+ grants already allow, batch the gated part into as few commands as possible,
166
+ and if they deny, do not retry it; finish what you can and say what is
164
167
  left in your last message, with the exact commands.
165
- - You cannot file work, so nothing you find becomes a task by itself. Anything
166
- worth doing next goes in your last message, in one sentence, as something they
167
- can say yes to. Their attention is the gate here on purpose.
168
+ - You cannot file work, so nothing you find becomes a task by itself. Work
169
+ outside their authorization goes in your last message as something they can
170
+ say yes to. Continue work they already authorized.
168
171
  - Code changes happen in the product's repo (your cwd), on a branch, committed
169
172
  as you go. Name the branch the way this repo already names branches; read a
170
173
  few with `git branch` and follow them. Never commit to main; the founder
@@ -191,15 +194,15 @@ session's, you are misapplying it. Answer well, then apply what actually fits.
191
194
  <!-- no-store-tools:end -->
192
195
 
193
196
  <!-- subagent-rule:start -->
194
- ## One row, or several
195
-
196
- Split work into separate work items when each piece needs its own answer from
197
- the founder or its own session. Keep helpers inside one item when their work
198
- combines into a single result.
197
+ ## One job is one thread
199
198
 
200
- Twenty issues off one call are twenty rows. A research task fanned across ten
201
- subagents that merge into one document is one row, however many subagents it
202
- takes. Spawning a subagent is not by itself a reason to file anything.
199
+ DEFAULT TO ONE THREAD; one PR is one thread. A second costs them one more thing
200
+ to read, rank and start or close. Ask how many ANSWERS you need, not how many
201
+ THINGS you found: twenty issues off one call are ONE thread when the answer is
202
+ "yes, fix them". Never hand work to another thread that you could do here, and
203
+ run parallel work as subagents in this one: subagents are not threads. File a
204
+ second only for a separate job, UNDER this one (`parent`), which keeps it out of
205
+ their inbox.
203
206
  <!-- subagent-rule:end -->
204
207
 
205
208
  ## Honesty
@@ -242,3 +245,23 @@ whatever it says. On a run with nothing in it, say that in one line and stop.
242
245
  One line costs them a second to read and tells them the job is still alive.
243
246
  <!-- no-store-tools:end -->
244
247
  <!-- repeat-rules:end -->
248
+ <!-- teammate-rules:start -->
249
+
250
+ ## If this item is one run of a teammate
251
+
252
+ Its labels carry `teammate:<id>`, and the system prompt tells you which
253
+ teammate you are. This run is your own work, not something the person asked
254
+ for, so the rules above about leaving their rows open do not apply to it. The
255
+ team chat is where you talk; their inbox is only for what needs them.
256
+
257
+ Finish one of two ways.
258
+
259
+ Nothing needs them: post a one-line summary with team_post, then update this
260
+ item with `status: "done"` and its labels plus `"clean"`, all in one call
261
+ (labels replace the array, so send every one). This is the ordinary finish. A
262
+ report they did not ask a question about belongs in the chat, not their inbox.
263
+
264
+ They need to decide, approve or sign in: end with that question as your last
265
+ message, short: a bold first line, two or three sentences, numbered options
266
+ with your recommendation first. Leave the item open. It reaches their inbox.
267
+ <!-- teammate-rules:end -->
@@ -1,31 +1,33 @@
1
- // THE ACCOUNTS AGENTBOX RUNS ON ARE THE ONES ON THE DISK, not the ones somebody
2
- // remembered to type into a config file.
1
+ // THE ACCOUNTS AGENTBOX KNOWS ABOUT ARE THE ONES ON THE DISK, not the ones
2
+ // somebody remembered to type into a config file.
3
3
  //
4
- // Picture someone with two subscriptions: a second account signed in through
5
- // its own CLAUDE_CONFIG_DIR, and a first one that has hit its WEEKLY limit.
6
- // The fleet stops for days, with a rested account on the same disk the whole
7
- // time. The register of accounts was the
8
- // hand-written `authProfiles` key in zero.config.json, which nothing in the app
9
- // writes and nothing in the app checks against reality, so an account set up
10
- // the ordinary way (a new CLAUDE_CONFIG_DIR, a /login) was invisible BY
11
- // CONSTRUCTION. There was no symptom: the page drew two healthy rows and every
12
- // word on them was true.
4
+ // The register of accounts was the hand-written `authProfiles` key in
5
+ // zero.config.json, which nothing in the app writes and nothing in the app
6
+ // checks against reality, so an account set up the ordinary way (a new
7
+ // CLAUDE_CONFIG_DIR, a /login) was invisible BY CONSTRUCTION: it could not be
8
+ // seen on the Accounts page and could not be picked.
13
9
  // tests/an-account-she-signed-into-is-not-invisible.test.mjs has the folders,
14
10
  // the ids and the CLI's own sentence.
15
11
  //
12
+ // KNOWING ABOUT AN ACCOUNT IS NOT SENDING WORK TO IT (w-dac993a554). This list
13
+ // is what the Accounts page draws and what somebody picks from; the work goes
14
+ // to one account, `Supervisor#_theOneAccount`. A second subscription found here
15
+ // sits idle until it is picked or a project is tied to it.
16
+ //
16
17
  // THE RULE, and it is four lines because each one was a way to get this wrong:
17
18
  //
18
19
  // - A folder under the home directory named `.claude` or `.claude-<anything>`
19
20
  // that holds a login is an account.
20
21
  // - IT ONLY COUNTS IF ITS SUBSCRIPTION IS NEW. Two folders holding one
21
22
  // account is a real setup (~/.claude and ~/.claude-second both signed in
22
- // as the same person), and counting it twice sends two thirds of every
23
- // spawn round to an account that is already at its limit. The test is on
24
- // `accountUuid`, the id Claude Code stores for the subscription itself,
25
- // never on the email, because one email can hold more than one subscription.
26
- // - NOTHING THE USER CONFIGURED IS EVER DROPPED OR REORDERED. The config is a
27
- // decision; this is only a discovery. Discovered accounts go after the
28
- // configured ones.
23
+ // as the same person), and counting it twice draws one subscription as two
24
+ // rows to choose between. The test is on `accountUuid`, the id Claude Code
25
+ // stores for the subscription itself, never on the email, because one
26
+ // email can hold more than one subscription.
27
+ // - NOTHING THE USER CONFIGURED IS REORDERED, and nothing is dropped except
28
+ // a home-directory folder that fails the two rules above and below: one
29
+ // with no login, or a second one holding a subscription already listed.
30
+ // Discovered accounts go after the configured ones.
29
31
  // - A folder with no login is not an account. Folders such as
30
32
  // ~/.claude-swap-backup or ~/.claude-worktrees are exactly that, and
31
33
  // neither is a place to send work.
@@ -47,6 +49,7 @@ import fs from 'node:fs';
47
49
  import os from 'node:os';
48
50
  import path from 'node:path';
49
51
  import { accountIdentity, defaultClaudeHome } from './account-tooling.mjs';
52
+ import { shellDataDirsNow } from './shell-path.mjs';
50
53
 
51
54
  // The listing, held against the home folder's mtime. Creating or deleting a
52
55
  // folder there moves it; signing IN does not, which is why only the listing is
@@ -98,35 +101,87 @@ export function discoverProfiles({ home = os.homedir() } = {}) {
98
101
  out.push({ dir, email: id.email, accountUuid: id.accountUuid });
99
102
  }
100
103
  // The default login leads, because it is the one every other account is a
101
- // second of, and the rest keep the disk's alphabetical order so the fleet's
102
- // round-robin does not reshuffle itself between ticks.
104
+ // second of and the one work runs on until somebody picks another
105
+ // (`_theOneAccount` takes the first of this list). The rest keep the disk's
106
+ // alphabetical order, so the order does not change between ticks.
103
107
  return out.sort((a, b) => (a.dir === dflt ? -1 : b.dir === dflt ? 1 : 0));
104
108
  }
105
109
 
110
+ /** A `.claude-<anything>` folder directly in the home directory: the shape
111
+ * discovery looks for and Add account makes. */
112
+ function isHomeFolder(dir, home) {
113
+ const full = path.resolve(dir);
114
+ return path.dirname(full) === path.resolve(home) && path.basename(full).startsWith('.claude-');
115
+ }
116
+
106
117
  /**
107
- * THE PROFILES THE FLEET ACTUALLY RUNS ON: the configured ones first, exactly
108
- * as written, then any subscription found on the disk that none of the
109
- * configured ones is already running on.
118
+ * THE PROFILES THE FLEET ACTUALLY RUNS ON: the configured ones first, in her
119
+ * order, then any subscription found on the disk that none of the configured
120
+ * ones is already running on.
110
121
  *
111
122
  * Profiles are the strings the rest of the app has always used: the literal
112
123
  * 'default' for ~/.claude (which is what spawning with no CLAUDE_CONFIG_DIR
113
124
  * means), and an absolute folder path for anything else. It always answers
114
125
  * with at least ['default'], because somewhere has to be spawnable. */
115
- export function effectiveProfiles(configured, { home = os.homedir() } = {}) {
126
+ export function effectiveProfiles(configured, { home = os.homedir(), shellDir = shellDataDirsNow().CLAUDE_CONFIG_DIR } = {}) {
116
127
  const mine = (Array.isArray(configured) ? configured : []).map((p) => p || 'default');
117
- const list = mine.length ? [...mine] : ['default'];
118
128
  const dflt = defaultClaudeHome(home);
119
129
  const dirOf = (p) => (p === 'default' ? dflt : p);
130
+ const isDefault = (p) => p === 'default' || dirOf(p) === dflt;
131
+ const idOf = (p) => accountIdentity(isDefault(p) ? null : dirOf(p), { home });
120
132
 
121
- // Which subscriptions her own list already covers. A configured folder with
122
- // no readable login covers nothing, which is right: it cannot run anything,
123
- // so it cannot be the reason a working account is left out.
133
+ // Which subscriptions her own list already covers, the default login's first
134
+ // so that it is the one kept when a second folder holds the same account.
124
135
  const have = new Set();
125
- for (const p of list) {
126
- const id = accountIdentity(p === 'default' ? null : dirOf(p), { home });
136
+ for (const p of mine) {
137
+ const id = isDefault(p) ? idOf(p) : null;
127
138
  if (id?.accountUuid) have.add(id.accountUuid);
128
139
  }
140
+
141
+ // A `.claude-<n>` FOLDER IN THE HOME DIRECTORY IS AN ACCOUNT ONLY WHEN IT IS
142
+ // SIGNED IN, AND ONLY ONCE PER SUBSCRIPTION, whether discovery found it or
143
+ // the config names it. Add account used to write its folder into the config
144
+ // before the sign-in, so an abandoned one sat in Settings as "Not signed in"
145
+ // and was handed every other task; and one account signed into two folders
146
+ // was listed twice (tests/an-account-shows-once-and-only-once-it-is-signed-in).
147
+ // The default login is never judged, and a folder anywhere else is a
148
+ // hand-written setting and stays exactly as written.
149
+ const list = [];
150
+ for (const p of mine) {
151
+ if (isDefault(p)) { list.push(p); continue; }
152
+ const id = idOf(p);
153
+ if (isHomeFolder(dirOf(p), home)) {
154
+ if (!id?.accountUuid || have.has(id.accountUuid)) continue;
155
+ }
156
+ if (id?.accountUuid) have.add(id.accountUuid);
157
+ list.push(p);
158
+ }
159
+ if (!list.length) list.push('default');
160
+
161
+ // THE LOGIN THE PERSON'S SHELL PROFILE POINTS AT (w-6d8c543a43). Their
162
+ // terminal's `claude` reads the folder `CLAUDE_CONFIG_DIR` names in .zshrc,
163
+ // which an app opened from the Dock never sees. When the default holds no
164
+ // login, or the same account, the shell's folder takes its place; a
165
+ // different account at ~/.claude keeps running beside it. It is always a
166
+ // folder, never 'default', even when it IS ~/.claude: Claude Code names its
167
+ // keychain entry after the variable, so naming the folder out loud is a
168
+ // different sign-in from naming nothing.
169
+ const shell = typeof shellDir === 'string' && path.isAbsolute(shellDir) ? shellDir : null;
170
+ const shellId = shell ? accountIdentity(shell, { home }) : null;
171
+ if (shellId?.accountUuid && !list.includes(shell)) {
172
+ const dfltId = idOf('default');
173
+ const takesDefault = !dfltId?.accountUuid || dfltId.accountUuid === shellId.accountUuid;
174
+ if (takesDefault && list.some(isDefault)) {
175
+ list.splice(list.findIndex(isDefault), 1, shell);
176
+ for (let i = list.length - 1; i >= 0; i--) if (list[i] !== shell && isDefault(list[i])) list.splice(i, 1);
177
+ have.add(shellId.accountUuid);
178
+ } else if (!have.has(shellId.accountUuid)) {
179
+ have.add(shellId.accountUuid);
180
+ list.push(shell);
181
+ }
182
+ }
129
183
  const listed = new Set(list.map((p) => dirOf(p)));
184
+ if (shell && list.includes(shell)) listed.add(dflt);
130
185
 
131
186
  for (const found of discoverProfiles({ home })) {
132
187
  if (listed.has(found.dir)) continue;
@@ -138,6 +193,43 @@ export function effectiveProfiles(configured, { home = os.homedir() } = {}) {
138
193
  return list;
139
194
  }
140
195
 
196
+ /**
197
+ * WHAT IS WRONG WITH THE SUBSCRIPTION THIS ROW STANDS FOR, not merely with the
198
+ * folder it is named after.
199
+ *
200
+ * Trouble is filed per FOLDER, because a folder is what a worker is handed.
201
+ * `effectiveProfiles` above lists one row per SUBSCRIPTION, dropping a second
202
+ * folder holding an account it already has. So a limit hit in the dropped
203
+ * folder is filed under a key no row carries and is drawn nowhere, while the
204
+ * row for that same subscription goes on showing whatever was last filed on
205
+ * the listed folder, however old.
206
+ *
207
+ * Measured 2026-10-16: ~/.claude-3 hit a session limit at 3:15pm and the row
208
+ * for that subscription showed an "organization has turned Claude Code off"
209
+ * filed on ~/.claude five days earlier. Both folders hold accountUuid
210
+ * 8fe4a357; the newest fact about the account was the invisible one.
211
+ *
212
+ * THE NEWEST READING WINS, because that is the only honest answer when two
213
+ * folders of one subscription disagree: each entry is what was true when a
214
+ * run last died there, and nothing clears a folder nothing spawns on.
215
+ * Codex keys are never considered — an account's key carries its engine. */
216
+ export function troubleForProfile(troubles, profile, { home = os.homedir() } = {}) {
217
+ const dflt = defaultClaudeHome(home);
218
+ const dirOf = (p) => (p === 'default' ? dflt : p);
219
+ const idOf = (p) => accountIdentity(dirOf(p) === dflt ? null : dirOf(p), { home });
220
+ const own = troubles?.[profile] ?? null;
221
+ const mine = idOf(profile)?.accountUuid;
222
+ if (!mine) return own;
223
+ const when = (t) => t?.at ?? t?.since ?? 0;
224
+ let best = own;
225
+ for (const [key, t] of Object.entries(troubles ?? {})) {
226
+ if (!t || key === profile || key.startsWith('codex:')) continue;
227
+ if (idOf(key)?.accountUuid !== mine) continue;
228
+ if (when(t) > when(best)) best = t;
229
+ }
230
+ return best;
231
+ }
232
+
141
233
  /**
142
234
  * Test seam only: forget the cached directory listing. Nothing in the app
143
235
  * calls this — the mtime check is what keeps it honest at runtime. */
@@ -213,7 +213,7 @@ export function accountIdentity(dir, { home = os.homedir() } = {}) {
213
213
  try {
214
214
  const acct = JSON.parse(fs.readFileSync(file, 'utf8'))?.oauthAccount;
215
215
  if (acct?.emailAddress) {
216
- value = { email: acct.emailAddress, accountUuid: acct.accountUuid ?? null };
216
+ value = { email: acct.emailAddress, accountUuid: acct.accountUuid ?? null, ...(acct.organizationUuid ? { organizationUuid: acct.organizationUuid } : {}) };
217
217
  }
218
218
  } catch {
219
219
  // A half-written file, or one Claude Code has changed the shape of. The
@@ -224,6 +224,34 @@ export function accountIdentity(dir, { home = os.homedir() } = {}) {
224
224
  return value;
225
225
  }
226
226
 
227
+ /**
228
+ * A CLOUD LOGIN'S NAME, from the one login here in the same organization. A
229
+ * cloud token can say its organization but not its email. Null when no login
230
+ * here is in it, or when two different emails are, so a team organization is
231
+ * never pinned on whichever member happens to be signed in here.
232
+ */
233
+ export function nameForOrganization(org, identities) {
234
+ if (!org) return null;
235
+ const emails = new Set(identities.filter((id) => id?.organizationUuid === org && id.email).map((id) => id.email));
236
+ return emails.size === 1 ? [...emails][0] : null;
237
+ }
238
+
239
+ /**
240
+ * The folder here holding a cloud login, so the two share one reading. A login
241
+ * that says its email is that person in its organization; one that says only
242
+ * its organization is the one email here in it, and none when two people here
243
+ * share it.
244
+ */
245
+ export function profileForCloudLogin(login, identities) {
246
+ return identities.find((id) => isCloudLoginOf(login, id, identities))?.profile ?? null;
247
+ }
248
+
249
+ /** Whether a cloud login is the same subscription as one login here, by `profileForCloudLogin`'s rule. */
250
+ export function isCloudLoginOf({ org, email }, identity, identities) {
251
+ if (!org || !identity?.email || identity.organizationUuid !== org) return false;
252
+ return (email || nameForOrganization(org, identities)) === identity.email;
253
+ }
254
+
227
255
  /**
228
256
  * THE SENTENCE FOR TWO ROWS THAT TURNED OUT TO BE ONE ACCOUNT. It only ever
229
257
  * speaks on a machine that already has two profile folders, so it describes a
@@ -0,0 +1,46 @@
1
+ // EVERY CLAUDE ACCOUNT'S OWN USAGE, for the Settings page.
2
+ //
3
+ // main/claude-usage.mjs reads the account in use, for the corner on every
4
+ // screen. This reads all of them, only when Settings asks, so the page can say
5
+ // which login has room, and so a reading can take back a mark the account no
6
+ // longer deserves (`Supervisor#noteUsageReading`).
7
+ import { ClaudeUsage, STALE_MS } from './claude-usage.mjs';
8
+
9
+ export class AccountUsage {
10
+ constructor({ bin, env = () => process.env, onReading = () => {}, run, now = () => Date.now(), where } = {}) {
11
+ this._bin = bin;
12
+ this._env = env;
13
+ this._onReading = onReading;
14
+ this._run = run;
15
+ this._now = now;
16
+ this._where = where;
17
+ this._readers = new Map();
18
+ }
19
+
20
+ _reader(profile) {
21
+ let reader = this._readers.get(profile);
22
+ if (!reader) {
23
+ reader = new ClaudeUsage({
24
+ bin: this._bin, run: this._run, now: this._now, where: this._where,
25
+ account: () => ({ profile, env: this._env() }),
26
+ });
27
+ this._readers.set(profile, reader);
28
+ }
29
+ return reader;
30
+ }
31
+
32
+ /** One answer per account, in the order given: its limits, or why there are none. */
33
+ async readAll(profiles, { refresh = false } = {}) {
34
+ return Promise.all(profiles.map(async (profile) => {
35
+ const reader = this._reader(profile);
36
+ const last = reader.peek();
37
+ const fresh = !refresh && last && this._now() - last.at < STALE_MS;
38
+ const result = fresh ? { ok: true, reading: last } : await reader.refreshNow();
39
+ if (result.ok && result.reading) {
40
+ if (!fresh) this._onReading(profile, result.reading);
41
+ return { ...result.reading, profile };
42
+ }
43
+ return { ...(reader.peek() ?? {}), profile, error: result.error ?? null };
44
+ }));
45
+ }
46
+ }