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,282 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: install in one go
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The one flow behind every Install of the Studio (the Publish tab's button, Review's "Approve & install", the
6
+ // workspace banner): the service plans the install (POST /v2/installations/plan), the owner is asked one question
7
+ // only when the plan asks one (a folder SynaBun did not install or that changed since, the scan's findings: what
8
+ // lib/skills/deploy.js needs confirmed), and the install sends exactly that plan (its digest, the decisions per
9
+ // target). A plan that changed under it is planned again, once, and asked again. The outcome comes back as data
10
+ // (sx-install.js installOutcome) with its words. Every protection of the service is kept: nothing is written
11
+ // without a plan, nothing replaced without its digest, nothing blocked is sent.
12
+
13
+ import { scanFindingText } from './sx-scan.js';
14
+ import { studioErrorText } from './sx-errors.js';
15
+ import { html, setHTML } from './sx-dom.js';
16
+ import { BLOCK_CODES, planModel, resultModel } from './sx-plan.js';
17
+ import { installTargets, wholeTarget } from './sx-subject.js';
18
+ import { confirmations, installOutcome, parseTargetKey, reviewDestinations, assistantsOf, installedRuntimesOf, alsoInstallTargets, alsoInstallRevision, alsoInstallScope } from './sx-install.js';
19
+
20
+ // ── One install at a time per skill ──
21
+ // Every surface that installs (the Publish tab's button, Review's card, the workspace banner) takes this lock for
22
+ // the skill before it approves or plans, and lets it go when the flow ends, however it ends. While it is held every
23
+ // surface shows the install as on its way and refuses a second one (a click, or Enter on a button it disables).
24
+ const _busy = new Map(); // skillId → since
25
+ const _watchers = new Set(); // told when a lock is taken or let go
26
+
27
+ /** Whether an install of this skill is on its way, from any surface. */
28
+ export const installBusy = (skillId) => !!skillId && _busy.has(skillId);
29
+
30
+ /**
31
+ * Take the lock for a skill: what lets it go, or null when it is held already (the caller refuses).
32
+ * @param {string} skillId
33
+ * @returns {(() => void)|null}
34
+ */
35
+ export function acquireInstall(skillId) {
36
+ if (!skillId || _busy.has(skillId)) return null;
37
+ _busy.set(skillId, Date.now());
38
+ tell(skillId, true);
39
+ let done = false;
40
+ return () => { if (done) return; done = true; _busy.delete(skillId); tell(skillId, false); };
41
+ }
42
+
43
+ /** Be told when a lock is taken or let go (the surfaces draw the install as on its way, or not). */
44
+ export function onInstallBusy(fn) { _watchers.add(fn); return () => _watchers.delete(fn); }
45
+ function tell(skillId, busy) { for (const fn of _watchers) { try { fn({ skillId, busy }); } catch { /* a watcher's own trouble */ } } }
46
+
47
+ /** The words of a target: "Claude Code · all projects", "Codex · project Repo", "Claude Code · account work". */
48
+ export function targetWords(ctx, tg) {
49
+ const { t } = ctx;
50
+ const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(tg?.runtime) ? tg.runtime : 'claude'}`);
51
+ if (tg?.scope === 'project') return t('skillsStudio.publish.targetProject', { runtime, project: tg.projectLabel || ctx.projects?.find((p) => p.path === tg.projectPath)?.label || tg.projectPath || '' });
52
+ if (tg?.accountId && tg.accountId !== 'default') return t('skillsStudio.publish.targetAccount', { runtime, account: tg.accountId });
53
+ return t('skillsStudio.publish.targetUser', { runtime });
54
+ }
55
+
56
+ /** The words of a target key (an install's result names targets by key). */
57
+ export const keyWords = (ctx, key) => targetWords(ctx, parseTargetKey(key));
58
+
59
+ /** A folder path, short: the home folder as ~ (the full path stays in the element's title). */
60
+ export function shortPath(path) {
61
+ return String(path || '').replace(/^\/Users\/[^/]+(?=\/)/, '~').replace(/^\/home\/[^/]+(?=\/)/, '~').replace(/^[A-Za-z]:\\Users\\[^\\]+(?=\\)/, '~');
62
+ }
63
+
64
+ /** The owner's assistants in effect (read with the Library: GET /v2/status `assistants`), or null on an older server. */
65
+ export function assistantsNow(ctx) { return assistantsOf(ctx.data?.status || null); }
66
+
67
+ /** The runtimes the resolver found on this computer (GET /v2/status `assistants.installed`), or null when not known. */
68
+ export function installedNow(ctx) { return installedRuntimesOf(ctx.data?.status || null); }
69
+
70
+ /**
71
+ * "Also install for Codex · OpenCode": the assistants in effect that a skill installed somewhere does not reach yet
72
+ * (sx-install.js alsoInstallTargets), with their words. Empty when there is nothing to add.
73
+ */
74
+ export function alsoInstall(ctx, { skill, row }) {
75
+ // A skill installed for one project only adds to that project (sx-install.js alsoInstallScope); its words say so.
76
+ const scope = alsoInstallScope(skill);
77
+ const project = scope.startsWith('project:') ? scope.slice(8) : null;
78
+ const targets = alsoInstallTargets({ skill, row, targets: targetsNow(ctx, { projects: project ? [project] : [] }), assistants: assistantsNow(ctx), installed: installedNow(ctx) });
79
+ return { targets: targets.map(wholeTarget), words: targets.map((t) => (project ? targetWords(ctx, t) : ctx.t(`skillsStudio.runtime.${t.runtime}`))).join(' · ') };
80
+ }
81
+
82
+ /**
83
+ * One press: the version that is installed already (sx-install.js alsoInstallRevision) goes to the assistants that lack it, through the one flow (the plan, one question only when it asks one,
84
+ * then exactly that plan). The outcome's words come back for a toast or a banner; null when nothing was to add.
85
+ */
86
+ export async function alsoInstallNow(ctx, { skill, row }) {
87
+ const { t } = ctx;
88
+ const { targets } = alsoInstall(ctx, { skill, row });
89
+ if (!targets.length || ctx.guest) return null;
90
+ const revisionId = alsoInstallRevision(skill);
91
+ if (!revisionId) return { kind: 'error', line: t('skillsStudio.publish.noVersion'), lines: [] };
92
+ const release = acquireInstall(skill.skillId);
93
+ if (!release) return { kind: 'error', line: t('skillsStudio.publish.busyElsewhere'), lines: [] };
94
+ let out;
95
+ try { out = await installFlow(ctx, { revisionId, targets }); } catch (err) { out = { status: 'failed', error: studioErrorText(ctx.t, err) }; } finally { release(); }
96
+ return flowWords(ctx, out);
97
+ }
98
+
99
+ /** The targets a skill can be installed into now, with the shared folder as Settings has it (read with the Library). */
100
+ export function targetsNow(ctx, { projects = [] } = {}) {
101
+ const config = ctx.data?.config || { sharedAgentsRoot: false };
102
+ return installTargets({ roots: ctx.data?.catalog?.roots || [], config, projects: ctx.projects.filter((p) => projects.includes(p.path)) });
103
+ }
104
+
105
+ /**
106
+ * Where a review's install goes (sx-install.js reviewDestinations), from the same targets every surface uses: the
107
+ * label a button shows and the targets its click sends are one computation. `targets` as requests name them;
108
+ * `words` each destination in words; `missing` what the submission named that is not available here, with why.
109
+ */
110
+ export function reviewTargets(ctx, { review, skill, row }) {
111
+ const projects = [...new Set((review?.targets || []).filter((x) => x?.scope === 'project' && x.projectPath && ctx.projects.some((p) => p.path === x.projectPath)).map((x) => x.projectPath))];
112
+ const all = targetsNow(ctx, { projects });
113
+ const d = reviewDestinations({ review, targets: all, skill, row, assistants: assistantsNow(ctx) });
114
+ return {
115
+ targets: d.targets.map(wholeTarget), all, keys: d.keys, named: d.named, fallback: d.fallback,
116
+ words: all.filter((t) => d.keys.has(t.key)).map((t) => targetWords(ctx, t)),
117
+ missing: d.missing.map((m) => ({ ...m, words: targetWords(ctx, m.target), why: ctx.t(`skillsStudio.publish.unavailable.${m.reason}`) })),
118
+ };
119
+ }
120
+
121
+ /**
122
+ * The workspaces of a skill read it again (its installations, pointers) after an install from another surface, so
123
+ * their header pill, banners and Publish say what the Library row says. `resetPublish` starts their Publish over.
124
+ */
125
+ export async function refreshSkillWorkspaces(ctx, skillId, { resetPublish = false } = {}) {
126
+ if (!skillId) return;
127
+ let fresh = null;
128
+ try { fresh = (await ctx.api.fetchSkill(skillId)).skill; } catch { return; }
129
+ for (const ws of ctx.workspaces.values()) {
130
+ if (ws.skill?.skillId !== skillId) continue;
131
+ ws.skill = fresh;
132
+ if (resetPublish) { ws.pub = null; ws.publishReviewId = null; ws.publishRevisionId = null; }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * The refresh hints of an outcome, worded (one per runtime that needs something). A runtime the resolver did not
138
+ * find on this computer is said as such: the copy waits in its folder, and nothing lists it until it is installed.
139
+ */
140
+ export function refreshWords(ctx, outcome) {
141
+ const installed = installedNow(ctx);
142
+ return (outcome?.refresh || []).map((x) => {
143
+ const kind = Array.isArray(installed) && ['claude', 'codex', 'opencode', 'antigravity'].includes(x.runtime) && !installed.includes(x.runtime) ? 'not-installed' : x.kind;
144
+ return ctx.t(`skillsStudio.publish.refresh.${kind}`, { runtime: ctx.t(`skillsStudio.runtime.${x.runtime}`) });
145
+ });
146
+ }
147
+
148
+ /** Why a runtime's settings turn the skill off, in words (sx-plan.js eligibility reason). */
149
+ export function eligibilityWords(ctx, runtime, reason) {
150
+ const { t } = ctx;
151
+ const name = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(runtime) ? runtime : 'claude'}`);
152
+ const file = shortPath(reason?.file || '');
153
+ const kind = ['codex-config', 'claude-override', 'claude-deny', 'opencode-permission', 'unreadable', 'invalid-json'].includes(reason?.kind) ? reason.kind : 'other';
154
+ return t(`skillsStudio.publish.eligibility.reason.${kind}`, { runtime: name, file, pattern: reason?.pattern || '*', rule: reason?.rule || reason?.key || '' });
155
+ }
156
+
157
+ /**
158
+ * The owner pressed "Enable for <runtime>" on a plan line: the runtime setting that turns the skill off is changed by
159
+ * the server over a backup (POST /v2/eligibility/enable); the words of what happened come back for a toast. A plan
160
+ * is made again by the caller: this changes no file of the skill.
161
+ * @returns {Promise<{ ok: boolean, line: string }>}
162
+ */
163
+ export async function enableFor(ctx, { runtime, line }) {
164
+ const { t } = ctx;
165
+ const name = t(`skillsStudio.runtime.${runtime}`);
166
+ const target = parseTargetKey(line.targetKey);
167
+ const sep = line.destination && line.destination.includes('\\') && !line.destination.includes('/') ? '\\' : '/';
168
+ const body = { runtime, name: line.destination ? line.destination.split(/[\\/]/).pop() : '', skillMd: `${line.destination}${sep}SKILL.md`, accountId: target.accountId, projectPath: target.projectPath };
169
+ const said = (out) => (out.changed === false ? { ok: true, line: t('skillsStudio.publish.enableNothing', { runtime: name }) } : { ok: out.ok !== false, line: t('skillsStudio.publish.enabled', { runtime: name, file: shortPath(out.file || ''), backup: shortPath(out.backup || '') }) });
170
+ try {
171
+ return said(await ctx.api.enableSkillsEligibility(body));
172
+ } catch (err) {
173
+ // OpenCode's rules deny every skill: loosened only after the owner's word (the server refuses first).
174
+ if (err?.code === 'deny_all' && runtime === 'opencode' && typeof ctx.ask === 'function') {
175
+ const answer = await ctx.ask({
176
+ title: t('skillsStudio.publish.enableDenyAllTitle', { runtime: name }),
177
+ lines: [t('skillsStudio.publish.enableDenyAllBody', { file: shortPath(err.body?.file || err.file || '') })],
178
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'go', label: t('skillsStudio.publish.enableDenyAllGo'), kind: 'danger' }],
179
+ focus: 'cancel',
180
+ });
181
+ if (answer !== 'go') return { ok: false, line: t('skillsStudio.publish.enableFailed', { runtime: name, error: studioErrorText(ctx.t, err) }) };
182
+ try { return said(await ctx.api.enableSkillsEligibility({ ...body, confirmDenyAll: true })); } catch (e2) { return { ok: false, line: t('skillsStudio.publish.enableFailed', { runtime: name, error: studioErrorText(ctx.t, e2) }) }; }
183
+ }
184
+ return { ok: false, line: t('skillsStudio.publish.enableFailed', { runtime: name, error: studioErrorText(ctx.t, err) }) };
185
+ }
186
+ }
187
+
188
+ /** An outcome in words: the line, the hints for open sessions, what failed. */
189
+ export function outcomeWords(ctx, outcome) {
190
+ const { t } = ctx;
191
+ const where = outcome.ok.map((k) => keyWords(ctx, k)).join(', ');
192
+ const failed = outcome.failed.map((f) => keyWords(ctx, f.targetKey)).join(', ');
193
+ const reason = (f) => f?.code && BLOCK_CODES.includes(f.code) ? t(`skillsStudio.publish.blocked.${f.code}`) : f?.code || f?.reason ? studioErrorText(t, f) : f ? t(`skillsStudio.publish.result.${f.status}`) : failed;
194
+ const line = outcome.ok.length && !outcome.failed.length ? t('skillsStudio.publish.doneFor', { where })
195
+ : outcome.ok.length ? t('skillsStudio.publish.doneSome', { where, failed })
196
+ : t('skillsStudio.publish.doneNone', { reason: reason(outcome.failed[0]) });
197
+ return { line, refresh: refreshWords(ctx, outcome), failed: outcome.failed.map((f) => `${keyWords(ctx, f.targetKey)}: ${reason(f)}`) };
198
+ }
199
+
200
+ /** What blocks a plan's targets, in words (one line per target). */
201
+ export function blockedWords(ctx, rows) {
202
+ const { t } = ctx;
203
+ return (rows || []).map((r) => t('skillsStudio.publish.blockedFor', { target: keyWords(ctx, r.targetKey), reason: r.blocked.map((b) => t(`skillsStudio.publish.blocked.${b.code}`)).join(' ') }));
204
+ }
205
+
206
+ /**
207
+ * Ask what the plan asks, in one question, or nothing when it asks nothing.
208
+ * @returns {Promise<object|null>} the decisions to send (empty when nothing was asked), null when the owner said no
209
+ */
210
+ export async function askConfirmations(ctx, model) {
211
+ const { t } = ctx;
212
+ const c = confirmations(model);
213
+ if (!c.replace.length && !c.scan.length) return c.decisions;
214
+ const box = document.createElement('div');
215
+ box.className = 'sx-confirm-list';
216
+ setHTML(box, html`${c.replace.map((r) => html`<p class="sx-confirm-item"><strong>${keyWords(ctx, r.targetKey)}</strong>: <span class="sx-mono">${shortPath(r.destination)}</span> ${t(`skillsStudio.publish.confirmWhy.${r.replace.state === 'modified' ? 'modified' : 'unowned'}`)} ${t('skillsStudio.publish.confirmReplaced')}</p>`)}
217
+ ${c.scan.map((r) => html`<div class="sx-confirm-item"><p><strong>${keyWords(ctx, r.targetKey)}</strong>: ${t('skillsStudio.publish.confirmScanLine')}</p>
218
+ <ul class="sx-findings">${r.scanAck.findings.map((f) => html`<li class="sx-finding sx-finding--${f.severity}"><span class="sx-finding-sev">${t(`skillsStudio.review.severity.${['critical', 'high', 'medium', 'low'].includes(f.severity) ? f.severity : 'low'}`)}</span> ${scanFindingText(t, f)}${f.file ? html` <span class="sx-mono sx-dim">${f.file}${f.line ? `:${f.line}` : ''}</span>` : ''}</li>`)}</ul></div>`)}`);
219
+ const answer = await ctx.ask({
220
+ title: t('skillsStudio.publish.confirmTitle'),
221
+ lines: [t(c.replace.length ? 'skillsStudio.publish.confirmBodyReplace' : 'skillsStudio.publish.confirmBodyScan')],
222
+ extra: box,
223
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'go', label: t(c.replace.length ? 'skillsStudio.publish.confirmGo' : 'skillsStudio.publish.confirmGoScan'), kind: c.replace.length ? 'danger' : 'primary' }],
224
+ focus: 'cancel',
225
+ wide: c.scan.length > 0,
226
+ });
227
+ return answer === 'go' ? c.decisions : null;
228
+ }
229
+
230
+ /**
231
+ * Plan, ask only what must be asked, install exactly that plan.
232
+ * @param {object} ctx the Studio's context
233
+ * @param {{ revisionId: string, targets: object[], reviewId?: string|null, plan?: object|null, onPlanned?: (model: object) => void }} o
234
+ * plan: a plan already made for exactly this ask (sx-plan.js planModel): used instead of asking for one
235
+ * @returns {Promise<{ status: 'done'|'blocked'|'cancelled'|'plan-failed'|'failed'|'gone', plan?: object, results?: object[], outcome?: object, blocked?: object[], error?: string }>}
236
+ */
237
+ export async function installFlow(ctx, { revisionId, targets, reviewId = null, plan = null, onPlanned = null }) {
238
+ const gen = ctx.gen();
239
+ let model = plan;
240
+ for (let attempt = 0; attempt < 2; attempt++) {
241
+ if (!model) {
242
+ try { model = planModel((await ctx.api.planSkillsInstall(revisionId, targets)).plan); } catch (err) { return { status: 'plan-failed', error: studioErrorText(ctx.t, err) }; }
243
+ if (!ctx.alive(gen)) return { status: 'gone' };
244
+ onPlanned?.(model);
245
+ }
246
+ const c = confirmations(model);
247
+ if (!c.ready.length) return { status: 'blocked', plan: model, blocked: c.blocked };
248
+ const decisions = await askConfirmations(ctx, model);
249
+ if (!ctx.alive(gen)) return { status: 'gone' };
250
+ if (decisions === null) return { status: 'cancelled', plan: model };
251
+ let result = null;
252
+ try {
253
+ result = await ctx.api.installSkillsRevision({ revisionId, targets, planDigest: model.planDigest, decisions, ...(reviewId ? { reviewId } : {}) });
254
+ } catch (err) {
255
+ if (!ctx.alive(gen)) return { status: 'gone' };
256
+ if (err?.status === 207 && err.body) result = err.body;
257
+ else if (err?.code === 'plan_changed' && err.body?.plan) { model = planModel(err.body.plan); onPlanned?.(model); continue; } // what is there changed meanwhile: planned again, asked again
258
+ else return { status: 'failed', error: studioErrorText(ctx.t, err), plan: model };
259
+ }
260
+ if (!ctx.alive(gen)) return { status: 'gone' };
261
+ const results = resultModel(result);
262
+ return { status: 'done', plan: model, results, outcome: installOutcome(results), blocked: c.blocked };
263
+ }
264
+ return { status: 'failed', error: ctx.t('skillsStudio.publish.planChanged'), plan: model };
265
+ }
266
+
267
+ /**
268
+ * The words a finished flow gets, for a toast and a notice: null when nothing is to be said (gone, cancelled).
269
+ * @returns {{ kind: 'info'|'error', line: string, lines: string[] }|null}
270
+ */
271
+ export function flowWords(ctx, out) {
272
+ const { t } = ctx;
273
+ if (!out || out.status === 'gone' || out.status === 'cancelled') return null;
274
+ if (out.status === 'done') {
275
+ const w = outcomeWords(ctx, out.outcome);
276
+ const extra = [...w.refresh, ...w.failed, ...blockedWords(ctx, out.blocked)];
277
+ return { kind: out.outcome.all ? 'info' : 'error', line: w.line, lines: extra };
278
+ }
279
+ if (out.status === 'blocked') return { kind: 'error', line: t('skillsStudio.publish.notInstalledBlocked'), lines: blockedWords(ctx, out.blocked) };
280
+ if (out.status === 'plan-failed') return { kind: 'error', line: t('skillsStudio.publish.planFailed', { error: out.error }), lines: [] };
281
+ return { kind: 'error', line: t('skillsStudio.publish.installFailed', { error: out.error || '' }), lines: [] };
282
+ }
@@ -0,0 +1,313 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: the simple install flow, as data
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // Installing an approved skill is one decision (where) and one press (Install). This module decides, without a
6
+ // page: the one status a library skill shows everywhere (the workspace header, its Library row, the banner), what
7
+ // is ticked when Publish opens (the review's targets, else where the skill already is, else Claude Code for every
8
+ // project: never everything), the simple picker (one tile per runtime in one scope; everything else, accounts,
9
+ // the shared folder, other projects, lives under Advanced and is only reported here), the version picked by
10
+ // default (the approved one when the latest was made by an agent and is not approved), the one line each target
11
+ // of a plan gets (what will happen there, as a code the view words), what an install must ask first (a folder
12
+ // SynaBun did not install or that changed since, the scan's findings) and never asks otherwise, the words of the
13
+ // one button, and the outcome of an install. The plan and the install themselves stay the service's
14
+ // (lib/skills/deploy.js) through sx-plan.js: nothing here changes what is sent, only what is asked and shown.
15
+ // No DOM; imports sx-subject.js and sx-plan.js only.
16
+
17
+ import { targetKey, targetState, defaultTargets, seenThrough } from './sx-subject.js';
18
+ import { installableRows, decisionsOf } from './sx-plan.js';
19
+
20
+ export const STATUS_CODES = Object.freeze(['draft', 'review', 'changes', 'approved', 'installed', 'outdated', 'none']);
21
+ export const LINE_CODES = Object.freeze(['create', 'replace-owned', 'replace-modified', 'replace-unowned', 'identical', 'blocked']);
22
+ // What a tile says of a runtime: the target's state, 'via-claude' when OpenCode sees the skill through Claude Code's
23
+ // folder (ticked, or holding it) and is not ticked itself, 'unavailable' when there is no folder for it here.
24
+ export const TILE_STATES = Object.freeze(['installed', 'outdated', 'present', 'bridge', 'none', 'off', 'unavailable', 'via-claude', 'not-installed']);
25
+
26
+ /** The runtimes that are installed on this computer (the status' `assistants.installed`), or null when not known. */
27
+ export function installedRuntimesOf(source) {
28
+ const list_ = source?.assistants?.installed ?? source?.installed ?? null;
29
+ return Array.isArray(list_) ? list_.filter((r) => typeof r === 'string') : null;
30
+ }
31
+
32
+ /**
33
+ * "Also install for Codex · OpenCode": the assistants in effect (user scope, default account) that do not have a
34
+ * skill that is installed somewhere already; empty when nothing is missing, when nothing is installed yet (Publish
35
+ * is the way), or when the skill is not a library skill. A runtime known not to be installed here is left out.
36
+ * @param {{ skill?: object|null, row?: object|null, targets: object[], assistants?: string[]|null, installed?: string[]|null }} o
37
+ * @returns {object[]} targets of installTargets()
38
+ */
39
+ export function alsoInstallTargets({ skill = null, row = null, targets = [], assistants = null, installed = null } = {}) {
40
+ if (!skill || row?.source === 'synabun' || !list(skill.installations).length) return [];
41
+ const wanted = Array.isArray(assistants) && assistants.length ? assistants : [];
42
+ const scope = alsoInstallScope(skill);
43
+ return SIMPLE_RUNTIMES.filter((r) => wanted.includes(r) && !(Array.isArray(installed) && !installed.includes(r)))
44
+ .map((r) => list(targets).find((t) => t.key === keyIn(r, scope)) || null)
45
+ .filter((t) => t && !t.off && !['installed', 'outdated', 'present'].includes(targetState(t, { skill, row }).state));
46
+ }
47
+
48
+ /**
49
+ * The scope "Also install for …" adds to: the one project every installation is in (a skill installed for one
50
+ * project only is never offered to every project), else the owner's ('user').
51
+ */
52
+ export function alsoInstallScope(skill) {
53
+ const installs = list(skill?.installations);
54
+ const projects = new Set(installs.map((i) => (i?.scope === 'project' && i.projectPath ? i.projectPath : '')));
55
+ return projects.size === 1 && !projects.has('') ? `project:${[...projects][0]}` : 'user';
56
+ }
57
+
58
+ /**
59
+ * The version "Also install for …" sends: the one the installations hold (the one most of them hold when they
60
+ * differ; on a tie the approved one, then the latest), else the approved one, else the latest; null for none.
61
+ */
62
+ export function alsoInstallRevision(skill) {
63
+ const held = list(skill?.installations).map((i) => i?.sourceRevisionId).filter((r) => typeof r === 'string' && r);
64
+ const pointers = skill?.pointers || {};
65
+ if (!held.length) return pointers.approved || pointers.latest || null;
66
+ const count = new Map();
67
+ for (const r of held) count.set(r, (count.get(r) || 0) + 1);
68
+ const rank = (r) => (r === pointers.approved ? 2 : r === pointers.latest ? 1 : 0);
69
+ return [...count.entries()].sort((a, b) => b[1] - a[1] || rank(b[0]) - rank(a[0]) || held.indexOf(a[0]) - held.indexOf(b[0]))[0][0];
70
+ }
71
+ export const BUTTON_CODES = Object.freeze(['install', 'update', 'installed', 'checking', 'installing', 'nothing', 'blocked']);
72
+ export const SIMPLE_RUNTIMES = Object.freeze(['claude', 'codex', 'opencode', 'antigravity']);
73
+ // The runtimes with a project folder of their own. Antigravity has none: in a project it reads .agents/skills, the
74
+ // Codex tile's folder, so a project scope shows no Antigravity tile (the plan says Antigravity reads that folder).
75
+ const PROJECT_RUNTIMES = new Set(['claude', 'codex', 'opencode']);
76
+ const inProject = (scope) => String(scope || '').startsWith('project:');
77
+ const APPROVED_STATES = ['approved', 'partial', 'publish_failed'];
78
+
79
+ const list = (v) => (Array.isArray(v) ? v : []);
80
+
81
+ /** Where an installation is, as a target (the view words it: "Claude Code · all projects"). */
82
+ export function whereOf(i) {
83
+ const scope = i?.scope === 'project' ? 'project' : 'user';
84
+ return { runtime: i?.runtime || 'claude', scope, projectPath: scope === 'project' ? i.projectPath || null : null, accountId: scope === 'user' ? i?.accountId || 'default' : 'default', outdated: !!i?.updateAvailable };
85
+ }
86
+
87
+ /**
88
+ * The one status of a library skill: draft (no version yet), in review, changes asked for, approved but not
89
+ * installed, installed (where), installed with an update available, or not installed. Null for what is not a
90
+ * library skill (a plugin's, SynaBun's own, a folder not adopted): those keep their source and health.
91
+ * @param {{ skill?: object|null, row?: object|null }} o the library summary (GET /skills) and its Library row
92
+ */
93
+ export function installStatus({ skill = null, row = null } = {}) {
94
+ if (!skill || row?.source === 'synabun') return null;
95
+ const installs = list(skill.installations);
96
+ const review = row?.review || null;
97
+ const reviewState = review?.state || null;
98
+ if (installs.length) {
99
+ const outdated = !row?.keptUpdate && installs.some((i) => !!i.updateAvailable);
100
+ return { code: outdated ? 'outdated' : 'installed', where: installs.map(whereOf), reviewState, reviewId: review?.reviewId || null };
101
+ }
102
+ if (reviewState === 'pending') return { code: 'review', where: [], reviewState, reviewId: review.reviewId };
103
+ if (reviewState === 'changes_requested') return { code: 'changes', where: [], reviewState, reviewId: review.reviewId };
104
+ if (APPROVED_STATES.includes(reviewState)) return { code: 'approved', where: [], reviewState, reviewId: review.reviewId };
105
+ if (!skill.revisions) return { code: 'draft', where: [], reviewState: null, reviewId: null };
106
+ return { code: 'none', where: [], reviewState: null, reviewId: null };
107
+ }
108
+
109
+ /**
110
+ * What is ticked when Publish opens: the targets the review named (those that exist and are on), else where the
111
+ * skill already is, else the owner's assistants (sx-subject.js defaultTargets: never everything, no second copy
112
+ * for a runtime that reads a ticked folder).
113
+ * @param {{ review?: object|null, targets: object[], skill?: object|null, row?: object|null, assistants?: string[]|null }} o targets: sx-subject.js installTargets()
114
+ * @returns {Set<string>} target keys
115
+ */
116
+ export function defaultSelection({ review = null, targets = [], skill = null, row = null, assistants = null } = {}) {
117
+ const named = list(review?.targets).filter((x) => x && typeof x.runtime === 'string');
118
+ const keys = new Set(named.map((x) => targetKey(x)));
119
+ const fromReview = targets.filter((x) => !x.off && keys.has(x.key)).map((x) => x.key);
120
+ if (fromReview.length) return new Set(fromReview);
121
+ // The review named targets and none of them is here (another computer's project, the shared folder off): nothing
122
+ // is ticked, and the view says what is missing; never a silent fallback to the assistants.
123
+ if (named.length) return new Set();
124
+ return defaultTargets(targets, { skill, row, assistants });
125
+ }
126
+
127
+ /** The assistants in effect (GET /v2/config or /status `assistants.effective`), or null when the server has none. */
128
+ export function assistantsOf(source) {
129
+ const list_ = source?.assistants?.effective ?? source?.effective ?? null;
130
+ return Array.isArray(list_) && list_.length ? list_.filter((r) => typeof r === 'string') : null;
131
+ }
132
+
133
+ /** The scope a selection sits in: 'user', 'project:<path>' when every ticked target is of one project, else null (mixed). */
134
+ export function scopeOf(checked, targets) {
135
+ const picked = list(targets).filter((t) => checked?.has?.(t.key));
136
+ const scopes = new Set(picked.map((t) => (t.scope === 'project' ? `project:${t.projectPath}` : 'user')));
137
+ if (scopes.size === 1) return [...scopes][0];
138
+ return scopes.size ? null : 'user';
139
+ }
140
+
141
+ /** The key of a runtime's target in a scope ('user' or 'project:<path>'), the default account. */
142
+ export const keyIn = (runtime, scope) => (scope && scope.startsWith('project:') ? `${runtime}:project:${scope.slice(8)}` : `${runtime}:user:default`);
143
+
144
+ /**
145
+ * The simple picker: one tile per runtime for the scope chosen (ticked or not, what is there now), and whatever
146
+ * else is ticked (another account, the shared folder, another project: changed under Advanced only).
147
+ * @param {{ targets: object[], checked: Set<string>, scope: string, skill?: object|null, row?: object|null, revisionId?: string|null }} o
148
+ */
149
+ export function simplePicker({ targets = [], checked = new Set(), scope = 'user', skill = null, row = null, revisionId = null, installed = null } = {}) {
150
+ const tiles = SIMPLE_RUNTIMES.filter((runtime) => !inProject(scope) || PROJECT_RUNTIMES.has(runtime)).map((runtime) => {
151
+ const key = keyIn(runtime, scope);
152
+ const target = list(targets).find((t) => t.key === key) || null;
153
+ const st = target ? targetState(target, { skill, row, revisionId }) : { state: 'none', path: null };
154
+ const on = !!target && checked.has(key);
155
+ const available = !!target && !target.off;
156
+ // A terminal OpenCode reads Claude Code's folder: with that one ticked (or holding the skill) and OpenCode not
157
+ // ticked, the tile says so; SynaBun's own OpenCode panel does not read it, so the tile also says a copy is needed.
158
+ const via = available && !on && st.state === 'none' ? seenThrough(runtime, list(targets), checked, { scope, skill, row }) : null;
159
+ // A runtime the resolver did not find on this computer: said on the tile (the folder can still be written for a
160
+ // later install of the runtime; nothing fails), never ticked by default (the detected assistants leave it out).
161
+ const notInstalled = Array.isArray(installed) && !installed.includes(runtime);
162
+ const tileState = !available ? 'unavailable' : notInstalled && st.state === 'none' ? 'not-installed' : via ? 'via-claude' : st.state;
163
+ return { runtime, key, target, on, available, state: target ? st.state : 'none', path: st.path || target?.path || null, via, notInstalled, tileState };
164
+ });
165
+ const own = new Set(tiles.map((x) => x.key));
166
+ // What is ticked beyond the tiles, and would be sent: a target that is off (the shared folder turned off in
167
+ // Settings) is never sent, so it is neither counted nor listed.
168
+ const extra = list(targets).filter((t) => checked.has(t.key) && !own.has(t.key) && !t.off);
169
+ return { tiles, extra };
170
+ }
171
+
172
+ /** The selection moved to another scope: each ticked tile of `from` is ticked in `to` instead; the rest stays. */
173
+ export function moveScope(checked, from, to) {
174
+ const next = new Set();
175
+ for (const key of checked || []) {
176
+ const runtime = SIMPLE_RUNTIMES.find((r) => key === keyIn(r, from));
177
+ // A runtime with no folder of its own in the new scope (Antigravity in a project) is not ticked there.
178
+ if (runtime && inProject(to) && !PROJECT_RUNTIMES.has(runtime)) continue;
179
+ next.add(runtime ? keyIn(runtime, to) : key);
180
+ }
181
+ return next;
182
+ }
183
+
184
+ /**
185
+ * The version Publish picks by default: the one asked for, else what SynaBun ships for its own skill, else the
186
+ * latest, unless the latest was made by an agent and is not the approved one (then the approved one: the latest
187
+ * would be refused until it is reviewed).
188
+ * @param {{ skill?: object|null, revisions?: object[], bundled?: boolean, asked?: string|null }} o
189
+ */
190
+ export function defaultRevisionId({ skill = null, revisions = [], bundled = false, asked = null } = {}) {
191
+ if (asked) return asked;
192
+ const p = skill?.pointers || {};
193
+ if (bundled) return p.upstream || p.approved || p.latest || null;
194
+ const latest = list(revisions).find((r) => r.revisionId === p.latest) || null;
195
+ const agentMade = !!latest && ['assistant', 'agent'].includes(latest.author?.kind);
196
+ if (agentMade && p.approved && p.approved !== p.latest) return p.approved;
197
+ return p.latest || p.approved || null;
198
+ }
199
+
200
+ /** The one plain label of a version in the install path (the rest, candidate and best, stay in Versions). */
201
+ export function plainLabel(labels) {
202
+ const l = list(labels);
203
+ if (l.includes('approved')) return 'approved';
204
+ if (l.includes('upstream')) return 'upstream';
205
+ if (l.includes('latest')) return 'latest';
206
+ return null;
207
+ }
208
+
209
+ /**
210
+ * One line per target of a plan (sx-plan.js planModel): what will happen there, as a code. `asks` says what the
211
+ * owner is asked before it goes (a folder to replace, the scan's findings); `notes` are secondary facts.
212
+ */
213
+ export function planLines(model) {
214
+ return list(model?.rows).map((row) => {
215
+ let code = 'create';
216
+ if (row.blocked.length) code = 'blocked';
217
+ else if (row.state === 'identical') code = 'identical';
218
+ else if (row.replace) code = row.replace.state === 'modified' ? 'replace-modified' : 'replace-unowned';
219
+ else if (row.state === 'owned') code = 'replace-owned';
220
+ return {
221
+ targetKey: row.targetKey, runtime: row.runtime, scope: row.scope, accountId: row.accountId, projectPath: row.projectPath,
222
+ code, destination: row.destination || null,
223
+ blocked: row.blocked, asks: { replace: !!row.replace, scan: !!row.scanAck },
224
+ alsoReadBy: row.affectedRuntimes.filter((r) => r !== row.runtime),
225
+ overlap: row.overlap, transforms: row.transforms, refresh: row.refresh, name: row.name || '',
226
+ // The runtimes whose own settings turn a skill of this name off here (the view offers "Enable for …").
227
+ disabledFor: Object.entries(row.eligibility || {}).filter(([, e]) => e.state === 'disabled').map(([runtime, e]) => ({ runtime, reason: e.reason })),
228
+ unknownFor: Object.entries(row.eligibility || {}).filter(([, e]) => e.state === 'unknown' && e.reason).map(([runtime, e]) => ({ runtime, reason: e.reason })),
229
+ };
230
+ });
231
+ }
232
+
233
+ /**
234
+ * What an install must ask first, and nothing else: the folders to replace (not SynaBun's, or changed since) and
235
+ * the scan findings to read; what is blocked is said, never asked; the rest goes without a question.
236
+ * @returns {{ replace: object[], scan: object[], blocked: object[], ready: object[], decisions: object }} decisions:
237
+ * what the install sends when the owner confirms everything asked (sx-plan.js decisionsOf)
238
+ */
239
+ export function confirmations(model) {
240
+ const rows = list(model?.rows);
241
+ const replace = rows.filter((r) => !r.blocked.length && r.replace);
242
+ const scan = rows.filter((r) => !r.blocked.length && r.scanAck);
243
+ const blocked = rows.filter((r) => r.blocked.length);
244
+ const keys = { replace: replace.map((r) => r.targetKey), scan: scan.map((r) => r.targetKey) };
245
+ return { replace, scan, blocked, ready: installableRows(model, keys), decisions: decisionsOf(model, keys) };
246
+ }
247
+
248
+ /**
249
+ * The one button: its code and whether it can be pressed.
250
+ * @param {{ picked: number, states?: string[], model?: object|null, checking?: boolean, busy?: boolean, error?: boolean }} o
251
+ * picked: how many targets are ticked; states: what each ticked target has now (targetState); model: the plan
252
+ */
253
+ export function installButton({ picked = 0, states = [], model = null, checking = false, busy = false, error = false } = {}) {
254
+ if (busy) return { code: 'installing', enabled: false };
255
+ if (!picked) return { code: 'nothing', enabled: false };
256
+ if (checking || (!model && !error)) return { code: 'checking', enabled: false };
257
+ if (error) return { code: 'install', enabled: false };
258
+ const rows = list(model?.rows);
259
+ if (rows.length && rows.every((r) => r.blocked.length)) return { code: 'blocked', enabled: false };
260
+ if (rows.length && rows.every((r) => r.blocked.length || r.state === 'identical')) return { code: 'installed', enabled: false };
261
+ return { code: states.includes('outdated') ? 'update' : 'install', enabled: true };
262
+ }
263
+
264
+ /** An install's outcome (sx-plan.js resultModel), summed up: where it went, where it did not, how sessions pick it up. */
265
+ export function installOutcome(results) {
266
+ const rows = list(results);
267
+ const ok = rows.filter((r) => r.status === 'committed');
268
+ const failed = rows.filter((r) => r.status !== 'committed');
269
+ const refresh = [];
270
+ for (const r of ok) for (const x of r.refresh) if (x.kind !== 'automatic' && !refresh.some((y) => y.runtime === x.runtime && y.kind === x.kind)) refresh.push({ runtime: x.runtime, kind: x.kind });
271
+ return { ok: ok.map((r) => r.targetKey), failed: failed.map((r) => ({ targetKey: r.targetKey, status: r.status, reason: r.error || r.reasons[0]?.message || '', ...(r.code || r.reasons[0]?.code ? { code: r.code || r.reasons[0].code } : {}) })), refresh, all: rows.length > 0 && !failed.length };
272
+ }
273
+
274
+ /** A target key back as a target ({ runtime, scope, accountId, projectPath }): the words the view gives it. */
275
+ export function parseTargetKey(key) {
276
+ const s = String(key || '');
277
+ const at = s.indexOf(':');
278
+ const runtime = at > 0 ? s.slice(0, at) : s;
279
+ const rest = at > 0 ? s.slice(at + 1) : '';
280
+ if (rest.startsWith('project:')) return { runtime, scope: 'project', projectPath: rest.slice(8), accountId: 'default' };
281
+ return { runtime, scope: 'user', projectPath: null, accountId: rest.startsWith('user:') ? rest.slice(5) || 'default' : 'default' };
282
+ }
283
+
284
+ /**
285
+ * Where a review's install goes, said whole, never a silent fallback: the targets the submission named that exist
286
+ * here and are on (`missing` names each one that does not, with why: no folder for it, or the shared folder turned
287
+ * off); when it named none, where the skill is already installed, else Claude Code for every project (`fallback`
288
+ * says which). When it named targets and none is available, nothing (`targets` empty): the caller refuses and
289
+ * says why, and never installs elsewhere.
290
+ * @param {{ review?: object|null, targets: object[], skill?: object|null, row?: object|null, assistants?: string[]|null }} o targets: installTargets()
291
+ * @returns {{ keys: Set<string>, targets: object[], named: object[], missing: { target: object, reason: 'off'|'missing' }[],
292
+ * fallback: 'installed'|'claude'|'assistants'|null, projects: string[] }}
293
+ */
294
+ export function reviewDestinations({ review = null, targets = [], skill = null, row = null, assistants = null } = {}) {
295
+ const named = list(review?.targets).filter((x) => x && typeof x.runtime === 'string');
296
+ const projects = [...new Set(named.filter((x) => x.scope === 'project' && x.projectPath).map((x) => x.projectPath))];
297
+ const all = list(targets);
298
+ if (named.length) {
299
+ const keys = new Set();
300
+ const missing = [];
301
+ for (const x of named) {
302
+ const key = targetKey(x);
303
+ const have = all.find((t) => t.key === key);
304
+ if (have && !have.off) keys.add(key);
305
+ else missing.push({ target: x, reason: have?.off ? 'off' : 'missing' });
306
+ }
307
+ return { keys, targets: all.filter((t) => keys.has(t.key)).map((t) => t.target), named, missing, fallback: null, projects };
308
+ }
309
+ const there = all.filter((t) => !t.off && ['installed', 'outdated', 'present'].includes(targetState(t, { skill, row }).state));
310
+ const keys = there.length ? new Set(there.map((t) => t.key)) : defaultTargets(all, { skill, row, assistants });
311
+ const onlyClaude = [...keys].every((k) => k === keyIn('claude', 'user'));
312
+ return { keys, targets: all.filter((t) => keys.has(t.key)).map((t) => t.target), named, missing: [], fallback: there.length ? 'installed' : (onlyClaude ? 'claude' : 'assistants'), projects };
313
+ }