synabun 2.0.10 → 2.2.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 (656) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +13 -13
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +30 -17
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +7 -6
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +302 -0
  24. package/lib/virustotal.mjs +203 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  37. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  38. package/mcp-server/dist/services/budget-judgments.js +5 -3
  39. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  40. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  41. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  42. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  43. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +2 -2
  45. package/mcp-server/dist/services/identity.js +2 -2
  46. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  47. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  48. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  49. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  50. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  51. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  52. package/mcp-server/dist/services/memory-sessions.js +28 -2
  53. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  54. package/mcp-server/dist/services/neural-interface.d.ts +18 -11
  55. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  56. package/mcp-server/dist/services/neural-interface.js +67 -11
  57. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  58. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  59. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  60. package/mcp-server/dist/services/server-instructions.js +22 -63
  61. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  62. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  63. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  64. package/mcp-server/dist/services/tool-catalog.js +339 -0
  65. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  66. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  67. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  68. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  69. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  70. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  71. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  72. package/mcp-server/dist/services/tool-list.js +334 -0
  73. package/mcp-server/dist/services/tool-list.js.map +1 -0
  74. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  75. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  76. package/mcp-server/dist/services/tool-runtime.js +400 -0
  77. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  78. package/mcp-server/dist/services/typesafe-config.js +1 -1
  79. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  80. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  81. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  82. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  83. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  84. package/mcp-server/dist/tools/agents.d.ts +14 -14
  85. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  86. package/mcp-server/dist/tools/agents.js +29 -19
  87. package/mcp-server/dist/tools/agents.js.map +1 -1
  88. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  89. package/mcp-server/dist/tools/choice.d.ts +6 -0
  90. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/choice.js +18 -13
  92. package/mcp-server/dist/tools/choice.js.map +1 -1
  93. package/mcp-server/dist/tools/git-tools.js +2 -2
  94. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  95. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  96. package/mcp-server/dist/tools/image-tools.js +1 -1
  97. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  98. package/mcp-server/dist/tools/morelogin.js +9 -6
  99. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  100. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  101. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  102. package/mcp-server/dist/tools/skill-tools.js +642 -0
  103. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  104. package/mcp-server/dist/tools/skill.d.ts +7 -0
  105. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  106. package/mcp-server/dist/tools/skill.js +11 -0
  107. package/mcp-server/dist/tools/skill.js.map +1 -0
  108. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  109. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  110. package/mcp-server/dist/tools/tool-groups.js +147 -0
  111. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  112. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  113. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  114. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  115. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  116. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  117. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  118. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  119. package/mcp-server/package-lock.json +2 -2
  120. package/mcp-server/package.json +1 -1
  121. package/mcp-server/src/http.ts +75 -12
  122. package/mcp-server/src/index.ts +147 -154
  123. package/mcp-server/src/preload.ts +12 -12
  124. package/mcp-server/src/services/budget-judgments.ts +5 -3
  125. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  126. package/mcp-server/src/services/identity.ts +2 -2
  127. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  128. package/mcp-server/src/services/memory-sessions.ts +24 -3
  129. package/mcp-server/src/services/neural-interface.ts +81 -21
  130. package/mcp-server/src/services/server-instructions.ts +29 -64
  131. package/mcp-server/src/services/tool-catalog.ts +403 -0
  132. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  133. package/mcp-server/src/services/tool-list.ts +410 -0
  134. package/mcp-server/src/services/tool-runtime.ts +457 -0
  135. package/mcp-server/src/services/typesafe-config.ts +1 -1
  136. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  137. package/mcp-server/src/tools/agents.ts +29 -20
  138. package/mcp-server/src/tools/choice.ts +17 -12
  139. package/mcp-server/src/tools/git-tools.ts +2 -2
  140. package/mcp-server/src/tools/image-tools.ts +1 -1
  141. package/mcp-server/src/tools/morelogin.ts +6 -4
  142. package/mcp-server/src/tools/skill-tools.ts +592 -0
  143. package/mcp-server/src/tools/skill.ts +13 -0
  144. package/mcp-server/src/tools/tool-groups.ts +164 -0
  145. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  146. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  147. package/neural-interface/i18n/en.json +3953 -228
  148. package/neural-interface/i18n/pt-BR.json +3901 -179
  149. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  150. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  151. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  152. package/neural-interface/lib/antigravity/bypass.js +100 -0
  153. package/neural-interface/lib/antigravity/catalog.js +273 -0
  154. package/neural-interface/lib/antigravity/home.js +260 -0
  155. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  156. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  157. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  158. package/neural-interface/lib/antigravity/policy.js +74 -0
  159. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  160. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  161. package/neural-interface/lib/antigravity/usage.js +43 -0
  162. package/neural-interface/lib/assistant-api.js +128 -28
  163. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  164. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  165. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  166. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  167. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  168. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  169. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  170. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  171. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  172. package/neural-interface/lib/assistant-budget-split.js +42 -15
  173. package/neural-interface/lib/assistant-catalog.js +81 -15
  174. package/neural-interface/lib/assistant-config.js +6 -3
  175. package/neural-interface/lib/assistant-dispatch.js +1073 -200
  176. package/neural-interface/lib/assistant-envelope.js +156 -2
  177. package/neural-interface/lib/assistant-evidence.js +80 -0
  178. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  179. package/neural-interface/lib/assistant-persona.js +93 -19
  180. package/neural-interface/lib/assistant-plan-permissions.js +25 -9
  181. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  182. package/neural-interface/lib/assistant-route-gate.js +6 -4
  183. package/neural-interface/lib/assistant-router.js +245 -38
  184. package/neural-interface/lib/assistant-runtime.js +214 -30
  185. package/neural-interface/lib/assistant-task-prompt.js +87 -21
  186. package/neural-interface/lib/assistant-tools.js +51 -0
  187. package/neural-interface/lib/assistant-usage.js +29 -0
  188. package/neural-interface/lib/backup-service.js +626 -4
  189. package/neural-interface/lib/backup-zip-reader.js +10 -5
  190. package/neural-interface/lib/backup-zip-writer.js +257 -0
  191. package/neural-interface/lib/browser-execution.js +10 -2
  192. package/neural-interface/lib/browser-guard-cdp.js +84 -0
  193. package/neural-interface/lib/browser-network-guard.js +476 -0
  194. package/neural-interface/lib/browser-policy-roots.js +64 -0
  195. package/neural-interface/lib/browser-tool-policy.js +198 -38
  196. package/neural-interface/lib/claude-agent-bridge.js +11 -2
  197. package/neural-interface/lib/claude-history-cache.js +210 -0
  198. package/neural-interface/lib/claude-hooks-status.js +103 -0
  199. package/neural-interface/lib/claude-model-catalog.js +5 -1
  200. package/neural-interface/lib/claude-temporary.js +5 -11
  201. package/neural-interface/lib/cli-install-api.js +1 -1
  202. package/neural-interface/lib/cli-install.js +163 -154
  203. package/neural-interface/lib/cli-resolver.js +13 -3
  204. package/neural-interface/lib/cli-update-planner.js +52 -41
  205. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  206. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  207. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  208. package/neural-interface/lib/codex-worker-app-server.js +191 -0
  209. package/neural-interface/lib/confined-fs.js +30 -3
  210. package/neural-interface/lib/crash-notice-api.js +98 -0
  211. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  212. package/neural-interface/lib/editor-git-api.js +3 -20
  213. package/neural-interface/lib/exec-loop-command.js +13 -10
  214. package/neural-interface/lib/file-explorer-api.js +74 -40
  215. package/neural-interface/lib/file-explorer-watch.js +9 -3
  216. package/neural-interface/lib/git-bash/install.js +52 -6
  217. package/neural-interface/lib/git-bash/manager.js +59 -17
  218. package/neural-interface/lib/git-bash/pins.js +12 -0
  219. package/neural-interface/lib/git-read.js +2 -2
  220. package/neural-interface/lib/git-status-runner.js +390 -0
  221. package/neural-interface/lib/git-status-watchdog.js +40 -0
  222. package/neural-interface/lib/greeting/budgets.js +1 -0
  223. package/neural-interface/lib/greeting/engine.js +2 -2
  224. package/neural-interface/lib/greeting/sources.js +10 -12
  225. package/neural-interface/lib/greeting/writer.js +1 -1
  226. package/neural-interface/lib/http-guards.js +8 -2
  227. package/neural-interface/lib/install-details.js +4 -1
  228. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  229. package/neural-interface/lib/mcp-client-config.js +35 -33
  230. package/neural-interface/lib/mcp-external-servers.js +332 -0
  231. package/neural-interface/lib/mcp-installer.js +2 -1
  232. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  233. package/neural-interface/lib/memory-session-capture.js +17 -3
  234. package/neural-interface/lib/morelogin.js +177 -25
  235. package/neural-interface/lib/native-loop-providers.js +965 -66
  236. package/neural-interface/lib/native-loop-runtime.js +290 -42
  237. package/neural-interface/lib/opencode-config.js +557 -0
  238. package/neural-interface/lib/opencode-db-schema.js +17 -34
  239. package/neural-interface/lib/opencode-history.js +48 -218
  240. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  241. package/neural-interface/lib/opencode-native-boundary.js +53 -0
  242. package/neural-interface/lib/opencode-protocol.js +249 -112
  243. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  244. package/neural-interface/lib/opencode-serve-records.js +39 -21
  245. package/neural-interface/lib/opencode-v2-client.js +48 -153
  246. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  247. package/neural-interface/lib/opencode-worker-data.js +27 -0
  248. package/neural-interface/lib/opencode2-client.js +73 -37
  249. package/neural-interface/lib/opencode2-proxy.js +23 -63
  250. package/neural-interface/lib/opencode2-translate.js +51 -2
  251. package/neural-interface/lib/owner-authority.js +249 -0
  252. package/neural-interface/lib/page-session.js +275 -0
  253. package/neural-interface/lib/path-confine.js +12 -9
  254. package/neural-interface/lib/peer-identity.js +793 -0
  255. package/neural-interface/lib/pty-host/core.js +28 -1
  256. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  257. package/neural-interface/lib/pty-host/manager.js +12 -1
  258. package/neural-interface/lib/remote-policy.js +1 -1
  259. package/neural-interface/lib/request-gate.js +20 -2
  260. package/neural-interface/lib/rulesets/api.js +7 -6
  261. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  262. package/neural-interface/lib/rulesets/installer.js +180 -36
  263. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  264. package/neural-interface/lib/rulesets/render.js +1 -1
  265. package/neural-interface/lib/session-cache-builder.js +81 -31
  266. package/neural-interface/lib/session-title-generator.js +58 -36
  267. package/neural-interface/lib/skills/api-v2.js +495 -0
  268. package/neural-interface/lib/skills/bundle.js +418 -0
  269. package/neural-interface/lib/skills/catalog.js +298 -0
  270. package/neural-interface/lib/skills/creation-api.js +191 -0
  271. package/neural-interface/lib/skills/creation-brief.js +247 -0
  272. package/neural-interface/lib/skills/creation-sources.js +285 -0
  273. package/neural-interface/lib/skills/creation.js +843 -0
  274. package/neural-interface/lib/skills/deploy.js +569 -0
  275. package/neural-interface/lib/skills/digest.js +119 -0
  276. package/neural-interface/lib/skills/discover.js +111 -0
  277. package/neural-interface/lib/skills/draft-confinement.js +216 -0
  278. package/neural-interface/lib/skills/eval-confinement.js +399 -0
  279. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  280. package/neural-interface/lib/skills/eval-trace.js +135 -0
  281. package/neural-interface/lib/skills/evals-api.js +212 -0
  282. package/neural-interface/lib/skills/evals.js +1573 -0
  283. package/neural-interface/lib/skills/export.js +203 -0
  284. package/neural-interface/lib/skills/frontmatter.js +64 -0
  285. package/neural-interface/lib/skills/health.js +75 -0
  286. package/neural-interface/lib/skills/json-edit.js +165 -0
  287. package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
  288. package/neural-interface/lib/skills/known-bridges.js +96 -0
  289. package/neural-interface/lib/skills/library.js +823 -0
  290. package/neural-interface/lib/skills/local-store.js +21 -0
  291. package/neural-interface/lib/skills/migration.js +433 -0
  292. package/neural-interface/lib/skills/operations.js +45 -0
  293. package/neural-interface/lib/skills/ownership.js +427 -0
  294. package/neural-interface/lib/skills/packages.js +88 -0
  295. package/neural-interface/lib/skills/reviews.js +212 -0
  296. package/neural-interface/lib/skills/revision.js +80 -0
  297. package/neural-interface/lib/skills/roots.js +269 -0
  298. package/neural-interface/lib/skills/runtime-checks.js +358 -0
  299. package/neural-interface/lib/skills/scan-assets.js +252 -0
  300. package/neural-interface/lib/skills/scan-context.js +26 -0
  301. package/neural-interface/lib/skills/scan-shell.js +324 -0
  302. package/neural-interface/lib/skills/scan.js +364 -0
  303. package/neural-interface/lib/skills/scanners.js +54 -0
  304. package/neural-interface/lib/skills/service.js +530 -0
  305. package/neural-interface/lib/skills/telemetry.js +119 -0
  306. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  307. package/neural-interface/lib/synabun-version.js +1 -1
  308. package/neural-interface/lib/system-activity.js +1 -1
  309. package/neural-interface/lib/tool-lists.js +124 -0
  310. package/neural-interface/lib/whatsapp/brain.js +7 -3
  311. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  312. package/neural-interface/lib/whatsapp/cards.js +22 -9
  313. package/neural-interface/lib/whatsapp/config.js +1 -1
  314. package/neural-interface/lib/worker-confinement.js +1268 -0
  315. package/neural-interface/lib/worker-guard-setting.js +56 -0
  316. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  317. package/neural-interface/lib/worker-question.js +74 -0
  318. package/neural-interface/lib/worker-relay-ports.js +227 -0
  319. package/neural-interface/lib/worker-resume.js +238 -0
  320. package/neural-interface/lib/worker-sandbox.js +329 -0
  321. package/neural-interface/package-lock.json +34 -12
  322. package/neural-interface/package.json +3 -2
  323. package/neural-interface/public/index.html +3 -1
  324. package/neural-interface/public/index2d.html +19 -11
  325. package/neural-interface/public/onboarding.html +3 -3
  326. package/neural-interface/public/shared/account-menu.js +8 -5
  327. package/neural-interface/public/shared/agent-runtime-options.js +125 -31
  328. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  329. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  330. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  331. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  332. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  333. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  334. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  335. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  336. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  337. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  338. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  339. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  340. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  341. package/neural-interface/public/shared/api.js +1527 -1336
  342. package/neural-interface/public/shared/app-frame.js +192 -0
  343. package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
  344. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  345. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  346. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  347. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  348. package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
  349. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  350. package/neural-interface/public/shared/assistant/asst-control.js +205 -26
  351. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  352. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  353. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  354. package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
  355. package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
  356. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  357. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  358. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  359. package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  361. package/neural-interface/public/shared/assistant/asst-render.js +445 -82
  362. package/neural-interface/public/shared/assistant/asst-route.js +304 -53
  363. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  364. package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
  365. package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
  366. package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
  367. package/neural-interface/public/shared/assistant/asst-state.js +87 -24
  368. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
  371. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  372. package/neural-interface/public/shared/automation-context.js +5 -2
  373. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  374. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  375. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  376. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  377. package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
  378. package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
  379. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  380. package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
  381. package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
  382. package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
  383. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  384. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  385. package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
  386. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  387. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  388. package/neural-interface/public/shared/cli-install-view.js +11 -12
  389. package/neural-interface/public/shared/cli-status.js +11 -10
  390. package/neural-interface/public/shared/constants.js +5 -4
  391. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  392. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  393. package/neural-interface/public/shared/context-menu-model.js +367 -0
  394. package/neural-interface/public/shared/context-menu.js +372 -0
  395. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  396. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  397. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  398. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  399. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  400. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  401. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  402. package/neural-interface/public/shared/desktop-update-view.js +21 -0
  403. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  404. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  405. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  406. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  407. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  408. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  409. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  410. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  411. package/neural-interface/public/shared/html-shell.js +30 -15
  412. package/neural-interface/public/shared/i18n.js +2 -0
  413. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  414. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  415. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  416. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  417. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  418. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  419. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  420. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  421. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  422. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  423. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  424. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
  425. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  426. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  427. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  428. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
  429. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  430. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  431. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  432. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  433. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  434. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  435. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  436. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
  437. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  438. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
  439. package/neural-interface/public/shared/page-session.js +96 -0
  440. package/neural-interface/public/shared/provider-icons.js +4 -2
  441. package/neural-interface/public/shared/registry.js +5 -3
  442. package/neural-interface/public/shared/retired-2d.js +3 -0
  443. package/neural-interface/public/shared/schedules-view.js +195 -61
  444. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  445. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  446. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  447. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  448. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  449. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  450. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  451. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  452. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  453. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  454. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  455. package/neural-interface/public/shared/skills/ss-creation.js +222 -0
  456. package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
  457. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  458. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  459. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  460. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  461. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  462. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  463. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  464. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  465. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  466. package/neural-interface/public/shared/skills/ss-lint.js +149 -0
  467. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  468. package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
  469. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  470. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  471. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  472. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  473. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  474. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  475. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  476. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
  477. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  478. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  479. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  480. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  481. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  482. package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
  483. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  484. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  485. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  486. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  487. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  488. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  489. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  490. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  491. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
  492. package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
  493. package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
  494. package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
  495. package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
  496. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  497. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  498. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  499. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  500. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  501. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  502. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  503. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  504. package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
  505. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  506. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  507. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  508. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
  509. package/neural-interface/public/shared/state.js +1 -1
  510. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  511. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  512. package/neural-interface/public/shared/styles.css +1822 -1756
  513. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  514. package/neural-interface/public/shared/text-cache.js +29 -0
  515. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  516. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  517. package/neural-interface/public/shared/tool-list-view.js +112 -0
  518. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  519. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  520. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  521. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  522. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  523. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  524. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  525. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  526. package/neural-interface/public/shared/tour/tour.css +144 -0
  527. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  528. package/neural-interface/public/shared/ui-assistant.js +2 -0
  529. package/neural-interface/public/shared/ui-automation-studio.js +111 -140
  530. package/neural-interface/public/shared/ui-claude-panel.js +376 -82
  531. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  532. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  533. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  534. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  535. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  536. package/neural-interface/public/shared/ui-detail.js +15 -3
  537. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  538. package/neural-interface/public/shared/ui-file-links.js +2 -2
  539. package/neural-interface/public/shared/ui-games.js +2 -2
  540. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  541. package/neural-interface/public/shared/ui-judgments.js +1 -1
  542. package/neural-interface/public/shared/ui-keybinds.js +33 -2
  543. package/neural-interface/public/shared/ui-link.js +3 -3
  544. package/neural-interface/public/shared/ui-menubar.js +10 -20
  545. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  546. package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
  547. package/neural-interface/public/shared/ui-navbar.js +49 -24
  548. package/neural-interface/public/shared/ui-notifications.js +1 -0
  549. package/neural-interface/public/shared/ui-panels.js +96 -65
  550. package/neural-interface/public/shared/ui-resume.js +9 -7
  551. package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
  552. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  553. package/neural-interface/public/shared/ui-sessions.js +27 -15
  554. package/neural-interface/public/shared/ui-settings.js +683 -526
  555. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  556. package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
  557. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  558. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  559. package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
  560. package/neural-interface/public/shared/ui-skills.js +33 -2830
  561. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  562. package/neural-interface/public/shared/ui-sync.js +15 -5
  563. package/neural-interface/public/shared/ui-terminal.js +270 -190
  564. package/neural-interface/public/shared/ui-tool-updates.js +6 -4
  565. package/neural-interface/public/shared/ui-tooltip.js +81 -51
  566. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  567. package/neural-interface/public/shared/ui-trash.js +6 -4
  568. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  569. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  570. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  571. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  572. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  573. package/neural-interface/public/shared/ui-update.js +38 -2
  574. package/neural-interface/public/shared/ui-whiteboard.js +312 -59
  575. package/neural-interface/public/shared/ui-worker-end.js +5 -1
  576. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  577. package/neural-interface/public/shared/view-variant.js +66 -0
  578. package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
  579. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  580. package/neural-interface/public/shared/worker-end.js +10 -3
  581. package/neural-interface/public/shared/worker-questions.js +34 -0
  582. package/neural-interface/public/shared/worker-restart.js +12 -0
  583. package/neural-interface/public/sidepanel-session.html +2 -2
  584. package/neural-interface/public/variant/2d/background.js +2 -1
  585. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  586. package/neural-interface/public/variant/2d/graph.js +42 -20
  587. package/neural-interface/public/variant/2d/hulls.js +14 -9
  588. package/neural-interface/public/variant/2d/lasso.js +11 -7
  589. package/neural-interface/public/variant/2d/main.js +32 -1
  590. package/neural-interface/public/variant/2d/minimap.js +11 -13
  591. package/neural-interface/public/variant/3d/main.js +69 -6
  592. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  593. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  594. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  595. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  596. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  597. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  598. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  599. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  600. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  601. package/neural-interface/scripts/build-vendor.mjs +32 -0
  602. package/neural-interface/server.js +4553 -2855
  603. package/neural-interface/templates/rulesets/README.md +1 -1
  604. package/neural-interface/templates/rulesets/core.md +1 -1
  605. package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
  606. package/neural-interface/templates/rulesets/manifest.json +8 -8
  607. package/npm-shrinkwrap.json +34 -12
  608. package/package.json +17 -3
  609. package/postinstall.js +9 -4
  610. package/preuninstall.js +1 -1
  611. package/setup.js +97 -322
  612. package/skills/skill-forge/SKILL.md +177 -0
  613. package/skills/skill-forge/evals/evals.json +54 -0
  614. package/skills/skill-forge/references/package.md +105 -0
  615. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  616. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  617. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  618. package/skills/synabun/SKILL.md +10 -7
  619. package/skills/synabun/modules/audit.md +1 -5
  620. package/skills/synabun/modules/changelog.md +1 -5
  621. package/skills/synabun/modules/idea.md +1 -1
  622. package/skills/synabun/modules/memorize.md +6 -9
  623. package/skills/synabun/modules/schedule.md +6 -6
  624. package/mcp-server/dist/services/profiles.d.ts +0 -120
  625. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  626. package/mcp-server/dist/services/profiles.js +0 -387
  627. package/mcp-server/dist/services/profiles.js.map +0 -1
  628. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  629. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  630. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  631. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  632. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  633. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  634. package/mcp-server/dist/tools/leonardo.js +0 -21
  635. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  636. package/mcp-server/dist/tools/profile.d.ts +0 -17
  637. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  638. package/mcp-server/dist/tools/profile.js +0 -114
  639. package/mcp-server/dist/tools/profile.js.map +0 -1
  640. package/mcp-server/src/services/profiles.ts +0 -428
  641. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  642. package/mcp-server/src/tools/leonardo.ts +0 -30
  643. package/mcp-server/src/tools/profile.ts +0 -122
  644. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  645. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  646. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  647. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  648. package/skills/leonardo/SKILL.md +0 -168
  649. package/skills/leonardo/icon.svg +0 -1
  650. package/skills/leonardo/modules/browser-guide.md +0 -378
  651. package/skills/leonardo/modules/image-prompter.md +0 -283
  652. package/skills/leonardo/modules/model-advisor.md +0 -100
  653. package/skills/leonardo/modules/prompt-library.md +0 -110
  654. package/skills/leonardo/modules/style-guide.md +0 -186
  655. package/skills/leonardo/modules/video-prompter.md +0 -317
  656. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -0,0 +1,495 @@
1
+ // ── /api/skills-studio/v2: the Skills Service over HTTP ──
2
+ // The routes the Studio, Settings and the MCP `skill` tool use (lib/skills/service.js does the work). Under the
3
+ // Skills Studio permission like the rest of /api/skills-studio; on top of it, who may do what:
4
+ //
5
+ // read everyone the permission lets in (the catalog, the library, plans, reviews, profiles, status);
6
+ // draft the owner and agents (the Assistant, a worker): drafts, snapshots, checks, adopting a folder,
7
+ // submitting a revision for review, commenting on it, withdrawing one's own. Never a guest.
8
+ // deploy the owner, from SynaBun's own page only: installing, rolling back, removing, approving or
9
+ // rejecting a review, migrating the old bridges, moving a pointer, deleting a skill from the
10
+ // library, the settings. A request that
11
+ // carries an agent's headers is refused (403 UI_ONLY) whatever else it says, as is a guest, another
12
+ // site's page, a request without the page's X-SynaBun-UI header, or one without this boot's page
13
+ // secret cookie (lib/page-session.js, 403 PAGE_SECRET_REQUIRED). An agent asks for a plan
14
+ // (POST /installations/plan) and submits for review; the owner installs.
15
+ //
16
+ // Ids in paths are opaque (`skl_`, `rev_`, `drf_`, `rvw_`, `ins_`, `mig_`, `art_`) and checked by shape before
17
+ // anything is looked up; no id is ever a path. Errors are JSON `{ ok: false, error, code, … }` with 400/422 for
18
+ // what the request says, 403 for who sent it, 404, 409 for a conflict (with what is current), 413 for a limit.
19
+ // A POST that creates something takes an idempotency key (header `Idempotency-Key` or `idempotencyKey`): the same
20
+ // key from the same caller within ten minutes gets the first answer again, and nothing is made twice.
21
+
22
+ import { join } from 'node:path';
23
+ import { AGENT_HEADERS, sendGuardError } from '../http-guards.js';
24
+ import { PathConfineError } from '../path-confine.js';
25
+ import { readConfinedFile } from '../confined-fs.js';
26
+ import { isArtifactId } from './catalog.js';
27
+ import { checkFolder, folderDigest, isDraftId, isRevisionId, isSkillId } from './library.js';
28
+ import { bundlePathProblem, collectFolder } from './bundle.js';
29
+ import { isEditableText } from '../../public/shared/skills/ss-doc.js';
30
+
31
+ /** The largest file of a catalog skill the Studio reads (a SKILL.md's bound, as the P0 routes read one). */
32
+ const CATALOG_FILE_MAX_BYTES = 2 * 1024 * 1024;
33
+
34
+ export const V2_BASE = '/api/skills-studio/v2';
35
+ const IDS = {
36
+ skillId: isSkillId, revisionId: isRevisionId, draftId: isDraftId, artifactId: isArtifactId,
37
+ reviewId: (v) => typeof v === 'string' && /^rvw_[0-9a-f]{20}$/.test(v),
38
+ installationId: (v) => typeof v === 'string' && /^ins_[0-9a-f]{20}$/.test(v),
39
+ journalId: (v) => typeof v === 'string' && /^mig_[0-9a-f]{20}$/.test(v),
40
+ };
41
+ const IDEMPOTENCY_TTL_MS = 10 * 60 * 1000;
42
+ const IDEMPOTENCY_MAX = 500;
43
+
44
+ /**
45
+ * Who is asking: an agent (its headers), a guest, or the owner. An agent is the Assistant only when `assistantOf`
46
+ * resolves the credential its brain was launched with (X-Synabun-Brain-Token: assistant-runtime.js
47
+ * mintBrainCredential) to that brain's session: X-Synabun-Role is a header any local process can send, so without a
48
+ * verifier, or without the credential, an agent is only an agent.
49
+ * @param {(req) => string|null} [assistantOf] the Assistant session a request's brain credential belongs to
50
+ */
51
+ export function principalOf(req, isGuestRequest = null, assistantOf = null) {
52
+ const h = req.headers || {};
53
+ if (AGENT_HEADERS.some((name) => h[name] !== undefined)) {
54
+ const role = typeof h['x-synabun-role'] === 'string' ? h['x-synabun-role'] : '';
55
+ let session = null;
56
+ if (role === 'assistant' && typeof assistantOf === 'function') { try { session = assistantOf(req) || null; } catch { session = null; } }
57
+ return { kind: session ? 'assistant' : 'agent', role: role || null, terminal: typeof h['x-synabun-terminal'] === 'string' ? h['x-synabun-terminal'].slice(0, 200) : null, ...(session ? { assistantSessionId: String(session) } : {}) };
58
+ }
59
+ let guest = false;
60
+ if (typeof isGuestRequest === 'function') { try { guest = !!isGuestRequest(req); } catch { guest = true; } }
61
+ return guest ? { kind: 'guest' } : { kind: 'owner' };
62
+ }
63
+
64
+ /** Whether a principal is the author a record names: the same kind of caller, from the same (non-empty) terminal. */
65
+ function sameAuthor(principal, author) {
66
+ if (!principal || !author || principal.kind === 'owner' || author.kind === 'owner') return false;
67
+ return principal.kind === author.kind && typeof principal.terminal === 'string' && principal.terminal !== '' && principal.terminal === author.terminal;
68
+ }
69
+
70
+ /** An author record for something a principal made, with what the request adds (an agent's run, provider, model). */
71
+ function authorOf(principal, body = {}) {
72
+ const kind = principal.kind === 'owner' ? 'owner' : (principal.kind === 'assistant' ? 'assistant' : 'agent');
73
+ const out = { kind };
74
+ if (principal.terminal) out.terminal = principal.terminal;
75
+ if (kind !== 'owner') for (const key of ['runId', 'provider', 'model', 'modelProvider', 'sessionId']) if (typeof body?.[key] === 'string' && body[key]) out[key] = body[key].slice(0, 200);
76
+ return out;
77
+ }
78
+
79
+ function fail(res, err) {
80
+ const status = err instanceof PathConfineError ? err.status : (Number.isInteger(err?.status) && err.status >= 400 && err.status < 600 ? err.status : 500);
81
+ const body = { ok: false, error: status >= 500 && !(err instanceof PathConfineError) ? `The skills service failed: ${err?.message || err}` : (err?.message || String(err)) };
82
+ if (err?.reason === 'lint' && Array.isArray(err.diagnostics)) { body.reason = 'lint'; body.diagnostics = err.diagnostics.slice(0, 30); }
83
+ if (typeof err?.code === 'string' && !/^E[A-Z]+$/.test(err.code)) body.code = err.code;
84
+ for (const key of ['problems', 'currentEtag', 'currentDigest', 'currentRevision', 'plan', 'notRestored', 'installations', 'file', 'rule']) if (err?.[key] !== undefined) body[key] = err[key];
85
+ return res.status(status).json(body);
86
+ }
87
+
88
+ /**
89
+ * @param {object} app express app
90
+ * @param {object} o
91
+ * @param {object} o.service lib/skills/service.js
92
+ * @param {(req) => boolean} [o.isGuestRequest]
93
+ * @param {Function} [o.ownerGuard] lib/http-guards.js ownerRouteGuard(…) of this server (Host, Origin, guests)
94
+ * @param {(req) => string|null} [o.assistantOf] the Assistant session a request's brain credential belongs to (principalOf)
95
+ */
96
+ export function registerSkillsV2Routes(app, { service, isGuestRequest = null, ownerGuard = null, assistantOf = null, pageSession = null } = {}) {
97
+ const idem = new Map();
98
+
99
+ const json = (req, res, next) => {
100
+ if (String(req.headers['sec-fetch-site'] || '').toLowerCase() === 'cross-site') return sendGuardError(res, 403, 'CROSS_SITE', 'Cross-site requests are refused.');
101
+ const hasBody = Number(req.headers['content-length'] || 0) > 0 || req.headers['transfer-encoding'] !== undefined;
102
+ if (hasBody && typeof req.is === 'function' && !req.is('application/json')) return sendGuardError(res, 415, 'UNSUPPORTED_MEDIA_TYPE', 'Send JSON (Content-Type: application/json).');
103
+ if (req.body !== undefined && req.body !== null && (typeof req.body !== 'object' || Array.isArray(req.body))) {
104
+ return res.status(400).json({ ok: false, error: 'The body is a JSON object.', code: 'invalid_body' });
105
+ }
106
+ next();
107
+ };
108
+ // A read of the owner's page hands it the page secret (lib/page-session.js), which its writes then carry.
109
+ const reader = (req, res, next) => { req.principal = principalOf(req, isGuestRequest, assistantOf); if (req.principal.kind === 'owner') pageSession?.attach?.(res); next(); };
110
+ const writer = (req, res, next) => {
111
+ req.principal = principalOf(req, isGuestRequest, assistantOf);
112
+ if (req.principal.kind === 'guest') return sendGuardError(res, 403, 'GUEST_FORBIDDEN', 'Available to the owner only.');
113
+ next();
114
+ };
115
+ const owner = (req, res, next) => {
116
+ req.principal = principalOf(req, isGuestRequest, assistantOf);
117
+ if (req.principal.kind !== 'owner') return sendGuardError(res, 403, req.principal.kind === 'guest' ? 'GUEST_FORBIDDEN' : 'UI_ONLY', req.principal.kind === 'guest' ? 'Available to the owner only.' : 'Installing, approving and migrating are the owner\'s, from SynaBun\'s own page: ask the owner to review it in Skills Studio.');
118
+ if (req.headers['x-synabun-ui'] !== '1') return sendGuardError(res, 403, 'UI_HEADER_REQUIRED', 'Send the X-SynaBun-UI: 1 header.');
119
+ // The page secret: what a local process that merely left the agent headers out lacks.
120
+ if (pageSession && !pageSession.verify(req)) return pageSession.require(req, res, next);
121
+ if (!ownerGuard) return next();
122
+ return ownerGuard(req, res, next);
123
+ };
124
+ const ids = (...names) => (req, res, next) => {
125
+ for (const name of names) {
126
+ if (!IDS[name](req.params[name])) return res.status(400).json({ ok: false, error: `Not a ${name.replace(/Id$/, '')} id.`, code: 'invalid_id' });
127
+ }
128
+ next();
129
+ };
130
+ /** Same caller, same route, same key within ten minutes: the first answer again. */
131
+ const once = (handler) => async (req, res) => {
132
+ const key = typeof req.headers['idempotency-key'] === 'string' ? req.headers['idempotency-key'] : (typeof req.body?.idempotencyKey === 'string' ? req.body.idempotencyKey : null);
133
+ if (!key) return handler(req, res);
134
+ if (key.length > 200) return res.status(400).json({ ok: false, error: 'An idempotency key is at most 200 characters.', code: 'invalid_idempotency_key' });
135
+ const slot = `${req.principal?.kind}\0${req.principal?.terminal || ''}\0${req.method}\0${req.originalUrl || req.url}\0${key}`;
136
+ const hit = idem.get(slot);
137
+ if (hit && Date.now() - hit.at < IDEMPOTENCY_TTL_MS) return res.status(hit.status).json({ ...hit.body, replayed: true });
138
+ const original = res.json.bind(res);
139
+ res.json = (body) => {
140
+ if (res.statusCode < 300) {
141
+ idem.set(slot, { at: Date.now(), status: res.statusCode, body });
142
+ if (idem.size > IDEMPOTENCY_MAX) idem.delete(idem.keys().next().value);
143
+ }
144
+ return original(body);
145
+ };
146
+ return handler(req, res);
147
+ };
148
+ const safe = (fn) => async (req, res) => { try { await fn(req, res); } catch (err) { fail(res, err); } };
149
+ const route = (method, path, ...handlers) => app[method](`${V2_BASE}${path}`, ...handlers);
150
+
151
+ // P5: reads share the feature gate; settings, outbound access and health checks require the owner's page.
152
+ // The authenticated Assistant can read evidence and preview packages. It cannot enable a source or scanner.
153
+ route('get', '/operations', reader, safe(async (req, res) => res.json({ ok: true, settings: service.operations.settings(), scanners: service.scanners.list() })));
154
+ route('put', '/operations', json, owner, safe(async (req, res) => res.json({ ok: true, settings: service.operations.configure(req.body) })));
155
+ route('get', '/usage', reader, safe(async (req, res) => res.json({ ok: true, ...service.telemetry.summary({ skillId: isSkillId(req.query?.skillId) ? req.query.skillId : null }) })));
156
+ route('delete', '/usage', json, owner, safe(async (req, res) => { service.telemetry.clear(); res.json({ ok: true }); }));
157
+ route('get', '/health', reader, safe(async (req, res) => res.json({ ok: true, ...service.health.view(), proposals: service.operations.proposals() })));
158
+ route('post', '/health/check', json, owner, safe(async (req, res) => res.json({ ok: true, ...await service.health.check({ probe: req.body?.probe === true }) })));
159
+ route('get', '/discover', reader, safe(async (req, res) => res.json({ ok: true, ...service.discover.list({ sourceId: req.query?.sourceId, q: req.query?.q }) })));
160
+ route('post', '/discover/refresh', json, owner, safe(async (req, res) => res.json({ ok: true, ...await service.discover.refresh(req.body?.sourceId) })));
161
+ route('post', '/discover/catalog', json, owner, safe(async (req, res) => res.json({ ok: true, ...service.discover.importCatalog(req.body?.sourceId, req.body?.entries) })));
162
+ route('post', '/discover/stage', json, owner, once(safe(async (req, res) => res.status(201).json({ ok: true, ...await service.discover.stage(req.body?.itemId, { kind: 'owner' }) }))));
163
+ route('get', '/revisions/:revisionId/package', reader, ids('revisionId'), safe(async (req, res) => res.json({ ok: true, package: service.packages.preview(req.params.revisionId, { format: req.query?.format || 'bundle', profile: req.query?.profile || null, excludeEvals: req.query?.excludeEvals === 'true' }) })));
164
+ route('post', '/revisions/:revisionId/external-scans', json, owner, ids('revisionId'), safe(async (req, res) => {
165
+ const folder = service.library.readRevisionFiles(req.params.revisionId);
166
+ res.json({ ok: true, results: await service.scanners.scan(folder.revision, folder) });
167
+ }));
168
+
169
+ // ── Reading ──
170
+ route('get', '/capabilities', reader, safe(async (req, res) => res.json({ ok: true, version: service.version, capabilities: service.capabilities })));
171
+ route('get', '/status', reader, safe(async (req, res) => res.json({ ok: true, ...(await service.status()) })));
172
+ route('get', '/roots', reader, safe(async (req, res) => res.json({ ok: true, roots: service.roots() })));
173
+ route('get', '/profiles', reader, safe(async (req, res) => res.json({ ok: true, profiles: service.profiles() })));
174
+ route('get', '/catalog', reader, safe(async (req, res) => {
175
+ const q = req.query || {};
176
+ const str = (v) => (typeof v === 'string' && v ? v.slice(0, 500) : null);
177
+ const page = await service.catalogPage({ cursor: str(q.cursor), limit: Number(q.limit) || 100, kind: str(q.kind), runtime: str(q.runtime), rootId: str(q.rootId), scope: str(q.scope), q: str(q.q) || '' });
178
+ const scan = await service.catalogScan();
179
+ res.json({ ok: true, ...page, roots: scan.roots.map(({ rootId, family, kind, scope, sourceKind, runtimes, accountId, projectLabel, path, status, policy, count, truncated }) => ({ rootId, family, kind, scope, sourceKind, runtimes, accountId, projectLabel, path, status, policy, count, truncated })), diagnostics: scan.diagnostics.slice(0, 500) });
180
+ }));
181
+ route('get', '/catalog/:artifactId', reader, ids('artifactId'), safe(async (req, res) => {
182
+ const found = await service.catalogDetail(req.params.artifactId);
183
+ if (!found) return res.status(404).json({ ok: false, error: 'There is no such entry in the catalog.', code: 'artifact_not_found' });
184
+ res.json({ ok: true, entry: found.entry });
185
+ }));
186
+ // One file of a catalog skill (a plugin's, a Claude account's, Codex's own, a folder of the owner's), read the way
187
+ // the head file is: inside the skill's real folder, the last component opened without following a link, a regular
188
+ // file, bounded. Hidden files and node_modules are left out, as an export leaves them out. Text comes as text,
189
+ // anything else as base64. The Studio shows a skill it cannot write without copying it first.
190
+ route('get', '/catalog/:artifactId/file', reader, ids('artifactId'), safe(async (req, res) => {
191
+ const path = typeof req.query?.path === 'string' ? req.query.path : '';
192
+ const why = path ? bundlePathProblem(path) : 'no path';
193
+ if (why) return res.status(400).json({ ok: false, error: `Not a file of a skill: ${why}.`, code: 'invalid_path' });
194
+ if (path.split('/').some((part) => part.startsWith('.') || part === 'node_modules')) return res.status(403).json({ ok: false, error: 'Hidden files and node_modules are not read here.', code: 'hidden_file' });
195
+ const entry = await service.catalogFind(req.params.artifactId);
196
+ if (!entry) return res.status(404).json({ ok: false, error: 'There is no such entry in the catalog.', code: 'artifact_not_found' });
197
+ if (entry.kind !== 'skill') return res.status(400).json({ ok: false, error: 'A command or an agent is one file: GET /catalog/:id carries its text.', code: 'not_a_skill' });
198
+ let bytes;
199
+ try { bytes = readConfinedFile(entry.realPath, join(entry.realPath, path), { maxBytes: CATALOG_FILE_MAX_BYTES }); } catch (err) {
200
+ if (err?.code === 'ENOENT' || err?.status === 404) return res.status(404).json({ ok: false, error: 'The skill has no such file.', code: 'file_not_found' });
201
+ throw err;
202
+ }
203
+ const text = isEditableText(bytes);
204
+ res.json({ ok: true, path, size: bytes.length, binary: !text, ...(text ? { content: bytes.toString('utf-8') } : { contentBase64: bytes.toString('base64') }) });
205
+ }));
206
+ // A revision against a catalog skill folder as it is now (an installed copy against what SynaBun ships): the
207
+ // folder is read whole the way an export reads it (no link followed, within an import's limits).
208
+ route('get', '/compare', reader, safe(async (req, res) => {
209
+ const { from, artifactId } = req.query || {};
210
+ if (!isRevisionId(from) || !isArtifactId(artifactId)) return res.status(400).json({ ok: false, error: 'from is a revision id and artifactId a catalog entry.', code: 'invalid_id' });
211
+ const entry = await service.catalogFind(artifactId);
212
+ if (!entry || entry.kind !== 'skill') return res.status(404).json({ ok: false, error: 'There is no such skill in the catalog.', code: 'artifact_not_found' });
213
+ const { files, dirs } = collectFolder(entry.realPath);
214
+ res.json({ ok: true, diff: service.library.diffRevisionFolder(from, { files, dirs }, { path: entry.path }) });
215
+ }));
216
+ route('post', '/catalog/rescan', json, writer, safe(async (req, res) => { const s = await service.catalogScan({ force: true }); res.json({ ok: true, generation: s.generation, generatedAt: s.generatedAt, entries: s.entries.length, truncated: s.truncated }); }));
217
+ // The settings, with the assistants on this computer (detected, chosen, in effect): what Publish ticks by default.
218
+ route('get', '/config', reader, safe(async (req, res) => res.json({ ok: true, config: service.getConfig(), assistants: service.assistantsView() })));
219
+ route('put', '/config', json, owner, safe(async (req, res) => res.json({ ok: true, config: service.setConfig(req.body || {}), assistants: service.assistantsView() })));
220
+
221
+ // ── The library ──
222
+ route('get', '/skills', reader, safe(async (req, res) => res.json({ ok: true, skills: service.listSkills() })));
223
+ route('post', '/skills', json, writer, once(safe(async (req, res) => {
224
+ const body = req.body || {};
225
+ const author = authorOf(req.principal, body);
226
+ if (body.artifactId !== undefined) {
227
+ if (!isArtifactId(body.artifactId)) return res.status(400).json({ ok: false, error: 'Not an artifact id.', code: 'invalid_id' });
228
+ const out = await service.adopt({ artifactId: body.artifactId, author });
229
+ return res.status(out.created ? 201 : 200).json({ ok: true, ...out });
230
+ }
231
+ const files = decodeFiles(body.files);
232
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
233
+ if (!name) return res.status(400).json({ ok: false, error: 'A new skill has a name.', code: 'invalid_name' });
234
+ const draft = service.library.createDraft({ name, files, dirs: Array.isArray(body.dirs) ? body.dirs : [], author, origin: { kind: author.kind === 'owner' ? 'local' : 'assistant' } });
235
+ service.announce({ type: 'skill.changed', skillId: draft.skillId });
236
+ res.status(201).json({ ok: true, skillId: draft.skillId, draft: draftView(draft) });
237
+ })));
238
+ // The skills deleted from the library (their versions kept): before /skills/:skillId, which it is not.
239
+ route('get', '/skills/archived', reader, safe(async (req, res) => res.json({ ok: true, archived: service.listArchived() })));
240
+ route('get', '/skills/:skillId', reader, ids('skillId'), safe(async (req, res) => res.json({ ok: true, skill: service.getSkill(req.params.skillId) })));
241
+ // Taking a skill out of the library: the owner's, from the page. `expectedEtag` (optional, the record as read)
242
+ // guards against deleting something changed since; an installed skill (409 skill_installed, with its
243
+ // installations) and one SynaBun ships (403 skill_bundled) are refused. Its versions go to the library's archive.
244
+ route('post', '/skills/:skillId/restore', json, owner, ids('skillId'), safe(async (req, res) => res.json({ ok: true, ...service.restoreSkill(req.params.skillId) })));
245
+ route('delete', '/skills/:skillId', json, owner, ids('skillId'), safe(async (req, res) => {
246
+ const expectedEtag = req.body?.expectedEtag ?? null;
247
+ if (expectedEtag !== null && !Number.isInteger(expectedEtag)) return res.status(400).json({ ok: false, error: 'expectedEtag is the etag number the skill was read with.', code: 'invalid_etag' });
248
+ res.json({ ok: true, ...service.removeSkill(req.params.skillId, { expectedEtag, by: { kind: 'owner' } }) });
249
+ }));
250
+ route('get', '/skills/:skillId/revisions', reader, ids('skillId'), safe(async (req, res) => {
251
+ const list = service.library.listRevisions(req.params.skillId);
252
+ const skill = service.library.getSkill(req.params.skillId);
253
+ res.json({ ok: true, revisions: list.map((r) => ({ revisionId: r.revisionId, parentRevisionId: r.parentRevisionId, contentDigest: r.contentDigest, createdAt: r.createdAt, author: r.author, source: r.source, files: r.manifest?.files?.length || 0, integrity: r.integrity, error: r.error || null, labels: service.library.labelsOf(skill, r.revisionId), note: r.note, noteCode: r.noteCode || (r.source?.kind === 'bundled' && /^Shipped with SynaBun(?: [\d.]+)?\.$/.test(r.note) ? 'bundled' : null), scan: r.scan ? { status: r.scan.status, gate: r.scan.gate, counts: r.scan.counts, digest: r.scan.digest, coverage: { complete: r.scan.coverage?.complete } } : null })) });
254
+ }));
255
+ route('post', '/skills/:skillId/pointers', json, owner, ids('skillId'), safe(async (req, res) => {
256
+ const { pointer, revisionId, expectedEtag = null, reason = '' } = req.body || {};
257
+ if (revisionId !== null && !isRevisionId(revisionId)) return res.status(400).json({ ok: false, error: 'Not a revision id.', code: 'invalid_id' });
258
+ const rec = service.library.setPointer(req.params.skillId, pointer, revisionId, { expectedEtag, by: 'owner', reason });
259
+ service.announce({ type: 'skill.changed', skillId: rec.skillId });
260
+ res.json({ ok: true, pointers: rec.pointers, etag: rec.etag });
261
+ }));
262
+ route('get', '/revisions/:revisionId', reader, ids('revisionId'), safe(async (req, res) => {
263
+ if (typeof req.query?.file === 'string') {
264
+ const { entry, bytes } = service.library.readRevisionFile(req.params.revisionId, req.query.file);
265
+ return res.json({ ok: true, path: entry.path, digest: entry.digest, encoding: entry.encoding, mode: entry.mode, ...(entry.encoding === 'utf-8' ? { content: bytes.toString('utf-8') } : { contentBase64: bytes.toString('base64') }) });
266
+ }
267
+ res.json({ ok: true, revision: service.revisionView(req.params.revisionId) });
268
+ }));
269
+ route('get', '/diff', reader, safe(async (req, res) => {
270
+ const { from, to } = req.query || {};
271
+ if (!isRevisionId(from) || !isRevisionId(to)) return res.status(400).json({ ok: false, error: 'from and to are revision ids.', code: 'invalid_id' });
272
+ res.json({ ok: true, diff: service.library.diffRevisions(from, to) });
273
+ }));
274
+ // A revision as a file to keep or hand over: `format=bundle` (a SynaBun bundle, what Import takes back) or `skill`
275
+ // (a ZIP with the skill's folder at its root, the .skill format); `profile` the export of one runtime or surface
276
+ // (`claude-upload` for Claude.ai) through the same transforms an install applies, on the copy only: the revision
277
+ // is never rewritten. The name is the one the revision installs as.
278
+ route('get', '/revisions/:revisionId/export', reader, ids('revisionId'), safe(async (req, res) => {
279
+ const out = service.packages.download(req.params.revisionId, {
280
+ format: req.query?.format || 'bundle', profile: req.query?.profile || null,
281
+ excludeEvals: req.query?.excludeEvals === 'true', acknowledgedDigest: req.query?.acknowledgedDigest || null,
282
+ });
283
+ res.setHeader('Content-Type', out.contentType);
284
+ res.setHeader('Content-Disposition', `attachment; filename="${out.filename}"`);
285
+ res.setHeader('X-SynaBun-Artifact-Digest', out.manifest.artifactDigest);
286
+ res.setHeader('X-SynaBun-Archive-Digest', out.manifest.archiveDigest);
287
+ return res.send(out.bytes);
288
+ }));
289
+
290
+ // ── Drafts ──
291
+ route('post', '/drafts', json, writer, once(safe(async (req, res) => {
292
+ const body = req.body || {};
293
+ const author = authorOf(req.principal, body);
294
+ let draft;
295
+ if (body.skillId !== undefined) {
296
+ if (!isSkillId(body.skillId)) return res.status(400).json({ ok: false, error: 'Not a skill id.', code: 'invalid_id' });
297
+ if (body.baseRevisionId !== undefined && body.baseRevisionId !== null && !isRevisionId(body.baseRevisionId)) return res.status(400).json({ ok: false, error: 'Not a revision id.', code: 'invalid_id' });
298
+ draft = service.library.createDraft({ skillId: body.skillId, baseRevisionId: body.baseRevisionId || null, author });
299
+ } else {
300
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
301
+ if (!name) return res.status(400).json({ ok: false, error: 'A draft of a new skill has a name.', code: 'invalid_name' });
302
+ draft = service.library.createDraft({ name, files: decodeFiles(body.files), dirs: Array.isArray(body.dirs) ? body.dirs : [], author, origin: { kind: author.kind === 'owner' ? 'local' : 'assistant' } });
303
+ }
304
+ service.announce({ type: 'skill.changed', skillId: draft.skillId });
305
+ res.status(201).json({ ok: true, draft: draftView(draft) });
306
+ })));
307
+ route('get', '/drafts/:draftId', reader, ids('draftId'), safe(async (req, res) => {
308
+ if (typeof req.query?.file === 'string') {
309
+ const { files } = service.library.readDraftFiles(req.params.draftId);
310
+ const f = files.find((x) => x.path === req.query.file);
311
+ if (!f) return res.status(404).json({ ok: false, error: 'The draft has no such file.', code: 'file_not_found' });
312
+ const text = isText(f.bytes);
313
+ return res.json({ ok: true, path: f.path, executable: f.executable, ...(text ? { content: f.bytes.toString('utf-8') } : { contentBase64: f.bytes.toString('base64') }) });
314
+ }
315
+ res.json({ ok: true, draft: draftView(service.library.getDraft(req.params.draftId)) });
316
+ }));
317
+ route('patch', '/drafts/:draftId', json, writer, ids('draftId'), safe(async (req, res) => {
318
+ const { expectedEtag, ops } = req.body || {};
319
+ const draft = service.library.patchDraft(req.params.draftId, { expectedEtag, ops });
320
+ service.announce({ type: 'skill.changed', skillId: draft.skillId });
321
+ res.json({ ok: true, draft: draftView(draft) });
322
+ }));
323
+ route('post', '/drafts/:draftId/snapshot', json, writer, ids('draftId'), once(safe(async (req, res) => {
324
+ const body = req.body || {};
325
+ const author = authorOf(req.principal, body);
326
+ const { files } = service.library.readDraftFiles(req.params.draftId);
327
+ const lint = service.lintFolder({ files, dirs: [] }, {});
328
+ const out = service.library.snapshotDraft(req.params.draftId, { expectedEtag: body.expectedEtag, author, source: { kind: author.kind === 'owner' ? 'draft' : 'assistant' }, invocationIntent: lint.invocation, note: typeof body.note === 'string' ? body.note : '' });
329
+ if (!out.unchanged) service.announce({ type: 'revision.created', skillId: out.revision.skillId, revisionId: out.revision.revisionId });
330
+ res.status(out.unchanged ? 200 : 201).json({ ok: true, unchanged: out.unchanged, revision: service.revisionView(out.revision.revisionId), draft: draftView(service.library.getDraft(req.params.draftId)) });
331
+ })));
332
+
333
+ // ── Checks ──
334
+ route('post', '/lint', json, writer, safe(async (req, res) => {
335
+ const { folder, folderName } = await folderFrom(service, req.body || {});
336
+ const profiles = Array.isArray(req.body?.profiles) ? req.body.profiles.filter((p) => typeof p === 'string').slice(0, 10) : null;
337
+ res.json({ ok: true, ...service.lintFolder(folder, { profiles, folderName }) });
338
+ }));
339
+ route('post', '/scan', json, writer, safe(async (req, res) => {
340
+ const { folder, folderName, digest } = await folderFrom(service, req.body || {});
341
+ res.json({ ok: true, scan: service.scanFolder(folder, { name: folderName, digest }) });
342
+ }));
343
+
344
+ // ── Reviews ──
345
+ route('get', '/reviews', reader, safe(async (req, res) => {
346
+ const q = req.query || {};
347
+ const open = q.open === 'true' ? true : q.open === 'false' ? false : null;
348
+ res.json({ ok: true, reviews: service.reviews.list({ state: typeof q.state === 'string' ? q.state : null, skillId: isSkillId(q.skillId) ? q.skillId : null, open }) });
349
+ }));
350
+ route('get', '/reviews/:reviewId', reader, ids('reviewId'), safe(async (req, res) => res.json({ ok: true, review: service.reviews.get(req.params.reviewId) })));
351
+ route('post', '/reviews', json, writer, once(safe(async (req, res) => {
352
+ const body = req.body || {};
353
+ if (!isRevisionId(body.revisionId)) return res.status(400).json({ ok: false, error: 'Name the revision to review (revisionId).', code: 'invalid_id' });
354
+ if (body.previousReviewId !== undefined && body.previousReviewId !== null && !IDS.reviewId(body.previousReviewId)) return res.status(400).json({ ok: false, error: 'Not a review id.', code: 'invalid_id' });
355
+ const out = service.submitReview({ revisionId: body.revisionId, targets: Array.isArray(body.targets) ? body.targets : [], releaseNotes: body.releaseNotes, assumptions: body.assumptions, author: authorOf(req.principal, body), previousReviewId: body.previousReviewId || null });
356
+ res.status(out.existing ? 200 : 201).json({ ok: true, review: out.review, existing: out.existing });
357
+ })));
358
+ route('post', '/reviews/:reviewId/comment', json, writer, ids('reviewId'), safe(async (req, res) => res.json({ ok: true, review: service.reviews.comment(req.params.reviewId, { by: authorOf(req.principal, req.body), body: req.body?.body }) })));
359
+ route('post', '/reviews/:reviewId/withdraw', json, writer, ids('reviewId'), safe(async (req, res) => {
360
+ const rec = service.reviews.get(req.params.reviewId);
361
+ // The owner may close any review; an agent only its own: the same kind of caller, from the same terminal (a
362
+ // caller without one is nobody's author).
363
+ if (req.principal.kind !== 'owner' && !sameAuthor(req.principal, rec.author)) return sendGuardError(res, 403, 'NOT_AUTHOR', 'Only who submitted it, or the owner, withdraws a review.');
364
+ res.json({ ok: true, review: service.reviews.withdraw(req.params.reviewId, { by: authorOf(req.principal, req.body) }) });
365
+ }));
366
+ route('post', '/reviews/:reviewId/decision', json, owner, ids('reviewId'), safe(async (req, res) => {
367
+ const { decision, reason } = req.body || {};
368
+ res.json({ ok: true, review: service.decideReview(req.params.reviewId, { decision, reason, by: { kind: 'owner' } }) });
369
+ }));
370
+
371
+ // ── Installs ──
372
+ route('post', '/installations/plan', json, writer, safe(async (req, res) => {
373
+ const { revisionId, targets } = req.body || {};
374
+ if (!isRevisionId(revisionId)) return res.status(400).json({ ok: false, error: 'Name the revision to install (revisionId).', code: 'invalid_id' });
375
+ res.json({ ok: true, plan: await service.installPlan({ revisionId, targets }) });
376
+ }));
377
+ route('get', '/installations', reader, safe(async (req, res) => {
378
+ const q = req.query || {};
379
+ res.json({ ok: true, installations: service.deployer.listInstallations({ skillId: isSkillId(q.skillId) ? q.skillId : null, active: q.active === 'true' ? true : q.active === 'false' ? false : null }) });
380
+ }));
381
+ route('post', '/installations', json, owner, once(safe(async (req, res) => {
382
+ const { revisionId, targets, planDigest, decisions, reviewId = null } = req.body || {};
383
+ if (!isRevisionId(revisionId)) return res.status(400).json({ ok: false, error: 'Name the revision to install (revisionId).', code: 'invalid_id' });
384
+ if (reviewId !== null && !IDS.reviewId(reviewId)) return res.status(400).json({ ok: false, error: 'Not a review id.', code: 'invalid_id' });
385
+ const out = await service.install({ revisionId, targets, planDigest, decisions, principal: { kind: 'owner' }, reviewId });
386
+ res.status(out.ok ? 200 : 207).json({ ok: out.ok, partial: out.partial, results: out.results, plan: out.plan });
387
+ })));
388
+ route('get', '/installations/:installationId', reader, ids('installationId'), safe(async (req, res) => res.json({ ok: true, installation: service.deployer.getInstallation(req.params.installationId) })));
389
+ route('post', '/installations/:installationId/rollback', json, owner, ids('installationId'), safe(async (req, res) => {
390
+ const out = service.deployer.rollback(req.params.installationId, { expectedDestinationDigest: req.body?.expectedDestinationDigest ?? null, principal: { kind: 'owner' } });
391
+ service.announce({ type: 'installation.changed', installationId: req.params.installationId, action: 'rollback' });
392
+ res.json(out);
393
+ }));
394
+ route('post', '/installations/:installationId/uninstall', json, owner, ids('installationId'), safe(async (req, res) => {
395
+ const out = service.deployer.uninstall(req.params.installationId, { expectedDestinationDigest: req.body?.expectedDestinationDigest ?? null, principal: { kind: 'owner' } });
396
+ service.announce({ type: 'installation.changed', installationId: req.params.installationId, action: 'uninstall' });
397
+ res.json(out);
398
+ }));
399
+ route('post', '/installations/:installationId/verify', json, owner, ids('installationId'), safe(async (req, res) => {
400
+ res.json({ ok: true, installation: await service.deployer.verify(req.params.installationId, { probeRuntime: req.body?.probe === true }) });
401
+ }));
402
+
403
+ // ── The old bridges ──
404
+ // The server moves what is proven SynaBun's by itself at start (migration.js autoMigrate) and leaves a notice; the
405
+ // owner undoes it (revert), dismisses the notice, or moves a bridge that is not proven SynaBun's by confirming its
406
+ // bytes (`confirm`: bridgeId → digest, as the plan's `offer` names them).
407
+ const confirmOf = (body) => {
408
+ const raw = body?.confirm;
409
+ if (raw === undefined || raw === null) return {};
410
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new PathConfineError(400, 'confirm maps a bridge id to the digest of its bytes.');
411
+ const out = {};
412
+ for (const [id, digest] of Object.entries(raw)) {
413
+ if (!/^brg_[0-9a-f]{20}$/.test(id) || typeof digest !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(digest)) throw new PathConfineError(400, 'confirm maps a bridge id to the digest of its bytes.');
414
+ out[id] = digest;
415
+ }
416
+ return out;
417
+ };
418
+ route('get', '/migrations', reader, safe(async (req, res) => res.json({ ok: true, migrations: service.migration.listJournals() })));
419
+ route('get', '/migrations/notice', reader, safe(async (req, res) => { service.syncBundled(); res.json({ ok: true, notice: await service.migration.notice() }); }));
420
+ route('post', '/migrations/notice/dismiss', json, owner, safe(async (req, res) => res.json({ ok: true, ...service.migration.dismissNotice() })));
421
+ route('get', '/migrations/bridges/plan', reader, safe(async (req, res) => { service.syncBundled(); res.json({ ok: true, plan: await service.migration.plan() }); }));
422
+ // The plan with the owner's confirmations of bridges that are not proven SynaBun's (a reading: nothing moves).
423
+ route('post', '/migrations/bridges/plan', json, reader, safe(async (req, res) => { service.syncBundled(); res.json({ ok: true, plan: await service.migration.plan({ confirm: confirmOf(req.body) }) }); }));
424
+ route('post', '/migrations/bridges', json, owner, once(safe(async (req, res) => {
425
+ service.syncBundled();
426
+ const out = await service.migration.migrate({ planDigest: req.body?.planDigest, confirm: confirmOf(req.body), principal: { kind: 'owner' } });
427
+ service.announce({ type: 'migration.changed', journalId: out.journalId });
428
+ res.json({ ok: out.ok, ...out });
429
+ })));
430
+ // The one-click fix of a runtime setting that turns a skill off (runtime-checks.js enable): the owner's, from the
431
+ // page; the answer names the file, its backup and the lines that changed. Never silently.
432
+ route('post', '/eligibility/enable', json, owner, safe(async (req, res) => {
433
+ const b = req.body || {};
434
+ res.json(service.enableEligibility({ runtime: b.runtime, name: b.name, skillMd: b.skillMd, accountId: b.accountId === undefined || b.accountId === null || b.accountId === '' ? 'default' : b.accountId, projectPath: b.projectPath ?? null, confirmDenyAll: b.confirmDenyAll === true }));
435
+ }));
436
+ route('get', '/migrations/:journalId', reader, ids('journalId'), safe(async (req, res) => res.json({ ok: true, migration: service.migration.readJournal(req.params.journalId) })));
437
+ route('post', '/migrations/:journalId/revert', json, owner, ids('journalId'), safe(async (req, res) => {
438
+ const out = await service.migration.revert(req.params.journalId, { principal: { kind: 'owner' } });
439
+ service.announce({ type: 'migration.changed', journalId: req.params.journalId });
440
+ res.json(out);
441
+ }));
442
+ }
443
+
444
+ // ── Helpers ──
445
+
446
+ const isText = (bytes) => {
447
+ try { new TextDecoder('utf-8', { fatal: true }).decode(bytes); } catch { return false; }
448
+ return !bytes.includes(0);
449
+ };
450
+
451
+ /** `files` of a request: `[{ path, content | contentBase64, executable? }]` → bytes. */
452
+ export function decodeFiles(list) {
453
+ if (!Array.isArray(list) || !list.length) throw new PathConfineError(400, 'files is a list of { path, content } (or contentBase64).');
454
+ if (list.length > 2000) throw new PathConfineError(413, 'Too many files.');
455
+ return list.map((f, i) => {
456
+ if (!f || typeof f !== 'object' || typeof f.path !== 'string') throw new PathConfineError(400, `files[${i}] has no path.`);
457
+ let bytes;
458
+ if (typeof f.content === 'string' && f.contentBase64 === undefined) bytes = Buffer.from(f.content, 'utf-8');
459
+ else if (typeof f.contentBase64 === 'string' && f.content === undefined) {
460
+ bytes = Buffer.from(f.contentBase64, 'base64');
461
+ if (bytes.toString('base64') !== f.contentBase64) throw new PathConfineError(400, `files[${i}].contentBase64 is not canonical base64.`);
462
+ } else throw new PathConfineError(400, `files[${i}] has either content (text) or contentBase64.`);
463
+ return { path: f.path, bytes, executable: f.executable === true };
464
+ });
465
+ }
466
+
467
+ /** The folder a check runs on: a revision, a draft, a catalog entry, or files sent with the request. */
468
+ async function folderFrom(service, body) {
469
+ if (body.revisionId !== undefined) {
470
+ if (!isRevisionId(body.revisionId)) throw new PathConfineError(400, 'Not a revision id.');
471
+ const { files, dirs, revision } = service.library.readRevisionFiles(body.revisionId);
472
+ const skill = service.library.getSkill(revision.skillId);
473
+ return { folder: { files, dirs }, folderName: typeof body.folderName === 'string' ? body.folderName : skill.name, digest: revision.contentDigest };
474
+ }
475
+ if (body.draftId !== undefined) {
476
+ if (!isDraftId(body.draftId)) throw new PathConfineError(400, 'Not a draft id.');
477
+ const { files, dirs, draft } = service.library.readDraftFiles(body.draftId);
478
+ const skill = service.library.getSkill(draft.skillId);
479
+ return { folder: { files, dirs }, folderName: typeof body.folderName === 'string' ? body.folderName : skill.name, digest: folderDigest({ files, dirs }) };
480
+ }
481
+ if (body.artifactId !== undefined) {
482
+ if (!isArtifactId(body.artifactId)) throw new PathConfineError(400, 'Not an artifact id.');
483
+ const entry = await service.catalogFind(body.artifactId);
484
+ if (!entry || entry.kind !== 'skill') throw new PathConfineError(404, 'There is no such skill in the catalog.');
485
+ const { files, dirs } = collectFolder(entry.realPath);
486
+ return { folder: { files, dirs }, folderName: entry.folderName, digest: folderDigest({ files, dirs }) };
487
+ }
488
+ const files = decodeFiles(body.files);
489
+ const folder = checkFolder({ files, dirs: Array.isArray(body.dirs) ? body.dirs : [] });
490
+ return { folder, folderName: typeof body.folderName === 'string' ? body.folderName : '', digest: folderDigest(folder) };
491
+ }
492
+
493
+ function draftView(d) {
494
+ return { draftId: d.draftId, skillId: d.skillId, baseRevisionId: d.baseRevisionId, etag: d.etag, files: d.files.map((f) => ({ path: f.path, digest: f.blobId, executable: !!f.executable })), dirs: d.dirs, updatedAt: d.updatedAt, createdBy: d.createdBy };
495
+ }