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,28 @@
1
+ // Which Library folder an artifact lives in. ONE rule, because main and the
2
+ // renderer both answer this question and they were answering it differently.
3
+ //
4
+ // A folder-backed artifact lives at creations/<...>/<slug> on disk, and
5
+ // `creations/` is storage, not a place the founder put anything: left in, it
6
+ // surfaced as a top-level folder called "Creations" holding everything
7
+ // folder-backed, which reads as nonsense when every artifact is one (founder
8
+ // report, 2026-07-24). So the storage root is stripped and whatever is left is
9
+ // the real folder.
10
+ //
11
+ // Main used to skip that strip, which is how the two views disagreed in the
12
+ // worst possible direction: the founder saw nine apps and canvases sitting
13
+ // loose at the top of her Library, while the agent's own list_library showed
14
+ // them tidily filed under "creations/" and therefore never noticed anything to
15
+ // organize.
16
+ export const STORAGE_ROOT = 'creations';
17
+
18
+ /**
19
+ * @param {{ path?: string, dir?: string }} envelope
20
+ * @returns {string} 'designs/round-1', or '' for something at the top level
21
+ */
22
+ export function libraryFolderOf(envelope) {
23
+ const p = envelope?.path ?? envelope?.dir ?? '';
24
+ const cut = String(p).lastIndexOf('/');
25
+ const folder = cut <= 0 ? '' : String(p).slice(0, cut);
26
+ if (folder === STORAGE_ROOT) return '';
27
+ return folder.startsWith(`${STORAGE_ROOT}/`) ? folder.slice(STORAGE_ROOT.length + 1) : folder;
28
+ }
@@ -0,0 +1,120 @@
1
+ // WHEN AGENTBOX IS ALLOWED TO INTERRUPT HER, and what the banner says when it is.
2
+ //
3
+ // Both halves of that sentence are rules, and the second one is the harder one:
4
+ // a notification that fires while she is looking at the row it is about is
5
+ // worse than no notification at all, because it costs her the flow the whole
6
+ // product is built to protect. So the gate is closed by default and opens only
7
+ // when she is provably somewhere else.
8
+ //
9
+ // This is the only copy of the rule. main/notify.mjs is the live wiring around
10
+ // it (the window's focus, the banner's lifetime); everything decidable is here,
11
+ // as plain functions over plain values, because that is the half worth pinning
12
+ // with tests.
13
+
14
+ // HOW LONG A STILL KEYBOARD MEANS SHE HAS GONE, and it is deliberately long.
15
+ // The window can hold focus while she is not at the machine at all: the lid is
16
+ // open, Agentbox is the front app, and she is in another room. That is squarely
17
+ // "in a diff app" as she meant it, so the banner should fire.
18
+ //
19
+ // Five minutes rather than one, because the failure modes are not symmetric.
20
+ // Firing late costs her a few minutes of not knowing. Firing while she is
21
+ // reading a long message with her hands off the keyboard is exactly the
22
+ // interruption she asked us to stop, and she would be right to read it as the
23
+ // feature not working. Reading one card for five unbroken minutes is rare;
24
+ // walking away for five minutes is not.
25
+ export const AWAY_AFTER_MS = 5 * 60 * 1000;
26
+
27
+ // She is at the app, so say nothing. Focus alone is not enough (see above) and
28
+ // idleness alone is not either: a still keyboard while she is reading Slack is
29
+ // not a reason to stay quiet.
30
+ export function atTheApp({ focused = false, idleMs = 0 } = {}) {
31
+ return !!focused && idleMs < AWAY_AFTER_MS;
32
+ }
33
+
34
+ // A title long enough to be a paragraph is normal here; hers run past a
35
+ // hundred characters. macOS will hard-truncate mid-word, so cut on a space and
36
+ // say that it was cut.
37
+ export function clip(text, max = 120) {
38
+ const s = String(text ?? '').replace(/\s+/g, ' ').trim();
39
+ if (s.length <= max) return s;
40
+ const cut = s.slice(0, max);
41
+ const space = cut.lastIndexOf(' ');
42
+ return `${(space > max * 0.6 ? cut.slice(0, space) : cut).replace(/[.,;:]$/, '')}…`;
43
+ }
44
+
45
+ // ONE BANNER FOR THE WHOLE TIME SHE IS AWAY. Not one per arrival, and not one
46
+ // per rewrite either, which is what this file used to do.
47
+ //
48
+ // The version before this one already refused to STACK banners: it took the old
49
+ // one down and put a fresh one up. That is one banner on screen, and it is
50
+ // still twenty interruptions, because on macOS there is no way to edit a
51
+ // notification that is already up. Rewriting it means showing another one, with
52
+ // another sound. A forty minute meeting with an agent finishing every two
53
+ // minutes was twenty chimes. That is the thing she is naming, and the count in
54
+ // the old wording is what made it feel necessary to keep re-firing: a number
55
+ // goes stale the second the next agent lands.
56
+ //
57
+ // So: she is told once, in words that stay true however many arrive after, and
58
+ // the rest is waiting on the rows when she comes back. `seen` in
59
+ // main/notify.mjs resets this the moment she has the window, so the next time
60
+ // she walks away she can be told again.
61
+ //
62
+ // THE ONE EXCEPTION, and it is here because an approval card has a clock on it.
63
+ // A finished agent waits as long as it takes. An approval is a worker frozen
64
+ // mid-run with her as the only way through, and it is DENIED AUTOMATICALLY
65
+ // after fifteen minutes (TIMEOUT_MS, main/approval-prompt-server.mjs). If the
66
+ // first banner of the stretch was a finished agent, an approval landing twenty
67
+ // minutes later would die unheard under a strict one-and-done rule. So an
68
+ // approval gets to speak once more, and that is the cap: at most two in one
69
+ // stretch away, never more.
70
+ export function shouldSpeak({ spoke = false, spokeAsk = false, waiting = [] } = {}) {
71
+ const items = (waiting ?? []).filter(Boolean);
72
+ if (!items.length) return false;
73
+ if (!spoke) return true;
74
+ if (spokeAsk) return false;
75
+ return items.some((i) => i.kind === 'approval');
76
+ }
77
+
78
+ // WHAT THE ONE BANNER SAYS. No number in it, for two reasons that point the
79
+ // same way. The design law rejects counts on a glance surface (STATE.md), and
80
+ // under the rule above a count would be a lie within a minute: it can only ever
81
+ // describe the first second and a half of a stretch that may run for hours.
82
+ //
83
+ // TWO LINES, NEVER THREE, AND NO PROJECT NAME ANYWHERE IN IT.
84
+ //
85
+ // The subtitle field went first, earlier the same day, because she read a lone
86
+ // project name stacked under the title as the title bleeding over. Putting the
87
+ // project into the title instead only moved the problem: a title is the one
88
+ // line macOS will not wrap, so a project called something long pushes "An agent
89
+ // is ready" out of the banner and the fact she needed is the part that gets
90
+ // cut. Both fields are hers to fill with anything, and neither of them is a
91
+ // safe place for a string we do not control the length of.
92
+ //
93
+ // So the title is a fixed sentence and the body is the row's own name, which is
94
+ // where the project is recognisable anyway. Plural without a number covers the
95
+ // rest.
96
+ //
97
+ // `waiting` is everything unseen, oldest first. Returns null when there is
98
+ // nothing to say.
99
+ export function banner(waiting) {
100
+ const items = (waiting ?? []).filter(Boolean);
101
+ if (!items.length) return null;
102
+
103
+ // A frozen worker outranks a finished one, so when both are unseen the
104
+ // banner speaks for the one with a clock on it.
105
+ const asks = items.filter((i) => i.kind === 'approval');
106
+ const lead = asks.length ? asks[0] : items[items.length - 1];
107
+ const many = items.length > 1;
108
+
109
+ const title = asks.length
110
+ ? (many ? 'Agents are waiting on you' : 'An agent is waiting on you')
111
+ : (many ? 'Agents are ready' : 'An agent is ready');
112
+
113
+ return {
114
+ title,
115
+ body: clip(lead.title),
116
+ // What a click opens. An approval card is already on screen the moment the
117
+ // window comes forward, so that one only needs the window.
118
+ open: lead.kind === 'approval' ? null : (lead.id ?? null),
119
+ };
120
+ }
@@ -0,0 +1,6 @@
1
+ export const NAME: string;
2
+ export const WAS: readonly string[];
3
+ export const Name: string;
4
+ export const nameSlug: string;
5
+ export const possessive: string;
6
+ export const Possessive: string;
@@ -0,0 +1,144 @@
1
+ // THE ONE PLACE THIS APP IS NAMED. Change the two lines under NAME and every
2
+ // screen, every notification, every agent brief and the Dock follow.
3
+ //
4
+ // She is right about the frequency. This app has been Zero, then Astral, then
5
+ // the app, and is agentbox now, and each of the first three renames was a
6
+ // hand-run search and replace over about two hundred literals
7
+ // (scripts/rename-to-powerup.mjs is the third one, kept as history). A sweep
8
+ // cannot be run ahead of time, so between renames the old name sat in the code
9
+ // waiting to be missed, and one of them was: the screenshot on her row shows
10
+ // "Import into the app?" two names later.
11
+ //
12
+ // HOW TO RENAME IT NEXT TIME, the whole procedure:
13
+ //
14
+ // 1. Change NAME below to the new name, spelled exactly as it should read in
15
+ // the MIDDLE of a sentence.
16
+ // 2. Add the name it had before to WAS, at the front of the list.
17
+ // 3. Run `node scripts/product-name.mjs --write`. That is what carries the
18
+ // name into package.json, which Electron reads before any of our code
19
+ // runs and so cannot import this file.
20
+ //
21
+ // Nothing else. `tests/the-app-is-named-in-one-place.test.mjs` fails if step
22
+ // 3 is skipped, and it fails if a new literal of the name is typed into the
23
+ // code by hand instead of read from here.
24
+ //
25
+ // CAPITALISATION IS A RULE HERE, NOT A SPELLING.
26
+ //
27
+ // So NAME is the mid-sentence spelling and `Name` is that same word with its
28
+ // first letter raised. A name that is already an ordinary proper noun, as two
29
+ // of the names in WAS are, comes out identical from both, so the rule costs
30
+ // nothing when it does not apply, and a lowercase name gets it right without
31
+ // anyone having to remember.
32
+ // Pick the one the position calls for. Sentence start, a heading, a title, a
33
+ // menu item and a button label take `Name`; anywhere else in a sentence takes
34
+ // `name`.
35
+
36
+ /** The name, spelled as it reads in the middle of a sentence. */
37
+ export const NAME = 'agentbox';
38
+
39
+ /**
40
+ * Every name this app has had, newest first. main/main.mjs walks this to find
41
+ * the data folder an existing install already writes to, because Chromium
42
+ * names that folder after the app and a rename would otherwise hand her an
43
+ * empty one and lose her theme, her zoom and her project order. HISTORY IS
44
+ * NEVER REWRITTEN: add to the front, never edit an entry. */
45
+ export const WAS = Object.freeze(['Powerup', 'Astral', 'Zero']);
46
+
47
+ /** The name at the start of a sentence, in a heading, or on a button. */
48
+ export const Name = NAME.charAt(0).toUpperCase() + NAME.slice(1);
49
+
50
+ /** One name as a slug, so the current one and the old ones are made the same way. */
51
+ const slugOf = (name) => String(name).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
52
+
53
+ /**
54
+ * The name lowercased throughout, for a slug, a folder or a log prefix.
55
+ * NOT for anything she reads. */
56
+ export const nameSlug = slugOf(NAME);
57
+
58
+ /**
59
+ * EVERY SLUG THIS APP HAS ANSWERED TO, current first.
60
+ *
61
+ * The same argument as `envNames` below, one layer up. A slug of ours is not
62
+ * only a folder name: it is the name of the MCP server that IS the store, so it
63
+ * is written into a `--allowedTools` grant in her zero.config.json, and into the
64
+ * config of every Codex thread that has ever run. Both of those outlive a
65
+ * rename, and neither is ours to rewrite: the grant is her file, and a thread
66
+ * started last week is a record.
67
+ *
68
+ * So a rename cannot be allowed to mean "the store stops being the store". The
69
+ * places that ask "is this the store server?" ask it through here, the current
70
+ * name wins, and a name we used to have still answers.
71
+ *
72
+ * THIS IS NOT THE OLD `LEGACY_STORE_SERVERS`. That list held another PRODUCT's
73
+ * name, hardcoded, and was deleted for exactly that reason. Nothing is
74
+ * hardcoded here: this is `WAS`, which the app already keeps for its data
75
+ * folder and its env vars, read one more way.
76
+ */
77
+ export const WAS_SLUGS = Object.freeze(WAS.map(slugOf));
78
+
79
+ /** The current slug and every slug it has had, current first. */
80
+ export const nameSlugs = Object.freeze([nameSlug, ...WAS_SLUGS]);
81
+
82
+ /** True when `slug` is this app under its current name or any name it has had. */
83
+ export const isOurSlug = (slug) => typeof slug === 'string' && nameSlugs.includes(slug.trim());
84
+
85
+ /**
86
+ * THE NAME AS AN ENVIRONMENT VARIABLE PREFIX, and every prefix it has had.
87
+ *
88
+ * The app's own env vars carried the name typed out: `ASTRAL_HOME` was still
89
+ * the store root two renames after the app stopped being the app, which is the
90
+ * same leak this whole module exists to close and the one an agent grepping the
91
+ * repo hits first. `envName('HOME')` is what code asks for now.
92
+ *
93
+ * `readEnv` FALLS BACK THROUGH THE OLD NAMES, newest first, for the same reason
94
+ * `WAS` exists at all: a shell, a launchd job or a running process started
95
+ * before the rename still exports the old one, and an app that cannot find its
96
+ * own store root opens empty.
97
+ */
98
+ export const ENV_PREFIX = NAME.toUpperCase().replace(/[^A-Z0-9]+/g, '_');
99
+ export const WAS_ENV_PREFIXES = Object.freeze(WAS.map((was) => was.toUpperCase().replace(/[^A-Z0-9]+/g, '_')));
100
+
101
+ /** One env var of ours, by its suffix. `envName('HOME')`. */
102
+ export const envName = (suffix) => `${ENV_PREFIX}_${suffix}`;
103
+
104
+ /** Every spelling of one env var of ours, current name first. */
105
+ export const envNames = (suffix) => [ENV_PREFIX, ...WAS_ENV_PREFIXES].map((p) => `${p}_${suffix}`);
106
+
107
+ /**
108
+ * The value of one env var of ours, under the current name or any name this app
109
+ * has had. Empty and whitespace-only count as unset, because an exported-but-
110
+ * blank variable is how a shell says nothing rather than how it says "".
111
+ */
112
+ export function readEnv(suffix, env = process.env) {
113
+ for (const key of envNames(suffix)) {
114
+ const value = env?.[key];
115
+ if (typeof value === 'string' && value.trim()) return value;
116
+ }
117
+ return undefined;
118
+ }
119
+
120
+ /**
121
+ * The possessive, because "agentbox's" and "Atlas'" are not the same rule and
122
+ * a template that hardcodes 's is wrong the day the name ends in one. */
123
+ export const possessive = `${NAME}${/s$/i.test(NAME) ? "'" : "'s"}`;
124
+
125
+ /** The possessive at the start of a sentence. */
126
+ export const Possessive = `${Name}${/s$/i.test(NAME) ? "'" : "'s"}`;
127
+
128
+ /**
129
+ * THE NAME INTO TEXT THAT IS NOT CODE: the agent briefs in `briefs/`, which are
130
+ * markdown files a session reads and, for three of the four, files SHE can edit
131
+ * in Settings. They cannot import anything, so they carry `{{name}}` and
132
+ * `{{Name}}` and this fills them in.
133
+ *
134
+ * FILLED WHERE THE BRIEF IS USED, NOT WHERE IT IS READ FOR EDITING. The editor
135
+ * in Settings reads the same files, and substituting on the way in would show
136
+ * her the filled text and then save it back with the name baked into her own
137
+ * copy, which is the one thing this whole change exists to prevent. So
138
+ * `buildBrief` calls this and the settings editor does not.
139
+ */
140
+ export function fillName(text) {
141
+ return String(text ?? '')
142
+ .replaceAll('{{Name}}', Name)
143
+ .replaceAll('{{name}}', NAME);
144
+ }
@@ -0,0 +1,22 @@
1
+ // Types for shared/project-folder-check.mjs, which is plain ESM so that main
2
+ // (Node) and the renderer (Vite) can share one copy of the rule.
3
+
4
+ export interface FolderVerdict {
5
+ ok: boolean;
6
+ /** Why it was turned down. For tests and the log; never put on a screen. */
7
+ reason?: 'home' | 'guarded' | 'system' | 'volume';
8
+ /** The sentence to show as it stands, when `ok` is false. */
9
+ say?: string;
10
+ }
11
+
12
+ export const GUARDED: string[];
13
+
14
+ export function checkProjectFolder(
15
+ folder: string | null | undefined,
16
+ opts?: { home?: string },
17
+ ): FolderVerdict;
18
+
19
+ export function proposeParent(
20
+ existing?: string[],
21
+ opts?: { home?: string },
22
+ ): string;
@@ -0,0 +1,152 @@
1
+ // THE FOLDERS A PROJECT MAY NOT BE, AND WHY macOS ASKS ABOUT MUSIC.
2
+ //
3
+ // That sentence is macOS, not us. Desktop, Documents, Downloads, Music,
4
+ // Pictures, Movies and everything under ~/Library are guarded by the system's
5
+ // privacy layer, so the FIRST time any process reads one of them the system
6
+ // throws up a panel with our name on it. Agentbox spawns Claude Code with the
7
+ // project folder as its working directory, and a worker that starts in the home
8
+ // folder will list it within its first few tool calls. One folder choice is
9
+ // therefore the difference between no panels at all and a run of them naming
10
+ // every private corner of somebody's Mac, on the second screen they ever see.
11
+ //
12
+ // Nothing stopped that choice. `zero:choose-folder` handed back whatever the
13
+ // Mac's own picker returned, and the picker opens on the home folder, so "just
14
+ // press Open" was a live path to exactly this.
15
+ //
16
+ // SO THIS IS A REFUSAL, NOT A WARNING. A warning on this screen is a question
17
+ // somebody with no way to answer it has to answer anyway. The folder is turned
18
+ // down and the sentence says which folder to pick instead.
19
+ //
20
+ // It is deliberately SHORT. The only folders refused are the ones that are not
21
+ // projects by construction: the home folder itself, the system's own top-level
22
+ // directories, and the seven guarded folders IN THEMSELVES. A project INSIDE
23
+ // any of them is fine and is not asked about — `~/Documents/my-app` is where
24
+ // plenty of people keep their code, it costs one panel the first time and it is
25
+ // genuinely their project. What is refused is pointing at `~/Documents` whole.
26
+ //
27
+ // Pure, so both sides of the bridge and the tests can use one copy: the
28
+ // renderer checks as she picks, and main checks again before it writes.
29
+
30
+ /**
31
+ * The guarded folders, by their name directly under the home folder. The
32
+ * system asks about each of these separately, which is why the panels arrive
33
+ * as a run rather than as one. `Library` is the one that reads as "every app":
34
+ * it holds one directory per application under Application Support. */
35
+ export const GUARDED = [
36
+ 'Desktop', 'Documents', 'Downloads', 'Movies', 'Music', 'Pictures', 'Library', 'Public',
37
+ ];
38
+
39
+ /** Top-level directories that are the Mac and not anybody's work. */
40
+ const SYSTEM = [
41
+ '/', '/Applications', '/Library', '/System', '/Users', '/Volumes',
42
+ '/private', '/tmp', '/var', '/etc', '/usr', '/bin', '/sbin', '/opt', '/net', '/cores',
43
+ ];
44
+
45
+ /**
46
+ * Trailing slashes off, and `~` left alone: the renderer does not know whose
47
+ * home this is and hands paths through exactly as it got them. */
48
+ function tidy(p) {
49
+ const s = String(p ?? '').trim();
50
+ if (!s) return '';
51
+ return s.length > 1 ? s.replace(/\/+$/, '') : s;
52
+ }
53
+
54
+ function underHome(p, home) {
55
+ const h = tidy(home);
56
+ if (!h) return null;
57
+ if (p === h) return '';
58
+ return p.startsWith(`${h}/`) ? p.slice(h.length + 1) : null;
59
+ }
60
+
61
+ /**
62
+ * WHETHER THIS FOLDER CAN BE A PROJECT, and the sentence to show when it
63
+ * cannot. Returns `{ ok: true }` or `{ ok: false, reason, say }`.
64
+ *
65
+ * `say` is written to be shown as it stands, on the setup screen and on the
66
+ * New Project card, so it names the folder that was picked and says what to
67
+ * pick instead. `reason` is for tests and for the note in the log; nothing
68
+ * puts it on a screen.
69
+ */
70
+ export function checkProjectFolder(folder, { home = '' } = {}) {
71
+ const p = tidy(folder);
72
+ if (!p) return { ok: true };
73
+
74
+ // `~` and `~/` are the home folder written the way our own screens write it.
75
+ const rel = p === '~' || p === '~/' ? '' : (p.startsWith('~/') ? p.slice(2) : underHome(p, home));
76
+
77
+ if (rel === '') {
78
+ return {
79
+ ok: false,
80
+ reason: 'home',
81
+ say: 'That is your whole home folder. Agents would work across everything on this Mac, and macOS will ask you about Downloads, Music and every app you have. Pick the folder your project’s code is in.',
82
+ };
83
+ }
84
+
85
+ if (rel !== null && GUARDED.includes(rel)) {
86
+ return {
87
+ ok: false,
88
+ reason: 'guarded',
89
+ say: `${rel} holds a lot more than one project, and macOS guards it, so choosing it means a run of permission panels. Pick the folder inside it that your project’s code is in.`,
90
+ };
91
+ }
92
+
93
+ if (SYSTEM.includes(p)) {
94
+ return {
95
+ ok: false,
96
+ reason: 'system',
97
+ say: `${p} is part of macOS rather than one of your projects. Pick the folder your project’s code is in.`,
98
+ };
99
+ }
100
+
101
+ // A volume root: /Volumes/Whatever with nothing after it.
102
+ if (/^\/Volumes\/[^/]+$/.test(p)) {
103
+ return {
104
+ ok: false,
105
+ reason: 'volume',
106
+ say: `${p} is a whole disk rather than one project. Pick the folder your project’s code is in.`,
107
+ };
108
+ }
109
+
110
+ return { ok: true };
111
+ }
112
+
113
+ /**
114
+ * WHERE A NEW PROJECT'S FOLDER IS PROPOSED, when nobody has pointed at one.
115
+ *
116
+ * This used to be `~/Desktop/dev/<slug>` flat, which is one developer's own
117
+ * filing system shipped to everybody, and it is inside Desktop, which is one
118
+ * of the guarded seven above: the very first project anybody made through the
119
+ * card asked macOS for the Desktop before it had drawn anything.
120
+ *
121
+ * So the parent is LEARNED from the projects they already have, and only
122
+ * guessed when there are none. `existing` is the folders of their current
123
+ * projects; the commonest parent among them wins, because somebody with three
124
+ * projects in `~/code` wants the fourth there too.
125
+ *
126
+ * The guess when there is nothing to learn from is `~/dev`, which is not
127
+ * guarded and costs no panel. It is a guess and the card says so by showing
128
+ * the whole path, which is the same thing it has always done.
129
+ */
130
+ export function proposeParent(existing = [], { home = '~' } = {}) {
131
+ const counts = new Map();
132
+ for (const f of existing) {
133
+ const p = tidy(f);
134
+ if (!p) continue;
135
+ const cut = p.lastIndexOf('/');
136
+ if (cut <= 0) continue;
137
+ const parent = p.slice(0, cut);
138
+ // The PARENT is what gets proposed, so the parent is what is judged. A
139
+ // project sitting directly in ~/Downloads is a real project and is allowed;
140
+ // proposing ~/Downloads as the home of the next one is not.
141
+ if (!checkProjectFolder(parent, { home }).ok) continue;
142
+ counts.set(parent, (counts.get(parent) ?? 0) + 1);
143
+ }
144
+ let best = null;
145
+ let bestN = 0;
146
+ for (const [parent, n] of counts) {
147
+ // Ties go to the shorter path, so a stable answer rather than whichever
148
+ // project happened to be listed first.
149
+ if (n > bestN || (n === bestN && best && parent.length < best.length)) { best = parent; bestN = n; }
150
+ }
151
+ return best ?? `${tidy(home) || '~'}/dev`;
152
+ }
@@ -0,0 +1,8 @@
1
+ import type { ClaudeCommand } from './claude-commands.mjs';
2
+ export const CODEX_COMMANDS: ClaudeCommand[];
3
+ export const LOCAL_COMMANDS: ClaudeCommand[];
4
+ export function providerCommands(engine: string): ClaudeCommand[];
5
+ export function providerCommand(text: unknown, engine: string): {name: string; args: string; route: string} | null;
6
+ export function reviewTarget(args?: string): Record<string,string>;
7
+
8
+ export function nativeCommandNames(frame: unknown): string[] | null;
@@ -0,0 +1,58 @@
1
+ // A command is an operation, never prose asking a model to imitate a CLI.
2
+ import { CLAUDE_COMMANDS, commandWords } from './claude-commands.mjs';
3
+ const command = (name, description, argumentHint = null, aliases = []) => ({ name, description, menuDescription: null, whole: true, argumentHint, aliases });
4
+ export const CODEX_COMMANDS = [
5
+ command('model','Show models or change this task’s model','[model]'),
6
+ command('effort','Show or change reasoning effort','[level]'),
7
+ command('compact','Free up context in this conversation'),
8
+ command('review','Review uncommitted changes, a branch, or specific instructions','[--base branch | --commit sha | instructions]'),
9
+ command('status','Show this task’s configuration and conversation status'),
10
+ command('usage','Show the account’s last reported usage'),
11
+ command('skills','List skills available in this project'),
12
+ command('mcp','Show this conversation’s connected MCP servers'),
13
+ command('goal','View, pause, or clear this conversation’s goal','[pause | clear]'),
14
+ command('diff','Show staged and unstaged changes'),
15
+ command('rename','Rename this task','<name>'),
16
+ command('copy','Copy the last assistant response'),
17
+ command('help','Show available commands and remaining compatibility gaps'),
18
+ ];
19
+ export const LOCAL_COMMANDS = CODEX_COMMANDS.filter(c=>['model','effort','diff','rename','help'].includes(c.name));
20
+ // A FORK IS A NEW TASK THAT STARTS WHERE THIS ONE GOT TO (MP-08). It is offered
21
+ // on both engines because both can do it natively: Claude Code resumes under a
22
+ // new id with `--fork-session`, and Codex's own protocol has `thread/fork`. It
23
+ // is refused on Codex for now, in a sentence that says why, because that call
24
+ // has never been made against a real server here.
25
+ const FORK = command('fork','Start a new task from this conversation','<what to try instead>');
26
+ export function providerCommands(engine) {
27
+ // NOT ON THE CODEX MENU, because this menu's rule is that it offers nothing
28
+ // that engine cannot run, and Codex forking is not connected yet. Typed there
29
+ // it gets the ordinary unavailable refusal, which points at the native Codex
30
+ // client, where `codex fork` genuinely works.
31
+ return engine==='codex' ? CODEX_COMMANDS : [command('remote-control','Continue this conversation in Claude on another device',null,['rc']),FORK,...CLAUDE_COMMANDS.map(c=>LOCAL_COMMANDS.find(l=>l.name===c.name)??c),...LOCAL_COMMANDS.filter(l=>!CLAUDE_COMMANDS.some(c=>c.name===l.name))];
32
+ }
33
+ export function providerCommand(text, engine) {
34
+ if(typeof text!=='string')return null;
35
+ const m=text.trim().match(/^\/([a-z][a-z0-9:_-]*)(?:\s+([\s\S]*))?$/i);
36
+ if(!m)return null;
37
+ const word=m[1].toLowerCase(), args=(m[2]??'').trim();
38
+ const cmd=providerCommands(engine).find(c=>commandWords(c).includes(word));
39
+ if(!cmd) return {name:word,args,route:'unavailable'};
40
+ if(cmd.name==='fork')return {name:'fork',args,route:'fork'};
41
+ return {name:cmd.name,args,route:cmd.name==='remote-control'?'remote':engine==='codex'?'codex':LOCAL_COMMANDS.some(c=>c.name===cmd.name)?'local':'claude'};
42
+ }
43
+ export function reviewTarget(args='') {
44
+ args=args.trim();
45
+ if(!args)return {type:'uncommittedChanges'};
46
+ const scope=args.match(/^--(base|commit)\s+([^\s]+)$/);
47
+ if(scope)return scope[1]==='base'?{type:'baseBranch',branch:scope[2]}:{type:'commit',sha:scope[2]};
48
+ if(args.startsWith('--'))throw new Error('Use /review, /review --base branch, /review --commit sha, or /review instructions.');
49
+ return {type:'custom',instructions:args};
50
+ }
51
+
52
+ // Native prompt/skill commands are advertised by the running Claude session,
53
+ // including user and plugin commands. Never guess skill names from filenames.
54
+ export function nativeCommandNames(frame) {
55
+ if(frame?.type!=='system'||frame?.subtype!=='init'||!Array.isArray(frame.slash_commands))return null;
56
+ const blocked=new Set(['add-dir','auto-mode-setup','clear','color','config','import','design-consent','design-revoke','heapdump','exit','stop','__remote-workflow','workflow-launch-exec','usage-credits','extra-usage']);
57
+ return [...new Set(frame.slash_commands.filter(n=>typeof n==='string'&&/^[a-z][a-z0-9:_-]*$/i.test(n)&&!blocked.has(n)))];
58
+ }
@@ -0,0 +1,19 @@
1
+ // Types for shared/rank.mjs, which is plain ESM so that main (Node) and the
2
+ // renderer (Vite) can share one copy of the ranking rule.
3
+
4
+ export const RANK_STEP: number;
5
+ export function normalizeOrder(slugs: unknown): string[];
6
+ export function productRankScore(order: string[], slug: string): number;
7
+
8
+ export const DEFAULT_PRIORITY: number;
9
+
10
+ export function itemPriority(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): number;
11
+ export const URGENT_PRIORITY: number;
12
+ export function isUrgent(item: { priority?: number; wrote?: Record<string, { source?: string } | undefined> } | null | undefined): boolean;
13
+
14
+ export function orderFromTiers(tiers: Record<string, number> | null | undefined): string[];
15
+ export function landingIndex(
16
+ rects: Array<{ top: number; bottom: number; left: number; width: number }>,
17
+ x: number, y: number,
18
+ ): number;
19
+ export function moveProduct(order: string[], slug: string, toIndex: number): string[];