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,639 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: the Library place
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The table of every skill, agent and command (sx-library.js): name and description, kind, where it comes from,
6
+ // its health (a word and an icon, never a colour alone), which runtimes have it, and when it last changed. Search,
7
+ // filters with counts (the app's own dropdown, never a native one), a sort that puts what needs attention first,
8
+ // and bulk actions that say per item what they did. The keyboard moves through the rows with the arrows (one row
9
+ // in the tab order), Enter opens, Space selects. At a narrow window the rows are cards (CSS).
10
+
11
+ import { studioErrorText } from './sx-errors.js';
12
+ import { dropdownHTML, wireDropdowns } from '../../cc-dropdown.js';
13
+ import { searchLibrary, filterRows, sortRows, worstHealth, SORTS, KINDS, SOURCES, RUNTIMES, HEALTH_FILTERS } from './sx-library.js';
14
+ import { subjectOf } from './sx-subject.js';
15
+ import { installStatus } from './sx-install.js';
16
+ import { alsoInstall, alsoInstallNow } from './sx-install-flow.js';
17
+ import { html, raw, setHTML, ICONS, healthIcon, relativeTime, fullTime, rovingFocus, plural } from './sx-dom.js';
18
+ import { dropKept, dropDraft } from './sx-docs.js';
19
+ import { encodeArtifactId } from '../ss-ids.js';
20
+ import { revealHidden } from '../ss-unicode.js';
21
+
22
+ let _life = null; // the listeners of the drawn header (filters): they end with the next drawing
23
+ let _lastChecked = null;
24
+
25
+ const RT_LETTER = { claude: 'C', codex: 'X', opencode: 'O' };
26
+ const DIAG_REASONS = ['no-skill-md', 'unreadable-head', 'link', 'too-deep', 'unreadable', 'outside-project', 'not-a-folder'];
27
+
28
+ /** Where a row comes from, in words: a label and a detail. */
29
+ export function sourceLabel(row, t) {
30
+ const detail = (() => {
31
+ switch (row.source) {
32
+ case 'plugin': return row.copies > 1 ? plural(t, 'skillsStudio.source.pluginCopies', row.copies, { plugin: row.pluginId || '' }) : (row.pluginId || '');
33
+ case 'project': return row.projectLabel || '';
34
+ case 'mine': return row.accounts.filter((a) => a !== 'default').length ? plural(t, 'skillsStudio.source.accounts', row.accounts.length) : '';
35
+ case 'library': return row.skill?.revisions ? plural(t, 'skillsStudio.source.libraryVersions', row.skill.revisions) : t('skillsStudio.source.libraryDraft');
36
+ case 'synabun': return row.installedCopies?.length ? plural(t, 'skillsStudio.source.installedCopies', row.installedCopies.length) : '';
37
+ default: return '';
38
+ }
39
+ })();
40
+ const label = row.source === 'mine' && row.scope === 'user' ? t('skillsStudio.source.mine')
41
+ : row.orphanBridge ? t('skillsStudio.source.legacyBridge')
42
+ : t(`skillsStudio.source.${row.source}`);
43
+ return { label, detail };
44
+ }
45
+
46
+ /** The words of a health item. */
47
+ export function healthWords(h, t) {
48
+ switch (h.code) {
49
+ case 'invalid': return plural(t, 'skillsStudio.health.invalid', h.count);
50
+ case 'warnings': return plural(t, 'skillsStudio.health.warnings', h.count);
51
+ case 'twice': return t('skillsStudio.health.twice');
52
+ case 'shadowed': return t('skillsStudio.health.shadowed');
53
+ case 'same-description': return t('skillsStudio.health.sameDescription');
54
+ default: return t(`skillsStudio.health.${h.code}`);
55
+ }
56
+ }
57
+
58
+ /** A health capsule: the worst item's word and icon, with the rest named in its title. */
59
+ /** Why a health item is there, for the capsule's title: which runtime does what, or which skills look the same. */
60
+ export function healthWhy(h, t) {
61
+ const runtime = h.runtime ? t(`skillsStudio.runtime.${h.runtime}`) : '';
62
+ if (h.code === 'shadowed' && runtime) return t('skillsStudio.health.shadowedWhy', { runtime });
63
+ if (h.code === 'twice' && runtime) return t('skillsStudio.health.twiceWhy', { runtime });
64
+ if (h.code === 'same-description' && h.names?.length) return t('skillsStudio.health.sameDescriptionWhy', { names: h.names.map((n) => revealHidden(String(n))).join(', ') });
65
+ return '';
66
+ }
67
+
68
+ export function healthCapsule(row, t) {
69
+ const worst = worstHealth(row);
70
+ const rest = row.health.filter((h) => h !== worst && h.sev !== 'ok');
71
+ const all = row.health.map((h) => { const why = healthWhy(h, t); return why ? `${healthWords(h, t)}: ${why}` : healthWords(h, t); }).join(' · ');
72
+ return html`<span class="sx-health sx-health--${worst.sev}" title="${all}">${healthIcon(worst.code)}<span>${healthWords(worst, t)}</span>${rest.length ? html`<span class="sx-health-more">+${rest.length}</span>` : ''}</span>`;
73
+ }
74
+
75
+ /**
76
+ * The one status of a library skill (sx-install.js installStatus) as a pill: draft, in review, changes asked for,
77
+ * approved · not installed, installed (where, in its title), installed with an update, not installed. A button
78
+ * when it opens something (`button`: the data-ws action).
79
+ */
80
+ export function statusPill(status, ctx, { button = null, id = null } = {}) {
81
+ const { t } = ctx;
82
+ const where = status.where.map((w) => {
83
+ const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`);
84
+ if (w.scope === 'project') return t('skillsStudio.publish.targetProject', { runtime, project: ctx.projects?.find((p) => p.path === w.projectPath)?.label || w.projectPath || '' });
85
+ if (w.accountId && w.accountId !== 'default') return t('skillsStudio.publish.targetAccount', { runtime, account: w.accountId });
86
+ return t('skillsStudio.publish.targetUser', { runtime });
87
+ });
88
+ const runtimes = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(', ');
89
+ const text = status.code === 'installed' ? t('skillsStudio.status.installed', { where: runtimes }) : status.code === 'outdated' ? t('skillsStudio.status.outdated', { where: runtimes }) : t(`skillsStudio.status.${status.code}`);
90
+ const title = where.length ? where.join(', ') : t(`skillsStudio.status.help.${status.code}`);
91
+ // Not installed is no tick: the dashed circle (healthIcon has no 'off' and would fall through to the tick).
92
+ const icon = status.code === 'installed' ? healthIcon('ok') : status.code === 'outdated' ? healthIcon('update') : status.code === 'review' || status.code === 'changes' ? healthIcon('review') : status.code === 'draft' ? healthIcon('draft') : status.code === 'approved' ? ICONS.publish : ICONS.off;
93
+ const inner = html`${icon}<span>${text}</span>`;
94
+ return button
95
+ ? html`<button type="button" class="sx-status sx-status--${status.code}" data-ws="${button}"${id ? html` id="${id}"` : ''} title="${title}">${inner}</button>`
96
+ : html`<span class="sx-status sx-status--${status.code}"${id ? html` id="${id}"` : ''} title="${title}">${inner}</span>`;
97
+ }
98
+
99
+ /** The runtime capsules of a row (C X O), each named in full. */
100
+ export function runtimeCapsules(row, t) {
101
+ return html`<span class="sx-rts">${RUNTIMES.map((rt) => {
102
+ const st = row.runtimes[rt]?.state || 'none';
103
+ const label = `${t(`skillsStudio.runtime.${rt}`)}: ${t(`skillsStudio.runtimeState.${st}`)}`;
104
+ return html`<span class="sx-rt sx-rt--${st}" role="img" aria-label="${label}" title="${label}">${RT_LETTER[rt]}</span>`;
105
+ })}</span>`;
106
+ }
107
+
108
+ const kindLabel = (row, t) => (row.kind === 'command' ? t('skillsStudio.kind.commandLegacy') : t(`skillsStudio.kind.${row.kind}`));
109
+
110
+ function filterHeadHTML(ctx) {
111
+ const { t, lib, counts } = ctx;
112
+ const c = counts || { kinds: {}, sources: {}, runtimes: {}, health: {}, total: 0 };
113
+ const entries = (key, values, countOf, words) => [
114
+ { value: 'all', label: t(`skillsStudio.library.filter.${key}All`), selected: lib[key] === 'all' || (key === 'health' && lib.health === 'any') },
115
+ ...values.map((v) => ({ value: v, label: `${words(v)} · ${countOf[v] ?? 0}`, selected: lib[key] === v })),
116
+ ];
117
+ const kind = entries('kind', KINDS, c.kinds, (v) => (v === 'command' ? t('skillsStudio.kind.commandLegacy') : t(`skillsStudio.kind.${v}`)));
118
+ const source = entries('source', SOURCES, c.sources, (v) => t(`skillsStudio.source.${v}`));
119
+ const runtime = entries('runtime', RUNTIMES, c.runtimes, (v) => t(`skillsStudio.runtime.${v}`));
120
+ const health = [{ value: 'any', label: t('skillsStudio.library.filter.healthAll'), selected: lib.health === 'any' }, ...HEALTH_FILTERS.map((v) => ({ value: v, label: `${t(`skillsStudio.library.healthFilter.${v}`)} · ${c.health[v] ?? 0}`, selected: lib.health === v }))];
121
+ const sort = SORTS.map((v) => ({ value: v, label: t(`skillsStudio.library.sort.${v}`), selected: lib.sort === v }));
122
+ const dd = (id, label, list) => html`<div class="sx-filter"><span class="sx-filter-key" id="${id}-label">${label}</span>${raw(dropdownHTML(id, list, { labelledBy: `${id}-label`, classes: 'sx-dd' }))}</div>`;
123
+ return html`
124
+ <div class="sx-place-head">
125
+ <h3 class="sx-place-title" id="sx-lib-title" tabindex="-1">${t('skillsStudio.library.title')}</h3>
126
+ <div class="sx-search">
127
+ ${ICONS.search}
128
+ <label class="sx-sr" for="sx-lib-search">${t('skillsStudio.library.searchLabel')}</label>
129
+ <input id="sx-lib-search" type="search" autocomplete="off" spellcheck="false" placeholder="${t('skillsStudio.library.searchPlaceholder')}" value="${lib.q}" data-autofocus>
130
+ </div>
131
+ ${dd('sx-f-kind', t('skillsStudio.library.filter.kind'), kind)}
132
+ ${dd('sx-f-source', t('skillsStudio.library.filter.source'), source)}
133
+ ${dd('sx-f-runtime', t('skillsStudio.library.filter.runtime'), runtime)}
134
+ ${dd('sx-f-health', t('skillsStudio.library.filter.health'), health)}
135
+ <span class="sx-spacer"></span>
136
+ ${dd('sx-f-sort', t('skillsStudio.library.sort.label'), sort)}
137
+ ${ctx.guest ? '' : html`<button type="button" class="sx-btn sx-btn--primary" data-lib="new">${ICONS.plus}<span>${t('skillsStudio.library.new')}</span></button>`}
138
+ </div>`;
139
+ }
140
+
141
+ /** Whether the server reads when each skill changed (catalog `updatedAt`): a server older than the page does not. */
142
+ const knowsUpdated = (ctx) => !ctx.capsKnown || ctx.caps.has('catalog-updated');
143
+
144
+ function rowHTML(row, ctx, selected, first) {
145
+ const { t, locale } = ctx;
146
+ const src = sourceLabel(row, t);
147
+ const name = row.label;
148
+ const description = row.shownDescription;
149
+ const updated = row.updatedAt ? relativeTime(row.updatedAt, locale) : '';
150
+ const unknown = knowsUpdated(ctx) ? t('skillsStudio.library.unknownDate') : t('skillsStudio.library.unknownDateRestart');
151
+ return html`<tr class="sx-lib-row${selected ? ' is-selected' : ''}" data-key="${row.key}">
152
+ <td class="sx-col-check"><input type="checkbox" class="sx-check" data-check="${row.key}" tabindex="-1"${selected ? ' checked' : ''} aria-label="${t('skillsStudio.library.select', { name })}"></td>
153
+ <td class="sx-col-name">
154
+ <button type="button" class="sx-name-btn" data-open="${row.key}" tabindex="${first ? '0' : '-1'}" aria-describedby="sx-desc-${row.key.replace(/[^A-Za-z0-9_-]/g, '_')}">${name}</button>
155
+ <span class="sx-desc" id="sx-desc-${row.key.replace(/[^A-Za-z0-9_-]/g, '_')}" title="${description}">${description || t('skillsStudio.library.noDescription')}</span>
156
+ </td>
157
+ <td class="sx-col-kind">${kindLabel(row, t)}</td>
158
+ <td class="sx-col-source"><span class="sx-source">${src.label}</span>${src.detail ? html`<span class="sx-source-detail" title="${src.detail}">${src.detail}</span>` : ''}</td>
159
+ <td class="sx-col-health">${healthCellHTML(row, ctx)}</td>
160
+ <td class="sx-col-rt">${runtimeCapsules(row, t)}</td>
161
+ <td class="sx-col-updated">${updated ? html`<time datetime="${row.updatedAt}" title="${fullTime(row.updatedAt, locale)}">${updated}</time>` : html`<span class="sx-dim" role="img" aria-label="${unknown}" title="${unknown}">—</span>`}</td>
162
+ </tr>`;
163
+ }
164
+
165
+ /**
166
+ * The health cell: a library skill shows its one status (the same as its workspace header) and its health only when
167
+ * something is wrong, with Install when it is approved and not installed yet; the rest shows its health.
168
+ */
169
+ function healthCellHTML(row, ctx) {
170
+ const { t } = ctx;
171
+ const status = installStatus({ skill: row.skill, row });
172
+ if (!status) return healthCapsule(row, t);
173
+ const worst = worstHealth(row);
174
+ // Installed for one assistant and not the others in effect: one press from the row adds them.
175
+ const also = ['installed', 'outdated'].includes(status.code) && !ctx.guest ? alsoInstall(ctx, { skill: row.skill, row }) : { targets: [], words: '' };
176
+ return html`<span class="sx-health-cell">${statusPill(status, ctx)}${worst.sev === 'err' || worst.sev === 'warn' ? healthCapsule(row, t) : ''}${status.code === 'approved' && !ctx.guest ? html`<button type="button" class="sx-btn sx-btn--sm sx-btn--primary" data-install="${row.key}" tabindex="-1" title="${t('skillsStudio.library.installHelp')}">${t('skillsStudio.library.install')}</button>` : ''}${also.targets.length ? html`<button type="button" class="sx-btn sx-btn--sm" data-also-install="${row.key}" tabindex="-1" title="${t('skillsStudio.library.alsoInstallHelp')}">${t('skillsStudio.library.alsoInstall', { targets: also.words })}</button>` : ''}</span>`;
177
+ }
178
+
179
+ /**
180
+ * The start-up move of the old Codex prompt / OpenCode command bridges (lib/skills/migration.js autoMigrate), said
181
+ * once: what moved, with Undo (the bridges come back, the folders it made go) and OK; and the bridge files of shipped
182
+ * skills that SynaBun did not move by itself because they are not proven its own, each with "Move it too" (the owner
183
+ * confirms exactly those bytes). Drawn from GET /v2/migrations/notice (read with the Library); nothing on an older server.
184
+ */
185
+ export function migrationNoticeHTML(ctx) {
186
+ const n = ctx.data?.notice;
187
+ if (!n || ctx.guest) return '';
188
+ const { t, tp } = ctx;
189
+ const out = [];
190
+ if (n.pending) {
191
+ const label = (m) => `${t(`skillsStudio.runtime.${['claude', 'codex', 'opencode'].includes(m.runtime) ? m.runtime : 'claude'}`)} /${m.name}`;
192
+ const moved = (n.pending.migrated || []).map(label);
193
+ // Moved by itself because its bytes were exactly an earlier SynaBun version's bridge (proof generated-previous).
194
+ const earlier = (n.pending.migrated || []).filter((m) => m.proof === 'generated-previous').map(label);
195
+ const failed = (n.pending.notMoved || []).map((m) => `/${m.name}`);
196
+ out.push(html`<div class="sx-notice sx-notice--info" role="status" data-notice-box="auto">
197
+ ${ICONS.ok}<span class="sx-notice-text">${tp('skillsStudio.notice.autoMoved', moved.length, { names: moved.join(', ') })}${earlier.length ? ` ${tp('skillsStudio.notice.autoEarlier', earlier.length, { names: earlier.join(', ') })}` : ''} ${t('skillsStudio.notice.autoWhy')}${failed.length ? ` ${tp('skillsStudio.notice.autoNotMoved', failed.length, { names: failed.join(', ') })}` : ''}</span>
198
+ <button type="button" class="sx-btn" data-notice="undo" data-journal="${n.pending.journalId}">${t('skillsStudio.notice.undo')}</button>
199
+ <button type="button" class="sx-btn sx-btn--ghost" data-notice="dismiss">${t('skillsStudio.notice.ok')}</button>
200
+ </div>`);
201
+ }
202
+ for (const o of n.offers || []) {
203
+ const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode'].includes(o.runtime) ? o.runtime : 'claude'}`);
204
+ out.push(html`<div class="sx-notice sx-notice--warn" role="note" data-notice-box="offer" data-bridge="${o.bridgeId}">
205
+ ${ICONS.warn}<span class="sx-notice-text">${t('skillsStudio.notice.offer', { runtime, name: `/${o.name}`, path: shortPath(o.path) })}</span>
206
+ <button type="button" class="sx-btn" data-notice="move" data-bridge="${o.bridgeId}" data-digest="${o.digest}">${t('skillsStudio.notice.moveToo')}</button>
207
+ </div>`);
208
+ }
209
+ return html`${out}`;
210
+ }
211
+
212
+ /**
213
+ * What the row's "Also install for …" did, kept above the list until OK: where it went and, per runtime, what a
214
+ * session needs to see it (SynaBun's own panels list it at once; a terminal session needs a new one). A toast alone
215
+ * would be gone before those lines are read.
216
+ */
217
+ function installNoticeHTML(ctx) {
218
+ const n = ctx.lib.installNotice;
219
+ if (!n || ctx.guest) return '';
220
+ return html`<div class="sx-notice sx-notice--${n.kind === 'info' ? 'info' : 'warn'}" role="status" data-notice-box="install">
221
+ ${n.kind === 'info' ? ICONS.ok : ICONS.warn}<span class="sx-notice-text"><strong>${n.name}</strong>: ${n.line}${n.lines.map((l) => html`<span class="sx-block">${l}</span>`)}</span>
222
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="install-notice-ok">${ctx.t('skillsStudio.notice.ok')}</button>
223
+ </div>`;
224
+ }
225
+
226
+ /** The home folder as ~ (the full path stays in the element's title). */
227
+ function shortPath(path) { return String(path || '').replace(/^\/Users\/[^/]+(?=\/)/, '~').replace(/^\/home\/[^/]+(?=\/)/, '~'); }
228
+
229
+ /** A button of the start-up move's notice: Undo, OK, or Move it too (a confirmed move: the plan with the confirmation, then exactly that plan). */
230
+ async function onNoticeAction(btn, ctx) {
231
+ const { t } = ctx;
232
+ const act = btn.dataset.notice;
233
+ const box = btn.closest('[data-notice-box]');
234
+ for (const b of box?.querySelectorAll('button') || []) b.disabled = true;
235
+ const gen = ctx.gen();
236
+ try {
237
+ if (act === 'dismiss') { await ctx.api.dismissSkillsMigrationNotice(); }
238
+ else if (act === 'undo') {
239
+ const out = await ctx.api.revertSkillsMigration(btn.dataset.journal);
240
+ if (ctx.alive(gen)) ctx.toast(out.status === 'reverted' ? t('skillsStudio.notice.undone') : t('skillsStudio.notice.undonePartly'), out.status === 'reverted' ? 'info' : 'error');
241
+ } else if (act === 'move') {
242
+ const confirm = { [btn.dataset.bridge]: btn.dataset.digest };
243
+ const planned = await ctx.api.planSkillsBridgeMigrationConfirmed(confirm);
244
+ const out = await ctx.api.runSkillsBridgeMigration(planned.plan.planDigest, confirm);
245
+ if (ctx.alive(gen)) ctx.toast(out.status === 'done' ? t('skillsStudio.notice.moved') : t('skillsStudio.notice.movedPartly'), out.status === 'done' ? 'info' : 'error');
246
+ }
247
+ } catch (err) {
248
+ if (ctx.alive(gen)) ctx.toast(t('skillsStudio.notice.failed', { error: studioErrorText(ctx.t, err) }), 'error');
249
+ }
250
+ if (ctx.alive(gen)) ctx.reload();
251
+ }
252
+
253
+ function keptNoticeHTML(ctx) {
254
+ const kept = Object.entries(ctx.keptDrafts()).filter(([, d]) => !ctx.workspaces.has(d.wsKey));
255
+ if (!kept.length) return '';
256
+ const known = kept.filter(([, d]) => ctx.rows.some((r) => r.key === d.wsKey));
257
+ // Named whether or not their skill is listed now (one deleted, or from another data home): only those listed reopen.
258
+ const names = [...new Set([...known, ...kept.filter((k) => !known.includes(k))].map(([, d]) => String(d.name || d.label || d.path || '?')))].slice(0, 4);
259
+ return html`<div class="sx-notice sx-notice--info" role="status">
260
+ ${ICONS.draft}<span class="sx-notice-text">${ctx.tp('skillsStudio.library.kept', kept.length, { names: names.join(', ') })}</span>
261
+ ${known.length ? html`<button type="button" class="sx-btn" data-lib="kept-reopen">${ctx.t('skillsStudio.library.keptReopen')}</button>` : ''}
262
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="kept-discard">${ctx.t('skillsStudio.library.keptDiscard')}</button>
263
+ </div>`;
264
+ }
265
+
266
+ function summaryHTML(ctx, shown) {
267
+ const { t, tp, counts, data } = ctx;
268
+ const total = counts?.total ?? 0;
269
+ const parts = [t('skillsStudio.library.shown', { shown: shown.length, total })];
270
+ if (counts?.views?.attention) parts.push(tp('skillsStudio.library.attentionCount', counts.views.attention));
271
+ const diag = data?.catalog?.diagnostics || [];
272
+ // A server older than the page reads no change times: the dashes are said once, with what brings them.
273
+ const restart = knowsUpdated(ctx) ? '' : html` · <span class="sx-note-warn" id="sx-lib-updated-restart">${t('skillsStudio.library.updatedRestart')}</span>`;
274
+ return html`<p class="sx-summary" role="status">${parts.join(' · ')}${diag.length ? html` · <button type="button" class="sx-link" data-lib="diagnostics">${tp('skillsStudio.library.diagnostics', diag.length)}</button>` : ''}${data?.catalog?.truncated ? html` · <span class="sx-warn-text">${t('skillsStudio.library.truncated')}</span>` : ''}${restart}</p>`;
275
+ }
276
+
277
+ /**
278
+ * What a bulk Delete does with a row: `disk` removes it from where it is (an agent, a command, a folder of the
279
+ * owner's not in the library), `library` takes a library skill out of the library (a folder it came from stays; one
280
+ * installed somewhere is left out: its installations are removed in Publish first), null leaves it.
281
+ */
282
+ export function deleteKind(ctx, row, s) {
283
+ if (ctx.guest) return null;
284
+ if ((s.mode === 'inplace' || s.mode === 'adopt') && s.v1Id) return 'disk';
285
+ if (s.mode === 'library' && row.skill?.skillId && row.skill.origin?.kind !== 'bundled' && !(row.skill.installations || []).length && ctx.caps.has('library-delete')) return 'library';
286
+ return null;
287
+ }
288
+
289
+ function bulkHTML(ctx, rows) {
290
+ const { t, tp, lib } = ctx;
291
+ if (!lib.selected.size || ctx.guest) return '';
292
+ const chosen = rows.filter((r) => lib.selected.has(r.key));
293
+ const subjects = chosen.map((r) => ({ row: r, s: subjectOf(r, { guest: ctx.guest, caps: ctx.caps }) }));
294
+ const adoptable = subjects.filter((x) => x.s.mode === 'adopt').length;
295
+ const exportable = subjects.filter((x) => x.s.v1Id || (x.row.skill?.latest && ctx.caps.has('export'))).length;
296
+ const deletable = subjects.filter((x) => deleteKind(ctx, x.row, x.s)).length;
297
+ return html`<div class="sx-bulk" role="region" aria-label="${t('skillsStudio.library.bulk.region')}">
298
+ <span class="sx-bulk-count">${tp('skillsStudio.library.bulk.selected', lib.selected.size)}</span>
299
+ <button type="button" class="sx-btn" data-lib="bulk-adopt"${adoptable ? '' : ' disabled'} title="${t('skillsStudio.library.bulk.adoptHelp')}">${t('skillsStudio.library.bulk.adopt', { count: adoptable })}</button>
300
+ <button type="button" class="sx-btn" data-lib="bulk-export"${exportable ? '' : ' disabled'}>${ICONS.download}<span>${t('skillsStudio.library.bulk.export', { count: exportable })}</span></button>
301
+ <button type="button" class="sx-btn sx-btn--danger" data-lib="bulk-delete"${deletable ? '' : ' disabled'}>${ICONS.trash}<span>${t('skillsStudio.library.bulk.delete', { count: deletable })}</span></button>
302
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="bulk-clear">${t('skillsStudio.library.bulk.clear')}</button>
303
+ </div>`;
304
+ }
305
+
306
+ /** The Archived view: the skills deleted from the library, each with Restore (its versions never left). */
307
+ function archivedHTML(ctx) {
308
+ const { t, locale } = ctx;
309
+ const list = Array.isArray(ctx.data?.archived) ? ctx.data.archived : [];
310
+ if (!list.length) return html`<div class="sx-empty"><h4 class="sx-empty-title">${t('skillsStudio.library.archivedTitle')}</h4><p>${t('skillsStudio.library.archivedEmpty')}</p></div>`;
311
+ return html`<section class="sx-archived" aria-labelledby="sx-archived-title">
312
+ <h4 class="sx-section-title" id="sx-archived-title">${t('skillsStudio.library.archivedTitle')}</h4>
313
+ <p class="sx-help">${t('skillsStudio.library.archivedHelp')}</p>
314
+ <ul class="sx-installs sx-archived-list">${list.map((a) => html`<li class="sx-install-row" data-archived="${a.skillId}">
315
+ <span class="sx-install-where"><strong>${revealHidden(String(a.name || a.skillId))}</strong></span>
316
+ <span class="sx-dim">${t('skillsStudio.library.archivedWhen', { when: relativeTime(a.archivedAt, locale), by: a.archivedBy || 'owner' })} · ${plural(t, 'skillsStudio.library.archivedVersions', Number(a.revisions) || 0)}</span>
317
+ ${ctx.guest ? '' : html`<span class="sx-install-actions"><button type="button" class="sx-btn sx-btn--sm sx-btn--primary" data-restore="${a.skillId}">${t('skillsStudio.library.restore')}</button></span>`}
318
+ </li>`)}</ul>
319
+ </section>`;
320
+ }
321
+
322
+ /** The row's "Also install for …": one press, the installed version to the assistants that lack it. */
323
+ async function onAlsoInstall(btn, host, ctx) {
324
+ const row = ctx.rows.find((r) => r.key === btn.dataset.alsoInstall);
325
+ if (!row?.skill) return;
326
+ btn.disabled = true;
327
+ const gen = ctx.gen();
328
+ const words = await alsoInstallNow(ctx, { skill: row.skill, row });
329
+ if (!ctx.alive(gen)) return;
330
+ if (words) {
331
+ ctx.lib.installNotice = { kind: words.kind, name: row.name || row.skill.name || '', line: words.line, lines: Array.isArray(words.lines) ? words.lines : [] };
332
+ ctx.toast(words.line, words.kind);
333
+ ctx.announce([words.line, ...ctx.lib.installNotice.lines].join(' '));
334
+ }
335
+ ctx.reload();
336
+ }
337
+
338
+ /** Restore from the Archived view: the record and its draft are back, the versions never left. */
339
+ async function onRestore(btn, host, ctx) {
340
+ const { t } = ctx;
341
+ btn.disabled = true;
342
+ const gen = ctx.gen();
343
+ try {
344
+ const out = await ctx.api.restoreSkillsSkill(btn.dataset.restore);
345
+ if (!ctx.alive(gen)) return;
346
+ ctx.toast(t('skillsStudio.library.restored', { name: out.name || '' }));
347
+ ctx.announce(t('skillsStudio.library.restored', { name: out.name || '' }));
348
+ } catch (err) {
349
+ if (!ctx.alive(gen)) return;
350
+ ctx.toast(t('skillsStudio.library.restoreFailed', { error: studioErrorText(ctx.t, err) }), 'error');
351
+ }
352
+ ctx.reload();
353
+ }
354
+
355
+ function tableHTML(ctx, shown) {
356
+ const { t, lib } = ctx;
357
+ if (lib.view === 'archived') return archivedHTML(ctx);
358
+ if (ctx.loading) return html`<div class="sx-empty" role="status"><p>${t('skillsStudio.library.loading')}</p></div>`;
359
+ if (ctx.loadError && !ctx.rows.length) {
360
+ return html`<div class="sx-empty" role="alert"><h4 class="sx-empty-title">${t('skillsStudio.library.loadFailed')}</h4><p class="sx-dim">${studioErrorText(ctx.t, ctx.loadError)}</p><button type="button" class="sx-btn" data-lib="retry">${t('skillsStudio.shell.retry')}</button></div>`;
361
+ }
362
+ if (!ctx.rows.length) {
363
+ return html`<div class="sx-empty"><h4 class="sx-empty-title">${t('skillsStudio.library.emptyTitle')}</h4><p>${t('skillsStudio.library.emptyHelp')}</p>${ctx.guest ? '' : html`<button type="button" class="sx-btn sx-btn--primary" data-lib="new">${t('skillsStudio.library.new')}</button>`}</div>`;
364
+ }
365
+ if (!shown.length) {
366
+ return html`<div class="sx-empty"><h4 class="sx-empty-title">${t('skillsStudio.library.noMatchTitle')}</h4><p>${t('skillsStudio.library.noMatchHelp')}</p><button type="button" class="sx-btn" data-lib="clear-filters">${t('skillsStudio.library.clearFilters')}</button></div>`;
367
+ }
368
+ const allChecked = shown.every((r) => lib.selected.has(r.key));
369
+ return html`<table class="sx-lib">
370
+ <caption class="sx-sr">${t('skillsStudio.library.caption')}</caption>
371
+ <thead><tr>
372
+ <th class="sx-col-check" scope="col"><input type="checkbox" class="sx-check" data-check-all="1"${allChecked ? ' checked' : ''} aria-label="${t('skillsStudio.library.selectAll')}"${ctx.guest ? ' disabled' : ''}></th>
373
+ <th class="sx-col-name" scope="col">${t('skillsStudio.library.col.name')}</th>
374
+ <th class="sx-col-kind" scope="col">${t('skillsStudio.library.col.kind')}</th>
375
+ <th class="sx-col-source" scope="col">${t('skillsStudio.library.col.source')}</th>
376
+ <th class="sx-col-health" scope="col">${t('skillsStudio.library.col.health')}</th>
377
+ <th class="sx-col-rt" scope="col">${t('skillsStudio.library.col.runtimes')}</th>
378
+ <th class="sx-col-updated" scope="col">${t('skillsStudio.library.col.updated')}</th>
379
+ </tr></thead>
380
+ <tbody>${shown.map((r, i) => rowHTML(r, ctx, lib.selected.has(r.key), lib.focus ? r.key === lib.focus : i === 0))}</tbody>
381
+ </table>`;
382
+ }
383
+
384
+ const visible = (ctx) => sortRows(filterRows(ctx.rows, ctx.lib), ctx.lib.sort);
385
+
386
+ /** Draw the Library place. */
387
+ /** The Library is left for another place: its listeners on the shared main area end. */
388
+ export function leaveLibraryPlace() { _life?.abort(); _life = null; }
389
+
390
+ export function renderLibraryPlace(host, ctx) {
391
+ _life?.abort();
392
+ _life = new AbortController();
393
+ const signal = _life.signal;
394
+ const shown = visible(ctx);
395
+ setHTML(host, html`<section class="sx-place sx-place--library" aria-labelledby="sx-lib-title">
396
+ ${filterHeadHTML(ctx)}
397
+ <div class="sx-place-notices">${installNoticeHTML(ctx)}${migrationNoticeHTML(ctx)}${keptNoticeHTML(ctx)}${updateNoticeHTML(ctx)}</div>
398
+ <div class="sx-place-scroll" id="sx-lib-scroll">
399
+ <div id="sx-lib-summary">${summaryHTML(ctx, shown)}</div>
400
+ <div id="sx-lib-table">${tableHTML(ctx, shown)}</div>
401
+ </div>
402
+ <div id="sx-lib-bulk">${bulkHTML(ctx, shown)}</div>
403
+ </section>`);
404
+ wireDropdowns(host, { signal });
405
+ for (const [id, key] of [['sx-f-kind', 'kind'], ['sx-f-source', 'source'], ['sx-f-runtime', 'runtime'], ['sx-f-health', 'health'], ['sx-f-sort', 'sort']]) {
406
+ host.querySelector(`#${id}`)?.addEventListener('change', (e) => {
407
+ ctx.lib[key] = e.target.value;
408
+ if (key !== 'sort') ctx.lib.view = 'all';
409
+ ctx.saveLibraryView();
410
+ redrawRows(host, ctx);
411
+ }, { signal });
412
+ }
413
+ const search = host.querySelector('#sx-lib-search');
414
+ search?.addEventListener('input', () => { const starting = !ctx.lib.q.trim() && search.value.trim(); searchLibrary(ctx.lib, search.value); ctx.saveLibraryView(); if (starting) ctx.render({ keepFocus: true }); else redrawRows(host, ctx); }, { signal });
415
+ host.addEventListener('click', (e) => onClick(e, host, ctx), { signal });
416
+ host.addEventListener('change', (e) => onCheck(e, host, ctx), { signal });
417
+ host.addEventListener('keydown', (e) => onRowKey(e, host, ctx), { signal });
418
+ const table = host.querySelector('#sx-lib-table');
419
+ if (table) rovingFocus(table, '.sx-name-btn', { signal, onMove: (el) => { ctx.lib.focus = el.dataset.open; el.closest('tr')?.scrollIntoView({ block: 'nearest' }); } });
420
+ }
421
+
422
+ /** The table, its summary and the bulk bar again, after a filter or the selection changed (the header stays). */
423
+ function redrawRows(host, ctx) {
424
+ const shown = visible(ctx);
425
+ setHTML(host.querySelector('#sx-lib-summary'), summaryHTML(ctx, shown));
426
+ setHTML(host.querySelector('#sx-lib-table'), tableHTML(ctx, shown));
427
+ setHTML(host.querySelector('#sx-lib-bulk'), bulkHTML(ctx, shown));
428
+ const table = host.querySelector('#sx-lib-table');
429
+ if (table) rovingFocus(table, '.sx-name-btn', { signal: _life?.signal, onMove: (el) => { ctx.lib.focus = el.dataset.open; } });
430
+ ctx.announce(ctx.t('skillsStudio.library.shown', { shown: shown.length, total: ctx.counts?.total ?? 0 }));
431
+ }
432
+
433
+ /** Put the keyboard in the Library's search field (⌘/Ctrl+P). */
434
+ export function focusLibrary(panel) {
435
+ requestAnimationFrame(() => panel?.querySelector('#sx-lib-search')?.focus());
436
+ }
437
+
438
+ function updateNoticeHTML(ctx) {
439
+ if (!ctx.updates.length || ctx.lib.view !== 'all') return '';
440
+ return html`<div class="sx-notice sx-notice--info" role="status">${ICONS.update}<span class="sx-notice-text">${ctx.tp('skillsStudio.library.updatesNotice', ctx.updates.length, { names: ctx.updates.map((u) => u.name).join(', ') })}</span>
441
+ <button type="button" class="sx-btn" data-act="place" data-place="review">${ctx.t('skillsStudio.library.reviewUpdates')}</button></div>`;
442
+ }
443
+
444
+ async function onClick(e, host, ctx) {
445
+ const notice = e.target.closest?.('[data-notice]');
446
+ if (notice && !notice.disabled) { onNoticeAction(notice, ctx); return; }
447
+ const install = e.target.closest?.('[data-install]');
448
+ if (install && host.contains(install)) { ctx.lib.focus = install.dataset.install; ctx.openRow(install.dataset.install, { tab: 'publish' }); return; }
449
+ const also = e.target.closest?.('[data-also-install]');
450
+ if (also && host.contains(also) && !also.disabled) { onAlsoInstall(also, host, ctx); return; }
451
+ const restore = e.target.closest?.('[data-restore]');
452
+ if (restore && host.contains(restore) && !restore.disabled) { onRestore(restore, host, ctx); return; }
453
+ const open = e.target.closest?.('[data-open]');
454
+ if (open && host.contains(open)) { ctx.lib.focus = open.dataset.open; ctx.openRow(open.dataset.open); return; }
455
+ const btn = e.target.closest?.('[data-lib]');
456
+ if (!btn || !host.contains(btn) || btn.disabled) return;
457
+ const act = btn.dataset.lib;
458
+ if (act === 'new') ctx.goPlace('create');
459
+ else if (act === 'retry') ctx.reload();
460
+ else if (act === 'install-notice-ok') { ctx.lib.installNotice = null; ctx.render(); }
461
+ else if (act === 'clear-filters') { Object.assign(ctx.lib, { view: 'all', kind: 'all', source: 'all', runtime: 'all', health: 'any', q: '' }); ctx.saveLibraryView(); ctx.render(); }
462
+ else if (act === 'bulk-clear') { ctx.lib.selected.clear(); redrawRows(host, ctx); }
463
+ else if (act === 'diagnostics') showDiagnostics(ctx);
464
+ else if (act === 'kept-reopen') reopenKept(ctx);
465
+ else if (act === 'kept-discard') discardKept(ctx);
466
+ else if (act === 'bulk-adopt') bulkAdopt(ctx);
467
+ else if (act === 'bulk-export') bulkExport(ctx);
468
+ else if (act === 'bulk-delete') bulkDelete(ctx);
469
+ }
470
+
471
+ function onCheck(e, host, ctx) {
472
+ const box = e.target;
473
+ if (!(box instanceof HTMLInputElement) || box.type !== 'checkbox') return;
474
+ const shown = visible(ctx);
475
+ if (box.dataset.checkAll) {
476
+ if (box.checked) for (const r of shown) ctx.lib.selected.add(r.key); else for (const r of shown) ctx.lib.selected.delete(r.key);
477
+ } else if (box.dataset.check) {
478
+ const key = box.dataset.check;
479
+ // Shift-click selects every row between the last one ticked and this one.
480
+ if (e.shiftKey && _lastChecked) {
481
+ const keys = shown.map((r) => r.key);
482
+ const [a, b] = [keys.indexOf(_lastChecked), keys.indexOf(key)].sort((x, y) => x - y);
483
+ if (a >= 0 && b >= 0) for (const k of keys.slice(a, b + 1)) { if (box.checked) ctx.lib.selected.add(k); else ctx.lib.selected.delete(k); }
484
+ } else if (box.checked) ctx.lib.selected.add(key); else ctx.lib.selected.delete(key);
485
+ _lastChecked = key;
486
+ } else return;
487
+ const focusKey = box.dataset.check;
488
+ redrawRows(host, ctx);
489
+ if (focusKey) host.querySelector(`[data-check="${CSS.escape(focusKey)}"]`)?.focus();
490
+ }
491
+
492
+ function onRowKey(e, host, ctx) {
493
+ const btn = e.target.closest?.('.sx-name-btn');
494
+ if (!btn || e.key !== ' ' || ctx.guest) return;
495
+ e.preventDefault();
496
+ const key = btn.dataset.open;
497
+ if (ctx.lib.selected.has(key)) ctx.lib.selected.delete(key); else ctx.lib.selected.add(key);
498
+ ctx.lib.focus = key;
499
+ redrawRows(host, ctx);
500
+ host.querySelector(`.sx-name-btn[data-open="${CSS.escape(key)}"]`)?.focus();
501
+ }
502
+
503
+ // ── Notices and bulk actions ──
504
+
505
+ async function showDiagnostics(ctx) {
506
+ const { t } = ctx;
507
+ const diag = ctx.data?.catalog?.diagnostics || [];
508
+ const list = document.createElement('ul');
509
+ list.className = 'sx-plain-list';
510
+ for (const d of diag.slice(0, 50)) {
511
+ const li = document.createElement('li');
512
+ const key = `skillsStudio.library.diagReason.${d.reason}`;
513
+ const reason = DIAG_REASONS.includes(d.reason) ? t(key) : (d.message || d.reason || '');
514
+ li.textContent = `${d.path || t('skillsStudio.library.diagRoot')}: ${reason}`;
515
+ li.title = d.message || '';
516
+ list.append(li);
517
+ }
518
+ await ctx.ask({ title: t('skillsStudio.library.diagnosticsTitle'), lines: [t('skillsStudio.library.diagnosticsHelp')], extra: list, buttons: [{ id: 'ok', label: t('skillsStudio.dialog.close'), kind: 'primary' }], cancelId: 'ok', wide: true });
519
+ }
520
+
521
+ async function reopenKept(ctx) {
522
+ const kept = Object.values(ctx.keptDrafts()).filter((d) => !ctx.workspaces.has(d.wsKey) && ctx.rows.some((r) => r.key === d.wsKey));
523
+ const first = kept[0];
524
+ if (!first) return;
525
+ await ctx.openRow(first.wsKey, { tab: 'edit', file: first.path });
526
+ const others = new Set(kept.slice(1).map((d) => d.wsKey));
527
+ if (others.size) ctx.toast(ctx.tp('skillsStudio.library.keptMore', others.size));
528
+ }
529
+
530
+ async function discardKept(ctx) {
531
+ const { t, tp } = ctx;
532
+ const kept = Object.entries(ctx.keptDrafts()).filter(([, d]) => !ctx.workspaces.has(d.wsKey));
533
+ const answer = await ctx.ask({
534
+ title: t('skillsStudio.library.keptDiscardTitle'),
535
+ lines: [tp('skillsStudio.library.keptDiscardHelp', kept.length)],
536
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'discard', label: t('skillsStudio.dialog.discard'), kind: 'danger' }],
537
+ focus: 'cancel',
538
+ });
539
+ if (answer !== 'discard') return;
540
+ dropKept(kept.map(([key]) => key));
541
+ ctx.render();
542
+ }
543
+
544
+ async function bulkAdopt(ctx) {
545
+ const { t, tp, api } = ctx;
546
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key) && subjectOf(r, { caps: ctx.caps }).mode === 'adopt');
547
+ const answer = await ctx.ask({
548
+ title: tp('skillsStudio.library.bulk.adoptTitle', chosen.length),
549
+ lines: [t('skillsStudio.library.bulk.adoptExplain'), ...chosen.slice(0, 8).map((r) => `• ${r.label}`)],
550
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'adopt', label: t('skillsStudio.library.bulk.adoptConfirm'), kind: 'primary' }],
551
+ });
552
+ if (answer !== 'adopt') return;
553
+ const gen = ctx.gen();
554
+ let done = 0; const failed = [];
555
+ for (const row of chosen) {
556
+ try { await api.adoptSkillsArtifact(row.primary.id, { idempotencyKey: ctx.key('adopt') }); done++; } catch (err) { failed.push(`${row.label}: ${studioErrorText(ctx.t, err)}`); }
557
+ }
558
+ if (!ctx.alive(gen)) return;
559
+ ctx.toast(failed.length ? t('skillsStudio.library.bulk.adoptPartial', { done, failed: failed.length, first: failed[0] }) : tp('skillsStudio.library.bulk.adoptDone', done), failed.length ? 'error' : 'info');
560
+ ctx.lib.selected.clear();
561
+ await ctx.reload();
562
+ }
563
+
564
+ function bulkExport(ctx) {
565
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key));
566
+ let n = 0;
567
+ for (const row of chosen) {
568
+ const s = subjectOf(row, { caps: ctx.caps });
569
+ const url = row.skill?.latest && ctx.caps.has('export') ? ctx.api.getSkillsRevisionExportUrl(row.skill.latest.revisionId, { format: 'bundle' })
570
+ : s.v1Id ? ctx.api.getSkillsExportUrl(s.v1Id) : null;
571
+ if (!url) continue;
572
+ const a = document.createElement('a');
573
+ a.href = url;
574
+ a.download = '';
575
+ a.rel = 'noopener';
576
+ document.body.append(a);
577
+ a.click();
578
+ a.remove();
579
+ n++;
580
+ }
581
+ ctx.toast(ctx.tp('skillsStudio.library.bulk.exported', n));
582
+ }
583
+
584
+ async function bulkDelete(ctx) {
585
+ const { t, tp, api } = ctx;
586
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key)).map((r) => ({ row: r, s: subjectOf(r, { caps: ctx.caps }) }))
587
+ .map((x) => ({ ...x, kind: deleteKind(ctx, x.row, x.s) })).filter((x) => x.kind);
588
+ if (!chosen.length) return;
589
+ const onDisk = chosen.filter((x) => x.kind === 'disk');
590
+ const inLibrary = chosen.filter((x) => x.kind === 'library');
591
+ // Unsaved text in an open workspace of one of them goes with it only when the owner says so.
592
+ const unsaved = chosen.filter((x) => ctx.workspaces.get(x.row.key)?.tabs.some((tab) => tab.dirty));
593
+ const box = document.createElement('div');
594
+ box.className = 'sx-dlg-form';
595
+ const label = document.createElement('label');
596
+ label.className = 'sx-field-label';
597
+ label.htmlFor = 'sx-del-confirm';
598
+ label.textContent = t('skillsStudio.library.bulk.deleteType', { word: t('skillsStudio.library.bulk.deleteWord') });
599
+ const input = document.createElement('input');
600
+ input.id = 'sx-del-confirm';
601
+ input.className = 'sx-input';
602
+ input.autocomplete = 'off';
603
+ const err = document.createElement('p');
604
+ err.className = 'ss-dlg-error';
605
+ err.setAttribute('role', 'alert');
606
+ box.append(label, input, err);
607
+ const answer = await ctx.ask({
608
+ title: tp('skillsStudio.library.bulk.deleteTitle', chosen.length),
609
+ // Two kinds, said apart: what is removed from where it is, and what only leaves the library.
610
+ lines: [
611
+ ...(onDisk.length ? [t('skillsStudio.library.bulk.deleteExplain'), ...onDisk.slice(0, 8).map((x) => `• ${x.row.label} (${x.row.path || ''})`)] : []),
612
+ ...(inLibrary.length ? [t('skillsStudio.library.bulk.deleteLibraryExplain'), ...inLibrary.slice(0, 8).map((x) => `• ${x.row.label}`)] : []),
613
+ ...(unsaved.length ? [tp('skillsStudio.library.bulk.deleteUnsaved', unsaved.length)] : []),
614
+ ],
615
+ extra: box,
616
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'delete', label: t('skillsStudio.library.bulk.deleteConfirm'), kind: 'danger' }],
617
+ focus: 'cancel',
618
+ accept: (id) => { if (id === 'delete' && input.value.trim().toLowerCase() !== t('skillsStudio.library.bulk.deleteWord').toLowerCase()) { err.textContent = t('skillsStudio.library.bulk.deleteTypeError'); return false; } return true; },
619
+ });
620
+ if (answer !== 'delete') return;
621
+ const gen = ctx.gen();
622
+ let done = 0; const failed = [];
623
+ for (const x of chosen) {
624
+ try {
625
+ if (x.kind === 'disk') await api.deleteSkillsArtifact(x.s.v1Id);
626
+ else await api.deleteLibrarySkill(x.row.skill.skillId);
627
+ done++;
628
+ const ws = ctx.workspaces.get(x.row.key);
629
+ if (ws) { for (const tab of ws.tabs) dropDraft(ws, tab); ctx.closeWorkspace(ws); }
630
+ } catch (e) { failed.push(`${x.row.label}: ${studioErrorText(ctx.t, e)}`); }
631
+ }
632
+ if (!ctx.alive(gen)) return;
633
+ ctx.toast(failed.length ? t('skillsStudio.library.bulk.deletePartial', { done, failed: failed.length, first: failed[0] }) : tp('skillsStudio.library.bulk.deleted', done), failed.length ? 'error' : 'info');
634
+ ctx.lib.selected.clear();
635
+ await ctx.reload();
636
+ }
637
+
638
+ /** The P0 id of a row's entry (exported for the workspace's Export of an agent or a command). */
639
+ export const v1ExportId = (entry) => (entry?.path ? encodeArtifactId(`${entry.kind}:${entry.path}`) : null);