agentbox-app 0.1.6

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 (302) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +176 -0
  3. package/bin/agentbox.mjs +103 -0
  4. package/briefs/adhd-mode.md +5 -0
  5. package/briefs/message-rules.md +95 -0
  6. package/briefs/worker.md +230 -0
  7. package/briefs/writing-rules.md +336 -0
  8. package/main/account-discovery.mjs +145 -0
  9. package/main/account-tooling.mjs +261 -0
  10. package/main/agent-activity.mjs +56 -0
  11. package/main/agent-files.mjs +422 -0
  12. package/main/agent-schedule.mjs +254 -0
  13. package/main/agent-sessions.mjs +435 -0
  14. package/main/agents.mjs +1095 -0
  15. package/main/analytics.mjs +206 -0
  16. package/main/answer-modes.mjs +206 -0
  17. package/main/approval-prompt-server.mjs +160 -0
  18. package/main/approvals.mjs +487 -0
  19. package/main/artifact-path.mjs +144 -0
  20. package/main/claude-bin.mjs +311 -0
  21. package/main/claude-input.mjs +117 -0
  22. package/main/claude-models.mjs +244 -0
  23. package/main/claude-plan.mjs +148 -0
  24. package/main/claude-usage.mjs +149 -0
  25. package/main/code-change.mjs +383 -0
  26. package/main/code-file.mjs +116 -0
  27. package/main/codex-account.mjs +146 -0
  28. package/main/codex-app-server.mjs +796 -0
  29. package/main/codex-approvals.mjs +712 -0
  30. package/main/codex-bin.mjs +261 -0
  31. package/main/codex-compaction.mjs +49 -0
  32. package/main/codex-models.mjs +194 -0
  33. package/main/codex-session.mjs +797 -0
  34. package/main/codex-threads.mjs +268 -0
  35. package/main/codex-usage-file.mjs +192 -0
  36. package/main/codex-usage.mjs +149 -0
  37. package/main/codex-watch.mjs +35 -0
  38. package/main/codex.mjs +757 -0
  39. package/main/config.mjs +345 -0
  40. package/main/copy-selection.mjs +55 -0
  41. package/main/crash-report.mjs +158 -0
  42. package/main/demo.mjs +254 -0
  43. package/main/dev-window.mjs +103 -0
  44. package/main/doc-file.mjs +99 -0
  45. package/main/doc-scheme.mjs +126 -0
  46. package/main/first-run.mjs +190 -0
  47. package/main/folders.mjs +91 -0
  48. package/main/fresh-user.mjs +120 -0
  49. package/main/git-change.mjs +323 -0
  50. package/main/img-scheme.mjs +90 -0
  51. package/main/instruction-settings.mjs +205 -0
  52. package/main/ipc.mjs +1558 -0
  53. package/main/live-replies.mjs +77 -0
  54. package/main/machine.mjs +113 -0
  55. package/main/main.mjs +972 -0
  56. package/main/menu.mjs +177 -0
  57. package/main/notify.mjs +155 -0
  58. package/main/open-source-file.mjs +8 -0
  59. package/main/project-identity.mjs +149 -0
  60. package/main/provider-commands.mjs +32 -0
  61. package/main/rail-note.mjs +74 -0
  62. package/main/repeats.mjs +261 -0
  63. package/main/row-label.mjs +185 -0
  64. package/main/screen-detail.mjs +44 -0
  65. package/main/serve.mjs +262 -0
  66. package/main/settings.mjs +912 -0
  67. package/main/staleness.mjs +76 -0
  68. package/main/store/chats.mjs +188 -0
  69. package/main/store/home.mjs +383 -0
  70. package/main/store/migrate.mjs +40 -0
  71. package/main/store/paths.mjs +82 -0
  72. package/main/store/pending-writes.mjs +122 -0
  73. package/main/store/project-lock.mjs +200 -0
  74. package/main/store/project.mjs +523 -0
  75. package/main/store/queues.mjs +31 -0
  76. package/main/store/work-items.mjs +299 -0
  77. package/main/store/write-queue.mjs +14 -0
  78. package/main/store-modules.mjs +23 -0
  79. package/main/store.mjs +1269 -0
  80. package/main/supervisor.mjs +6875 -0
  81. package/main/task-commands.mjs +161 -0
  82. package/main/task-folders-offthread.mjs +84 -0
  83. package/main/task-folders-thread.mjs +13 -0
  84. package/main/task-folders.mjs +409 -0
  85. package/main/task-remote-control.mjs +65 -0
  86. package/main/task-terminals.mjs +169 -0
  87. package/main/terminal-access.mjs +6 -0
  88. package/main/updater.mjs +240 -0
  89. package/main/zoom-keys.mjs +54 -0
  90. package/main/zoom-readout.mjs +20 -0
  91. package/mcp/agentbox-mcp.sh +34 -0
  92. package/mcp/core/account.mjs +77 -0
  93. package/mcp/core/chat.mjs +61 -0
  94. package/mcp/core/documents.mjs +238 -0
  95. package/mcp/core/page.mjs +152 -0
  96. package/mcp/core/products.mjs +110 -0
  97. package/mcp/core/skills.mjs +126 -0
  98. package/mcp/core/work.mjs +377 -0
  99. package/mcp/stdio.mjs +70 -0
  100. package/mcp/tools.mjs +257 -0
  101. package/package.json +186 -0
  102. package/renderer/dist/assets/Geist-Variable-Bj2R_7yk.woff2 +0 -0
  103. package/renderer/dist/assets/GeistMono-Variable-Dispecij.woff2 +0 -0
  104. package/renderer/dist/assets/addon-fit-YJmn1quW.js +12 -0
  105. package/renderer/dist/assets/addon-serialize-CsFQTvP_.js +13 -0
  106. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  107. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  108. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  109. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  110. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  111. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  112. package/renderer/dist/assets/gouache-valley-ChSu14-t.webp +0 -0
  113. package/renderer/dist/assets/gouache-valley-d9qasI6k.webp +0 -0
  114. package/renderer/dist/assets/gouache-valley-sm-CdLjT5Ec.webp +0 -0
  115. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  116. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  117. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  118. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  119. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  120. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  121. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  122. package/renderer/dist/assets/index-CwiDv5bL.css +32 -0
  123. package/renderer/dist/assets/index-D9hCAVPl.js +684 -0
  124. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  125. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  126. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  127. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  128. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  129. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  130. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  131. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  132. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  133. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  134. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  135. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  136. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  137. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  138. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  139. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  140. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  141. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  142. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  143. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  144. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  145. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  146. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  147. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  148. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  149. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  150. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  151. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  152. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  153. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  154. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  155. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  156. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  157. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  158. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  159. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  160. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  161. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  162. package/renderer/dist/assets/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
  163. package/renderer/dist/assets/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
  164. package/renderer/dist/assets/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
  165. package/renderer/dist/assets/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
  166. package/renderer/dist/assets/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
  167. package/renderer/dist/assets/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
  168. package/renderer/dist/assets/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
  169. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  170. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  171. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  172. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  173. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  174. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  175. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  176. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  177. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  178. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  179. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  180. package/renderer/dist/assets/xterm-BT7tBf8A.js +27 -0
  181. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  182. package/renderer/dist/emulator/data/compression/README.md +11 -0
  183. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  184. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  185. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  186. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  187. package/renderer/dist/emulator/data/cores/README.md +22 -0
  188. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  189. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  190. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  191. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  192. package/renderer/dist/emulator/data/cores/package.json +65 -0
  193. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  194. package/renderer/dist/emulator/data/emulator.css +1614 -0
  195. package/renderer/dist/emulator/data/loader.js +170 -0
  196. package/renderer/dist/emulator/data/localization/README.md +63 -0
  197. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  198. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  199. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  200. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  201. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  202. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  203. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  204. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  205. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  206. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  207. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  208. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  209. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  210. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  211. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  212. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  213. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  214. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  215. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  216. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  217. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  218. package/renderer/dist/emulator/data/src/compression.js +142 -0
  219. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  220. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  221. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  222. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  223. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  224. package/renderer/dist/emulator/data/src/storage.js +125 -0
  225. package/renderer/dist/emulator/data/version.json +5 -0
  226. package/renderer/dist/emulator/host.js +299 -0
  227. package/renderer/dist/emulator/index.html +28 -0
  228. package/renderer/dist/emulator/provenance.json +6 -0
  229. package/renderer/dist/fixtures/onboard/designs/w-n13/the-first-row.html +1 -0
  230. package/renderer/dist/index.html +13 -0
  231. package/scripts/after-install.mjs +59 -0
  232. package/scripts/approval-server.sh +43 -0
  233. package/scripts/before-publish.mjs +133 -0
  234. package/shared/agent-import.d.mts +13 -0
  235. package/shared/agent-import.mjs +252 -0
  236. package/shared/agents.d.mts +190 -0
  237. package/shared/agents.mjs +937 -0
  238. package/shared/analytics-events.mjs +63 -0
  239. package/shared/answers.d.mts +12 -0
  240. package/shared/answers.mjs +80 -0
  241. package/shared/artifact-keys.d.mts +28 -0
  242. package/shared/artifact-keys.mjs +140 -0
  243. package/shared/bridge-map.mjs +128 -0
  244. package/shared/claude-commands.d.mts +20 -0
  245. package/shared/claude-commands.generated.mjs +149 -0
  246. package/shared/claude-commands.mjs +131 -0
  247. package/shared/claude-models.generated.d.mts +15 -0
  248. package/shared/claude-models.generated.mjs +26 -0
  249. package/shared/claude-usage.d.mts +16 -0
  250. package/shared/claude-usage.mjs +172 -0
  251. package/shared/codex-commands.d.mts +4 -0
  252. package/shared/codex-commands.mjs +17 -0
  253. package/shared/codex-import.d.mts +38 -0
  254. package/shared/codex-import.mjs +232 -0
  255. package/shared/codex-modes.mjs +159 -0
  256. package/shared/codex-usage.mjs +180 -0
  257. package/shared/contracts.mjs +412 -0
  258. package/shared/crash-scrub.mjs +182 -0
  259. package/shared/dashboard.mjs +481 -0
  260. package/shared/dead-run-trace.mjs +127 -0
  261. package/shared/demo-world.mjs +415 -0
  262. package/shared/effort-levels.d.mts +15 -0
  263. package/shared/effort-levels.mjs +76 -0
  264. package/shared/engines.d.mts +62 -0
  265. package/shared/engines.mjs +389 -0
  266. package/shared/first-run-examples.mjs +60 -0
  267. package/shared/first-run-line.mjs +149 -0
  268. package/shared/first-run-practice.d.mts +32 -0
  269. package/shared/first-run-practice.mjs +254 -0
  270. package/shared/first-run-shape.mjs +100 -0
  271. package/shared/fresh-user-home.mjs +189 -0
  272. package/shared/hook-failure.mjs +22 -0
  273. package/shared/install-id.mjs +39 -0
  274. package/shared/instruction-defaults.json +5 -0
  275. package/shared/library-folder.mjs +28 -0
  276. package/shared/notify-rules.mjs +120 -0
  277. package/shared/product-name.d.mts +6 -0
  278. package/shared/product-name.mjs +144 -0
  279. package/shared/project-folder-check.d.mts +22 -0
  280. package/shared/project-folder-check.mjs +152 -0
  281. package/shared/provider-commands.d.mts +8 -0
  282. package/shared/provider-commands.mjs +58 -0
  283. package/shared/rank.d.mts +19 -0
  284. package/shared/rank.mjs +146 -0
  285. package/shared/recovery.mjs +26 -0
  286. package/shared/referenced-files.d.mts +11 -0
  287. package/shared/referenced-files.mjs +122 -0
  288. package/shared/repeats.d.mts +45 -0
  289. package/shared/repeats.mjs +193 -0
  290. package/shared/schemes.mjs +33 -0
  291. package/shared/settings-terminal.d.mts +1 -0
  292. package/shared/settings-terminal.mjs +14 -0
  293. package/shared/side-build.mjs +57 -0
  294. package/shared/spawn-trouble.d.mts +8 -0
  295. package/shared/spawn-trouble.mjs +599 -0
  296. package/shared/terminal-state.d.mts +1 -0
  297. package/shared/terminal-state.mjs +58 -0
  298. package/shared/usage.d.mts +53 -0
  299. package/shared/usage.mjs +319 -0
  300. package/shared/work-items.mjs +561 -0
  301. package/shared/work-lines.mjs +360 -0
  302. package/worker-permissions.json +6 -0
@@ -0,0 +1,261 @@
1
+ // A SECOND ACCOUNT GETS YOUR SKILLS, COMMANDS AND AGENTS.
2
+ //
3
+ // THE PROBLEM THIS EXISTS FOR, and it is one we ship rather than one a person
4
+ // causes. The Accounts page in Settings tells a person to add a second
5
+ // subscription by logging in with CLAUDE_CONFIG_DIR set to a brand new folder.
6
+ // Do exactly that and the folder holds a login and nothing else, because
7
+ // Claude Code reads a session's skills, commands and subagents out of whichever
8
+ // folder CLAUDE_CONFIG_DIR names. The supervisor then round-robins every spawn
9
+ // across the accounts, so from that moment half the person's workers run with
10
+ // none of their own tooling and nothing anywhere says so. Measured on the one
11
+ // machine where it had happened: 65 skills on the first login, 12 on the
12
+ // second, and 445 of 874 sessions had already run on the empty one.
13
+ //
14
+ // THE REPAIR IS THREE SYMLINKS, made the first time a worker spawns on an
15
+ // account that is missing them:
16
+ //
17
+ // <account folder>/skills -> ~/.claude/skills
18
+ // <account folder>/commands -> ~/.claude/commands
19
+ // <account folder>/agents -> ~/.claude/agents
20
+ //
21
+ // Links rather than copies, so a person edits a skill once and every account
22
+ // has it. Claude Code 2.1.236 follows them; that was verified on her machine
23
+ // rather than assumed.
24
+ //
25
+ // WHAT IS DELIBERATELY NOT IN THAT LIST. Plugins. They carry their own install
26
+ // records and two of the ones on this machine want their own login, so sharing
27
+ // that folder is a different and riskier change and it should be its own
28
+ // decision. Settings, credentials, history, projects and todos are per account
29
+ // on purpose: a shared `.credentials.json` or `settings.json` would be two
30
+ // subscriptions fighting over one file, which is the opposite of the point.
31
+ //
32
+ // WHAT THIS WILL NOT DO, in every case leaving the folder exactly as it found
33
+ // it: overwrite anything. If a name is already taken, by a real directory of
34
+ // their own, a file, or a link pointing somewhere else, it is left alone and
35
+ // reported as theirs. Someone who copied their commands across by hand keeps
36
+ // the copy.
37
+
38
+ import fs from 'node:fs';
39
+ import os from 'node:os';
40
+ import path from 'node:path';
41
+
42
+ /**
43
+ * The three folders a session reads its own tooling out of, and the only
44
+ * three. See the note above for why `plugins` is not here. */
45
+ export const SHARED_DIRS = ['skills', 'commands', 'agents'];
46
+
47
+ /**
48
+ * Where the first login lives: the home folder Claude Code uses when nothing
49
+ * sets CLAUDE_CONFIG_DIR, which is the account every one of these links
50
+ * points back at. */
51
+ export function defaultClaudeHome(home = os.homedir()) {
52
+ return path.join(home, '.claude');
53
+ }
54
+
55
+ /**
56
+ * A FOLDER FOR THE NEXT CLAUDE LOGIN, MADE BEFORE ANYTHING IS ASKED TO USE IT.
57
+ *
58
+ * The twin of `makeCodexHome`, and it exists for the same reason that one does:
59
+ * a second login is a second home folder, and the app should make it rather
60
+ * than print a line telling somebody to make it themselves.
61
+ *
62
+ * THE NAME IS COUNTED, NEVER GUESSED. `~/.claude-2` and up, skipping anything
63
+ * that exists, so pressing Add twice cannot hand the second login the first
64
+ * one's folder and sign over a working account. The `.claude-` shape is also
65
+ * exactly what main/account-discovery.mjs looks for, so a folder made here is
66
+ * found again on its own even if the config entry is later removed by hand.
67
+ *
68
+ * Claude Code will create a config directory it is pointed at, unlike
69
+ * codex-cli, so this is not strictly required to make the sign in work. It is
70
+ * still made here: the row for the new account appears the moment she presses,
71
+ * and a row for a folder that does not exist yet would be a screen describing
72
+ * something that is not there.
73
+ *
74
+ * @returns {{ home: string, profile: string }} `profile` is the word the fleet
75
+ * spawns on, which for every home but the first one IS the path.
76
+ */
77
+ export function makeClaudeHome({ home = os.homedir(), mkdir = fs.mkdirSync, exists = fs.existsSync } = {}) {
78
+ for (let n = 2; n < 100; n += 1) {
79
+ const dir = path.join(home, `.claude-${n}`);
80
+ if (exists(dir)) continue;
81
+ mkdir(dir, { recursive: true });
82
+ return { home: dir, profile: dir };
83
+ }
84
+ throw new Error('There are already a lot of Claude folders in your home directory.');
85
+ }
86
+
87
+ /**
88
+ * The one line that signs a new Claude account in, for the terminal to run.
89
+ *
90
+ * `claude auth login` is their own sign-in command and this app has no
91
+ * business driving the browser half of it. The folder is spelled out in full
92
+ * rather than left as `~`, because `~` inside a variable assignment is not
93
+ * expanded by every shell the same way. */
94
+ export function claudeLoginCommand(home) {
95
+ return `CLAUDE_CONFIG_DIR=${home} claude auth login`;
96
+ }
97
+
98
+ function realOrNull(p) {
99
+ try { return fs.realpathSync(p); } catch { return null; }
100
+ }
101
+
102
+ /**
103
+ * WHAT IS ALREADY AT THIS NAME, without following the link, because a link is
104
+ * precisely the thing we must be able to see. Returns 'none' when the name is
105
+ * free, 'ours' when it is already the link this module would have made, and
106
+ * 'theirs' for anything else at all. */
107
+ function occupancy(target, source) {
108
+ let st;
109
+ try { st = fs.lstatSync(target); } catch { return 'none'; }
110
+ if (!st.isSymbolicLink()) return 'theirs';
111
+ const to = realOrNull(target);
112
+ const from = realOrNull(source);
113
+ return to && from && to === from ? 'ours' : 'theirs';
114
+ }
115
+
116
+ /**
117
+ * MAKE THE LINKS FOR ONE ACCOUNT. `dir` is the folder that account's
118
+ * CLAUDE_CONFIG_DIR names.
119
+ *
120
+ * Returns what happened, in the four kinds a person could be told about:
121
+ * `linked` are the ones this call created, `already` were there and correct,
122
+ * `theirs` are names that were taken and were left untouched, and `missing`
123
+ * are folders the first login does not have either, which is not a fault and
124
+ * is why nothing is created for them.
125
+ *
126
+ * `skipped` is set instead when there was nothing to do at all, and is one of:
127
+ * 'default' (the first login, which is the source), 'same' (an account folder
128
+ * that IS the first login by another path), or 'no-dir' (a folder that does
129
+ * not exist, which means the account is misconfigured and making a home for it
130
+ * would only hide that). */
131
+ export function linkAccountTooling(dir, { home = os.homedir(), source = null } = {}) {
132
+ const src = source ?? defaultClaudeHome(home);
133
+ const out = { dir, linked: [], already: [], theirs: [], missing: [], skipped: null };
134
+ if (!dir || dir === 'default') return { ...out, skipped: 'default' };
135
+
136
+ const real = realOrNull(dir);
137
+ if (!real) return { ...out, skipped: 'no-dir' };
138
+ try { if (!fs.statSync(real).isDirectory()) return { ...out, skipped: 'no-dir' }; } catch { return { ...out, skipped: 'no-dir' }; }
139
+ // An account pointed at ~/.claude by a second path is the first login. Linking
140
+ // a folder into itself would make skills/skills/skills and there is a real way
141
+ // to arrive here: a symlinked home, or `authProfiles: ["~/.claude"]`.
142
+ if (real === realOrNull(src)) return { ...out, skipped: 'same' };
143
+
144
+ for (const name of SHARED_DIRS) {
145
+ const from = path.join(src, name);
146
+ if (!realOrNull(from)) { out.missing.push(name); continue; }
147
+ const to = path.join(real, name);
148
+ const held = occupancy(to, from);
149
+ if (held === 'ours') { out.already.push(name); continue; }
150
+ if (held === 'theirs') { out.theirs.push(name); continue; }
151
+ try {
152
+ fs.symlinkSync(from, to, 'dir');
153
+ out.linked.push(name);
154
+ } catch {
155
+ // A read-only folder, a race with another Agentbox, a permission the app
156
+ // does not have. The account still runs; it just runs the way it did
157
+ // before this module existed, so this is reported and never thrown.
158
+ out.theirs.push(name);
159
+ }
160
+ }
161
+ return out;
162
+ }
163
+
164
+ /**
165
+ * ONE LINE FOR THE TRACE, in the words she would use, or null when nothing
166
+ * changed and there is therefore nothing to say. Silence is the ordinary case:
167
+ * after the first spawn on an account every later one finds the links already
168
+ * made. */
169
+ export function toolingLine(result) {
170
+ if (!result || !result.linked.length) return null;
171
+ const where = path.basename(result.dir);
172
+ return `Gave the ${where} account your ${list(result.linked)}.`;
173
+ }
174
+
175
+ function list(names) {
176
+ if (names.length === 1) return names[0];
177
+ return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
178
+ }
179
+
180
+ /* ------------------------- WHOSE LOGIN IS IN THERE ------------------------ */
181
+ // WHICH CLAUDE ACCOUNT A FOLDER IS ACTUALLY SIGNED IN AS.
182
+ //
183
+ // The Accounts page named its two rows `default` and `.claude-second`, which
184
+ // are folders, not people. So there was no way to tell from the screen which
185
+ // subscription either row was running on, and on 2026-08-29 that cost her the
186
+ // second one. The page said the second account was signed out. She did exactly
187
+ // what it told her to do, ran the command and typed /login at 1:47pm, and the
188
+ // browser was already signed in as her personal account, so the login handed
189
+ // that one back. Her work account, work@example.com, had been in that folder
190
+ // since at least 08-23 and was gone by 1:50pm, replaced by the same account the
191
+ // first row was already using. Measured off the config backups the CLI itself
192
+ // writes. Nothing on the page changed when it happened, because nothing on the
193
+ // page had ever said whose account it was, which is also exactly what she
194
+ // reported: "Idk which is logged in nothing changed in settings".
195
+ //
196
+ // One line of the answer, read from the file Claude Code keeps its own account
197
+ // in. Null when the folder holds no login, which reads on the page as the one
198
+ // honest thing we can say about it.
199
+ const identityCache = new Map();
200
+
201
+ export function accountIdentity(dir, { home = os.homedir() } = {}) {
202
+ const file = path.join(dir || defaultClaudeHome(home), '.claude.json');
203
+ // Cached on the file's own mtime rather than on a timer. That file is ~120 KB
204
+ // on this machine and the settings page re-reads on every poll, so parsing it
205
+ // each time would be real work for an answer that changes about twice a
206
+ // month; keying on mtime means a /login is still seen the moment it lands.
207
+ let mtime = 0;
208
+ try { mtime = fs.statSync(file).mtimeMs; } catch { return null; }
209
+ const hit = identityCache.get(file);
210
+ if (hit && hit.mtime === mtime) return hit.value;
211
+
212
+ let value = null;
213
+ try {
214
+ const acct = JSON.parse(fs.readFileSync(file, 'utf8'))?.oauthAccount;
215
+ if (acct?.emailAddress) {
216
+ value = { email: acct.emailAddress, accountUuid: acct.accountUuid ?? null };
217
+ }
218
+ } catch {
219
+ // A half-written file, or one Claude Code has changed the shape of. The
220
+ // page falls back to naming the folder, which is what it did before this
221
+ // existed, so a bad read costs the extra sentence and nothing else.
222
+ }
223
+ identityCache.set(file, { mtime, value });
224
+ return value;
225
+ }
226
+
227
+ /**
228
+ * THE SENTENCE FOR TWO ROWS THAT TURNED OUT TO BE ONE ACCOUNT. It only ever
229
+ * speaks on a machine that already has two profile folders, so it describes a
230
+ * setup rather than suggesting one.
231
+ *
232
+ * The first fault is that it is an instruction she cannot carry out from this
233
+ * screen and, worse, one that only means anything to somebody who already has
234
+ * a second subscription. Anybody else reads it as Agentbox telling them to go
235
+ * and get one, which is the thing her standing rule forbids. So this sentence
236
+ * instructs nobody now. It says what is true and stops.
237
+ *
238
+ * The second is her point about email, and it is a fair one, so it is worth
239
+ * writing down plainly: THE CHECK IS NOT ON THE EMAIL. It is on `accountUuid`,
240
+ * the id Claude Code stores for the subscription itself, so what it catches is
241
+ * two folders holding one subscription, not two folders showing one address.
242
+ *
243
+ * AND IT NO LONGER CLAIMS THEY ADD NO CAPACITY, because that is not what the
244
+ * app does. `_capacity` in main/supervisor.mjs is `maxConcurrentSessions *
245
+ * liveProfiles.length`, which counts FOLDERS. So a duplicated machine is told
246
+ * it can run double and really does start double, all of it against one
247
+ * subscription. The old sentence described what we intended; this one describes
248
+ * what happens, and says the number above is too high rather than telling her
249
+ * to go and change something.
250
+ * Null when the two really are different subscriptions. */
251
+ export function duplicateAccountNote(identities) {
252
+ const seen = new Map();
253
+ for (const id of identities) {
254
+ if (!id?.accountUuid) continue;
255
+ seen.set(id.accountUuid, (seen.get(id.accountUuid) ?? 0) + 1);
256
+ }
257
+ const dupes = [...seen.values()].filter((n) => n > 1).length;
258
+ if (!dupes) return null;
259
+ return 'Both of these are signed in to the same Claude account.'
260
+ + ' That is one subscription rather than two, so the number above is higher than it should be.';
261
+ }
@@ -0,0 +1,56 @@
1
+ // Ephemeral, local status from matched tool start/end events. A persisted trace
2
+ // is history and cannot prove a command is still running. Nothing here executes
3
+ // commands, records reasoning, or changes worker permissions.
4
+ import { plainCommand, shortPath } from '../shared/work-lines.mjs';
5
+ const clean = value => typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
6
+ function add(session, id, label, detail) {
7
+ if (!id || !label) return false;
8
+ const pending = session.activeTools ??= new Map();
9
+ if (pending.has(id)) return false;
10
+ pending.set(id, { id, label: label.length > 240 ? `${label.slice(0, 237)}…` : label, detail: String(detail ?? ''), startedAt: Date.now() });
11
+ return true;
12
+ }
13
+ function finish(session, id) { return session.activeTools?.delete(id) ?? false; }
14
+ function clear(session) { const changed = !!session.activeTools?.size; session.activeTools?.clear(); return changed; }
15
+ export function currentActivity(session) { return [...(session.activeTools?.values() ?? [])]; }
16
+ function toolLabel(name, input) {
17
+ const command = clean(input.command);
18
+ const file = clean(input.file_path ?? input.path);
19
+ if (name === 'Bash') return ['Running ' + (plainCommand(command) || 'command'), input.command ?? ''];
20
+ if (['Read','Write','Edit','MultiEdit'].includes(name)) return [`${name === 'Read' ? 'Reading' : name === 'Write' ? 'Writing' : 'Editing'} ${shortPath(file) || 'file'}`, file];
21
+ if (['Grep','Glob','WebSearch'].includes(name)) return [`Searching ${clean(input.pattern ?? input.query) || 'files'}`, input.pattern ?? input.query ?? ''];
22
+ if (name === 'WebFetch') return ['Reading web page', input.url ?? ''];
23
+ const friendly = clean(name).split('__').pop().replace(/_/g, ' ');
24
+ return [`Using ${friendly || 'tool'}`, clean(input.description ?? input.title)];
25
+ }
26
+ export function claudeActivity(session, line) {
27
+ try {
28
+ const obj = JSON.parse(line);
29
+ if (obj.parent_tool_use_id) return false;
30
+ if (obj.type === 'result') return clear(session);
31
+ let changed = false;
32
+ for (const part of Array.isArray(obj.message?.content) ? obj.message.content : []) {
33
+ if (obj.type === 'assistant' && part.type === 'tool_use') {
34
+ const [label, detail] = toolLabel(part.name, part.input ?? {});
35
+ changed = add(session, part.id, label, detail) || changed;
36
+ }
37
+ if (obj.type === 'user' && part.type === 'tool_result') changed = finish(session, part.tool_use_id) || changed;
38
+ }
39
+ return changed;
40
+ } catch { return false; }
41
+ }
42
+ export function codexActivity(session, method, params) {
43
+ if (method === 'turn/completed' || method === 'turn/failed') return clear(session);
44
+ const item = params?.item;
45
+ if (!item?.id) return false;
46
+ if (method === 'item/completed') return finish(session, item.id);
47
+ if (method !== 'item/started') return false;
48
+ if (item.status && !['inProgress', 'in_progress', 'running'].includes(item.status)) return false;
49
+ switch (item.type) {
50
+ case 'commandExecution': return add(session, item.id, `Running ${plainCommand(clean(item.command)) || 'command'}`, item.command);
51
+ case 'fileChange': return add(session, item.id, 'Editing files', (Array.isArray(item.changes) ? item.changes : []).map(c => c?.path ?? '').join('\n'));
52
+ case 'mcpToolCall': return add(session, item.id, `Using ${clean(item.tool).replace(/_/g, ' ') || 'tool'}`, `${item.server ?? ''} · ${item.tool ?? ''}`);
53
+ case 'webSearch': return add(session, item.id, 'Searching the web', item.query ?? '');
54
+ default: return false;
55
+ }
56
+ }