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
@@ -0,0 +1,557 @@
1
+ // ─────────────────────────────────────────────────────────────────────────────
2
+ // OpenCode 2's configuration, as SynaBun reads and writes it.
3
+ //
4
+ // OpenCode 2 reads its global configuration from `opencode.json` and
5
+ // `opencode.jsonc` in its configuration folder (OPENCODE_CONFIG_DIR when set,
6
+ // else $XDG_CONFIG_HOME/opencode, else ~/.config/opencode), the .jsonc after
7
+ // the .json, and never reads `config.json` (OpenCode 1's legacy file, which
8
+ // older SynaBun releases wrote). It still accepts OpenCode 1's field shapes in
9
+ // those files and converts them as it reads them; SynaBun writes OpenCode 2's
10
+ // shapes only:
11
+ // mcp.servers.<name> { type: 'local', command: [...], environment, disabled }
12
+ // or { type: 'remote', url, headers, disabled }
13
+ // permissions [{ action, resource, effect: 'allow'|'ask'|'deny' }],
14
+ // the last matching rule deciding; OpenCode 1's
15
+ // `permission` object (read first) is converted here the
16
+ // way OpenCode converts it (bash → shell, task → subagent)
17
+ // agents.<name> { system, description, mode, hidden, model, steps, permissions }
18
+ // plugins [package | { package, options }]
19
+ // providers.<id> { name, package, settings: { baseURL }, models }
20
+ // compaction { auto, buffer, keep: { tokens } }
21
+ // snapshots boolean
22
+ // SynaBun writes `opencode.json` and never the .jsonc (its comments are the
23
+ // user's): OpenCode merges both, so an entry of SynaBun's in opencode.json is
24
+ // in effect unless the user's .jsonc overrides it.
25
+ //
26
+ // Pure, apart from the read and write helpers at the end that take a path.
27
+ // ─────────────────────────────────────────────────────────────────────────────
28
+
29
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
30
+ import os from 'node:os';
31
+ import { dirname, join, resolve } from 'node:path';
32
+
33
+ export const OPENCODE_CONFIG_SCHEMA = 'https://opencode.ai/config.json';
34
+ /** The global documents OpenCode 2 reads, lowest precedence first. */
35
+ export const OPENCODE_CONFIG_FILES = Object.freeze(['opencode.json', 'opencode.jsonc']);
36
+ /** The one SynaBun writes. */
37
+ export const OPENCODE_CONFIG_FILE = 'opencode.json';
38
+
39
+ /**
40
+ * Permission actions of OpenCode 2's own tools (its edit, write and patch tools all ask `edit`), in the order
41
+ * Settings → OpenCode lists them. MCP tools are actions too, named `<server>_<tool>`.
42
+ */
43
+ export const OPENCODE_TOOL_ACTIONS = Object.freeze([
44
+ 'read', 'edit', 'shell', 'glob', 'grep', 'list', 'lsp', 'webfetch', 'websearch',
45
+ 'subagent', 'skill', 'question', 'external_directory', 'browser', 'doom_loop',
46
+ ]);
47
+ /** What OpenCode 2 decides for an action no rule names (its default agent: everything allowed, its browser denied). */
48
+ export const OPENCODE_DEFAULT_EFFECTS = Object.freeze({ browser: 'deny' });
49
+ export const PERMISSION_EFFECTS = Object.freeze(['allow', 'ask', 'deny']);
50
+
51
+ /** OpenCode 1 names of permission actions OpenCode 2 renamed. */
52
+ const RENAMED_ACTIONS = Object.freeze({ bash: 'shell', task: 'subagent' });
53
+
54
+ const isObject = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
55
+ const asObject = (value) => (isObject(value) ? value : {});
56
+
57
+ /** OpenCode's configuration folder: OPENCODE_CONFIG_DIR, else $XDG_CONFIG_HOME/opencode, else ~/.config/opencode. */
58
+ export function opencodeConfigDir({ env = process.env, home = os.homedir() } = {}) {
59
+ if (env.OPENCODE_CONFIG_DIR) return resolve(String(env.OPENCODE_CONFIG_DIR));
60
+ if (env.XDG_CONFIG_HOME) return join(String(env.XDG_CONFIG_HOME), 'opencode');
61
+ return join(home, '.config', 'opencode');
62
+ }
63
+
64
+ /** The file SynaBun writes in a configuration folder. */
65
+ export function opencodeConfigFilePath(dir) {
66
+ return join(dir, OPENCODE_CONFIG_FILE);
67
+ }
68
+
69
+ // ── JSONC ────────────────────────────────────────────────────────────────────
70
+
71
+ /**
72
+ * The value of a JSON-with-comments text (`//` and block comments, trailing commas), as OpenCode reads its
73
+ * .jsonc. Throws a SyntaxError on text that is not JSON once comments and trailing commas are gone.
74
+ */
75
+ export function parseJsonc(text) {
76
+ const s = String(text ?? '').replace(/^/, '');
77
+ let out = '';
78
+ let i = 0;
79
+ while (i < s.length) {
80
+ const ch = s[i];
81
+ if (ch === '"') {
82
+ let j = i + 1;
83
+ while (j < s.length && s[j] !== '"') j += s[j] === '\\' ? 2 : 1;
84
+ out += s.slice(i, j + 1);
85
+ i = j + 1;
86
+ continue;
87
+ }
88
+ if (ch === '/' && s[i + 1] === '/') {
89
+ while (i < s.length && s[i] !== '\n') i++;
90
+ continue;
91
+ }
92
+ if (ch === '/' && s[i + 1] === '*') {
93
+ const end = s.indexOf('*/', i + 2);
94
+ if (end < 0) throw new SyntaxError('Unterminated comment');
95
+ i = end + 2;
96
+ continue;
97
+ }
98
+ if (ch === ',') {
99
+ // A comma followed (after blanks and comments) by a closing bracket is a trailing comma.
100
+ let j = i + 1;
101
+ for (;;) {
102
+ while (j < s.length && /\s/.test(s[j])) j++;
103
+ if (s[j] === '/' && s[j + 1] === '/') { while (j < s.length && s[j] !== '\n') j++; continue; }
104
+ if (s[j] === '/' && s[j + 1] === '*') { const end = s.indexOf('*/', j + 2); if (end < 0) break; j = end + 2; continue; }
105
+ break;
106
+ }
107
+ if (s[j] === '}' || s[j] === ']') { i++; continue; }
108
+ }
109
+ out += ch;
110
+ i++;
111
+ }
112
+ return out.trim() ? JSON.parse(out) : {};
113
+ }
114
+
115
+ // ── MCP servers ──────────────────────────────────────────────────────────────
116
+
117
+ // A remote server's OAuth client as OpenCode 2 names it (seen on 2.1.4: it keeps these and drops any other name,
118
+ // `clientId` and `callbackPort` included), from the names SynaBun's MCP registry and Claude Code use.
119
+ const OAUTH_NAMES = { clientId: 'client_id', clientSecret: 'client_secret', scope: 'scope', callbackPort: 'callback_port', redirectUri: 'redirect_uri' };
120
+ const OAUTH_KEPT = new Set(Object.values(OAUTH_NAMES));
121
+
122
+ /** `false` (no OAuth), the OAuth client in OpenCode 2's names, or undefined (nothing to say: OpenCode's own default). */
123
+ function opencodeOauth(oauth) {
124
+ if (oauth === false) return false;
125
+ if (!isObject(oauth)) return undefined;
126
+ const out = {};
127
+ for (const [key, value] of Object.entries(oauth)) {
128
+ const name = OAUTH_NAMES[key] || (OAUTH_KEPT.has(key) ? key : null);
129
+ if (name && value !== undefined && value !== null && value !== '') out[name] = value;
130
+ }
131
+ return Object.keys(out).length ? out : undefined;
132
+ }
133
+
134
+ /**
135
+ * An MCP server as OpenCode 2 takes it (`mcp.servers.<name>`), from what SynaBun describes as
136
+ * `{ command, args, env }` / `{ url, headers, oauth }`, or from an entry of either major (`enabled: false` →
137
+ * `disabled`). Every word of a command goes through as it is: an argument may be empty or carry spaces on purpose.
138
+ */
139
+ export function opencodeMcpServerEntry(input = {}) {
140
+ const source = asObject(input);
141
+ const disabled = source.disabled === true || source.enabled === false;
142
+ const timeout = isObject(source.timeout)
143
+ ? { ...source.timeout }
144
+ : (Number(source.timeout) > 0 ? { startup: Number(source.timeout), catalog: Number(source.timeout), execution: Number(source.timeout) } : null);
145
+ // Options OpenCode 2 takes on either kind of server, as they are.
146
+ const shared = {
147
+ ...(typeof source.codemode === 'boolean' ? { codemode: source.codemode } : {}),
148
+ ...(source.protocol !== undefined && source.protocol !== null ? { protocol: source.protocol } : {}),
149
+ ...(timeout ? { timeout } : {}),
150
+ ...(disabled ? { disabled: true } : {}),
151
+ };
152
+ if (source.type === 'remote' || (typeof source.url === 'string' && source.url && source.type !== 'local')) {
153
+ const oauth = opencodeOauth(source.oauth);
154
+ return {
155
+ type: 'remote',
156
+ url: String(source.url || ''),
157
+ ...(isObject(source.headers) && Object.keys(source.headers).length ? { headers: { ...source.headers } } : {}),
158
+ ...(oauth !== undefined ? { oauth } : {}),
159
+ ...shared,
160
+ };
161
+ }
162
+ const head = Array.isArray(source.command) ? source.command : (source.command != null ? [source.command] : []);
163
+ const command = [...head, ...(Array.isArray(source.args) ? source.args : [])].filter((part) => typeof part === 'string');
164
+ const environment = isObject(source.environment) ? source.environment : (isObject(source.env) ? source.env : null);
165
+ return {
166
+ type: 'local',
167
+ command,
168
+ ...(environment && Object.keys(environment).length ? { environment: { ...environment } } : {}),
169
+ ...(typeof source.cwd === 'string' && source.cwd ? { cwd: source.cwd } : {}),
170
+ ...shared,
171
+ };
172
+ }
173
+
174
+ /** The MCP servers a config names, OpenCode 1 entries (`mcp.<name>`) as OpenCode 2 reads them: `{ name: entry }`. */
175
+ export function mcpServersOf(config) {
176
+ const mcp = asObject(asObject(config).mcp);
177
+ const out = {};
178
+ for (const [name, entry] of Object.entries(mcp)) {
179
+ if (name === 'servers' || name === 'timeout' || !isObject(entry)) continue;
180
+ Object.defineProperty(out, name, { value: opencodeMcpServerEntry(entry), enumerable: true, configurable: true, writable: true });
181
+ }
182
+ for (const [name, entry] of Object.entries(asObject(mcp.servers))) if (isObject(entry)) Object.defineProperty(out, name, { value: { ...entry }, enumerable: true, configurable: true, writable: true });
183
+ return out;
184
+ }
185
+
186
+ /** `config` with MCP server `name` set to `entry` (an OpenCode 1 entry of that name taken out). Mutates and returns it. */
187
+ export function setMcpServer(config, name, entry) {
188
+ const mcp = asObject(config.mcp);
189
+ delete mcp[name];
190
+ mcp.servers = { ...asObject(mcp.servers), [name]: opencodeMcpServerEntry(entry) };
191
+ config.mcp = mcp;
192
+ return config;
193
+ }
194
+
195
+ /** `config` without MCP server `name`, in either shape; empty containers go. Mutates and returns it. */
196
+ export function removeMcpServer(config, name) {
197
+ if (!isObject(config.mcp)) return config;
198
+ delete config.mcp[name];
199
+ if (isObject(config.mcp.servers)) {
200
+ delete config.mcp.servers[name];
201
+ if (!Object.keys(config.mcp.servers).length) delete config.mcp.servers;
202
+ }
203
+ if (!Object.keys(config.mcp).length) delete config.mcp;
204
+ return config;
205
+ }
206
+
207
+ /**
208
+ * Every MCP server of `config` moved into `mcp.servers` (OpenCode 1 entries converted), so SynaBun's own entries
209
+ * and the user's live in one shape before SynaBun changes any. Mutates and returns it.
210
+ */
211
+ export function normalizeMcpServers(config) {
212
+ if (!isObject(config.mcp)) return config;
213
+ const servers = mcpServersOf(config);
214
+ const timeout = config.mcp.timeout;
215
+ config.mcp = { ...(isObject(timeout) ? { timeout } : {}), ...(Object.keys(servers).length ? { servers } : {}) };
216
+ if (!Object.keys(config.mcp).length) delete config.mcp;
217
+ return config;
218
+ }
219
+
220
+ // ── Permission rules ─────────────────────────────────────────────────────────
221
+
222
+ /** The OpenCode 2 name of a permission action (OpenCode 1's bash → shell, task → subagent). */
223
+ export function opencodeAction(name) {
224
+ const key = String(name ?? '');
225
+ return RENAMED_ACTIONS[key] || key;
226
+ }
227
+
228
+ /** One permission rule. */
229
+ export function permissionRule(action, effect, resource = '*') {
230
+ return { action: opencodeAction(action), resource: String(resource), effect };
231
+ }
232
+
233
+ /** OpenCode 1's `permission` object as OpenCode 2's rules, the way OpenCode converts it. */
234
+ export function rulesFromPermissionObject(permission) {
235
+ if (typeof permission === 'string') return PERMISSION_EFFECTS.includes(permission) ? [permissionRule('*', permission)] : [];
236
+ const rules = [];
237
+ for (const [action, value] of Object.entries(asObject(permission))) {
238
+ if (typeof value === 'string') {
239
+ if (PERMISSION_EFFECTS.includes(value)) rules.push(permissionRule(action, value));
240
+ } else if (isObject(value)) {
241
+ for (const [resource, effect] of Object.entries(value)) {
242
+ if (PERMISSION_EFFECTS.includes(effect)) rules.push(permissionRule(action, effect, resource));
243
+ }
244
+ }
245
+ }
246
+ return rules;
247
+ }
248
+
249
+ /** Every rule of a config, in the order OpenCode applies them: OpenCode 1's object first, then `permissions`. */
250
+ export function permissionRulesOf(config) {
251
+ const source = asObject(config);
252
+ const v2 = Array.isArray(source.permissions)
253
+ ? source.permissions.filter((rule) => isObject(rule) && typeof rule.action === 'string' && PERMISSION_EFFECTS.includes(rule.effect))
254
+ .map((rule) => ({ action: rule.action, resource: typeof rule.resource === 'string' ? rule.resource : '*', effect: rule.effect }))
255
+ : [];
256
+ return [...rulesFromPermissionObject(source.permission), ...v2];
257
+ }
258
+
259
+ function wildcardMatch(pattern, value) {
260
+ const text = String(pattern ?? '');
261
+ if (text === '*') return true;
262
+ const re = new RegExp(`^${text.split('*').map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('.*')}$`);
263
+ return re.test(String(value ?? ''));
264
+ }
265
+
266
+ /**
267
+ * The effect the last rule matching `action` on `resource` gives ('allow' | 'ask' | 'deny'), or null when no rule
268
+ * names it. `exact` counts only rules naming the action itself (not a wildcard).
269
+ */
270
+ export function effectFor(rules, action, { resource = '*', exact = false } = {}) {
271
+ const wanted = opencodeAction(action);
272
+ let effect = null;
273
+ for (const rule of Array.isArray(rules) ? rules : []) {
274
+ const named = exact ? rule.action === wanted : wildcardMatch(rule.action, wanted);
275
+ if (named && (rule.resource === '*' || wildcardMatch(rule.resource, resource))) effect = rule.effect;
276
+ }
277
+ return effect;
278
+ }
279
+
280
+ /** What a config decides for each of OpenCode's own tool actions, for resource `*` (its defaults where no rule says). */
281
+ export function toolEffectsOf(config) {
282
+ const rules = permissionRulesOf(config);
283
+ const out = {};
284
+ for (const action of OPENCODE_TOOL_ACTIONS) out[action] = effectFor(rules, action) || OPENCODE_DEFAULT_EFFECTS[action] || 'allow';
285
+ return out;
286
+ }
287
+
288
+ /**
289
+ * `config` with an `allow` rule for each of `actions` that has no rule of its own (an explicit deny or ask of the
290
+ * user's stays the user's). Mutates and returns it.
291
+ */
292
+ export function allowActions(config, actions) {
293
+ const rules = permissionRulesOf(config);
294
+ const list = Array.isArray(config.permissions) ? config.permissions : [];
295
+ for (const action of actions || []) {
296
+ if (effectFor(rules, action, { exact: true }) == null) list.push(permissionRule(action, 'allow'));
297
+ }
298
+ if (list.length) config.permissions = list;
299
+ return config;
300
+ }
301
+
302
+ /**
303
+ * `config` with exactly one rule deciding `action` for every resource: `effect`. Earlier `*` rules of that action
304
+ * go (OpenCode 1's string value too); rules for particular resources stay. Mutates and returns it.
305
+ */
306
+ export function setActionEffect(config, action, effect) {
307
+ if (!PERMISSION_EFFECTS.includes(effect)) throw new Error(`Unknown permission effect: ${effect}`);
308
+ const wanted = opencodeAction(action);
309
+ if (isObject(config.permission)) {
310
+ for (const [key, value] of Object.entries(config.permission)) {
311
+ if (opencodeAction(key) === wanted && typeof value === 'string') delete config.permission[key];
312
+ }
313
+ if (!Object.keys(config.permission).length) delete config.permission;
314
+ }
315
+ const list = (Array.isArray(config.permissions) ? config.permissions : []).filter((rule) => !(isObject(rule) && rule.action === wanted && (rule.resource ?? '*') === '*'));
316
+ list.push(permissionRule(wanted, effect));
317
+ config.permissions = list;
318
+ return config;
319
+ }
320
+
321
+ /** `config` with `rules` appended to its `permissions` (each converted to OpenCode 2's action names). Mutates and returns it. */
322
+ export function appendRules(config, rules) {
323
+ const list = Array.isArray(config.permissions) ? config.permissions : [];
324
+ for (const rule of rules || []) if (isObject(rule) && PERMISSION_EFFECTS.includes(rule.effect)) list.push(permissionRule(rule.action, rule.effect, rule.resource ?? '*'));
325
+ if (list.length) config.permissions = list;
326
+ return config;
327
+ }
328
+
329
+ // ── Agents ───────────────────────────────────────────────────────────────────
330
+
331
+ /** An agent as OpenCode 2 takes it, from an entry of either major (`prompt` → `system`, `permission` → rules). */
332
+ export function opencodeAgentEntry(input = {}) {
333
+ const source = asObject(input);
334
+ const { prompt, permission, permissions, tools: _tools, ...rest } = source;
335
+ const out = { ...rest };
336
+ if (typeof prompt === 'string' && out.system === undefined) out.system = prompt;
337
+ const rules = [...rulesFromPermissionObject(permission), ...(Array.isArray(permissions) ? permissions.filter(isObject) : [])];
338
+ if (rules.length) out.permissions = rules;
339
+ return out;
340
+ }
341
+
342
+ /** Agent `name` of a config in OpenCode 2's shape (an OpenCode 1 `agent.<name>` converted), or `{}`. */
343
+ export function agentOf(config, name) {
344
+ const v1 = asObject(asObject(config).agent)[name];
345
+ const v2 = asObject(asObject(config).agents)[name];
346
+ if (!isObject(v1) && !isObject(v2)) return {};
347
+ const converted = opencodeAgentEntry(v1 || {});
348
+ const own = opencodeAgentEntry(v2 || {});
349
+ const permissions = [...(converted.permissions || []), ...(own.permissions || [])];
350
+ return { ...converted, ...own, ...(permissions.length ? { permissions } : {}) };
351
+ }
352
+
353
+ /** `config` with agent `name` set to `agent` (an OpenCode 1 `agent.<name>` taken out). Mutates and returns it. */
354
+ export function setAgent(config, name, agent) {
355
+ if (isObject(config.agent)) {
356
+ delete config.agent[name];
357
+ if (!Object.keys(config.agent).length) delete config.agent;
358
+ }
359
+ config.agents = { ...asObject(config.agents), [name]: opencodeAgentEntry(agent) };
360
+ return config;
361
+ }
362
+
363
+ // ── Plugins ──────────────────────────────────────────────────────────────────
364
+
365
+ const pluginPackage = (entry) => (isObject(entry) ? entry.package : Array.isArray(entry) ? entry[0] : entry);
366
+
367
+ /** `config` with plugin `pkg` (a file URL or package name) listed once, with `options`. Mutates and returns it. */
368
+ export function withPlugin(config, pkg, options = {}) {
369
+ const list = (Array.isArray(config.plugins) ? config.plugins : []).filter((entry) => pluginPackage(entry) !== pkg);
370
+ list.push({ package: pkg, options: { ...asObject(options) } });
371
+ config.plugins = list;
372
+ return config;
373
+ }
374
+
375
+ /** `config` without plugin `pkg`, in either list. Mutates and returns it. */
376
+ export function withoutPlugin(config, pkg) {
377
+ for (const key of ['plugins', 'plugin']) {
378
+ if (!Array.isArray(config[key])) continue;
379
+ config[key] = config[key].filter((entry) => pluginPackage(entry) !== pkg);
380
+ if (!config[key].length) delete config[key];
381
+ }
382
+ return config;
383
+ }
384
+
385
+ // ── Providers, compaction, snapshots ─────────────────────────────────────────
386
+
387
+ /** Provider `id` of a config, either shape (`provider.<id>` or `providers.<id>`), or null. */
388
+ export function providerOf(config, id) {
389
+ const source = asObject(config);
390
+ const v2 = asObject(source.providers)[id];
391
+ if (isObject(v2)) return v2;
392
+ const v1 = asObject(source.provider)[id];
393
+ return isObject(v1) ? v1 : null;
394
+ }
395
+
396
+ /**
397
+ * Whether a document keeps providers other than `id` in OpenCode 1's `provider` section. OpenCode 2 reads one
398
+ * provider section per document: its own `providers` replaces OpenCode 1's whole when both are there, so an entry
399
+ * of SynaBun's in `providers` would hide every one of those.
400
+ */
401
+ export function hasOpencode1Providers(config, id = null) {
402
+ return Object.keys(asObject(asObject(config).provider)).some((key) => key !== id);
403
+ }
404
+
405
+ /**
406
+ * `config` with provider `id` set to `entry` (OpenCode 2's shape; an OpenCode 1 entry of that id taken out).
407
+ * Throws (code OPENCODE1_PROVIDERS) when the document keeps other providers in OpenCode 1's section: they are
408
+ * moved to `providers` first (as OpenCode converted them: GET /api/config), never dropped.
409
+ */
410
+ export function setProvider(config, id, entry) {
411
+ if (hasOpencode1Providers(config, id)) {
412
+ throw Object.assign(new Error('This OpenCode configuration keeps its providers in OpenCode 1\'s "provider" section, which a "providers" section would hide.'), { code: 'OPENCODE1_PROVIDERS' });
413
+ }
414
+ removeProvider(config, id);
415
+ config.providers = { ...asObject(config.providers), [id]: { ...asObject(entry) } };
416
+ return config;
417
+ }
418
+
419
+ /** `config` without provider `id`, in either shape. Mutates and returns it. */
420
+ export function removeProvider(config, id) {
421
+ for (const key of ['provider', 'providers']) {
422
+ if (!isObject(config[key])) continue;
423
+ delete config[key][id];
424
+ if (!Object.keys(config[key]).length) delete config[key];
425
+ }
426
+ return config;
427
+ }
428
+
429
+ /**
430
+ * A request timeout (ms) for provider `id` of a config written for one of SynaBun's serves, unless the provider
431
+ * sets one: `providers.<id>.settings.timeout`. A document that keeps its providers in OpenCode 1's section is left
432
+ * as it is (a `providers` section would hide them: hasOpencode1Providers). Mutates and returns it.
433
+ */
434
+ export function withProviderTimeout(config, id, timeoutMs) {
435
+ if (!id || isObject(config.provider)) return config;
436
+ const existing = asObject(asObject(config.providers)[id]);
437
+ const settings = { ...asObject(existing.settings) };
438
+ if (settings.timeout == null) settings.timeout = timeoutMs;
439
+ config.providers = { ...asObject(config.providers), [id]: { ...existing, settings } };
440
+ return config;
441
+ }
442
+
443
+ /** `{ auto, buffer }` of a config's compaction (OpenCode 1's `reserved` read as `buffer`); null where unset. */
444
+ export function compactionOf(config) {
445
+ const compaction = asObject(asObject(config).compaction);
446
+ return {
447
+ auto: typeof compaction.auto === 'boolean' ? compaction.auto : null,
448
+ buffer: Number.isFinite(compaction.buffer) ? compaction.buffer : (Number.isFinite(compaction.reserved) ? compaction.reserved : null),
449
+ };
450
+ }
451
+
452
+ /**
453
+ * `config` with its compaction set (`auto`, `buffer`) in OpenCode 2's shape (OpenCode 1's `prune`, `reserved`
454
+ * gone). A `buffer` of null takes it out: OpenCode's own default applies again.
455
+ */
456
+ export function setCompaction(config, { auto, buffer } = {}) {
457
+ const current = asObject(config.compaction);
458
+ const { prune: _prune, reserved, ...rest } = current;
459
+ const next = { ...rest };
460
+ if (rest.buffer === undefined && Number.isFinite(reserved)) next.buffer = reserved;
461
+ if (typeof auto === 'boolean') next.auto = auto;
462
+ if (buffer === null) delete next.buffer;
463
+ else if (buffer !== undefined) {
464
+ const value = Math.round(Number(buffer));
465
+ if (!Number.isFinite(value) || value < 0) throw new Error('The compaction buffer must be a whole number of tokens, 0 or more.');
466
+ next.buffer = value;
467
+ }
468
+ config.compaction = next;
469
+ return config;
470
+ }
471
+
472
+ /** Whether a config keeps snapshots (OpenCode 1's `snapshot` read too); null where unset. */
473
+ export function snapshotsOf(config) {
474
+ const source = asObject(config);
475
+ if (typeof source.snapshots === 'boolean') return source.snapshots;
476
+ if (typeof source.snapshot === 'boolean') return source.snapshot;
477
+ return null;
478
+ }
479
+
480
+ /** `config` with `snapshots` set (OpenCode 1's `snapshot` gone). Mutates and returns it. */
481
+ export function setSnapshots(config, enabled) {
482
+ delete config.snapshot;
483
+ config.snapshots = enabled === true;
484
+ return config;
485
+ }
486
+
487
+ // ── Files ────────────────────────────────────────────────────────────────────
488
+
489
+ /**
490
+ * One configuration document: `{ path, exists, config, error }`. A .jsonc is read with its comments; a file that is
491
+ * not JSON is `{ config: null, error }`, never thrown.
492
+ */
493
+ export function readOpencodeConfigDocument(path) {
494
+ if (!existsSync(path)) return { path, exists: false, config: {}, error: null };
495
+ try {
496
+ const text = readFileSync(path, 'utf8');
497
+ const value = path.endsWith('.jsonc') ? parseJsonc(text) : (text.trim() ? JSON.parse(text.replace(/^/, '')) : {});
498
+ if (!isObject(value)) return { path, exists: true, config: null, error: 'it does not hold a JSON object' };
499
+ return { path, exists: true, config: value, error: null };
500
+ } catch (error) {
501
+ return { path, exists: true, config: null, error: error?.message || String(error) };
502
+ }
503
+ }
504
+
505
+ /** The global documents of a configuration folder, lowest precedence first. */
506
+ export function readOpencodeConfigDocuments(dir) {
507
+ return OPENCODE_CONFIG_FILES.map((name) => readOpencodeConfigDocument(join(dir, name)));
508
+ }
509
+
510
+ /**
511
+ * Read `opencode.json` of `dir`, change it with `mutate(config)` and write it back (a new file when there is none),
512
+ * whole, through a temporary file and a rename. A file that is not JSON is never overwritten: throws instead.
513
+ * Resolves the path written.
514
+ */
515
+ export function updateOpencodeConfigFile(dir, mutate) {
516
+ const path = opencodeConfigFilePath(dir);
517
+ const doc = readOpencodeConfigDocument(path);
518
+ if (doc.error) throw new Error(`${path} could not be read (${doc.error}); SynaBun did not change it.`);
519
+ const config = doc.exists ? doc.config : { $schema: OPENCODE_CONFIG_SCHEMA };
520
+ const next = mutate(config) || config;
521
+ mkdirSync(dirname(path), { recursive: true });
522
+ const temp = `${path}.synabun-${process.pid}-${Date.now()}.tmp`;
523
+ writeFileSync(temp, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
524
+ renameSync(temp, path);
525
+ return path;
526
+ }
527
+
528
+ /**
529
+ * The user's global configuration as one object, for a serve of SynaBun's own whose configuration folder is
530
+ * another (XDG_CONFIG_HOME set to it): opencode.json, then opencode.jsonc over it (top-level keys replaced; `mcp`
531
+ * servers, agents, providers and permission rules merged the way OpenCode merges them). A document that cannot be
532
+ * read is left out.
533
+ */
534
+ export function mergedOpencodeConfig(documents) {
535
+ const merged = {};
536
+ for (const doc of documents || []) {
537
+ const config = doc?.config;
538
+ if (!isObject(config)) continue;
539
+ for (const [key, value] of Object.entries(config)) {
540
+ if (key === 'permission' || key === 'permissions') continue;
541
+ if (key === 'mcp' && isObject(value)) {
542
+ const servers = { ...mcpServersOf(merged), ...mcpServersOf(config) };
543
+ merged.mcp = { ...(isObject(value.timeout) ? { timeout: value.timeout } : (isObject(merged.mcp?.timeout) ? { timeout: merged.mcp.timeout } : {})), ...(Object.keys(servers).length ? { servers } : {}) };
544
+ } else if ((key === 'agents' || key === 'agent' || key === 'providers' || key === 'provider') && isObject(value)) {
545
+ merged[key] = { ...asObject(merged[key]), ...value };
546
+ } else if ((key === 'plugins' || key === 'plugin' || key === 'instructions') && Array.isArray(value)) {
547
+ merged[key] = [...(Array.isArray(merged[key]) ? merged[key] : []), ...value];
548
+ } else {
549
+ merged[key] = value;
550
+ }
551
+ }
552
+ // A document's rules in OpenCode's order (its OpenCode 1 object first), after the earlier document's.
553
+ const rules = permissionRulesOf(config);
554
+ if (rules.length) merged.permissions = [...(Array.isArray(merged.permissions) ? merged.permissions : []), ...rules];
555
+ }
556
+ return merged;
557
+ }
@@ -1,14 +1,12 @@
1
1
  // ─────────────────────────────────────────────────────────────────────────────
2
- // Which OpenCode database SynaBun is reading.
2
+ // The tables of OpenCode's database SynaBun reads.
3
3
  //
4
4
  // SynaBun reads OpenCode's own SQLite database ($XDG_DATA_HOME/opencode/
5
5
  // opencode.db) for the session lists of every registered project and for the
6
- // Resume cache. OpenCode 1 keeps sessions in `session` / `message` / `part`;
7
- // OpenCode 2 keeps them in `session_v2` / `session_message`, in the same file.
8
- // An OpenCode 2 that opened an OpenCode 1 database copies its sessions into
9
- // `session_v2` and leaves the old tables behind, frozen: read by OpenCode 1's
10
- // queries they would show yesterday's sessions and none of today's. So the
11
- // tables are picked by the OpenCode SynaBun talks to, and by what the file has.
6
+ // Resume cache. OpenCode 2 keeps its sessions in `session_v2` and their
7
+ // messages in `session_message`. A file OpenCode 2 migrated from OpenCode 1
8
+ // also holds OpenCode 1's `session` / `message` / `part` tables, frozen: they
9
+ // are never read (they would show yesterday's sessions and none of today's).
12
10
  // ─────────────────────────────────────────────────────────────────────────────
13
11
 
14
12
  /** The table names of an open node:sqlite database, or an empty set. */
@@ -20,38 +18,23 @@ export function tableNamesOf(db) {
20
18
  }
21
19
  }
22
20
 
23
- /**
24
- * 'v2' (OpenCode 2's tables), 'v1' (OpenCode 1's) or null (neither). `protocol`
25
- * is the major SynaBun talks to (1, 2, or null when it is not known): with 2,
26
- * OpenCode 2's tables are read when the file has them; with 1, OpenCode 1's;
27
- * not known, OpenCode 2's when there are any.
28
- */
29
- export function opencodeDbFlavor(db, protocol = null) {
30
- if (!db) return null;
21
+ /** Whether a database holds OpenCode 2's session tables (false for none, or for OpenCode 1's alone). */
22
+ export function hasOpencodeSessionTables(db) {
23
+ if (!db) return false;
31
24
  const tables = tableNamesOf(db);
32
- const v2 = tables.has('session_v2') && tables.has('session_message');
33
- const v1 = tables.has('session') && tables.has('message');
34
- if (protocol === 1) return v1 ? 'v1' : null;
35
- if (protocol === 2) return v2 ? 'v2' : null;
36
- if (v2) return 'v2';
37
- return v1 ? 'v1' : null;
25
+ return tables.has('session_v2') && tables.has('session_message');
38
26
  }
39
27
 
40
- /** The session table and the message-count expression (`s` is the session row) of a flavor. */
41
- export function sessionTablesFor(flavor) {
42
- if (flavor === 'v2') {
43
- return {
44
- session: 'session_v2',
45
- messageCount: "(SELECT COUNT(*) FROM session_message m WHERE m.session_id = s.id AND m.type IN ('user', 'assistant'))",
46
- };
47
- }
48
- return { session: 'session', messageCount: '(SELECT COUNT(*) FROM message m WHERE m.session_id = s.id)' };
49
- }
28
+ /** The session table and the message-count expression (`s` is the session row). */
29
+ export const OPENCODE_SESSION_TABLES = Object.freeze({
30
+ session: 'session_v2',
31
+ messageCount: "(SELECT COUNT(*) FROM session_message m WHERE m.session_id = s.id AND m.type IN ('user', 'assistant'))",
32
+ });
50
33
 
51
34
  /**
52
- * The visible text of one OpenCode 2 `session_message` row (its `type` and
53
- * JSON `data`) as `{ role, text }`, or null for a row without any (a switch, an
54
- * idle marker, a tool-only step).
35
+ * The visible text of one `session_message` row (its `type` and JSON `data`)
36
+ * as `{ role, text }`, or null for a row without any (a switch, an idle
37
+ * marker, a tool-only step).
55
38
  */
56
39
  export function v2MessageText(type, data) {
57
40
  let body = data;