agentbox-app 0.1.12 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1 @@
1
+ export function terminalKeys(options?: { startOpen?: boolean; wasOpen?: boolean }): { takesKeys(revision?: number): boolean; hidden(): void };
@@ -0,0 +1,13 @@
1
+ // Whether a terminal takes the keyboard when TaskTerminal builds it. Opening
2
+ // it, showing it again, Retry and New session do. Coming back to a thread that
3
+ // left its terminal open does not: xterm's input is a textarea, and the app's
4
+ // shortcuts stand aside for a textarea. `startOpen` is a page that is only a
5
+ // terminal, which always takes it.
6
+ // Pinned by tests/coming-back-to-an-open-terminal-leaves-the-keyboard-alone.test.mjs.
7
+ export function terminalKeys({ startOpen = false, wasOpen = false } = {}) {
8
+ let cameBack = wasOpen === true && !startOpen;
9
+ return {
10
+ takesKeys: (revision = 0) => !cameBack || revision > 0,
11
+ hidden: () => { cameBack = false; },
12
+ };
13
+ }
@@ -0,0 +1 @@
1
+ export function inputQueue(send: (data: string) => Promise<unknown>, options?: { chunk?: number; onError?: (e: unknown) => void }): { push(data: string): void };
@@ -0,0 +1,30 @@
1
+ // KEYS TYPED WHILE ONE WRITE IS ON ITS WAY GO OUT TOGETHER, as the next write.
2
+ //
3
+ // The panel used to send every key as its own round trip to main, each one
4
+ // waiting for the last, so holding Delete built a line of trips that drained
5
+ // after she let go. Now the first key goes at once, and whatever she types
6
+ // while it travels is one write behind it, in order. A paste still goes in
7
+ // pieces main accepts (64 KB is its limit; `chunk` stays well under it).
8
+ //
9
+ // A write that fails (the shell exited) drops what was waiting behind it: those
10
+ // keys were for a shell that is gone, and the failure is reported once.
11
+ export function inputQueue(send, { chunk = 16384, onError } = {}) {
12
+ let queued = '', sending = false;
13
+ async function drain() {
14
+ if (sending) return;
15
+ sending = true;
16
+ try {
17
+ while (queued) {
18
+ const data = queued.slice(0, chunk);
19
+ queued = queued.slice(data.length);
20
+ await send(data);
21
+ }
22
+ } catch (e) {
23
+ queued = '';
24
+ onError?.(e);
25
+ } finally {
26
+ sending = false;
27
+ }
28
+ }
29
+ return { push(data) { queued += data; void drain(); } };
30
+ }
@@ -0,0 +1 @@
1
+ export function optionWordKey(event: { type: string; key: string; altKey: boolean; ctrlKey: boolean; metaKey: boolean; shiftKey: boolean }, isMac: boolean): string | undefined;
@@ -0,0 +1,16 @@
1
+ // OPTION+LEFT AND OPTION+RIGHT JUMP A WORD, as they do in Mac Terminal.
2
+ // xterm 6 sends them as ESC[1;3D and ESC[1;3C, which zsh and bash do not bind,
3
+ // so a shell typed `;3D` into the line instead. Mac Terminal sends ESC b and
4
+ // ESC f, the backward-word and forward-word every shell already knows.
5
+ //
6
+ // Returns what to send for this key, '' to swallow the keyup of one we
7
+ // handled, or undefined to leave the key to xterm. Off a Mac nothing changes:
8
+ // Linux terminals send the same ESC[1;3D xterm does.
9
+ const WORD = { ArrowLeft: '\x1bb', ArrowRight: '\x1bf' };
10
+
11
+ export function optionWordKey(event, isMac) {
12
+ if (!isMac || !event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return undefined;
13
+ const sequence = WORD[event.key];
14
+ if (!sequence) return undefined;
15
+ return event.type === 'keydown' ? sequence : '';
16
+ }
@@ -0,0 +1,4 @@
1
+ export const SIZE_KEY: string;
2
+ export const TERMINAL_MIN: { height: number; width: number };
3
+ export function dragSize(dimension: 'height' | 'width', start: number, delta: number, room: number): number;
4
+ export function readSavedSize(raw: string | null | undefined): { height?: number; width?: number };
@@ -0,0 +1,29 @@
1
+ // HOW BIG THE TERMINAL MAY BE DRAGGED (renderer/src/components/TaskTerminal.tsx).
2
+ //
3
+ // Small enough still shows a few lines; large enough still leaves the page it
4
+ // sits in a strip to read (the thread's title and a line or two above it, or a
5
+ // column of the conversation beside it). The size is one for the whole app,
6
+ // remembered under SIZE_KEY, the way an editor remembers its panel.
7
+ export const SIZE_KEY = 'zero.terminal.size';
8
+ export const TERMINAL_MIN = { height: 120, width: 280 };
9
+ const LEAVE = { height: 120, width: 320 };
10
+ const LARGEST = 4000;
11
+
12
+ /** `start` px, the edge moved `delta` px toward growing, in a pane `room` px across. */
13
+ export function dragSize(dimension, start, delta, room) {
14
+ const min = TERMINAL_MIN[dimension];
15
+ const max = Math.max(min, room - LEAVE[dimension]);
16
+ return Math.round(Math.min(max, Math.max(min, start + delta)));
17
+ }
18
+
19
+ export function readSavedSize(raw) {
20
+ let saved;
21
+ try { saved = JSON.parse(raw ?? ''); } catch { return {}; }
22
+ if (!saved || typeof saved !== 'object' || Array.isArray(saved)) return {};
23
+ const out = {};
24
+ for (const dimension of ['height', 'width']) {
25
+ const v = saved[dimension];
26
+ if (Number.isFinite(v) && v >= TERMINAL_MIN[dimension] && v <= LARGEST) out[dimension] = v;
27
+ }
28
+ return out;
29
+ }
@@ -16,6 +16,8 @@
16
16
  // private thread publishes no card at all (decided 2026-10-01
17
17
  // from user research): not a wordless one, not a count.
18
18
 
19
+ import { shownPriority } from './rank.mjs';
20
+
19
21
  export const THREAD_STATES = ['waiting', 'running', 'scheduled', 'done'];
20
22
 
21
23
  /** Which of the four states a thread is in. */
@@ -245,7 +247,8 @@ export function cardsFor({ products, readItems, now = Date.now(), since = null }
245
247
  // team. Never an empty list, which would have to mean both. Both clouds
246
248
  // carry it and refuse to show the card to anybody else.
247
249
  people: shownToPeople(item, product).length ? shownToPeople(item, product) : null,
248
- state, priority: Number.isFinite(item.priority) ? item.priority : null,
250
+ // The level it runs at, so a teammate reads what the owner reads.
251
+ state, priority: shownPriority(item),
249
252
  problem: s.problem || null, progress: s.progress || null, solution: s.solution || null,
250
253
  blockedBy: linked(item.blockedBy), blocks: linked(item.blocks), updatedAt: item.updatedAt ?? now,
251
254
  });
@@ -3,6 +3,9 @@
3
3
  // rule about which coding agent the corner is about, the zone primitives the
4
4
  // Claude reader is built on, and the words the panel prints.
5
5
 
6
+ /** What a usage check that got nothing readable says, for any cause we have not named. */
7
+ export const USAGE_UNREADABLE: string;
8
+
6
9
  export interface UsageLimit {
7
10
  span: 'session' | 'week';
8
11
  qualifier: string | null;
@@ -39,8 +42,17 @@ export declare function sessionLimit(limits: UsageLimit[] | null | undefined): U
39
42
  export declare function headlineLimit(limits: UsageLimit[] | null | undefined): UsageLimit | null;
40
43
  export declare const ANY_MOMENT: string;
41
44
  export declare function spanLeft(ms: number): string;
45
+ export declare const toMinute: (at: number) => number;
46
+ export declare function resetDay(at: number, now?: number): string | null;
42
47
  export declare function untilReset(ms: number): string;
43
48
 
49
+ /** The windows a reading can still answer for: one whose reset has gone past
50
+ * measures a window nobody is in any more. */
51
+ export declare function limitsStillRunning(
52
+ limits: UsageLimit[] | null | undefined,
53
+ now?: number,
54
+ ): UsageLimit[];
55
+
44
56
  export declare function limitRows(
45
57
  limits: UsageLimit[] | null | undefined,
46
58
  now?: number,
package/shared/usage.mjs CHANGED
@@ -33,6 +33,13 @@ import { DEFAULT_ENGINE } from './engines.mjs';
33
33
 
34
34
  /* ------------------ WHICH CODING AGENT THE CORNER IS ABOUT ---------------- */
35
35
 
36
+ // WHAT A USAGE CHECK THAT GOT NOTHING READABLE SAYS, for every reason we have
37
+ // not named. It used to be "Could not refresh usage. Try again.", which reads
38
+ // as the app having broken; the measured case (2026-10-11) was an account whose
39
+ // subscription had ended, and Claude Code simply printed no limits for it. So
40
+ // it points at the account, which is where an unnamed cause almost always is.
41
+ export const USAGE_UNREADABLE = "Claude Code didn't report usage for this account. Check that its plan is active at claude.ai.";
42
+
36
43
  /**
37
44
  * THE AGENT MOST OF HER LIVE WORK IS ON, or the workspace's own when nothing is
38
45
  * running and when the fleet is level.
@@ -227,6 +234,51 @@ export function spanLeft(ms) {
227
234
  return `${hours}h ${minutes % 60}m`;
228
235
  }
229
236
 
237
+ /**
238
+ * HOW LONG A WINDOW GOES ON BEING DRAWN AFTER ITS RESET, so a countdown
239
+ * crossing zero does not flicker out of the panel a minute before the next
240
+ * reading lands. Inside it "any moment" is what it says: the reset is here.
241
+ */
242
+ const JUST_RESET_MS = 2 * 60_000;
243
+
244
+ /**
245
+ * THE WINDOWS A READING CAN STILL ANSWER FOR (w-7b7182ef61).
246
+ *
247
+ * A window whose reset has gone past is over, and the one running in its place
248
+ * has never been read. shared/codex-usage.mjs already holds the rule for that:
249
+ * absence of data and a real zero are different facts and must never look the
250
+ * same. So it is DROPPED rather than relabelled -- no wording makes a
251
+ * percentage about a window nobody is in worth reading -- and the corner falls
252
+ * back to whatever the reading can still answer for (`headlineLimit`).
253
+ *
254
+ * A RESET WE COULD NOT PLACE ON A CLOCK KEEPS ITS ROW. `resetsAt` is null
255
+ * whenever Claude Code printed its stamp in a timezone we could not honestly
256
+ * convert, and a limit we cannot time is not a limit we may call finished.
257
+ */
258
+ export function limitsStillRunning(limits, now = Date.now()) {
259
+ return (limits ?? []).filter((l) => !Number.isFinite(l?.resetsAt) || l.resetsAt + JUST_RESET_MS > now);
260
+ }
261
+
262
+ /**
263
+ * A reset to the nearest minute. Claude Code gives one a moment before the
264
+ * minute (11:59:59.6) and the cloud's header the minute itself, and both name
265
+ * the same reset.
266
+ */
267
+ export const toMinute = (at) => Math.round(at / 60_000) * 60_000;
268
+
269
+ /**
270
+ * THE DAY A RESET FALLS ON, in this machine's calendar: null today, "Thu" this
271
+ * week, and "Tue Oct 27" six or more days out, where a weekday alone would
272
+ * read as today.
273
+ */
274
+ export function resetDay(at, now = Date.now()) {
275
+ const d = new Date(toMinute(at));
276
+ if (d.toDateString() === new Date(now).toDateString()) return null;
277
+ const weekday = d.toLocaleDateString('en-US', { weekday: 'short' });
278
+ if (at - now < 6 * 86_400_000) return weekday;
279
+ return `${weekday} ${d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
280
+ }
281
+
230
282
  /* -------------------------------- the panel ------------------------------- */
231
283
 
232
284
  /**
@@ -274,7 +326,7 @@ export function spanLeft(ms) {
274
326
  */
275
327
  export function limitRows(limits, now = Date.now()) {
276
328
  const here = localZone();
277
- return (limits ?? []).map((l) => ({
329
+ return limitsStillRunning(limits, now).map((l) => ({
278
330
  // Stable across redraws, and it has to carry the qualifier: Claude Code's
279
331
  // two week rows are otherwise the same string.
280
332
  key: `${l.span}:${l.qualifier ?? ''}`,
@@ -309,8 +361,9 @@ function whenItStartsOver(l, now, here) {
309
361
  * 2026-09-01.
310
362
  */
311
363
  export function usageSentence(limits, now = Date.now(), engineWord = null) {
312
- if (!limits?.length) return null;
313
- const said = limits.map((l) => {
364
+ const live = limitsStillRunning(limits, now);
365
+ if (!live.length) return null;
366
+ const said = live.map((l) => {
314
367
  const when = l.resetsAt ? `, ${untilReset(l.resetsAt - now)}` : l.resetsText ? `, resets ${l.resetsText}` : '';
315
368
  return `${l.name}: ${l.percent}% used${when}.`;
316
369
  }).join(' ');
@@ -0,0 +1,15 @@
1
+ // Types for shared/websites.mjs, the rules the main process and the screen
2
+ // share about a page off the open web: which addresses are one, what the
3
+ // pane's header calls it, and where its sign-ins go.
4
+
5
+ /** The session partition a website's cookies and sign-ins live in. */
6
+ export const WEBSITE_PARTITION: string;
7
+
8
+ /** Is this an ordinary page on the web, rather than a local app or a file? */
9
+ export function isWebsiteAddress(src: string | undefined | null): boolean;
10
+
11
+ /** What an address typed into the pane's bar means, with its scheme, or null. */
12
+ export function addressYouTyped(typed: string | undefined | null): string | null;
13
+
14
+ /** Can this page embed a website in a browser of its own (Electron only)? */
15
+ export function canFrameAWebsite(doc: { createElement(tag: string): unknown } | null | undefined): boolean;
@@ -0,0 +1,61 @@
1
+ // PURE. What counts as a website, and where its sign-ins live. Shared because
2
+ // the main process pins what an embedded page may do and the screen decides
3
+ // what to draw, and the two must agree.
4
+ //
5
+ // A local address is NOT a website here. A dev server is drawn in an ordinary
6
+ // frame with a liveness check in front of it (renderer/src/local-preview.ts)
7
+ // and its framing headers rewritten below the page (main/local-app-frames.mjs);
8
+ // none of that applies to a site on the web.
9
+
10
+ /**
11
+ * The store a website's cookies and sign-ins go into. Not `persist:junk`:
12
+ * that one is safe for an agent to drive precisely because it carries
13
+ * nothing, and a person may sign into a page here.
14
+ */
15
+ export const WEBSITE_PARTITION = 'persist:websites';
16
+
17
+ const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
18
+
19
+ /** Is this an ordinary page on the web, the kind the pane opens in a browser? */
20
+ export function isWebsiteAddress(src) {
21
+ if (typeof src !== 'string' || !src) return false;
22
+ try {
23
+ const url = new URL(src);
24
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
25
+ return !LOOPBACK.has(url.hostname);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * What an address typed into the pane's bar means: a page on the web or a
33
+ * local app, with the scheme a browser would add, or null. Never a search,
34
+ * which would send what you typed to somebody else.
35
+ */
36
+ export function addressYouTyped(typed) {
37
+ const text = typeof typed === 'string' ? typed.trim() : '';
38
+ if (!text || /\s/.test(text)) return null;
39
+ const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(text);
40
+ const bareHost = text.split(/[/?#]/)[0].replace(/:\d+$/, '').toLowerCase();
41
+ const full = hasScheme ? text : `${LOOPBACK.has(bareHost) ? 'http' : 'https'}://${text}`;
42
+ let url;
43
+ try { url = new URL(full); } catch { return null; }
44
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
45
+ if (url.username || url.password) return null;
46
+ if (!LOOPBACK.has(url.hostname) && !url.hostname.includes('.')) return null;
47
+ return url.href;
48
+ }
49
+
50
+ /**
51
+ * Can this page embed a website in a browser of its own? Only the desktop
52
+ * window can: `<webview>` is Electron's, and nothing registers the tag in an
53
+ * ordinary tab or on a phone. Asking the tag beats guessing which door this is.
54
+ */
55
+ export function canFrameAWebsite(doc) {
56
+ try {
57
+ return !!doc && 'partition' in doc.createElement('webview');
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
@@ -0,0 +1,43 @@
1
+ // Types for shared/where-it-runs-from.mjs, which is plain ESM so that main
2
+ // (Node) and the screen (Vite) share one copy of the rule about whether this
3
+ // copy of the app can update itself from where it is, and one copy of the
4
+ // words that say so. The screen asks for the words and `sayWhere`; the places
5
+ // and the move are main's.
6
+
7
+ /** The place a copy of the app is in, as far as updating itself is concerned. */
8
+ export type AppPlace =
9
+ | 'disk-image' | 'translocated' | 'applications' | 'elsewhere' | 'source' | 'not-mac';
10
+
11
+ export const PLACE: {
12
+ diskImage: 'disk-image';
13
+ translocated: 'translocated';
14
+ applications: 'applications';
15
+ elsewhere: 'elsewhere';
16
+ source: 'source';
17
+ notMac: 'not-mac';
18
+ };
19
+
20
+ export const MOVE_SAY: {
21
+ cannotUpdateHere: string;
22
+ button: string;
23
+ title: string;
24
+ where: Record<string, string>;
25
+ shortWhere: Record<string, string>;
26
+ fix: string;
27
+ retry: string;
28
+ };
29
+
30
+ /** The sentence for a place, or null where there is nothing to say. */
31
+ export function sayWhere(place: string | null | undefined, how?: { short?: boolean }): string | null;
32
+
33
+ export function placeOfApp(facts?: {
34
+ platform?: string;
35
+ packaged?: boolean;
36
+ exePath?: string | null;
37
+ inApplications?: boolean;
38
+ }): { place: AppPlace; canUpdate: boolean; offerMove: boolean };
39
+
40
+ export function createPlace(host: { app: unknown; log?: unknown }): {
41
+ state(): { place: AppPlace; canUpdate: boolean; offerMove: boolean };
42
+ move(): { moved: boolean; error: string | null };
43
+ };
@@ -0,0 +1,155 @@
1
+ // AN APP IN THE WRONG PLACE CAN NEVER UPDATE ITSELF, AND NEVER SAID SO.
2
+ //
3
+ // macOS has two places it will happily run Agentbox from and never let it
4
+ // replace itself:
5
+ //
6
+ // THE DISK IMAGE. You double-click the .dmg, you double-click the app
7
+ // inside it, and it works. The volume is read-only, so Squirrel.Mac refuses
8
+ // the swap with "Cannot update while running on a read-only volume".
9
+ //
10
+ // A TRANSLOCATED COPY. You drag the app to Downloads and open it. Because
11
+ // it still carries the quarantine flag and is not in Applications, macOS
12
+ // runs it from a read-only copy it makes under
13
+ // `/private/var/folders/…/AppTranslocation/<uuid>/d/`. Same refusal, and
14
+ // this is why Squirrel's own sentence talks about the Downloads directory.
15
+ //
16
+ // Measured in PostHog on 2026-10-12: 23 crash reports over three weeks, two
17
+ // installs, both on 0.1.11, one report every six hours (`EVERY_MS` in
18
+ // main/updater.mjs) for as long as the app was open. The reports stopped being
19
+ // filed as crashes in 292d419. The two Macs are still on 0.1.11.
20
+ //
21
+ // SO THE APP READS WHERE IT IS AND OFFERS THE ONE PRESS THAT FIXES IT. Electron
22
+ // has had `moveToApplicationsFolder` since 4: it moves the bundle, replaces an
23
+ // older copy if you say so, and relaunches from the new place. After that the
24
+ // ordinary six-hourly check works and the person never thinks about it again.
25
+ //
26
+ // EVERYTHING HERE IS A FACT AND NOT A GUESS, which is why the decision is a
27
+ // pure function with the four facts passed in: a wrong "you are in the wrong
28
+ // place" is a nag on a healthy install, and two of those are worse than the
29
+ // silence this replaces.
30
+
31
+ import { Name } from './product-name.mjs';
32
+
33
+ // THE WORDS, IN ONE PLACE, and main and the renderer both read them from here.
34
+ // `cannotUpdateHere` is the sentence `shortError` in main/updater.mjs puts in
35
+ // place of Squirrel's own paragraph, which is 240 characters with a URL in the
36
+ // middle and was arriving on the Settings row cut off at 160.
37
+ //
38
+ // Every sentence says where the app is and what one press does about it, and
39
+ // none of them says "read-only volume": that is the mechanism, and nobody
40
+ // holding the Mac asked about the mechanism.
41
+ export const MOVE_SAY = {
42
+ cannotUpdateHere: `${Name} cannot update itself from where it is.`,
43
+ button: 'Move to Applications',
44
+ /** The sidebar card's heading. */
45
+ title: 'Updates are stuck',
46
+ /** What the place is, as a person would say it. */
47
+ where: {
48
+ 'disk-image': `${Name} is running from the disk image it was downloaded in.`,
49
+ translocated: `${Name} is running from a read-only copy macOS made of it.`,
50
+ },
51
+ /** The same fact in half the words, for the sidebar card, which is one
52
+ * column wide and had the row's sentence running to six lines in it. */
53
+ shortWhere: {
54
+ 'disk-image': `${Name} can’t update itself from the disk image.`,
55
+ translocated: `${Name} can’t update itself from the copy macOS made.`,
56
+ },
57
+ /** And the press, which is the same either way. */
58
+ fix: 'Moving it to your Applications folder takes a few seconds and gets updates working again.',
59
+ retry: 'Pressing it again tries again.',
60
+ };
61
+
62
+ /** The sentence for a place, or null where there is nothing to say. `short` is
63
+ * the sidebar card's half of it: the button beside it already says the fix. */
64
+ export function sayWhere(place, { short = false } = {}) {
65
+ const words = short ? MOVE_SAY.shortWhere : MOVE_SAY.where;
66
+ return words[place] ?? null;
67
+ }
68
+
69
+ /** Where a copy of the app can be, as far as updating itself is concerned. */
70
+ export const PLACE = {
71
+ /** Run out of the mounted .dmg. Read-only; cannot update. */
72
+ diskImage: 'disk-image',
73
+ /** macOS is running a read-only copy of its own. Cannot update. */
74
+ translocated: 'translocated',
75
+ /** Where it belongs. */
76
+ applications: 'applications',
77
+ /** Somewhere writable that is not Applications: updates itself fine. */
78
+ elsewhere: 'elsewhere',
79
+ /** `npm start` from a checkout: main/source-updater.mjs, nothing to move. */
80
+ source: 'source',
81
+ /** No Applications folder and no `moveToApplicationsFolder`. */
82
+ notMac: 'not-mac',
83
+ };
84
+
85
+ // The folder macOS puts a translocated copy in. The path is always under
86
+ // `/private/var/folders`, but the one segment that names the mechanism is this
87
+ // one, and it is the only part Apple has kept stable. Written with "/" and
88
+ // not `path.sep`: these are only ever read on a Mac (`placeOfApp` returns
89
+ // before them anywhere else), and the screen imports this file, so it may not
90
+ // import anything from Node.
91
+ const TRANSLOCATED = '/AppTranslocation/';
92
+ // Every mounted volume lives here, including the disk image the download is.
93
+ const VOLUMES = '/Volumes/';
94
+
95
+ /**
96
+ * PURE. The whole decision, off four facts, so every case can be tested
97
+ * without a packaged app, a disk image, or a Mac.
98
+ *
99
+ * `exePath` is `app.getPath('exe')`, the binary inside the bundle;
100
+ * `inApplications` is Electron's own `app.isInApplicationsFolder()`, which
101
+ * knows about `~/Applications` as well as `/Applications` and is therefore
102
+ * never re-derived from the path here.
103
+ */
104
+ export function placeOfApp({ platform, packaged, exePath, inApplications } = {}) {
105
+ const no = (place) => ({ place, canUpdate: true, offerMove: false });
106
+ if (platform !== 'darwin') return no(PLACE.notMac);
107
+ if (!packaged) return no(PLACE.source);
108
+ if (inApplications) return no(PLACE.applications);
109
+ if (typeof exePath !== 'string' || !exePath) return no(PLACE.elsewhere);
110
+ const where = exePath.includes(TRANSLOCATED) ? PLACE.translocated
111
+ : exePath.startsWith(VOLUMES) ? PLACE.diskImage
112
+ : null;
113
+ // THE PLACES THAT ARE NOT APPLICATIONS AND UPDATE THEMSELVES ANYWAY are left
114
+ // alone on purpose. A copy sitting in Downloads that macOS did not
115
+ // translocate is on a writable disk and Squirrel swaps it happily; offering
116
+ // to move it would be the app asking for something it does not need.
117
+ if (!where) return no(PLACE.elsewhere);
118
+ return { place: where, canUpdate: false, offerMove: true };
119
+ }
120
+
121
+ /**
122
+ * The live one, over Electron's `app`. Read ONCE, at construction: the bundle
123
+ * cannot move under a running process except by the move below, which
124
+ * relaunches.
125
+ */
126
+ export function createPlace({ app, log = console }) {
127
+ const where = placeOfApp({
128
+ platform: app?.platform ?? process.platform,
129
+ packaged: !!app?.isPackaged,
130
+ exePath: (() => { try { return app?.getPath?.('exe') ?? null; } catch { return null; } })(),
131
+ inApplications: (() => { try { return !!app?.isInApplicationsFolder?.(); } catch { return false; } })(),
132
+ });
133
+
134
+ return {
135
+ state: () => ({ ...where }),
136
+
137
+ /**
138
+ * THE PRESS. Electron moves the bundle and relaunches, so nothing after a
139
+ * `true` here ever runs. It answers false when the person cancels its own
140
+ * "a copy is already there" box, which is an answer and not a failure.
141
+ */
142
+ move() {
143
+ if (!where.offerMove) return { moved: false, error: null };
144
+ try {
145
+ const moved = !!app.moveToApplicationsFolder();
146
+ return { moved, error: null };
147
+ } catch (e) {
148
+ // One line, because Settings shows this string to a person.
149
+ const error = String(e?.message ?? e ?? 'The move did not work.').split('\n')[0].trim();
150
+ log.warn?.('[place] could not move the app into Applications:', error);
151
+ return { moved: false, error };
152
+ }
153
+ },
154
+ };
155
+ }