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,59 @@
1
+ #!/usr/bin/env node
2
+ // THE ONE THING AN NPM INSTALL GETS WRONG, AND IT IS A PERMISSION BIT.
3
+ //
4
+ // node-pty is how this app opens a terminal. On a Mac it does not spawn the
5
+ // shell itself: it runs a tiny bundled executable called `spawn-helper` that
6
+ // sets the terminal up and then becomes the shell. The prebuilt binaries ship
7
+ // inside the npm tarball, and a tarball does not reliably carry the executable
8
+ // bit through `npm install`.
9
+ //
10
+ // So the file arrives as `-rw-r--r--`, node-pty loads perfectly, and the first
11
+ // terminal anybody opens fails with `posix_spawnp failed`. Nothing else in the
12
+ // app is affected, which is what makes it hard to find: the inbox works, the
13
+ // agents run, and one button is broken.
14
+ //
15
+ // Measured on 2026-09-25 by installing the packed tarball into an empty folder:
16
+ // both prebuilds came out 644, against 755 in the working checkout.
17
+ //
18
+ // THIS MUST NEVER FAIL AN INSTALL. A missing node-pty, a read-only folder or a
19
+ // platform with no prebuilds are all ordinary, and none of them is a reason to
20
+ // make `npm install` exit non-zero. It says what it did and leaves quietly.
21
+
22
+ import fs from 'node:fs';
23
+ import path from 'node:path';
24
+ import { createRequire } from 'node:module';
25
+
26
+ const require = createRequire(import.meta.url);
27
+
28
+ function prebuildsDir() {
29
+ // Resolve the package rather than guessing at ../node-pty: npm may have
30
+ // hoisted it, nested it, or put it in a workspace root.
31
+ const entry = require.resolve('node-pty');
32
+ let at = path.dirname(entry);
33
+ for (let up = 0; up < 6; up += 1) {
34
+ const here = path.join(at, 'prebuilds');
35
+ if (fs.existsSync(here)) return here;
36
+ const next = path.dirname(at);
37
+ if (next === at) break;
38
+ at = next;
39
+ }
40
+ return null;
41
+ }
42
+
43
+ try {
44
+ const dir = prebuildsDir();
45
+ if (!dir) process.exit(0);
46
+ let fixed = 0;
47
+ for (const platform of fs.readdirSync(dir)) {
48
+ const helper = path.join(dir, platform, 'spawn-helper');
49
+ if (!fs.existsSync(helper)) continue;
50
+ // Only touch it when it is actually wrong, so a correct install prints
51
+ // nothing and a re-install is not noise.
52
+ if (fs.statSync(helper).mode & 0o111) continue;
53
+ fs.chmodSync(helper, 0o755);
54
+ fixed += 1;
55
+ }
56
+ if (fixed) console.log(`agentbox: made ${fixed} node-pty spawn-helper binary${fixed === 1 ? '' : 'ies'} executable, so the terminal works.`);
57
+ } catch {
58
+ // Nothing here is worth stopping an install for.
59
+ }
@@ -0,0 +1,43 @@
1
+ #!/bin/bash
2
+ # Launcher for the approvals MCP server.
3
+ #
4
+ # TWO WORLDS, AND THE PACKAGED ONE WAS BROKEN UNTIL 2026-08-21. Inside a bundle
5
+ # this file is the copy electron-builder's `asarUnpack` leaves in
6
+ # `app.asar.unpacked/scripts`, because macOS cannot exec a path inside the
7
+ # archive. The old line then walked one directory up and asked for
8
+ # `app.asar.unpacked/main/approval-prompt-server.mjs`, which has never existed:
9
+ # only `scripts/` and `worker-permissions.json` are unpacked, never `main/`.
10
+ # node threw MODULE_NOT_FOUND, the MCP server never came up, and the Claude CLI
11
+ # refuses to start a session at all when the tool named by
12
+ # --permission-prompt-tool is missing. Every agent in every packaged Agentbox died
13
+ # in about four seconds. Measured on the walk she was testing, 08-21.
14
+ #
15
+ # Agentbox's own binary IS Electron, and Electron reads straight out of app.asar,
16
+ # so the packaged branch runs the server with that instead of node. It needs no
17
+ # node on the machine, which also fixes the second half of the old line: the
18
+ # nvm path below only exists on a Mac that installed node that way.
19
+ #
20
+ # AND IT NO LONGER SPELLS THE APP'S NAME. This line said `MacOS/Astral`, and
21
+ # electron-builder names the bundle's executable after `productName`, so the
22
+ # path stopped resolving the moment the app was renamed. A shell script cannot
23
+ # import shared/product-name.mjs, so it asks the bundle it is standing inside:
24
+ # CFBundleExecutable is exactly that name, whatever it is today. The fallback is
25
+ # the one file in MacOS/, because a bundle has exactly one and a Mac without
26
+ # PlistBuddy should still start her agents.
27
+ here="$(cd "$(dirname "$0")" && pwd)"
28
+ case "$here" in
29
+ *app.asar.unpacked/scripts)
30
+ res="$(cd "$here/../.." && pwd)"
31
+ app="$(cd "$res/.." && pwd)"
32
+ exe="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist" 2>/dev/null)"
33
+ [ -x "$app/MacOS/$exe" ] || exe="$(ls "$app/MacOS" 2>/dev/null | head -1)"
34
+ exec env ELECTRON_RUN_AS_NODE=1 "$app/MacOS/$exe" "$res/app.asar/main/approval-prompt-server.mjs"
35
+ ;;
36
+ *)
37
+ # Running from source: node is nvm-only on this machine, and MCP servers
38
+ # boot from a bare login shell, so resolve it the same way the store
39
+ # launcher does.
40
+ export PATH="$HOME/.nvm/versions/node/$(ls "$HOME/.nvm/versions/node" 2>/dev/null | tail -1)/bin:$PATH"
41
+ exec node "$(cd "$here/.." && pwd)/main/approval-prompt-server.mjs"
42
+ ;;
43
+ esac
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ // WHAT `npm publish` IS ABOUT TO SEND, CHECKED AGAINST WHAT IS ON MAIN.
3
+ //
4
+ // A published version can never be replaced, only superseded, so the wrong one
5
+ // going out costs a version number and a correction rather than an undo.
6
+ //
7
+ // THE MISTAKE THIS EXISTS TO CATCH IS NOT HYPOTHETICAL. There is more than one
8
+ // checkout of this repo on the machine it is developed on: the one work happens
9
+ // in, and ~/Astral, which is where the running app lives and sits on whatever
10
+ // branch it was last built from. Both have a package.json saying agentbox-app.
11
+ // Signing in to npm is global, so `npm login` in one folder makes `npm publish`
12
+ // work in the other, and on 2026-09-25 that is exactly where the terminal was:
13
+ // ~/Astral, eight commits behind main, carrying a description that had been
14
+ // replaced. Nothing about that command would have looked wrong.
15
+ //
16
+ // So this refuses unless three things are true, and says which one is not.
17
+ // 1. Nothing uncommitted that would actually be packed. Scratch files that
18
+ // the `files` list does not reach are somebody else's business.
19
+ // 2. HEAD is on origin/main, so what ships is what was reviewed.
20
+ // 3. origin/main has been fetched recently enough to mean anything.
21
+ //
22
+ // Bypass, deliberately awkward, for a case nobody has met yet:
23
+ // AGENTBOX_PUBLISH_ANYWAY=1 npm publish
24
+
25
+ import { execFileSync } from 'node:child_process';
26
+
27
+ const git = (...args) => execFileSync('git', args, { encoding: 'utf8' }).trim();
28
+
29
+ function stop(what, fix) {
30
+ console.error(`\n Not publishing.\n\n ${what}\n ${fix}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ if (process.env.AGENTBOX_PUBLISH_ANYWAY === '1') {
35
+ console.log('agentbox: publishing without the checks, because AGENTBOX_PUBLISH_ANYWAY is set.');
36
+ process.exit(0);
37
+ }
38
+
39
+ let head, dirty;
40
+ try {
41
+ head = git('rev-parse', 'HEAD');
42
+ dirty = git('status', '--porcelain');
43
+ } catch {
44
+ stop('This folder is not a git checkout.', 'Publish from the repo.');
45
+ }
46
+
47
+ // WHICH UNCOMMITTED FILES ACTUALLY MATTER, rather than how many there are.
48
+ //
49
+ // The first version of this refused on any dirty tree at all, and that made it
50
+ // useless in the checkout it most needs to work in: a repo several agents share
51
+ // always has somebody's scratch file in it, and none of that scratch is in the
52
+ // package. Refusing over it trains you to reach for the override, and an
53
+ // override you always use is not a guard.
54
+ //
55
+ // So it asks npm what it would actually pack and refuses only where the two
56
+ // lists meet. Exact, because it is npm's own resolution of `files` rather than
57
+ // this script's guess at it. Ignored paths never appear: `git status` leaves
58
+ // them out, which is why renderer/dist, packed on every publish and committed
59
+ // on none, is correctly not a complaint.
60
+ function wouldShip(changed) {
61
+ if (!changed.length) return [];
62
+ let packed;
63
+ try {
64
+ const raw = execFileSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], {
65
+ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024,
66
+ });
67
+ packed = new Set(JSON.parse(raw.slice(raw.indexOf('['))) [0].files.map((f) => f.path));
68
+ } catch {
69
+ // If npm cannot tell us, the honest answer is the cautious one.
70
+ return changed;
71
+ }
72
+ // THE FILES THE PUBLISH ITSELF REWRITES, which would otherwise make every
73
+ // attempt poison the next one. `prepack` runs `npm run build`, and the build
74
+ // reads the installed Claude Code and writes what it found into
75
+ // shared/*.generated.*. So a failed publish leaves those modified, and the
76
+ // next publish refuses because of dirt the last publish made. Measured
77
+ // 2026-09-28: one failed attempt left claude-models.generated.mjs changed by
78
+ // a version number, and that alone blocked the retry.
79
+ //
80
+ // They are safe to ignore here BECAUSE the build rewrites them anyway. What
81
+ // ships is whatever the build produces on the machine publishing, every time,
82
+ // committed or not, so a difference in the working tree changes nothing.
83
+ return changed.filter((f) => packed.has(f) && !/\.generated\./.test(f));
84
+ }
85
+
86
+ if (dirty) {
87
+ // --untracked-files=all, or a whole new directory arrives as one line and a
88
+ // file inside it is never compared against anything.
89
+ const changed = git('status', '--porcelain', '--untracked-files=all')
90
+ .split('\n')
91
+ .map((line) => line.slice(3).trim())
92
+ .filter(Boolean);
93
+ const bad = wouldShip(changed);
94
+ if (bad.length) {
95
+ stop(
96
+ `${bad.length} uncommitted file${bad.length === 1 ? '' : 's'} would go into the package:\n ${bad.slice(0, 8).join('\n ')}`,
97
+ 'Commit them, or move them out, or the package holds something no commit does.',
98
+ );
99
+ }
100
+ }
101
+
102
+ try {
103
+ execFileSync('git', ['fetch', 'origin', '--quiet'], { stdio: 'ignore' });
104
+ } catch {
105
+ // Offline is not a reason to refuse. The ancestor check below still runs
106
+ // against whatever origin/main was last known to be.
107
+ }
108
+
109
+ let onMain = false;
110
+ try {
111
+ execFileSync('git', ['merge-base', '--is-ancestor', head, 'origin/main'], { stdio: 'ignore' });
112
+ onMain = true;
113
+ } catch {}
114
+
115
+ if (!onMain) {
116
+ const branch = (() => { try { return git('rev-parse', '--abbrev-ref', 'HEAD'); } catch { return '?'; } })();
117
+ stop(
118
+ `This checkout is on ${branch} at ${head.slice(0, 8)}, which is not on origin/main.`,
119
+ 'Publish from a checkout whose commit has been merged and pushed.',
120
+ );
121
+ }
122
+
123
+ const behind = (() => {
124
+ try { return Number(git('rev-list', '--count', `${head}..origin/main`)); } catch { return 0; }
125
+ })();
126
+ if (behind > 0) {
127
+ stop(
128
+ `This checkout is ${behind} commit${behind === 1 ? '' : 's'} behind origin/main.`,
129
+ 'Pull, then publish, or you will send an older build than the one on main.',
130
+ );
131
+ }
132
+
133
+ console.log(`agentbox: publishing ${head.slice(0, 8)}, which is the tip of origin/main.`);
@@ -0,0 +1,13 @@
1
+ // Types for shared/agent-import.mjs, which is plain ESM so that main (Node) and
2
+ // the renderer (Vite) can share one copy of the words.
3
+ //
4
+ // Only the two the card itself calls are declared. The rest of that file writes
5
+ // work items, which is main's half and never the renderer's, and declaring them
6
+ // here would put a store write one import away from a component.
7
+
8
+ /** "today", "yesterday", "on Monday", or nothing when the moment cannot be
9
+ * placed inside the week the card covers. */
10
+ export function whenWords(when: number | null | undefined, now?: number): string;
11
+
12
+ /** "in a terminal" or "in the Claude Code desktop app". */
13
+ export function startedIn(source: string | null | undefined): string;
@@ -0,0 +1,252 @@
1
+ // WHAT AN IMPORTED AGENT LOOKS LIKE IN THE INBOX.
2
+ //
3
+ // So the import stops being a note to itself and becomes rows. One per agent
4
+ // the person ticked, in the project the walk just made, each one that agent
5
+ // asking to be put to work.
6
+ //
7
+ // WHY A ROW HAS TO ASK SOMETHING. An imported agent needs no carrying across:
8
+ // Claude Code loads its own agent folders at the start of every session, which
9
+ // was measured on 2026-08-23 and is why the old import had nothing to do. What
10
+ // was actually missing is that Agentbox could not put one to work. A row that
11
+ // only announced an agent would be the same empty promise in a louder place,
12
+ // so the row asks for the agent's first job and the reply is what runs it.
13
+ //
14
+ // Pure on purpose: the files are read in main/agent-files.mjs and the rows are
15
+ // written in main/store.mjs. Everything here is words, so the words are tested
16
+ // rather than eyeballed on a card nobody can screenshot twice.
17
+
18
+ /**
19
+ * The label every one of these rows carries, so a second walk through the
20
+ * first run can tell which agents already have a row and file nothing twice. */
21
+ import { NAME, Name } from '../shared/product-name.mjs';
22
+ export const IMPORT_LABEL = 'agent-import';
23
+
24
+ /**
25
+ * And the one that says WHICH agent, for the same reason. Names only, the way
26
+ * `projectAgents` stores them: the file is the author's and never moves, so a
27
+ * name that has gone stale costs nothing. */
28
+ export const agentLabel = (name) => `agent:${String(name ?? '').trim()}`;
29
+
30
+ /**
31
+ * `/Users/x/.claude/agents/a.md` as `~/.claude/agents/a.md`. A path with a
32
+ * stranger's home folder in it is a path they read twice to find their own
33
+ * name in it. */
34
+ export function shortenHome(p, home) {
35
+ const full = String(p ?? '');
36
+ const h = String(home ?? '').replace(/\/+$/, '');
37
+ return h && full.startsWith(h + '/') ? `~${full.slice(h.length)}` : full;
38
+ }
39
+
40
+ /**
41
+ * ONE ROW'S WORDS.
42
+ *
43
+ * The first line is the ask and nothing else, because the inbox row shows the
44
+ * opening of the body and the reading pane leads with it. The agent's own
45
+ * description follows when it has one; a file with nothing but a heading in it
46
+ * gets no second paragraph rather than a blank one.
47
+ *
48
+ * The last paragraph is the honest half. Agentbox does not copy an agent file,
49
+ * does not move one, and did not make it work: it says where the file is and
50
+ * which of Claude Code's two scopes it is in, in the same two words the finish
51
+ * card uses.
52
+ */
53
+ export function agentImportRow(agent, { projectName = null, home = '' } = {}) {
54
+ const title = String(agent?.title || agent?.name || '').trim();
55
+ const name = String(agent?.name || '').trim();
56
+ const line = String(agent?.line || '').trim();
57
+ const where = shortenHome(agent?.path, home);
58
+ const inProject = projectName ? ` here in ${projectName}` : '';
59
+ const scopeLine = agent?.scope === 'project'
60
+ ? 'It works in this project only.'
61
+ : 'It works in every project on this Mac.';
62
+ const body = [
63
+ `**Tell ${title} what to do first, in one sentence.**`,
64
+ ...(line ? [line] : []),
65
+ `Reply with the job and ${NAME} runs it${inProject}, putting your ${name} agent on the work and answering you here.`,
66
+ `${scopeLine} The file stays exactly where you wrote it, at ${where}, and ${NAME} never moves or copies one.`,
67
+ ].join('\n\n');
68
+ return {
69
+ title: `${title} is ready. Give it its first job.`,
70
+ body,
71
+ kind: 'question',
72
+ priority: 3,
73
+ labels: [IMPORT_LABEL, agentLabel(name)],
74
+ };
75
+ }
76
+
77
+ /**
78
+ * WHICH OF THE TICKED AGENTS STILL NEED A ROW.
79
+ *
80
+ * Walking the first run again is a thing the app offers from ⌘K, and it opens
81
+ * on what was taken last time with everything still ticked. Without this, the
82
+ * second walk files a second copy of every row, which is the one failure an
83
+ * inbox cannot absorb.
84
+ *
85
+ * Matched on the agent's own name rather than on the row's title, so renaming
86
+ * an agent's front matter files a new row and rewording ours does not.
87
+ *
88
+ * ONE ROW PER NAME, EVEN WHEN TWO FILES CLAIM IT. Claude Code lets a project
89
+ * folder shadow a home folder agent of the same name, and the project's copy
90
+ * is the one that runs, so that is the one the row describes.
91
+ */
92
+ export function agentsNeedingRows(chosen, existingItems = []) {
93
+ const already = new Set();
94
+ for (const item of existingItems) {
95
+ for (const label of item?.labels ?? []) {
96
+ if (typeof label === 'string' && label.startsWith('agent:')) already.add(label.slice(6));
97
+ }
98
+ }
99
+ const byName = new Map();
100
+ for (const a of chosen ?? []) {
101
+ const name = String(a?.name ?? '').trim();
102
+ if (!name || already.has(name)) continue;
103
+ const seen = byName.get(name);
104
+ if (!seen || (a.scope === 'project' && seen.scope !== 'project')) byName.set(name, a);
105
+ }
106
+ return [...byName.values()];
107
+ }
108
+
109
+ /* ------------------------- AND THE THREADS SHE MEANT ----------------------- */
110
+ // She was offered three things a picked thread could do and she picked the
111
+ // plainest one. It does NOT resume the conversation, and that is the half these
112
+ // words have to carry, because a row named after a conversation she had on
113
+ // Friday is a row anybody would assume was carrying it on. So the body says, in
114
+ // her vocabulary, that this starts fresh.
115
+ //
116
+ // Everything else is the agent import's shape, for the same reasons: one row per
117
+ // thing she ticked, a label so a second press files nothing twice, and a
118
+ // question so the supervisor waits for her rather than starting on its own.
119
+
120
+ /** The label every imported thread carries. */
121
+ export const THREAD_LABEL = 'thread-import';
122
+
123
+ /**
124
+ * And the one that says WHICH thread. The id is Claude Code's own session id,
125
+ * which is stable across renames and is what the transcript is filed under. */
126
+ export const threadLabel = (id) => `thread:${String(id ?? '').trim()}`;
127
+
128
+ /**
129
+ * WHEN SHE HAD IT, in the words a person uses about this week.
130
+ *
131
+ * The window is seven days (`RECENT_DAYS`, main/agent-sessions.mjs), so a day
132
+ * name covers all of it and a date never has to appear. Anything the arithmetic
133
+ * cannot place says nothing rather than guessing. */
134
+ export function whenWords(when, now = Date.now()) {
135
+ const then = new Date(Number(when) || 0);
136
+ if (!Number.isFinite(then.getTime()) || !when) return '';
137
+ const midnight = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
138
+ const days = Math.round((midnight(new Date(now)) - midnight(then)) / 86400000);
139
+ if (days <= 0) return 'today';
140
+ if (days === 1) return 'yesterday';
141
+ if (days < 7) return `on ${then.toLocaleDateString('en-US', { weekday: 'long' })}`;
142
+ return '';
143
+ }
144
+
145
+ /** Where she was when she started it, in the two places she named. */
146
+ export function startedIn(source) {
147
+ if (source === 'codex') return 'in Codex';
148
+ return source === 'desktop' ? 'in the Claude Code desktop app' : 'in a terminal';
149
+ }
150
+
151
+ /**
152
+ * ONE IMPORTED THREAD'S WORDS.
153
+ *
154
+ * The title is the thread's own, which is the first thing she typed into it, so
155
+ * the row in her inbox is called what she would call it. Ours is not added in
156
+ * front of it: a list of rows all opening with the same three words is a list
157
+ * she has to read past to find which is which.
158
+ *
159
+ * The last paragraph is the honest one. Agentbox read the top of the transcript
160
+ * to find this name and nothing else, and the reply starts a fresh session. A
161
+ * row that let her believe otherwise would be the same empty promise the agent
162
+ * import had before 08-23, in a new place. */
163
+ export function threadImportRow(thread, { projectName = null, now = Date.now() } = {}) {
164
+ const title = String(thread?.title || '').trim();
165
+ const where = String(thread?.short || thread?.folder || '').trim();
166
+ const when = whenWords(thread?.when, now);
167
+ const started = [
168
+ 'You started this',
169
+ startedIn(thread?.source),
170
+ when,
171
+ where ? `in ${where}` : '',
172
+ ].filter(Boolean).join(' ');
173
+ const inProject = projectName ? ` here in ${projectName}` : '';
174
+ const body = [
175
+ '**Say what you want done next in this thread.**',
176
+ `${started}.`,
177
+ `Reply with the job and ${NAME} runs it${inProject}, answering you here.`,
178
+ `${Name} has not read the conversation, only the line above that names it, and replying starts a fresh session rather than carrying the old one on. So say what you need in the reply.`,
179
+ ].join('\n\n');
180
+ return {
181
+ title,
182
+ body,
183
+ kind: 'question',
184
+ priority: 3,
185
+ labels: [THREAD_LABEL, threadLabel(thread?.id)],
186
+ };
187
+ }
188
+
189
+ /**
190
+ * WHICH OF THE TICKED THREADS STILL NEED A ROW. The card opens on a Mac whose
191
+ * threads it has offered before, so pressing twice is ordinary and a second row
192
+ * for the same conversation is the one failure an inbox cannot absorb. */
193
+ export function threadsNeedingRows(chosen, existingItems = []) {
194
+ const already = new Set();
195
+ for (const item of existingItems) {
196
+ for (const label of item?.labels ?? []) {
197
+ if (typeof label === 'string' && label.startsWith('thread:')) already.add(label.slice(7));
198
+ }
199
+ }
200
+ const byId = new Map();
201
+ for (const t of chosen ?? []) {
202
+ const id = String(t?.id ?? '').trim();
203
+ if (!id || already.has(id) || byId.has(id)) continue;
204
+ if (!String(t?.title ?? '').trim()) continue;
205
+ byId.set(id, t);
206
+ }
207
+ return [...byId.values()];
208
+ }
209
+
210
+ /**
211
+ * WHICH AGENT A ROW IS ABOUT, or null for every other row in the inbox.
212
+ *
213
+ * The worker's brief reads this. Without it the row's body is the only thing
214
+ * saying whose job this is, and a body is prose: it tells a session that an
215
+ * agent exists, not that it is the one meant to do the work. The whole of the
216
+ * fix is that the reply reaches the agent, so the instruction is explicit
217
+ * (`importedAgentBrief`) rather than inferred from a sentence on a card. */
218
+ export function importedAgentName(item) {
219
+ for (const label of item?.labels ?? []) {
220
+ if (typeof label === 'string' && label.startsWith('agent:')) {
221
+ const name = label.slice(6).trim();
222
+ if (name) return name;
223
+ }
224
+ }
225
+ return null;
226
+ }
227
+
228
+ /**
229
+ * AND WHAT THE BRIEF SAYS ABOUT IT.
230
+ *
231
+ * The last sentence is the honest one and it is not decoration. Claude Code
232
+ * loads its own agent folders every session, so the subagent is normally right
233
+ * there — but it is loaded from the person's own disk, under whichever profile
234
+ * the worker is billing to, and Agentbox cannot promise that from here. A
235
+ * session that quietly does the work itself would leave them believing their
236
+ * agent ran when it did not, which is the same empty promise this row exists
237
+ * to end. */
238
+ export function importedAgentBrief(name) {
239
+ const n = String(name ?? '').trim();
240
+ if (!n) return '';
241
+ return [
242
+ '# This row belongs to one of their own Claude Code agents',
243
+ '',
244
+ `They imported \`${n}\` on their first run, and this row is that agent asking for its first job.`,
245
+ 'Their answer above is the job.',
246
+ '',
247
+ `Do it by dispatching the \`${n}\` subagent and giving it that job, rather than doing the work yourself,`,
248
+ 'then report on this row what it did. If that subagent is not available to you in this session, do not',
249
+ 'quietly do the work as yourself: say so plainly in your result, because they are expecting their own',
250
+ 'agent to have run.',
251
+ ].join('\n');
252
+ }