synabun 2.0.10 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -0,0 +1,621 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: the Publish tab
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // One decision and one press: where (one tile per runtime, Claude Code · Codex · OpenCode, in one scope: every
6
+ // project or one project), then Install, always in sight at the foot of the tab. What is ticked when the tab
7
+ // opens is the review's targets, else where the skill already is, else Claude Code for every project: never
8
+ // everything (sx-install.js defaultSelection). The plan runs by itself whenever the choice changes and says in one
9
+ // line per target what will happen there ("Will create ~/.claude/skills/x"); Install asks one question only when
10
+ // the plan asks one (a folder SynaBun did not install or that changed since, the scan's findings) and otherwise
11
+ // goes (sx-install-flow.js). The version (the approved one by default), the other accounts, the shared folder, the
12
+ // project folders and their paths are under Advanced; Versions keeps candidate and best. Installations are
13
+ // verified, rolled back or removed from the Installed list; exports are under Export.
14
+ //
15
+ // Every protection stays: a plan is used only for exactly what it was asked for (sx-subject.js sameAsk; a plan
16
+ // that comes back for a selection that changed meanwhile is dropped and the one for the current selection is on
17
+ // its way), the install sends that plan's digest and only the decisions the owner confirmed, nothing blocked is
18
+ // sent, and while an install is on its way the choice is locked. Each drawing of the tab listens once: the
19
+ // listeners of the drawing before end with it.
20
+
21
+ import { scanStatus, scanText, scanFindingText } from './sx-scan.js';
22
+ import { studioErrorText } from './sx-errors.js';
23
+ import { dropdownHTML, wireDropdowns } from '../../cc-dropdown.js';
24
+ import { html, raw, setHTML, ICONS, relativeTime, listText } from './sx-dom.js';
25
+ import { installTargets, targetState, wholeTarget, sameAsk } from './sx-subject.js';
26
+ import { verifyFacts, needsConfirmation, newPublishState, clearPlan, choosePublishRevision, planModel } from './sx-plan.js';
27
+ import { defaultSelection, scopeOf, simplePicker, moveScope, defaultRevisionId, plainLabel, planLines, installButton, installStatus, whereOf } from './sx-install.js';
28
+ import { targetWords, keyWords, shortPath, installFlow, flowWords, installBusy, acquireInstall, assistantsNow, installedNow, eligibilityWords, enableFor } from './sx-install-flow.js';
29
+ import { changedFiles } from './sx-diff.js';
30
+ import { dirtyTabs, saveVersion } from './sx-docs.js';
31
+ import { shortDigest } from './sx-editor-model.js';
32
+ import { adoptNow, customizeCopy } from './sx-actions.js';
33
+
34
+ let _life = null; // the tab's listeners: they end when the tab is drawn again
35
+ let _draw = null; // the listeners of the tab's last drawing: they end with the next drawing
36
+ const APPROVED = ['approved', 'partial', 'publish_failed'];
37
+ const PLAN_DELAY_MS = 200;
38
+
39
+ /** The signal of one drawing: it ends with the next drawing, and with the tab. */
40
+ function drawSignal(life) {
41
+ _draw?.abort();
42
+ const ctrl = new AbortController();
43
+ _draw = ctrl;
44
+ if (life.aborted) ctrl.abort();
45
+ else life.addEventListener('abort', () => ctrl.abort(), { once: true, signal: ctrl.signal });
46
+ return ctrl.signal;
47
+ }
48
+
49
+ function stateHTML(t, state, target) {
50
+ if (target?.off) return html`<span class="sx-state-chip sx-state-chip--off">${t('skillsStudio.publish.state.off')}</span>`;
51
+ return html`<span class="sx-state-chip sx-state-chip--${state}">${t(`skillsStudio.publish.state.${state}`)}</span>`;
52
+ }
53
+
54
+ /** Which revision Publish shows: the one asked for (a review, Versions), else the default (sx-install.js). */
55
+ const chosenRevision = (ws) => defaultRevisionId({ skill: ws.skill, revisions: ws.revisions || [], bundled: ws.row?.source === 'synabun', asked: ws.publishRevisionId });
56
+
57
+ /** The review this install belongs to: the one Publish was opened for, else the skill's approved one of this version. */
58
+ function reviewOf(ctx, ws, revisionId) {
59
+ const reviews = ctx.data?.reviews || [];
60
+ if (ws.publishReviewId) return reviews.find((r) => r.reviewId === ws.publishReviewId) || null;
61
+ const own = ws.row?.review;
62
+ return own && APPROVED.includes(own.state) && own.submittedRevisionId === revisionId ? own : null;
63
+ }
64
+
65
+ /** Draw the Publish tab. */
66
+ export function renderPublishTab(panel, ctx, ws) {
67
+ const { t } = ctx;
68
+ _life?.abort();
69
+ _life = new AbortController();
70
+ const signal = _life.signal;
71
+ const s = ws.subject;
72
+ if (!ws.skill || (s.mode !== 'library' && !(s.mode === 'readonly' && s.canPublish))) {
73
+ const why = s.mode === 'adopt' ? 'adopt' : s.mode === 'inplace' ? 'inplace' : 'readonly';
74
+ setHTML(panel, html`<div class="sx-empty"><span class="sx-placeholder-icon" aria-hidden="true">${ICONS.publish}</span>
75
+ <h4 class="sx-empty-title">${t('skillsStudio.publish.noneTitle')}</h4><p>${t(`skillsStudio.publish.none.${why}`)}</p>
76
+ <div class="sx-row">${why === 'adopt' && !ctx.guest ? html`<button type="button" class="sx-btn sx-btn--primary" data-pub="adopt">${t('skillsStudio.ws.adoptNow')}</button>` : ''}
77
+ ${why === 'readonly' && s.canCustomize ? html`<button type="button" class="sx-btn" data-pub="customize">${t('skillsStudio.ws.customize')}</button>` : ''}
78
+ ${s.v1Id ? html`<a class="sx-btn" href="${ctx.api.getSkillsExportUrl(s.v1Id)}" download="">${ICONS.download}<span>${t('skillsStudio.publish.exportBundle')}</span></a>` : ''}</div></div>`);
79
+ panel.querySelector('[data-pub="adopt"]')?.addEventListener('click', () => adoptNow(ctx, ws), { signal });
80
+ panel.querySelector('[data-pub="customize"]')?.addEventListener('click', () => customizeCopy(ctx, ws), { signal });
81
+ return;
82
+ }
83
+ if (!ws.pub) ws.pub = newPublishState();
84
+ // What is read lands in this Publish state only: one that started over meanwhile (a version or a review opened
85
+ // Publish again) is drawn by its own reading.
86
+ const pub = ws.pub;
87
+ const gen = ctx.gen();
88
+ const needRevisions = !ws.revisions;
89
+ setHTML(panel, html`<div class="sx-publish"><div class="sx-pub-scroll"><p class="sx-help" role="status">${t('skillsStudio.publish.loading')}</p></div></div>`);
90
+ Promise.all([
91
+ needRevisions ? ctx.api.fetchSkillRevisions(ws.skill.skillId).then((r) => { ws.revisions = (r.revisions || []).slice().sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt))); }) : null,
92
+ ctx.api.fetchSkill(ws.skill.skillId).then((r) => { ws.skill = r.skill; }),
93
+ ctx.api.fetchSkillsConfig().then((r) => r.config || {}, () => ({ sharedAgentsRoot: false })).then((config) => { pub.config = config; }),
94
+ ]).then(() => {
95
+ if (!ctx.alive(gen) || !ws.shown || ws.tab !== 'publish' || ws.pub !== pub) return;
96
+ draw(panel, ctx, ws, signal);
97
+ }).catch((err) => {
98
+ if (!ctx.alive(gen) || ws.tab !== 'publish' || ws.pub !== pub) return;
99
+ setHTML(panel, html`<div class="sx-empty" role="alert"><p>${t('skillsStudio.publish.loadFailed', { error: studioErrorText(ctx.t, err) })}</p></div>`);
100
+ });
101
+ }
102
+
103
+ function targetsOf(ctx, ws) {
104
+ return installTargets({ roots: ctx.data?.catalog?.roots || [], config: ws.pub.config || {}, projects: ctx.projects.filter((p) => ws.pub.projects.includes(p.path)) });
105
+ }
106
+
107
+ /** What Publish shows now: the version and the targets ticked (as a request names them). */
108
+ function askNow(ctx, ws) {
109
+ const checked = ws.pub?.checked || new Set();
110
+ return { revisionId: chosenRevision(ws), targets: targetsOf(ctx, ws).filter((tg) => checked.has(tg.key) && !tg.off).map((tg) => wholeTarget(tg.target)) };
111
+ }
112
+
113
+ /** The plan Publish holds, as what it was asked for. */
114
+ const planAsk = (pub) => ({ revisionId: pub.planRevision, targets: pub.planTargets });
115
+
116
+ /** The panel the tab is drawn in now (the one given, or the one drawn again meanwhile). */
117
+ const panelNow = (panel, ctx) => (panel?.isConnected ? panel : ctx.panel?.querySelector('#sx-tabpanel'));
118
+
119
+ /**
120
+ * Whether this workspace is still the one open for its key, and on screen with Publish: a late plan or install
121
+ * answer of a workspace that was closed (and maybe opened again as a new one) draws nowhere.
122
+ */
123
+ const publishShown = (ctx, ws) => !!ws && ctx.workspaces.get(ws.key) === ws && !!ws.shown && ws.tab === 'publish';
124
+
125
+ /** Whether an install is on its way for this skill, from this tab or any other surface. */
126
+ const installing = (ws) => !!ws.pub?.busy || installBusy(ws.skill?.skillId);
127
+
128
+ function draw(panel, ctx, ws, life) {
129
+ const { t, tp, locale } = ctx;
130
+ const signal = drawSignal(life);
131
+ const pub = ws.pub;
132
+ const revisionId = chosenRevision(ws);
133
+ const review = reviewOf(ctx, ws, revisionId);
134
+ const owner = !ctx.guest;
135
+ if (!pub.checked) {
136
+ // The projects a review names are offered (as targets of that project).
137
+ for (const x of Array.isArray(review?.targets) ? review.targets : []) {
138
+ if (x?.scope === 'project' && x.projectPath && !pub.projects.includes(x.projectPath) && ctx.projects.some((p) => p.path === x.projectPath)) pub.projects.push(x.projectPath);
139
+ }
140
+ }
141
+ const targets = targetsOf(ctx, ws);
142
+ if (!pub.checked) {
143
+ pub.checked = defaultSelection({ review, targets, skill: ws.skill, row: ws.row, assistants: assistantsNow(ctx) });
144
+ // An update applied from Review or a banner picks what it replaces (sx-review.js updateTargetKeys).
145
+ if (typeof ws.pubPresetApply === 'function') { ws.pubPresetApply(pub, targets); ws.pubPresetApply = null; }
146
+ pub.scope = scopeOf(pub.checked, targets) || 'user';
147
+ }
148
+ // A plan for another version or other targets than those on screen is neither shown nor installed.
149
+ if (pub.plan && !sameAsk(planAsk(pub), askNow(ctx, ws))) clearPlan(pub);
150
+ // While an install is on its way, from here or from the banner or Review, the choice is locked.
151
+ const locked = installing(ws);
152
+ const picker = simplePicker({ targets, checked: pub.checked, scope: pub.scope, skill: ws.skill, row: ws.row, revisionId, installed: installedNow(ctx) });
153
+ const draftChanges = ws.draft && ws.revision ? changedFiles(ws.revision.manifest?.files || [], ws.draft.files || []) : [];
154
+ const revisions = ws.revisions || [];
155
+ const rev = revisions.find((r) => r.revisionId === revisionId) || null;
156
+ const revEntries = revisions.map((r) => { const l = plainLabel(r.labels); return { value: r.revisionId, label: `${t('skillsStudio.versions.rev', { id: shortDigest(r.contentDigest) })}${l ? ` · ${t(`skillsStudio.versions.label.${l}`)}` : ''}`, selected: r.revisionId === revisionId }; });
157
+ const projectScope = pub.scope.startsWith('project:') ? pub.scope.slice(8) : null;
158
+ const projectEntries = ctx.projects.map((p) => ({ value: p.path, label: p.label, selected: p.path === projectScope }));
159
+ const otherProjects = ctx.projects.filter((p) => !pub.projects.includes(p.path));
160
+ const installs = ws.skill?.installations || [];
161
+ setHTML(panel, html`<div class="sx-publish">
162
+ <div class="sx-pub-scroll">
163
+ <section class="sx-pub-section">
164
+ ${statusHTML(ctx, ws, review, revisionId)}
165
+ ${!revisions.length ? html`<p class="sx-note" role="note">${t('skillsStudio.publish.noVersion')}</p>` : ''}
166
+ ${draftChanges.length && ws.subject.mode === 'library' ? html`<p class="sx-note" role="note">${tp('skillsStudio.publish.draftNotPublished', draftChanges.length)} ${owner ? html`<button type="button" class="sx-btn sx-btn--sm" data-pub="snapshot">${t('skillsStudio.publish.saveVersionAndUse')}</button>` : ''}</p>` : ''}
167
+ </section>
168
+ <section class="sx-pub-section" aria-labelledby="sx-pub-where">
169
+ <h4 class="sx-section-title" id="sx-pub-where">${t('skillsStudio.publish.installFor')}</h4>
170
+ <div class="sx-tiles" role="group" aria-labelledby="sx-pub-where">${picker.tiles.map((x) => html`<label class="sx-tile${x.on ? ' is-on' : ''}${x.available ? '' : ' is-off'}${x.via ? ' is-via' : ''}" title="${x.via ? t('skillsStudio.publish.viaClaudeHelp') : (x.path || '')}">
171
+ <input type="checkbox" class="sx-check" data-target="${x.key}"${x.on ? ' checked' : ''}${!x.available || !owner || locked ? ' disabled' : ''} aria-describedby="sx-tile-${x.runtime}-state">
172
+ <span class="sx-tile-name">${t(`skillsStudio.runtime.${x.runtime}`)}</span>
173
+ <span class="sx-tile-state sx-tile-state--${x.tileState}" id="sx-tile-${x.runtime}-state">${t(`skillsStudio.publish.state.${x.tileState === 'via-claude' ? 'viaClaude' : x.tileState === 'not-installed' ? 'notInstalled' : x.tileState}`)}</span>
174
+ </label>`)}</div>
175
+ ${picker.tiles.some((x) => x.via) ? html`<p class="sx-help" id="sx-pub-via">${t('skillsStudio.publish.viaClaudeHelp')}</p>` : ''}
176
+ <div class="sx-row sx-pub-scope">
177
+ <span class="sx-filter-key" id="sx-pub-scope-label">${t('skillsStudio.publish.scope')}</span>
178
+ <div class="sx-seg" role="group" aria-labelledby="sx-pub-scope-label">
179
+ <button type="button" data-scope="user" aria-pressed="${projectScope ? 'false' : 'true'}"${!owner || locked ? ' disabled' : ''}>${t('skillsStudio.publish.scopeAll')}</button>
180
+ <button type="button" data-scope="project" aria-pressed="${projectScope ? 'true' : 'false'}"${!owner || locked || !ctx.projects.length ? ' disabled' : ''} title="${ctx.projects.length ? '' : t('skillsStudio.publish.noProjects')}">${t('skillsStudio.publish.scopeProject')}</button>
181
+ </div>
182
+ ${projectScope ? html`<span class="sx-sr" id="sx-pub-proj-label">${t('skillsStudio.publish.scopeProject')}</span>${raw(dropdownHTML('sx-pub-proj', projectEntries, { labelledBy: 'sx-pub-proj-label', classes: 'sx-dd', disabled: !owner || locked }))}` : ''}
183
+ </div>
184
+ ${picker.extra.length ? html`<p class="sx-help" id="sx-pub-extra">${t('skillsStudio.publish.extraPicked', { targets: picker.extra.map((x) => targetWords(ctx, x)).join(', ') })}</p>` : ''}
185
+ ${owner ? '' : html`<p class="sx-help">${t('skillsStudio.publish.guest')}</p>`}
186
+ </section>
187
+ <section class="sx-pub-section" id="sx-pub-plan" aria-live="polite">${planHTML(ctx, ws, picker)}</section>
188
+ ${installs.length ? html`<section class="sx-pub-section" aria-labelledby="sx-pub-installed">
189
+ <h4 class="sx-section-title" id="sx-pub-installed">${t('skillsStudio.publish.installedTitle')}</h4>
190
+ <ul class="sx-installs">${installs.map((i) => installRowHTML(ctx, ws, i, revisionId, owner))}</ul>
191
+ </section>` : ''}
192
+ <details class="sx-details sx-pub-advanced" id="sx-pub-advanced"${pub.advanced ? ' open' : ''}>
193
+ <summary>${t('skillsStudio.publish.advancedTitle')}</summary>
194
+ <div class="sx-pub-advanced-body">
195
+ ${revisions.length ? html`<div class="sx-row"><span class="sx-filter-key" id="sx-pub-rev-label">${t('skillsStudio.publish.version')}</span>${raw(dropdownHTML('sx-pub-rev', revEntries, { labelledBy: 'sx-pub-rev-label', classes: 'sx-dd', disabled: locked }))}
196
+ ${rev ? html`<span class="sx-dim">${t('skillsStudio.publish.versionMeta', { when: relativeTime(rev.createdAt, locale) })}</span>` : ''}</div>
197
+ <p class="sx-help">${t('skillsStudio.publish.versionHelp')}</p>` : ''}
198
+ <table class="sx-grid">
199
+ <caption class="sx-sr">${t('skillsStudio.publish.targetsCaption')}</caption>
200
+ <thead><tr><th scope="col"><span class="sx-sr">${t('skillsStudio.publish.pick')}</span></th><th scope="col">${t('skillsStudio.publish.col.target')}</th><th scope="col">${t('skillsStudio.publish.col.folder')}</th><th scope="col">${t('skillsStudio.publish.col.now')}</th></tr></thead>
201
+ <tbody>${targets.map((tg) => {
202
+ const st = targetState(tg, { skill: ws.skill, row: ws.row, revisionId });
203
+ const label = targetWords(ctx, tg);
204
+ return html`<tr>
205
+ <td><input type="checkbox" class="sx-check" data-adv-target="${tg.key}"${pub.checked.has(tg.key) ? ' checked' : ''}${tg.off || !owner || locked ? ' disabled' : ''} aria-label="${t('skillsStudio.publish.pickTarget', { target: label })}"></td>
206
+ <td>${label}${tg.off ? html`<span class="sx-help sx-block">${t('skillsStudio.publish.sharedOff')}</span>` : st.state === 'bridge' ? html`<span class="sx-help sx-block">${t('skillsStudio.publish.bridgeHelp', { runtime: t(`skillsStudio.runtime.${tg.runtime}`) })}</span>` : ''}</td>
207
+ <td class="sx-mono sx-path" title="${st.path || tg.path || ''}">${shortPath(st.path || tg.path || '')}</td>
208
+ <td>${stateHTML(t, st.state, tg)}</td>
209
+ </tr>`;
210
+ })}</tbody>
211
+ </table>
212
+ ${otherProjects.length && owner ? html`<div class="sx-row"><span class="sx-filter-key" id="sx-pub-proj-add-label">${t('skillsStudio.publish.addProject')}</span>${raw(dropdownHTML('sx-pub-project', [{ value: '', label: t('skillsStudio.publish.chooseProject'), selected: true, disabled: true }, ...otherProjects.map((p) => ({ value: p.path, label: p.label }))], { labelledBy: 'sx-pub-proj-add-label', classes: 'sx-dd', disabled: locked }))}</div>` : ''}
213
+ <p class="sx-help">${t('skillsStudio.publish.settingsNote')}</p>
214
+ </div>
215
+ </details>
216
+ <details class="sx-details sx-pub-advanced" id="sx-pub-export"${pub.exportOpen ? ' open' : ''}>
217
+ <summary>${t('skillsStudio.publish.exportTitle')}</summary>
218
+ <div class="sx-pub-advanced-body">${exportHTML(ctx, ws, revisionId)}</div>
219
+ </details>
220
+ </div>
221
+ <div class="sx-pub-bar" id="sx-pub-bar">${barHTML(ctx, ws, picker, revisionId, rev)}</div>
222
+ </div>`);
223
+ // This drawing's listeners: they end with the next drawing (a listener of each drawing would answer every click once
224
+ // more: a dropdown opened and closed by one click, an action sent twice).
225
+ wireDropdowns(panel, { signal });
226
+ const changed = () => { clearPlan(pub); pub.results = null; pub.planError = null; };
227
+ panel.querySelector('#sx-pub-rev')?.addEventListener('change', (e) => { choosePublishRevision(ws, e.target.value, { reviews: ctx.data?.reviews || [] }); pub.results = null; draw(panel, ctx, ws, life); }, { signal });
228
+ panel.querySelector('#sx-pub-project')?.addEventListener('change', (e) => { if (e.target.value) { pub.projects.push(e.target.value); changed(); draw(panel, ctx, ws, life); } }, { signal });
229
+ panel.querySelector('#sx-pub-proj')?.addEventListener('change', (e) => { if (e.target.value && e.target.value !== projectScope) setScope(panel, ctx, ws, life, `project:${e.target.value}`); }, { signal });
230
+ for (const id of ['sx-pub-advanced', 'sx-pub-export']) panel.querySelector(`#${id}`)?.addEventListener('toggle', (e) => { pub[id === 'sx-pub-advanced' ? 'advanced' : 'exportOpen'] = e.target.open; }, { signal });
231
+ panel.addEventListener('change', (e) => {
232
+ const box = e.target;
233
+ const key = box.dataset?.target || box.dataset?.advTarget;
234
+ if (!key) return;
235
+ if (pub.busy) return;
236
+ if (box.checked) pub.checked.add(key); else pub.checked.delete(key);
237
+ changed();
238
+ draw(panel, ctx, ws, life);
239
+ panel.querySelector(`[data-${box.dataset.target ? 'target' : 'adv-target'}="${CSS.escape(key)}"]`)?.focus();
240
+ }, { signal });
241
+ panel.addEventListener('click', (e) => {
242
+ const scope = e.target.closest?.('[data-scope]');
243
+ if (scope && !scope.disabled) {
244
+ if (scope.dataset.scope === 'user') { if (projectScope) setScope(panel, ctx, ws, life, 'user'); } else if (!projectScope) setScope(panel, ctx, ws, life, `project:${ctx.projects.find((p) => pub.projects.includes(p.path))?.path || ctx.projects[0].path}`);
245
+ return;
246
+ }
247
+ const btn = e.target.closest?.('[data-pub]');
248
+ if (!btn || btn.disabled) return;
249
+ onAction(btn.dataset.pub, btn, panel, ctx, ws, life);
250
+ }, { signal });
251
+ schedulePlan(panel, ctx, ws);
252
+ }
253
+
254
+ /** The scope changed: the tiles ticked move with it; the project's targets are offered. */
255
+ function setScope(panel, ctx, ws, life, to) {
256
+ const pub = ws.pub;
257
+ if (pub.busy || to === pub.scope) return;
258
+ const path = to.startsWith('project:') ? to.slice(8) : null;
259
+ if (path && !pub.projects.includes(path)) pub.projects.push(path);
260
+ pub.checked = moveScope(pub.checked, pub.scope, to);
261
+ pub.scope = to;
262
+ clearPlan(pub); pub.results = null; pub.planError = null;
263
+ draw(panel, ctx, ws, life);
264
+ panel.querySelector(`[data-scope="${path ? 'project' : 'user'}"]`)?.focus();
265
+ }
266
+
267
+ /** The first line: where it is now, or that it is not, and that the version picked is the approved one. */
268
+ function statusHTML(ctx, ws, review, revisionId) {
269
+ const { t } = ctx;
270
+ const status = installStatus({ skill: ws.skill, row: ws.row }) || { code: 'none', where: [] };
271
+ const where = status.where.map((w) => targetWords(ctx, w)).join(', ');
272
+ const reviewed = review && review.submittedRevisionId === revisionId;
273
+ let line;
274
+ if (status.code === 'installed') line = t('skillsStudio.publish.statusInstalled', { where });
275
+ else if (status.code === 'outdated') line = t('skillsStudio.publish.statusOutdated', { where });
276
+ else if (status.code === 'draft') line = '';
277
+ else line = t('skillsStudio.publish.statusNone'); // the banner above says it is approved; here, that its version is picked
278
+ return line ? html`<p class="sx-pub-status" id="sx-pub-status" role="status">${status.code === 'installed' ? ICONS.ok : ICONS.info}<span>${line}${reviewed || status.code === 'approved' ? html` <span class="sx-dim">${t('skillsStudio.publish.statusApprovedVersion')}</span>` : ''}</span></p>` : '';
279
+ }
280
+
281
+ /** One line per target of the plan, the outcome of an install, or what is waited for. */
282
+ function planHTML(ctx, ws, picker) {
283
+ const { t } = ctx;
284
+ const pub = ws.pub;
285
+ const picked = picker.tiles.filter((x) => x.on).length + picker.extra.length;
286
+ if (pub.results) return outcomeHTML(ctx, pub.results);
287
+ if (pub.planError) return html`<p class="sx-note sx-note--warn" role="alert">${t('skillsStudio.publish.planFailed', { error: pub.planError })} <button type="button" class="sx-btn sx-btn--sm" data-pub="replan">${t('skillsStudio.publish.retryPlan')}</button></p>`;
288
+ if (!chosenRevision(ws)) return '';
289
+ if (!picked) return html`<p class="sx-help">${t('skillsStudio.publish.pickOne')}</p>`;
290
+ if (!pub.plan) return html`<p class="sx-help sx-pub-checking">${t('skillsStudio.publish.checkingPlan')}</p>`;
291
+ const lines = planLines(pub.plan);
292
+ return html`<ul class="sx-plan-lines" data-plan-rev="${pub.planRevision || ''}">${lines.map((l) => {
293
+ const folder = html`<span class="sx-mono" title="${l.destination || ''}">${shortPath(l.destination || '')}</span>`;
294
+ const reason = l.blocked.map((b) => t(`skillsStudio.publish.blocked.${b.code}`)).join(' ');
295
+ const sev = l.code === 'blocked' ? 'err' : (l.asks.replace || l.asks.scan || l.overlap.length || l.disabledFor.length) ? 'warn' : l.code === 'identical' ? 'ok' : 'info';
296
+ const canEnable = !ctx.guest && ctx.caps.has('eligibility-fix') && !installing(ws);
297
+ const icon = sev === 'err' ? ICONS.err : sev === 'warn' ? ICONS.warn : sev === 'ok' ? ICONS.ok : ICONS.info;
298
+ const main = l.code === 'blocked' ? t('skillsStudio.publish.line.blocked', { reason })
299
+ : l.code === 'identical' ? html`${t('skillsStudio.publish.line.identical')} ${folder}`
300
+ : l.code === 'replace-owned' ? html`${t('skillsStudio.publish.line.replaceOwnedBefore')} ${folder} ${t('skillsStudio.publish.line.replaceOwnedAfter')}`
301
+ : l.code === 'replace-modified' ? html`${folder} ${t('skillsStudio.publish.line.replaceModified')}`
302
+ : l.code === 'replace-unowned' ? html`${folder} ${t('skillsStudio.publish.line.replaceUnowned')}`
303
+ : html`${t('skillsStudio.publish.line.create')} ${folder}`;
304
+ return html`<li class="sx-plan-line sx-plan-line--${sev}" data-plan-line="${l.targetKey}"><span class="sx-plan-line-icon">${icon}</span><span class="sx-plan-line-text"><strong>${keyWords(ctx, l.targetKey)}</strong>: ${main}
305
+ ${l.alsoReadBy.length ? html`<span class="sx-dim"> · ${t('skillsStudio.publish.readBy', { runtimes: l.alsoReadBy.map((r) => t(`skillsStudio.runtime.${r}`)).join(', ') })}</span>` : ''}
306
+ ${l.asks.scan ? html`<span class="sx-block sx-warn-text">${t('skillsStudio.publish.line.scanAsk')}</span>` : ''}
307
+ ${l.overlap.map((o) => html`<span class="sx-block sx-warn-text">${o.runtimes.length > 1 ? t('skillsStudio.publish.overlapMany', { runtimes: listText(o.runtimes.map((r) => t(`skillsStudio.runtime.${r}`)), ctx.locale), name: o.name || l.name || '', path: shortPath(o.path || '') }) : t('skillsStudio.publish.overlapOne', { runtime: t(`skillsStudio.runtime.${o.runtimes[0] || l.runtime}`), name: o.name || l.name || '', path: shortPath(o.path || '') })}</span>`)}
308
+ ${l.disabledFor.map((d) => html`<span class="sx-block sx-warn-text sx-eligibility" data-eligibility="${l.targetKey}:${d.runtime}">${t('skillsStudio.publish.eligibility.disabled', { runtime: t(`skillsStudio.runtime.${d.runtime}`), reason: eligibilityWords(ctx, d.runtime, d.reason) })}
309
+ ${canEnable ? html` <button type="button" class="sx-btn sx-btn--sm" data-pub="enable" data-key="${l.targetKey}" data-runtime="${d.runtime}">${t('skillsStudio.publish.enableFor', { runtime: t(`skillsStudio.runtime.${d.runtime}`) })}</button>` : ''}</span>`)}
310
+ ${l.unknownFor.map((d) => html`<span class="sx-block sx-dim">${t('skillsStudio.publish.eligibility.unknown', { runtime: t(`skillsStudio.runtime.${d.runtime}`), reason: eligibilityWords(ctx, d.runtime, d.reason) })}</span>`)}
311
+ ${l.transforms.map((tr) => html`<span class="sx-block sx-dim">${t('skillsStudio.publish.transform')} ${tr.reason}</span>`)}</span></li>`;
312
+ })}</ul>`;
313
+ }
314
+
315
+ function outcomeHTML(ctx, results) {
316
+ const { t } = ctx;
317
+ const w = results.words;
318
+ return html`<div class="sx-outcome sx-outcome--${w.kind}" role="status">
319
+ <p class="sx-outcome-line">${w.kind === 'info' ? ICONS.ok : ICONS.warn}<strong>${w.line}</strong></p>
320
+ ${w.lines.length ? html`<ul class="sx-plain-list sx-outcome-notes">${w.lines.map((x) => html`<li>${x}</li>`)}</ul>` : ''}
321
+ ${results.outcome?.ok.length ? html`<p class="sx-help">${t('skillsStudio.publish.outcomeHelp')}</p>` : ''}
322
+ </div>`;
323
+ }
324
+
325
+ /** One installation: where, which version it has, and its actions. */
326
+ function installRowHTML(ctx, ws, i, revisionId, owner) {
327
+ const { t } = ctx;
328
+ const current = revisionId ? i.sourceRevisionId === revisionId : !i.updateAvailable;
329
+ const facts = ws.pub.verify[i.installationId] || null;
330
+ return html`<li class="sx-install-row">
331
+ <span class="sx-install-where">${targetWords(ctx, whereOf(i))}</span>
332
+ <span class="sx-mono sx-dim sx-path" title="${i.destination || ''}">${shortPath(i.destination || '')}</span>
333
+ ${stateHTML(t, current ? 'installed' : 'outdated')}
334
+ ${owner ? html`<span class="sx-install-actions">
335
+ <button type="button" class="sx-btn sx-btn--sm" data-pub="verify" data-inst="${i.installationId}">${t('skillsStudio.publish.verify')}</button>
336
+ <button type="button" class="sx-btn sx-btn--sm" data-pub="rollback" data-inst="${i.installationId}">${t('skillsStudio.publish.rollback')}</button>
337
+ <button type="button" class="sx-btn sx-btn--sm sx-btn--danger" data-pub="remove" data-inst="${i.installationId}">${t('skillsStudio.publish.remove')}</button></span>` : ''}
338
+ ${facts ? factsHTML(t, facts) : ''}
339
+ </li>`;
340
+ }
341
+
342
+ function factsHTML(t, f) {
343
+ return html`<ul class="sx-facts sx-install-facts">
344
+ <li>${t(`skillsStudio.publish.fact.written.${f.written === 'verified' ? 'ok' : f.written === 'missing' ? 'missing' : f.written === 'changed' ? 'changed' : 'unknown'}`)}</li>
345
+ <li>${t(`skillsStudio.publish.fact.discoverable.${f.discoverable}`)}</li>
346
+ <li>${t(`skillsStudio.publish.fact.eligibility.${f.eligibility}`)}</li>
347
+ ${f.discovered.map((d) => html`<li>${t(`skillsStudio.publish.fact.discovered.${d.discovered === true ? 'yes' : d.discovered === false ? 'no' : d.code || 'unknown'}`, { runtime: t(`skillsStudio.runtime.${d.runtime}`) })}</li>`)}
348
+ ${f.checks.filter((c) => !c.ok).map((c) => html`<li class="sx-warn-text">${c.message}</li>`)}
349
+ </ul>`;
350
+ }
351
+
352
+ /** The foot of the tab: what is picked, and the one button. */
353
+ function barHTML(ctx, ws, picker, revisionId, rev) {
354
+ const { t } = ctx;
355
+ const pub = ws.pub;
356
+ if (ctx.guest) return html`<span class="sx-pub-bar-text sx-help">${t('skillsStudio.publish.guest')}</span>`;
357
+ // Only what the install would send is counted and named: a tile that is on, and what is ticked under Advanced
358
+ // and not off (simplePicker leaves an off target out, as askNow does).
359
+ const picked = [...picker.tiles.filter((x) => x.on && x.available).map((x) => x.target), ...picker.extra];
360
+ const states = picked.map((tg) => targetState(tg, { skill: ws.skill, row: ws.row, revisionId }).state);
361
+ const asked = askNow(ctx, ws);
362
+ const planCurrent = pub.plan && sameAsk(planAsk(pub), asked) ? pub.plan : null;
363
+ const b = installButton({ picked: picked.length, states, model: planCurrent, checking: !planCurrent && !pub.planError, busy: installing(ws), error: !!pub.planError });
364
+ if (!revisionId) return html`<span class="sx-pub-bar-text sx-help">${t('skillsStudio.publish.noVersion')}</span>`;
365
+ const label = t(`skillsStudio.publish.bar.${b.code === 'nothing' || b.code === 'blocked' ? 'install' : b.code}`);
366
+ const label2 = picked.length > 1 && b.enabled ? t('skillsStudio.publish.bar.count', { label, count: picked.length }) : label;
367
+ const where = picked.map((tg) => targetWords(ctx, tg)).join(', ');
368
+ const l = rev ? plainLabel(rev.labels) : null;
369
+ const version = rev ? `${t('skillsStudio.versions.rev', { id: shortDigest(rev.contentDigest) })}${l ? ` · ${t(`skillsStudio.versions.label.${l}`)}` : ''}` : '';
370
+ return html`<span class="sx-pub-bar-text" id="sx-pub-bar-text">${picked.length ? html`<span class="sx-pub-bar-where">${where}</span>` : html`<span class="sx-help">${t('skillsStudio.publish.pickOne')}</span>`}${version ? html`<span class="sx-dim sx-pub-bar-version">${version}</span>` : ''}</span>
371
+ <button type="button" class="sx-btn sx-btn--primary sx-btn--lg" id="sx-pub-install" data-pub="install" data-state="${b.code}"${b.enabled ? '' : ' disabled'} aria-describedby="sx-pub-bar-text">${b.code === 'installed' ? ICONS.ok : ''}<span>${label2}</span></button>`;
372
+ }
373
+
374
+ /** The plan section and the foot again, after a plan came back (the tiles keep the keyboard). */
375
+ function drawPlanParts(ctx, ws) {
376
+ const panel = ctx.panel?.querySelector('#sx-tabpanel');
377
+ if (!panel || !publishShown(ctx, ws)) return;
378
+ const pub = ws.pub;
379
+ const revisionId = chosenRevision(ws);
380
+ const targets = targetsOf(ctx, ws);
381
+ const picker = simplePicker({ targets, checked: pub.checked || new Set(), scope: pub.scope, skill: ws.skill, row: ws.row, revisionId, installed: installedNow(ctx) });
382
+ const rev = (ws.revisions || []).find((r) => r.revisionId === revisionId) || null;
383
+ setHTML(panel.querySelector('#sx-pub-plan'), planHTML(ctx, ws, picker));
384
+ setHTML(panel.querySelector('#sx-pub-bar'), barHTML(ctx, ws, picker, revisionId, rev));
385
+ }
386
+
387
+ /** The plan for what is picked, a moment after the last change: one request for the choice as it then is. */
388
+ function schedulePlan(panel, ctx, ws) {
389
+ const pub = ws.pub;
390
+ const asked = askNow(ctx, ws);
391
+ clearTimeout(pub.planTimer);
392
+ if (!asked.revisionId || !asked.targets.length || ctx.guest) { pub.planning = null; return; }
393
+ if (pub.plan && sameAsk(planAsk(pub), asked)) return;
394
+ if (pub.planning && sameAsk(pub.planning, asked)) return; // on its way for exactly this already
395
+ pub.planTimer = setTimeout(() => runPlan(ctx, ws), PLAN_DELAY_MS);
396
+ }
397
+
398
+ /**
399
+ * An install was taken up or let go on another surface (the banner, Review): the choice and the button of a Publish
400
+ * on screen follow, without a reading (sx-main.js calls this from the lock's watcher).
401
+ */
402
+ export function refreshPublishBusy(ctx, ws) {
403
+ const panel = ctx.panel?.querySelector('#sx-tabpanel');
404
+ if (!panel || !publishShown(ctx, ws) || !ws.pub?.checked || !panel.querySelector('#sx-pub-bar')) return;
405
+ if (!_life || _life.signal.aborted) return;
406
+ draw(panel, ctx, ws, _life.signal);
407
+ }
408
+
409
+ async function runPlan(ctx, ws) {
410
+ const pub = ws.pub;
411
+ if (ctx.workspaces.get(ws.key) !== ws) return; // closed meanwhile: its plan is nobody's
412
+ const asked = askNow(ctx, ws);
413
+ if (!asked.revisionId || !asked.targets.length || pub.busy) return;
414
+ if (pub.plan && sameAsk(planAsk(pub), asked)) return;
415
+ pub.planning = asked;
416
+ pub.planError = null;
417
+ const gen = ctx.gen();
418
+ try {
419
+ const res = await ctx.api.planSkillsInstall(asked.revisionId, asked.targets);
420
+ if (!ctx.alive(gen) || ws.pub !== pub) return;
421
+ // What is picked changed while this plan was on its way: this answers something else and is dropped; the plan
422
+ // for the choice as it is now is on its way or scheduled.
423
+ if (!sameAsk(asked, askNow(ctx, ws))) return;
424
+ pub.plan = planModel(res.plan);
425
+ pub.planTargets = asked.targets;
426
+ pub.planRevision = asked.revisionId;
427
+ pub.confirmed = { replace: new Set(), scan: new Set() };
428
+ } catch (err) {
429
+ if (!ctx.alive(gen) || ws.pub !== pub || !sameAsk(asked, askNow(ctx, ws))) return;
430
+ pub.planError = studioErrorText(ctx.t, err);
431
+ } finally {
432
+ if (ws.pub === pub && pub.planning === asked) pub.planning = null;
433
+ }
434
+ drawPlanParts(ctx, ws);
435
+ }
436
+
437
+ function exportHTML(ctx, ws, revisionId) {
438
+ const { t } = ctx;
439
+ if (!revisionId) return html`<p class="sx-help">${t('skillsStudio.publish.exportNeedsVersion')}</p>`;
440
+ if (!ctx.caps.has('export')) {
441
+ return html`<p class="sx-help">${t('skillsStudio.publish.exportRestart')}</p>${ws.subject.v1Id ? html`<a class="sx-btn" href="${ctx.api.getSkillsExportUrl(ws.subject.v1Id)}" download="">${ICONS.download}<span>${t('skillsStudio.publish.exportBundle')}</span></a>` : ''}`;
442
+ }
443
+ if (ctx.caps.has('packages')) return html`<div class="sx-row">${['bundle', 'skill', 'portable', 'claude-upload', 'claude-plugin', 'claude-api', 'github'].map((format) => html`<button class="sx-btn" type="button" data-pub="package" data-format="${format}">${ICONS.download}<span>${t(`skillsStudio.operations.formats.${format}`)}</span></button>`)}</div><p class="sx-help">${t('skillsStudio.publish.exportHelp')}</p>`;
444
+ const url = (o) => ctx.api.getSkillsRevisionExportUrl(revisionId, o);
445
+ return html`<div class="sx-row">
446
+ <a class="sx-btn" href="${url({ format: 'bundle' })}" download="">${ICONS.download}<span>${t('skillsStudio.publish.exportBundle')}</span></a>
447
+ <a class="sx-btn" href="${url({ format: 'skill' })}" download="">${ICONS.download}<span>${t('skillsStudio.publish.exportSkill')}</span></a>
448
+ <a class="sx-btn" href="${url({ format: 'skill', profile: 'claude-upload' })}" download="">${ICONS.download}<span>${t('skillsStudio.publish.exportClaude')}</span></a>
449
+ ${ctx.guest ? '' : html`<button type="button" class="sx-btn sx-btn--ghost" data-pub="check-claude">${t('skillsStudio.publish.checkClaude')}</button>`}
450
+ </div>
451
+ <p class="sx-help">${t('skillsStudio.publish.exportHelp')}</p>
452
+ <div id="sx-pub-claude" aria-live="polite"></div>`;
453
+ }
454
+
455
+ async function onAction(act, btn, panel, ctx, ws, life) {
456
+ const { t } = ctx;
457
+ const pub = ws.pub;
458
+ const gen = ctx.gen();
459
+ // Drawn again only while this Publish is the one on screen (not after another version or review started it over),
460
+ // in the tab as it is now: one drawn again meanwhile (the owner went to Edit and back) is the one updated.
461
+ const redraw = () => {
462
+ if (!ctx.alive(gen) || !publishShown(ctx, ws) || ws.pub !== pub) return;
463
+ if (panel.isConnected) { draw(panel, ctx, ws, life); return; }
464
+ const now = ctx.panel?.querySelector('#sx-tabpanel');
465
+ if (now && _life && !_life.signal.aborted) draw(now, ctx, ws, _life.signal);
466
+ };
467
+ if (act === 'package') {
468
+ const revisionId = chosenRevision(ws), format = btn.dataset.format;
469
+ btn.disabled = true;
470
+ try {
471
+ const pkg = (await ctx.api.previewSkillPackage(revisionId, { format })).package;
472
+ if (!ctx.alive(gen) || !panel.isConnected || ws.pub !== pub) return;
473
+ const lines = [t('skillsStudio.operations.packageHelp', { revision: revisionId, digest: pkg.artifactDigest, count: pkg.files.length }),
474
+ t(pkg.namingPolicy === 'target' ? 'skillsStudio.operations.packageName_target' : 'skillsStudio.operations.packageName_preserved', { name: pkg.name }),
475
+ t('skillsStudio.operations.packageLicense', { license: pkg.license || t('skillsStudio.operations.unknown') }),
476
+ t('skillsStudio.operations.packageSourceScan', { status: scanText(t, pkg.sourceScan, pkg.sourceDigest), passed: t(scanStatus(pkg.sourceScan, pkg.sourceDigest).passed ? 'skillsStudio.scan.passed' : 'skillsStudio.scan.notPassed') }),
477
+ pkg.losses.length ? t('skillsStudio.operations.packageLosses', { fields: pkg.losses.map((x) => x.field).join(', ') }) : t('skillsStudio.operations.packageNoLosses'),
478
+ ...pkg.scan.findings.slice(0, 8).map((f) => `${f.severity}: ${f.ruleId} — ${scanFindingText(t, f)}`)];
479
+ const answer = await ctx.ask({ title: t('skillsStudio.operations.packageTitle'), lines, buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'download', label: t('skillsStudio.operations.download') }], focus: 'cancel' });
480
+ if (answer !== 'download' || !ctx.alive(gen) || ws.pub !== pub || chosenRevision(ws) !== revisionId) return;
481
+ const a = document.createElement('a');
482
+ a.href = ctx.api.getSkillsRevisionExportUrl(revisionId, { format, acknowledgedDigest: pkg.artifactDigest });
483
+ a.download = ''; document.body.appendChild(a); a.click(); a.remove();
484
+ } catch (err) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.operations.failed', { error: studioErrorText(ctx.t, err) }), 'error'); }
485
+ finally { if (btn.isConnected) btn.disabled = false; }
486
+ return;
487
+ }
488
+ if (act === 'replan') {
489
+ pub.planError = null;
490
+ clearPlan(pub);
491
+ redraw(); // draws "Checking…" and asks for the plan again
492
+ return;
493
+ }
494
+ if (act === 'enable') {
495
+ // The runtime's setting that turns the skill off: changed by the server over a backup, on this click only; then
496
+ // the plan is made again (the setting is not part of the plan's digest, but the line must say what is true now).
497
+ if (!pub.plan || installing(ws)) return;
498
+ const line = planLines(pub.plan).find((l) => l.targetKey === btn.dataset.key);
499
+ const runtime = btn.dataset.runtime;
500
+ if (!line || !['claude', 'codex', 'opencode'].includes(runtime)) return;
501
+ btn.disabled = true;
502
+ const said = await enableFor(ctx, { runtime, line });
503
+ if (!ctx.alive(gen)) return;
504
+ ctx.toast(said.line, said.ok ? 'info' : 'error');
505
+ ctx.announce(said.line);
506
+ if (ws.pub !== pub) return;
507
+ clearPlan(pub);
508
+ redraw();
509
+ return;
510
+ }
511
+ if (act === 'snapshot') {
512
+ if (dirtyTabs(ws).length) { ctx.toast(t('skillsStudio.toast.saveFirst'), 'error'); return; }
513
+ try {
514
+ const out = await saveVersion(ws, ctx.api, { key: ctx.key });
515
+ if (!ctx.alive(gen)) return;
516
+ ws.publishRevisionId = out.revision.revisionId;
517
+ ws.revisions = null;
518
+ ctx.toast(out.unchanged ? t('skillsStudio.toast.versionUnchanged') : t('skillsStudio.toast.versionSaved'));
519
+ renderPublishTab(panelNow(panel, ctx), ctx, ws);
520
+ } catch (err) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.toast.versionFailed', { error: studioErrorText(ctx.t, err) }), 'error'); }
521
+ return;
522
+ }
523
+ if (act === 'install') {
524
+ const asked = askNow(ctx, ws);
525
+ if (!asked.revisionId || !asked.targets.length || installing(ws)) return;
526
+ // One install of a skill at a time, from whichever surface (the banner, Review, here).
527
+ const release = acquireInstall(ws.skill?.skillId);
528
+ if (!release) { ctx.toast(t('skillsStudio.publish.busyElsewhere'), 'error'); return; }
529
+ // Exactly the plan that is shown, for exactly what it was asked for; without one, the flow asks for one first.
530
+ const plan = pub.plan && sameAsk(planAsk(pub), asked) ? pub.plan : null;
531
+ const review = reviewOf(ctx, ws, asked.revisionId);
532
+ clearTimeout(pub.planTimer);
533
+ pub.busy = true;
534
+ redraw(); // the choice is locked until the install is back
535
+ let out;
536
+ try {
537
+ out = await installFlow(ctx, {
538
+ revisionId: asked.revisionId, targets: asked.targets, reviewId: review?.reviewId || null, plan,
539
+ onPlanned: (m) => { if (ws.pub === pub) { pub.plan = m; pub.planTargets = asked.targets; pub.planRevision = asked.revisionId; } },
540
+ });
541
+ } finally { pub.busy = false; release(); }
542
+ if (!ctx.alive(gen) || ws.pub !== pub) return;
543
+ if (out.status === 'gone') return;
544
+ if (out.status === 'cancelled') { redraw(); return; }
545
+ const words = flowWords(ctx, out);
546
+ if (out.status === 'done') {
547
+ pub.results = { outcome: out.outcome, results: out.results, blocked: out.blocked, words };
548
+ clearPlan(pub);
549
+ ws.publishReviewId = null;
550
+ ctx.toast(words.line, words.kind);
551
+ ctx.announce(words.line);
552
+ try { ws.skill = (await ctx.api.fetchSkill(ws.skill.skillId)).skill; } catch { /* the reload below */ }
553
+ redraw(); // the outcome at once; the reload below draws the tab again from the same state
554
+ ctx.reload(); // the rows, the review (published), the rail
555
+ return;
556
+ }
557
+ ctx.toast(words.line, 'error');
558
+ if (out.plan) { pub.plan = out.plan; pub.planTargets = asked.targets; pub.planRevision = asked.revisionId; }
559
+ redraw();
560
+ return;
561
+ }
562
+ if (act === 'verify') {
563
+ const id = btn.dataset.inst;
564
+ btn.disabled = true;
565
+ try {
566
+ const res = await ctx.api.verifySkillsInstallation(id, { probe: true });
567
+ if (!ctx.alive(gen)) return;
568
+ pub.verify[id] = verifyFacts(res.installation);
569
+ ctx.announce(t('skillsStudio.publish.verified'));
570
+ } catch (err) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.publish.verifyFailed', { error: studioErrorText(ctx.t, err) }), 'error'); }
571
+ redraw();
572
+ return;
573
+ }
574
+ if (act === 'rollback' || act === 'remove') {
575
+ const id = btn.dataset.inst;
576
+ const answer = await ctx.ask({
577
+ title: t(act === 'rollback' ? 'skillsStudio.publish.rollbackTitle' : 'skillsStudio.publish.removeTitle'),
578
+ lines: [t(act === 'rollback' ? 'skillsStudio.publish.rollbackBody' : 'skillsStudio.publish.removeBody')],
579
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'go', label: t(act === 'rollback' ? 'skillsStudio.publish.rollback' : 'skillsStudio.publish.remove'), kind: 'danger' }],
580
+ focus: 'cancel',
581
+ });
582
+ if (answer !== 'go') return;
583
+ const call = (body) => (act === 'rollback' ? ctx.api.rollbackSkillsInstallation(id, body) : ctx.api.uninstallSkillsInstallation(id, body));
584
+ try {
585
+ await call({});
586
+ } catch (err) {
587
+ if (!needsConfirmation(err)) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.publish.undoFailed', { error: studioErrorText(ctx.t, err) }), 'error'); return; }
588
+ const again = await ctx.ask({
589
+ title: t('skillsStudio.publish.changedSinceTitle'),
590
+ lines: [t('skillsStudio.publish.changedSinceBody')],
591
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'go', label: t('skillsStudio.publish.changedSinceConfirm'), kind: 'danger' }],
592
+ focus: 'cancel',
593
+ });
594
+ if (again !== 'go') return;
595
+ try { await call({ expectedDestinationDigest: err.body.currentDigest }); } catch (e2) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.publish.undoFailed', { error: studioErrorText(ctx.t, e2) }), 'error'); return; }
596
+ }
597
+ if (!ctx.alive(gen)) return;
598
+ ctx.toast(t(act === 'rollback' ? 'skillsStudio.publish.rolledBack' : 'skillsStudio.publish.removed'));
599
+ try { ws.skill = (await ctx.api.fetchSkill(ws.skill.skillId)).skill; } catch { /* */ }
600
+ clearPlan(pub); pub.results = null;
601
+ ctx.reload();
602
+ redraw();
603
+ return;
604
+ }
605
+ if (act === 'check-claude') {
606
+ const box = panel.querySelector('#sx-pub-claude');
607
+ const revisionId = chosenRevision(ws);
608
+ if (!box || !revisionId) return;
609
+ setHTML(box, html`<p class="sx-help">${t('skillsStudio.publish.checking')}</p>`);
610
+ try {
611
+ const res = await ctx.api.lintSkillsFolder({ revisionId, profiles: ['claude-upload'] });
612
+ if (!ctx.alive(gen) || !box.isConnected) return;
613
+ const p = res.profiles?.['claude-upload'];
614
+ const items = (p?.diagnostics || []).filter((d) => d.severity === 'error' || d.severity === 'warning');
615
+ setHTML(box, items.length
616
+ ? html`<p class="sx-note sx-note--warn" role="note">${t('skillsStudio.publish.claudeProblems', { errors: p.errors, warnings: p.warnings })}</p><ul class="sx-problems">${items.slice(0, 12).map((d) => html`<li class="sx-problem sx-problem--${d.severity}">${d.message}</li>`)}</ul>`
617
+ : html`<p class="sx-help sx-ok-text">${ICONS.ok} ${t('skillsStudio.publish.claudeOk')}</p>`);
618
+ } catch (err) { if (box.isConnected) setHTML(box, html`<p class="sx-note sx-note--warn" role="alert">${t('skillsStudio.publish.checkFailed', { error: studioErrorText(ctx.t, err) })}</p>`); }
619
+ }
620
+ }
621
+