synabun 2.0.9 → 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 (679) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +68 -4
  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/launcher.mjs +9 -3
  10. package/lib/claude-hooks.js +172 -18
  11. package/lib/client-config-repair.js +17 -8
  12. package/lib/desktop-app.js +7 -1
  13. package/lib/external-tools.js +1 -1
  14. package/lib/first-launch-protection.js +57 -12
  15. package/lib/mcp-dist.js +176 -0
  16. package/lib/release-npm-ci.mjs +138 -0
  17. package/lib/release-publish.mjs +91 -0
  18. package/lib/release-scan.mjs +50 -0
  19. package/lib/server-crash-record.js +123 -0
  20. package/lib/server-exit.js +318 -0
  21. package/lib/server-supervisor.js +508 -0
  22. package/lib/start-launcher.js +43 -5
  23. package/lib/tools-cli.js +45 -0
  24. package/lib/update-snapshot.js +228 -0
  25. package/lib/virustotal.mjs +192 -0
  26. package/lib/windows-crash-event.js +269 -0
  27. package/mcp-server/dist/http.d.ts +20 -1
  28. package/mcp-server/dist/http.d.ts.map +1 -1
  29. package/mcp-server/dist/http.js +59 -12
  30. package/mcp-server/dist/http.js.map +1 -1
  31. package/mcp-server/dist/index.d.ts +33 -27
  32. package/mcp-server/dist/index.d.ts.map +1 -1
  33. package/mcp-server/dist/index.js +128 -143
  34. package/mcp-server/dist/index.js.map +1 -1
  35. package/mcp-server/dist/preload.js +1 -1
  36. package/mcp-server/dist/preload.js.map +1 -1
  37. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  38. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  39. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  40. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  42. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  44. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  45. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  46. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  47. package/mcp-server/dist/services/neural-interface.js +67 -11
  48. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  49. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  50. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  51. package/mcp-server/dist/services/server-instructions.js +20 -62
  52. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  53. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  54. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  55. package/mcp-server/dist/services/tool-catalog.js +339 -0
  56. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  60. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  61. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  62. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  63. package/mcp-server/dist/services/tool-list.js +334 -0
  64. package/mcp-server/dist/services/tool-list.js.map +1 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  66. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  67. package/mcp-server/dist/services/tool-runtime.js +400 -0
  68. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  70. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  71. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  72. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  73. package/mcp-server/dist/tools/agents.d.ts +10 -10
  74. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  75. package/mcp-server/dist/tools/agents.js +22 -13
  76. package/mcp-server/dist/tools/agents.js.map +1 -1
  77. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  78. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  79. package/mcp-server/dist/tools/image-tools.js +1 -1
  80. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  81. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  82. package/mcp-server/dist/tools/skill-tools.js +639 -0
  83. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  84. package/mcp-server/dist/tools/skill.d.ts +7 -0
  85. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  86. package/mcp-server/dist/tools/skill.js +11 -0
  87. package/mcp-server/dist/tools/skill.js.map +1 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  89. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  90. package/mcp-server/dist/tools/tool-groups.js +147 -0
  91. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  92. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  93. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  94. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  96. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  97. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  98. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  99. package/mcp-server/package-lock.json +2 -2
  100. package/mcp-server/package.json +1 -1
  101. package/mcp-server/src/http.ts +75 -12
  102. package/mcp-server/src/index.ts +147 -154
  103. package/mcp-server/src/preload.ts +12 -12
  104. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  105. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  106. package/mcp-server/src/services/neural-interface.ts +79 -20
  107. package/mcp-server/src/services/server-instructions.ts +27 -63
  108. package/mcp-server/src/services/tool-catalog.ts +403 -0
  109. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  110. package/mcp-server/src/services/tool-list.ts +410 -0
  111. package/mcp-server/src/services/tool-runtime.ts +457 -0
  112. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  113. package/mcp-server/src/tools/agents.ts +22 -14
  114. package/mcp-server/src/tools/image-tools.ts +1 -1
  115. package/mcp-server/src/tools/skill-tools.ts +589 -0
  116. package/mcp-server/src/tools/skill.ts +13 -0
  117. package/mcp-server/src/tools/tool-groups.ts +164 -0
  118. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  119. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  120. package/neural-interface/i18n/en.json +3680 -206
  121. package/neural-interface/i18n/pt-BR.json +3628 -157
  122. package/neural-interface/lib/account-usage.js +5 -3
  123. package/neural-interface/lib/app-controls-api.js +161 -0
  124. package/neural-interface/lib/assistant-api.js +148 -35
  125. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  126. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  127. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  128. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  129. package/neural-interface/lib/assistant-brains/opencode.js +122 -44
  130. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +59 -0
  131. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +97 -0
  132. package/neural-interface/lib/assistant-catalog.js +23 -9
  133. package/neural-interface/lib/assistant-dispatch.js +874 -156
  134. package/neural-interface/lib/assistant-envelope.js +22 -2
  135. package/neural-interface/lib/assistant-persona.js +80 -11
  136. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  137. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  138. package/neural-interface/lib/assistant-route-gate.js +6 -4
  139. package/neural-interface/lib/assistant-router.js +13 -8
  140. package/neural-interface/lib/assistant-runtime.js +183 -26
  141. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  142. package/neural-interface/lib/assistant-tools.js +51 -0
  143. package/neural-interface/lib/backup-service.js +620 -3
  144. package/neural-interface/lib/backup-zip-reader.js +10 -5
  145. package/neural-interface/lib/backup-zip-writer.js +257 -0
  146. package/neural-interface/lib/browser-execution.js +10 -2
  147. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  148. package/neural-interface/lib/browser-network-guard.js +284 -0
  149. package/neural-interface/lib/browser-policy-roots.js +64 -0
  150. package/neural-interface/lib/browser-tool-policy.js +186 -31
  151. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  152. package/neural-interface/lib/claude-hooks-status.js +103 -0
  153. package/neural-interface/lib/claude-model-catalog.js +64 -14
  154. package/neural-interface/lib/claude-temporary.js +5 -11
  155. package/neural-interface/lib/cli-install.js +18 -138
  156. package/neural-interface/lib/cli-resolver.js +4 -1
  157. package/neural-interface/lib/cli-update-planner.js +32 -31
  158. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  159. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  160. package/neural-interface/lib/codex-settings-schema.js +109 -47
  161. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  162. package/neural-interface/lib/confined-fs.js +30 -3
  163. package/neural-interface/lib/crash-notice-api.js +98 -0
  164. package/neural-interface/lib/editor-completion-opencode.js +164 -67
  165. package/neural-interface/lib/exec-loop-command.js +4 -1
  166. package/neural-interface/lib/http-guards.js +8 -2
  167. package/neural-interface/lib/install-details.js +4 -1
  168. package/neural-interface/lib/launch-cache.js +293 -0
  169. package/neural-interface/lib/login-start.js +524 -0
  170. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  171. package/neural-interface/lib/mcp-client-config.js +0 -29
  172. package/neural-interface/lib/mcp-external-servers.js +323 -0
  173. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  174. package/neural-interface/lib/morelogin.js +30 -0
  175. package/neural-interface/lib/native-loop-providers.js +565 -50
  176. package/neural-interface/lib/native-loop-runtime.js +287 -41
  177. package/neural-interface/lib/opencode-config.js +557 -0
  178. package/neural-interface/lib/opencode-db-schema.js +58 -0
  179. package/neural-interface/lib/opencode-history.js +49 -204
  180. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  181. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  182. package/neural-interface/lib/opencode-protocol.js +310 -0
  183. package/neural-interface/lib/opencode-provider-view.js +81 -0
  184. package/neural-interface/lib/opencode-runtime-config.js +32 -15
  185. package/neural-interface/lib/opencode-serve-records.js +624 -0
  186. package/neural-interface/lib/opencode-v2-client.js +53 -131
  187. package/neural-interface/lib/opencode-v2-ws-requests.js +35 -195
  188. package/neural-interface/lib/opencode-worker-activity.js +229 -0
  189. package/neural-interface/lib/opencode-worker-data.js +27 -0
  190. package/neural-interface/lib/opencode2-client.js +796 -0
  191. package/neural-interface/lib/opencode2-proxy.js +76 -0
  192. package/neural-interface/lib/opencode2-translate.js +1057 -0
  193. package/neural-interface/lib/owner-authority.js +249 -0
  194. package/neural-interface/lib/page-session.js +275 -0
  195. package/neural-interface/lib/peer-identity.js +763 -0
  196. package/neural-interface/lib/process-identity.js +361 -0
  197. package/neural-interface/lib/request-gate.js +17 -2
  198. package/neural-interface/lib/rulesets/api.js +1 -1
  199. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  200. package/neural-interface/lib/rulesets/installer.js +92 -27
  201. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  202. package/neural-interface/lib/session-cache-builder.js +18 -10
  203. package/neural-interface/lib/session-title-generator.js +58 -18
  204. package/neural-interface/lib/skills/api-v2.js +495 -0
  205. package/neural-interface/lib/skills/bundle.js +418 -0
  206. package/neural-interface/lib/skills/catalog.js +294 -0
  207. package/neural-interface/lib/skills/creation-api.js +191 -0
  208. package/neural-interface/lib/skills/creation-brief.js +247 -0
  209. package/neural-interface/lib/skills/creation-sources.js +267 -0
  210. package/neural-interface/lib/skills/creation.js +843 -0
  211. package/neural-interface/lib/skills/deploy.js +564 -0
  212. package/neural-interface/lib/skills/digest.js +119 -0
  213. package/neural-interface/lib/skills/discover.js +111 -0
  214. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  215. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  216. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  217. package/neural-interface/lib/skills/eval-trace.js +119 -0
  218. package/neural-interface/lib/skills/evals-api.js +212 -0
  219. package/neural-interface/lib/skills/evals.js +1573 -0
  220. package/neural-interface/lib/skills/export.js +200 -0
  221. package/neural-interface/lib/skills/frontmatter.js +64 -0
  222. package/neural-interface/lib/skills/health.js +75 -0
  223. package/neural-interface/lib/skills/json-edit.js +165 -0
  224. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  225. package/neural-interface/lib/skills/known-bridges.js +96 -0
  226. package/neural-interface/lib/skills/library.js +823 -0
  227. package/neural-interface/lib/skills/local-store.js +21 -0
  228. package/neural-interface/lib/skills/migration.js +433 -0
  229. package/neural-interface/lib/skills/operations.js +45 -0
  230. package/neural-interface/lib/skills/ownership.js +427 -0
  231. package/neural-interface/lib/skills/packages.js +88 -0
  232. package/neural-interface/lib/skills/reviews.js +212 -0
  233. package/neural-interface/lib/skills/revision.js +80 -0
  234. package/neural-interface/lib/skills/roots.js +262 -0
  235. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  236. package/neural-interface/lib/skills/scan-assets.js +252 -0
  237. package/neural-interface/lib/skills/scan-context.js +26 -0
  238. package/neural-interface/lib/skills/scan-shell.js +324 -0
  239. package/neural-interface/lib/skills/scan.js +364 -0
  240. package/neural-interface/lib/skills/scanners.js +54 -0
  241. package/neural-interface/lib/skills/service.js +522 -0
  242. package/neural-interface/lib/skills/telemetry.js +113 -0
  243. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  244. package/neural-interface/lib/style-guide/api.js +4 -3
  245. package/neural-interface/lib/tool-lists.js +124 -0
  246. package/neural-interface/lib/untrusted-file-headers.js +27 -0
  247. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  248. package/neural-interface/lib/worker-confinement.js +1192 -0
  249. package/neural-interface/lib/worker-guard-setting.js +56 -0
  250. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  251. package/neural-interface/lib/worker-relay-ports.js +227 -0
  252. package/neural-interface/lib/worker-resume.js +229 -0
  253. package/neural-interface/lib/worker-sandbox.js +299 -0
  254. package/neural-interface/package-lock.json +1354 -50
  255. package/neural-interface/package.json +6 -2
  256. package/neural-interface/public/index.html +5 -3
  257. package/neural-interface/public/index2d.html +19 -11
  258. package/neural-interface/public/onboarding.html +2 -2
  259. package/neural-interface/public/shared/account-menu.js +8 -5
  260. package/neural-interface/public/shared/agent-runtime-options.js +76 -16
  261. package/neural-interface/public/shared/api.js +1527 -1336
  262. package/neural-interface/public/shared/app-frame.js +192 -0
  263. package/neural-interface/public/shared/assistant/asst-agents-dock.js +205 -19
  264. package/neural-interface/public/shared/assistant/asst-api.js +11 -3
  265. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  266. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  267. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  268. package/neural-interface/public/shared/assistant/asst-brain-picker.js +241 -112
  269. package/neural-interface/public/shared/assistant/asst-composer.js +1 -1
  270. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  271. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  272. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  273. package/neural-interface/public/shared/assistant/asst-hosts.js +49 -2
  274. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  275. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  276. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  277. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  278. package/neural-interface/public/shared/assistant/asst-menu.js +84 -12
  279. package/neural-interface/public/shared/assistant/asst-panel.js +303 -44
  280. package/neural-interface/public/shared/assistant/asst-rack-styles.js +93 -58
  281. package/neural-interface/public/shared/assistant/asst-redact.js +186 -0
  282. package/neural-interface/public/shared/assistant/asst-render.js +770 -100
  283. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  284. package/neural-interface/public/shared/assistant/asst-run-stage.js +231 -0
  285. package/neural-interface/public/shared/assistant/asst-sanitize.js +193 -3
  286. package/neural-interface/public/shared/assistant/asst-scene-styles.js +273 -0
  287. package/neural-interface/public/shared/assistant/asst-scenes.js +530 -0
  288. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +3 -15
  289. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  290. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  291. package/neural-interface/public/shared/assistant/asst-state.js +25 -15
  292. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  293. package/neural-interface/public/shared/assistant/asst-styles.js +102 -21
  294. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +165 -13
  295. package/neural-interface/public/shared/automation-context.js +5 -2
  296. package/neural-interface/public/shared/cc-dropdown.js +550 -0
  297. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  298. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  299. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  300. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  301. package/neural-interface/public/shared/cdx/cdx-panel.js +58 -150
  302. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  303. package/neural-interface/public/shared/cdx/cdx-render.js +62 -32
  304. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  305. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  306. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  307. package/neural-interface/public/shared/cdx/cdx-tabs.js +201 -74
  308. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  309. package/neural-interface/public/shared/cli-install-view.js +4 -8
  310. package/neural-interface/public/shared/cli-status.js +2 -2
  311. package/neural-interface/public/shared/constants.js +2 -2
  312. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  313. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  314. package/neural-interface/public/shared/context-menu-model.js +366 -0
  315. package/neural-interface/public/shared/context-menu.js +372 -0
  316. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  317. package/neural-interface/public/shared/cp/cp-markdown.js +4 -57
  318. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  319. package/neural-interface/public/shared/data-cache.js +223 -0
  320. package/neural-interface/public/shared/desktop-bridge.js +11 -0
  321. package/neural-interface/public/shared/desktop-titlebar-view.js +78 -1
  322. package/neural-interface/public/shared/desktop-titlebar.js +214 -28
  323. package/neural-interface/public/shared/desktop-update-view.js +35 -5
  324. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  325. package/neural-interface/public/shared/file-links.js +73 -8
  326. package/neural-interface/public/shared/html-shell.js +78 -86
  327. package/neural-interface/public/shared/i18n.js +2 -0
  328. package/neural-interface/public/shared/launch-prefetch.js +59 -0
  329. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  330. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  331. package/neural-interface/public/shared/ocp-v2/ocp-v2-caps.js +3 -2
  332. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  333. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +9 -2
  334. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  335. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  336. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  337. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  338. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  339. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  340. package/neural-interface/public/shared/ocp-v2/ocp-v2-modelpicker.js +54 -17
  341. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  342. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +140 -254
  343. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  344. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +97 -203
  345. package/neural-interface/public/shared/ocp-v2/ocp-v2-providers.js +11 -9
  346. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +15 -4
  347. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +7 -22
  348. package/neural-interface/public/shared/ocp-v2/ocp-v2-send-logic.js +401 -0
  349. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +20 -13
  350. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  351. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +53 -82
  352. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  353. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  354. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  355. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  356. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +48 -40
  357. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  358. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +205 -62
  359. package/neural-interface/public/shared/opencode-providers.js +39 -0
  360. package/neural-interface/public/shared/page-session.js +96 -0
  361. package/neural-interface/public/shared/registry.js +5 -3
  362. package/neural-interface/public/shared/retired-2d.js +3 -0
  363. package/neural-interface/public/shared/schedules-view.js +452 -0
  364. package/neural-interface/public/shared/settings/settings-app-pane.js +335 -0
  365. package/neural-interface/public/shared/settings/settings-app.js +243 -0
  366. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  367. package/neural-interface/public/shared/settings/settings-data.js +90 -0
  368. package/neural-interface/public/shared/settings/settings-ia.js +23 -6
  369. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  370. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  371. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  372. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  373. package/neural-interface/public/shared/sidepanel-session-status.js +49 -0
  374. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  375. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  376. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  377. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  378. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  379. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  380. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  381. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  382. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  383. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  384. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  385. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  386. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  387. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  388. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  389. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  390. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  391. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  392. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  393. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  394. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  395. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  396. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  397. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  398. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  399. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  400. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  401. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  402. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  403. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  404. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  405. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  406. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  407. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  408. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  409. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  410. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  411. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  412. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  413. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  414. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  415. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  416. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  417. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  418. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  419. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  420. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  421. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  422. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  423. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  424. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  425. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  426. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  427. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  428. package/neural-interface/public/shared/styles.css +2065 -2150
  429. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  430. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  431. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  432. package/neural-interface/public/shared/tool-list-view.js +112 -0
  433. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  434. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  435. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  436. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  437. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  438. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  439. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  440. package/neural-interface/public/shared/tour/tour.css +144 -0
  441. package/neural-interface/public/shared/ui-assistant.js +27 -5
  442. package/neural-interface/public/shared/ui-automation-studio.js +155 -152
  443. package/neural-interface/public/shared/ui-bookmarks.js +2 -1
  444. package/neural-interface/public/shared/ui-claude-panel.js +153 -13
  445. package/neural-interface/public/shared/ui-codex-panel.js +1 -0
  446. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  447. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  448. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  449. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  450. package/neural-interface/public/shared/ui-detail.js +21 -9
  451. package/neural-interface/public/shared/ui-file-links.js +1 -0
  452. package/neural-interface/public/shared/ui-image-gallery.js +56 -52
  453. package/neural-interface/public/shared/ui-invite.js +18 -2
  454. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  455. package/neural-interface/public/shared/ui-link.js +9 -9
  456. package/neural-interface/public/shared/ui-menubar.js +229 -40
  457. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  458. package/neural-interface/public/shared/ui-native-loop-router.js +9 -1
  459. package/neural-interface/public/shared/ui-navbar.js +4 -24
  460. package/neural-interface/public/shared/ui-opencode-panel-v2.js +1 -0
  461. package/neural-interface/public/shared/ui-panels.js +96 -65
  462. package/neural-interface/public/shared/ui-schedules-studio.js +2267 -953
  463. package/neural-interface/public/shared/ui-sessions.js +20 -7
  464. package/neural-interface/public/shared/ui-settings.js +679 -496
  465. package/neural-interface/public/shared/ui-sidebar.js +4 -4
  466. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +13 -0
  467. package/neural-interface/public/shared/ui-sidepanel-sessions.js +56 -5
  468. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  469. package/neural-interface/public/shared/ui-sidepanel-tray.js +84 -8
  470. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  471. package/neural-interface/public/shared/ui-skills.js +42 -2781
  472. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  473. package/neural-interface/public/shared/ui-sync.js +22 -5
  474. package/neural-interface/public/shared/ui-terminal.js +159 -199
  475. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  476. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  477. package/neural-interface/public/shared/ui-trash.js +14 -5
  478. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  479. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  480. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -61
  481. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  482. package/neural-interface/public/shared/ui-tutorial.js +285 -499
  483. package/neural-interface/public/shared/ui-update.js +121 -16
  484. package/neural-interface/public/shared/ui-whiteboard.js +313 -59
  485. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  486. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  487. package/neural-interface/public/shared/view-variant.js +66 -0
  488. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  489. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  490. package/neural-interface/public/shared/worker-end.js +10 -3
  491. package/neural-interface/public/shared/worker-restart.js +12 -0
  492. package/neural-interface/public/sidepanel-session.html +1 -1
  493. package/neural-interface/public/variant/2d/background.js +2 -1
  494. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  495. package/neural-interface/public/variant/2d/graph.js +42 -20
  496. package/neural-interface/public/variant/2d/hulls.js +14 -9
  497. package/neural-interface/public/variant/2d/lasso.js +11 -7
  498. package/neural-interface/public/variant/2d/main.js +36 -2
  499. package/neural-interface/public/variant/2d/minimap.js +11 -13
  500. package/neural-interface/public/variant/3d/main.js +72 -6
  501. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  502. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  503. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  504. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  505. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  506. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  507. package/neural-interface/public/vendor/highlight/LICENSE +29 -0
  508. package/neural-interface/public/vendor/highlight/github-dark-dimmed-11.12.0.min.css +9 -0
  509. package/neural-interface/public/vendor/highlight/highlight-11.12.0.min.js +1262 -0
  510. package/neural-interface/public/vendor/highlight/manifest.json +5 -0
  511. package/neural-interface/public/vendor/html2canvas/LICENSE +22 -0
  512. package/neural-interface/public/vendor/html2canvas/html2canvas-1.4.1.min.js +20 -0
  513. package/neural-interface/public/vendor/html2canvas/manifest.json +4 -0
  514. package/neural-interface/public/vendor/marked/LICENSE.md +44 -0
  515. package/neural-interface/public/vendor/marked/manifest.json +4 -0
  516. package/neural-interface/public/vendor/marked/marked-14.1.4.esm.js +2502 -0
  517. package/neural-interface/public/vendor/mermaid/LICENSE +21 -0
  518. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-PAR5IXJI.mjs +1 -0
  519. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-M7G3SWCO.mjs +1 -0
  520. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-2PNWDENW.mjs +36 -0
  521. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-PUWGUBOA.mjs +129 -0
  522. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-3NLNHZP5.mjs +38 -0
  523. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-2NMSUPJH.mjs +1 -0
  524. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-32MCUW2P.mjs +206 -0
  525. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3NPNU5GP.mjs +1 -0
  526. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SMGFRGC.mjs +1 -0
  527. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4CAJECRU.mjs +14 -0
  528. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-4OADUZ5Q.mjs +10 -0
  529. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IMINLNL.mjs +1 -0
  530. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5IQCK5RY.mjs +3 -0
  531. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-5MVFYWFU.mjs +1 -0
  532. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AQ6EADP3.mjs +1 -0
  533. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-AZZRMDJM.mjs +15 -0
  534. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-CLS4B6BI.mjs +168 -0
  535. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs +70 -0
  536. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-EICP7K5X.mjs +1 -0
  537. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDY2UAB4.mjs +1 -0
  538. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7VHZCWX.mjs +1 -0
  539. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-IJBDOHL6.mjs +1 -0
  540. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JFFVMJIA.mjs +1 -0
  541. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JQRUD6KW.mjs +1 -0
  542. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-JZGDI5JK.mjs +1 -0
  543. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-KD6SPIPN.mjs +2 -0
  544. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NF366YI6.mjs +231 -0
  545. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-NUDPKBEF.mjs +32 -0
  546. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-OJINMHTS.mjs +1 -0
  547. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Q6NGPIAH.mjs +2 -0
  548. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-QJSWEUOL.mjs +132 -0
  549. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-R7JDMUKV.mjs +1 -0
  550. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RCXAM2GK.mjs +1 -0
  551. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-RT4MAWR3.mjs +1 -0
  552. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SFMINX4W.mjs +1 -0
  553. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-SLDQ463B.mjs +88 -0
  554. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-STOV2HOB.mjs +1 -0
  555. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-T5OCTHI4.mjs +1 -0
  556. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-TCEHX7R3.mjs +1 -0
  557. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VQC2EELJ.mjs +1 -0
  558. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VRZKL3EH.mjs +1 -0
  559. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTHDQ7BV.mjs +1 -0
  560. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-VY2OSTJR.mjs +2 -0
  561. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-WUBWJARI.mjs +1 -0
  562. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YBMQ5JGJ.mjs +2 -0
  563. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-YDMLL4PJ.mjs +1 -0
  564. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-Z6V44ARX.mjs +62 -0
  565. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZJJ7VQJP.mjs +339 -0
  566. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZNLK7HRR.mjs +1 -0
  567. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZQN7UHIL.mjs +1 -0
  568. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZS6VBONO.mjs +174 -0
  569. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZUNWM646.mjs +1 -0
  570. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-FGAMPOII.mjs +1 -0
  571. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-AI5RJEK4.mjs +1 -0
  572. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-Q72C2OJ3.mjs +1 -0
  573. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-6OCTWOGG.mjs +1 -0
  574. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-2OOWUY4M.mjs +62 -0
  575. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/dagre-MPVFI544.mjs +4 -0
  576. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-AE4NWXFW.mjs +24 -0
  577. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-BUJID5UG.mjs +41 -0
  578. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-C44CZ5NI.mjs +24 -0
  579. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-EYOLEXXS.mjs +30 -0
  580. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/diagram-P7B6PWOJ.mjs +3 -0
  581. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-FTPZ3PV6.mjs +1 -0
  582. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-H3ERR26V.mjs +99 -0
  583. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-WKSWQHFS.mjs +1 -0
  584. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-YHGXBVSY.mjs +1 -0
  585. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-RWFTB7ED.mjs +292 -0
  586. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-JH2HKYUO.mjs +1 -0
  587. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-MAR6GKOP.mjs +106 -0
  588. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-3TX3OFWW.mjs +1 -0
  589. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-LV547AFH.mjs +2 -0
  590. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-PUJQGNDS.mjs +70 -0
  591. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-EW5QJT4W.mjs +139 -0
  592. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-JSQERAZB.mjs +89 -0
  593. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/katex-CZ4GXH2S.mjs +257 -0
  594. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-VBJCLQLM.mjs +96 -0
  595. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-MKII6P3Y.mjs +1 -0
  596. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-7WILOQTJ.mjs +1 -0
  597. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-6B4OPMJ5.mjs +1 -0
  598. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-EFA7CBSB.mjs +39 -0
  599. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-WARVYA7P.mjs +7 -0
  600. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-UPTR3XUL.mjs +1 -0
  601. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-MEFUPFMS.mjs +1 -0
  602. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-R5YG2S5D.mjs +1 -0
  603. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-G5QEX2L6.mjs +1 -0
  604. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-H5I2ZRY2.mjs +1 -0
  605. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-5NIQVNLQ.mjs +1 -0
  606. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SXREGOKZ.mjs +84 -0
  607. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-VV74M2VP.mjs +40 -0
  608. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-52ZFIFDL.mjs +162 -0
  609. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-FGA5AGYP.mjs +1 -0
  610. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-3QAKPIVI.mjs +1 -0
  611. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-HN2P4RDZ.mjs +1 -0
  612. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-QLVP42QD.mjs +1 -0
  613. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-JKPDBFCN.mjs +8 -0
  614. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-GS45XAXW.mjs +120 -0
  615. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-ZRJGANNQ.mjs +1 -0
  616. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-K64VN5AS.mjs +1 -0
  617. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-QIQU2ZRJ.mjs +34 -0
  618. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-D67OWCCH.mjs +1 -0
  619. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-U6YNM66N.mjs +78 -0
  620. package/neural-interface/public/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-XJAZEDNM.mjs +7 -0
  621. package/neural-interface/public/vendor/mermaid/manifest.json +4 -0
  622. package/neural-interface/public/vendor/mermaid/mermaid-11.17.2.esm.min.mjs +14 -0
  623. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  624. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  625. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  626. package/neural-interface/scripts/build-vendor.mjs +116 -3
  627. package/neural-interface/server.js +4834 -2541
  628. package/neural-interface/templates/rulesets/core.md +1 -1
  629. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  630. package/neural-interface/templates/rulesets/manifest.json +8 -8
  631. package/npm-shrinkwrap.json +18 -12
  632. package/package.json +16 -3
  633. package/postinstall.js +9 -4
  634. package/preuninstall.js +1 -1
  635. package/setup.js +97 -322
  636. package/skills/skill-forge/SKILL.md +177 -0
  637. package/skills/skill-forge/evals/evals.json +54 -0
  638. package/skills/skill-forge/references/package.md +105 -0
  639. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  640. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  641. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  642. package/skills/synabun/SKILL.md +10 -7
  643. package/skills/synabun/modules/audit.md +1 -5
  644. package/skills/synabun/modules/changelog.md +1 -5
  645. package/skills/synabun/modules/idea.md +1 -1
  646. package/skills/synabun/modules/memorize.md +6 -9
  647. package/skills/synabun/modules/schedule.md +5 -5
  648. package/mcp-server/dist/services/profiles.d.ts +0 -120
  649. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  650. package/mcp-server/dist/services/profiles.js +0 -387
  651. package/mcp-server/dist/services/profiles.js.map +0 -1
  652. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  653. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  654. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  655. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  656. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  657. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  658. package/mcp-server/dist/tools/leonardo.js +0 -21
  659. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  660. package/mcp-server/dist/tools/profile.d.ts +0 -17
  661. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  662. package/mcp-server/dist/tools/profile.js +0 -114
  663. package/mcp-server/dist/tools/profile.js.map +0 -1
  664. package/mcp-server/src/services/profiles.ts +0 -428
  665. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  666. package/mcp-server/src/tools/leonardo.ts +0 -30
  667. package/mcp-server/src/tools/profile.ts +0 -122
  668. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  669. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  670. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  671. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  672. package/skills/leonardo/SKILL.md +0 -168
  673. package/skills/leonardo/icon.svg +0 -1
  674. package/skills/leonardo/modules/browser-guide.md +0 -378
  675. package/skills/leonardo/modules/image-prompter.md +0 -283
  676. package/skills/leonardo/modules/model-advisor.md +0 -100
  677. package/skills/leonardo/modules/prompt-library.md +0 -110
  678. package/skills/leonardo/modules/style-guide.md +0 -186
  679. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -1,1354 +1,1545 @@
1
- // ═══════════════════════════════════════════
2
- // SynaBun Neural Interface — API Client
3
- // ═══════════════════════════════════════════
4
- // Single source of truth for all REST API calls.
5
- // Every fetch('/api/...') in the UI should route through here.
6
-
7
- // ─── Helpers ─────────────────────────────
8
-
9
- async function jsonFetch(url, options = {}) {
10
- const res = await fetch(url, options);
11
- if (!res.ok) {
12
- let errMsg = `HTTP ${res.status}`;
13
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
14
- if (res.status === 403) {
15
- console.warn(`[api] 403 Forbidden: ${url}`);
16
- // Trigger global toast for guest feedback
17
- window.dispatchEvent(new CustomEvent('synabun:forbidden', { detail: errMsg }));
18
- const err = new Error(errMsg);
19
- err.forbidden = true;
20
- throw err;
21
- }
22
- throw new Error(errMsg);
23
- }
24
- return res.json();
25
- }
26
-
27
- function jsonBody(data) {
28
- return {
29
- headers: { 'Content-Type': 'application/json' },
30
- body: JSON.stringify(data),
31
- };
32
- }
33
-
34
- // ─── Health ──────────────────────────────
35
-
36
- export async function fetchHealth() {
37
- const res = await fetch('/api/health', { signal: AbortSignal.timeout(3000) });
38
- return res.json();
39
- }
40
-
41
- export async function startHealth() {
42
- const res = await fetch('/api/health/start', { method: 'POST' });
43
- return res.json();
44
- }
45
-
46
- // ─── Memories ────────────────────────────
47
-
48
- export async function fetchMemories(includeLinks = true) {
49
- // fields=graph: slim payload (truncated content is flagged with
50
- // contentTruncated and hydrated on demand by ui-detail.js)
51
- const url = includeLinks ? '/api/memories?fields=graph' : '/api/memories?links=false&fields=graph';
52
- const res = await fetch(url);
53
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
54
- return res.json();
55
- }
56
-
57
- export async function fetchLinks() {
58
- return jsonFetch('/api/links');
59
- }
60
-
61
- export async function fetchMemory(id) {
62
- return jsonFetch(`/api/memory/${encodeURIComponent(id)}`);
63
- }
64
-
65
- export async function updateMemory(id, payload) {
66
- return jsonFetch(`/api/memory/${encodeURIComponent(id)}`, {
67
- method: 'PATCH',
68
- ...jsonBody(payload),
69
- });
70
- }
71
-
72
- export async function deleteMemory(id) {
73
- return jsonFetch(`/api/memory/${encodeURIComponent(id)}`, {
74
- method: 'DELETE',
75
- });
76
- }
77
-
78
- export async function deleteMemoryPermanent(id) {
79
- return jsonFetch(`/api/memory/${encodeURIComponent(id)}?permanent=true`, {
80
- method: 'DELETE',
81
- });
82
- }
83
-
84
- // ─── Search ──────────────────────────────
85
-
86
- export async function searchMemories(query, limit = 15) {
87
- return jsonFetch('/api/search', {
88
- method: 'POST',
89
- ...jsonBody({ query, limit }),
90
- });
91
- }
92
-
93
- // ─── Stats ───────────────────────────────
94
-
95
- export async function fetchStats() {
96
- return jsonFetch('/api/stats');
97
- }
98
-
99
- // ─── Categories ──────────────────────────
100
-
101
- export async function fetchCategories() {
102
- return jsonFetch('/api/categories');
103
- }
104
-
105
- export async function createCategory(payload) {
106
- // payload: { name, description, color?, parent?, is_parent? }
107
- return jsonFetch('/api/categories', {
108
- method: 'POST',
109
- ...jsonBody(payload),
110
- });
111
- }
112
-
113
- export async function updateCategory(name, updates) {
114
- // updates: { new_name?, description?, parent?, color?, is_parent? }
115
- return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
116
- method: 'PUT',
117
- ...jsonBody(updates),
118
- });
119
- }
120
-
121
- export async function patchCategory(name, updates) {
122
- // updates: { description? }
123
- return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
124
- method: 'PATCH',
125
- ...jsonBody(updates),
126
- });
127
- }
128
-
129
- export async function deleteCategory(name, body = {}) {
130
- // body: { reassign_to?, reassign_children_to?, delete_memories? }
131
- return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
132
- method: 'DELETE',
133
- ...jsonBody(body),
134
- });
135
- }
136
-
137
- export async function exportCategory(name) {
138
- const res = await fetch(`/api/categories/${encodeURIComponent(name)}/export`);
139
- if (!res.ok) {
140
- let errMsg = 'Export failed';
141
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
142
- throw new Error(errMsg);
143
- }
144
- return res.blob();
145
- }
146
-
147
- export async function uploadCategoryLogo(name, fileBuffer, contentType) {
148
- return jsonFetch(`/api/categories/${encodeURIComponent(name)}/logo`, {
149
- method: 'POST',
150
- headers: { 'Content-Type': contentType || 'image/png' },
151
- body: fileBuffer,
152
- });
153
- }
154
-
155
- export async function deleteCategoryLogo(name) {
156
- return jsonFetch(`/api/categories/${encodeURIComponent(name)}/logo`, {
157
- method: 'DELETE',
158
- });
159
- }
160
-
161
- // ─── Trash ───────────────────────────────
162
-
163
- export async function fetchTrash() {
164
- return jsonFetch('/api/trash');
165
- }
166
-
167
- export async function restoreFromTrash(id) {
168
- return jsonFetch(`/api/trash/${encodeURIComponent(id)}/restore`, {
169
- method: 'POST',
170
- });
171
- }
172
-
173
- export async function purgeTrash() {
174
- return jsonFetch('/api/trash/purge', {
175
- method: 'DELETE',
176
- ...jsonBody({}),
177
- });
178
- }
179
-
180
- // ─── Settings ────────────────────────────
181
-
182
- export async function fetchSettings() {
183
- return jsonFetch('/api/settings');
184
- }
185
-
186
- export async function saveSettings(payload) {
187
- // payload: { databasePath? }
188
- return jsonFetch('/api/settings', {
189
- method: 'PUT',
190
- ...jsonBody(payload),
191
- });
192
- }
193
-
194
- // ─── Display Settings ────────────────────
195
-
196
- export async function fetchDisplaySettings() {
197
- return jsonFetch('/api/display-settings');
198
- }
199
-
200
- export async function saveDisplaySettings(payload) {
201
- // payload: { recallMaxChars? }
202
- return jsonFetch('/api/display-settings', {
203
- method: 'PUT',
204
- ...jsonBody(payload),
205
- });
206
- }
207
-
208
- // ─── Keybinds ────────────────────────────
209
-
210
- export async function fetchKeybinds() {
211
- return jsonFetch('/api/keybinds');
212
- }
213
-
214
- export async function saveKeybindsToServer(payload) {
215
- return jsonFetch('/api/keybinds', {
216
- method: 'PUT',
217
- ...jsonBody(payload),
218
- });
219
- }
220
-
221
- // ─── Connections ─────────────────────────
222
-
223
- export async function fetchConnections() {
224
- return jsonFetch('/api/connections');
225
- }
226
-
227
- export async function suggestPort() {
228
- return jsonFetch('/api/connections/suggest-port');
229
- }
230
-
231
- export async function createConnection(payload) {
232
- // payload: { id, label, url, apiKey, collection }
233
- return jsonFetch('/api/connections', {
234
- method: 'POST',
235
- ...jsonBody(payload),
236
- });
237
- }
238
-
239
- export async function setActiveConnection(id) {
240
- return jsonFetch('/api/connections/active', {
241
- method: 'PUT',
242
- ...jsonBody({ id }),
243
- });
244
- }
245
-
246
- export async function deleteConnection(id) {
247
- return jsonFetch(`/api/connections/${encodeURIComponent(id)}`, {
248
- method: 'DELETE',
249
- });
250
- }
251
-
252
- export async function syncConnectionEnv() {
253
- return jsonFetch('/api/connections/sync-env', { method: 'POST' });
254
- }
255
-
256
- export async function startConnectionContainer(id) {
257
- return jsonFetch('/api/connections/start-container', {
258
- method: 'POST',
259
- ...jsonBody({ id }),
260
- });
261
- }
262
-
263
- export async function backupConnection(id) {
264
- const res = await fetch(`/api/connections/${encodeURIComponent(id)}/backup`, {
265
- method: 'POST',
266
- });
267
- if (!res.ok) {
268
- let errMsg = 'Backup failed';
269
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
270
- throw new Error(errMsg);
271
- }
272
- // Returns a blob (snapshot file) with content-disposition header
273
- const blob = await res.blob();
274
- const disposition = res.headers.get('content-disposition') || '';
275
- const filenameMatch = disposition.match(/filename="?([^"]+)"?/);
276
- const filename = filenameMatch ? filenameMatch[1] : `backup-${id}.snapshot`;
277
- return { blob, filename };
278
- }
279
-
280
- export async function restoreConnection(id, snapshotBuffer) {
281
- return jsonFetch(`/api/connections/${encodeURIComponent(id)}/restore`, {
282
- method: 'POST',
283
- headers: { 'Content-Type': 'application/octet-stream' },
284
- body: snapshotBuffer,
285
- });
286
- }
287
-
288
- export async function restoreStandalone(params, snapshotBuffer) {
289
- // params: { url, apiKey, collection, label? }
290
- const qs = new URLSearchParams(params).toString();
291
- return jsonFetch(`/api/connections/restore-standalone?${qs}`, {
292
- method: 'POST',
293
- headers: { 'Content-Type': 'application/octet-stream' },
294
- body: snapshotBuffer,
295
- });
296
- }
297
-
298
- // ─── Full System Backup & Restore ────────
299
-
300
- export async function systemBackup() {
301
- const res = await fetch('/api/system/backup');
302
- if (!res.ok) {
303
- let errMsg = 'Backup failed';
304
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
305
- throw new Error(errMsg);
306
- }
307
- const blob = await res.blob();
308
- const disposition = res.headers.get('content-disposition') || '';
309
- const filenameMatch = disposition.match(/filename="?([^"]+)"?/);
310
- const filename = filenameMatch ? filenameMatch[1] : 'synabun-backup.zip';
311
- return { blob, filename };
312
- }
313
-
314
- export async function systemRestorePreview(zipBuffer) {
315
- const res = await fetch('/api/system/restore/preview', {
316
- method: 'POST',
317
- headers: { 'Content-Type': 'application/zip' },
318
- body: zipBuffer,
319
- });
320
- if (!res.ok) {
321
- let errMsg = 'Preview failed';
322
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
323
- throw new Error(errMsg);
324
- }
325
- return res.json();
326
- }
327
-
328
- export async function systemRestore(zipBuffer, mode = 'full') {
329
- const res = await fetch(`/api/system/restore?mode=${encodeURIComponent(mode)}`, {
330
- method: 'POST',
331
- headers: { 'Content-Type': 'application/zip' },
332
- body: zipBuffer,
333
- });
334
- if (!res.ok) {
335
- let errMsg = 'Restore failed';
336
- try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
337
- throw new Error(errMsg);
338
- }
339
- return res.json();
340
- }
341
-
342
- export async function createCollection(payload) {
343
- // payload: { url, apiKey, collection }
344
- return jsonFetch('/api/connections/create-collection', {
345
- method: 'POST',
346
- ...jsonBody(payload),
347
- });
348
- }
349
-
350
- // ─── OpenClaw Bridge ─────────────────────
351
-
352
- export async function fetchOpenClawBridge() {
353
- return jsonFetch('/api/bridges/openclaw');
354
- }
355
-
356
- export async function connectOpenClawBridge() {
357
- return jsonFetch('/api/bridges/openclaw/connect', {
358
- method: 'POST',
359
- ...jsonBody({}),
360
- });
361
- }
362
-
363
- export async function syncOpenClawBridge() {
364
- return jsonFetch('/api/bridges/openclaw/sync', { method: 'POST' });
365
- }
366
-
367
- export async function disconnectOpenClawBridge() {
368
- return jsonFetch('/api/bridges/openclaw', { method: 'DELETE' });
369
- }
370
-
371
- // ─── Memory Sync ─────────────────────────
372
-
373
- export async function checkSync() {
374
- return jsonFetch('/api/sync/check');
375
- }
376
-
377
- // ─── Claude Code Integrations ────────────
378
-
379
- export async function fetchClaudeCodeIntegrations() {
380
- return jsonFetch('/api/claude-code/integrations');
381
- }
382
-
383
- export async function toggleClaudeCodeIntegration(method, payload) {
384
- // method: 'POST' | 'DELETE'
385
- // payload: { target, projectPath?, hook? }
386
- return jsonFetch('/api/claude-code/integrations', {
387
- method,
388
- ...jsonBody(payload),
389
- });
390
- }
391
-
392
- export async function removeClaudeCodeProject(index) {
393
- return jsonFetch(`/api/claude-code/projects/${index}`, {
394
- method: 'DELETE',
395
- });
396
- }
397
-
398
- export async function fetchClaudeCodeHookFeatures() {
399
- return jsonFetch('/api/claude-code/hook-features');
400
- }
401
-
402
- export async function updateClaudeCodeHookFeature(feature, enabled) {
403
- return jsonFetch('/api/claude-code/hook-features', {
404
- method: 'PUT',
405
- ...jsonBody({ feature, enabled }),
406
- });
407
- }
408
-
409
- export async function fetchClaudeCodeRuleset() {
410
- return jsonFetch('/api/claude-code/ruleset');
411
- }
412
-
413
- // ─── Claude Code MCP ─────────────────────
414
-
415
- export async function fetchClaudeCodeMcp() {
416
- return jsonFetch('/api/claude-code/mcp');
417
- }
418
-
419
- export async function toggleClaudeCodeMcp(method) {
420
- // method: 'POST' to register, 'DELETE' to unregister
421
- return jsonFetch('/api/claude-code/mcp', { method });
422
- }
423
-
424
- // ─── Claude Code Skills ──────────────────
425
-
426
- export async function fetchClaudeCodeSkills() {
427
- return jsonFetch('/api/claude-code/skills');
428
- }
429
-
430
- export async function toggleClaudeCodeSkill(method, skillName) {
431
- // method: 'POST' to install, 'DELETE' to uninstall
432
- return jsonFetch('/api/claude-code/skills', {
433
- method,
434
- ...jsonBody({ name: skillName }),
435
- });
436
- }
437
-
438
- // ─── Skills Studio ───────────────────────
439
-
440
- export async function fetchSkillsLibrary() {
441
- return jsonFetch('/api/skills-studio/library');
442
- }
443
-
444
- export async function fetchSkillsArtifact(encodedId) {
445
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}`);
446
- }
447
-
448
- export async function saveSkillsArtifact(encodedId, rawContent) {
449
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}`, {
450
- method: 'PUT',
451
- ...jsonBody({ rawContent }),
452
- });
453
- }
454
-
455
- export async function fetchSkillsSubFile(encodedId, path) {
456
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file?path=${encodeURIComponent(path)}`);
457
- }
458
-
459
- export async function saveSkillsSubFile(encodedId, path, content) {
460
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
461
- method: 'PUT',
462
- ...jsonBody({ path, content }),
463
- });
464
- }
465
-
466
- export async function createSkillsSubFile(encodedId, path, content, isDir) {
467
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
468
- method: 'POST',
469
- ...jsonBody({ path, content, isDir }),
470
- });
471
- }
472
-
473
- export async function deleteSkillsSubFile(encodedId, path) {
474
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
475
- method: 'DELETE',
476
- ...jsonBody({ path }),
477
- });
478
- }
479
-
480
- export async function createSkillsArtifact(payload) {
481
- return jsonFetch('/api/skills-studio/create', {
482
- method: 'POST',
483
- ...jsonBody(payload),
484
- });
485
- }
486
-
487
- export async function deleteSkillsArtifact(encodedId) {
488
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}`, {
489
- method: 'DELETE',
490
- });
491
- }
492
-
493
- export async function validateSkillsArtifact(rawContent, type) {
494
- return jsonFetch('/api/skills-studio/validate', {
495
- method: 'POST',
496
- ...jsonBody({ rawContent, type }),
497
- });
498
- }
499
-
500
- export async function installSkillsBundled(dirName) {
501
- return jsonFetch('/api/skills-studio/install', {
502
- method: 'POST',
503
- ...jsonBody({ dirName }),
504
- });
505
- }
506
-
507
- export async function uninstallSkillsBundled(dirName) {
508
- return jsonFetch('/api/skills-studio/install', {
509
- method: 'DELETE',
510
- ...jsonBody({ dirName }),
511
- });
512
- }
513
-
514
- // `overwrite`: the user confirmed that files which already exist may be replaced.
515
- // The server refuses a bundle that would replace any without it, naming them:
516
- // that error carries `code: 'import_exists'` and `existing` (the file names).
517
- export async function importSkillsBundle(bundle, scope, projectPath, { overwrite = false } = {}) {
518
- const res = await fetch('/api/skills-studio/import', {
519
- method: 'POST',
520
- ...jsonBody({ bundle, scope, projectPath, ...(overwrite === true ? { overwrite: true } : {}) }),
521
- });
522
- if (res.ok) return res.json();
523
- let body = {};
524
- try { body = (await res.json()) || {}; } catch {}
525
- const err = new Error(body.error || `HTTP ${res.status}`);
526
- if (res.status === 403) {
527
- // Same as jsonFetch: the global toast for a guest.
528
- console.warn('[api] 403 Forbidden: /api/skills-studio/import');
529
- window.dispatchEvent(new CustomEvent('synabun:forbidden', { detail: err.message }));
530
- err.forbidden = true;
531
- } else if (res.status === 409 && body.code === 'import_exists' && Array.isArray(body.existing)) {
532
- err.code = body.code;
533
- err.existing = body.existing.filter(n => typeof n === 'string');
534
- }
535
- throw err;
536
- }
537
-
538
- // Import a bundle, asking before anything that exists is replaced: `ask(existing)`
539
- // gets the names the server refused over, and only `true` sends the bundle
540
- // again as confirmed. Resolves to null when the answer was not a yes.
541
- export async function importSkillsBundleAsking(bundle, scope, projectPath, ask) {
542
- try { return await importSkillsBundle(bundle, scope, projectPath); }
543
- catch (err) {
544
- if (err?.code !== 'import_exists' || !Array.isArray(err.existing) || !err.existing.length) throw err;
545
- if ((await ask(err.existing)) !== true) return null;
546
- return importSkillsBundle(bundle, scope, projectPath, { overwrite: true });
547
- }
548
- }
549
-
550
- export function getSkillsExportUrl(encodedId) {
551
- return `/api/skills-studio/export/${encodedId}`;
552
- }
553
-
554
- export function getSkillsIconUrl(encodedId) {
555
- return `/api/skills-studio/artifact/${encodedId}/icon`;
556
- }
557
-
558
- export async function uploadSkillsIcon(encodedId, fileBuffer, contentType) {
559
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/icon`, {
560
- method: 'POST',
561
- headers: { 'Content-Type': contentType || 'image/png' },
562
- body: fileBuffer,
563
- });
564
- }
565
-
566
- export async function deleteSkillsIcon(encodedId) {
567
- return jsonFetch(`/api/skills-studio/artifact/${encodedId}/icon`, { method: 'DELETE' });
568
- }
569
-
570
- // ─── MCP Key ─────────────────────────────
571
-
572
- export async function fetchMcpKey() {
573
- return jsonFetch('/api/mcp-key');
574
- }
575
-
576
- export async function generateMcpKey() {
577
- return jsonFetch('/api/mcp-key', { method: 'POST' });
578
- }
579
-
580
- export async function revokeMcpKey() {
581
- return jsonFetch('/api/mcp-key', { method: 'DELETE' });
582
- }
583
-
584
- // ─── Tunnel ──────────────────────────────
585
-
586
- export async function fetchTunnelStatus() {
587
- return jsonFetch('/api/tunnel/status');
588
- }
589
-
590
- export async function startTunnel() {
591
- return jsonFetch('/api/tunnel/start', { method: 'POST' });
592
- }
593
-
594
- export async function stopTunnel() {
595
- return jsonFetch('/api/tunnel/stop', { method: 'POST' });
596
- }
597
-
598
- // ─── Last Session (resume after restart) ──
599
-
600
- export async function fetchLastSession() {
601
- const r = await fetch('/api/last-session');
602
- if (!r.ok) return null;
603
- return r.json();
604
- }
605
-
606
- export async function dismissLastSession() {
607
- return fetch('/api/last-session', { method: 'DELETE' });
608
- }
609
-
610
- // ─── Terminal ────────────────────────────
611
-
612
- export async function fetchTerminalSessions() {
613
- return jsonFetch('/api/terminal/sessions');
614
- }
615
-
616
- export async function createTerminalSession(profile, cols, rows, cwd, opts = {}) {
617
- return jsonFetch('/api/terminal/sessions', {
618
- method: 'POST',
619
- ...jsonBody({ profile, cols, rows, cwd, ...opts }),
620
- });
621
- }
622
-
623
- export async function fetchProjects() {
624
- return jsonFetch('/api/projects');
625
- }
626
-
627
- // ─── Style Guide v2 ────────────────────────────
628
- const sgQuery = values => new URLSearchParams(Object.entries(values).filter(([,v]) => v !== undefined && v !== null)).toString();
629
- const sgPost = (path, body) => jsonFetch(path, { method: 'POST', ...jsonBody(body) });
630
- export const fetchStyleGuide = projectPath => jsonFetch(`/api/style-guide?${sgQuery({projectPath})}`);
631
- export const saveStyleGuide = (projectPath, config, {source = 'ui', label} = {}) => jsonFetch('/api/style-guide', { method: 'PUT', ...jsonBody({projectPath, config, source, label}) });
632
- export const fetchStyleGuideProjects = () => jsonFetch('/api/style-guide/projects');
633
- export const fetchStyleGuideDefaults = projectPath => jsonFetch(`/api/style-guide/defaults?${sgQuery({projectPath})}`);
634
- export const fetchStyleGuideSummary = (projectPath, taskClass) => jsonFetch(`/api/style-guide/summary?${sgQuery({projectPath, taskClass})}`);
635
- export const fetchStyleGuideFonts = q => jsonFetch(`/api/style-guide/fonts?${sgQuery({q})}`);
636
- export const fetchStyleGuidePresets = () => jsonFetch('/api/style-guide/presets');
637
- export const applyStyleGuidePreset = (projectPath, presetId, merge) => sgPost('/api/style-guide/presets/apply', {projectPath, presetId, merge});
638
- export const importStyleGuide = (projectPath, options) => sgPost('/api/style-guide/import', {projectPath, ...options});
639
- export const writeStyleGuideExports = (projectPath, formats) => sgPost('/api/style-guide/export', {projectPath, formats});
640
- export const fetchStyleGuideHistory = projectPath => jsonFetch(`/api/style-guide/history?${sgQuery({projectPath})}`);
641
- export const restoreStyleGuideRevision = (projectPath, revision) => sgPost('/api/style-guide/history/restore', {projectPath, revision});
642
- export const fetchStyleGuideProposals = (projectPath, status) => jsonFetch(`/api/style-guide/proposals?${sgQuery({projectPath, status})}`);
643
- export const proposeStyleGuide = (projectPath, options) => sgPost('/api/style-guide/proposals', {projectPath, ...options});
644
- export const acceptStyleGuideProposal = (projectPath, id) => sgPost(`/api/style-guide/proposals/${encodeURIComponent(id)}/accept`, {projectPath});
645
- export const rejectStyleGuideProposal = (projectPath, id) => sgPost(`/api/style-guide/proposals/${encodeURIComponent(id)}/reject`, {projectPath});
646
- export const styleGuideContrast = options => sgPost('/api/style-guide/contrast', options);
647
- export const styleGuideScale = (base, options = {}) => sgPost('/api/style-guide/scale', {base, ...options});
648
- export const styleGuideDarkSemantic = (light, neutral) => sgPost('/api/style-guide/dark-semantic', {light, neutral});
649
- export const setStyleGuidePointers = (projectPath, enabled) => sgPost('/api/style-guide/pointers', {projectPath, enabled});
650
- export async function fetchStyleGuidePreviewMd(projectPath) {
651
- const res = await fetch(`/api/style-guide/preview-md?${sgQuery({projectPath})}`);
652
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
653
- return res.text();
654
- }
655
- export async function fetchStyleGuideExport(projectPath, format, theme) {
656
- const data = await jsonFetch(`/api/style-guide/export?${sgQuery({projectPath, format, theme, as:'json'})}`);
657
- return data.text;
658
- }
659
- export async function uploadStyleGuideLogo(projectPath, file, {variantId, name, bg, kind} = {}) {
660
- const res = await fetch(`/api/style-guide/logo?${sgQuery({projectPath, variantId, name, bg, kind})}`, {method:'POST', headers:{'Content-Type':file.type || 'image/png'}, body:file});
661
- if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `HTTP ${res.status}`); }
662
- return res.json();
663
- }
664
- export const deleteStyleGuideLogo = (projectPath, variantId) => jsonFetch(`/api/style-guide/logo/${encodeURIComponent(variantId)}?${sgQuery({projectPath})}`, {method:'DELETE'});
665
- export function styleGuideAssetUrl(assetsHash, file) {
666
- return assetsHash && file ? `/api/style-guide/assets/${encodeURIComponent(assetsHash)}/${encodeURIComponent(file)}` : '';
667
- }
668
-
669
- // ─── Claude Code Sessions (Resume) ─────
670
-
671
- export async function fetchClaudeSessions({ project, limit, offset, search, refresh, account } = {}) {
672
- const params = new URLSearchParams();
673
- if (project) params.set('project', project);
674
- if (limit) params.set('limit', String(limit));
675
- if (offset) params.set('offset', String(offset));
676
- if (search) params.set('search', search);
677
- if (refresh) params.set('refresh', 'true');
678
- // A named Claude account: the session is looked up (by its id, in `search`) in that account's catalogue.
679
- if (account && account !== 'default') params.set('account', account);
680
- return jsonFetch(`/api/claude-code/sessions?${params}`);
681
- }
682
-
683
- // ─── Codex CLI Sessions (Resume) ─────
684
-
685
- export async function fetchCodexSessions({ project, limit, offset, search, refresh } = {}) {
686
- const params = new URLSearchParams();
687
- if (project) params.set('project', project);
688
- if (limit) params.set('limit', String(limit));
689
- if (offset) params.set('offset', String(offset));
690
- if (search) params.set('search', search);
691
- if (refresh) params.set('refresh', 'true');
692
- return jsonFetch(`/api/codex/sessions?${params}`);
693
- }
694
-
695
- // ─── OpenCode Sessions (Resume) ─────
696
-
697
- export async function fetchOpencodeSessions({ project, limit, offset, search } = {}) {
698
- const params = new URLSearchParams();
699
- if (project) params.set('project', project);
700
- if (limit) params.set('limit', String(limit));
701
- if (offset) params.set('offset', String(offset));
702
- if (search) params.set('search', search);
703
- return jsonFetch(`/api/opencode/sessions?${params}`);
704
- }
705
-
706
- // ─── Gemini CLI Sessions (Resume) ─────
707
-
708
- export async function fetchGeminiSessions({ project, limit, offset, search } = {}) {
709
- const params = new URLSearchParams();
710
- if (project) params.set('project', project);
711
- if (limit) params.set('limit', String(limit));
712
- if (offset) params.set('offset', String(offset));
713
- if (search) params.set('search', search);
714
- return jsonFetch(`/api/gemini/sessions?${params}`);
715
- }
716
-
717
- // ─── Hybrid session content search (FTS5 + semantic for Claude) ─────
718
-
719
- export async function searchSessions({ q, provider, project, limit } = {}) {
720
- const params = new URLSearchParams();
721
- if (q) params.set('q', q);
722
- if (provider) params.set('provider', provider);
723
- if (project) params.set('project', project);
724
- if (limit) params.set('limit', String(limit));
725
- return jsonFetch(`/api/session-search?${params}`);
726
- }
727
-
728
- // ─── Session Indexing ─────
729
-
730
- export async function startSessionIndexing({ project, reindex, sessionIds } = {}) {
731
- return jsonFetch('/api/session-indexing/start', {
732
- method: 'POST',
733
- headers: { 'Content-Type': 'application/json' },
734
- body: JSON.stringify({ project, reindex, sessionIds }),
735
- });
736
- }
737
-
738
- export async function cancelSessionIndexing() {
739
- return jsonFetch('/api/session-indexing/cancel', {
740
- method: 'POST',
741
- headers: { 'Content-Type': 'application/json' },
742
- body: JSON.stringify({}),
743
- });
744
- }
745
-
746
- export async function fetchIndexingStatus() {
747
- return jsonFetch('/api/session-indexing/status');
748
- }
749
-
750
- export async function deleteTerminalSession(sessionId) {
751
- return jsonFetch(`/api/terminal/sessions/${encodeURIComponent(sessionId)}`, {
752
- method: 'DELETE',
753
- });
754
- }
755
-
756
- export async function fetchTerminalFiles(dirPath, search) {
757
- let url = `/api/terminal/files?path=${encodeURIComponent(dirPath)}`;
758
- if (search) url += `&search=${encodeURIComponent(search)}`;
759
- return jsonFetch(url);
760
- }
761
-
762
- export async function fetchTerminalBranches(dirPath) {
763
- return jsonFetch(`/api/terminal/branches?path=${encodeURIComponent(dirPath)}`);
764
- }
765
-
766
- export async function checkoutTerminalBranch(dirPath, branch) {
767
- return jsonFetch('/api/terminal/checkout', {
768
- method: 'POST',
769
- ...jsonBody({ path: dirPath, branch }),
770
- });
771
- }
772
-
773
- // ─── Terminal Links ──────────────────────
774
-
775
- export async function fetchTerminalLinks() {
776
- return jsonFetch('/api/terminal/links');
777
- }
778
-
779
- export async function fetchTerminalLink(linkId) {
780
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`);
781
- }
782
-
783
- export async function createTerminalLink(sessions, config = {}) {
784
- return jsonFetch('/api/terminal/links', {
785
- method: 'POST',
786
- ...jsonBody({ sessions, ...config }),
787
- });
788
- }
789
-
790
- export async function deleteTerminalLink(linkId) {
791
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`, {
792
- method: 'DELETE',
793
- });
794
- }
795
-
796
- export async function detectClaudeSession(terminalSessionId) {
797
- return jsonFetch(`/api/terminal/sessions/${encodeURIComponent(terminalSessionId)}/claude-session`);
798
- }
799
-
800
- export async function updateTerminalLink(linkId, config) {
801
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`, {
802
- method: 'PATCH',
803
- ...jsonBody(config),
804
- });
805
- }
806
-
807
- export async function sendLinkMessage(linkId, message, targetIdx) {
808
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/send`, {
809
- method: 'POST',
810
- ...jsonBody({ message, targetIdx }),
811
- });
812
- }
813
-
814
- export async function pauseLink(linkId) {
815
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/pause`, {
816
- method: 'POST',
817
- });
818
- }
819
-
820
- export async function resumeLink(linkId) {
821
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/resume`, {
822
- method: 'POST',
823
- });
824
- }
825
-
826
- export async function nudgeLink(linkId) {
827
- return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/nudge`, {
828
- method: 'POST',
829
- });
830
- }
831
-
832
- // ─── Browser Sessions ────────────────────
833
-
834
- export async function fetchBrowserSessions() {
835
- return jsonFetch('/api/browser/sessions');
836
- }
837
-
838
- export async function createBrowserSession(url, width, height, fingerprint, opts = {}) {
839
- return jsonFetch('/api/browser/sessions', {
840
- method: 'POST',
841
- ...jsonBody({ url, width, height, ...fingerprint, ...opts }),
842
- });
843
- }
844
-
845
- export async function deleteBrowserSession(sessionId) {
846
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}`, {
847
- method: 'DELETE',
848
- });
849
- }
850
-
851
- export async function navigateBrowser(sessionId, url) {
852
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/navigate`, {
853
- method: 'POST',
854
- ...jsonBody({ url }),
855
- });
856
- }
857
-
858
- export async function browserBack(sessionId) {
859
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/back`, {
860
- method: 'POST',
861
- ...jsonBody({}),
862
- });
863
- }
864
-
865
- export async function browserForward(sessionId) {
866
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/forward`, {
867
- method: 'POST',
868
- ...jsonBody({}),
869
- });
870
- }
871
-
872
- export async function browserReload(sessionId) {
873
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/reload`, {
874
- method: 'POST',
875
- ...jsonBody({}),
876
- });
877
- }
878
-
879
- export async function fetchBrowserCdp(sessionId) {
880
- return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/cdp`);
881
- }
882
-
883
- // ─── Setup (Onboarding) ─────────────────
884
-
885
- export async function checkSetupDeps() {
886
- return jsonFetch('/api/setup/check-deps');
887
- }
888
-
889
- export async function fetchSetupStatus() {
890
- return jsonFetch('/api/setup/onboarding');
891
- }
892
-
893
- export async function saveSetupConfig(payload) {
894
- return jsonFetch('/api/setup/save-config', {
895
- method: 'POST',
896
- ...jsonBody(payload),
897
- });
898
- }
899
-
900
- export async function setupCreateCollection(payload) {
901
- return jsonFetch('/api/setup/create-collection', {
902
- method: 'POST',
903
- ...jsonBody(payload),
904
- });
905
- }
906
-
907
- export async function setupBuild() {
908
- return jsonFetch('/api/setup/build', { method: 'POST' });
909
- }
910
-
911
- export async function testDatabase() {
912
- return jsonFetch('/api/setup/test-qdrant');
913
- }
914
-
915
- export async function testDatabaseRemote(payload) {
916
- return jsonFetch('/api/setup/test-qdrant-cloud', {
917
- method: 'POST',
918
- ...jsonBody(payload),
919
- });
920
- }
921
-
922
- export async function writeMcpJson(payload) {
923
- return jsonFetch('/api/setup/write-mcp-json', {
924
- method: 'POST',
925
- ...jsonBody(payload),
926
- });
927
- }
928
-
929
- export async function completeSetup() {
930
- return jsonFetch('/api/setup/complete', { method: 'POST' });
931
- }
932
-
933
- // ─── SynaBun rules (lib/rulesets/api.js) ──
934
- // A server that has not been restarted since the update answers 404 here.
935
- // The status read returns null for that; the actions throw, and callers show
936
- // the copy-only fallback or a "Restart SynaBun to finish this update" hint.
937
- // An action the files said no to is a 409 and throws too, with the server's
938
- // own sentence as the message: an edited copy on install, damaged markers, a
939
- // symlink, a file that changed while it was being written (code
940
- // CHANGED_UNDERNEATH) or another SynaBun process on the same data home that
941
- // was still at work when the wait ran out (code BUSY, `retryable: true`).
942
- // The last two mean "try again": nothing was changed. A 500 with code
943
- // LOCK_UNAVAILABLE means the server could not use its own lock file.
944
-
945
- /**
946
- * {ok, version, autoUpdate, hosts:{<host>:{carrier, path, state, installedVersion, managed, error, …}}, legacy, notice},
947
- * or null when the route is missing, refuses or fails. A host may also carry
948
- * `replaceable: false` (state `modified`, but the file has no SynaBun markers:
949
- * Replace and a forced Remove are refused), `partialRemoval: {at, error, paths}`
950
- * (the last removal could not take these files out) and, for OpenCode,
951
- * `entry: 'user'` (a line in config.json that SynaBun did not add lists the
952
- * file: Remove keeps both unless it is sent with force). State `newer` is an
953
- * untouched copy a newer SynaBun wrote: it is never updated from here. Unlike the calls below
954
- * it never throws and never raises the guest "forbidden" toast: badges and
955
- * reminders read it in the background on every page load.
956
- */
957
- export async function fetchRulesStatus() {
958
- try {
959
- const res = await fetch('/api/setup/rules');
960
- if (!res.ok) return null;
961
- const data = await res.json();
962
- return data?.ok ? data : null;
963
- } catch {
964
- return null;
965
- }
966
- }
967
-
968
- /** The rendered text for a host (claude, codex, opencode, gemini, cursor) or the copy-only `coexistence` snippet. */
969
- export async function fetchRulesText(host) {
970
- return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}/text`);
971
- }
972
-
973
- /**
974
- * Install or update a host's rules. `force` replaces one well-formed copy the
975
- * user edited (it is backed up first); damaged or duplicate markers are
976
- * refused either way.
977
- */
978
- export async function installRules(host, { force = false } = {}) {
979
- return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}`, {
980
- method: 'POST',
981
- ...jsonBody({ force }),
982
- });
983
- }
984
-
985
- /**
986
- * Remove a host's rules. A copy the user edited stays in place and is listed
987
- * in the result's `kept` (`ok` is still true); `force` removes it after a
988
- * backup. Throws (409) for what is refused either way: damaged markers, a
989
- * symlink, a config that cannot be rewritten.
990
- */
991
- export async function removeRules(host, { force = false } = {}) {
992
- return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}`, { method: 'DELETE', ...jsonBody({ force }) });
993
- }
994
-
995
- /** Install for every connected tool, or only for `hosts` when given. */
996
- export async function installAllRules(hosts = null) {
997
- return jsonFetch('/api/setup/rules/install-all', {
998
- method: 'POST',
999
- ...jsonBody(Array.isArray(hosts) && hosts.length ? { hosts } : {}),
1000
- });
1001
- }
1002
-
1003
- export async function setRulesAutoUpdate(autoUpdate) {
1004
- return jsonFetch('/api/setup/rules/settings', {
1005
- method: 'PUT',
1006
- ...jsonBody({ autoUpdate: !!autoUpdate }),
1007
- });
1008
- }
1009
-
1010
- /** Take a hash-exact pasted copy out of one of the files the status lists under `legacy`. */
1011
- export async function removeLegacyRules(path) {
1012
- return jsonFetch('/api/setup/rules/legacy/remove', {
1013
- method: 'POST',
1014
- ...jsonBody({ path }),
1015
- });
1016
- }
1017
-
1018
- export async function ackRulesNotice() {
1019
- return jsonFetch('/api/setup/rules/notice/ack', { method: 'POST' });
1020
- }
1021
-
1022
- /**
1023
- * The text to show or copy for a host, from the rules route and, when that is
1024
- * not there, from the older read-only endpoint. Throws when neither answers.
1025
- */
1026
- export async function fetchRulesTextWithFallback(host) {
1027
- try {
1028
- const data = await fetchRulesText(host);
1029
- if (data?.text) return data.text;
1030
- } catch { /* older server: try the endpoint it does have */ }
1031
- const format = host === 'opencode' ? 'generic' : host;
1032
- const data = await jsonFetch(`/api/claude-code/ruleset?format=${encodeURIComponent(format)}`);
1033
- if (!data?.ruleset) throw new Error('Rules text not available');
1034
- return data.ruleset;
1035
- }
1036
-
1037
- // ─── Invite / Session Sharing ─────────────
1038
-
1039
- export async function fetchInviteStatus() {
1040
- return jsonFetch('/api/invite/status');
1041
- }
1042
-
1043
- export async function generateInviteKey(custom) {
1044
- const body = custom ? { custom } : {};
1045
- return jsonFetch('/api/invite/key', {
1046
- method: 'POST',
1047
- ...jsonBody(body),
1048
- });
1049
- }
1050
-
1051
- export async function revokeInviteKey() {
1052
- return jsonFetch('/api/invite/key', { method: 'DELETE' });
1053
- }
1054
-
1055
- export async function revokeAllInviteSessions() {
1056
- return jsonFetch('/api/invite/sessions', { method: 'DELETE' });
1057
- }
1058
-
1059
- export async function saveInviteProxy(config) {
1060
- return jsonFetch('/api/invite/proxy', {
1061
- method: 'PUT',
1062
- ...jsonBody(config),
1063
- });
1064
- }
1065
-
1066
- export async function fetchInvitePermissions() {
1067
- return jsonFetch('/api/invite/permissions');
1068
- }
1069
-
1070
- export async function saveInvitePermissions(perms) {
1071
- return jsonFetch('/api/invite/permissions', {
1072
- method: 'PUT',
1073
- ...jsonBody(perms),
1074
- });
1075
- }
1076
-
1077
- // ── Loop Templates ──
1078
-
1079
- export async function fetchLoopTemplates() {
1080
- return jsonFetch('/api/loop/templates');
1081
- }
1082
-
1083
- export async function createLoopTemplate(payload) {
1084
- return jsonFetch('/api/loop/templates', {
1085
- method: 'POST',
1086
- ...jsonBody(payload),
1087
- });
1088
- }
1089
-
1090
- export async function updateLoopTemplate(id, payload) {
1091
- return jsonFetch(`/api/loop/templates/${id}`, {
1092
- method: 'PUT',
1093
- ...jsonBody(payload),
1094
- });
1095
- }
1096
-
1097
- export async function deleteLoopTemplate(id) {
1098
- return jsonFetch(`/api/loop/templates/${id}`, { method: 'DELETE' });
1099
- }
1100
-
1101
- export async function importLoopTemplates(data) {
1102
- return jsonFetch('/api/loop/templates/import', {
1103
- method: 'POST',
1104
- ...jsonBody(data),
1105
- });
1106
- }
1107
-
1108
- // ── Loop Folders ──
1109
-
1110
- export async function fetchLoopFolders() {
1111
- return jsonFetch('/api/loop/folders');
1112
- }
1113
-
1114
- export async function createLoopFolder(payload) {
1115
- return jsonFetch('/api/loop/folders', {
1116
- method: 'POST',
1117
- ...jsonBody(payload),
1118
- });
1119
- }
1120
-
1121
- export async function updateLoopFolder(id, payload) {
1122
- return jsonFetch(`/api/loop/folders/${id}`, {
1123
- method: 'PUT',
1124
- ...jsonBody(payload),
1125
- });
1126
- }
1127
-
1128
- export async function deleteLoopFolder(id) {
1129
- return jsonFetch(`/api/loop/folders/${id}`, { method: 'DELETE' });
1130
- }
1131
-
1132
- export async function reorderLoopFolders(orderedIds) {
1133
- return jsonFetch('/api/loop/folders/reorder', {
1134
- method: 'POST',
1135
- ...jsonBody({ orderedIds }),
1136
- });
1137
- }
1138
-
1139
- export async function fetchActiveLoop() {
1140
- return jsonFetch('/api/loop/active');
1141
- }
1142
-
1143
- export async function launchLoop(params) {
1144
- return jsonFetch('/api/loop/launch', {
1145
- method: 'POST',
1146
- ...jsonBody(params),
1147
- });
1148
- }
1149
-
1150
- export async function fetchLoopLog(terminalSessionId) {
1151
- const res = await fetch(`/api/loop/log/${encodeURIComponent(terminalSessionId)}`);
1152
- if (!res.ok) {
1153
- const text = await res.text().catch(() => '');
1154
- throw new Error(text || `HTTP ${res.status}`);
1155
- }
1156
- return res.text();
1157
- }
1158
-
1159
- export async function sendClientLoopLog(terminalSessionId, tag, msg, meta) {
1160
- try {
1161
- return await jsonFetch('/api/loop/client-log', {
1162
- method: 'POST',
1163
- ...jsonBody({ terminalSessionId: terminalSessionId || null, tag, msg, meta }),
1164
- });
1165
- } catch { /* logging best-effort */ }
1166
- }
1167
-
1168
- export async function stopLoop(terminalSessionId) {
1169
- return jsonFetch('/api/loop/stop', {
1170
- method: 'POST',
1171
- ...jsonBody(terminalSessionId ? { terminalSessionId } : {}),
1172
- });
1173
- }
1174
-
1175
- export async function fetchLoopHistory(limit = 50) {
1176
- return jsonFetch(`/api/loop/history?limit=${limit}`);
1177
- }
1178
-
1179
- export async function deleteLoopHistory(id) {
1180
- return jsonFetch(`/api/loop/history/${id}`, { method: 'DELETE' });
1181
- }
1182
-
1183
- export async function storeLoopCompletion(data) {
1184
- return jsonFetch('/api/loop/complete', {
1185
- method: 'POST',
1186
- ...jsonBody(data),
1187
- });
1188
- }
1189
-
1190
- export async function searchMemoriesByCategory(query, category, limit = 15) {
1191
- return jsonFetch('/api/search/memories', {
1192
- method: 'POST',
1193
- ...jsonBody({ query, category, limit }),
1194
- });
1195
- }
1196
-
1197
- // ── Loop Schedules ──
1198
-
1199
- export async function fetchScheduleGroups() {
1200
- return jsonFetch('/api/schedule-groups');
1201
- }
1202
-
1203
- export async function createScheduleGroup(payload) {
1204
- return jsonFetch('/api/schedule-groups', {
1205
- method: 'POST',
1206
- ...jsonBody(payload),
1207
- });
1208
- }
1209
-
1210
- export async function updateScheduleGroup(id, payload) {
1211
- return jsonFetch(`/api/schedule-groups/${encodeURIComponent(id)}`, {
1212
- method: 'PUT',
1213
- ...jsonBody(payload),
1214
- });
1215
- }
1216
-
1217
- export async function deleteScheduleGroup(id) {
1218
- return jsonFetch(`/api/schedule-groups/${encodeURIComponent(id)}`, {
1219
- method: 'DELETE',
1220
- });
1221
- }
1222
-
1223
- export async function reorderScheduleGroups(orderedIds) {
1224
- return jsonFetch('/api/schedule-groups/reorder', {
1225
- method: 'POST',
1226
- ...jsonBody({ orderedIds }),
1227
- });
1228
- }
1229
-
1230
- export async function fetchSchedules() {
1231
- return jsonFetch('/api/schedules');
1232
- }
1233
-
1234
- export async function createSchedule(params) {
1235
- return jsonFetch('/api/schedules', {
1236
- method: 'POST',
1237
- ...jsonBody(params),
1238
- });
1239
- }
1240
-
1241
- export async function fetchSchedule(id) {
1242
- return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`);
1243
- }
1244
-
1245
- export async function updateSchedule(id, params) {
1246
- return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`, {
1247
- method: 'PUT',
1248
- ...jsonBody(params),
1249
- });
1250
- }
1251
-
1252
- export async function deleteSchedule(id) {
1253
- return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`, {
1254
- method: 'DELETE',
1255
- });
1256
- }
1257
-
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — API Client
3
+ // ═══════════════════════════════════════════
4
+ // Single source of truth for all REST API calls.
5
+ // Every fetch('/api/...') in the UI should route through here.
6
+
7
+ // ─── Helpers ─────────────────────────────
8
+
9
+ async function jsonFetch(url, options = {}) {
10
+ return jsonOf(await fetch(url, options), url);
11
+ }
12
+
13
+ /** An answer as JSON, or the error jsonFetch throws for it (a 403 also tells the page, for its guest notice). */
14
+ async function jsonOf(res, url) {
15
+ if (!res.ok) {
16
+ let errMsg = `HTTP ${res.status}`;
17
+ let errBody = null;
18
+ try { const body = await res.json(); errBody = body && typeof body === 'object' ? body : null; errMsg = body.error || errMsg; } catch {}
19
+ if (res.status === 403) {
20
+ console.warn(`[api] 403 Forbidden: ${url}`);
21
+ // Trigger global toast for guest feedback
22
+ window.dispatchEvent(new CustomEvent('synabun:forbidden', { detail: errMsg }));
23
+ const err = new Error(errMsg);
24
+ err.forbidden = true;
25
+ err.status = 403;
26
+ err.code = errBody?.code;
27
+ err.body = errBody;
28
+ throw err;
29
+ }
30
+ // What the server said besides the message (a code, the revision on disk…) stays with the error.
31
+ const err = new Error(errMsg);
32
+ err.status = res.status;
33
+ err.code = errBody?.code;
34
+ err.body = errBody;
35
+ throw err;
36
+ }
37
+ return res.json();
38
+ }
39
+
40
+ function jsonBody(data) {
41
+ return {
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify(data),
44
+ };
45
+ }
46
+
47
+ // ─── Health ──────────────────────────────
48
+
49
+ export async function fetchHealth() {
50
+ const res = await fetch('/api/health', { signal: AbortSignal.timeout(3000) });
51
+ return res.json();
52
+ }
53
+
54
+ export async function startHealth() {
55
+ const res = await fetch('/api/health/start', { method: 'POST' });
56
+ return res.json();
57
+ }
58
+
59
+ // ─── Memories ────────────────────────────
60
+
61
+ export async function fetchMemories(includeLinks = true) {
62
+ // fields=graph: slim payload (truncated content is flagged with
63
+ // contentTruncated and hydrated on demand by ui-detail.js)
64
+ const url = includeLinks ? '/api/memories?fields=graph' : '/api/memories?links=false&fields=graph';
65
+ const res = await fetch(url);
66
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
67
+ return res.json();
68
+ }
69
+
70
+ export async function fetchLinks() {
71
+ return jsonFetch('/api/links');
72
+ }
73
+
74
+ export async function fetchMemory(id) {
75
+ return jsonFetch(`/api/memory/${encodeURIComponent(id)}`);
76
+ }
77
+
78
+ export async function updateMemory(id, payload) {
79
+ return jsonFetch(`/api/memory/${encodeURIComponent(id)}`, {
80
+ method: 'PATCH',
81
+ ...jsonBody(payload),
82
+ });
83
+ }
84
+
85
+ export async function deleteMemory(id) {
86
+ return jsonFetch(`/api/memory/${encodeURIComponent(id)}`, {
87
+ method: 'DELETE',
88
+ });
89
+ }
90
+
91
+ export async function deleteMemoryPermanent(id) {
92
+ return jsonFetch(`/api/memory/${encodeURIComponent(id)}?permanent=true`, {
93
+ method: 'DELETE',
94
+ });
95
+ }
96
+
97
+ // ─── Search ──────────────────────────────
98
+
99
+ export async function searchMemories(query, limit = 15) {
100
+ return jsonFetch('/api/search', {
101
+ method: 'POST',
102
+ ...jsonBody({ query, limit }),
103
+ });
104
+ }
105
+
106
+ // ─── Stats ───────────────────────────────
107
+
108
+ export async function fetchStats() {
109
+ return jsonFetch('/api/stats');
110
+ }
111
+
112
+ // ─── Categories ──────────────────────────
113
+
114
+ export async function fetchCategories() {
115
+ return jsonFetch('/api/categories');
116
+ }
117
+
118
+ export async function createCategory(payload) {
119
+ // payload: { name, description, color?, parent?, is_parent? }
120
+ return jsonFetch('/api/categories', {
121
+ method: 'POST',
122
+ ...jsonBody(payload),
123
+ });
124
+ }
125
+
126
+ export async function updateCategory(name, updates) {
127
+ // updates: { new_name?, description?, parent?, color?, is_parent? }
128
+ return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
129
+ method: 'PUT',
130
+ ...jsonBody(updates),
131
+ });
132
+ }
133
+
134
+ export async function patchCategory(name, updates) {
135
+ // updates: { description? }
136
+ return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
137
+ method: 'PATCH',
138
+ ...jsonBody(updates),
139
+ });
140
+ }
141
+
142
+ export async function deleteCategory(name, body = {}) {
143
+ // body: { reassign_to?, reassign_children_to?, delete_memories? }
144
+ return jsonFetch(`/api/categories/${encodeURIComponent(name)}`, {
145
+ method: 'DELETE',
146
+ ...jsonBody(body),
147
+ });
148
+ }
149
+
150
+ export async function exportCategory(name) {
151
+ const res = await fetch(`/api/categories/${encodeURIComponent(name)}/export`);
152
+ if (!res.ok) {
153
+ let errMsg = 'Export failed';
154
+ try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
155
+ throw new Error(errMsg);
156
+ }
157
+ return res.blob();
158
+ }
159
+
160
+ export async function uploadCategoryLogo(name, fileBuffer, contentType) {
161
+ return jsonFetch(`/api/categories/${encodeURIComponent(name)}/logo`, {
162
+ method: 'POST',
163
+ headers: { 'Content-Type': contentType || 'image/png' },
164
+ body: fileBuffer,
165
+ });
166
+ }
167
+
168
+ export async function deleteCategoryLogo(name) {
169
+ return jsonFetch(`/api/categories/${encodeURIComponent(name)}/logo`, {
170
+ method: 'DELETE',
171
+ });
172
+ }
173
+
174
+ // ─── Trash ───────────────────────────────
175
+
176
+ export async function fetchTrash() {
177
+ return jsonFetch('/api/trash');
178
+ }
179
+
180
+ export async function restoreFromTrash(id) {
181
+ return jsonFetch(`/api/trash/${encodeURIComponent(id)}/restore`, {
182
+ method: 'POST',
183
+ });
184
+ }
185
+
186
+ export async function purgeTrash() {
187
+ return jsonFetch('/api/trash/purge', {
188
+ method: 'DELETE',
189
+ ...jsonBody({}),
190
+ });
191
+ }
192
+
193
+ // ─── Settings ────────────────────────────
194
+
195
+ export async function fetchSettings() {
196
+ return jsonFetch('/api/settings');
197
+ }
198
+
199
+ export async function saveSettings(payload) {
200
+ // payload: { databasePath? }
201
+ return jsonFetch('/api/settings', {
202
+ method: 'PUT',
203
+ ...jsonBody(payload),
204
+ });
205
+ }
206
+
207
+ // ─── Display Settings ────────────────────
208
+
209
+ export async function fetchDisplaySettings() {
210
+ return jsonFetch('/api/display-settings');
211
+ }
212
+
213
+ export async function saveDisplaySettings(payload) {
214
+ // payload: { recallMaxChars? }
215
+ return jsonFetch('/api/display-settings', {
216
+ method: 'PUT',
217
+ ...jsonBody(payload),
218
+ });
219
+ }
220
+
221
+ // ─── Keybinds ────────────────────────────
222
+
223
+ export async function fetchKeybinds() {
224
+ return jsonFetch('/api/keybinds');
225
+ }
226
+
227
+ export async function saveKeybindsToServer(payload) {
228
+ return jsonFetch('/api/keybinds', {
229
+ method: 'PUT',
230
+ ...jsonBody(payload),
231
+ });
232
+ }
233
+
234
+ // ─── Connections ─────────────────────────
235
+
236
+ export async function fetchConnections() {
237
+ return jsonFetch('/api/connections');
238
+ }
239
+
240
+ export async function suggestPort() {
241
+ return jsonFetch('/api/connections/suggest-port');
242
+ }
243
+
244
+ export async function createConnection(payload) {
245
+ // payload: { id, label, url, apiKey, collection }
246
+ return jsonFetch('/api/connections', {
247
+ method: 'POST',
248
+ ...jsonBody(payload),
249
+ });
250
+ }
251
+
252
+ export async function setActiveConnection(id) {
253
+ return jsonFetch('/api/connections/active', {
254
+ method: 'PUT',
255
+ ...jsonBody({ id }),
256
+ });
257
+ }
258
+
259
+ export async function deleteConnection(id) {
260
+ return jsonFetch(`/api/connections/${encodeURIComponent(id)}`, {
261
+ method: 'DELETE',
262
+ });
263
+ }
264
+
265
+ export async function syncConnectionEnv() {
266
+ return jsonFetch('/api/connections/sync-env', { method: 'POST' });
267
+ }
268
+
269
+ export async function startConnectionContainer(id) {
270
+ return jsonFetch('/api/connections/start-container', {
271
+ method: 'POST',
272
+ ...jsonBody({ id }),
273
+ });
274
+ }
275
+
276
+ export async function backupConnection(id) {
277
+ const res = await fetch(`/api/connections/${encodeURIComponent(id)}/backup`, {
278
+ method: 'POST',
279
+ });
280
+ if (!res.ok) {
281
+ let errMsg = 'Backup failed';
282
+ try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
283
+ throw new Error(errMsg);
284
+ }
285
+ // Returns a blob (snapshot file) with content-disposition header
286
+ const blob = await res.blob();
287
+ const disposition = res.headers.get('content-disposition') || '';
288
+ const filenameMatch = disposition.match(/filename="?([^"]+)"?/);
289
+ const filename = filenameMatch ? filenameMatch[1] : `backup-${id}.snapshot`;
290
+ return { blob, filename };
291
+ }
292
+
293
+ export async function restoreConnection(id, snapshotBuffer) {
294
+ return jsonFetch(`/api/connections/${encodeURIComponent(id)}/restore`, {
295
+ method: 'POST',
296
+ headers: { 'Content-Type': 'application/octet-stream' },
297
+ body: snapshotBuffer,
298
+ });
299
+ }
300
+
301
+ export async function restoreStandalone(params, snapshotBuffer) {
302
+ // params: { url, apiKey, collection, label? }
303
+ const qs = new URLSearchParams(params).toString();
304
+ return jsonFetch(`/api/connections/restore-standalone?${qs}`, {
305
+ method: 'POST',
306
+ headers: { 'Content-Type': 'application/octet-stream' },
307
+ body: snapshotBuffer,
308
+ });
309
+ }
310
+
311
+ // ─── Full System Backup & Restore ────────
312
+
313
+ export async function systemBackup() {
314
+ const res = await fetch('/api/system/backup');
315
+ if (!res.ok) {
316
+ let errMsg = 'Backup failed';
317
+ try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
318
+ throw new Error(errMsg);
319
+ }
320
+ const blob = await res.blob();
321
+ const disposition = res.headers.get('content-disposition') || '';
322
+ const filenameMatch = disposition.match(/filename="?([^"]+)"?/);
323
+ const filename = filenameMatch ? filenameMatch[1] : 'synabun-backup.zip';
324
+ return { blob, filename };
325
+ }
326
+
327
+ export async function systemRestorePreview(zipBuffer) {
328
+ const res = await fetch('/api/system/restore/preview', {
329
+ method: 'POST',
330
+ headers: { 'Content-Type': 'application/zip' },
331
+ body: zipBuffer,
332
+ });
333
+ if (!res.ok) {
334
+ let errMsg = 'Preview failed';
335
+ try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
336
+ throw new Error(errMsg);
337
+ }
338
+ return res.json();
339
+ }
340
+
341
+ export async function systemRestore(zipBuffer, mode = 'full') {
342
+ const res = await fetch(`/api/system/restore?mode=${encodeURIComponent(mode)}`, {
343
+ method: 'POST',
344
+ headers: { 'Content-Type': 'application/zip' },
345
+ body: zipBuffer,
346
+ });
347
+ if (!res.ok) {
348
+ let errMsg = 'Restore failed';
349
+ try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
350
+ throw new Error(errMsg);
351
+ }
352
+ return res.json();
353
+ }
354
+
355
+ export async function createCollection(payload) {
356
+ // payload: { url, apiKey, collection }
357
+ return jsonFetch('/api/connections/create-collection', {
358
+ method: 'POST',
359
+ ...jsonBody(payload),
360
+ });
361
+ }
362
+
363
+ // ─── OpenClaw Bridge ─────────────────────
364
+
365
+ export async function fetchOpenClawBridge() {
366
+ return jsonFetch('/api/bridges/openclaw');
367
+ }
368
+
369
+ export async function connectOpenClawBridge() {
370
+ return jsonFetch('/api/bridges/openclaw/connect', {
371
+ method: 'POST',
372
+ ...jsonBody({}),
373
+ });
374
+ }
375
+
376
+ export async function syncOpenClawBridge() {
377
+ return jsonFetch('/api/bridges/openclaw/sync', { method: 'POST' });
378
+ }
379
+
380
+ export async function disconnectOpenClawBridge() {
381
+ return jsonFetch('/api/bridges/openclaw', { method: 'DELETE' });
382
+ }
383
+
384
+ // ─── Memory Sync ─────────────────────────
385
+
386
+ export async function checkSync() {
387
+ return jsonFetch('/api/sync/check');
388
+ }
389
+
390
+ // ─── Claude Code Integrations ────────────
391
+
392
+ export async function fetchClaudeCodeIntegrations() {
393
+ return jsonFetch('/api/claude-code/integrations');
394
+ }
395
+
396
+ export async function toggleClaudeCodeIntegration(method, payload) {
397
+ // method: 'POST' | 'DELETE'
398
+ // payload: { target, projectPath?, hook? }
399
+ return jsonFetch('/api/claude-code/integrations', {
400
+ method,
401
+ ...jsonBody(payload),
402
+ });
403
+ }
404
+
405
+ export async function removeClaudeCodeProject(index) {
406
+ return jsonFetch(`/api/claude-code/projects/${index}`, {
407
+ method: 'DELETE',
408
+ });
409
+ }
410
+
411
+ export async function fetchClaudeCodeHookFeatures() {
412
+ return jsonFetch('/api/claude-code/hook-features');
413
+ }
414
+
415
+ export async function updateClaudeCodeHookFeature(feature, enabled) {
416
+ return jsonFetch('/api/claude-code/hook-features', {
417
+ method: 'PUT',
418
+ ...jsonBody({ feature, enabled }),
419
+ });
420
+ }
421
+
422
+ export async function fetchClaudeCodeRuleset() {
423
+ return jsonFetch('/api/claude-code/ruleset');
424
+ }
425
+
426
+ // ─── Claude Code MCP ─────────────────────
427
+
428
+ export async function fetchClaudeCodeMcp() {
429
+ return jsonFetch('/api/claude-code/mcp');
430
+ }
431
+
432
+ export async function toggleClaudeCodeMcp(method) {
433
+ // method: 'POST' to register, 'DELETE' to unregister
434
+ return jsonFetch('/api/claude-code/mcp', { method });
435
+ }
436
+
437
+ // ─── Claude Code Skills ──────────────────
438
+
439
+ export async function fetchClaudeCodeSkills() {
440
+ return jsonFetch('/api/claude-code/skills');
441
+ }
442
+
443
+ export async function toggleClaudeCodeSkill(method, skillName) {
444
+ // method: 'POST' to install, 'DELETE' to uninstall
445
+ return jsonFetch('/api/claude-code/skills', {
446
+ method,
447
+ ...jsonBody({ name: skillName }),
448
+ });
449
+ }
450
+
451
+ // ─── Skills Studio ───────────────────────
452
+ // Every change here is the owner's, from this page: the routes refuse a write without the X-SynaBun-UI header (and a
453
+ // guest's, and one that carries an agent's headers), as the Skills Service's owner routes below do.
454
+
455
+ const STUDIO_UI = { 'X-SynaBun-UI': '1' };
456
+
457
+ /** A Skills Studio write's JSON body, sent as this page's. */
458
+ function studioJsonBody(data) {
459
+ return { headers: { 'Content-Type': 'application/json', ...STUDIO_UI }, body: JSON.stringify(data) };
460
+ }
461
+
462
+ export async function fetchSkillsLibrary() {
463
+ return jsonFetch('/api/skills-studio/library');
464
+ }
465
+
466
+ export async function fetchSkillsArtifact(encodedId) {
467
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}`);
468
+ }
469
+
470
+ // `expectedRevision`: the revision the editor opened (or last saved). A file that is not that any more is not
471
+ // written: the error has status 409, code `revision_conflict` and body.currentRevision.
472
+ export async function saveSkillsArtifact(encodedId, rawContent, { expectedRevision } = {}) {
473
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}`, {
474
+ method: 'PUT',
475
+ ...studioJsonBody({ rawContent, ...(expectedRevision ? { expectedRevision } : {}) }),
476
+ });
477
+ }
478
+
479
+ export async function fetchSkillsSubFile(encodedId, path) {
480
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file?path=${encodeURIComponent(path)}`);
481
+ }
482
+
483
+ export async function saveSkillsSubFile(encodedId, path, content, { expectedRevision } = {}) {
484
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
485
+ method: 'PUT',
486
+ ...studioJsonBody({ path, content, ...(expectedRevision ? { expectedRevision } : {}) }),
487
+ });
488
+ }
489
+
490
+ // `contentBase64`: a file that is not text (an image, a PDF), sent byte for byte.
491
+ export async function createSkillsSubFile(encodedId, path, content, isDir, { contentBase64 } = {}) {
492
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
493
+ method: 'POST',
494
+ ...studioJsonBody({ path, content, isDir, ...(contentBase64 !== undefined ? { contentBase64 } : {}) }),
495
+ });
496
+ }
497
+
498
+ export async function deleteSkillsSubFile(encodedId, path) {
499
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/file`, {
500
+ method: 'DELETE',
501
+ ...studioJsonBody({ path }),
502
+ });
503
+ }
504
+
505
+ export async function createSkillsArtifact(payload) {
506
+ return jsonFetch('/api/skills-studio/create', {
507
+ method: 'POST',
508
+ ...studioJsonBody(payload),
509
+ });
510
+ }
511
+
512
+ export async function deleteSkillsArtifact(encodedId) {
513
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}`, {
514
+ method: 'DELETE',
515
+ headers: STUDIO_UI,
516
+ });
517
+ }
518
+
519
+ export async function validateSkillsArtifact(rawContent, type) {
520
+ return jsonFetch('/api/skills-studio/validate', {
521
+ method: 'POST',
522
+ ...studioJsonBody({ rawContent, type }),
523
+ });
524
+ }
525
+
526
+ // A folder already there is replaced only when SynaBun installed it and it is unchanged; otherwise the error is
527
+ // a 409 (`skill_unowned` / `skill_modified`) whose body names `currentDigest`: sending that back as
528
+ // `expectedDestinationDigest` is the person's confirmation of replacing exactly what is there (a backup is kept).
529
+ export async function installSkillsBundled(dirName, { expectedDestinationDigest } = {}) {
530
+ return jsonFetch('/api/skills-studio/install', {
531
+ method: 'POST',
532
+ ...studioJsonBody({ dirName, ...(expectedDestinationDigest ? { expectedDestinationDigest } : {}) }),
533
+ });
534
+ }
535
+
536
+ export async function uninstallSkillsBundled(dirName, { expectedDestinationDigest } = {}) {
537
+ return jsonFetch('/api/skills-studio/install', {
538
+ method: 'DELETE',
539
+ ...studioJsonBody({ dirName, ...(expectedDestinationDigest ? { expectedDestinationDigest } : {}) }),
540
+ });
541
+ }
542
+
543
+ // An editable copy of a bundled skill, under a name of the user's own, in a scope they chose.
544
+ export async function customizeSkillsCopy({ id, name, scope, projectPath }) {
545
+ return jsonFetch('/api/skills-studio/customize', {
546
+ method: 'POST',
547
+ ...studioJsonBody({ id, name, scope, ...(projectPath ? { projectPath } : {}) }),
548
+ });
549
+ }
550
+
551
+ // An import is checked and staged first (nothing is installed), then installed by its staged digest once a
552
+ // person reviewed it. `scope` is 'global', or 'project' with a registered projectPath: never guessed.
553
+ export async function stageSkillsImport(bundle, scope, projectPath) {
554
+ return jsonFetch('/api/skills-studio/import/stage', {
555
+ method: 'POST',
556
+ ...studioJsonBody({ bundle, scope, ...(projectPath ? { projectPath } : {}) }),
557
+ });
558
+ }
559
+
560
+ export async function commitSkillsImport({ stagingId, expectedDigest, acknowledgedDigest, overwrite }) {
561
+ return jsonFetch('/api/skills-studio/import/commit', {
562
+ method: 'POST',
563
+ ...studioJsonBody({ stagingId, expectedDigest, ...(acknowledgedDigest ? { acknowledgedDigest } : {}), ...(overwrite === true ? { overwrite: true } : {}) }),
564
+ });
565
+ }
566
+
567
+ export async function discardSkillsImport(stagingId) {
568
+ return jsonFetch(`/api/skills-studio/import/stage/${encodeURIComponent(stagingId)}`, { method: 'DELETE', headers: STUDIO_UI });
569
+ }
570
+
571
+ // `overwrite`: the user confirmed that files which already exist may be replaced.
572
+ // The server refuses a bundle that would replace any without it, naming them:
573
+ // that error carries `code: 'import_exists'` and `existing` (the file names).
574
+ export async function importSkillsBundle(bundle, scope, projectPath, { overwrite = false } = {}) {
575
+ const res = await fetch('/api/skills-studio/import', {
576
+ method: 'POST',
577
+ ...studioJsonBody({ bundle, scope, projectPath, ...(overwrite === true ? { overwrite: true } : {}) }),
578
+ });
579
+ if (res.ok) return res.json();
580
+ let body = {};
581
+ try { body = (await res.json()) || {}; } catch {}
582
+ const err = new Error(body.error || `HTTP ${res.status}`);
583
+ if (res.status === 403) {
584
+ // Same as jsonFetch: the global toast for a guest.
585
+ console.warn('[api] 403 Forbidden: /api/skills-studio/import');
586
+ window.dispatchEvent(new CustomEvent('synabun:forbidden', { detail: err.message }));
587
+ err.forbidden = true;
588
+ } else if (res.status === 409 && body.code === 'import_exists' && Array.isArray(body.existing)) {
589
+ err.code = body.code;
590
+ err.existing = body.existing.filter(n => typeof n === 'string');
591
+ }
592
+ throw err;
593
+ }
594
+
595
+ // Import a bundle, asking before anything that exists is replaced: `ask(existing)`
596
+ // gets the names the server refused over, and only `true` sends the bundle
597
+ // again as confirmed. Resolves to null when the answer was not a yes.
598
+ export async function importSkillsBundleAsking(bundle, scope, projectPath, ask) {
599
+ try { return await importSkillsBundle(bundle, scope, projectPath); }
600
+ catch (err) {
601
+ if (err?.code !== 'import_exists' || !Array.isArray(err.existing) || !err.existing.length) throw err;
602
+ if ((await ask(err.existing)) !== true) return null;
603
+ return importSkillsBundle(bundle, scope, projectPath, { overwrite: true });
604
+ }
605
+ }
606
+
607
+ export function getSkillsExportUrl(encodedId) {
608
+ return `/api/skills-studio/export/${encodedId}`;
609
+ }
610
+
611
+ export function getSkillsIconUrl(encodedId) {
612
+ return `/api/skills-studio/artifact/${encodedId}/icon`;
613
+ }
614
+
615
+ export async function uploadSkillsIcon(encodedId, fileBuffer, contentType) {
616
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/icon`, {
617
+ method: 'POST',
618
+ headers: { 'Content-Type': contentType || 'image/png', ...STUDIO_UI },
619
+ body: fileBuffer,
620
+ });
621
+ }
622
+
623
+ export async function deleteSkillsIcon(encodedId) {
624
+ return jsonFetch(`/api/skills-studio/artifact/${encodedId}/icon`, { method: 'DELETE', headers: STUDIO_UI });
625
+ }
626
+
627
+ // ─── Skills Service (/api/skills-studio/v2) ──
628
+ // Reads for everyone the Skills Studio permission lets in. Installs, approvals, rollbacks, the move of the old
629
+ // bridges and the settings are the owner's, from this page: those routes want the X-SynaBun-UI header and refuse
630
+ // a request that carries an agent's headers (lib/skills/api-v2.js).
631
+
632
+ function skillsV2(path, { method = 'GET', body, idempotencyKey } = {}) {
633
+ const headers = { 'X-SynaBun-UI': '1' };
634
+ if (body !== undefined) headers['Content-Type'] = 'application/json';
635
+ // A write that creates something is sent once: the same key within ten minutes answers the first result again.
636
+ if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
637
+ if (method === 'GET') return skillsV2Read(`/api/skills-studio/v2${path}`, headers);
638
+ return jsonFetch(`/api/skills-studio/v2${path}`, { method, headers, ...(body !== undefined ? { body: JSON.stringify(body) } : {}) });
639
+ }
640
+
641
+ /**
642
+ * A Studio read. Some are the owner's page's only (an evaluation's answers and files: lib/skills/evals-api.js): a tab
643
+ * left open across a server restart holds the earlier boot's page secret, so a read refused for it asks this boot's
644
+ * once (GET /api/page-session, public/shared/page-session.js does the same for writes) and is sent again.
645
+ */
646
+ async function skillsV2Read(url, headers) {
647
+ let res = await fetch(url, { method: 'GET', headers });
648
+ if (res.status === 403) {
649
+ let code = null;
650
+ try { code = (await res.clone().json())?.code || null; } catch { code = null; }
651
+ if (code === 'PAGE_SECRET_REQUIRED') {
652
+ const refreshed = await fetch('/api/page-session', { method: 'GET', credentials: 'same-origin', cache: 'no-store', headers: { Accept: 'application/json' } }).then((r) => r.ok, () => false);
653
+ if (refreshed) res = await fetch(url, { method: 'GET', headers });
654
+ }
655
+ }
656
+ return jsonOf(res, url);
657
+ }
658
+
659
+ export function fetchSkillsStatus() { return skillsV2('/status'); }
660
+ export function fetchSkillsReviews({ open = null } = {}) { return skillsV2(`/reviews${open === null ? '' : `?open=${open}`}`); }
661
+ export function decideSkillsReview(reviewId, decision, reason = '') { return skillsV2(`/reviews/${encodeURIComponent(reviewId)}/decision`, { method: 'POST', body: { decision, reason } }); }
662
+ export function fetchSkillsRevision(revisionId) { return skillsV2(`/revisions/${encodeURIComponent(revisionId)}`); }
663
+ export function fetchSkillsRevisionFile(revisionId, file) { return skillsV2(`/revisions/${encodeURIComponent(revisionId)}?file=${encodeURIComponent(file)}`); }
664
+ export function fetchSkillsDiff(from, to) { return skillsV2(`/diff?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}`); }
665
+ export function planSkillsInstall(revisionId, targets) { return skillsV2('/installations/plan', { method: 'POST', body: { revisionId, targets } }); }
666
+ export function installSkillsRevision(payload) { return skillsV2('/installations', { method: 'POST', body: payload }); }
667
+ export function fetchSkillsInstallations({ active = null } = {}) { return skillsV2(`/installations${active === null ? '' : `?active=${active}`}`); }
668
+ export function rollbackSkillsInstallation(id, body = {}) { return skillsV2(`/installations/${encodeURIComponent(id)}/rollback`, { method: 'POST', body }); }
669
+ export function verifySkillsInstallation(id, { probe = false } = {}) { return skillsV2(`/installations/${encodeURIComponent(id)}/verify`, { method: 'POST', body: { probe } }); }
670
+ export function planSkillsBridgeMigration() { return skillsV2('/migrations/bridges/plan'); }
671
+ export function runSkillsBridgeMigration(planDigest, confirm = null) { return skillsV2('/migrations/bridges', { method: 'POST', body: confirm && Object.keys(confirm).length ? { planDigest, confirm } : { planDigest } }); }
672
+ // The plan with the owner's confirmations of bridges that are not proven SynaBun's (a reading; nothing moves).
673
+ export function planSkillsBridgeMigrationConfirmed(confirm) { return skillsV2('/migrations/bridges/plan', { method: 'POST', body: { confirm: confirm || {} } }); }
674
+ // The start-up move's one-time notice (what moved, what is offered), and the owner's dismissal of it.
675
+ export function fetchSkillsMigrationNotice() { return skillsV2('/migrations/notice'); }
676
+ export function dismissSkillsMigrationNotice() { return skillsV2('/migrations/notice/dismiss', { method: 'POST', body: {} }); }
677
+ // The owner's "Enable for <runtime>": the runtime setting that turns a skill off, changed over a backup.
678
+ export function enableSkillsEligibility(body) { return skillsV2('/eligibility/enable', { method: 'POST', body }); }
679
+ export function revertSkillsMigration(journalId) { return skillsV2(`/migrations/${encodeURIComponent(journalId)}/revert`, { method: 'POST', body: {} }); }
680
+ export function fetchSkillsMigrations() { return skillsV2('/migrations'); }
681
+ export function fetchSkillsConfig() { return skillsV2('/config'); }
682
+ export function saveSkillsConfig(patch) { return skillsV2('/config', { method: 'PUT', body: patch }); }
683
+
684
+ // Skills Studio 2.0 (P2): the catalog of every runtime folder, the managed library (skills, drafts, revisions,
685
+ // pointers), checks, exports, and what a page newer than its server may ask (capabilities).
686
+ const enc = (v) => encodeURIComponent(v);
687
+ const query = (params) => { const q = Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '').map(([k, v]) => `${enc(k)}=${enc(v)}`).join('&'); return q ? `?${q}` : ''; };
688
+ export function fetchSkillsCapabilities() { return skillsV2('/capabilities'); }
689
+ export function fetchSkillsCatalog({ cursor, limit, kind, runtime, rootId, scope, q } = {}) { return skillsV2(`/catalog${query({ cursor, limit, kind, runtime, rootId, scope, q })}`); }
690
+ export function fetchSkillsCatalogEntry(artifactId) { return skillsV2(`/catalog/${enc(artifactId)}`); }
691
+ export function fetchSkillsCatalogFile(artifactId, path) { return skillsV2(`/catalog/${enc(artifactId)}/file?path=${enc(path)}`); }
692
+ export function fetchSkillsCompare(fromRevisionId, artifactId) { return skillsV2(`/compare?from=${enc(fromRevisionId)}&artifactId=${enc(artifactId)}`); }
693
+ export function rescanSkillsCatalog() { return skillsV2('/catalog/rescan', { method: 'POST', body: {} }); }
694
+ export function fetchSkillsList() { return skillsV2('/skills'); }
695
+ // The skills deleted from the library (their versions kept), and the owner's Restore of one.
696
+ export function fetchSkillsArchived() { return skillsV2('/skills/archived'); }
697
+ export function restoreSkillsSkill(skillId) { return skillsV2(`/skills/${enc(skillId)}/restore`, { method: 'POST', body: {} }); }
698
+ export function fetchSkill(skillId) { return skillsV2(`/skills/${enc(skillId)}`); }
699
+ // Out of the library (its versions kept in the library's archive): 409 `skill_installed` while it is installed
700
+ // somewhere, 403 `skill_bundled` for one SynaBun ships, 409 `skill_conflict` when `expectedEtag` is not its etag now.
701
+ export function deleteLibrarySkill(skillId, { expectedEtag = null } = {}) { return skillsV2(`/skills/${enc(skillId)}`, { method: 'DELETE', body: expectedEtag === null ? {} : { expectedEtag } }); }
702
+ export function fetchSkillRevisions(skillId) { return skillsV2(`/skills/${enc(skillId)}/revisions`); }
703
+ export function createLibrarySkill({ name, files, dirs = [] }, { idempotencyKey } = {}) { return skillsV2('/skills', { method: 'POST', body: { name, files, dirs }, idempotencyKey }); }
704
+ export function adoptSkillsArtifact(artifactId, { idempotencyKey } = {}) { return skillsV2('/skills', { method: 'POST', body: { artifactId }, idempotencyKey }); }
705
+ export function moveSkillPointer(skillId, { pointer, revisionId, expectedEtag = null, reason = '' }) { return skillsV2(`/skills/${enc(skillId)}/pointers`, { method: 'POST', body: { pointer, revisionId, expectedEtag, reason } }); }
706
+ export function createSkillsDraft({ skillId, baseRevisionId = null }, { idempotencyKey } = {}) { return skillsV2('/drafts', { method: 'POST', body: { skillId, baseRevisionId }, idempotencyKey }); }
707
+ export function fetchSkillsDraft(draftId) { return skillsV2(`/drafts/${enc(draftId)}`); }
708
+ export function fetchSkillsDraftFile(draftId, path) { return skillsV2(`/drafts/${enc(draftId)}?file=${enc(path)}`); }
709
+ export function patchSkillsDraft(draftId, { expectedEtag, ops }) { return skillsV2(`/drafts/${enc(draftId)}`, { method: 'PATCH', body: { expectedEtag, ops } }); }
710
+ export function snapshotSkillsDraft(draftId, { expectedEtag, note = '' }, { idempotencyKey } = {}) { return skillsV2(`/drafts/${enc(draftId)}/snapshot`, { method: 'POST', body: { expectedEtag, note }, idempotencyKey }); }
711
+ export function lintSkillsFolder(body) { return skillsV2('/lint', { method: 'POST', body }); }
712
+ export function uninstallSkillsInstallation(id, body = {}) { return skillsV2(`/installations/${enc(id)}/uninstall`, { method: 'POST', body }); }
713
+ export function commentSkillsReview(reviewId, body) { return skillsV2(`/reviews/${enc(reviewId)}/comment`, { method: 'POST', body: { body } }); }
714
+ export function getSkillsRevisionExportUrl(revisionId, { format = 'bundle', profile = '', acknowledgedDigest = '', excludeEvals = false } = {}) { return `/api/skills-studio/v2/revisions/${enc(revisionId)}/export${query({ format, profile, acknowledgedDigest, excludeEvals })}`; }
715
+
716
+ // AI drafting (P3, lib/skills/creation-api.js): a creation from the owner's words and sources, one worker drafting it,
717
+ // the result checked and put in the Review queue. Followed by a long poll (`wait`); cancelled and retried from here.
718
+ export function fetchSkillCreationOptions() { return skillsV2('/creations/options'); }
719
+ export function fetchSkillCreationSessions({ provider, project = '', q = '', limit = 40 } = {}) { return skillsV2(`/creations/sessions${query({ provider, project, q, limit })}`); }
720
+ export function fetchSkillCreations({ limit = 20 } = {}) { return skillsV2(`/creations${query({ limit })}`); }
721
+ export function startSkillCreation(body, { idempotencyKey } = {}) { return skillsV2('/creations', { method: 'POST', body, idempotencyKey }); }
722
+ export function fetchSkillCreation(id) { return skillsV2(`/creations/${enc(id)}`); }
723
+ export function waitSkillCreation(id, { since = -1, seconds = 20 } = {}) { return skillsV2(`/creations/${enc(id)}/wait${query({ since, seconds })}`); }
724
+ export function cancelSkillCreation(id) { return skillsV2(`/creations/${enc(id)}/cancel`, { method: 'POST', body: {} }); }
725
+ export function retrySkillCreation(id, { idempotencyKey } = {}) { return skillsV2(`/creations/${enc(id)}/retry`, { method: 'POST', body: {}, idempotencyKey }); }
726
+
727
+ // Evaluations (P4, lib/skills/evals-api.js): the Test tab's runs of a revision's evals/evals.json, followed by a long poll
728
+ // (`wait`); estimated before they start, cancelled, reviewed trial by trial, compared, exported.
729
+ export function fetchSkillEvalOptions() { return skillsV2('/evals/options'); }
730
+ export function estimateSkillEval(body) { return skillsV2('/evals/estimate', { method: 'POST', body }); }
731
+ export function startSkillEval(body, { idempotencyKey } = {}) { return skillsV2('/evals', { method: 'POST', body, idempotencyKey }); }
732
+ export function fetchSkillEvals({ skillId = '', revisionId = '', limit = 30 } = {}) { return skillsV2(`/evals${query({ skillId, revisionId, limit })}`); }
733
+ export function fetchSkillEvalQueue() { return skillsV2('/evals/queue'); }
734
+ export function fetchSkillEval(id) { return skillsV2(`/evals/${enc(id)}`); }
735
+ export function waitSkillEval(id, { since = -1, seconds = 20, full = false } = {}) { return skillsV2(`/evals/${enc(id)}/wait${query({ since, seconds, full: full ? '1' : '' })}`); }
736
+ export function cancelSkillEval(id) { return skillsV2(`/evals/${enc(id)}/cancel`, { method: 'POST', body: {} }); }
737
+ export function fetchSkillEvalTrial(id, trialId) { return skillsV2(`/evals/${enc(id)}/trials/${enc(trialId)}`); }
738
+ export function fetchSkillEvalTrialFile(id, trialId, path) { return skillsV2(`/evals/${enc(id)}/trials/${enc(trialId)}/file${query({ path })}`); }
739
+ export function saveSkillEvalFeedback(id, { trialId, text, reviewed }) { return skillsV2(`/evals/${enc(id)}/feedback`, { method: 'POST', body: { trialId, text, reviewed } }); }
740
+ export function compareSkillEvals(a, b) { return skillsV2(`/evals/compare${query({ a, b })}`); }
741
+ export function importSkillEvalSuite(body) { return skillsV2('/evals/import', { method: 'POST', body }); }
742
+ export function getSkillEvalExportUrl(id, format = 'native') { return `/api/skills-studio/v2/evals/${enc(id)}/export${query({ format })}`; }
743
+ // Codex is the owner's opt-in per skill version (its shell can read files anywhere on the computer): whether it is
744
+ // confirmed for versions, and the owner's confirmation (or its withdrawal).
745
+ export function fetchSkillEvalCodexTrust(revisionIds) { return skillsV2(`/evals/codex-trust${query({ revisionIds: (revisionIds || []).join(',') })}`); }
746
+ export function setSkillEvalCodexTrust(revisionIds, trusted = true) { return skillsV2('/evals/codex-trust', { method: 'POST', body: { revisionIds, trusted } }); }
747
+
748
+ // ─── MCP Key ─────────────────────────────
749
+
750
+ export async function fetchMcpKey() {
751
+ return jsonFetch('/api/mcp-key');
752
+ }
753
+
754
+ export async function generateMcpKey() {
755
+ return jsonFetch('/api/mcp-key', { method: 'POST' });
756
+ }
757
+
758
+ export async function revokeMcpKey() {
759
+ return jsonFetch('/api/mcp-key', { method: 'DELETE' });
760
+ }
761
+
762
+ // ─── Tunnel ──────────────────────────────
763
+
764
+ export async function fetchTunnelStatus() {
765
+ return jsonFetch('/api/tunnel/status');
766
+ }
767
+
768
+ export async function startTunnel() {
769
+ return jsonFetch('/api/tunnel/start', { method: 'POST' });
770
+ }
771
+
772
+ export async function stopTunnel() {
773
+ return jsonFetch('/api/tunnel/stop', { method: 'POST' });
774
+ }
775
+
776
+ // ─── Last Session (resume after restart) ──
777
+
778
+ export async function fetchLastSession() {
779
+ const r = await fetch('/api/last-session');
780
+ if (!r.ok) return null;
781
+ return r.json();
782
+ }
783
+
784
+ export async function dismissLastSession() {
785
+ return fetch('/api/last-session', { method: 'DELETE' });
786
+ }
787
+
788
+ // ─── Terminal ────────────────────────────
789
+
790
+ export async function fetchTerminalSessions() {
791
+ return jsonFetch('/api/terminal/sessions');
792
+ }
793
+
794
+ export async function createTerminalSession(profile, cols, rows, cwd, opts = {}) {
795
+ return jsonFetch('/api/terminal/sessions', {
796
+ method: 'POST',
797
+ ...jsonBody({ profile, cols, rows, cwd, ...opts }),
798
+ });
799
+ }
800
+
801
+ export async function fetchProjects() {
802
+ return jsonFetch('/api/projects');
803
+ }
804
+
805
+ // ─── Style Guide v2 ────────────────────────────
806
+ const sgQuery = values => new URLSearchParams(Object.entries(values).filter(([,v]) => v !== undefined && v !== null)).toString();
807
+ const sgPost = (path, body) => jsonFetch(path, { method: 'POST', ...jsonBody(body) });
808
+ export const fetchStyleGuide = projectPath => jsonFetch(`/api/style-guide?${sgQuery({projectPath})}`);
809
+ export const saveStyleGuide = (projectPath, config, {source = 'ui', label} = {}) => jsonFetch('/api/style-guide', { method: 'PUT', ...jsonBody({projectPath, config, source, label}) });
810
+ export const fetchStyleGuideProjects = () => jsonFetch('/api/style-guide/projects');
811
+ export const fetchStyleGuideDefaults = projectPath => jsonFetch(`/api/style-guide/defaults?${sgQuery({projectPath})}`);
812
+ export const fetchStyleGuideSummary = (projectPath, taskClass) => jsonFetch(`/api/style-guide/summary?${sgQuery({projectPath, taskClass})}`);
813
+ export const fetchStyleGuideFonts = q => jsonFetch(`/api/style-guide/fonts?${sgQuery({q})}`);
814
+ export const fetchStyleGuidePresets = () => jsonFetch('/api/style-guide/presets');
815
+ export const applyStyleGuidePreset = (projectPath, presetId, merge) => sgPost('/api/style-guide/presets/apply', {projectPath, presetId, merge});
816
+ export const importStyleGuide = (projectPath, options) => sgPost('/api/style-guide/import', {projectPath, ...options});
817
+ export const writeStyleGuideExports = (projectPath, formats) => sgPost('/api/style-guide/export', {projectPath, formats});
818
+ export const fetchStyleGuideHistory = projectPath => jsonFetch(`/api/style-guide/history?${sgQuery({projectPath})}`);
819
+ export const restoreStyleGuideRevision = (projectPath, revision) => sgPost('/api/style-guide/history/restore', {projectPath, revision});
820
+ export const fetchStyleGuideProposals = (projectPath, status) => jsonFetch(`/api/style-guide/proposals?${sgQuery({projectPath, status})}`);
821
+ export const proposeStyleGuide = (projectPath, options) => sgPost('/api/style-guide/proposals', {projectPath, ...options});
822
+ export const acceptStyleGuideProposal = (projectPath, id) => sgPost(`/api/style-guide/proposals/${encodeURIComponent(id)}/accept`, {projectPath});
823
+ export const rejectStyleGuideProposal = (projectPath, id) => sgPost(`/api/style-guide/proposals/${encodeURIComponent(id)}/reject`, {projectPath});
824
+ export const styleGuideContrast = options => sgPost('/api/style-guide/contrast', options);
825
+ export const styleGuideScale = (base, options = {}) => sgPost('/api/style-guide/scale', {base, ...options});
826
+ export const styleGuideDarkSemantic = (light, neutral) => sgPost('/api/style-guide/dark-semantic', {light, neutral});
827
+ export const setStyleGuidePointers = (projectPath, enabled) => sgPost('/api/style-guide/pointers', {projectPath, enabled});
828
+ export async function fetchStyleGuidePreviewMd(projectPath) {
829
+ const res = await fetch(`/api/style-guide/preview-md?${sgQuery({projectPath})}`);
830
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
831
+ return res.text();
832
+ }
833
+ export async function fetchStyleGuideExport(projectPath, format, theme) {
834
+ const data = await jsonFetch(`/api/style-guide/export?${sgQuery({projectPath, format, theme, as:'json'})}`);
835
+ return data.text;
836
+ }
837
+ export async function uploadStyleGuideLogo(projectPath, file, {variantId, name, bg, kind} = {}) {
838
+ const res = await fetch(`/api/style-guide/logo?${sgQuery({projectPath, variantId, name, bg, kind})}`, {method:'POST', headers:{'Content-Type':file.type || 'image/png'}, body:file});
839
+ if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `HTTP ${res.status}`); }
840
+ return res.json();
841
+ }
842
+ export const deleteStyleGuideLogo = (projectPath, variantId) => jsonFetch(`/api/style-guide/logo/${encodeURIComponent(variantId)}?${sgQuery({projectPath})}`, {method:'DELETE'});
843
+ export function styleGuideAssetUrl(assetsHash, file) {
844
+ return assetsHash && file ? `/api/style-guide/assets/${encodeURIComponent(assetsHash)}/${encodeURIComponent(file)}` : '';
845
+ }
846
+
847
+ // ─── Claude Code Sessions (Resume) ─────
848
+
849
+ export async function fetchClaudeSessions({ project, limit, offset, search, refresh, account } = {}) {
850
+ const params = new URLSearchParams();
851
+ if (project) params.set('project', project);
852
+ if (limit) params.set('limit', String(limit));
853
+ if (offset) params.set('offset', String(offset));
854
+ if (search) params.set('search', search);
855
+ if (refresh) params.set('refresh', 'true');
856
+ // A named Claude account: the session is looked up (by its id, in `search`) in that account's catalogue.
857
+ if (account && account !== 'default') params.set('account', account);
858
+ return jsonFetch(`/api/claude-code/sessions?${params}`);
859
+ }
860
+
861
+ // ─── Codex CLI Sessions (Resume) ─────
862
+
863
+ export async function fetchCodexSessions({ project, limit, offset, search, refresh } = {}) {
864
+ const params = new URLSearchParams();
865
+ if (project) params.set('project', project);
866
+ if (limit) params.set('limit', String(limit));
867
+ if (offset) params.set('offset', String(offset));
868
+ if (search) params.set('search', search);
869
+ if (refresh) params.set('refresh', 'true');
870
+ return jsonFetch(`/api/codex/sessions?${params}`);
871
+ }
872
+
873
+ // ─── OpenCode Sessions (Resume) ─────
874
+
875
+ export async function fetchOpencodeSessions({ project, limit, offset, search } = {}) {
876
+ const params = new URLSearchParams();
877
+ if (project) params.set('project', project);
878
+ if (limit) params.set('limit', String(limit));
879
+ if (offset) params.set('offset', String(offset));
880
+ if (search) params.set('search', search);
881
+ return jsonFetch(`/api/opencode/sessions?${params}`);
882
+ }
883
+
884
+ // ─── Gemini CLI Sessions (Resume) ─────
885
+
886
+ export async function fetchGeminiSessions({ project, limit, offset, search } = {}) {
887
+ const params = new URLSearchParams();
888
+ if (project) params.set('project', project);
889
+ if (limit) params.set('limit', String(limit));
890
+ if (offset) params.set('offset', String(offset));
891
+ if (search) params.set('search', search);
892
+ return jsonFetch(`/api/gemini/sessions?${params}`);
893
+ }
894
+
895
+ // ─── Hybrid session content search (FTS5 + semantic for Claude) ─────
896
+
897
+ export async function searchSessions({ q, provider, project, limit } = {}) {
898
+ const params = new URLSearchParams();
899
+ if (q) params.set('q', q);
900
+ if (provider) params.set('provider', provider);
901
+ if (project) params.set('project', project);
902
+ if (limit) params.set('limit', String(limit));
903
+ return jsonFetch(`/api/session-search?${params}`);
904
+ }
905
+
906
+ // ─── Session Indexing ─────
907
+
908
+ export async function startSessionIndexing({ project, reindex, sessionIds } = {}) {
909
+ return jsonFetch('/api/session-indexing/start', {
910
+ method: 'POST',
911
+ headers: { 'Content-Type': 'application/json' },
912
+ body: JSON.stringify({ project, reindex, sessionIds }),
913
+ });
914
+ }
915
+
916
+ export async function cancelSessionIndexing() {
917
+ return jsonFetch('/api/session-indexing/cancel', {
918
+ method: 'POST',
919
+ headers: { 'Content-Type': 'application/json' },
920
+ body: JSON.stringify({}),
921
+ });
922
+ }
923
+
924
+ export async function fetchIndexingStatus() {
925
+ return jsonFetch('/api/session-indexing/status');
926
+ }
927
+
928
+ export async function deleteTerminalSession(sessionId) {
929
+ return jsonFetch(`/api/terminal/sessions/${encodeURIComponent(sessionId)}`, {
930
+ method: 'DELETE',
931
+ });
932
+ }
933
+
934
+ export async function fetchTerminalFiles(dirPath, search) {
935
+ let url = `/api/terminal/files?path=${encodeURIComponent(dirPath)}`;
936
+ if (search) url += `&search=${encodeURIComponent(search)}`;
937
+ return jsonFetch(url);
938
+ }
939
+
940
+ export async function fetchTerminalBranches(dirPath) {
941
+ return jsonFetch(`/api/terminal/branches?path=${encodeURIComponent(dirPath)}`);
942
+ }
943
+
944
+ export async function checkoutTerminalBranch(dirPath, branch) {
945
+ return jsonFetch('/api/terminal/checkout', {
946
+ method: 'POST',
947
+ ...jsonBody({ path: dirPath, branch }),
948
+ });
949
+ }
950
+
951
+ // ─── Terminal Links ──────────────────────
952
+
953
+ export async function fetchTerminalLinks() {
954
+ return jsonFetch('/api/terminal/links');
955
+ }
956
+
957
+ export async function fetchTerminalLink(linkId) {
958
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`);
959
+ }
960
+
961
+ export async function createTerminalLink(sessions, config = {}) {
962
+ return jsonFetch('/api/terminal/links', {
963
+ method: 'POST',
964
+ ...jsonBody({ sessions, ...config }),
965
+ });
966
+ }
967
+
968
+ export async function deleteTerminalLink(linkId) {
969
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`, {
970
+ method: 'DELETE',
971
+ });
972
+ }
973
+
974
+ export async function detectClaudeSession(terminalSessionId) {
975
+ return jsonFetch(`/api/terminal/sessions/${encodeURIComponent(terminalSessionId)}/claude-session`);
976
+ }
977
+
978
+ export async function updateTerminalLink(linkId, config) {
979
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}`, {
980
+ method: 'PATCH',
981
+ ...jsonBody(config),
982
+ });
983
+ }
984
+
985
+ export async function sendLinkMessage(linkId, message, targetIdx) {
986
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/send`, {
987
+ method: 'POST',
988
+ ...jsonBody({ message, targetIdx }),
989
+ });
990
+ }
991
+
992
+ export async function pauseLink(linkId) {
993
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/pause`, {
994
+ method: 'POST',
995
+ });
996
+ }
997
+
998
+ export async function resumeLink(linkId) {
999
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/resume`, {
1000
+ method: 'POST',
1001
+ });
1002
+ }
1003
+
1004
+ export async function nudgeLink(linkId) {
1005
+ return jsonFetch(`/api/terminal/links/${encodeURIComponent(linkId)}/nudge`, {
1006
+ method: 'POST',
1007
+ });
1008
+ }
1009
+
1010
+ // ─── Browser Sessions ────────────────────
1011
+
1012
+ export async function fetchBrowserSessions() {
1013
+ return jsonFetch('/api/browser/sessions');
1014
+ }
1015
+
1016
+ export async function createBrowserSession(url, width, height, fingerprint, opts = {}) {
1017
+ return jsonFetch('/api/browser/sessions', {
1018
+ method: 'POST',
1019
+ ...jsonBody({ url, width, height, ...fingerprint, ...opts }),
1020
+ });
1021
+ }
1022
+
1023
+ export async function deleteBrowserSession(sessionId) {
1024
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}`, {
1025
+ method: 'DELETE',
1026
+ });
1027
+ }
1028
+
1029
+ export async function navigateBrowser(sessionId, url) {
1030
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/navigate`, {
1031
+ method: 'POST',
1032
+ ...jsonBody({ url }),
1033
+ });
1034
+ }
1035
+
1036
+ export async function browserBack(sessionId) {
1037
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/back`, {
1038
+ method: 'POST',
1039
+ ...jsonBody({}),
1040
+ });
1041
+ }
1042
+
1043
+ export async function browserForward(sessionId) {
1044
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/forward`, {
1045
+ method: 'POST',
1046
+ ...jsonBody({}),
1047
+ });
1048
+ }
1049
+
1050
+ export async function browserReload(sessionId) {
1051
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/reload`, {
1052
+ method: 'POST',
1053
+ ...jsonBody({}),
1054
+ });
1055
+ }
1056
+
1057
+ export async function fetchBrowserCdp(sessionId) {
1058
+ return jsonFetch(`/api/browser/sessions/${encodeURIComponent(sessionId)}/cdp`);
1059
+ }
1060
+
1061
+ // ─── Setup (Onboarding) ─────────────────
1062
+
1063
+ export async function checkSetupDeps() {
1064
+ return jsonFetch('/api/setup/check-deps');
1065
+ }
1066
+
1067
+ export async function fetchSetupStatus() {
1068
+ return jsonFetch('/api/setup/onboarding');
1069
+ }
1070
+
1071
+ export async function saveSetupConfig(payload) {
1072
+ return jsonFetch('/api/setup/save-config', {
1073
+ method: 'POST',
1074
+ ...jsonBody(payload),
1075
+ });
1076
+ }
1077
+
1078
+ export async function setupCreateCollection(payload) {
1079
+ return jsonFetch('/api/setup/create-collection', {
1080
+ method: 'POST',
1081
+ ...jsonBody(payload),
1082
+ });
1083
+ }
1084
+
1085
+ export async function setupBuild() {
1086
+ return jsonFetch('/api/setup/build', { method: 'POST' });
1087
+ }
1088
+
1089
+ export async function testDatabase() {
1090
+ return jsonFetch('/api/setup/test-qdrant');
1091
+ }
1092
+
1093
+ export async function testDatabaseRemote(payload) {
1094
+ return jsonFetch('/api/setup/test-qdrant-cloud', {
1095
+ method: 'POST',
1096
+ ...jsonBody(payload),
1097
+ });
1098
+ }
1099
+
1100
+ export async function writeMcpJson(payload) {
1101
+ return jsonFetch('/api/setup/write-mcp-json', {
1102
+ method: 'POST',
1103
+ ...jsonBody(payload),
1104
+ });
1105
+ }
1106
+
1107
+ export async function completeSetup() {
1108
+ return jsonFetch('/api/setup/complete', { method: 'POST' });
1109
+ }
1110
+
1111
+ // ─── SynaBun rules (lib/rulesets/api.js) ──
1112
+ // A server that has not been restarted since the update answers 404 here.
1113
+ // The status read returns null for that; the actions throw, and callers show
1114
+ // the copy-only fallback or a "Restart SynaBun to finish this update" hint.
1115
+ // An action the files said no to is a 409 and throws too, with the server's
1116
+ // own sentence as the message: an edited copy on install, damaged markers, a
1117
+ // symlink, a file that changed while it was being written (code
1118
+ // CHANGED_UNDERNEATH) or another SynaBun process on the same data home that
1119
+ // was still at work when the wait ran out (code BUSY, `retryable: true`).
1120
+ // The last two mean "try again": nothing was changed. A 500 with code
1121
+ // LOCK_UNAVAILABLE means the server could not use its own lock file.
1122
+
1123
+ /**
1124
+ * {ok, version, autoUpdate, hosts:{<host>:{carrier, path, state, installedVersion, managed, error, …}}, legacy, notice},
1125
+ * or null when the route is missing, refuses or fails. A host may also carry
1126
+ * `replaceable: false` (state `modified`, but the file has no SynaBun markers:
1127
+ * Replace and a forced Remove are refused), `partialRemoval: {at, error, paths}`
1128
+ * (the last removal could not take these files out) and, for OpenCode,
1129
+ * `entry: 'user'` (a line in config.json that SynaBun did not add lists the
1130
+ * file: Remove keeps both unless it is sent with force). State `newer` is an
1131
+ * untouched copy a newer SynaBun wrote: it is never updated from here. Unlike the calls below
1132
+ * it never throws and never raises the guest "forbidden" toast: badges and
1133
+ * reminders read it in the background on every page load.
1134
+ */
1135
+ export async function fetchRulesStatus() {
1136
+ try {
1137
+ const res = await fetch('/api/setup/rules');
1138
+ if (!res.ok) return null;
1139
+ const data = await res.json();
1140
+ return data?.ok ? data : null;
1141
+ } catch {
1142
+ return null;
1143
+ }
1144
+ }
1145
+
1146
+ /** The rendered text for a host (claude, codex, opencode, gemini, cursor) or the copy-only `coexistence` snippet. */
1147
+ export async function fetchRulesText(host) {
1148
+ return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}/text`);
1149
+ }
1150
+
1151
+ /**
1152
+ * Install or update a host's rules. `force` replaces one well-formed copy the
1153
+ * user edited (it is backed up first); damaged or duplicate markers are
1154
+ * refused either way.
1155
+ */
1156
+ export async function installRules(host, { force = false } = {}) {
1157
+ return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}`, {
1158
+ method: 'POST',
1159
+ ...jsonBody({ force }),
1160
+ });
1161
+ }
1162
+
1163
+ /**
1164
+ * Remove a host's rules. A copy the user edited stays in place and is listed
1165
+ * in the result's `kept` (`ok` is still true); `force` removes it after a
1166
+ * backup. Throws (409) for what is refused either way: damaged markers, a
1167
+ * symlink, a config that cannot be rewritten.
1168
+ */
1169
+ export async function removeRules(host, { force = false } = {}) {
1170
+ return jsonFetch(`/api/setup/rules/${encodeURIComponent(host)}`, { method: 'DELETE', ...jsonBody({ force }) });
1171
+ }
1172
+
1173
+ /** Install for every connected tool, or only for `hosts` when given. */
1174
+ export async function installAllRules(hosts = null) {
1175
+ return jsonFetch('/api/setup/rules/install-all', {
1176
+ method: 'POST',
1177
+ ...jsonBody(Array.isArray(hosts) && hosts.length ? { hosts } : {}),
1178
+ });
1179
+ }
1180
+
1181
+ export async function setRulesAutoUpdate(autoUpdate) {
1182
+ return jsonFetch('/api/setup/rules/settings', {
1183
+ method: 'PUT',
1184
+ ...jsonBody({ autoUpdate: !!autoUpdate }),
1185
+ });
1186
+ }
1187
+
1188
+ /** Take a hash-exact pasted copy out of one of the files the status lists under `legacy`. */
1189
+ export async function removeLegacyRules(path) {
1190
+ return jsonFetch('/api/setup/rules/legacy/remove', {
1191
+ method: 'POST',
1192
+ ...jsonBody({ path }),
1193
+ });
1194
+ }
1195
+
1196
+ export async function ackRulesNotice() {
1197
+ return jsonFetch('/api/setup/rules/notice/ack', { method: 'POST' });
1198
+ }
1199
+
1200
+ /**
1201
+ * The text to show or copy for a host, from the rules route and, when that is
1202
+ * not there, from the older read-only endpoint. Throws when neither answers.
1203
+ */
1204
+ export async function fetchRulesTextWithFallback(host) {
1205
+ try {
1206
+ const data = await fetchRulesText(host);
1207
+ if (data?.text) return data.text;
1208
+ } catch { /* older server: try the endpoint it does have */ }
1209
+ const format = host === 'opencode' ? 'generic' : host;
1210
+ const data = await jsonFetch(`/api/claude-code/ruleset?format=${encodeURIComponent(format)}`);
1211
+ if (!data?.ruleset) throw new Error('Rules text not available');
1212
+ return data.ruleset;
1213
+ }
1214
+
1215
+ // ─── Invite / Session Sharing ─────────────
1216
+
1217
+ export async function fetchInviteStatus() {
1218
+ return jsonFetch('/api/invite/status');
1219
+ }
1220
+
1221
+ export async function generateInviteKey(custom) {
1222
+ const body = custom ? { custom } : {};
1223
+ return jsonFetch('/api/invite/key', {
1224
+ method: 'POST',
1225
+ ...jsonBody(body),
1226
+ });
1227
+ }
1228
+
1229
+ export async function revokeInviteKey() {
1230
+ return jsonFetch('/api/invite/key', { method: 'DELETE' });
1231
+ }
1232
+
1233
+ export async function revokeAllInviteSessions() {
1234
+ return jsonFetch('/api/invite/sessions', { method: 'DELETE' });
1235
+ }
1236
+
1237
+ export async function saveInviteProxy(config) {
1238
+ return jsonFetch('/api/invite/proxy', {
1239
+ method: 'PUT',
1240
+ ...jsonBody(config),
1241
+ });
1242
+ }
1243
+
1244
+ export async function fetchInvitePermissions() {
1245
+ return jsonFetch('/api/invite/permissions');
1246
+ }
1247
+
1248
+ export async function saveInvitePermissions(perms) {
1249
+ return jsonFetch('/api/invite/permissions', {
1250
+ method: 'PUT',
1251
+ ...jsonBody(perms),
1252
+ });
1253
+ }
1254
+
1255
+ // ── Loop Templates ──
1256
+
1257
+ export async function fetchLoopTemplates() {
1258
+ return jsonFetch('/api/loop/templates');
1259
+ }
1260
+
1261
+ export async function createLoopTemplate(payload) {
1262
+ return jsonFetch('/api/loop/templates', {
1263
+ method: 'POST',
1264
+ ...jsonBody(payload),
1265
+ });
1266
+ }
1267
+
1268
+ export async function updateLoopTemplate(id, payload) {
1269
+ return jsonFetch(`/api/loop/templates/${id}`, {
1270
+ method: 'PUT',
1271
+ ...jsonBody(payload),
1272
+ });
1273
+ }
1274
+
1275
+ export async function deleteLoopTemplate(id) {
1276
+ return jsonFetch(`/api/loop/templates/${id}`, { method: 'DELETE' });
1277
+ }
1278
+
1279
+ export async function importLoopTemplates(data) {
1280
+ return jsonFetch('/api/loop/templates/import', {
1281
+ method: 'POST',
1282
+ ...jsonBody(data),
1283
+ });
1284
+ }
1285
+
1286
+ // ── Loop Folders ──
1287
+
1288
+ export async function fetchLoopFolders() {
1289
+ return jsonFetch('/api/loop/folders');
1290
+ }
1291
+
1292
+ export async function createLoopFolder(payload) {
1293
+ return jsonFetch('/api/loop/folders', {
1294
+ method: 'POST',
1295
+ ...jsonBody(payload),
1296
+ });
1297
+ }
1298
+
1299
+ export async function updateLoopFolder(id, payload) {
1300
+ return jsonFetch(`/api/loop/folders/${id}`, {
1301
+ method: 'PUT',
1302
+ ...jsonBody(payload),
1303
+ });
1304
+ }
1305
+
1306
+ export async function deleteLoopFolder(id) {
1307
+ return jsonFetch(`/api/loop/folders/${id}`, { method: 'DELETE' });
1308
+ }
1309
+
1310
+ export async function reorderLoopFolders(orderedIds) {
1311
+ return jsonFetch('/api/loop/folders/reorder', {
1312
+ method: 'POST',
1313
+ ...jsonBody({ orderedIds }),
1314
+ });
1315
+ }
1316
+
1317
+ export async function fetchActiveLoop() {
1318
+ return jsonFetch('/api/loop/active');
1319
+ }
1320
+
1321
+ export async function launchLoop(params) {
1322
+ return jsonFetch('/api/loop/launch', {
1323
+ method: 'POST',
1324
+ ...jsonBody(params),
1325
+ });
1326
+ }
1327
+
1328
+ export async function fetchLoopLog(terminalSessionId) {
1329
+ const res = await fetch(`/api/loop/log/${encodeURIComponent(terminalSessionId)}`);
1330
+ if (!res.ok) {
1331
+ const text = await res.text().catch(() => '');
1332
+ throw new Error(text || `HTTP ${res.status}`);
1333
+ }
1334
+ return res.text();
1335
+ }
1336
+
1337
+ export async function sendClientLoopLog(terminalSessionId, tag, msg, meta) {
1338
+ try {
1339
+ return await jsonFetch('/api/loop/client-log', {
1340
+ method: 'POST',
1341
+ ...jsonBody({ terminalSessionId: terminalSessionId || null, tag, msg, meta }),
1342
+ });
1343
+ } catch { /* logging best-effort */ }
1344
+ }
1345
+
1346
+ export async function stopLoop(terminalSessionId) {
1347
+ return jsonFetch('/api/loop/stop', {
1348
+ method: 'POST',
1349
+ ...jsonBody(terminalSessionId ? { terminalSessionId } : {}),
1350
+ });
1351
+ }
1352
+
1353
+ export async function fetchLoopHistory(limit = 50) {
1354
+ return jsonFetch(`/api/loop/history?limit=${limit}`);
1355
+ }
1356
+
1357
+ export async function deleteLoopHistory(id) {
1358
+ return jsonFetch(`/api/loop/history/${id}`, { method: 'DELETE' });
1359
+ }
1360
+
1361
+ export async function storeLoopCompletion(data) {
1362
+ return jsonFetch('/api/loop/complete', {
1363
+ method: 'POST',
1364
+ ...jsonBody(data),
1365
+ });
1366
+ }
1367
+
1368
+ export async function searchMemoriesByCategory(query, category, limit = 15) {
1369
+ return jsonFetch('/api/search/memories', {
1370
+ method: 'POST',
1371
+ ...jsonBody({ query, category, limit }),
1372
+ });
1373
+ }
1374
+
1375
+ // ── Loop Schedules ──
1376
+
1377
+ export async function fetchScheduleGroups() {
1378
+ return jsonFetch('/api/schedule-groups');
1379
+ }
1380
+
1381
+ export async function createScheduleGroup(payload) {
1382
+ return jsonFetch('/api/schedule-groups', {
1383
+ method: 'POST',
1384
+ ...jsonBody(payload),
1385
+ });
1386
+ }
1387
+
1388
+ export async function updateScheduleGroup(id, payload) {
1389
+ return jsonFetch(`/api/schedule-groups/${encodeURIComponent(id)}`, {
1390
+ method: 'PUT',
1391
+ ...jsonBody(payload),
1392
+ });
1393
+ }
1394
+
1395
+ export async function deleteScheduleGroup(id) {
1396
+ return jsonFetch(`/api/schedule-groups/${encodeURIComponent(id)}`, {
1397
+ method: 'DELETE',
1398
+ });
1399
+ }
1400
+
1401
+ export async function reorderScheduleGroups(orderedIds) {
1402
+ return jsonFetch('/api/schedule-groups/reorder', {
1403
+ method: 'POST',
1404
+ ...jsonBody({ orderedIds }),
1405
+ });
1406
+ }
1407
+
1408
+ export async function fetchSchedules() {
1409
+ return jsonFetch('/api/schedules');
1410
+ }
1411
+
1412
+ export async function createSchedule(params) {
1413
+ return jsonFetch('/api/schedules', {
1414
+ method: 'POST',
1415
+ ...jsonBody(params),
1416
+ });
1417
+ }
1418
+
1419
+ export async function fetchSchedule(id) {
1420
+ return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`);
1421
+ }
1422
+
1423
+ export async function updateSchedule(id, params) {
1424
+ return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`, {
1425
+ method: 'PUT',
1426
+ ...jsonBody(params),
1427
+ });
1428
+ }
1429
+
1430
+ export async function deleteSchedule(id) {
1431
+ return jsonFetch(`/api/schedules/${encodeURIComponent(id)}`, {
1432
+ method: 'DELETE',
1433
+ });
1434
+ }
1435
+
1258
1436
  export async function testSchedule(id, { sidepanelWindowId, sidepanelClaimToken } = {}) {
1259
1437
  return jsonFetch(`/api/schedules/${encodeURIComponent(id)}/test`, {
1260
1438
  method: 'POST',
1261
1439
  ...jsonBody({ sidepanelWindowId, sidepanelClaimToken }),
1262
1440
  });
1263
1441
  }
1264
-
1265
- export async function startScheduleTimer(id, minutes) {
1266
- return jsonFetch(`/api/schedules/${encodeURIComponent(id)}/timer`, {
1267
- method: 'POST',
1268
- ...jsonBody({ minutes }),
1269
- });
1270
- }
1271
-
1272
- export async function cancelScheduleTimer(id) {
1273
- return jsonFetch(`/api/schedules/${encodeURIComponent(id)}/timer`, {
1274
- method: 'DELETE',
1275
- });
1276
- }
1277
-
1278
- export async function fetchScheduleTimers() {
1279
- return jsonFetch('/api/schedules/timers');
1280
- }
1281
-
1282
- // ── Quick Timers ──
1283
-
1284
- export async function createQuickTimer(templateId, minutes, { profile, model, contextMode, effort, mcpProfile, usesBrowser, codexAccountId } = {}) {
1442
+
1443
+ export async function startScheduleTimer(id, minutes) {
1444
+ return jsonFetch(`/api/schedules/${encodeURIComponent(id)}/timer`, {
1445
+ method: 'POST',
1446
+ ...jsonBody({ minutes }),
1447
+ });
1448
+ }
1449
+
1450
+ export async function cancelScheduleTimer(id) {
1451
+ return jsonFetch(`/api/schedules/${encodeURIComponent(id)}/timer`, {
1452
+ method: 'DELETE',
1453
+ });
1454
+ }
1455
+
1456
+ export async function fetchScheduleTimers() {
1457
+ return jsonFetch('/api/schedules/timers');
1458
+ }
1459
+
1460
+ // ── Quick Timers ──
1461
+
1462
+ export async function createQuickTimer(templateId, minutes, { profile, model, contextMode, effort, tools, usesBrowser, codexAccountId } = {}) {
1285
1463
  return jsonFetch('/api/quick-timer', {
1286
1464
  method: 'POST',
1287
- ...jsonBody({ templateId, minutes, profile, model, contextMode, effort, mcpProfile, usesBrowser, codexAccountId }),
1288
- });
1289
- }
1290
-
1291
- export async function fetchQuickTimers() {
1292
- return jsonFetch('/api/quick-timers');
1293
- }
1294
-
1295
- export async function cancelQuickTimer(id) {
1296
- return jsonFetch(`/api/quick-timers/${encodeURIComponent(id)}`, {
1297
- method: 'DELETE',
1298
- });
1299
- }
1300
-
1465
+ ...jsonBody({ templateId, minutes, profile, model, contextMode, effort, tools, usesBrowser, codexAccountId }),
1466
+ });
1467
+ }
1468
+
1469
+ export async function fetchQuickTimers() {
1470
+ return jsonFetch('/api/quick-timers');
1471
+ }
1472
+
1473
+ export async function cancelQuickTimer(id) {
1474
+ return jsonFetch(`/api/quick-timers/${encodeURIComponent(id)}`, {
1475
+ method: 'DELETE',
1476
+ });
1477
+ }
1478
+
1301
1479
  export async function triggerQuickTimerNow(templateId, {
1302
- profile, model, contextMode, effort, mcpProfile, usesBrowser, codexAccountId,
1480
+ profile, model, contextMode, effort, tools, usesBrowser, codexAccountId,
1303
1481
  sidepanelWindowId, sidepanelClaimToken,
1304
1482
  } = {}) {
1305
1483
  return jsonFetch('/api/quick-timer/now', {
1306
1484
  method: 'POST',
1307
1485
  ...jsonBody({
1308
- templateId, profile, model, contextMode, effort, mcpProfile, usesBrowser, codexAccountId,
1486
+ templateId, profile, model, contextMode, effort, tools, usesBrowser, codexAccountId,
1309
1487
  sidepanelWindowId, sidepanelClaimToken,
1310
1488
  }),
1311
1489
  });
1312
1490
  }
1313
-
1314
- // ── Isolated Agents ──
1315
-
1316
- export async function launchAgent(params) {
1317
- return jsonFetch('/api/agents/launch', {
1318
- method: 'POST',
1319
- ...jsonBody(params),
1320
- });
1321
- }
1322
-
1323
- export async function fetchAgents() {
1324
- return jsonFetch('/api/agents');
1325
- }
1326
-
1327
- export async function fetchAgent(id) {
1328
- return jsonFetch(`/api/agents/${id}`);
1329
- }
1330
-
1331
- export async function stopAgent(id) {
1332
- return jsonFetch(`/api/agents/${id}/stop`, { method: 'POST' });
1333
- }
1334
-
1335
- export async function removeAgent(id) {
1336
- return jsonFetch(`/api/agents/${id}`, { method: 'DELETE' });
1337
- }
1338
-
1339
- // ─── Image Gallery ──────────────────────
1340
-
1341
- export async function fetchImages() {
1342
- return jsonFetch('/api/images');
1343
- }
1344
-
1345
- export async function toggleImageFavorite(filename, favorite) {
1346
- return jsonFetch('/api/images/favorite', {
1347
- method: 'POST',
1348
- ...jsonBody({ filename, favorite }),
1349
- });
1350
- }
1351
-
1352
- export async function deleteImage(filename) {
1353
- return jsonFetch(`/api/images/${encodeURIComponent(filename)}`, { method: 'DELETE' });
1354
- }
1491
+
1492
+ // ── Isolated Agents ──
1493
+
1494
+ export async function launchAgent(params) {
1495
+ return jsonFetch('/api/agents/launch', {
1496
+ method: 'POST',
1497
+ ...jsonBody(params),
1498
+ });
1499
+ }
1500
+
1501
+ export async function fetchAgents() {
1502
+ return jsonFetch('/api/agents');
1503
+ }
1504
+
1505
+ export async function fetchAgent(id) {
1506
+ return jsonFetch(`/api/agents/${id}`);
1507
+ }
1508
+
1509
+ export async function stopAgent(id) {
1510
+ return jsonFetch(`/api/agents/${id}/stop`, { method: 'POST' });
1511
+ }
1512
+
1513
+ export async function removeAgent(id) {
1514
+ return jsonFetch(`/api/agents/${id}`, { method: 'DELETE' });
1515
+ }
1516
+
1517
+ // ─── Image Gallery ──────────────────────
1518
+
1519
+ export async function fetchImages() {
1520
+ return jsonFetch('/api/images');
1521
+ }
1522
+
1523
+ export async function toggleImageFavorite(filename, favorite) {
1524
+ return jsonFetch('/api/images/favorite', {
1525
+ method: 'POST',
1526
+ ...jsonBody({ filename, favorite }),
1527
+ });
1528
+ }
1529
+
1530
+ export async function deleteImage(filename) {
1531
+ return jsonFetch(`/api/images/${encodeURIComponent(filename)}`, { method: 'DELETE' });
1532
+ }
1533
+
1534
+ // Skills Studio P5: local operations and explicit Discover requests.
1535
+ export function fetchSkillOperations() { return skillsV2('/operations'); }
1536
+ export function configureSkillOperations(body) { return skillsV2('/operations', { method: 'PUT', body }); }
1537
+ export function fetchSkillUsage() { return skillsV2('/usage'); }
1538
+ export function clearSkillUsage() { return skillsV2('/usage', { method: 'DELETE', body: {} }); }
1539
+ export function fetchSkillHealth() { return skillsV2('/health'); }
1540
+ export function checkSkillHealth() { return skillsV2('/health/check', { method: 'POST', body: {} }); }
1541
+ export function fetchSkillDiscover() { return skillsV2('/discover'); }
1542
+ export function refreshSkillDiscover(sourceId) { return skillsV2('/discover/refresh', { method: 'POST', body: { sourceId } }); }
1543
+ export function importSkillDiscoverCatalog(sourceId, entries) { return skillsV2('/discover/catalog', { method: 'POST', body: { sourceId, entries } }); }
1544
+ export function stageSkillDiscover(itemId) { return skillsV2('/discover/stage', { method: 'POST', body: { itemId } }); }
1545
+ export function previewSkillPackage(revisionId, { format = 'bundle', excludeEvals = false } = {}) { return skillsV2(`/revisions/${encodeURIComponent(revisionId)}/package${query({ format, excludeEvals })}`); }