synabun 2.0.0-beta → 2.0.5

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 (374) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/THIRD-PARTY-LICENSES.md +51 -0
  5. package/benchmarks/fixtures/browser/README.md +1 -1
  6. package/benchmarks/fixtures/desktop/README.md +1 -1
  7. package/docs/whatsapp.md +2 -2
  8. package/hooks/claude-code/pre-compact.mjs +11 -0
  9. package/hooks/claude-code/prompt-submit.mjs +60 -184
  10. package/hooks/claude-code/session-start.mjs +73 -16
  11. package/hooks/claude-code/shared.mjs +20 -0
  12. package/hooks/claude-code/stop.mjs +5 -5
  13. package/launcher.mjs +40 -0
  14. package/lib/claude-hooks.js +61 -8
  15. package/lib/client-config-repair.js +4 -3
  16. package/lib/data-home-migration.js +4 -0
  17. package/lib/desktop-pwa.js +206 -0
  18. package/lib/external-tools.js +221 -0
  19. package/lib/first-launch-protection.js +5 -0
  20. package/lib/packaged-runtime.js +110 -0
  21. package/lib/start-launcher.js +1396 -0
  22. package/mcp-server/package-lock.json +2 -2
  23. package/mcp-server/package.json +1 -3
  24. package/neural-interface/i18n/en.json +1540 -154
  25. package/neural-interface/i18n/pt-BR.json +1538 -153
  26. package/neural-interface/lib/assistant-api.js +62 -6
  27. package/neural-interface/lib/assistant-brains/claude.js +4 -0
  28. package/neural-interface/lib/assistant-catalog.js +179 -27
  29. package/neural-interface/lib/assistant-config.js +31 -2
  30. package/neural-interface/lib/assistant-dispatch.js +62 -13
  31. package/neural-interface/lib/assistant-envelope.js +9 -6
  32. package/neural-interface/lib/assistant-hidden-models.js +393 -66
  33. package/neural-interface/lib/assistant-memory.js +24 -10
  34. package/neural-interface/lib/assistant-persona.js +6 -2
  35. package/neural-interface/lib/assistant-pricing.js +21 -6
  36. package/neural-interface/lib/assistant-runtime.js +181 -12
  37. package/neural-interface/lib/assistant-task-prompt.js +13 -3
  38. package/neural-interface/lib/assistant-test-density.js +142 -0
  39. package/neural-interface/lib/assistant-user-context-budgets.js +123 -0
  40. package/neural-interface/lib/assistant-user-context.js +908 -0
  41. package/neural-interface/lib/assistant-user-knowledge.js +328 -0
  42. package/neural-interface/lib/assistant-user-learn.js +404 -0
  43. package/neural-interface/lib/assistant-user-threads.js +226 -0
  44. package/neural-interface/lib/assistant-user-writer.js +181 -0
  45. package/neural-interface/lib/augmented-path.js +23 -3
  46. package/neural-interface/lib/browser-background-tab.js +134 -0
  47. package/neural-interface/lib/browser-language.js +83 -0
  48. package/neural-interface/lib/browser-screenshot.js +678 -61
  49. package/neural-interface/lib/claude-agent-bridge.js +87 -153
  50. package/neural-interface/lib/claude-executable.js +241 -0
  51. package/neural-interface/lib/claude-model-catalog.js +82 -93
  52. package/neural-interface/lib/claude-temporary.js +46 -7
  53. package/neural-interface/lib/cloudflared/install.js +533 -0
  54. package/neural-interface/lib/cloudflared/manager.js +545 -0
  55. package/neural-interface/lib/cloudflared/pins.js +115 -0
  56. package/neural-interface/lib/cloudflared/pins.json +63 -0
  57. package/neural-interface/lib/cloudflared/tunnel.js +265 -0
  58. package/neural-interface/lib/editor-completion-endpoint.js +264 -0
  59. package/neural-interface/lib/editor-completion-opencode.js +436 -0
  60. package/neural-interface/lib/editor-completion.js +714 -0
  61. package/neural-interface/lib/editor-config.js +149 -0
  62. package/neural-interface/lib/editor-files-api.js +268 -0
  63. package/neural-interface/lib/editor-git-api.js +134 -0
  64. package/neural-interface/lib/file-explorer-api.js +1039 -0
  65. package/neural-interface/lib/file-explorer-system.js +355 -0
  66. package/neural-interface/lib/file-explorer-trash.ps1 +85 -0
  67. package/neural-interface/lib/file-explorer-watch.js +239 -0
  68. package/neural-interface/lib/git-diff-api.js +103 -0
  69. package/neural-interface/lib/git-read.js +202 -0
  70. package/neural-interface/lib/greeting/api.js +89 -0
  71. package/neural-interface/lib/greeting/budgets.js +90 -0
  72. package/neural-interface/lib/greeting/engine.js +461 -0
  73. package/neural-interface/lib/greeting/facts.js +320 -0
  74. package/neural-interface/lib/greeting/sources.js +252 -0
  75. package/neural-interface/lib/greeting/writer.js +139 -0
  76. package/neural-interface/lib/http-guards.js +189 -13
  77. package/neural-interface/lib/machine-names.js +88 -0
  78. package/neural-interface/lib/memory-api.js +31 -7
  79. package/neural-interface/lib/native-binary-runtime.js +69 -337
  80. package/neural-interface/lib/native-loop-providers.js +40 -49
  81. package/neural-interface/lib/offline-page.js +54 -0
  82. package/neural-interface/lib/plugin-installer.js +4 -2
  83. package/neural-interface/lib/project-storage.js +5 -7
  84. package/neural-interface/lib/request-gate.js +589 -0
  85. package/neural-interface/lib/session-title-generator.js +5 -0
  86. package/neural-interface/lib/synabun-update-plan.js +19 -0
  87. package/neural-interface/lib/ui-blob-api.js +108 -0
  88. package/neural-interface/lib/ui-blob-store.js +68 -0
  89. package/neural-interface/package-lock.json +701 -253
  90. package/neural-interface/package.json +13 -2
  91. package/neural-interface/public/install-app.html +34 -0
  92. package/neural-interface/public/manifest.json +2 -0
  93. package/neural-interface/public/offline.html +402 -66
  94. package/neural-interface/public/onboarding.html +264 -147
  95. package/neural-interface/public/shared/agent-runtime-options.js +22 -6
  96. package/neural-interface/public/shared/assistant/asst-automation.js +286 -0
  97. package/neural-interface/public/shared/assistant/asst-brain-picker.js +312 -62
  98. package/neural-interface/public/shared/assistant/asst-composer.js +11 -1
  99. package/neural-interface/public/shared/assistant/asst-mascot.js +26 -64
  100. package/neural-interface/public/shared/assistant/asst-menu.js +12 -8
  101. package/neural-interface/public/shared/assistant/asst-panel.js +150 -28
  102. package/neural-interface/public/shared/assistant/asst-rack-styles.js +0 -3
  103. package/neural-interface/public/shared/assistant/asst-render.js +219 -67
  104. package/neural-interface/public/shared/assistant/asst-route-models.js +108 -0
  105. package/neural-interface/public/shared/assistant/asst-route.js +23 -329
  106. package/neural-interface/public/shared/assistant/asst-routes-editor.js +495 -0
  107. package/neural-interface/public/shared/assistant/asst-sanitize.js +8 -1
  108. package/neural-interface/public/shared/assistant/asst-sidepanel.js +31 -9
  109. package/neural-interface/public/shared/assistant/asst-state.js +185 -1
  110. package/neural-interface/public/shared/assistant/asst-styles.js +162 -6
  111. package/neural-interface/public/shared/blob-store.js +111 -22
  112. package/neural-interface/public/shared/cdx/cdx-panel.js +32 -11
  113. package/neural-interface/public/shared/cdx/cdx-render.js +25 -89
  114. package/neural-interface/public/shared/cdx/cdx-styles.js +36 -0
  115. package/neural-interface/public/shared/cdx/cdx-tabs.js +87 -4
  116. package/neural-interface/public/shared/command-runner-model.js +30 -0
  117. package/neural-interface/public/shared/constants.js +6 -1
  118. package/neural-interface/public/shared/cp/cp-rehydrate.js +2 -1
  119. package/neural-interface/public/shared/cp/cp-styles.js +15 -0
  120. package/neural-interface/public/shared/editor/editor-ai-logic.js +409 -0
  121. package/neural-interface/public/shared/editor/editor-ai.js +619 -0
  122. package/neural-interface/public/shared/editor/editor-commands.js +212 -0
  123. package/neural-interface/public/shared/editor/editor-compare.js +103 -0
  124. package/neural-interface/public/shared/editor/editor-completion-list.js +172 -0
  125. package/neural-interface/public/shared/editor/editor-doc.js +257 -0
  126. package/neural-interface/public/shared/editor/editor-git-logic.js +131 -0
  127. package/neural-interface/public/shared/editor/editor-git.js +95 -0
  128. package/neural-interface/public/shared/editor/editor-languages.js +156 -0
  129. package/neural-interface/public/shared/editor/editor-lint.js +229 -0
  130. package/neural-interface/public/shared/editor/editor-loader.js +51 -0
  131. package/neural-interface/public/shared/editor/editor-markdown.js +90 -0
  132. package/neural-interface/public/shared/editor/editor-menu.js +233 -0
  133. package/neural-interface/public/shared/editor/editor-minimap.js +371 -0
  134. package/neural-interface/public/shared/editor/editor-palette-logic.js +192 -0
  135. package/neural-interface/public/shared/editor/editor-palette.js +125 -0
  136. package/neural-interface/public/shared/editor/editor-panel-logic.js +288 -0
  137. package/neural-interface/public/shared/editor/editor-panel.js +2099 -0
  138. package/neural-interface/public/shared/editor/editor-prefs.js +224 -0
  139. package/neural-interface/public/shared/editor/editor-safety.js +500 -0
  140. package/neural-interface/public/shared/editor/editor-search.js +352 -0
  141. package/neural-interface/public/shared/editor/editor-statusbar.js +144 -0
  142. package/neural-interface/public/shared/editor/editor-tabs-logic.js +331 -0
  143. package/neural-interface/public/shared/editor/editor-tabs.js +233 -0
  144. package/neural-interface/public/shared/editor/editor-theme.js +244 -0
  145. package/neural-interface/public/shared/editor/editor-view.js +332 -0
  146. package/neural-interface/public/shared/explorer/fx-api.js +216 -0
  147. package/neural-interface/public/shared/explorer/fx-links.js +204 -0
  148. package/neural-interface/public/shared/explorer/fx-logic.js +537 -0
  149. package/neural-interface/public/shared/explorer/fx-main.js +865 -0
  150. package/neural-interface/public/shared/explorer/fx-names.js +87 -0
  151. package/neural-interface/public/shared/explorer/fx-ops.js +370 -0
  152. package/neural-interface/public/shared/explorer/fx-popups.js +490 -0
  153. package/neural-interface/public/shared/explorer/fx-tree.js +790 -0
  154. package/neural-interface/public/shared/file-links.js +339 -0
  155. package/neural-interface/public/shared/greeting/greeting-card.js +178 -0
  156. package/neural-interface/public/shared/greeting/greeting-view.js +148 -0
  157. package/neural-interface/public/shared/html-shell.js +169 -170
  158. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -0
  159. package/neural-interface/public/shared/ocp-v2/ocp-v2-greeting.js +47 -0
  160. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +99 -17
  161. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -1
  162. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +5 -0
  163. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +40 -0
  164. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +3 -2
  165. package/neural-interface/public/shared/pwa-install-page.js +62 -0
  166. package/neural-interface/public/shared/pwa-install.css +12 -0
  167. package/neural-interface/public/shared/pwa-install.js +127 -0
  168. package/neural-interface/public/shared/settings/settings-code-editor-pane.js +412 -0
  169. package/neural-interface/public/shared/settings/settings-code-editor.js +328 -0
  170. package/neural-interface/public/shared/settings/settings-greeting.js +133 -0
  171. package/neural-interface/public/shared/settings/settings-ia.js +18 -4
  172. package/neural-interface/public/shared/settings/settings-rules.js +160 -0
  173. package/neural-interface/public/shared/settings/settings-user-context.js +154 -0
  174. package/neural-interface/public/shared/start-bridge.js +802 -0
  175. package/neural-interface/public/shared/styleguide/sg-colors.js +5 -2
  176. package/neural-interface/public/shared/styleguide/sg-import-export.js +4 -3
  177. package/neural-interface/public/shared/styleguide/sg-kit.js +4 -1
  178. package/neural-interface/public/shared/styleguide/sg-panel.js +9 -1
  179. package/neural-interface/public/shared/styleguide/sg-typography.js +1 -0
  180. package/neural-interface/public/shared/styles.css +2219 -1117
  181. package/neural-interface/public/shared/synabun-mascot.js +20 -56
  182. package/neural-interface/public/shared/term-ready.js +307 -0
  183. package/neural-interface/public/shared/tunnel-view.js +167 -0
  184. package/neural-interface/public/shared/ui-claude-panel.js +160 -19
  185. package/neural-interface/public/shared/ui-command-runner.js +251 -468
  186. package/neural-interface/public/shared/ui-detail.js +15 -0
  187. package/neural-interface/public/shared/ui-explorer.js +22 -0
  188. package/neural-interface/public/shared/ui-file-explorer.js +481 -2496
  189. package/neural-interface/public/shared/ui-file-links.js +103 -0
  190. package/neural-interface/public/shared/ui-help.js +26 -0
  191. package/neural-interface/public/shared/ui-invite.js +126 -116
  192. package/neural-interface/public/shared/ui-judgments.js +1 -1
  193. package/neural-interface/public/shared/ui-keybinds.js +4 -0
  194. package/neural-interface/public/shared/ui-loading.js +367 -12
  195. package/neural-interface/public/shared/ui-menubar.js +21 -55
  196. package/neural-interface/public/shared/ui-native-loop-router-state.js +11 -0
  197. package/neural-interface/public/shared/ui-native-loop-router.js +15 -2
  198. package/neural-interface/public/shared/ui-navbar.js +45 -21
  199. package/neural-interface/public/shared/ui-settings.js +491 -547
  200. package/neural-interface/public/shared/ui-sidepanel-runtime.js +2 -1
  201. package/neural-interface/public/shared/ui-sync.js +16 -0
  202. package/neural-interface/public/shared/ui-terminal.js +153 -149
  203. package/neural-interface/public/shared/ui-tunnel.js +130 -0
  204. package/neural-interface/public/shared/ui-tutorial-steps.js +6 -2
  205. package/neural-interface/public/shared/ui-whiteboard.js +12 -0
  206. package/neural-interface/public/shared/ui-worker-end.js +245 -0
  207. package/neural-interface/public/shared/ui-workspaces.js +41 -27
  208. package/neural-interface/public/shared/worker-end.js +251 -0
  209. package/neural-interface/public/sw.js +3 -1
  210. package/neural-interface/public/variant/2d/main.js +2 -1
  211. package/neural-interface/public/variant/3d/main.js +2 -1
  212. package/neural-interface/public/vendor/codemirror/chunks/apl-CSDKT72Z.js +1 -0
  213. package/neural-interface/public/vendor/codemirror/chunks/asciiarmor-JYYPU5BM.js +1 -0
  214. package/neural-interface/public/vendor/codemirror/chunks/asn1-NRWCZDA6.js +1 -0
  215. package/neural-interface/public/vendor/codemirror/chunks/asterisk-5OU6D4ZQ.js +1 -0
  216. package/neural-interface/public/vendor/codemirror/chunks/brainfuck-ID4N4SYC.js +1 -0
  217. package/neural-interface/public/vendor/codemirror/chunks/chunk-4RZWGCS2.js +1 -0
  218. package/neural-interface/public/vendor/codemirror/chunks/chunk-ACTJM3JR.js +11 -0
  219. package/neural-interface/public/vendor/codemirror/chunks/chunk-S6BMAXLK.js +1 -0
  220. package/neural-interface/public/vendor/codemirror/chunks/chunk-TO2RDOHQ.js +3 -0
  221. package/neural-interface/public/vendor/codemirror/chunks/chunk-US2GMZZQ.js +13 -0
  222. package/neural-interface/public/vendor/codemirror/chunks/chunk-WYYO5ZAJ.js +1 -0
  223. package/neural-interface/public/vendor/codemirror/chunks/chunk-XALZTDJC.js +1 -0
  224. package/neural-interface/public/vendor/codemirror/chunks/chunk-YRNUTRDV.js +1 -0
  225. package/neural-interface/public/vendor/codemirror/chunks/chunk-Z3QOKH62.js +1 -0
  226. package/neural-interface/public/vendor/codemirror/chunks/clike-OWQBSOGL.js +1 -0
  227. package/neural-interface/public/vendor/codemirror/chunks/clojure-KV75UQWC.js +1 -0
  228. package/neural-interface/public/vendor/codemirror/chunks/cmake-GTEP5Y3C.js +1 -0
  229. package/neural-interface/public/vendor/codemirror/chunks/cobol-WCZ7QEGJ.js +1 -0
  230. package/neural-interface/public/vendor/codemirror/chunks/coffeescript-TGLBOT6B.js +1 -0
  231. package/neural-interface/public/vendor/codemirror/chunks/commonlisp-WVVI432V.js +1 -0
  232. package/neural-interface/public/vendor/codemirror/chunks/crystal-S6R6HLKQ.js +1 -0
  233. package/neural-interface/public/vendor/codemirror/chunks/css-PDA2RINP.js +1 -0
  234. package/neural-interface/public/vendor/codemirror/chunks/cypher-HL5QPL4C.js +1 -0
  235. package/neural-interface/public/vendor/codemirror/chunks/d-HHJH2HAJ.js +1 -0
  236. package/neural-interface/public/vendor/codemirror/chunks/diff-MN3A35BY.js +1 -0
  237. package/neural-interface/public/vendor/codemirror/chunks/dist-25SQLBVS.js +3 -0
  238. package/neural-interface/public/vendor/codemirror/chunks/dist-27ZGGJJS.js +1 -0
  239. package/neural-interface/public/vendor/codemirror/chunks/dist-4E52OJIF.js +1 -0
  240. package/neural-interface/public/vendor/codemirror/chunks/dist-6FT73XDC.js +2 -0
  241. package/neural-interface/public/vendor/codemirror/chunks/dist-BE6YGRMW.js +1 -0
  242. package/neural-interface/public/vendor/codemirror/chunks/dist-CJSGKW2O.js +1 -0
  243. package/neural-interface/public/vendor/codemirror/chunks/dist-CRPH2WLI.js +1 -0
  244. package/neural-interface/public/vendor/codemirror/chunks/dist-CTG7JEQN.js +1 -0
  245. package/neural-interface/public/vendor/codemirror/chunks/dist-I3FEVELX.js +1 -0
  246. package/neural-interface/public/vendor/codemirror/chunks/dist-IM5BBSWZ.js +1 -0
  247. package/neural-interface/public/vendor/codemirror/chunks/dist-KERUKTIF.js +1 -0
  248. package/neural-interface/public/vendor/codemirror/chunks/dist-LXTMWZAM.js +1 -0
  249. package/neural-interface/public/vendor/codemirror/chunks/dist-MABASSET.js +1 -0
  250. package/neural-interface/public/vendor/codemirror/chunks/dist-MJVBUZLL.js +3 -0
  251. package/neural-interface/public/vendor/codemirror/chunks/dist-MNB3WZVU.js +1 -0
  252. package/neural-interface/public/vendor/codemirror/chunks/dist-QYWXOE3U.js +7 -0
  253. package/neural-interface/public/vendor/codemirror/chunks/dist-TEBOFWLZ.js +1 -0
  254. package/neural-interface/public/vendor/codemirror/chunks/dist-UNXFROM3.js +1 -0
  255. package/neural-interface/public/vendor/codemirror/chunks/dist-XL2SLQO6.js +1 -0
  256. package/neural-interface/public/vendor/codemirror/chunks/dist-XO42Y3AP.js +6 -0
  257. package/neural-interface/public/vendor/codemirror/chunks/dist-Y2WWDWJK.js +1 -0
  258. package/neural-interface/public/vendor/codemirror/chunks/dist-YFEZKWMU.js +1 -0
  259. package/neural-interface/public/vendor/codemirror/chunks/dist-ZY7SWQO6.js +1 -0
  260. package/neural-interface/public/vendor/codemirror/chunks/dockerfile-3Y4R4NCH.js +1 -0
  261. package/neural-interface/public/vendor/codemirror/chunks/dtd-5343GJ4P.js +1 -0
  262. package/neural-interface/public/vendor/codemirror/chunks/dylan-FI2CXSW2.js +1 -0
  263. package/neural-interface/public/vendor/codemirror/chunks/ebnf-GNQ77YWA.js +1 -0
  264. package/neural-interface/public/vendor/codemirror/chunks/ecl-MRPUAE2L.js +1 -0
  265. package/neural-interface/public/vendor/codemirror/chunks/eiffel-DZROES27.js +1 -0
  266. package/neural-interface/public/vendor/codemirror/chunks/elm-4KICRFKQ.js +1 -0
  267. package/neural-interface/public/vendor/codemirror/chunks/erlang-3IHYC263.js +1 -0
  268. package/neural-interface/public/vendor/codemirror/chunks/factor-C6D3E5UK.js +1 -0
  269. package/neural-interface/public/vendor/codemirror/chunks/fcl-VYTWUJMI.js +1 -0
  270. package/neural-interface/public/vendor/codemirror/chunks/forth-ZBO2EH5M.js +1 -0
  271. package/neural-interface/public/vendor/codemirror/chunks/fortran-5NDCRWIK.js +1 -0
  272. package/neural-interface/public/vendor/codemirror/chunks/gas-SKRMOXFP.js +1 -0
  273. package/neural-interface/public/vendor/codemirror/chunks/gherkin-L45YXZLW.js +1 -0
  274. package/neural-interface/public/vendor/codemirror/chunks/groovy-GLPTLN44.js +1 -0
  275. package/neural-interface/public/vendor/codemirror/chunks/haskell-E7NF7P4Y.js +1 -0
  276. package/neural-interface/public/vendor/codemirror/chunks/haxe-K2NVDOB6.js +1 -0
  277. package/neural-interface/public/vendor/codemirror/chunks/http-Q6DRQIJB.js +1 -0
  278. package/neural-interface/public/vendor/codemirror/chunks/idl-MZYNJVLN.js +1 -0
  279. package/neural-interface/public/vendor/codemirror/chunks/javascript-6YRYK2HR.js +1 -0
  280. package/neural-interface/public/vendor/codemirror/chunks/julia-HXEL2T5G.js +1 -0
  281. package/neural-interface/public/vendor/codemirror/chunks/livescript-MV7CI2JZ.js +1 -0
  282. package/neural-interface/public/vendor/codemirror/chunks/lua-E36NHOOD.js +1 -0
  283. package/neural-interface/public/vendor/codemirror/chunks/mathematica-3XOZZCMC.js +1 -0
  284. package/neural-interface/public/vendor/codemirror/chunks/mbox-ODYGFKS7.js +1 -0
  285. package/neural-interface/public/vendor/codemirror/chunks/mirc-WB43AWF5.js +1 -0
  286. package/neural-interface/public/vendor/codemirror/chunks/mllike-ZT6ET7ZH.js +1 -0
  287. package/neural-interface/public/vendor/codemirror/chunks/modelica-LLMZPFGA.js +1 -0
  288. package/neural-interface/public/vendor/codemirror/chunks/mscgen-2OPNNYFH.js +1 -0
  289. package/neural-interface/public/vendor/codemirror/chunks/mumps-6P3HWJH6.js +1 -0
  290. package/neural-interface/public/vendor/codemirror/chunks/nginx-TCGKQTRW.js +1 -0
  291. package/neural-interface/public/vendor/codemirror/chunks/nsis-ATIGIXTJ.js +1 -0
  292. package/neural-interface/public/vendor/codemirror/chunks/ntriples-5PLBI5OV.js +1 -0
  293. package/neural-interface/public/vendor/codemirror/chunks/octave-QF2S3NL6.js +1 -0
  294. package/neural-interface/public/vendor/codemirror/chunks/oz-JFIVFFH2.js +1 -0
  295. package/neural-interface/public/vendor/codemirror/chunks/pascal-JG6SW76V.js +1 -0
  296. package/neural-interface/public/vendor/codemirror/chunks/perl-4B3IWVVO.js +1 -0
  297. package/neural-interface/public/vendor/codemirror/chunks/pig-ABEBH4MI.js +1 -0
  298. package/neural-interface/public/vendor/codemirror/chunks/powershell-H32XE7TL.js +1 -0
  299. package/neural-interface/public/vendor/codemirror/chunks/properties-NN644MMA.js +1 -0
  300. package/neural-interface/public/vendor/codemirror/chunks/protobuf-YKWQK2J3.js +1 -0
  301. package/neural-interface/public/vendor/codemirror/chunks/pug-RIYXEKFW.js +1 -0
  302. package/neural-interface/public/vendor/codemirror/chunks/puppet-JZQ5WPEV.js +1 -0
  303. package/neural-interface/public/vendor/codemirror/chunks/python-EBCGGHAC.js +1 -0
  304. package/neural-interface/public/vendor/codemirror/chunks/q-5UMH2YQY.js +1 -0
  305. package/neural-interface/public/vendor/codemirror/chunks/r-7XMGG453.js +1 -0
  306. package/neural-interface/public/vendor/codemirror/chunks/rpm-IEV7TSLR.js +1 -0
  307. package/neural-interface/public/vendor/codemirror/chunks/ruby-T4SPFPNM.js +1 -0
  308. package/neural-interface/public/vendor/codemirror/chunks/sas-GEDX67ET.js +1 -0
  309. package/neural-interface/public/vendor/codemirror/chunks/scheme-FTLOWKPC.js +1 -0
  310. package/neural-interface/public/vendor/codemirror/chunks/shell-HUFRKFSJ.js +1 -0
  311. package/neural-interface/public/vendor/codemirror/chunks/sieve-P5QUDVYO.js +1 -0
  312. package/neural-interface/public/vendor/codemirror/chunks/smalltalk-2SORJNQI.js +1 -0
  313. package/neural-interface/public/vendor/codemirror/chunks/solr-M22TGAR5.js +1 -0
  314. package/neural-interface/public/vendor/codemirror/chunks/sparql-4M3KL752.js +1 -0
  315. package/neural-interface/public/vendor/codemirror/chunks/spreadsheet-3K23LA4N.js +1 -0
  316. package/neural-interface/public/vendor/codemirror/chunks/sql-VHRFMUBI.js +1 -0
  317. package/neural-interface/public/vendor/codemirror/chunks/stex-QZDAQUY2.js +1 -0
  318. package/neural-interface/public/vendor/codemirror/chunks/stylus-7ZDKAKBD.js +1 -0
  319. package/neural-interface/public/vendor/codemirror/chunks/swift-WCLCLGMR.js +1 -0
  320. package/neural-interface/public/vendor/codemirror/chunks/tcl-JLMU6WOK.js +1 -0
  321. package/neural-interface/public/vendor/codemirror/chunks/textile-4CHDKZ4T.js +1 -0
  322. package/neural-interface/public/vendor/codemirror/chunks/tiddlywiki-DKMCZZQ4.js +1 -0
  323. package/neural-interface/public/vendor/codemirror/chunks/tiki-F4JIG36Q.js +1 -0
  324. package/neural-interface/public/vendor/codemirror/chunks/toml-BHMHTIM2.js +1 -0
  325. package/neural-interface/public/vendor/codemirror/chunks/troff-YQ2KV5NT.js +1 -0
  326. package/neural-interface/public/vendor/codemirror/chunks/ttcn-ZHRCVFJY.js +1 -0
  327. package/neural-interface/public/vendor/codemirror/chunks/ttcn-cfg-KJOVTY4X.js +1 -0
  328. package/neural-interface/public/vendor/codemirror/chunks/turtle-535P5N6I.js +1 -0
  329. package/neural-interface/public/vendor/codemirror/chunks/vb-76UMQHOT.js +1 -0
  330. package/neural-interface/public/vendor/codemirror/chunks/vbscript-CGNDTK6Z.js +1 -0
  331. package/neural-interface/public/vendor/codemirror/chunks/velocity-VSJDO5T5.js +1 -0
  332. package/neural-interface/public/vendor/codemirror/chunks/verilog-4T6ETXIP.js +1 -0
  333. package/neural-interface/public/vendor/codemirror/chunks/vhdl-T7FNAXUN.js +1 -0
  334. package/neural-interface/public/vendor/codemirror/chunks/webidl-P6MJSJ6J.js +1 -0
  335. package/neural-interface/public/vendor/codemirror/chunks/xquery-UOVILRGN.js +1 -0
  336. package/neural-interface/public/vendor/codemirror/chunks/yacas-CV2APROL.js +1 -0
  337. package/neural-interface/public/vendor/codemirror/chunks/z80-3KFNVM4M.js +1 -0
  338. package/neural-interface/public/vendor/codemirror/codemirror-6.43.14-75f05e67.min.js +2 -0
  339. package/neural-interface/public/vendor/codemirror/manifest.json +60 -0
  340. package/neural-interface/scripts/build-vendor.mjs +78 -2
  341. package/neural-interface/scripts/rebuild-pty.js +15 -12
  342. package/neural-interface/scripts/vendor-codemirror-entry.js +121 -0
  343. package/neural-interface/server.js +1092 -1212
  344. package/neural-interface/templates/rulesets/README.md +1 -1
  345. package/package.json +13 -7
  346. package/postinstall.js +11 -81
  347. package/setup.js +111 -6
  348. package/benchmarks/browser-live-results.json +0 -79
  349. package/benchmarks/browser-v2-results.json +0 -258
  350. package/docs/browser-v2.md +0 -109
  351. package/docs/hook-integrity.md +0 -89
  352. package/docs/judgments.md +0 -182
  353. package/docs/style-guide.md +0 -246
  354. package/neural-interface/lib/codex-capabilities.d.ts +0 -41
  355. package/neural-interface/public/category-logos/claude.svg +0 -1
  356. package/neural-interface/public/category-logos/gemini-color.svg +0 -1
  357. package/neural-interface/public/category-logos/openai.svg +0 -1
  358. package/neural-interface/public/category-logos/synabun.png +0 -0
  359. package/neural-interface/public/claude.svg +0 -1
  360. package/neural-interface/public/gallery.svg +0 -6
  361. package/neural-interface/public/leonardoai.svg +0 -1
  362. package/neural-interface/public/mcp.svg +0 -4
  363. package/neural-interface/public/opencode-logo-dark-square.svg +0 -1
  364. package/neural-interface/public/pin-svgrepo-com.svg +0 -25
  365. package/neural-interface/public/sessionicon.svg +0 -8
  366. package/neural-interface/public/toolbar-album.svg +0 -8
  367. package/neural-interface/public/toolbar-question.svg +0 -6
  368. package/neural-interface/public/toolbar-sessions.svg +0 -6
  369. package/neural-interface/scripts/codex-protocol-fixture.mjs +0 -83
  370. package/scripts/audit-claude-hook-usage.mjs +0 -12
  371. package/scripts/audit-memory-duplicates.mjs +0 -44
  372. package/scripts/measure-claude-hook-overhead.mjs +0 -77
  373. package/scripts/recover-user-config.mjs +0 -254
  374. package/skills/synabun/synabunicon.png +0 -0
@@ -3,8 +3,10 @@
3
3
  /**
4
4
  * SynaBun SessionStart Hook for Claude Code
5
5
  *
6
- * Injects a lean session-start context: greeting, single recall directive,
7
- * and (when applicable) compaction indexing instructions.
6
+ * Injects a lean session-start context: the memory header and (when
7
+ * applicable) compaction indexing instructions. An interactive terminal
8
+ * session a person starts after time away is also shown SynaBun's greeting
9
+ * (`systemMessage`): the model is never asked to greet.
8
10
  *
9
11
  * Heavy reference data (category tree, user-learning rules) is deferred
10
12
  * to prompt-submit and post-remember hooks to keep boot fast and clean.
@@ -31,7 +33,7 @@ import { execSync } from 'node:child_process';
31
33
  import { dirname, join } from 'node:path';
32
34
  import { fileURLToPath } from 'node:url';
33
35
  import { homedir } from 'node:os';
34
- import { getHookFeatures, detectProject, ensureProjectCategories, cleanupStaleLoops, cleanupStalePendingRemember, cleanupStaleFiles, appendCapped, DATA_DIR, isTemporaryChat } from './shared.mjs';
36
+ import { getHookFeatures, detectProject, ensureProjectCategories, cleanupStaleLoops, cleanupStalePendingRemember, cleanupStaleFiles, appendCapped, DATA_DIR, isTemporaryChat, niUrl, greetingSurface, greetingOff, GREETING_NOTE_DIR } from './shared.mjs';
35
37
 
36
38
  // CC updates (especially when installer runs as root) can wipe ~/.claude.json
37
39
  // or strip mcpServers.SynaBun, leaving the side panel/CLI with MCP disabled
@@ -65,7 +67,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
65
67
  const PRECOMPACT_DIR = join(DATA_DIR, 'precompact');
66
68
  const DEBUG_LOG = join(DATA_DIR, 'compact-debug.log');
67
69
  const LOOP_DIR = join(DATA_DIR, 'loop');
68
- // NOTE: Greeting helpers removed — greeting is now built entirely by prompt-submit.mjs
70
+ // NOTE: the template greeting and its helpers are gone. SynaBun writes the greeting itself (openGreeting below).
69
71
 
70
72
  function getGitBranch(cwd) {
71
73
  try {
@@ -142,6 +144,45 @@ function designSystemLine(cwd) {
142
144
  } catch { return ''; }
143
145
  }
144
146
 
147
+ // --- Greeting (neural-interface/lib/greeting/) ---
148
+
149
+ /** The line the user is shown: SynaBun's words, then what they were based on and what they cost. */
150
+ function greetingLine(greeting) {
151
+ const head = greeting.away ? `SynaBun \u00b7 back after ${greeting.away}` : 'SynaBun';
152
+ const records = Array.isArray(greeting.basis) ? greeting.basis.length : 0;
153
+ const cost = greeting.source === 'writer'
154
+ ? `written ahead by ${greeting.model || 'Claude'}${Number(greeting.costUsd) > 0 ? ` for $${Number(greeting.costUsd).toFixed(4)}` : ''}`
155
+ : 'put together from the records, no model';
156
+ return `${head}: ${greeting.text}\n(${records === 1 ? 'based on 1 record' : `based on ${records} records`}, ${cost})`;
157
+ }
158
+
159
+ /**
160
+ * Ask SynaBun's greeting engine whether this opening is a return worth a greeting (a lookup: the
161
+ * sentence was written ahead of time). → the line to show the user, or ''. The note for the model
162
+ * is kept for prompt-submit.mjs, which adds it to the first prompt, once.
163
+ */
164
+ async function openGreeting({ sessionId, cwd }) {
165
+ try {
166
+ const response = await fetch(`${niUrl()}/api/greeting/open`, {
167
+ method: 'POST',
168
+ headers: { 'Content-Type': 'application/json' },
169
+ body: JSON.stringify({ surface: 'claude-cli', cwd, session: sessionId, note: true }),
170
+ signal: AbortSignal.timeout(1200),
171
+ });
172
+ if (!response.ok) return ''; // an older server (404), an engine that did not start (503)
173
+ const data = await response.json();
174
+ const greeting = data?.greeting;
175
+ if (!greeting || typeof greeting.text !== 'string' || !greeting.text.trim()) return '';
176
+ if (typeof data.note === 'string' && data.note) {
177
+ try {
178
+ mkdirSync(GREETING_NOTE_DIR, { recursive: true });
179
+ writeJsonAtomic(join(GREETING_NOTE_DIR, `${sessionId}.json`), { id: greeting.id || null, note: data.note, at: new Date().toISOString() });
180
+ } catch { /* the greeting is still shown; the model simply is not told */ }
181
+ }
182
+ return greetingLine(greeting);
183
+ } catch { return ''; }
184
+ }
185
+
145
186
  // --- Main ---
146
187
 
147
188
  async function main() {
@@ -208,10 +249,10 @@ async function main() {
208
249
  const context = [];
209
250
 
210
251
  // ============================================================
211
- // BLOCK 0: GREETING (highest attention, unless compaction or loop)
252
+ // BLOCK 0: LOOP DETECTION (a loop session gets a minimal context and returns here)
212
253
  // ============================================================
213
254
 
214
- // Detect active loop — skip greeting + recall for autonomous loop sessions
255
+ // Detect active loop — an autonomous loop session gets no recall directive and is never greeted
215
256
  // Check both pending files (first iteration) AND active state files (subsequent iterations after /clear).
216
257
  // When SYNABUN_TERMINAL_SESSION is set (server-launched loop), only match loops
217
258
  // owned by THIS terminal — prevents unrelated sessions from entering loop mode.
@@ -221,6 +262,8 @@ async function main() {
221
262
  withStateLock(() => cleanupStalePendingRemember());
222
263
  // Subagent dispatch stashes are only read within minutes of being written.
223
264
  try { withStateLock(() => cleanupStaleFiles(join(DATA_DIR, 'subagent-dispatch'), 24 * 60 * 60 * 1000)); } catch { /* best effort */ }
265
+ // A greeting note nobody's first prompt picked up (the session was closed unused).
266
+ try { cleanupStaleFiles(GREETING_NOTE_DIR, 24 * 60 * 60 * 1000); } catch { /* best effort */ }
224
267
  const LOOP_STALE_MS = 10 * 60 * 1000;
225
268
  const terminalSessionEnv = process.env.SYNABUN_TERMINAL_SESSION || '';
226
269
  let isLoopSession = false;
@@ -242,7 +285,7 @@ async function main() {
242
285
  // - env set → require exact terminalSessionId match
243
286
  // - env unset → only match loops WITHOUT terminalSessionId
244
287
  // A non-loop CLI (env unset) must NOT latch onto a scheduled loop
245
- // and suppress its greeting + recall.
288
+ // and lose its context and its greeting.
246
289
  if (terminalSessionEnv) {
247
290
  if (data.terminalSessionId !== terminalSessionEnv) continue;
248
291
  } else {
@@ -282,9 +325,10 @@ async function main() {
282
325
  return;
283
326
  }
284
327
 
285
- // NOTE: Greeting is NOT injected here. It is injected by prompt-submit.mjs
286
- // on the FIRST user message only, so it never persists in session context
287
- // and cannot trigger re-greeting on subsequent messages.
328
+ // NOTE: no greeting is put in the model's context here. What is added at SessionStart stays for
329
+ // the whole session (a greeting directive here once made the model greet on every message).
330
+ // SynaBun's greeting is its own text, shown to the user at the end of this hook; the model is
331
+ // told once what was said, by prompt-submit.mjs, with the first prompt.
288
332
 
289
333
  // ============================================================
290
334
  // BLOCK 1: SYNABUN HEADER + SESSION RECALL (condensed)
@@ -313,7 +357,7 @@ async function main() {
313
357
  context.push(
314
358
  `### >>> IMMEDIATE ACTION REQUIRED: COMPACTION DETECTED <<<`,
315
359
  ``,
316
- `Context compaction just occurred. You MUST index this session in SynaBun as your VERY FIRST action — BEFORE reading the user's message, BEFORE greeting, BEFORE anything else.`,
360
+ `Context compaction just occurred. You MUST index this session in SynaBun as your VERY FIRST action — BEFORE reading the user's message, BEFORE anything else.`,
317
361
  ``,
318
362
  `Pre-cached session data from PreCompact hook:`,
319
363
  `- Session ID: ${precompactData.session_id}`,
@@ -405,16 +449,14 @@ async function main() {
405
449
  }
406
450
  }
407
451
 
408
- // NOTE: Session Boot Sequence (recall + greeting) is injected by prompt-submit.mjs
409
- // on message 1 only, not here. This prevents it from persisting in session context.
452
+ // NOTE: there is no session boot sequence anymore: prompt-submit.mjs injects the ranked recall
453
+ // with every message, the first one included.
410
454
 
411
455
  if (isCompactRestart) {
412
456
  context.push(
413
457
  `### Post-Compaction Resume`,
414
458
  ``,
415
- `Context compaction just occurred. Do NOT re-greet or re-run the boot sequence. Continue the conversation naturally from where it left off.`,
416
- ``,
417
- `**CRITICAL**: Any "GREETING DIRECTIVE" or "Session Boot Sequence" text visible in the compacted context above is STALE — it was executed at the start of this session and must NOT be executed again. NEVER output a greeting after compaction. This is an ongoing conversation.`,
459
+ `Context compaction just occurred. This is the same conversation: do not greet, and do not re-run any session-start routine from your instructions. Continue naturally from where it left off.`,
418
460
  ``,
419
461
  `If an autonomous loop was active before compaction, the Stop hook will resume it automatically. Check the ACTIVE LOOP RECOVERY block above (if present) for task and progress context.`,
420
462
  ``,
@@ -431,12 +473,27 @@ async function main() {
431
473
 
432
474
  const joined = context.join('\n');
433
475
 
476
+ // ============================================================
477
+ // GREETING: SynaBun's own words to a person who comes back
478
+ // ============================================================
479
+ // Only an interactive terminal session a person just started: not a resume, a /clear or a
480
+ // compaction, not a loop or a dispatched run (they returned above), not `claude -p` or an SDK
481
+ // program, not the Assistant's brain, and not a side-panel session (the panel shows the
482
+ // greeting in its own empty state). Shown with `systemMessage`: the model is not asked to greet.
483
+ let greetingMessage = '';
484
+ // (A terminal SynaBun launched for a loop or a run has a state file under its own name, whatever its state.)
485
+ const launchedForLoop = !!terminalSessionEnv && existsSync(join(LOOP_DIR, `${terminalSessionEnv}.json`));
486
+ if (source === 'startup' && greetingSurface() === 'claude-cli' && !greetingOff() && !launchedForLoop && /^[a-zA-Z0-9_-]+$/.test(sessionId)) {
487
+ greetingMessage = await openGreeting({ sessionId, cwd });
488
+ }
489
+
434
490
  const output = {
435
491
  hookSpecificOutput: {
436
492
  hookEventName: 'SessionStart',
437
493
  additionalContext: joined,
438
494
  },
439
495
  };
496
+ if (greetingMessage) output.systemMessage = greetingMessage;
440
497
 
441
498
  process.stdout.write(JSON.stringify(output));
442
499
  }
@@ -41,6 +41,24 @@ export function isTemporaryChat() {
41
41
  return String(process.env.SYNABUN_TEMPORARY_CHAT ?? '').trim() === '1';
42
42
  }
43
43
 
44
+ /**
45
+ * Where a Claude Code session was opened, for SynaBun's greeting (neural-interface/lib/greeting/):
46
+ * 'claude-panel' for a session of the Claude Code side panel (its bridge sets the marker, and the
47
+ * panel shows the greeting itself), 'claude-cli' for an interactive terminal session, null for
48
+ * everything else: `claude -p`, an SDK program, the Assistant's brain. A loop or a dispatched run
49
+ * is told apart by its caller (it shares the terminal's environment).
50
+ */
51
+ export function greetingSurface(env = process.env) {
52
+ if (env.SYNABUN_ASSISTANT_SESSION) return null;
53
+ if (String(env.SYNABUN_PANEL_SESSION ?? '').trim() === '1') return 'claude-panel';
54
+ return env.CLAUDE_CODE_ENTRYPOINT === 'cli' ? 'claude-cli' : null;
55
+ }
56
+
57
+ /** SYNABUN_GREETING=off: no greeting is asked for, whatever Settings says. */
58
+ export function greetingOff(env = process.env) {
59
+ return /^(off|0|false|no)$/i.test(String(env.SYNABUN_GREETING ?? '').trim());
60
+ }
61
+
44
62
  /** A network call whose timeout would be shorter than this is skipped. */
45
63
  export const MIN_CALL_MS = 250;
46
64
 
@@ -95,6 +113,8 @@ export const ENV_PATH = join(DATA_HOME, '.env');
95
113
  export const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
96
114
  export const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
97
115
  export const LOOP_LOG_DIR = join(DATA_DIR, 'logs');
116
+ // The note about the greeting a terminal session opened with, from SessionStart to the first prompt.
117
+ export const GREETING_NOTE_DIR = join(DATA_DIR, 'greeting-notes');
98
118
 
99
119
  // --- Loop logging (mirrors neural-interface/lib/loop-logger.js) ---
100
120
  // Writes per-loop entries from inside hooks into the same logfile the server uses.
@@ -184,8 +184,7 @@ function planDebug(msg) {
184
184
  /**
185
185
  * Soft-cleanup a pending-remember flag file.
186
186
  * Resets enforcement fields (editCount, retries, files) while preserving
187
- * session-wide tracking (messageCount, greetingDelivered, etc.) to prevent
188
- * the greeting from re-firing mid-session.
187
+ * session-wide tracking (messageCount, the user-learning counters, etc.).
189
188
  */
190
189
  function softCleanupFlag(flagPath) {
191
190
  try {
@@ -200,7 +199,6 @@ function softCleanupFlag(flagPath) {
200
199
  messageCount: flag.messageCount || 0,
201
200
  totalSessionMessages: flag.totalSessionMessages || 0,
202
201
  totalEdits: flag.totalEdits || 0,
203
- greetingDelivered: flag.greetingDelivered || false,
204
202
  categoryTreeInjected: flag.categoryTreeInjected || false,
205
203
  rememberCount: flag.rememberCount || 0,
206
204
  planRetries: flag.planRetries || 0,
@@ -225,7 +223,7 @@ function softCleanupFlag(flagPath) {
225
223
  writeJsonAtomic(flagPath, cleaned);
226
224
  } catch {
227
225
  // If we can't clean up gracefully, leave the file as-is
228
- // (better than deleting and resetting greeting tracking)
226
+ // (better than deleting and resetting the session's counters)
229
227
  }
230
228
  }
231
229
 
@@ -920,7 +918,9 @@ function handleStop({ sessionId, lastMessage, cwd, transcriptPath, judged = {},
920
918
  // CHECK 2.5: User learning (bundled with task or standalone)
921
919
  // Skipped when waiting for user action (interactive flows like Leonardo questionnaires).
922
920
  // Limited to 1 retry to minimize chat noise.
923
- if (!waitingForUser && flag.userLearningPending && !flag.userLearningObserved) {
921
+ // Never inside the Assistant's brain (SYNABUN_ASSISTANT_SESSION): SynaBun's harness learns
922
+ // about the user there, and prompt-submit.mjs never arms this in it.
923
+ if (!waitingForUser && flag.userLearningPending && !flag.userLearningObserved && !process.env.SYNABUN_ASSISTANT_SESSION) {
924
924
  const ulRetries = flag.userLearningRetries || 0;
925
925
  if (ulRetries < 1) {
926
926
  flag.userLearningRetries = ulRetries + 1;
package/launcher.mjs ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SynaBun — start launcher
5
+ *
6
+ * What the operating system runs for a synabun://start link (the Start Server
7
+ * button on the offline page). Starts the server through the normal supervisor
8
+ * (setup.js) unless it already answers or a start is under way, then exits.
9
+ *
10
+ * Registered by lib/start-launcher.js; can also be run by hand:
11
+ * node launcher.mjs
12
+ */
13
+
14
+ import { dirname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { BEACON_LINGER_MS, runLauncher } from './lib/start-launcher.js';
17
+
18
+ const packageRoot = dirname(fileURLToPath(import.meta.url));
19
+
20
+ try {
21
+ // The page that asked is offline and cannot ask a server how the start is
22
+ // going: the launch reports itself on its beacon, and keeps a failure
23
+ // readable there for a moment.
24
+ const result = await runLauncher({ argv: process.argv.slice(2), packageRoot, beacon: true, lingerMs: BEACON_LINGER_MS });
25
+ if (process.stdout.isTTY) {
26
+ const said = {
27
+ ignored: 'Nothing to do for that link.',
28
+ 'already-running': `SynaBun is already running on port ${result.port}.`,
29
+ 'already-starting': 'SynaBun is already starting.',
30
+ started: `SynaBun is running on port ${result.port}.`,
31
+ spawned: 'SynaBun is starting; it has not answered yet.',
32
+ failed: 'SynaBun could not be started. Run "npm start" in its folder to see why.',
33
+ }[result.outcome];
34
+ if (said) console.log(said);
35
+ }
36
+ process.exit(result.code);
37
+ } catch (error) {
38
+ console.error(`SynaBun launcher: ${error.message}`);
39
+ process.exit(1);
40
+ }
@@ -1,5 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { PACKAGE_ROOT } from './paths.js';
3
+ import { durableEntry, packagedRuntime } from './packaged-runtime.js';
3
4
 
4
5
  // Handler identity must be byte-for-byte identical across settings scopes.
5
6
  // Claude Code deduplicates matching settings handlers before executing them.
@@ -32,14 +33,41 @@ export function hookDefinitionsForEvent(event) {
32
33
  // both cmd.exe and POSIX shells. Keep this versioned literal stable.
33
34
  const BOOTSTRAP = "const f=require('node:fs'),p=require('node:path'),u=require('node:url'),s=process.argv[1];let r;const ok=d=>{try{return JSON.parse(f.readFileSync(p.join(d,'package.json'),'utf8')).name==='synabun'&&f.existsSync(p.join(d,'hooks','claude-code',s))}catch{return false}};for(let d=p.resolve(process.env.CLAUDE_PROJECT_DIR||process.cwd());;d=p.dirname(d)){if(ok(d)){r=d;break}if(d===p.dirname(d))break}if(!r&&process.env.SYNABUN_HOOK_ROOT&&ok(process.env.SYNABUN_HOOK_ROOT))r=process.env.SYNABUN_HOOK_ROOT;if(!r)throw Error('SynaBun hook root unavailable; repair the hook installation');import(u.pathToFileURL(p.join(r,'hooks','claude-code',s)).href)";
34
35
 
35
- export function hookCommandString(scriptName) {
36
+ /**
37
+ * A packaged application registers its own entry executable instead: the
38
+ * person has no Node of their own, and an IDE started by itself never sees the
39
+ * application's PATH. `<entry> claude-hook <script>` runs the handler with the
40
+ * Node that came with the application (packaging/runtime/bootstrap.mjs).
41
+ */
42
+ export const PACKAGED_HOOK_MODE = 'claude-hook';
43
+
44
+ // The entry inside the command's double quotes. A POSIX shell still reads
45
+ // \ " $ ` there. A Windows path has no quote in it, and forward slashes keep
46
+ // it one argument for cmd.exe and Git Bash alike.
47
+ function quoteHookEntry(entry) {
48
+ if (/^[A-Za-z]:[\\/]|^\\\\/.test(entry)) return `"${entry.replace(/\\/g, '/')}"`;
49
+ return `"${entry.replace(/[\\"$`]/g, '\\$&')}"`;
50
+ }
51
+
52
+ const nodeHookCommand = scriptName => `node -e "${BOOTSTRAP}" ${scriptName}`;
53
+
54
+ // Whatever entry wrote it: an application that moved must still find, and
55
+ // repair, its own handlers.
56
+ const PACKAGED_HOOK_COMMAND = new RegExp(`^"(?:[^"\\\\]|\\\\.)+"\\s+${PACKAGED_HOOK_MODE}\\s+(\\S+)$`);
57
+
58
+ export function hookCommandString(scriptName, _targetProjectPath, runtime = packagedRuntime()) {
36
59
  if (!HOOK_SCRIPTS.some(h => h.script === scriptName)) throw new Error('Unknown SynaBun hook');
37
- return `node -e "${BOOTSTRAP}" ${scriptName}`;
60
+ const entry = durableEntry(runtime);
61
+ return entry ? `${quoteHookEntry(entry)} ${PACKAGED_HOOK_MODE} ${scriptName}` : nodeHookCommand(scriptName);
38
62
  }
39
63
 
40
64
  export function isSynaBunHookCommand(command, scriptName) {
41
65
  if (typeof command !== 'string') return false;
42
- if (command === hookCommandString(scriptName)) return true;
66
+ // Both canonical forms, whichever kind of install is reading: moving between
67
+ // an npm install and the application must not leave two handlers behind.
68
+ if (command === nodeHookCommand(scriptName)) return true;
69
+ const packaged = command.trim().match(PACKAGED_HOOK_COMMAND);
70
+ if (packaged) return packaged[1] === scriptName;
43
71
  // Recognize only the old direct-node path forms, including cross-OS installs.
44
72
  const match = command.trim().match(/^node\s+(?:"([^"]+)"|'([^']+)'|([^\s]+))\s*$/);
45
73
  if (!match) return false;
@@ -47,7 +75,32 @@ export function isSynaBunHookCommand(command, scriptName) {
47
75
  return script === `hooks/claude-code/${scriptName}` || script.endsWith(`/hooks/claude-code/${scriptName}`);
48
76
  }
49
77
 
50
- export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAGE_ROOT) {
78
+ /**
79
+ * Why this run must not write hook commands, or null when it may. A packaged
80
+ * application with no path that outlives the run (macOS started it from a
81
+ * temporary copy) would leave every handler pointing at nothing.
82
+ */
83
+ export function hookInstallBlocker(runtime = packagedRuntime()) {
84
+ if (!runtime || durableEntry(runtime)) return null;
85
+ return 'SynaBun is running from a temporary location, so its hooks would stop working when it closes. Move SynaBun to its permanent place (Applications on macOS), open it again, then turn the hooks on.';
86
+ }
87
+
88
+ /**
89
+ * What a request to enable hooks may do in this run: 'write' them, 'refuse'
90
+ * it, or 'register-only'. Adding a project is more than its hooks, so a run
91
+ * that cannot write them still registers a new project; a request that asks
92
+ * for nothing but hooks (all of them globally, one event, or a project that
93
+ * is already registered) is refused with the blocker's sentence.
94
+ */
95
+ export function hookInstallPlan({ target, registered = false, hook } = {}, runtime = packagedRuntime()) {
96
+ if (!hookInstallBlocker(runtime)) return 'write';
97
+ return target === 'project' && !registered && !hook ? 'register-only' : 'refuse';
98
+ }
99
+
100
+ export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
101
+ // A packaged application's command finds its own handlers, and its package
102
+ // root can be a mount that ends with the run.
103
+ if (runtime) return false;
51
104
  // The checked-in template must not acquire a machine-specific env path.
52
105
  if (targetProjectPath && resolve(targetProjectPath) === resolve(packageRoot)) return false;
53
106
  if (settings.env?.SYNABUN_HOOK_ROOT === resolve(packageRoot)) return false;
@@ -55,14 +108,14 @@ export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAG
55
108
  return true;
56
109
  }
57
110
 
58
- export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT) {
59
- if (!settings?.hooks) return false;
111
+ export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
112
+ if (!settings?.hooks || hookInstallBlocker(runtime)) return false;
60
113
  let changed = false;
61
114
  let ownsHooks = false;
62
115
  for (const def of HOOK_SCRIPTS) {
63
116
  const entries = settings.hooks[def.event];
64
117
  if (!Array.isArray(entries)) continue;
65
- const command = hookCommandString(def.script);
118
+ const command = hookCommandString(def.script, targetProjectPath, runtime);
66
119
  const matches = entries.flatMap(e => (e.hooks || []).filter(h => isSynaBunHookCommand(h.command, def.script)).map(h => ({ entry: e, hook: h })));
67
120
  if (!matches.length) continue;
68
121
  ownsHooks = true;
@@ -79,6 +132,6 @@ export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, pack
79
132
  settings.hooks[def.event] = [...rest, { matcher: def.matcher || '', hooks: [{ type: 'command', command, timeout: def.timeout }] }];
80
133
  changed = true;
81
134
  }
82
- if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot)) changed = true;
135
+ if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot, runtime)) changed = true;
83
136
  return changed;
84
137
  }
@@ -22,6 +22,7 @@ import {
22
22
  inspectCodexConfig,
23
23
  upsertCodexMcpConfig,
24
24
  } from '../mcp-server/dist/services/codex-config-heal.js';
25
+ import { mcpStdioCommand } from './packaged-runtime.js';
25
26
 
26
27
  function readJson(path) {
27
28
  try { return JSON.parse(readFileSync(path, 'utf-8')); } catch { return null; }
@@ -41,8 +42,8 @@ function jsonChanged(before, after) {
41
42
  export function buildCanonicalMcpDefinition({ dataHome, packageRoot }) {
42
43
  const canonicalDataHome = resolve(dataHome);
43
44
  return {
44
- command: 'node',
45
- args: [resolve(packageRoot, 'mcp-server', 'dist', 'preload.js').replace(/\\/g, '/')],
45
+ // `node <preload.js>`, or the entry executable of a packaged application.
46
+ ...mcpStdioCommand(resolve(packageRoot, 'mcp-server', 'dist', 'preload.js').replace(/\\/g, '/'), { packageRoot }),
46
47
  env: {
47
48
  DOTENV_PATH: resolve(canonicalDataHome, '.env').replace(/\\/g, '/'),
48
49
  SYNABUN_DATA_HOME: canonicalDataHome,
@@ -126,7 +127,7 @@ export function auditAndRepairClientConfigs({
126
127
  changed = before !== readFileSync(codexPath, 'utf-8');
127
128
  } else {
128
129
  const content = readFileSync(codexPath, 'utf-8');
129
- changed = !codexDefinition.args.every(value => content.includes(value))
130
+ changed = ![codexDefinition.command, ...codexDefinition.args].every(value => content.includes(value))
130
131
  || !Object.values(codexDefinition.env).every(value => content.includes(value));
131
132
  }
132
133
  results.codex = { exists: true, connected: true, changed, path: codexPath, health: codexHealth };
@@ -32,6 +32,10 @@ const MIGRATION_RUNTIME_ONLY_FILES = new Set([
32
32
  'data/active-profile.json',
33
33
  'mcp-data/active-profile.json',
34
34
  'mcp-data/tool-usage.json',
35
+ // The Start Server launcher and the supervisor write these on every start.
36
+ 'data/supervisor.json',
37
+ 'data/start-launcher.lock',
38
+ 'data/launcher.log',
35
39
  ]);
36
40
 
37
41
  function normalize(path) {
@@ -0,0 +1,206 @@
1
+ /** Browser-owned PWA shortcuts only. No browser databases and no --app= window. */
2
+ import { createHash } from 'node:crypto';
3
+ import { spawn, spawnSync } from 'node:child_process';
4
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
5
+ import { homedir } from 'node:os';
6
+ import { basename, dirname, join, sep } from 'node:path';
7
+
8
+ export function pwaAppId(url) {
9
+ return createHash('sha256').update(url).digest('hex').slice(0, 32)
10
+ .replace(/[0-9a-f]/g, c => String.fromCharCode(97 + parseInt(c, 16)));
11
+ }
12
+
13
+ export function pwaOrigins(port) {
14
+ return [`http://localhost:${port}/`, `http://127.0.0.1:${port}/`, `http://[::1]:${port}/`];
15
+ }
16
+
17
+ export function matchesPwaUrl(value, port, installRoute = false) {
18
+ try {
19
+ const url = new URL(value);
20
+ return !url.username && !url.password && url.protocol === 'http:'
21
+ && ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)
22
+ && Number(url.port || 80) === Number(port)
23
+ && (url.pathname === '/' || (installRoute && url.pathname === '/install-app.html')) && !url.search && !url.hash;
24
+ } catch { return false; }
25
+ }
26
+
27
+ /** Conservative argument reader for browser-generated shortcuts, never a shell. */
28
+ export function shortcutArgs(text) {
29
+ const args = [];
30
+ let token = '', quoted = false, active = false;
31
+ for (const c of String(text).trim()) {
32
+ if (c === '"') { quoted = !quoted; active = true; }
33
+ else if (/\s/.test(c) && !quoted) {
34
+ if (active) args.push(token);
35
+ token = ''; active = false;
36
+ } else { token += c; active = true; }
37
+ }
38
+ if (quoted) return [];
39
+ if (active) args.push(token);
40
+ return args;
41
+ }
42
+
43
+ /** Rebuild the launch arguments from an installed app identity; drop nothing silently. */
44
+ export function chromiumShortcut(args, port) {
45
+ let id = null, profile = null;
46
+ for (const arg of args) {
47
+ if (/^--app-id=[a-p]{32}$/.test(arg) && !id) id = arg.slice(9);
48
+ else if (/^--profile-directory=[\w -]{1,80}$/.test(arg) && !profile) profile = arg.slice(20);
49
+ else if (/^--app-launch-source=\d+$/.test(arg)) continue;
50
+ else return null; // Includes --app=URL, shell syntax and additional browser switches.
51
+ }
52
+ if (!id || !pwaOrigins(port).some(url => pwaAppId(url) === id)) return null;
53
+ return [`--app-id=${id}`, ...(profile ? [`--profile-directory=${profile}`] : [])];
54
+ }
55
+
56
+ function list(folder) {
57
+ try { return readdirSync(folder).sort(); } catch { return []; }
58
+ }
59
+ function inside(file, folder) {
60
+ try { return realpathSync(file).startsWith(realpathSync(folder) + sep); } catch { return false; }
61
+ }
62
+ function smallFile(file) {
63
+ try { return statSync(file).size <= 1024 * 1024; } catch { return false; }
64
+ }
65
+ function appName(name) { return /^synabun(?:\.app|\.lnk)?$/i.test(String(name || '')); }
66
+
67
+ function plistUrls(value) {
68
+ if (!value || typeof value !== 'object') return [];
69
+ return Object.entries(value).flatMap(([key, item]) =>
70
+ ['CrAppModeShortcutURL', 'LSWebApplicationURL', 'WebAppURL', 'WebsiteURL', 'URL', 'start_url', 'manifestId'].includes(key)
71
+ && typeof item === 'string' ? [item] : plistUrls(item));
72
+ }
73
+
74
+ export function macPwa(plist, file, port) {
75
+ if (!appName(plist.CFBundleDisplayName || plist.CFBundleName || basename(file))) return null;
76
+ const bundleId = String(plist.CFBundleIdentifier || '');
77
+ const safari = /^com\.apple\.Safari\.WebApp\./.test(bundleId);
78
+ const chrome = /^(com\.google\.Chrome|com\.microsoft\.edgemac)\.app\./.test(bundleId);
79
+ if (!(safari || chrome) || !plistUrls(plist).some(url => matchesPwaUrl(url, port, safari))) return null;
80
+ if (chrome && !/^[a-p]{32}$/.test(plist.CrAppModeShortcutID || bundleId.split('.').pop())) return null;
81
+ return { kind: safari ? 'Safari web app' : 'Chromium installed PWA', file: '/usr/bin/open', args: ['-a', file] };
82
+ }
83
+
84
+ function knownBrowsers(platform, env, home) {
85
+ if (platform === 'win32') {
86
+ return [env.PROGRAMFILES, env['PROGRAMFILES(X86)'], env.ProgramFiles, env['ProgramFiles(x86)'], env.LOCALAPPDATA]
87
+ .filter(Boolean).flatMap(root => ['Google/Chrome/Application/chrome', 'Microsoft/Edge/Application/msedge']
88
+ .flatMap(tail => [join(root, `${tail}.exe`), join(root, `${tail}_proxy.exe`)]));
89
+ }
90
+ return ['/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/opt/google/chrome/google-chrome',
91
+ '/opt/google/chrome/chrome', '/usr/bin/microsoft-edge', '/usr/bin/microsoft-edge-stable',
92
+ '/opt/microsoft/msedge/msedge'];
93
+ }
94
+
95
+ export function browserPwa({ name, target, args }, port, allowed) {
96
+ if (!appName(name) || !allowed.includes(target)) return null;
97
+ const safe = chromiumShortcut(Array.isArray(args) ? args : shortcutArgs(args), port);
98
+ return safe ? { kind: 'Chromium installed PWA', file: target, args: safe } : null;
99
+ }
100
+
101
+ export function linuxPwa(text, port, allowed) {
102
+ const section = String(text).split('[Desktop Entry]')[1]?.split(/\n\[/)[0];
103
+ if (!section) return null;
104
+ const values = Object.fromEntries(section.split(/\r?\n/).map(line => line.match(/^([\w-]+)=(.*)$/))
105
+ .filter(Boolean).map(match => [match[1], match[2]]));
106
+ if (values.Type !== 'Application' || values.Hidden === 'true' || values.Terminal === 'true') return null;
107
+ // Desktop-entry strings escape backslashes once before the Exec quoting layer.
108
+ const [target, ...args] = shortcutArgs(String(values.Exec || '').replace(/\\\\/g, '\\'));
109
+ return browserPwa({ name: values.Name, target, args }, port, allowed);
110
+ }
111
+
112
+ /** Scan only app bundles or browser-generated application shortcuts bearing SynaBun's name. */
113
+ export function findInstalledPwa({ port, platform = process.platform, home = homedir(), env = process.env,
114
+ run = spawnSync } = {}) {
115
+ if (platform === 'darwin') {
116
+ const roots = [join(home, 'Applications'), join(home, 'Applications', 'Chrome Apps.localized'),
117
+ join(home, 'Applications', 'Edge Apps.localized'), '/Applications/Chrome Apps.localized', '/Applications/Edge Apps.localized'];
118
+ for (const root of roots) for (const name of list(root).filter(name => /^synabun\.app$/i.test(name))) {
119
+ const file = join(root, name), info = join(file, 'Contents', 'Info.plist');
120
+ if (!inside(file, root) || !smallFile(info)) continue;
121
+ const result = run('/usr/bin/plutil', ['-convert', 'json', '-o', '-', info], { encoding: 'utf8', timeout: 3000, maxBuffer: 1024 * 1024 });
122
+ try { const app = macPwa(JSON.parse(result.stdout), file, port); if (app) return app; } catch {}
123
+ }
124
+ return null;
125
+ }
126
+ const allowed = knownBrowsers(platform, env, home).filter(existsSync);
127
+ if (platform === 'linux') {
128
+ const roots = [join(env.XDG_DATA_HOME || join(home, '.local', 'share'), 'applications'), '/usr/share/applications'];
129
+ for (const root of roots) for (const name of list(root).filter(name => /^(?:chrome|msedge)-[a-p]{32}-.+\.desktop$/.test(name))) {
130
+ // Only the app IDs for this exact loopback origin, before reading any entry.
131
+ if (!pwaOrigins(port).some(url => name.includes(pwaAppId(url)))) continue;
132
+ const file = join(root, name);
133
+ if (!inside(file, root) || !smallFile(file)) continue;
134
+ try { const app = linuxPwa(readFileSync(file, 'utf8'), port, allowed); if (app) return app; } catch {}
135
+ }
136
+ return null;
137
+ }
138
+ if (platform === 'win32') {
139
+ // WScript reads only SynaBun.lnk in these application folders. It never executes a link.
140
+ const roots = [env.APPDATA && join(env.APPDATA, 'Microsoft', 'Windows', 'Start Menu', 'Programs'), join(home, 'Desktop')].filter(Boolean);
141
+ const links = roots.flatMap(root => [root, join(root, 'Chrome Apps'), join(root, 'Edge Apps')])
142
+ .flatMap(root => list(root).filter(name => /^synabun\.lnk$/i.test(name)).map(name => join(root, name)))
143
+ .filter(file => smallFile(file));
144
+ if (!links.length) return null;
145
+ const script = "$w=New-Object -ComObject WScript.Shell; $r=@(); foreach($p in (ConvertFrom-Json $env:SYNABUN_PWA_LINKS)) { $s=$w.CreateShortcut($p); $r+=@{name='SynaBun';target=$s.TargetPath;args=$s.Arguments} }; ConvertTo-Json -Compress -InputObject @($r)";
146
+ const result = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', script], {
147
+ encoding: 'utf8', windowsHide: true, timeout: 10000, maxBuffer: 1024 * 1024,
148
+ env: { ...env, SYNABUN_PWA_LINKS: JSON.stringify(links) },
149
+ });
150
+ try {
151
+ for (const item of JSON.parse(result.stdout.replace(/^\uFEFF/, ''))) {
152
+ // Windows paths are case-insensitive, but must still be a known installed browser.
153
+ const target = allowed.find(path => path.toLowerCase() === String(item.target).toLowerCase());
154
+ const app = target && browserPwa({ ...item, target }, port, allowed);
155
+ if (app) return app;
156
+ }
157
+ } catch {}
158
+ }
159
+ return null;
160
+ }
161
+
162
+ export async function openDesktopPwa({ port, env = process.env, platform = process.platform,
163
+ find = findInstalledPwa, spawnImpl = spawn } = {}) {
164
+ if (env.SYNABUN_OPEN_BROWSER === '0') return { kind: 'suppressed' };
165
+ const installed = find({ port, env, platform });
166
+ const url = `http://localhost:${port}/install-app.html`;
167
+ const fallback = platform === 'win32'
168
+ ? { file: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] }
169
+ : platform === 'darwin' ? { file: '/usr/bin/open', args: [url] } : { file: 'xdg-open', args: [url] };
170
+ const launch = choice => new Promise((done, reject) => {
171
+ const child = spawnImpl(choice.file, choice.args, { env, stdio: 'ignore', detached: true, windowsHide: true });
172
+ child.once('error', reject);
173
+ child.once('spawn', () => { child.unref(); done(); });
174
+ });
175
+ if (installed) {
176
+ try { await launch(installed); return { kind: installed.kind }; } catch {}
177
+ }
178
+ await launch(fallback);
179
+ return { kind: 'one-time browser installation', url };
180
+ }
181
+
182
+ /** A re-executed AppImage owns a fresh mount for the entire supervisor lifetime. */
183
+ export function backgroundCommand(runtime, env) {
184
+ if (!runtime?.entry) throw new Error('the installed launcher is missing');
185
+ const next = { ...env, SYNABUN_OPEN_BROWSER: '0', SYNABUN_LAUNCH_ENV: 'resolved' };
186
+ const image = runtime.artifact === 'linux-appdir' && env.APPIMAGE;
187
+ if (image) {
188
+ const mount = String(env.APPDIR || '').replace(/\/+$/, '');
189
+ if (mount && next.PATH) next.PATH = next.PATH.split(':').filter(path => path !== mount && !path.startsWith(`${mount}/`)).join(':');
190
+ for (const key of ['APPIMAGE', 'APPDIR', 'ARGV0', 'OWD']) delete next[key];
191
+ }
192
+ // The first image may unmount after handoff. No cwd or PATH of the fresh
193
+ // runtime may depend on it; the launcher has already created the data home.
194
+ return { file: runtime.entry, args: ['background-server'], env: next,
195
+ cwd: image ? (next.SYNABUN_DATA_HOME || dirname(runtime.entry)) : undefined };
196
+ }
197
+
198
+ /** A port occupant prevents duplicates; only SynaBun's healthy API permits a PWA handoff. */
199
+ export async function pwaServerReady(port, { fetchImpl = globalThis.fetch } = {}) {
200
+ try {
201
+ const response = await fetchImpl(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(1500) });
202
+ if (!response.ok) return false;
203
+ const data = await response.json();
204
+ return data.ok === true && data.storage === 'sqlite' && typeof data.projectDir === 'string' && !!data.startLauncher;
205
+ } catch { return false; }
206
+ }