synabun 2.0.10 → 2.1.0

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 (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
package/CHANGELOG.md CHANGED
@@ -1,3 +1,89 @@
1
+ # SynaBun v.2.1.0
2
+
3
+ SynaBun 2.1.0 adds downloadable release verification reports, improves automated package publishing, and changes Windows clipboard images to explicit paste.
4
+
5
+ ## ✨ New
6
+
7
+ - **Release verification reports** — Desktop downloads and the npm package are checked with VirusTotal before publication. Release notes include analysis dates, engine results, file hashes and links to the reports. These results describe the scans and do not guarantee that a download is safe.
8
+
9
+ ## 🛠 Fixed
10
+
11
+ - **Automated npm publishing** — The npm package is published from the prepared release and verified against the scanned package before desktop downloads become available. This helps keep package installation and desktop updates on the same release.
12
+
13
+ ## ⚙️ Changed
14
+
15
+ - **Explicit clipboard image paste on Windows** — Focus the whiteboard and press Ctrl+V to add a clipboard image. Background clipboard monitoring has been removed; screenshot files in the watched folder still appear automatically. This change addresses a suspected source of antivirus alerts, but resolution of the reported Kaspersky detection has not been confirmed.
16
+
17
+ ---
18
+
19
+ # SynaBun v.2.0.121
20
+
21
+ 2.0.121 fixes repeated Codex MCP tool approval prompts and keeps automatic approvals aligned with the current session policy.
22
+
23
+ ## 🛠 Fixed
24
+
25
+ - **Codex MCP approvals** — Auto-accept handles tool approvals even when Codex omits the tool name, avoiding repeated permission cards. Plan mode and remote read-only sessions continue to apply their restrictions, while questions and authorization prompts still wait for your response.
26
+ - **Assistant routing** — Routing requests no longer need an extra tool permission card when approvals are set to Ask.
27
+
28
+ ---
29
+
30
+ # SynaBun v.2.0.12
31
+
32
+ 2.0.12 makes Skills Studio easier to install from, test and maintain, keeps dispatched workers running through a server restart, and adds a guided tour of the workspace. It also moves OpenCode support to OpenCode 2 and makes the 3D memory map the default for everyone.
33
+
34
+ ## ✨ New
35
+
36
+ - **Skill health and local usage** — Skills Studio shows local evidence of skill use in Claude Code, Codex and OpenCode runs, with native invocations distinguished from inferred reads. Optional periodic checks report integrity and eligibility changes to the Assistant.
37
+ - **A guided workspace tour** — The help button opens a tour on SynaBun's own whiteboard, with pointing arms, handwritten labels and steps that follow the workspace. It supports narrow windows, English and Portuguese, and reduced motion. The handwriting font ships with the app.
38
+ - **Tool lists** — Choose the SynaBun tools and external MCP servers available to a session, worker or automation from reusable tool lists. External server choices report why a requested server cannot be used, with provider context to help recover.
39
+
40
+ ## 🛠 Fixed
41
+
42
+ - **Workers resume after a restart** — Dispatched Claude Code, Codex and OpenCode workers can pause during a SynaBun server restart and resume the same run and provider conversation in the same sidepanel tab.
43
+ - **Server crash recovery** — SynaBun restarts a server that crashes and reports the reason. Desktop recovery shows the failure and recovery state, and a stalled Codex title request no longer holds the server open indefinitely.
44
+ - **MoreLogin attachment and cleanup** — Connecting to a MoreLogin profile preserves its native HTTP cache and WebSocket APIs while keeping SynaBun's network guards. Closing a connected browser session also closes the automation tabs SynaBun opened for it, so they do not return on the profile's next start.
45
+ - **Worker startup and permissions** — Codex workers start correctly when external MCP servers are disabled. Protected OpenCode workers receive MCP tools directly with Code Mode disabled, and Codex Plan mode with automatic approvals no longer repeats permission requests in the Assistant.
46
+ - **Claude Code hooks from the desktop** — Hooks work when SynaBun starts from the macOS Dock. Settings shows their actual state and lets you switch them on or off.
47
+ - **Stopping the intended worker** — A request naming one run stops that run. The Assistant's Stop all control requires a second press before it stops every running worker.
48
+ - **Compact activity and app layout** — Activity bubbles use one busy indicator and at most one ellipsis, with long paths and URLs wrapping at separators. Browser tabs and Safari web apps have consistent spacing between the navigation bar and the app frame, and Codex output and tab layout handle narrow panels more reliably.
49
+
50
+ ## ⚙️ Changed
51
+
52
+ - **Installing approved skills** — Review approval and installation have clearer, separate states. Publishing an approved skill presents one install decision with readable runtime targets, and Claude Code, Codex and OpenCode share the drafting, evaluation and installation workflow. Skill checks cover more shell wrappers, asset references and command forms while keeping passive references distinct from executable instructions.
53
+ - **OpenCode 2 required** — OpenCode 1 support is removed. Every launch path checks the installed version before starting and explains how to install OpenCode 2 when needed.
54
+ - **One memory map** — The memory map uses 3D. Saved 2D preferences and old links open in 3D, and the 2D switch and shortcut have been removed from the interface.
55
+ - **Project test commands** — Browser policy permits test runners inside registered code projects and accepts syntax-only Node checks. Commands that execute preloaded code still receive the normal policy checks.
56
+
57
+ ---
58
+
59
+ # SynaBun v.2.0.11
60
+
61
+ 2.0.11 rebuilds Skills Studio around a skill library: every skill in one list, a workspace to edit, test, version and publish it, native skill folders for Claude Code, Codex and OpenCode, a Review queue, and skills drafted by AI from your own sources. SynaBun's memory is also one command away for any MCP client (`npx -y synabun mcp`), the whole app has a right-click menu, desktop updates show a splash and restart in seconds, and the Schedules window speaks your language.
62
+
63
+ ## ✨ New
64
+
65
+ - **Skills Studio 2.0** — Skills Studio is now a window with three places: Library, Review and Create. The Library lists every skill once, whichever tool or account reads it, with where it comes from, what Claude Code, Codex and OpenCode each have of it, and a health badge for what needs a look (problems, an update available, a skill defined twice, an old-style install); filters, saved views, search and bulk actions keep it in order. A skill opens in a workspace with Edit, Test, Versions and Publish: the app's code editor, a form and the YAML kept as one document, the problems each runtime would report, a description helper, the skill's files, a Markdown preview with diagrams, versions with diffs and restore, and an install of exactly the plan you see. The window is not modal, docks beside the side panels, fits narrow widths and works fully from the keyboard, in English and Portuguese.
66
+ - **Native skill folders for Claude Code, Codex and OpenCode** — Installing a skill now puts it in each runtime's own skill folder, with a backup, a check after the install and a way back. Skills installed the old way, as Codex prompts or OpenCode commands, can be moved to the native folders from Settings › Skills › Skill folders: you see the plan first, nothing moves until you press Move, and Undo puts every one back. Nothing is moved at start, during an update or by an agent. The Codex panel runs a skill you pick as a Codex skill, and a skill's arguments are filled in the way Claude Code fills them.
67
+ - **Skills drafted by AI, and a Review queue** — Create's AI box drafts a whole skill from your description and up to five sources: a Claude Code, Codex or OpenCode session, a web page, a registered project, memories, a library skill or an imported bundle. One worker writes it in a folder of its own and can only read and write there; the result is checked, comes with test prompts and lands in Review. While it runs you see its steps, the files written so far, the time and the cost, and you can cancel. The SynaBun Assistant creates and improves skills the same way, with its bundled skill-forge workflow. Whatever an AI makes waits in Review and is installed only after you approve it.
68
+ - **`synabun mcp`** — The npm command now starts SynaBun's MCP server on stdio, for Cursor, Windsurf, Claude Code, Codex or any other MCP client: `"command": "npx", "args": ["-y", "synabun", "mcp"]`, or `"command": "synabun", "args": ["mcp"]` after `npm install -g synabun`. It is the same server, with the same memories and data folder, as the one SynaBun registers for your tools. Only the protocol goes to its output, and it stops when the client that started it stops it.
69
+ - **The official MCP Registry** — The npm package now names SynaBun's entry in the official MCP Registry, `io.github.danilokhury/synabun`, which MCP clients and directories read to find a server and how to start it.
70
+ - **Clickable follow-ups in the Assistant** — When the Assistant ends a turn with options or a question for you, they appear as buttons under its message: one click sends the answer, a set of choices becomes checkboxes with one Send, and Something else… takes you to the composer.
71
+ - **A countdown to a usage-limit reset** — When a provider's usage limit stops the Assistant or one of its workers, a card shows a live countdown to the reset and the local reset time, then Retry now. A worker is retried on the same model.
72
+ - **A right-click menu for the whole app** — A right-click anywhere that has no menu of its own now opens SynaBun's: page actions (Back, Forward, Reload, Copy, Full screen), then what fits what you clicked (selected text, a link, an image, a memory on the map, in the Memory Explorer or on a card, the empty space of the 3D or 2D map, the Focus mode board and its tools), then every panel and tool with its keyboard shortcut. Fields and Shift + right-click keep the browser's menu, and the terminal, the File Explorer's tree, editor tabs and graph cards keep their own. The Menu key and Shift + F10 open it from the keyboard.
73
+
74
+ ## 🛠 Fixed
75
+
76
+ - **Skills Studio keeps your work** — The Studio could erase frontmatter it did not show, mark text typed during a save as saved, save one skill's file under another's name, lose unsaved work on Escape or close, rewrite every line ending of a Windows-style file, write into SynaBun's bundled skill, replace or delete a same-named skill SynaBun never installed, and install an imported bundle unseen. Now an edit changes only the field you touched, a save that would overwrite a change made elsewhere asks first, bundled skills are read-only with Customize a copy, a skill SynaBun did not install is backed up and changed only after you confirm, and an import is checked, scanned and shown to you before anything is written.
77
+ - **A complete MCP server build** — SynaBun now checks that its MCP server is built completely, every module it loads and not only its entry file, and builds it again at install or start when something is missing.
78
+
79
+ ## ⚙️ Changed
80
+
81
+ - **Desktop updates** — Updating the desktop app no longer leaves you without a window: a small splash with the mascot shows at every start and through every step of an update, with a progress bar and Details. Once an update is downloaded, the update button becomes Restart to update (also in the app menu, the File menu and the tray), which installs at once when SynaBun is idle, or when the running work ends. The safety snapshot of your data is prepared ahead in the background and compared with your data at the restart, so the new version opens in seconds instead of minutes on a large memory store, and every database copy in it passes an integrity check. On a Mac the app is without a window for about a second, and dark pages on some Macs no longer show a lighter band across the middle.
82
+ - **`synabun help`** — `synabun help`, `synabun --help` and `synabun -h` list every command and the data folder in use. Before, they started SynaBun.
83
+ - **Schedules in your language** — The whole Schedules window now follows the app's language: every view, row, editor, dialog and message, schedules described in plain words, and dates and times in your locale. Changing the language with the window open brings you back to the same view.
84
+
85
+ ---
86
+
1
87
  # SynaBun v.2.0.10
2
88
 
3
89
  2.0.10 works with OpenCode 2, adds a Settings › App page for updates and start at login, and opens menus, model lists and Settings at once instead of waiting on the network, most noticeably in the Windows desktop app. It also redesigns the Schedules window, shows every worker the Assistant dispatches at work, and closes a security hole in the Codex panel.
package/README.md CHANGED
@@ -23,7 +23,26 @@
23
23
 
24
24
  ---
25
25
 
26
- SynaBun gives your AI coding tools a memory that lasts. What you decided, fixed and explained in one session is there in the next one, in every project and every tool you connect, so you stop repeating yourself. It all lives in one file on your computer: no account, no cloud service, no API key.
26
+ **SynaBun is an open-source, self-hosted AI workspace and persistent memory MCP server: SynaBun Assistant coordinates Claude Code, Codex and OpenCode agents.** What you decided, fixed and explained in one session is there in the next one, in every project and every tool you connect, so you stop repeating yourself. It all lives in one SQLite file on your computer: no account, no cloud service, no API key.
27
+
28
+ ```bash
29
+ npm install -g synabun
30
+ ```
31
+
32
+ `synabun` starts the app. `synabun mcp` is the MCP server on stdio, for any MCP client:
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "SynaBun": {
38
+ "command": "npx",
39
+ "args": ["-y", "synabun", "mcp"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ After the global install, `"command": "synabun", "args": ["mcp"]` runs the same server. Read the [docs](https://synabun.ai/docs), or see how SynaBun [compares](https://synabun.ai/compare) with other memory tools.
27
46
 
28
47
  It connects to Claude Code, Codex CLI, Gemini CLI and OpenCode with one switch each, and to Cursor, Windsurf or any other tool that supports MCP (the Model Context Protocol) with a config snippet you copy. It comes with the Neural Interface, a local web app where you see what your tools remember and run your coding agents.
29
48
 
@@ -159,7 +178,7 @@ Everything SynaBun stores is in one folder outside the app: `~/.synabun` on macO
159
178
 
160
179
  ## Links
161
180
 
162
- - [Changelog](./CHANGELOG.md): what is new in 2.0.10 and before
181
+ - [Changelog](./CHANGELOG.md): what is new in 2.1.0 and before
163
182
  - [Security](./SECURITY.md): security model and how to report a vulnerability
164
183
  - [Contributing](./CONTRIBUTING.md): bug reports and feature requests are welcome as [issues](https://github.com/danilokhury/Synabun/issues); pull requests are not accepted
165
184
  - [Website](https://synabun.ai), [docs](https://synabun.ai/docs) and [blog](https://synabun.ai/blog)
@@ -174,3 +193,7 @@ You are free to use, modify, and distribute SynaBun under Apache 2.0. Premium fe
174
193
  ## Trademark Notice
175
194
 
176
195
  "SynaBun" is a trademark of its authors. The license does not grant permission to use the SynaBun name, trademarks, service marks, or branding. If you fork this project, you must use a different name for your derivative work.
196
+
197
+ <!-- synabun:verification:start -->
198
+ Package verification: [downloadable VirusTotal reports and SHA-256 hashes](https://github.com/danilokhury/Synabun/releases/latest). Match the report to your exact version and file hash. Reports record engine coverage and analysis dates; they are not a guarantee of safety. Website results describe URL reputation, not a vulnerability audit.
199
+ <!-- synabun:verification:end -->
@@ -59,6 +59,7 @@ runtime dependencies in the published package.
59
59
  | `playwright` | `^1.59.1` | 1.59.1 | Apache-2.0 | [npm](https://www.npmjs.com/package/playwright) |
60
60
  | `undici` | `^8.5.0` | 8.8.0 | MIT | [npm](https://www.npmjs.com/package/undici) |
61
61
  | `ws` | `^8.21.0` | 8.21.1 | MIT | [npm](https://www.npmjs.com/package/ws) |
62
+ | `yaml` | `2.9.1` | 2.9.1 | ISC | [npm](https://www.npmjs.com/package/yaml) |
62
63
  | `zod` | `^4.4.3` | 4.4.3 | MIT | [npm](https://www.npmjs.com/package/zod) |
63
64
 
64
65
  ## Neural Interface Development Dependencies
@@ -94,6 +95,8 @@ runtime dependencies in the published package.
94
95
  | highlight.js | 11.12.0 | Bundled browser asset (`neural-interface/public/vendor/highlight/`) copied from the npm development dependency `@highlightjs/cdn-assets` | BSD-3-Clause | [GitHub](https://github.com/highlightjs/highlight.js) |
95
96
  | Mermaid | 11.17.2 | Bundled browser asset (`neural-interface/public/vendor/mermaid/`) copied from the npm development dependency | MIT | [GitHub](https://github.com/mermaid-js/mermaid) |
96
97
  | html2canvas | 1.4.1 | Bundled browser asset (`neural-interface/public/vendor/html2canvas/`) copied from the npm development dependency | MIT | [GitHub](https://github.com/niklasvh/html2canvas) |
98
+ | yaml | 2.9.1 | Bundled browser asset (`neural-interface/public/vendor/yaml/`) built from the npm dependency | ISC | [GitHub](https://github.com/eemeli/yaml) |
99
+ | Caveat (font) | 5.3.0 (`@fontsource/caveat`) | Bundled browser asset (`neural-interface/public/vendor/caveat/`): the Latin 400 and 700 WOFF2 files, copied unmodified with the package's license | OFL-1.1 | [GitHub](https://github.com/googlefonts/caveat) |
97
100
 
98
101
  ### CodeMirror 6
99
102
 
@@ -156,6 +159,24 @@ The card screenshot tool of the 3D map (`index.html`). Its own browser build (`d
156
159
  - **Copyright:** Copyright (c) 2012 Niklas von Hertzen
157
160
  - **URL:** https://github.com/niklasvh/html2canvas
158
161
 
162
+ ### yaml
163
+
164
+ The YAML parser behind the Skills Studio: it reads the frontmatter of skills, commands and agents, and changes it one key at a time without touching the rest (`neural-interface/lib/skills/`, `neural-interface/public/shared/skills/ss-frontmatter.js`). No dependencies of its own. The Studio's metadata form uses the same package in the browser: its browser build (`browser/dist/index.js`), bundled by `neural-interface/scripts/build-vendor.mjs` into one file in `neural-interface/public/vendor/yaml/` with the package's `LICENSE` and a `manifest.json`, loaded only when the editor opens.
165
+
166
+ - **Version:** 2.9.1
167
+ - **License:** ISC
168
+ - **Copyright:** Copyright Eemeli Aro
169
+ - **URL:** https://github.com/eemeli/yaml
170
+
171
+ ### Caveat
172
+
173
+ The handwriting of the whiteboard and of the Workspace tour, served by SynaBun itself instead of a font CDN. The Latin subset of weights 400 and 700 (`caveat-latin-400-normal.woff2`, `caveat-latin-700-normal.woff2`) from the npm package `@fontsource/caveat` 5.3.0, copied unmodified into `neural-interface/public/vendor/caveat/` with the package's `LICENSE` as `OFL.txt`, a `caveat.css` that declares the two faces and a `manifest.json` with the SHA-256 of each file. The font is not sold on its own and its name is not changed (OFL 1.1, conditions 1 and 3).
174
+
175
+ - **Version:** 5.3.0 (`@fontsource/caveat`)
176
+ - **License:** SIL Open Font License 1.1
177
+ - **Copyright:** Copyright 2014 The Caveat Project Authors
178
+ - **URL:** https://github.com/googlefonts/caveat
179
+
159
180
  ## Vendored Code
160
181
 
161
182
  | Component | Location | License | Project |
@@ -224,6 +245,7 @@ The model is distributed under Apache-2.0. See its
224
245
  - [BSD 2-Clause License](https://opensource.org/license/bsd-2-clause)
225
246
  - [BSD 3-Clause License](https://opensource.org/license/bsd-3-clause)
226
247
  - [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.html)
248
+ - [ISC License](https://opensource.org/license/isc-license-txt)
227
249
 
228
250
  SynaBun's own Apache-2.0 terms are in [LICENSE](./LICENSE), and attribution
229
251
  information is in [NOTICE](./NOTICE). The
package/bin/synabun.js CHANGED
@@ -1,30 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * `synabun`, the command an npm install puts on the PATH.
5
+ *
6
+ * synabun mcp [...] the MCP server on stdio, for an AI tool's MCP config. It
7
+ * starts mcp-server/run.mjs straight from here, as
8
+ * `node <package>/mcp-server/run.mjs` and a packaged build's
9
+ * `<entry> mcp` do (packaging/launcher/launcher.c): same
10
+ * environment, same working folder, same arguments after
11
+ * `mcp`. setup.js is never loaded, so nothing reaches stdout,
12
+ * the protocol channel, before the server does; the server
13
+ * logs to stderr.
14
+ * synabun [...] everything else: setup.js with the same arguments.
15
+ */
16
+
3
17
  import { spawn } from 'node:child_process';
18
+ import { realpathSync } from 'node:fs';
4
19
  import { dirname, resolve } from 'node:path';
5
20
  import { fileURLToPath } from 'node:url';
6
21
 
7
- const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
8
- const setupPath = resolve(packageRoot, 'setup.js');
9
- const child = spawn(process.execPath, [
10
- '--disable-warning=ExperimentalWarning',
11
- setupPath,
12
- ...process.argv.slice(2),
13
- ], {
14
- cwd: process.cwd(),
15
- env: process.env,
16
- stdio: 'inherit',
17
- });
18
-
19
- child.on('error', error => {
20
- console.error(`Could not start SynaBun: ${error.message}`);
21
- process.exitCode = 1;
22
- });
23
-
24
- child.on('exit', (code, signal) => {
25
- if (signal) {
26
- process.kill(process.pid, signal);
27
- return;
28
- }
29
- process.exitCode = code ?? 1;
30
- });
22
+ export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
23
+ export const MCP_COMMAND = 'mcp';
24
+ // node:sqlite's warning: every SynaBun process starts with it off (setup.js, the packaged launcher).
25
+ const NODE_FLAG = '--disable-warning=ExperimentalWarning';
26
+
27
+ /** Node's arguments for `synabun <args>`. */
28
+ export function nodeArgs(args, { packageRoot = PACKAGE_ROOT } = {}) {
29
+ if (args[0] === MCP_COMMAND) return [NODE_FLAG, resolve(packageRoot, 'mcp-server', 'run.mjs'), ...args.slice(1)];
30
+ return [NODE_FLAG, resolve(packageRoot, 'setup.js'), ...args];
31
+ }
32
+
33
+ /** The signals an MCP client (or a terminal) stops the server with. */
34
+ export function forwardedSignals(platform = process.platform) {
35
+ return platform === 'win32' ? ['SIGINT', 'SIGTERM'] : ['SIGINT', 'SIGTERM', 'SIGHUP'];
36
+ }
37
+
38
+ /**
39
+ * Node on `args`, with this process's stdio, environment and working folder.
40
+ * This process then ends the way the child ended: its exit code, or the same
41
+ * signal. With `forwardSignals`, a SIGINT, SIGTERM or SIGHUP sent to this
42
+ * process goes to the child while it runs: an MCP client stops the server it
43
+ * started by signalling the process it started, which is this one.
44
+ */
45
+ export function runNode(args, { execPath = process.execPath, env = process.env, cwd = process.cwd(), forwardSignals = false } = {}) {
46
+ const child = spawn(execPath, args, { cwd, env, stdio: 'inherit' });
47
+ const handlers = forwardSignals
48
+ ? forwardedSignals().map((signal) => [signal, () => { try { child.kill(signal); } catch {} }])
49
+ : [];
50
+ for (const [signal, handler] of handlers) process.on(signal, handler);
51
+ const release = () => { for (const [signal, handler] of handlers) process.removeListener(signal, handler); };
52
+
53
+ child.on('error', (error) => {
54
+ release();
55
+ console.error(`Could not start SynaBun: ${error.message}`);
56
+ process.exitCode = 1;
57
+ });
58
+
59
+ child.on('exit', (code, signal) => {
60
+ release();
61
+ if (signal) {
62
+ process.kill(process.pid, signal);
63
+ return;
64
+ }
65
+ process.exitCode = code ?? 1;
66
+ });
67
+ return child;
68
+ }
69
+
70
+ const invokedDirectly = (() => {
71
+ try { return !!process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; }
72
+ })();
73
+
74
+ if (invokedDirectly) {
75
+ const args = process.argv.slice(2);
76
+ runNode(nodeArgs(args), { forwardSignals: args[0] === MCP_COMMAND });
77
+ }
package/docs/whatsapp.md CHANGED
@@ -299,7 +299,7 @@ Ask for a file and the Assistant sends it to this chat as a real WhatsApp attach
299
299
  - password managers (1Password, Bitwarden, KeePass `*.kdbx`), `~/Library/Keychains`, GNOME Keyring and KWallet, Windows Credential Manager (with its DPAPI keys and the Vault), the Mail and Messages stores and Thunderbird;
300
300
  - shell histories (`.zsh_history`, `.bash_history`, fish's history, PowerShell's), `.netrc`, `.npmrc`, `.pypirc`, `.pgpass`, `.git-credentials` and git's `~/.config/git/credentials`, `.vault-token`, the `.docker`, `.kube`, `.aws`, `.azure`, gcloud, `gh`, `.gnupg` and `.ssh` folders, and CLI token stores (cargo, gem, Pulumi, Hugging Face, Kaggle, rclone, hub, glab, doctl, Stripe, Fly, cloudflared, OCI, Databricks);
301
301
  - key and certificate files anywhere (`*.pem`, `*.key`, `*.p12`, `*.pfx`, `*.jks`, `*.keystore`, `*.ppk`, `*.p8`, SSH `id_*` keys), `.env` files and their variants, terraform state;
302
- - AI tool configuration and credentials: `~/.claude.json`, `~/.claude*`, `~/.codex*`, OpenCode's configuration and `auth.json`, GitHub Copilot's tokens, `.cursor/mcp.json`, `claude_desktop_config.json`, `mcp-api-key` and `.synabun-api-key` files;
302
+ - AI tool configuration and credentials: `~/.claude.json`, `~/.claude*`, `~/.codex*`, OpenCode's configuration, its database (`opencode.db`, where OpenCode 2 keeps credentials) and the `auth.json` OpenCode 1 kept, GitHub Copilot's tokens, `.cursor/mcp.json`, `claude_desktop_config.json`, `mcp-api-key` and `.synabun-api-key` files;
303
303
  - copies of those places' secret parts wherever they are (a backup disk, Time Machine): `.synabun/whatsapp`, `mcp-data`, `runtime` and `backups`, `sidepanel-configs`, `.claude/settings*.json` and `.claude/projects`, `.codex*/config.toml`, `sessions` and `history.jsonl`; and SynaBun's own backup archives (`synabun-<kind>-<timestamp>.zip`, `synabun-auto-backup.zip`) by their name, in whatever folder the backups go to.
304
304
 
305
305
  The list is explicit (`neural-interface/lib/whatsapp/local-file.js`, `DENYLIST`) and matched without regard to letter case. The protected folders (the session, the data home, `~/.claude*`, `~/.codex*`) are also recognized by what they are on disk, their device and inode, so another spelling of the same folder is refused like the usual one: macOS reaches every folder of the data volume under `/System/Volumes/Data/…` too, which `realpath` keeps. On Windows a name is also checked as Windows opens it, without trailing dots and spaces (`.ssh.\id_rsa` is `.ssh\id_rsa`). **The content of a file is never inspected**: a secret inside an ordinary file with an ordinary name is sent like any other file, so ask only for what you mean to receive. Keynote presentations share the `.key` extension with private keys and are refused too: export them to PDF first.
@@ -390,7 +390,7 @@ App-wide, every WebSocket upgrade (`/ws/*`) must carry no `Origin` or this serve
390
390
  | "SynaBun is not reachable: nothing was sent" (`UNREACHABLE`) | The brain's MCP server could not reach SynaBun at all, so the file was not sent. Check that SynaBun is running, then ask again. |
391
391
  | Transcripts get words wrong | Set `transcriptionLanguage` to the language you speak (`auto` guesses per note), or point `transcriptionModelPath` at a larger ggml model. |
392
392
 
393
- **Not the same as the WhatsApp browser tools.** The `whatsapp` MCP profile (`browser_extract_wa_chats`, `browser_extract_wa_messages`) lets agents read WhatsApp Web in SynaBun's browser. The WhatsApp Link is you chatting with SynaBun from your phone. They share nothing.
393
+ **Not the same as the WhatsApp browser tools.** The `browser_whatsapp` tool group (`browser_extract_wa_chats`, `browser_extract_wa_messages`) lets agents read WhatsApp Web in SynaBun's browser. The WhatsApp Link is you chatting with SynaBun from your phone. They share nothing.
394
394
 
395
395
  ## Environment
396
396
 
@@ -29,7 +29,11 @@ import { getDataHome } from '../../lib/paths.js';
29
29
  const __dirname = dirname(fileURLToPath(import.meta.url));
30
30
  const DATA_HOME = getDataHome();
31
31
  const LOOP_DIR = join(DATA_HOME, 'data', 'loop');
32
- const NI_BASE = process.env.SYNABUN_NI_URL || 'http://localhost:3344';
32
+ // A guarded worker's sandbox blocks :3344, so SynaBun gives it the worker-only channel (its browser proxy forwards
33
+ // /api/browser/*). Inside such a worker the browser-only policy must NOT fail open: if SynaBun cannot be reached
34
+ // even over the channel, apply the worker default and deny WebSearch/WebFetch.
35
+ const WORKER_CHANNEL = process.env.SYNABUN_WORKER_CHANNEL || null;
36
+ const NI_BASE = WORKER_CHANNEL || process.env.SYNABUN_NI_URL || 'http://localhost:3344';
33
37
 
34
38
  const BLOCK_REASON = (toolName) =>
35
39
  `BLOCKED: ${toolName} is not allowed while a SynaBun browser session is open. Use the SynaBun browser tools (browser_navigate, browser_click, browser_content, etc.) instead. If the page requires login or CAPTCHA, STOP and tell the user what they need to do in the browser panel. Do NOT fall back to web search.`;
@@ -69,6 +73,13 @@ async function handleInput() {
69
73
  }
70
74
 
71
75
  if (!niReachable) {
76
+ // A guarded worker that cannot reach SynaBun even over its channel: apply the worker default (deny), so the
77
+ // sandbox's inability to reach :3344 never becomes a way to bypass the browser-only policy. Outside a guarded
78
+ // worker (no channel var) this stays fail-open and quiet — a stale loop file must not block the owner.
79
+ if (WORKER_CHANNEL) {
80
+ process.stdout.write(JSON.stringify({ decision: 'block', reason: BLOCK_REASON(tool_name) }));
81
+ return;
82
+ }
72
83
  process.stdout.write(JSON.stringify({}));
73
84
  return;
74
85
  }
@@ -864,8 +864,13 @@ export async function judgeText(kind, text, { project, timeoutMs = 1500, extra =
864
864
  /**
865
865
  * Fire-and-forget POST that may wait at most `waitMs` for the answer and never
866
866
  * keeps the process alive past that. Resolves true only on a 2xx.
867
+ * SYNABUN_HOOK_NOTICE_WAIT_MS replaces `waitMs` for the hook tests only
868
+ * (tests/helpers/hook-harness.mjs): on a machine running the whole suite the
869
+ * notice missed its 200 ms. Nothing SynaBun starts sets it.
867
870
  */
868
871
  export async function postNotice(path, body, waitMs = 200) {
872
+ const testWait = Number(process.env.SYNABUN_HOOK_NOTICE_WAIT_MS);
873
+ if (Number.isFinite(testWait) && testWait > 0) waitMs = testWait;
869
874
  let timer;
870
875
  try {
871
876
  const request = fetch(`${niUrl()}${path}`, {
@@ -916,7 +916,7 @@ function handleStop({ sessionId, lastMessage, cwd, transcriptPath, judged = {},
916
916
  }
917
917
 
918
918
  // CHECK 2.5: User learning (bundled with task or standalone)
919
- // Skipped when waiting for user action (interactive flows like Leonardo questionnaires).
919
+ // Skipped when waiting for user action (interactive questionnaire flows).
920
920
  // Limited to 1 retry to minimize chat noise.
921
921
  // Never inside the Assistant's brain (SYNABUN_ASSISTANT_SESSION): SynaBun's harness learns
922
922
  // about the user there, and prompt-submit.mjs never arms this in it.
@@ -1,6 +1,8 @@
1
- import { resolve } from 'node:path';
1
+ import { accessSync, constants, statSync } from 'node:fs';
2
+ import { homedir, tmpdir } from 'node:os';
3
+ import { isAbsolute, join, resolve, sep } from 'node:path';
2
4
  import { PACKAGE_ROOT } from './paths.js';
3
- import { durableEntry, packagedRuntime } from './packaged-runtime.js';
5
+ import { durableEntry, isReadOnlyVolumePath, isTransientPath, packagedRuntime } from './packaged-runtime.js';
4
6
 
5
7
  // Handler identity must be byte-for-byte identical across settings scopes.
6
8
  // Claude Code deduplicates matching settings handlers before executing them.
@@ -27,10 +29,10 @@ export function hookDefinitionsForEvent(event) {
27
29
  return HOOK_SCRIPTS.filter(def => events.includes(def.event));
28
30
  }
29
31
 
30
- // No shell-specific environment expansion, install paths, or downloads. Walk
31
- // up from the project/cwd for checkout-only installs, then use the installed
32
- // root. Single quotes inside JS work inside the command's double quotes on
33
- // both cmd.exe and POSIX shells. Keep this versioned literal stable.
32
+ // No install paths or downloads. Walk up from the project/cwd for checkout-only
33
+ // installs, then use the installed root. Single quotes inside JS work inside
34
+ // the command's double quotes on both cmd.exe and POSIX shells. Keep this
35
+ // versioned literal stable.
34
36
  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)";
35
37
 
36
38
  /**
@@ -49,23 +51,48 @@ function quoteHookEntry(entry) {
49
51
  return `"${entry.replace(/[\\"$`]/g, '\\$&')}"`;
50
52
  }
51
53
 
54
+ // Windows: cmd.exe, PowerShell and Git Bash all run this, and a Windows program
55
+ // gets the user's PATH from the registry whoever starts it.
52
56
  const nodeHookCommand = scriptName => `node -e "${BOOTSTRAP}" ${scriptName}`;
53
57
 
58
+ /**
59
+ * Where a hook started without the login PATH looks for Node after `node` on
60
+ * PATH: the official installer's and Homebrew's folders and Volta's shim (the
61
+ * Node a terminal finds, as a rule), then SYNABUN_NODE. The same names on every
62
+ * machine.
63
+ */
64
+ export const HOOK_NODE_FOLDERS = Object.freeze(['/usr/local/bin/node', '/opt/homebrew/bin/node', '$HOME/.volta/bin/node']);
65
+
66
+ // macOS and Linux: Claude Code runs a settings hook with /bin/sh in its own
67
+ // environment. Started from a terminal that has the login PATH; started from
68
+ // the Dock, Finder, a desktop app or launchd it is /usr/bin:/bin:/usr/sbin:/sbin,
69
+ // where `node` is not found and every hook exits 127. So: `node` on PATH, as a
70
+ // terminal finds it, then the usual folders, then the Node the installer named
71
+ // in the user's settings (SYNABUN_NODE: nvm and the like have no usual folder).
72
+ // Nothing in it names this machine, so the tracked template and the projects'
73
+ // settings carry it as they are.
74
+ const POSIX_NODE_FINDER = `n=$(command -v node) || for n in ${HOOK_NODE_FOLDERS.map(p => (p.startsWith('$') ? `"${p}"` : p)).join(' ')} "$SYNABUN_NODE"; do [ -f "$n" ] && [ -x "$n" ] && break; n=node; done; `;
75
+ const posixHookCommand = scriptName => `${POSIX_NODE_FINDER}exec "$n" -e "${BOOTSTRAP}" ${scriptName}`;
76
+
54
77
  // Whatever entry wrote it: an application that moved must still find, and
55
78
  // repair, its own handlers.
56
79
  const PACKAGED_HOOK_COMMAND = new RegExp(`^"(?:[^"\\\\]|\\\\.)+"\\s+${PACKAGED_HOOK_MODE}\\s+(\\S+)$`);
57
80
 
58
- export function hookCommandString(scriptName, _targetProjectPath, runtime = packagedRuntime()) {
81
+ export function hookCommandString(scriptName, _targetProjectPath, runtime = packagedRuntime(), platform = process.platform) {
59
82
  if (!HOOK_SCRIPTS.some(h => h.script === scriptName)) throw new Error('Unknown SynaBun hook');
60
83
  const entry = durableEntry(runtime);
61
- return entry ? `${quoteHookEntry(entry)} ${PACKAGED_HOOK_MODE} ${scriptName}` : nodeHookCommand(scriptName);
84
+ if (entry) return `${quoteHookEntry(entry)} ${PACKAGED_HOOK_MODE} ${scriptName}`;
85
+ return platform === 'win32' ? nodeHookCommand(scriptName) : posixHookCommand(scriptName);
62
86
  }
63
87
 
64
88
  export function isSynaBunHookCommand(command, scriptName) {
65
89
  if (typeof command !== 'string') return false;
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;
90
+ // Every canonical form, whichever kind of install and system is reading:
91
+ // moving between an npm install and the application, or a settings file
92
+ // shared between Windows and macOS, must not leave two handlers behind.
93
+ if (command === nodeHookCommand(scriptName) || command === posixHookCommand(scriptName)) return true;
94
+ // Any version of the POSIX finder in front of the same bootstrap.
95
+ if (command.startsWith('n=$(command -v node)') && command.endsWith(` -e "${BOOTSTRAP}" ${scriptName}`)) return true;
69
96
  const packaged = command.trim().match(PACKAGED_HOOK_COMMAND);
70
97
  if (packaged) return packaged[1] === scriptName;
71
98
  // Recognize only the old direct-node path forms, including cross-OS installs.
@@ -98,25 +125,47 @@ export function hookInstallPlan({ target, registered = false, hook } = {}, runti
98
125
  return target === 'project' && !registered && !hook ? 'register-only' : 'refuse';
99
126
  }
100
127
 
101
- export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
128
+ /**
129
+ * The Node a hook falls back to when Claude Code was started without the login
130
+ * PATH (SYNABUN_NODE): the one running SynaBun, which is known to run its hooks,
131
+ * when its path outlives this run. null on Windows (its programs get the user's
132
+ * PATH) and for a Node in a temporary place.
133
+ */
134
+ export function hookNodePin({ execPath = process.execPath, platform = process.platform, env = process.env, writable } = {}) {
135
+ if (platform === 'win32' || typeof execPath !== 'string' || !isAbsolute(execPath)) return null;
136
+ if (isTransientPath(execPath, env) || isReadOnlyVolumePath(execPath, { platform, writable })) return null;
137
+ const temporary = [tmpdir(), '/tmp', '/private/tmp', '/private/var/folders', '/var/folders'];
138
+ if (temporary.some(dir => resolve(execPath).startsWith(`${resolve(dir)}${sep}`))) return null;
139
+ return execPath;
140
+ }
141
+
142
+ export function ensureHookRoot(settings, targetProjectPath, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime(), { node = hookNodePin() } = {}) {
102
143
  // A packaged application's command finds its own handlers, and its package
103
144
  // root can be a mount that ends with the run.
104
145
  if (runtime) return false;
105
146
  // The checked-in template must not acquire a machine-specific env path.
106
147
  if (targetProjectPath && resolve(targetProjectPath) === resolve(packageRoot)) return false;
107
- if (settings.env?.SYNABUN_HOOK_ROOT === resolve(packageRoot)) return false;
108
- settings.env = { ...settings.env, SYNABUN_HOOK_ROOT: resolve(packageRoot) };
109
- return true;
148
+ let changed = false;
149
+ if (settings.env?.SYNABUN_HOOK_ROOT !== resolve(packageRoot)) {
150
+ settings.env = { ...settings.env, SYNABUN_HOOK_ROOT: resolve(packageRoot) };
151
+ changed = true;
152
+ }
153
+ // The user's own settings only (this machine's file): every session gets its env.
154
+ if (!targetProjectPath && node && settings.env?.SYNABUN_NODE !== node) {
155
+ settings.env = { ...settings.env, SYNABUN_NODE: node };
156
+ changed = true;
157
+ }
158
+ return changed;
110
159
  }
111
160
 
112
- export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime()) {
161
+ export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, packageRoot = PACKAGE_ROOT, runtime = packagedRuntime(), { platform = process.platform, node = hookNodePin({ platform }) } = {}) {
113
162
  if (!settings?.hooks || hookInstallBlocker(runtime)) return false;
114
163
  let changed = false;
115
164
  let ownsHooks = false;
116
165
  for (const def of HOOK_SCRIPTS) {
117
166
  const entries = settings.hooks[def.event];
118
167
  if (!Array.isArray(entries)) continue;
119
- const command = hookCommandString(def.script, targetProjectPath, runtime);
168
+ const command = hookCommandString(def.script, targetProjectPath, runtime, platform);
120
169
  const matches = entries.flatMap(e => (e.hooks || []).filter(h => isSynaBunHookCommand(h.command, def.script)).map(h => ({ entry: e, hook: h })));
121
170
  if (!matches.length) continue;
122
171
  ownsHooks = true;
@@ -133,6 +182,111 @@ export function sweepSettingsHooks(settings, targetProjectPath, stats = {}, pack
133
182
  settings.hooks[def.event] = [...rest, { matcher: def.matcher || '', hooks: [{ type: 'command', command, timeout: def.timeout }] }];
134
183
  changed = true;
135
184
  }
136
- if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot, runtime)) changed = true;
185
+ if (ownsHooks && ensureHookRoot(settings, targetProjectPath, packageRoot, runtime, { node })) changed = true;
137
186
  return changed;
138
187
  }
188
+
189
+ // ── What is really installed (Settings → Automations → Claude Code hooks) ────
190
+
191
+ /**
192
+ * What one settings file holds of SynaBun's handlers. `handlers`: one per
193
+ * HOOK_SCRIPTS entry, with `copies` (SynaBun handlers for that script in any
194
+ * form) and `current` (one of them is this machine's command, matcher and
195
+ * timeout); `events`: every handler of the event present; `installed` /
196
+ * `current` / `total` count handlers, `duplicates` the extra copies (each runs).
197
+ */
198
+ export function hookInstallState(settings, { targetProjectPath, runtime = packagedRuntime(), platform = process.platform } = {}) {
199
+ const handlers = HOOK_SCRIPTS.map((def) => {
200
+ const entries = Array.isArray(settings?.hooks?.[def.event]) ? settings.hooks[def.event] : [];
201
+ const owned = entries.flatMap(entry => (Array.isArray(entry?.hooks) ? entry.hooks : [])
202
+ .filter(hook => isSynaBunHookCommand(hook?.command, def.script)).map(hook => ({ entry, hook })));
203
+ const command = hookCommandString(def.script, targetProjectPath, runtime, platform);
204
+ const current = owned.some(({ entry, hook }) => hook.command === command && (entry.matcher || '') === (def.matcher || '') && hook.timeout === def.timeout);
205
+ return { event: def.event, script: def.script, copies: owned.length, current };
206
+ });
207
+ const events = {};
208
+ for (const h of handlers) events[h.event] = (events[h.event] ?? true) && h.copies > 0;
209
+ return {
210
+ handlers,
211
+ events,
212
+ installed: handlers.filter(h => h.copies > 0).length,
213
+ current: handlers.filter(h => h.current).length,
214
+ duplicates: handlers.reduce((sum, h) => sum + Math.max(0, h.copies - 1), 0),
215
+ total: handlers.length,
216
+ };
217
+ }
218
+
219
+ /** The commands of SynaBun's handlers in a settings file, script by script (to compare two scopes). */
220
+ export function hookCommandsOf(settings) {
221
+ const out = {};
222
+ for (const def of HOOK_SCRIPTS) {
223
+ const entries = Array.isArray(settings?.hooks?.[def.event]) ? settings.hooks[def.event] : [];
224
+ const commands = entries.flatMap(entry => (Array.isArray(entry?.hooks) ? entry.hooks : []))
225
+ .map(hook => hook?.command).filter(command => isSynaBunHookCommand(command, def.script));
226
+ out[`${def.event}:${def.script}`] = [...new Set(commands)].sort();
227
+ }
228
+ return out;
229
+ }
230
+
231
+ /**
232
+ * Whether a project's SynaBun handlers run once next to the user's: Claude Code
233
+ * runs one copy of identical handlers from both files, and both of different
234
+ * ones. 'same' (nothing of SynaBun's there, or the same commands), 'different'.
235
+ */
236
+ export function projectHooksAgree(userSettings, projectSettings) {
237
+ const user = hookCommandsOf(userSettings);
238
+ const project = hookCommandsOf(projectSettings);
239
+ for (const [key, commands] of Object.entries(project)) {
240
+ if (!commands.length) continue;
241
+ const mine = user[key] || [];
242
+ if (!mine.length) continue; // only the project runs it: once
243
+ if (commands.some(command => !mine.includes(command))) return 'different';
244
+ }
245
+ return 'same';
246
+ }
247
+
248
+ /** Claude Code's own switches that stop SynaBun's handlers, in one settings file: the first that applies, or null. */
249
+ export function hooksSwitchedOff(settings, { managed = false } = {}) {
250
+ if (!settings || typeof settings !== 'object') return null;
251
+ if (settings.disableAllHooks === true) return 'disableAllHooks';
252
+ // Only managed settings can keep user and project hooks from running.
253
+ if (managed && settings.allowManagedHooksOnly === true) return 'allowManagedHooksOnly';
254
+ return null;
255
+ }
256
+
257
+ /** Where Claude Code reads managed (organization) settings on this system. */
258
+ export function managedClaudeSettingsPath(platform = process.platform) {
259
+ if (platform === 'darwin') return '/Library/Application Support/ClaudeCode/managed-settings.json';
260
+ if (platform === 'win32') return 'C:\\Program Files\\ClaudeCode\\managed-settings.json';
261
+ return '/etc/claude-code/managed-settings.json';
262
+ }
263
+
264
+ /** The PATH macOS gives a program it starts from the Dock, Finder or launchd. */
265
+ export const LAUNCHD_PATH = Object.freeze(['/usr/bin', '/bin', '/usr/sbin', '/sbin']);
266
+
267
+ function runnableFile(path) {
268
+ try {
269
+ if (!statSync(path).isFile()) return false;
270
+ accessSync(path, constants.X_OK);
271
+ return true;
272
+ } catch { return false; }
273
+ }
274
+
275
+ /**
276
+ * The Node a SynaBun hook would run with in a Claude Code started without the
277
+ * login PATH (the Dock, Finder, a desktop app, launchd): what the POSIX command
278
+ * tries after PATH, in its order. { path, via: 'SYNABUN_NODE' | 'folder' } or
279
+ * null (such a session would fail every hook). Windows: not asked (null).
280
+ */
281
+ export function hookNodeWithoutPath({ env = {}, home = homedir(), platform = process.platform, runnable = runnableFile } = {}) {
282
+ if (platform === 'win32') return null;
283
+ // `command -v node` on the PATH such a launch has (a Linux distribution's own Node).
284
+ for (const dir of LAUNCHD_PATH) if (runnable(`${dir}/node`)) return { path: `${dir}/node`, via: 'PATH' };
285
+ for (const folder of HOOK_NODE_FOLDERS) {
286
+ const path = folder.startsWith('$HOME/') ? join(home, folder.slice('$HOME/'.length)) : folder;
287
+ if (runnable(path)) return { path, via: 'folder' };
288
+ }
289
+ const pinned = typeof env.SYNABUN_NODE === 'string' ? env.SYNABUN_NODE : '';
290
+ if (pinned && runnable(pinned)) return { path: pinned, via: 'SYNABUN_NODE' };
291
+ return null;
292
+ }