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,569 @@
1
+ // ── Installing library revisions into runtime folders ──
2
+ // An install puts one revision of a library skill (lib/skills/library.js) into one or more runtime folders as a
3
+ // native skill folder: Claude Code (<config dir>/skills or a project's .claude/skills), Codex ($CODEX_HOME/skills
4
+ // or a project's .agents/skills, which Antigravity reads too), OpenCode (<config>/opencode/skills or a project's
5
+ // .opencode/skills), Antigravity (~/.gemini/config/skills), and the shared ~/.agents/skills only when the owner
6
+ // turned it on. It is two steps:
7
+ //
8
+ // plan(…) what each target would receive (the export of the revision for that runtime, with its digest),
9
+ // where, which runtimes see that folder (and what else of that name they already see), what is there
10
+ // now (absent, SynaBun's, changed since, not SynaBun's), the checks (the target profile's lint, the
11
+ // scan of exactly those bytes, the review of an Assistant's revision), what each target needs
12
+ // confirmed, and how a running session picks the change up. The plan has a digest.
13
+ // install(…) takes the plan's digest: the plan is worked out again and must be the same, or nothing happens
14
+ // (409 plan_changed, with the new plan). Then each target on its own: the export is written into a
15
+ // staging folder beside the runtime's folder (never inside it), checked against its digest, what is
16
+ // at the destination is backed up and checked again, swapped out, the export swapped in, checked
17
+ // again, and only then the old copy goes. A target that fails is put back as it was; the others are
18
+ // not undone (there is no transaction across runtimes, and the answer never pretends there is).
19
+ //
20
+ // Every install is recorded (DATA_HOME/data/skills/installations.json, schema 2): the revision, the export and its
21
+ // digest, the destination and the runtimes it reaches, what it replaced (its backup), and its state: written
22
+ // (checked by digest), discoverable (the runtime's rules, read statically), eligible (enabled in the runtime's
23
+ // settings, when that can be read), discovered (only what a runtime itself answered), loaded (never claimed), and
24
+ // the refresh a running session needs. A rollback puts back exactly what the install replaced; an uninstall
25
+ // removes only what SynaBun installed and still holds those bytes (or what the owner confirmed as it is now).
26
+
27
+ import { effectiveInstallName, nameChecks } from './scan-context.js';
28
+ export { effectiveInstallName, nameChecks } from './scan-context.js';
29
+ import { randomBytes } from 'node:crypto';
30
+ import { chmodSync, lstatSync, mkdirSync, renameSync, rmSync } from 'node:fs';
31
+ import { basename, dirname, join, resolve } from 'node:path';
32
+ import { PathConfineError, isInside, isPlainName, realPathOf } from '../path-confine.js';
33
+ import { makeConfinedDir, readConfinedFile, removeConfinedEntry, replaceFileAtomic, writeConfinedFile } from '../confined-fs.js';
34
+ import { readManifest } from './digest.js';
35
+ import { LibraryError, digestOf } from './library.js';
36
+ import { buildExport, readHeadOf, readSidecar } from './export.js';
37
+ import { scanArtifact, scanAllows, withExternalScans, scanEvidenceKey } from './scan.js';
38
+ import { bundleNameProblem } from './bundle.js';
39
+ import { PORTABLE_NAME_RE, TARGET_PROFILE, getProfile, lintForProfile } from '../../public/shared/skills/ss-profiles.js';
40
+ import { analyzeResources, argumentDiagnostics } from '../../public/shared/skills/ss-resources.js';
41
+ import { isEditableText } from '../../public/shared/skills/ss-doc.js';
42
+
43
+ export const INSTALL_TARGETS = Object.freeze(['claude', 'codex', 'opencode', 'antigravity', 'agents']);
44
+ export const INSTALLATIONS_SCHEMA = 2;
45
+ const MAX_INSTALLATIONS = 2000;
46
+
47
+ export class DeployError extends PathConfineError {
48
+ constructor(status, message, code, extra = {}) {
49
+ super(status, message);
50
+ this.code = code;
51
+ Object.assign(this, extra);
52
+ }
53
+ }
54
+
55
+ // How a running session sees a change (checked 2026-10-23 in temporary homes: codex-cli 0.164.0's app-server lists
56
+ // a new or changed skill only when skills/list is asked with forceReload, which SynaBun's Codex panel does on every
57
+ // skills:changed; opencode 2.1.4's serve reads its skill folders again on every /api/skill, so SynaBun's OpenCode panel
58
+ // lists it at once. A Codex or OpenCode session open elsewhere, in a terminal, is not SynaBun's to refresh: it needs a
59
+ // new session. Claude Code watches the folders that existed when it started. A Claude Code skills folder is read by
60
+ // a terminal OpenCode only: SynaBun starts every OpenCode serve of its own, the panel's and an Assistant brain's, with
61
+ // OPENCODE_DISABLE_CLAUDE_CODE=1, so those never see it, `terminal-only`.)
62
+ const REFRESH = {
63
+ claude: (rootExisted) => (rootExisted
64
+ ? { kind: 'automatic', message: 'Claude Code picks up the change in sessions that are already open.' }
65
+ : { kind: 'reload-skills', message: 'This skills folder did not exist before: run /reload-skills in Claude Code sessions that are already open (new sessions see it).' }),
66
+ codex: () => ({ kind: 'panel-reload', message: 'SynaBun\'s Codex panel reloads its skills now; a Codex open in a terminal needs a new session.' }),
67
+ opencode: (rootExisted, root) => (root?.family === 'claude'
68
+ ? { kind: 'terminal-only', message: 'An OpenCode open in a terminal reads Claude Code\'s folder (new session); SynaBun\'s own OpenCode panel and an OpenCode Assistant do not: install for OpenCode for those.' }
69
+ : { kind: 'panel-reload', message: 'SynaBun\'s OpenCode panel lists it now; an OpenCode open in a terminal needs a new session.' }),
70
+ // Antigravity reads its skill folders again by itself: there is no command to run.
71
+ antigravity: () => ({ kind: 'automatic', message: 'Antigravity picks up the change by itself.' }),
72
+ };
73
+
74
+ const RUNTIME_LABEL = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', antigravity: 'Antigravity', agents: 'the shared agents folder' };
75
+
76
+ /** A target as a request may name it, checked. */
77
+ export function normalizeTarget(t) {
78
+ if (!t || typeof t !== 'object' || Array.isArray(t)) throw new DeployError(400, 'A target is { runtime, scope }.', 'invalid_target');
79
+ const runtime = t.runtime;
80
+ if (!INSTALL_TARGETS.includes(runtime)) throw new DeployError(400, `A target's runtime is one of ${INSTALL_TARGETS.join(', ')}.`, 'invalid_target');
81
+ const scope = t.scope === undefined ? 'user' : t.scope;
82
+ if (scope !== 'user' && scope !== 'project') throw new DeployError(400, 'A target\'s scope is user or project.', 'invalid_target');
83
+ if (runtime === 'agents' && scope !== 'user') throw new DeployError(400, 'The shared agents folder is a user folder; a project\'s .agents/skills is the codex target with scope project.', 'invalid_target');
84
+ // Antigravity has no project folder of its own: it reads the project's .agents/skills, the Codex project folder.
85
+ if (runtime === 'antigravity' && scope !== 'user') throw new DeployError(400, 'Antigravity reads a project\'s .agents/skills: that is the codex target with scope project.', 'invalid_target');
86
+ const accountId = t.accountId === undefined || t.accountId === null || t.accountId === '' ? 'default' : t.accountId;
87
+ if (typeof accountId !== 'string' || !/^[A-Za-z0-9_-]{1,64}$/.test(accountId)) throw new DeployError(400, 'Not an account id.', 'invalid_target');
88
+ if (scope === 'project' && (typeof t.projectPath !== 'string' || !t.projectPath)) throw new DeployError(400, 'A project target names its project (projectPath).', 'invalid_target');
89
+ const projectPath = scope === 'project' ? resolve(t.projectPath) : null;
90
+ const key = scope === 'project' ? `${runtime}:project:${projectPath}` : `${runtime}:user:${accountId}`;
91
+ return { runtime, scope, accountId, projectPath, key };
92
+ }
93
+
94
+ /**
95
+ * Write a folder into place by swapping it in: staged beside the runtime's folder, checked, then renamed over
96
+ * what is there (which is checked again first and kept until the new folder is in place and checked).
97
+ * @param {object} o
98
+ * @param {string} o.root the runtime's skills folder (made when it is missing)
99
+ * @param {string|null} o.anchor a project the root must really be inside
100
+ * @param {string} o.name the folder's name in the root
101
+ * @param {{ files, dirs }} o.folder
102
+ * @param {string} o.digest what the folder must read back as
103
+ * @param {string|null} o.expectedCurrentDigest what must be at the destination now (null: nothing)
104
+ * @returns {{ destination: string, leftover: string|null, rootCreated: boolean }}
105
+ */
106
+ export function swapFolderIn({ root, anchor = null, name, folder, digest, expectedCurrentDigest, random = () => randomBytes(8).toString('hex') }) {
107
+ if (!isPlainName(name) || bundleNameProblem(name)) throw new DeployError(400, `"${name}" cannot be a skill folder's name.`, 'invalid_name');
108
+ let rootCreated = false;
109
+ try { lstatSync(root); } catch { rootCreated = true; }
110
+ // The runtime's folder: made when missing, and it must be where it says (in its project, when it has one).
111
+ const parent = dirname(root);
112
+ if (anchor) {
113
+ let realParent;
114
+ try { realParent = realPathOf(parent); } catch { throw new DeployError(403, 'The runtime\'s folder leads somewhere that cannot be resolved.', 'root_unsafe'); }
115
+ if (!isInside(realPathOf(anchor), realParent, { allowRoot: true })) throw new DeployError(403, 'The runtime\'s folder leads out of the project through a link.', 'root_unsafe');
116
+ }
117
+ mkdirSync(root, { recursive: true });
118
+ const realRoot = realPathOf(root);
119
+ if (anchor && !isInside(realPathOf(anchor), realRoot)) throw new DeployError(403, 'The runtime\'s folder leads out of the project through a link.', 'root_unsafe');
120
+ const realParent = dirname(realRoot);
121
+ const destination = join(realRoot, name);
122
+ // Staging beside the folder (on the same disk when it can be), never inside it: a runtime that scans its folder
123
+ // deep would otherwise see the staged copy for a moment.
124
+ let stageHome = realParent;
125
+ try { if (lstatSync(realParent).dev !== lstatSync(realRoot).dev) stageHome = realRoot; } catch { stageHome = realRoot; }
126
+ const tag = random();
127
+ const stage = join(stageHome, `.synabun-stage-${tag}`);
128
+ const staged = join(stage, name);
129
+ const old = join(stageHome, `.synabun-old-${tag}`);
130
+ mkdirSync(stage, { mode: 0o700 });
131
+ try {
132
+ makeConfinedDir(stage, staged);
133
+ for (const d of folder.dirs || []) makeConfinedDir(staged, join(staged, d));
134
+ // A file's own mode when the folder carries one (a backup put back by a rollback: a private 0600 file stays
135
+ // 0600); else 0755 / 0644 by its executable bit, as an install writes them.
136
+ for (const f of folder.files) {
137
+ const mode = Number.isInteger(f.mode) ? f.mode & 0o777 : (f.executable ? 0o755 : 0o644);
138
+ writeConfinedFile(staged, join(staged, f.path), f.bytes, { exclusive: true, mode });
139
+ if (Number.isInteger(f.mode)) chmodSync(join(staged, f.path), mode);
140
+ }
141
+ const written = readManifest(staged);
142
+ if (written.digest !== digest) throw new DeployError(500, 'The staged files do not match what was meant to be written. Nothing was changed.', 'stage_mismatch');
143
+ const now = readManifest(destination);
144
+ const nowDigest = now.kind === 'absent' ? null : now.digest;
145
+ if (now.kind !== 'absent' && !now.digest) throw new DeployError(409, `"${destination}" could not be read whole: nothing was replaced.`, 'destination_unreadable');
146
+ if (nowDigest !== (expectedCurrentDigest ?? null)) throw new DeployError(409, `"${destination}" changed since the plan was made: nothing was replaced. Plan again.`, 'destination_changed', { currentDigest: nowDigest });
147
+ let moved = false;
148
+ if (now.kind !== 'absent') { renameSync(destination, old); moved = true; }
149
+ try {
150
+ renameSync(staged, destination);
151
+ } catch (err) {
152
+ if (moved) { try { renameSync(old, destination); } catch { /* the backup holds it */ } }
153
+ throw new DeployError(500, `The new folder could not be put in place (${err?.code || err?.message}). What was there is back.`, 'swap_failed');
154
+ }
155
+ const check = readManifest(destination);
156
+ if (check.digest !== digest) {
157
+ try { rmSync(destination, { recursive: true, force: true }); } catch {}
158
+ if (moved) { try { renameSync(old, destination); } catch {} }
159
+ throw new DeployError(500, 'The installed folder does not read back as it was written. What was there is back.', 'verify_failed');
160
+ }
161
+ // The folders' own modes last (deepest first, the folder itself at the end), once the folder reads back.
162
+ const dirModes = Object.entries(folder.dirModes || {}).sort((a, b) => (b[0] === '.' ? -1 : b[0].split('/').length) - (a[0] === '.' ? -1 : a[0].split('/').length));
163
+ for (const [rel, mode] of dirModes) { try { chmodSync(rel === '.' ? destination : join(destination, rel), mode & 0o777); } catch { /* keeps the mode it was made with */ } }
164
+ let leftover = null;
165
+ if (moved) { try { rmSync(old, { recursive: true, force: true }); } catch { leftover = old; } }
166
+ return { destination, leftover, rootCreated };
167
+ } finally {
168
+ try { rmSync(stage, { recursive: true, force: true }); } catch {}
169
+ }
170
+ }
171
+
172
+ /**
173
+ * @param {object} o
174
+ * @param {object} o.library lib/skills/library.js
175
+ * @param {object} o.roots lib/skills/roots.js resolver
176
+ * @param {object} o.ownership lib/skills/ownership.js installer (ledger, backups)
177
+ * @param {string} o.dataDir DATA_HOME/data/skills
178
+ * @param {() => object} [o.config] { sharedAgentsRoot }
179
+ * @param {(id: string) => Promise<object|null>} [o.catalogEntries] every catalog entry (for what else a runtime sees)
180
+ * @param {(o) => Promise<object|null>} [o.probe] ask a runtime itself whether it lists the skill
181
+ * @param {(o) => string} [o.eligibility] 'enabled' | 'disabled' | 'unknown' from the runtime's settings
182
+ * @param {(o) => { state: string, reason: object|null }} [o.eligibilityDetail] the same, with the setting that turns it off
183
+ * @param {(event) => void} [o.emit]
184
+ * @param {() => string[]} [o.shippedNames] the names of the skills SynaBun ships (what a scan checks names against)
185
+ * @param {(revisionId: string) => ({ installName: string|null }|null)} [o.approvalOf] the approval a revision has
186
+ * (lib/skills/reviews.js approvalOf): an agent's revision installs only under the name it was approved for
187
+ */
188
+ export function createDeployer({ library, roots, ownership, dataDir, config = () => ({}), catalogEntries = async () => [], probe = null, eligibility = () => 'unknown', eligibilityDetail = null, emit = () => {}, now = () => new Date().toISOString(), newId = (p) => `${p}_${randomBytes(10).toString('hex')}`, shippedNames = () => [], approvalOf = null }) {
189
+ let externalScanner = async () => [];
190
+ function setExternalScanner(scan) { externalScanner = scan; }
191
+ const storePath = join(dataDir, 'installations.json');
192
+
193
+ function readStore() {
194
+ try {
195
+ const parsed = JSON.parse(readConfinedFile(dataDir, storePath, { maxBytes: 32 * 1024 * 1024, encoding: 'utf-8' }));
196
+ if (parsed?.schemaVersion === INSTALLATIONS_SCHEMA && Array.isArray(parsed.records)) return parsed;
197
+ } catch { /* none yet */ }
198
+ return { schemaVersion: INSTALLATIONS_SCHEMA, records: [] };
199
+ }
200
+ function writeStore(store) {
201
+ mkdirSync(dataDir, { recursive: true, mode: 0o700 });
202
+ const records = store.records.length > MAX_INSTALLATIONS ? [...store.records.filter((r) => r.active), ...store.records.filter((r) => !r.active).slice(-(MAX_INSTALLATIONS - store.records.filter((r) => r.active).length))] : store.records;
203
+ replaceFileAtomic(storePath, `${JSON.stringify({ schemaVersion: INSTALLATIONS_SCHEMA, records }, null, 2)}\n`, { mode: 0o600 });
204
+ }
205
+ const sameDest = (a, b) => resolve(a) === resolve(b);
206
+
207
+ function listInstallations({ skillId = null, active = null } = {}) {
208
+ return readStore().records.filter((r) => (!skillId || r.skillId === skillId) && (active === null || r.active === active));
209
+ }
210
+ function getInstallation(id) {
211
+ const rec = readStore().records.find((r) => r.installationId === id);
212
+ if (!rec) throw new DeployError(404, 'There is no such installation.', 'installation_not_found');
213
+ return rec;
214
+ }
215
+ function activeAt(destination) {
216
+ return readStore().records.find((r) => r.active && sameDest(r.destination.path, destination)) || null;
217
+ }
218
+
219
+ const installName = effectiveInstallName;
220
+ const shipped = () => { try { return shippedNames() || []; } catch { return []; } };
221
+
222
+ /**
223
+ * The checks of one target: the profile of each runtime that reads that folder. An error blocks only for a
224
+ * runtime the folder is meant for (`primary`); a runtime that reads it only for compatibility (OpenCode in
225
+ * ~/.claude/skills) turns its errors into warnings that name it.
226
+ */
227
+ function lintTarget(runtimes, folder, name, primary = runtimes) {
228
+ const { parsed } = readHeadOf(folder.files);
229
+ const sidecar = readSidecar(folder.files);
230
+ const diagnostics = [];
231
+ const profiles = [];
232
+ for (const runtime of runtimes) {
233
+ const profileId = TARGET_PROFILE[runtime];
234
+ if (!profileId || profiles.includes(profileId)) continue;
235
+ profiles.push(profileId);
236
+ const secondary = !primary.includes(runtime);
237
+ const own = [
238
+ ...lintForProfile(profileId, { parsed, folderName: name, files: folder.files.map((f) => ({ path: f.path, size: f.bytes.length })), sidecar }).all,
239
+ ...argumentDiagnostics(getProfile(profileId).runtime, { body: parsed.body, bodyLine: parsed.bodyLine, declared: [] }).map((d) => ({ ...d, profile: profileId })),
240
+ ];
241
+ diagnostics.push(...own.map((d) => (secondary && d.severity === 'error' ? { ...d, severity: 'warning', message: `${RUNTIME_LABEL[runtime]}, which also reads this folder: ${d.message}` } : d)));
242
+ }
243
+ const textFiles = folder.files.map((f) => ({ path: f.path, size: f.bytes.length, text: isEditableText(f.bytes) ? f.bytes.toString('utf-8') : null }));
244
+ diagnostics.push(...analyzeResources({ files: textFiles, dirs: folder.dirs }).diagnostics);
245
+ // One problem said once, even when two profiles see it.
246
+ const seen = new Set();
247
+ const unique = diagnostics.filter((d) => { const k = `${d.ruleId}\0${d.file}\0${d.line}\0${d.message}`; if (seen.has(k)) return false; seen.add(k); return true; });
248
+ return { profiles, diagnostics: unique, errors: unique.filter((d) => d.severity === 'error').length, warnings: unique.filter((d) => d.severity === 'warning').length };
249
+ }
250
+
251
+ /**
252
+ * What installing a revision into these targets would do.
253
+ * @param {{ revisionId: string, targets: object[], reviewId?: string }} o
254
+ */
255
+ async function plan({ revisionId, targets }) {
256
+ if (!Array.isArray(targets) || !targets.length || targets.length > 12) throw new DeployError(400, 'Name 1 to 12 targets.', 'invalid_target');
257
+ const revision = library.getRevision(revisionId);
258
+ const skill = library.getSkill(revision.skillId);
259
+ const source = library.readRevisionFiles(revisionId);
260
+ const name = installName(skill, source.files);
261
+ const wanted = targets.map(normalizeTarget);
262
+ if (new Set(wanted.map((t) => t.key)).size !== wanted.length) throw new DeployError(400, 'A target is named twice.', 'invalid_target');
263
+ let entries = [];
264
+ try { entries = await catalogEntries(); } catch { entries = []; }
265
+ const agentMade = revision.author?.kind === 'assistant' || revision.author?.kind === 'agent';
266
+ const reviewNeeded = agentMade && skill.pointers.approved !== revisionId;
267
+ // An approval covers the name it was given under: one without a name (from before this check) or under another
268
+ // name is stale, and the revision goes back to review.
269
+ let approvalStale = null;
270
+ if (agentMade && !reviewNeeded && typeof approvalOf === 'function') {
271
+ let approval = null;
272
+ try { approval = approvalOf(revisionId); } catch { approval = null; }
273
+ if (!approval || approval.installName !== name) {
274
+ approvalStale = approval?.installName
275
+ ? `It was approved to install as "${approval.installName}"; it would install as "${name}": review it again.`
276
+ : `Its approval does not say which name it covers (it installs as "${name}"): review it again.`;
277
+ }
278
+ }
279
+ const names = nameChecks(skill, name, shipped());
280
+ const out = [];
281
+ for (const target of wanted) {
282
+ const blocked = [];
283
+ const needs = [];
284
+ const root = roots.installRoot(target);
285
+ if (!root) {
286
+ out.push({ targetKey: target.key, ...target, name, blocked: [{ code: 'no_root', message: target.scope === 'project' ? 'That project is not registered in SynaBun.' : `There is no ${RUNTIME_LABEL[target.runtime]} folder for that account.` }], needs, ready: false });
287
+ continue;
288
+ }
289
+ if (root.policy?.write === 'owner-opt-in') blocked.push({ code: 'shared_root_off', message: 'Installing into ~/.agents/skills is off: turn on the shared agents folder in Settings → Skills first.' });
290
+ else if (root.policy?.write !== 'owner') blocked.push({ code: 'root_read_only', message: root.policy?.reason || 'SynaBun does not write into this folder.' });
291
+ if (root.status !== 'ok' && root.status !== 'absent') blocked.push({ code: 'root_unusable', message: `The folder is ${root.status === 'not-a-folder' ? 'not a folder' : 'unreadable'}: ${root.path}` });
292
+ const runtimes = root.runtimes.length ? root.runtimes : [target.runtime];
293
+ const profileId = target.runtime === 'agents' ? 'portable-six-field' : TARGET_PROFILE[target.runtime];
294
+ // One export per folder: for a folder several runtimes read, the strictest invocation each needs is applied.
295
+ let exported = { files: source.files, dirs: source.dirs, digest: source.revision.contentDigest, transforms: [], diagnostics: [] };
296
+ for (const runtime of runtimes) {
297
+ const next = buildExport({ files: exported.files, dirs: exported.dirs }, TARGET_PROFILE[runtime]);
298
+ exported = { ...next, transforms: [...exported.transforms, ...next.transforms], diagnostics: [...exported.diagnostics, ...next.diagnostics] };
299
+ }
300
+ // Who the folder is for: the target's runtime, or every runtime of the shared folder.
301
+ const primary = target.runtime === 'agents' ? runtimes : [target.runtime];
302
+ const lint = lintTarget(runtimes, exported, name, primary);
303
+ const exportDiagnostics = exported.diagnostics;
304
+ if (lint.errors || exportDiagnostics.some((d) => d.severity === 'error')) blocked.push({ code: 'lint_errors', message: `${lint.errors + exportDiagnostics.filter((d) => d.severity === 'error').length} problem(s) keep it from working in ${runtimes.map((r) => RUNTIME_LABEL[r]).join(' and ')}.` });
305
+ const { parsed } = readHeadOf(exported.files);
306
+ const scan = withExternalScans(scanArtifact({ type: 'skill', name, files: exported.files, frontmatter: parsed.data, digest: exported.digest, knownNames: names.knownNames, reservedNames: names.reservedNames }), await externalScanner({ ...revision, contentDigest: exported.digest }, exported));
307
+ // What SynaBun ships was reviewed with the app it ships in: its findings are shown, not asked about again.
308
+ // Everything else (an import, a folder of the user's, an agent's revision) needs the findings confirmed.
309
+ const trustedBundled = revision.source?.kind === 'bundled' && !scan.external?.length && !['error', 'incomplete'].includes(scan.status);
310
+ if (scan.gate === 'blocked') blocked.push({ code: 'scan_blocked', message: scan.status === 'error' ? `The scan failed (${scan.error}): nothing is installed without one.` : 'The scan found something that blocks this install.' });
311
+ else if (scan.gate === 'review' && !trustedBundled) needs.push({ code: 'acknowledge_scan', digest: exported.digest, message: 'Review the scan\'s findings and confirm them for these exact files.' });
312
+ if (reviewNeeded) blocked.push({ code: 'review_required', message: 'The Assistant made this revision: approve it in the Review queue before it is installed.' });
313
+ else if (approvalStale) blocked.push({ code: 'approval_stale', message: approvalStale });
314
+ const destination = join(root.path, name);
315
+ const inspected = ownership.inspect({ destination, payload: { kind: 'dir', digest: exported.digest } });
316
+ const state = inspected.state;
317
+ const currentDigest = inspected.current.kind === 'absent' ? null : inspected.current.digest;
318
+ if (state === 'unreadable') blocked.push({ code: 'destination_unreadable', message: `"${destination}" cannot be read whole: move it away yourself first.` });
319
+ if (state === 'modified' || state === 'unowned') needs.push({ code: 'confirm_replace', currentDigest, message: `"${destination}" ${state === 'modified' ? 'was installed by SynaBun but has been changed since' : 'was not installed by SynaBun'}. Replace it? A backup is kept.` });
320
+ // What else these runtimes see under this name, elsewhere.
321
+ const overlap = entries.filter((e) => e.kind === 'skill' && (e.invocationName === name || e.name === name) && e.rootId !== root.rootId && e.runtimes.some((r) => runtimes.includes(r)))
322
+ .map((e) => {
323
+ // The page words it from `code`, `name`, `runtimes` and `path` (skillsStudio.publish.overlapOne / overlapMany);
324
+ // `why` is the English for a caller without the page's strings (the MCP tool, a log).
325
+ const seen = e.runtimes.filter((r) => runtimes.includes(r));
326
+ return { id: e.id, code: 'overlap', name, path: e.path, scope: e.scope, runtimes: seen, why: `${seen.map((r) => RUNTIME_LABEL[r]).join(' and ')} also ${seen.length > 1 ? 'see' : 'sees'} a "${name}" skill at ${e.path}.` };
327
+ });
328
+ const refresh = runtimes.map((r) => ({ runtime: r, ...(REFRESH[r] ? REFRESH[r](root.status === 'ok', root) : { kind: 'unknown', message: '' }) }));
329
+ // Whether each runtime's own settings let a skill of this name, at this place, through (read statically; the
330
+ // page offers the one-click fix for a runtime that turns it off). Not part of the plan's digest: it changes
331
+ // nothing of what is written.
332
+ const eligible = {};
333
+ for (const r of runtimes) {
334
+ try {
335
+ const d = typeof eligibilityDetail === 'function' ? eligibilityDetail({ runtime: r, name, skillMd: join(destination, 'SKILL.md'), accountId: target.accountId, projectPath: target.projectPath }) : { state: eligibility({ runtime: r, name, skillMd: join(destination, 'SKILL.md'), accountId: target.accountId, projectPath: target.projectPath }) || 'unknown', reason: null };
336
+ eligible[r] = { state: ['enabled', 'disabled', 'unknown'].includes(d?.state) ? d.state : 'unknown', reason: d?.reason || null };
337
+ } catch { eligible[r] = { state: 'unknown', reason: null }; }
338
+ }
339
+ out.push({
340
+ targetKey: target.key, ...target, name,
341
+ root: { rootId: root.rootId, path: root.path, status: root.status, scope: root.scope, sourceKind: root.sourceKind, policy: root.policy },
342
+ destination, affectedRuntimes: runtimes, overlap,
343
+ export: { profileId, profileVersion: getProfile(profileId)?.version || null, artifactDigest: exported.digest, sourceDigest: revision.contentDigest, transforms: exported.transforms },
344
+ lint: { profiles: lint.profiles, errors: lint.errors, warnings: lint.warnings, diagnostics: [...exportDiagnostics, ...lint.diagnostics].slice(0, 300) },
345
+ scan: { status: scan.status, gate: scan.gate, digest: scan.digest, engineVersion: scan.engineVersion, policy: scan.policy, evidenceKey: scanEvidenceKey(scan), external: scan.external || [], counts: scan.counts, findings: scan.findings.slice(0, 50), coverage: scan.coverage, disclosure: scan.disclosure, trustedBundled },
346
+ destinationState: { state, currentDigest, installedBySynabun: !!inspected.record, activeInstallationId: activeAt(destination)?.installationId || null },
347
+ refresh, eligibility: eligible, blocked, needs, ready: !blocked.length && !needs.length,
348
+ });
349
+ }
350
+ const subject = { revisionId, contentDigest: revision.contentDigest, targets: out.map((t) => [t.targetKey, t.destination || null, t.export?.artifactDigest || null, t.destinationState?.currentDigest ?? null, t.destinationState?.state || null, t.blocked.map((b) => b.code), t.scan?.evidenceKey || null, t.needs.map((n) => [n.code, n.digest || null])]) };
351
+ return {
352
+ skillId: skill.skillId, skillName: skill.name, revisionId, contentDigest: revision.contentDigest, author: revision.author, name,
353
+ targets: out, planDigest: digestOf(subject), createdAt: now(),
354
+ };
355
+ }
356
+
357
+ /**
358
+ * Install a planned revision. `decisions[targetKey]`: `{ expectedDestinationDigest, acknowledgedDigest }` (the
359
+ * person confirmed replacing exactly what is there, and the scan's findings for exactly these bytes).
360
+ */
361
+ async function install({ revisionId, targets, planDigest, decisions = {}, principal = { kind: 'owner' }, reviewId = null, migration = null }) {
362
+ const fresh = await plan({ revisionId, targets });
363
+ if (typeof planDigest !== 'string' || planDigest !== fresh.planDigest) {
364
+ throw new DeployError(409, 'What would be installed changed since the plan was shown. Nothing was installed; look at the new plan.', 'plan_changed', { plan: fresh });
365
+ }
366
+ const results = [];
367
+ for (const t of fresh.targets) {
368
+ const decision = decisions && typeof decisions === 'object' ? (decisions[t.targetKey] || {}) : {};
369
+ if (t.blocked.length) { results.push({ targetKey: t.targetKey, status: 'skipped', reasons: t.blocked }); continue; }
370
+ const missing = t.needs.filter((n) => (n.code === 'confirm_replace' && decision.expectedDestinationDigest !== n.currentDigest)
371
+ || (n.code === 'acknowledge_scan' && decision.acknowledgedDigest !== n.digest));
372
+ if (missing.length) { results.push({ targetKey: t.targetKey, status: 'needs-confirmation', reasons: missing }); continue; }
373
+ const scanOk = scanAllows({ digest: t.scan.digest, status: t.scan.status, gate: t.scan.gate }, { digest: t.export.artifactDigest, acknowledgedDigest: t.scan.trustedBundled ? t.export.artifactDigest : (decision.acknowledgedDigest || null) });
374
+ if (!scanOk.ok) { results.push({ targetKey: t.targetKey, status: 'skipped', reasons: [{ code: scanOk.code, message: scanOk.message }] }); continue; }
375
+ try {
376
+ const source = library.readRevisionFiles(revisionId);
377
+ let exported = { files: source.files, dirs: source.dirs };
378
+ for (const runtime of t.affectedRuntimes) exported = buildExport(exported, TARGET_PROFILE[runtime]);
379
+ if (exported.digest !== t.export.artifactDigest) throw new DeployError(409, 'The export changed while it was being installed.', 'export_changed');
380
+ let backupId = null;
381
+ const previous = activeAt(t.destination);
382
+ if (t.destinationState.state !== 'absent' && t.destinationState.state !== 'identical') {
383
+ backupId = ownership.backup({ destination: t.destination, target: t.runtime, name: t.name, reason: `install of ${revisionId} over ${t.destinationState.state}` });
384
+ }
385
+ let swap = { destination: t.destination, leftover: null, rootCreated: false };
386
+ if (t.destinationState.state !== 'identical') {
387
+ swap = swapFolderIn({ root: t.root.path, anchor: t.scope === 'project' ? t.projectPath : null, name: t.name, folder: exported, digest: t.export.artifactDigest, expectedCurrentDigest: t.destinationState.currentDigest });
388
+ }
389
+ // What the ledger said of this place before: a rollback puts that claim back (or none, when there was none).
390
+ const priorRecord = ownership.recordFor(t.destination);
391
+ ownership.recordWrite({ target: t.runtime, name: t.name, destination: t.destination, digest: t.export.artifactDigest, kind: 'dir', source: { kind: 'library', skillId: fresh.skillId, revisionId, digest: fresh.contentDigest } });
392
+ const installationId = newId('ins');
393
+ const record = {
394
+ schemaVersion: INSTALLATIONS_SCHEMA, installationId, skillId: fresh.skillId, sourceRevisionId: revisionId, sourceDigest: fresh.contentDigest,
395
+ export: { profileId: t.export.profileId, profileVersion: t.export.profileVersion, artifactDigest: t.export.artifactDigest, manifestDigest: t.export.artifactDigest, transforms: t.export.transforms },
396
+ target: { runtime: t.runtime, scope: t.scope, accountId: t.accountId, projectPath: t.projectPath },
397
+ destination: { rootId: t.root.rootId, root: t.root.path, path: t.destination, relativePath: t.name, method: 'copy', affectedRuntimes: t.affectedRuntimes },
398
+ ownership: {
399
+ managedBy: 'synabun', adopted: false, replaced: t.destinationState.state, expectedDestinationDigest: t.destinationState.currentDigest,
400
+ // 'identical': the bytes were there already and nothing was written; a rollback then changes no file.
401
+ wrote: t.destinationState.state !== 'identical',
402
+ priorRecord: priorRecord ? { target: priorRecord.target, name: priorRecord.name, kind: priorRecord.kind, digest: priorRecord.digest, source: priorRecord.source || null } : null,
403
+ },
404
+ previousInstallationId: previous?.installationId || null,
405
+ backup: backupId ? { backupId, manifestDigest: t.destinationState.currentDigest } : null,
406
+ migration, reviewId,
407
+ scanAcknowledgement: (decision.acknowledgedDigest === t.scan.digest || t.scan.trustedBundled) ? { digest: t.scan.digest, evidenceKey: t.scan.evidenceKey, engineVersion: t.scan.engineVersion, policy: t.scan.policy, at: now() } : null,
408
+ authorization: { principal: principal?.kind || 'owner', planDigest },
409
+ transaction: { state: 'committed', at: now(), leftover: swap.leftover },
410
+ state: { written: true, verifiedDigest: t.export.artifactDigest, discoverable: null, eligibility: 'unknown', discovered: null, loadedRevisionId: null, refresh: t.refresh, tested: false, checkedAt: now() },
411
+ active: true, createdAt: now(),
412
+ };
413
+ const store = readStore();
414
+ for (const r of store.records) if (r.active && sameDest(r.destination.path, t.destination)) { r.active = false; r.supersededBy = installationId; }
415
+ store.records.push(record);
416
+ writeStore(store);
417
+ results.push({ targetKey: t.targetKey, status: 'committed', installationId, backupId, destination: t.destination, refresh: t.refresh, unchanged: t.destinationState.state === 'identical', leftover: swap.leftover });
418
+ emit({ type: 'installation.changed', skillId: fresh.skillId, installationId, rootIds: [t.root.rootId], action: 'install' });
419
+ } catch (err) {
420
+ results.push({ targetKey: t.targetKey, status: 'failed', error: err?.message || String(err), code: err?.code || null });
421
+ }
422
+ }
423
+ const committed = results.filter((r) => r.status === 'committed').length;
424
+ return { ok: committed === results.length, partial: committed > 0 && committed < results.length, results, plan: fresh };
425
+ }
426
+
427
+ /**
428
+ * Put back what an installation replaced (or remove it, when nothing was there). An installation that found its
429
+ * exact bytes already there wrote nothing: its rollback changes no file, and gives the place back to whoever had
430
+ * it before (the earlier installation, the ledger's earlier record, or nobody: the user's own folder).
431
+ */
432
+ function rollback(installationId, { expectedDestinationDigest = null, principal = { kind: 'owner' } } = {}) {
433
+ const inst = getInstallation(installationId);
434
+ if (!inst.active) throw new DeployError(409, 'This installation is no longer the one in place.', 'not_active');
435
+ const destination = inst.destination.path;
436
+ const current = readManifest(destination);
437
+ const currentDigest = current.kind === 'absent' ? null : current.digest;
438
+ if (current.kind !== 'absent' && !current.digest) throw new DeployError(409, `"${destination}" cannot be read whole: nothing was changed.`, 'destination_unreadable');
439
+ if (currentDigest !== inst.export.artifactDigest && expectedDestinationDigest !== currentDigest) {
440
+ throw new DeployError(409, `"${destination}" was changed since it was installed. To roll back anyway, confirm it as it is now: a backup is kept.`, 'rollback_conflict', { currentDigest });
441
+ }
442
+ let restoredTo = 'absent';
443
+ const wroteNothing = inst.ownership?.wrote === false || inst.ownership?.replaced === 'identical';
444
+ const safety = !wroteNothing && current.kind !== 'absent' ? ownership.backup({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `rollback of ${installationId}` }) : null;
445
+ if (wroteNothing) {
446
+ restoredTo = 'unchanged';
447
+ } else if (inst.backup?.backupId) {
448
+ const kept = ownership.readBackup(inst.backup.backupId);
449
+ if (kept.kind === 'dir' && !kept.links.length) {
450
+ swapFolderIn({ root: inst.destination.root, anchor: inst.target.scope === 'project' ? inst.target.projectPath : null, name: inst.destination.relativePath, folder: { files: kept.files, dirs: kept.dirs, dirModes: kept.dirModes }, digest: kept.meta.digest, expectedCurrentDigest: currentDigest });
451
+ } else {
452
+ if (current.kind !== 'absent') removeConfinedEntry(inst.destination.root, destination);
453
+ ownership.restore(inst.backup.backupId, { root: inst.destination.root });
454
+ }
455
+ restoredTo = 'previous';
456
+ } else if (current.kind !== 'absent') {
457
+ removeConfinedEntry(inst.destination.root, destination);
458
+ }
459
+ const store = readStore();
460
+ const rec = store.records.find((r) => r.installationId === installationId);
461
+ rec.active = false;
462
+ rec.rolledBackAt = now();
463
+ rec.rollback = { by: principal?.kind || 'owner', safetyBackupId: safety, restoredTo };
464
+ // What was there before is SynaBun's again only when it was an earlier SynaBun install of exactly those bytes.
465
+ const prior = inst.previousInstallationId ? store.records.find((r) => r.installationId === inst.previousInstallationId) : null;
466
+ const back = readManifest(destination);
467
+ const priorRecord = inst.ownership?.priorRecord || null;
468
+ if (prior && back.digest && back.digest === prior.export.artifactDigest) {
469
+ prior.active = true;
470
+ prior.supersededBy = null;
471
+ ownership.recordWrite({ target: prior.target.runtime, name: prior.destination.relativePath, destination, digest: back.digest, kind: 'dir', source: { kind: 'library', skillId: prior.skillId, revisionId: prior.sourceRevisionId, digest: prior.sourceDigest } });
472
+ } else if (priorRecord && back.digest && back.digest === priorRecord.digest) {
473
+ // An install SynaBun made another way (the ledger's record, no installation of it): its claim is back.
474
+ ownership.recordWrite({ target: priorRecord.target, name: priorRecord.name, destination, digest: priorRecord.digest, kind: priorRecord.kind || 'dir', source: priorRecord.source });
475
+ } else {
476
+ ownership.forget({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `rolled back ${installationId}` });
477
+ }
478
+ writeStore(store);
479
+ emit({ type: 'installation.changed', skillId: inst.skillId, installationId, rootIds: [inst.destination.rootId], action: 'rollback' });
480
+ return { ok: true, installationId, restoredTo, safetyBackupId: safety, reactivated: prior && back.digest === prior.export.artifactDigest ? prior.installationId : null };
481
+ }
482
+
483
+ /** Remove an installation's folder: SynaBun's own bytes, or what the owner confirmed as it is now. A backup is kept. */
484
+ function uninstall(installationId, { expectedDestinationDigest = null, principal = { kind: 'owner' } } = {}) {
485
+ const inst = getInstallation(installationId);
486
+ if (!inst.active) throw new DeployError(409, 'This installation is no longer the one in place.', 'not_active');
487
+ const destination = inst.destination.path;
488
+ const current = readManifest(destination);
489
+ const currentDigest = current.kind === 'absent' ? null : current.digest;
490
+ let backupId = null;
491
+ if (current.kind !== 'absent') {
492
+ if (!current.digest) throw new DeployError(409, `"${destination}" cannot be read whole: nothing was removed.`, 'destination_unreadable');
493
+ if (currentDigest !== inst.export.artifactDigest && expectedDestinationDigest !== currentDigest) {
494
+ throw new DeployError(409, `"${destination}" was changed since it was installed. To remove it anyway, confirm it as it is now: a backup is kept.`, 'uninstall_conflict', { currentDigest });
495
+ }
496
+ backupId = ownership.backup({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `uninstall of ${installationId}` });
497
+ const again = readManifest(destination);
498
+ if (again.digest !== currentDigest) throw new DeployError(409, 'That folder changed while it was being removed. Nothing was removed.', 'destination_changed');
499
+ removeConfinedEntry(inst.destination.root, destination);
500
+ }
501
+ ownership.forget({ destination, target: inst.target.runtime, name: inst.destination.relativePath, reason: `uninstalled ${installationId}` });
502
+ const store = readStore();
503
+ const rec = store.records.find((r) => r.installationId === installationId);
504
+ rec.active = false;
505
+ rec.removedAt = now();
506
+ rec.removal = { by: principal?.kind || 'owner', backupId };
507
+ writeStore(store);
508
+ emit({ type: 'installation.changed', skillId: inst.skillId, installationId, rootIds: [inst.destination.rootId], action: 'uninstall' });
509
+ return { ok: true, installationId, backupId, removed: current.kind !== 'absent' };
510
+ }
511
+
512
+ /**
513
+ * Check an installation: the bytes (by digest), what the runtime's rules say of the folder, its settings
514
+ * (eligibility), and — only when asked and a probe exists — whether the runtime itself lists it now.
515
+ */
516
+ async function verify(installationId, { probeRuntime = false } = {}) {
517
+ const inst = getInstallation(installationId);
518
+ const destination = inst.destination.path;
519
+ const current = readManifest(destination);
520
+ const written = current.kind === 'absent' ? 'missing' : (current.digest === inst.export.artifactDigest ? 'verified' : 'changed');
521
+ const checks = [];
522
+ let discoverable = written === 'verified';
523
+ let live = null;
524
+ if (written === 'verified') {
525
+ // The folder must still be one the runtime reads: OPENCODE_CONFIG_DIR set after the install, or an account
526
+ // folder that moved, leaves the skill where nothing looks.
527
+ try { live = roots.listRoots().find((r) => resolve(r.path) === resolve(inst.destination.root)) || null; } catch { live = null; }
528
+ for (const runtime of inst.destination.affectedRuntimes) {
529
+ if (live && live.runtimes.includes(runtime)) continue;
530
+ discoverable = false;
531
+ checks.push({ runtime, ok: false, message: `${RUNTIME_LABEL[runtime] || runtime} does not read ${inst.destination.root} now${live?.policy?.reason ? `: ${live.policy.reason}` : '.'}` });
532
+ }
533
+ try {
534
+ const head = readConfinedFile(destination, join(destination, 'SKILL.md'), { maxBytes: 2 * 1024 * 1024 });
535
+ const files = [{ path: 'SKILL.md', bytes: head }];
536
+ const { parsed } = readHeadOf(files);
537
+ for (const runtime of inst.destination.affectedRuntimes) {
538
+ const lint = lintForProfile(TARGET_PROFILE[runtime], { parsed, folderName: basename(destination) });
539
+ if (lint.errors.length) { discoverable = false; checks.push({ runtime, ok: false, message: lint.errors[0].message }); }
540
+ else if (live && live.runtimes.includes(runtime)) checks.push({ runtime, ok: true, message: `${RUNTIME_LABEL[runtime]}'s rules find it at ${destination}.` });
541
+ }
542
+ } catch (err) { discoverable = false; checks.push({ ok: false, message: `SKILL.md cannot be read (${err?.message || err}).` }); }
543
+ }
544
+ const eligibilityByRuntime = {};
545
+ for (const runtime of inst.destination.affectedRuntimes) {
546
+ try { eligibilityByRuntime[runtime] = eligibility({ runtime, name: inst.destination.relativePath, path: destination, skillMd: join(destination, 'SKILL.md'), accountId: inst.target.accountId, projectPath: inst.target.projectPath }) || 'unknown'; } catch { eligibilityByRuntime[runtime] = 'unknown'; }
547
+ }
548
+ const discovered = {};
549
+ if (probeRuntime && typeof probe === 'function' && written === 'verified') {
550
+ for (const runtime of inst.destination.affectedRuntimes) {
551
+ // A Claude Code folder reaches a terminal OpenCode only: SynaBun's own serve (OPENCODE_DISABLE_CLAUDE_CODE=1)
552
+ // would answer "not listed" for a folder it never reads, so it is not asked (REFRESH `terminal-only`).
553
+ if (runtime === 'opencode' && live?.family === 'claude') { discovered[runtime] = { discovered: null, code: 'terminal-only', method: 'none', reason: 'SynaBun\'s OpenCode never reads Claude Code\'s folder; an OpenCode in a terminal does.' }; continue; }
554
+ try { discovered[runtime] = await probe({ runtime, name: inst.destination.relativePath, skillMd: join(destination, 'SKILL.md'), accountId: inst.target.accountId, projectPath: inst.target.projectPath }); } catch (err) { discovered[runtime] = { discovered: null, code: 'no-answer', error: err?.message || String(err) }; }
555
+ }
556
+ }
557
+ const store = readStore();
558
+ const rec = store.records.find((r) => r.installationId === installationId);
559
+ rec.state = {
560
+ ...rec.state, written: written === 'verified', writtenCheck: written, verifiedDigest: current.digest || null, discoverable, checks,
561
+ eligibility: Object.values(eligibilityByRuntime).includes('disabled') ? 'disabled' : (Object.values(eligibilityByRuntime).every((v) => v === 'enabled') ? 'enabled' : 'unknown'),
562
+ eligibilityByRuntime, discovered: Object.keys(discovered).length ? discovered : rec.state.discovered, loadedRevisionId: null, checkedAt: now(),
563
+ };
564
+ writeStore(store);
565
+ return rec;
566
+ }
567
+
568
+ return { setExternalScanner, plan, install, rollback, uninstall, verify, listInstallations, getInstallation, activeAt, paths: { storePath } };
569
+ }