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
@@ -1,78 +1,210 @@
1
1
  // ─────────────────────────────────────────────────────────────────────────────
2
2
  // Which OpenCode SynaBun talks to, and how to know it is really there.
3
3
  //
4
- // OpenCode 1 and OpenCode 2 are different servers behind the same
5
- // `opencode serve`:
6
- // • OpenCode 1 (`opencode --version` → "1.18.34"): its API at the root
7
- // (/global/health, /session, /global/event, …), no password.
8
- // • OpenCode 2 (→ "opencode v2.0.32"): its API under /api/*, HTTP Basic auth
9
- // (user `opencode`) with a password set per launch (OPENCODE_PASSWORD,
10
- // which OpenCode keeps out of its children's environment); every other
11
- // path is its web app, an HTML page with status 200.
12
- // SynaBun reads the major version of the binary it is about to start (the CLI
13
- // resolver's cached `--version`, keyed by the file, so an upgrade in place is
14
- // read again), starts it the way that version wants, and calls a serve healthy
15
- // only when the right API answers JSON: an HTML page never passes.
4
+ // SynaBun drives OpenCode 2 and nothing else (OpenCode 1 support was removed
5
+ // on 2026-10-24). OpenCode 2 (`opencode --version` → "opencode v2.1.4") serves
6
+ // its API under /api/*, behind HTTP Basic auth (user `opencode`) with a
7
+ // password set per launch. Production serves generate it privately on stdout
8
+ // (serveSpecFor passwordFrom 'stdout'); an environment password would remain
9
+ // readable in the process and its shell's programs. The low-level 'env' mode
10
+ // remains for explicit callers/fixtures. Every other path is its web app, an HTML page with
11
+ // status 200. SynaBun reads the version of the binary it is about to start
12
+ // (the CLI resolver's cached `--version`, keyed by the file, so an upgrade in
13
+ // place is read again), refuses an OpenCode it does not drive with one
14
+ // sentence that says what was found and how to get OpenCode 2, and calls a
15
+ // serve healthy only when OpenCode 2's API answers JSON: an HTML page never
16
+ // passes.
17
+ //
18
+ // OPENCODE_MIN_VERSION is the oldest OpenCode 2 SynaBun's OpenCode layer was
19
+ // built and checked against (the recorded events of tests/fixtures/
20
+ // opencode2-events.json, the plugin hook of the route gate and the browser
21
+ // policy, the live smoke test). npm's `opencode-ai` package is still OpenCode
22
+ // 1; OpenCode 2 comes from its own installer (https://opencode.ai/v2/install),
23
+ // Homebrew (anomalyco/tap/opencode-v2) or WinGet (Anomaly.OpenCode).
16
24
  // ─────────────────────────────────────────────────────────────────────────────
17
25
 
18
26
  import { randomBytes } from 'node:crypto';
27
+ import { request as httpRequest } from 'node:http';
19
28
 
20
- /** Protocols SynaBun speaks, by the OpenCode major version that serves them. */
21
- export const OPENCODE_PROTOCOLS = Object.freeze({ 1: 'opencode1', 2: 'opencode2' });
22
- export const OPENCODE2_USERNAME = 'opencode';
29
+ export const OPENCODE_MIN_VERSION = '2.0.32';
30
+ export const OPENCODE_USERNAME = 'opencode';
23
31
 
24
- /** "1.18.34", "opencode v2.0.32", "v1.19.3" → 1 / 2; null when no version can be read. */
25
- export function opencodeMajorOf(version) {
32
+ /** "2.1.4", "opencode v2.1.4", "v1.19.3" → { major, minor, patch }; null when no version can be read. */
33
+ export function opencodeVersionOf(version) {
26
34
  const match = String(version ?? '').match(/(\d+)\.(\d+)\.(\d+)/);
27
35
  if (!match) return null;
28
- const major = Number(match[1]);
29
- return Number.isFinite(major) ? major : null;
36
+ const [major, minor, patch] = match.slice(1, 4).map(Number);
37
+ return [major, minor, patch].every(Number.isFinite) ? { major, minor, patch } : null;
38
+ }
39
+
40
+ const MINIMUM = opencodeVersionOf(OPENCODE_MIN_VERSION);
41
+
42
+ function olderThanMinimum(v) {
43
+ if (v.major !== MINIMUM.major) return v.major < MINIMUM.major;
44
+ if (v.minor !== MINIMUM.minor) return v.minor < MINIMUM.minor;
45
+ return v.patch < MINIMUM.patch;
30
46
  }
31
47
 
32
48
  /**
33
- * The protocol for a version string: `{ protocol: 1 | 2 | null, major,
34
- * version, supported, reason }`. OpenCode 0.x is the 1.x API's predecessor and
35
- * speaks it; a major SynaBun does not know is refused with a sentence, never
36
- * guessed at.
49
+ * The commands that install OpenCode 2 on a system, the first one first: OpenCode's own installer
50
+ * (https://opencode.ai/v2/install, the one `opencode upgrade` runs), its Homebrew tap, WinGet on Windows.
37
51
  */
38
- export function protocolForVersion(version) {
39
- const major = opencodeMajorOf(version);
52
+ export function opencodeInstallCommands(platform = process.platform) {
53
+ if (platform === 'win32') return ['winget install --id Anomaly.OpenCode --exact'];
54
+ if (platform === 'darwin') return ['brew install anomalyco/tap/opencode-v2', 'curl -fsSL https://opencode.ai/v2/install | bash'];
55
+ return ['curl -fsSL https://opencode.ai/v2/install | bash'];
56
+ }
57
+
58
+ /** How to get OpenCode 2 on a system, as words of a sentence ("run: …, or: …"). */
59
+ export function opencodeInstallRoute(platform = process.platform) {
60
+ return opencodeInstallCommands(platform).map((command, index) => `${index ? 'or: ' : 'run: '}${command}`).join(', ');
61
+ }
62
+
63
+ /**
64
+ * Whether SynaBun drives the OpenCode of a version string: `{ supported,
65
+ * version, major, reason, code }`. `reason` is the one sentence a person reads
66
+ * when it does not: what was found (`path`, when known) and how to get OpenCode
67
+ * 2; `code` says which case it is ('unreadable' | 'opencode1' | 'newer' |
68
+ * 'outdated'), for a page that words it in its own language.
69
+ */
70
+ export function opencodeSupportFor(version, { path = null, platform = process.platform } = {}) {
40
71
  const text = version ? String(version).trim() : '';
41
- if (major == null) {
42
- return { protocol: null, major: null, version: text || null, supported: false, reason: text ? `SynaBun cannot read the OpenCode version "${text}".` : 'SynaBun could not read the OpenCode version.' };
72
+ const parsed = opencodeVersionOf(text);
73
+ const where = path ? ` (${path})` : '';
74
+ // What every refusal but an outdated OpenCode 2's ends with: how to get OpenCode 2, or where to pick one.
75
+ const getOpencode2 = `install OpenCode 2 (${opencodeInstallRoute(platform)}), or choose an OpenCode 2 under Settings → Executable paths`;
76
+ if (!parsed) {
77
+ const found = text ? `SynaBun cannot read the OpenCode version "${text}"${where}` : `SynaBun could not read the version of the OpenCode it found${where}`;
78
+ return { supported: false, version: text || null, major: null, code: 'unreadable', reason: `${found}, and it needs OpenCode 2 (${OPENCODE_MIN_VERSION} or newer): ${getOpencode2}.` };
79
+ }
80
+ const { major } = parsed;
81
+ // The version as OpenCode prints it, without its name ("opencode v2.1.4" → "2.1.4", a prerelease kept whole).
82
+ const shown = text.replace(/^opencode\s+/i, '').replace(/^v(?=\d)/i, '') || `${parsed.major}.${parsed.minor}.${parsed.patch}`;
83
+ if (major <= 1) {
84
+ return { supported: false, version: text, major, code: 'opencode1', reason: `SynaBun works with OpenCode 2 only, and the OpenCode it found${where} is version ${shown}: install OpenCode 2 (${opencodeInstallRoute(platform)}; npm's opencode-ai package is still OpenCode 1), or choose an OpenCode 2 under Settings → Executable paths.` };
85
+ }
86
+ if (major >= 3) {
87
+ return { supported: false, version: text, major, code: 'newer', reason: `OpenCode ${shown}${where} is newer than SynaBun supports: SynaBun works with OpenCode 2 only, so ${getOpencode2}.` };
88
+ }
89
+ if (olderThanMinimum(parsed)) {
90
+ return { supported: false, version: text, major, code: 'outdated', reason: `SynaBun needs OpenCode ${OPENCODE_MIN_VERSION} or newer, and the OpenCode it found${where} is version ${shown}: update it (run: opencode upgrade).` };
43
91
  }
44
- if (major <= 1) return { protocol: 1, major, version: text, supported: true, reason: null };
45
- if (major === 2) return { protocol: 2, major, version: text, supported: true, reason: null };
46
- return { protocol: null, major, version: text, supported: false, reason: `OpenCode ${major} (${text}) is newer than SynaBun supports: SynaBun works with OpenCode 1 and OpenCode 2.` };
92
+ return { supported: true, version: text, major, code: null, reason: null };
47
93
  }
48
94
 
49
- /** A fresh password for one OpenCode 2 launch. */
95
+ /** A fresh password for one serve launch. */
50
96
  export function newServePassword() {
51
97
  return randomBytes(24).toString('base64url');
52
98
  }
53
99
 
54
- /** The Authorization header for an OpenCode 2 serve. */
55
- export function basicAuthHeader(password, username = OPENCODE2_USERNAME) {
100
+ /** The Authorization header for a serve. */
101
+ export function basicAuthHeader(password, username = OPENCODE_USERNAME) {
56
102
  return `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`;
57
103
  }
58
104
 
105
+ /** The environment variables OpenCode 2 takes its serve password from (OPENCODE_PASSWORD first). */
106
+ export const SERVE_PASSWORD_ENV = Object.freeze(['OPENCODE_PASSWORD', 'OPENCODE_SERVER_PASSWORD']);
107
+
59
108
  /**
60
- * The arguments and environment of `opencode serve` on `port` for a protocol.
61
- * OpenCode 1 keeps exactly what SynaBun always ran (`serve --port N`); OpenCode
62
- * 2 also gets its loopback host name and the launch's password (OPENCODE_PASSWORD;
63
- * any OPENCODE_SERVER_PASSWORD the environment carries is dropped, so the
64
- * password SynaBun holds is the one the serve checks). A version SynaBun could
65
- * not read (`protocol` null: a command line in Settings) starts like OpenCode
66
- * 2: OpenCode 1 ignores OPENCODE_PASSWORD and takes --hostname too, and the
67
- * health check then says which one answered.
109
+ * The arguments and environment of `opencode serve` on `port`: its loopback
110
+ * host name and the launch's password. `passwordFrom`:
111
+ * 'env' (default) the password is OPENCODE_PASSWORD (any
112
+ * OPENCODE_SERVER_PASSWORD the environment carries is dropped, so
113
+ * the password SynaBun holds is the one the serve checks). OpenCode
114
+ * 2 keeps it in its own environment, so every program its shell
115
+ * starts inherits it, and a process of the same user can read it
116
+ * in the serve's start-up environment (ps eww).
117
+ * 'stdout' no password variable at all: OpenCode 2 makes one up and prints
118
+ * it once on its standard output ("server password <it>", after
119
+ * "server listening on …"), which SynaBun reads
120
+ * (servePasswordReader) and keeps out of its logs
121
+ * (redactServeOutput). The password is in no environment: not the
122
+ * serve's, not its shell's. `password` is null until it is read.
123
+ * A launch through a login shell removes both variables after the
124
+ * profile ran (`unset`, serveUnsetCommand), so a profile that
125
+ * exports one cannot put it back.
68
126
  */
69
- export function serveSpecFor(protocol, port, { env = process.env, password = null } = {}) {
70
- const args = ['serve', '--port', String(port)];
71
- if (protocol === 1) return { args, env, password: null };
127
+ export function serveSpecFor(port, { env = process.env, password = null, passwordFrom = 'env' } = {}) {
128
+ const args = ['serve', '--port', String(port), '--hostname', '127.0.0.1'];
129
+ const next = { ...env };
130
+ for (const key of SERVE_PASSWORD_ENV) delete next[key];
131
+ if (passwordFrom === 'stdout') return { args, env: next, password: null, passwordFrom: 'stdout' };
72
132
  const pass = password || newServePassword();
73
- const next = { ...env, OPENCODE_PASSWORD: pass };
74
- delete next.OPENCODE_SERVER_PASSWORD;
75
- return { args: [...args, '--hostname', '127.0.0.1'], env: next, password: pass };
133
+ next.OPENCODE_PASSWORD = pass;
134
+ return { args, env: next, password: pass, passwordFrom: 'env' };
135
+ }
136
+
137
+ /** What a POSIX shell runs before `exec opencode serve` of a 'stdout' launch: the password variables gone. */
138
+ export function serveUnsetCommand() {
139
+ return `unset ${SERVE_PASSWORD_ENV.join(' ')}`;
140
+ }
141
+
142
+ const SERVE_PASSWORD_LINE = /(?:^|\n)server password (\S+)[ \t]*\r?\n/;
143
+
144
+ /**
145
+ * A reader of a 'stdout' serve's output: feed it each chunk of standard output; it answers the password once its
146
+ * line is complete ("server password <it>" and a line end), else null. Chunks may split the line anywhere.
147
+ */
148
+ export function servePasswordReader() {
149
+ let buffered = '';
150
+ let found = null;
151
+ return (chunk) => {
152
+ if (found) return found;
153
+ buffered = `${buffered}${String(chunk ?? '')}`.slice(-4096);
154
+ const m = SERVE_PASSWORD_LINE.exec(buffered);
155
+ if (m) found = m[1];
156
+ return found;
157
+ };
158
+ }
159
+
160
+ /** Serve output with its password line taken out (what a log may hold). */
161
+ export function redactServeOutput(text) {
162
+ return String(text ?? '').replace(/server password \S+/g, 'server password <hidden>');
163
+ }
164
+
165
+ /**
166
+ * A line-buffering redactor for either of a serve's output streams. redactServeOutput runs on whole lines only: a chunk of a
167
+ * stream may split the "server password <x>" line anywhere (the token across two reads), and redacting each chunk on
168
+ * its own would let a fragment of the password through to the log. Feed every chunk to `push`; it assembles complete
169
+ * lines, redacts each and hands it to `emit` (the trailing newline kept). Call `flush` once the stream ends to emit a
170
+ * last line that never got its newline. A partial line that grows past `maxLine` without a newline is redacted and
171
+ * emitted so the buffer cannot grow without bound (the password line is short and newline-terminated, so this cap is
172
+ * never reached mid-password).
173
+ */
174
+ export function createServeLogRedactor(emit, { maxLine = 65536 } = {}) {
175
+ let buf = '';
176
+ const drain = (final) => {
177
+ let nl;
178
+ while ((nl = buf.indexOf('\n')) >= 0) {
179
+ const line = buf.slice(0, nl + 1);
180
+ buf = buf.slice(nl + 1);
181
+ emit(redactServeOutput(line));
182
+ }
183
+ if ((final || buf.length > maxLine) && buf) { emit(redactServeOutput(buf)); buf = ''; }
184
+ };
185
+ return {
186
+ push: (chunk) => { buf += String(chunk ?? ''); drain(false); },
187
+ flush: () => drain(true),
188
+ };
189
+ }
190
+
191
+ /**
192
+ * A serve's environment with a run's own settings on top: a string value is set, null (or undefined) removes the
193
+ * variable, anything else is ignored. `GIT_*: null` clears every inherited git variable BEFORE the explicit
194
+ * settings are applied, so SynaBun's own git safety settings survive. The marker never reaches the child.
195
+ * A new object; `env` is not changed. Also used for evaluation runtimes, whose whole process tree needs this policy.
196
+ */
197
+ export function withServeEnv(env, extra) {
198
+ const next = { ...(env || {}) };
199
+ if (extra && typeof extra === 'object') {
200
+ if (extra['GIT_*'] === null) for (const key of Object.keys(next)) if (key.startsWith('GIT_')) delete next[key];
201
+ for (const [key, value] of Object.entries(extra)) {
202
+ if (key === 'GIT_*') continue;
203
+ if (typeof value === 'string') next[key] = value;
204
+ else if (value === null || value === undefined) delete next[key];
205
+ }
206
+ }
207
+ return next;
76
208
  }
77
209
 
78
210
  function isJsonResponse(res) {
@@ -85,89 +217,94 @@ async function readJson(res) {
85
217
  }
86
218
 
87
219
  /**
88
- * Is a real OpenCode API answering on `port`? `protocol` 1 asks
89
- * /global/health, 2 asks /api/info with `password`; `protocol` null asks both
90
- * (an unknown serve: which one is it?). Resolves
91
- * `{ ok, protocol, version, state, error }`, where `state` is
92
- * 'healthy' the protocol's API answered JSON
93
- * 'locked' an OpenCode 2 serve whose password SynaBun does not hold
94
- * 'mismatch' an OpenCode of the other major answered
95
- * 'html' a web page (OpenCode 2's app, or anything else) — never healthy
96
- * 'down' nothing answered
97
- * 'error' something answered, but not an OpenCode API
220
+ * Is OpenCode 2's API answering on `port`? Asks /api/info with `password`.
221
+ * Resolves `{ ok, version, state, error }`, where `state` is
222
+ * 'healthy' OpenCode 2's API answered JSON, from a version SynaBun drives
223
+ * 'unsupported' OpenCode's API answered, from a version SynaBun does not drive
224
+ * (`error` is opencodeSupportFor's sentence)
225
+ * 'locked' an OpenCode 2 serve whose password SynaBun does not hold
226
+ * 'html' a web page — never healthy
227
+ * 'down' nothing answered
228
+ * 'error' something answered, but not OpenCode 2's API
98
229
  * Never throws.
99
230
  */
100
- export async function checkOpencodeServer({ port, protocol = null, password = null, fetchImpl = globalThis.fetch, timeoutMs = 2000, host = '127.0.0.1' } = {}) {
101
- const base = `http://${host}:${port}`;
102
- const get = async (path, headers = {}) => {
103
- try {
104
- return await fetchImpl(`${base}${path}`, { headers: { accept: 'application/json', ...headers }, signal: AbortSignal.timeout(timeoutMs) });
105
- } catch (error) {
106
- return { failed: true, error };
107
- }
108
- };
109
- const asV1 = async () => {
110
- const res = await get('/global/health');
111
- if (res.failed) return { ok: false, protocol: 1, version: null, state: 'down', error: String(res.error?.message || res.error) };
112
- const { json, html } = await readJson(res);
113
- if (res.ok && json && (json.healthy === true || typeof json.version === 'string')) {
114
- return { ok: true, protocol: 1, version: typeof json.version === 'string' ? json.version : null, state: 'healthy', error: null };
115
- }
116
- return { ok: false, protocol: 1, version: null, state: html ? 'html' : 'error', error: html ? 'answered with a web page, not the OpenCode 1 API' : `GET /global/health answered ${res.status}` };
117
- };
118
- const asV2 = async () => {
119
- const res = await get('/api/info', password ? { authorization: basicAuthHeader(password) } : {});
120
- if (res.failed) return { ok: false, protocol: 2, version: null, state: 'down', error: String(res.error?.message || res.error) };
121
- const { json, html } = await readJson(res);
122
- if (res.ok && json && typeof json.version === 'string' && Array.isArray(json.urls)) {
123
- return { ok: true, protocol: 2, version: json.version, state: 'healthy', error: null };
124
- }
125
- if (res.status === 401 && json && /unauthori[sz]ed/i.test(String(json._tag || json.name || ''))) {
126
- return { ok: false, protocol: 2, version: null, state: 'locked', error: password ? 'the OpenCode 2 serve refused SynaBun\'s password' : 'an OpenCode 2 serve SynaBun did not start (it needs that serve\'s password)' };
127
- }
128
- return { ok: false, protocol: 2, version: null, state: html ? 'html' : 'error', error: html ? 'answered with a web page, not the OpenCode 2 API' : `GET /api/info answered ${res.status}` };
129
- };
130
-
131
- if (protocol === 1) {
132
- const v1 = await asV1();
133
- if (v1.ok || v1.state === 'down') return v1;
134
- // A web page where OpenCode 1 was expected: is it an OpenCode 2?
135
- const v2 = await asV2();
136
- if (v2.ok || v2.state === 'locked') return { ...v2, ok: false, state: 'mismatch', error: 'an OpenCode 2 serve answered where SynaBun expected OpenCode 1' };
137
- return v1;
231
+ export async function checkOpencodeServer({ port, password = null, fetchImpl = globalThis.fetch, timeoutMs = 2000, host = '127.0.0.1' } = {}) {
232
+ let res;
233
+ try {
234
+ res = await fetchImpl(`http://${host}:${port}/api/info`, {
235
+ headers: { accept: 'application/json', ...(password ? { authorization: basicAuthHeader(password) } : {}) },
236
+ signal: AbortSignal.timeout(timeoutMs),
237
+ });
238
+ } catch (error) {
239
+ return { ok: false, version: null, state: 'down', error: String(error?.message || error) };
138
240
  }
139
- if (protocol === 2) {
140
- const v2 = await asV2();
141
- if (v2.ok || v2.state === 'down' || v2.state === 'locked') return v2;
142
- const v1 = await asV1();
143
- if (v1.ok) return { ...v1, ok: false, state: 'mismatch', error: 'an OpenCode 1 serve answered where SynaBun expected OpenCode 2' };
144
- return v2;
241
+ const { json, html } = await readJson(res);
242
+ if (res.ok && json && typeof json.version === 'string' && Array.isArray(json.urls)) {
243
+ const support = opencodeSupportFor(json.version);
244
+ if (!support.supported) return { ok: false, version: json.version, state: 'unsupported', error: support.reason };
245
+ return { ok: true, version: json.version, state: 'healthy', error: null };
145
246
  }
146
- const v1 = await asV1();
147
- if (v1.ok || v1.state === 'down') return v1;
148
- const v2 = await asV2();
149
- return v2.ok || v2.state === 'locked' ? v2 : v1;
247
+ if (res.status === 401 && json && /unauthori[sz]ed/i.test(String(json._tag || json.name || ''))) {
248
+ return { ok: false, version: null, state: 'locked', error: password ? 'the OpenCode serve refused SynaBun\'s password' : 'an OpenCode serve SynaBun did not start (it needs that serve\'s password)' };
249
+ }
250
+ return { ok: false, version: null, state: html ? 'html' : 'error', error: html ? 'answered with a web page, not the OpenCode 2 API' : `GET /api/info answered ${res.status}` };
251
+ }
252
+
253
+ // An upgrade must be rejected before PTY/resource lookup, even for an Origin
254
+ // that OpenCode permits. Never place a real credential in a URL or this probe.
255
+ export function probeUnauthenticatedUpgrade({ port, timeoutMs = 2000 } = {}) {
256
+ return new Promise(resolve => {
257
+ const req = httpRequest({ hostname: '127.0.0.1', port,
258
+ path: '/api/pty/synabun-auth-probe/connect',
259
+ headers: { connection: 'Upgrade', upgrade: 'websocket', 'sec-websocket-version': '13',
260
+ 'sec-websocket-key': randomBytes(16).toString('base64'), origin: 'http://localhost:1' },
261
+ });
262
+ req.on('response', res => { res.resume(); resolve(res.statusCode); });
263
+ req.on('upgrade', (_res, socket) => { socket.destroy(); resolve(101); });
264
+ req.on('error', () => resolve(null));
265
+ req.setTimeout(timeoutMs, () => { req.destroy(); resolve(null); });
266
+ req.end();
267
+ });
268
+ }
269
+
270
+ /** Readiness for private serves: valid client + denied anonymous HTTP AND WS.
271
+ * An unmanaged, passwordless API is never adopted. Errors contain no secrets.
272
+ */
273
+ export async function checkPrivateOpencodeServer(options = {}) {
274
+ const check = await checkOpencodeServer(options);
275
+ if (!check.ok) return check;
276
+ const refused = () => ({ ok: false, version: check.version, state: 'unsecured',
277
+ error: 'OpenCode did not prove private HTTP and WebSocket authentication; it will not be used.' });
278
+ if (!options.password) return refused();
279
+ try {
280
+ const response = await (options.fetchImpl || globalThis.fetch)(`http://127.0.0.1:${options.port}/api/info`, {
281
+ headers: { accept: 'application/json' }, signal: AbortSignal.timeout(options.timeoutMs || 2000), redirect: 'error',
282
+ });
283
+ await response.body?.cancel();
284
+ if (response.status !== 401 || await probeUnauthenticatedUpgrade(options) !== 401) return refused();
285
+ } catch { return refused(); }
286
+ return check;
150
287
  }
151
288
 
152
289
  /**
153
290
  * The sentence a person reads when OpenCode cannot be used: what was found and
154
- * what to do. `check` is a checkOpencodeServer answer, `detected` a
155
- * protocolForVersion answer.
291
+ * what to do. `check` is a checkOpencodeServer answer, `detected` an
292
+ * opencodeSupportFor answer.
156
293
  */
157
294
  export function opencodeProblemText({ check = null, detected = null, port = null } = {}) {
158
295
  if (detected && !detected.supported) return detected.reason;
159
296
  if (!check || check.ok) return '';
160
297
  const where = port ? ` on port ${port}` : '';
161
298
  switch (check.state) {
299
+ case 'unsupported':
300
+ return check.error || `The OpenCode server${where} is a version SynaBun does not drive.`;
162
301
  case 'locked':
163
- return `An OpenCode 2 server SynaBun did not start is already running${where}, and SynaBun does not have its password. Stop that server (or free the port) and start OpenCode again from SynaBun.`;
164
- case 'mismatch':
165
- return `The OpenCode server${where} is not the version SynaBun found on this computer (${check.error}). Stop it and start OpenCode again from SynaBun.`;
302
+ return `An OpenCode server SynaBun did not start is already running${where}, and SynaBun does not have its password. Stop that server (or free the port) and start OpenCode again from SynaBun.`;
166
303
  case 'html':
167
- return `Something${where} answers with a web page instead of the OpenCode API (${check.error}).`;
304
+ return `Something${where} answers with a web page instead of the OpenCode 2 API (${check.error}).`;
168
305
  case 'down':
169
306
  return `OpenCode is not answering${where}.`;
170
307
  default:
171
- return `OpenCode${where} did not answer as expected: ${check.error || 'unknown error'}.`;
308
+ return `OpenCode${where} did not answer as OpenCode 2: ${check.error || 'unknown error'}.`;
172
309
  }
173
310
  }
@@ -1,35 +1,33 @@
1
1
  import { dirname, resolve } from 'node:path';
2
2
  import { fileURLToPath, pathToFileURL } from 'node:url';
3
+ import { mcpServersOf, normalizeMcpServers, parseJsonc, setMcpServer, withPlugin } from './opencode-config.js';
3
4
 
4
- /** The browser-policy plugin (assistant-brains/opencode-browser-policy.js) as a file URL a serve can load. */
5
- export const OPENCODE_BROWSER_POLICY_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'assistant-brains', 'opencode-browser-policy.js')).href;
6
- /** Its OpenCode 2 form: a plugin folder, listed under `plugins` (OpenCode 1 ignores that key). */
7
- export const OPENCODE2_BROWSER_POLICY_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'assistant-brains', 'opencode2-browser-policy')).href;
5
+ /** The browser-policy plugin (assistant-brains/opencode2-browser-policy/, a plugin folder) as a file URL a serve can load. */
6
+ export const OPENCODE_BROWSER_POLICY_PLUGIN = pathToFileURL(resolve(dirname(fileURLToPath(import.meta.url)), 'assistant-brains', 'opencode2-browser-policy')).href;
8
7
 
9
8
  /**
10
9
  * An Assistant task run's serve config gets SynaBun's browser policy as a
11
- * plugin (`cwd`: the run's project, for the scripts it reads), for OpenCode 1
12
- * (`plugin`) and OpenCode 2 (`plugins`). The user's own plugins stay; an
13
- * earlier entry of ours is replaced. Returns `config`.
10
+ * plugin (`cwd`: the run's project, for the scripts it reads; `roots`: the
11
+ * registered code projects its test runners may also run in, as the run
12
+ * starts), listed under `plugins`. The user's own plugins stay; an earlier
13
+ * entry of ours is replaced. Returns `config`.
14
14
  */
15
- export function withOpenCodeBrowserPolicy(config, { cwd = null } = {}) {
16
- const options = cwd ? { cwd: String(cwd) } : {};
17
- const plugins = (Array.isArray(config.plugin) ? config.plugin : []).filter((entry) => (Array.isArray(entry) ? entry[0] : entry) !== OPENCODE_BROWSER_POLICY_PLUGIN);
18
- plugins.push([OPENCODE_BROWSER_POLICY_PLUGIN, options]);
19
- config.plugin = plugins;
20
- const plugins2 = (Array.isArray(config.plugins) ? config.plugins : []).filter((entry) => (entry && typeof entry === 'object' ? entry.package : entry) !== OPENCODE2_BROWSER_POLICY_PLUGIN);
21
- plugins2.push({ package: OPENCODE2_BROWSER_POLICY_PLUGIN, options });
22
- config.plugins = plugins2;
23
- return config;
15
+ export function withOpenCodeBrowserPolicy(config, { cwd = null, roots = [] } = {}) {
16
+ const list = (Array.isArray(roots) ? roots : []).filter((path) => typeof path === 'string' && path);
17
+ return withPlugin(config, OPENCODE_BROWSER_POLICY_PLUGIN, { ...(cwd ? { cwd: String(cwd) } : {}), ...(list.length ? { roots: list } : {}) });
24
18
  }
25
19
 
26
20
  /**
27
- * Build the SynaBun MCP entry used by managed OpenCode runtimes.
21
+ * Build the SynaBun MCP entry used by managed OpenCode runtimes, in OpenCode
22
+ * 2's shape (`mcp.servers.SynaBun`: type local, the command as an array, its
23
+ * `environment`).
28
24
  *
29
25
  * Managed sidepanels/automations must always use a local MCP child. Reusing a
30
26
  * user's remote entry would ignore process environment pins and collapse all
31
- * sessions onto the shared HTTP server. Preserve harmless local options, but
32
- * remove remote-only transport fields and canonicalize command/environment.
27
+ * sessions onto the shared HTTP server. Preserve harmless local options (its
28
+ * timeout, its working folder), but remove remote-only transport fields and a
29
+ * switch that turned it off, and canonicalize command/environment. SynaBun tools are always direct calls;
30
+ * OpenCode 2.1 otherwise hides them behind Code Mode (`execute`). Other servers keep their own settings.
33
31
  */
34
32
  export function buildManagedOpenCodeSynabunEntry(existing, {
35
33
  command,
@@ -45,7 +43,10 @@ export function buildManagedOpenCodeSynabunEntry(existing, {
45
43
  environment: _environment,
46
44
  env: _env,
47
45
  command: _command,
46
+ args: _args,
48
47
  type: _type,
48
+ enabled: _enabled,
49
+ disabled: _disabled,
49
50
  ...safeOptions
50
51
  } = source;
51
52
  const environment = {
@@ -57,9 +58,18 @@ export function buildManagedOpenCodeSynabunEntry(existing, {
57
58
  return {
58
59
  ...safeOptions,
59
60
  type: 'local',
60
- command: Array.isArray(command) ? [...command] : command,
61
- enabled: true,
61
+ codemode: false,
62
+ command: Array.isArray(command) ? [...command] : [command],
62
63
  environment,
63
- env: { ...environment },
64
64
  };
65
65
  }
66
+
67
+ /** Pin only SynaBun on the shared serve, preserving an inherited overlay and the owner's other servers. */
68
+ export function buildSharedOpenCodeConfigOverlay(config, content) {
69
+ const overlay = normalizeMcpServers(parseJsonc(content || '{}'));
70
+ const synabun = mcpServersOf(config).SynaBun;
71
+ const inherited = mcpServersOf(overlay).SynaBun;
72
+ if (!synabun && !inherited) return content;
73
+ setMcpServer(overlay, 'SynaBun', { ...synabun, ...inherited, codemode: false });
74
+ return JSON.stringify(overlay);
75
+ }