synabun 2.0.10 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (656) hide show
  1. package/CHANGELOG.md +207 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +13 -13
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +30 -17
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +7 -6
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +302 -0
  24. package/lib/virustotal.mjs +203 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  37. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  38. package/mcp-server/dist/services/budget-judgments.js +5 -3
  39. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  40. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  41. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  42. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  43. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  44. package/mcp-server/dist/services/identity.d.ts +2 -2
  45. package/mcp-server/dist/services/identity.js +2 -2
  46. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  47. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  48. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  49. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  50. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  51. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  52. package/mcp-server/dist/services/memory-sessions.js +28 -2
  53. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  54. package/mcp-server/dist/services/neural-interface.d.ts +18 -11
  55. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  56. package/mcp-server/dist/services/neural-interface.js +67 -11
  57. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  58. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  59. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  60. package/mcp-server/dist/services/server-instructions.js +22 -63
  61. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  62. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  63. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  64. package/mcp-server/dist/services/tool-catalog.js +339 -0
  65. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  66. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  67. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  68. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  69. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  70. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  71. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  72. package/mcp-server/dist/services/tool-list.js +334 -0
  73. package/mcp-server/dist/services/tool-list.js.map +1 -0
  74. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  75. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  76. package/mcp-server/dist/services/tool-runtime.js +400 -0
  77. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  78. package/mcp-server/dist/services/typesafe-config.js +1 -1
  79. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  80. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  81. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  82. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  83. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  84. package/mcp-server/dist/tools/agents.d.ts +14 -14
  85. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  86. package/mcp-server/dist/tools/agents.js +29 -19
  87. package/mcp-server/dist/tools/agents.js.map +1 -1
  88. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  89. package/mcp-server/dist/tools/choice.d.ts +6 -0
  90. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  91. package/mcp-server/dist/tools/choice.js +18 -13
  92. package/mcp-server/dist/tools/choice.js.map +1 -1
  93. package/mcp-server/dist/tools/git-tools.js +2 -2
  94. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  95. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  96. package/mcp-server/dist/tools/image-tools.js +1 -1
  97. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  98. package/mcp-server/dist/tools/morelogin.js +9 -6
  99. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  100. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  101. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  102. package/mcp-server/dist/tools/skill-tools.js +642 -0
  103. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  104. package/mcp-server/dist/tools/skill.d.ts +7 -0
  105. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  106. package/mcp-server/dist/tools/skill.js +11 -0
  107. package/mcp-server/dist/tools/skill.js.map +1 -0
  108. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  109. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  110. package/mcp-server/dist/tools/tool-groups.js +147 -0
  111. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  112. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  113. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  114. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  115. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  116. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  117. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  118. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  119. package/mcp-server/package-lock.json +2 -2
  120. package/mcp-server/package.json +1 -1
  121. package/mcp-server/src/http.ts +75 -12
  122. package/mcp-server/src/index.ts +147 -154
  123. package/mcp-server/src/preload.ts +12 -12
  124. package/mcp-server/src/services/budget-judgments.ts +5 -3
  125. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  126. package/mcp-server/src/services/identity.ts +2 -2
  127. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  128. package/mcp-server/src/services/memory-sessions.ts +24 -3
  129. package/mcp-server/src/services/neural-interface.ts +81 -21
  130. package/mcp-server/src/services/server-instructions.ts +29 -64
  131. package/mcp-server/src/services/tool-catalog.ts +403 -0
  132. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  133. package/mcp-server/src/services/tool-list.ts +410 -0
  134. package/mcp-server/src/services/tool-runtime.ts +457 -0
  135. package/mcp-server/src/services/typesafe-config.ts +1 -1
  136. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  137. package/mcp-server/src/tools/agents.ts +29 -20
  138. package/mcp-server/src/tools/choice.ts +17 -12
  139. package/mcp-server/src/tools/git-tools.ts +2 -2
  140. package/mcp-server/src/tools/image-tools.ts +1 -1
  141. package/mcp-server/src/tools/morelogin.ts +6 -4
  142. package/mcp-server/src/tools/skill-tools.ts +592 -0
  143. package/mcp-server/src/tools/skill.ts +13 -0
  144. package/mcp-server/src/tools/tool-groups.ts +164 -0
  145. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  146. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  147. package/neural-interface/i18n/en.json +3953 -228
  148. package/neural-interface/i18n/pt-BR.json +3901 -179
  149. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  150. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  151. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  152. package/neural-interface/lib/antigravity/bypass.js +100 -0
  153. package/neural-interface/lib/antigravity/catalog.js +273 -0
  154. package/neural-interface/lib/antigravity/home.js +260 -0
  155. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  156. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  157. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  158. package/neural-interface/lib/antigravity/policy.js +74 -0
  159. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  160. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  161. package/neural-interface/lib/antigravity/usage.js +43 -0
  162. package/neural-interface/lib/assistant-api.js +128 -28
  163. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  164. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  165. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  166. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  167. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  168. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  169. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  170. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  171. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  172. package/neural-interface/lib/assistant-budget-split.js +42 -15
  173. package/neural-interface/lib/assistant-catalog.js +81 -15
  174. package/neural-interface/lib/assistant-config.js +6 -3
  175. package/neural-interface/lib/assistant-dispatch.js +1073 -200
  176. package/neural-interface/lib/assistant-envelope.js +156 -2
  177. package/neural-interface/lib/assistant-evidence.js +80 -0
  178. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  179. package/neural-interface/lib/assistant-persona.js +93 -19
  180. package/neural-interface/lib/assistant-plan-permissions.js +25 -9
  181. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  182. package/neural-interface/lib/assistant-route-gate.js +6 -4
  183. package/neural-interface/lib/assistant-router.js +245 -38
  184. package/neural-interface/lib/assistant-runtime.js +214 -30
  185. package/neural-interface/lib/assistant-task-prompt.js +87 -21
  186. package/neural-interface/lib/assistant-tools.js +51 -0
  187. package/neural-interface/lib/assistant-usage.js +29 -0
  188. package/neural-interface/lib/backup-service.js +626 -4
  189. package/neural-interface/lib/backup-zip-reader.js +10 -5
  190. package/neural-interface/lib/backup-zip-writer.js +257 -0
  191. package/neural-interface/lib/browser-execution.js +10 -2
  192. package/neural-interface/lib/browser-guard-cdp.js +84 -0
  193. package/neural-interface/lib/browser-network-guard.js +476 -0
  194. package/neural-interface/lib/browser-policy-roots.js +64 -0
  195. package/neural-interface/lib/browser-tool-policy.js +198 -38
  196. package/neural-interface/lib/claude-agent-bridge.js +11 -2
  197. package/neural-interface/lib/claude-history-cache.js +210 -0
  198. package/neural-interface/lib/claude-hooks-status.js +103 -0
  199. package/neural-interface/lib/claude-model-catalog.js +5 -1
  200. package/neural-interface/lib/claude-temporary.js +5 -11
  201. package/neural-interface/lib/cli-install-api.js +1 -1
  202. package/neural-interface/lib/cli-install.js +163 -154
  203. package/neural-interface/lib/cli-resolver.js +13 -3
  204. package/neural-interface/lib/cli-update-planner.js +52 -41
  205. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  206. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  207. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  208. package/neural-interface/lib/codex-worker-app-server.js +191 -0
  209. package/neural-interface/lib/confined-fs.js +30 -3
  210. package/neural-interface/lib/crash-notice-api.js +98 -0
  211. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  212. package/neural-interface/lib/editor-git-api.js +3 -20
  213. package/neural-interface/lib/exec-loop-command.js +13 -10
  214. package/neural-interface/lib/file-explorer-api.js +74 -40
  215. package/neural-interface/lib/file-explorer-watch.js +9 -3
  216. package/neural-interface/lib/git-bash/install.js +52 -6
  217. package/neural-interface/lib/git-bash/manager.js +59 -17
  218. package/neural-interface/lib/git-bash/pins.js +12 -0
  219. package/neural-interface/lib/git-read.js +2 -2
  220. package/neural-interface/lib/git-status-runner.js +390 -0
  221. package/neural-interface/lib/git-status-watchdog.js +40 -0
  222. package/neural-interface/lib/greeting/budgets.js +1 -0
  223. package/neural-interface/lib/greeting/engine.js +2 -2
  224. package/neural-interface/lib/greeting/sources.js +10 -12
  225. package/neural-interface/lib/greeting/writer.js +1 -1
  226. package/neural-interface/lib/http-guards.js +8 -2
  227. package/neural-interface/lib/install-details.js +4 -1
  228. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  229. package/neural-interface/lib/mcp-client-config.js +35 -33
  230. package/neural-interface/lib/mcp-external-servers.js +332 -0
  231. package/neural-interface/lib/mcp-installer.js +2 -1
  232. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  233. package/neural-interface/lib/memory-session-capture.js +17 -3
  234. package/neural-interface/lib/morelogin.js +177 -25
  235. package/neural-interface/lib/native-loop-providers.js +965 -66
  236. package/neural-interface/lib/native-loop-runtime.js +290 -42
  237. package/neural-interface/lib/opencode-config.js +557 -0
  238. package/neural-interface/lib/opencode-db-schema.js +17 -34
  239. package/neural-interface/lib/opencode-history.js +48 -218
  240. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  241. package/neural-interface/lib/opencode-native-boundary.js +53 -0
  242. package/neural-interface/lib/opencode-protocol.js +249 -112
  243. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  244. package/neural-interface/lib/opencode-serve-records.js +39 -21
  245. package/neural-interface/lib/opencode-v2-client.js +48 -153
  246. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  247. package/neural-interface/lib/opencode-worker-data.js +27 -0
  248. package/neural-interface/lib/opencode2-client.js +73 -37
  249. package/neural-interface/lib/opencode2-proxy.js +23 -63
  250. package/neural-interface/lib/opencode2-translate.js +51 -2
  251. package/neural-interface/lib/owner-authority.js +249 -0
  252. package/neural-interface/lib/page-session.js +275 -0
  253. package/neural-interface/lib/path-confine.js +12 -9
  254. package/neural-interface/lib/peer-identity.js +793 -0
  255. package/neural-interface/lib/pty-host/core.js +28 -1
  256. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  257. package/neural-interface/lib/pty-host/manager.js +12 -1
  258. package/neural-interface/lib/remote-policy.js +1 -1
  259. package/neural-interface/lib/request-gate.js +20 -2
  260. package/neural-interface/lib/rulesets/api.js +7 -6
  261. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  262. package/neural-interface/lib/rulesets/installer.js +180 -36
  263. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  264. package/neural-interface/lib/rulesets/render.js +1 -1
  265. package/neural-interface/lib/session-cache-builder.js +81 -31
  266. package/neural-interface/lib/session-title-generator.js +58 -36
  267. package/neural-interface/lib/skills/api-v2.js +495 -0
  268. package/neural-interface/lib/skills/bundle.js +418 -0
  269. package/neural-interface/lib/skills/catalog.js +298 -0
  270. package/neural-interface/lib/skills/creation-api.js +191 -0
  271. package/neural-interface/lib/skills/creation-brief.js +247 -0
  272. package/neural-interface/lib/skills/creation-sources.js +285 -0
  273. package/neural-interface/lib/skills/creation.js +843 -0
  274. package/neural-interface/lib/skills/deploy.js +569 -0
  275. package/neural-interface/lib/skills/digest.js +119 -0
  276. package/neural-interface/lib/skills/discover.js +111 -0
  277. package/neural-interface/lib/skills/draft-confinement.js +216 -0
  278. package/neural-interface/lib/skills/eval-confinement.js +399 -0
  279. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  280. package/neural-interface/lib/skills/eval-trace.js +135 -0
  281. package/neural-interface/lib/skills/evals-api.js +212 -0
  282. package/neural-interface/lib/skills/evals.js +1573 -0
  283. package/neural-interface/lib/skills/export.js +203 -0
  284. package/neural-interface/lib/skills/frontmatter.js +64 -0
  285. package/neural-interface/lib/skills/health.js +75 -0
  286. package/neural-interface/lib/skills/json-edit.js +165 -0
  287. package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
  288. package/neural-interface/lib/skills/known-bridges.js +96 -0
  289. package/neural-interface/lib/skills/library.js +823 -0
  290. package/neural-interface/lib/skills/local-store.js +21 -0
  291. package/neural-interface/lib/skills/migration.js +433 -0
  292. package/neural-interface/lib/skills/operations.js +45 -0
  293. package/neural-interface/lib/skills/ownership.js +427 -0
  294. package/neural-interface/lib/skills/packages.js +88 -0
  295. package/neural-interface/lib/skills/reviews.js +212 -0
  296. package/neural-interface/lib/skills/revision.js +80 -0
  297. package/neural-interface/lib/skills/roots.js +269 -0
  298. package/neural-interface/lib/skills/runtime-checks.js +358 -0
  299. package/neural-interface/lib/skills/scan-assets.js +252 -0
  300. package/neural-interface/lib/skills/scan-context.js +26 -0
  301. package/neural-interface/lib/skills/scan-shell.js +324 -0
  302. package/neural-interface/lib/skills/scan.js +364 -0
  303. package/neural-interface/lib/skills/scanners.js +54 -0
  304. package/neural-interface/lib/skills/service.js +530 -0
  305. package/neural-interface/lib/skills/telemetry.js +119 -0
  306. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  307. package/neural-interface/lib/synabun-version.js +1 -1
  308. package/neural-interface/lib/system-activity.js +1 -1
  309. package/neural-interface/lib/tool-lists.js +124 -0
  310. package/neural-interface/lib/whatsapp/brain.js +7 -3
  311. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  312. package/neural-interface/lib/whatsapp/cards.js +22 -9
  313. package/neural-interface/lib/whatsapp/config.js +1 -1
  314. package/neural-interface/lib/worker-confinement.js +1268 -0
  315. package/neural-interface/lib/worker-guard-setting.js +56 -0
  316. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  317. package/neural-interface/lib/worker-question.js +74 -0
  318. package/neural-interface/lib/worker-relay-ports.js +227 -0
  319. package/neural-interface/lib/worker-resume.js +238 -0
  320. package/neural-interface/lib/worker-sandbox.js +329 -0
  321. package/neural-interface/package-lock.json +34 -12
  322. package/neural-interface/package.json +3 -2
  323. package/neural-interface/public/index.html +3 -1
  324. package/neural-interface/public/index2d.html +19 -11
  325. package/neural-interface/public/onboarding.html +3 -3
  326. package/neural-interface/public/shared/account-menu.js +8 -5
  327. package/neural-interface/public/shared/agent-runtime-options.js +125 -31
  328. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  329. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  330. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  331. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  332. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  333. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  334. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  335. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  336. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  337. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  338. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  339. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  340. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  341. package/neural-interface/public/shared/api.js +1527 -1336
  342. package/neural-interface/public/shared/app-frame.js +192 -0
  343. package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
  344. package/neural-interface/public/shared/assistant/asst-api.js +1 -1
  345. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  346. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  347. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  348. package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
  349. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  350. package/neural-interface/public/shared/assistant/asst-control.js +205 -26
  351. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  352. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  353. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  354. package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
  355. package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
  356. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  357. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  358. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  359. package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
  360. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  361. package/neural-interface/public/shared/assistant/asst-render.js +445 -82
  362. package/neural-interface/public/shared/assistant/asst-route.js +304 -53
  363. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  364. package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
  365. package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
  366. package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
  367. package/neural-interface/public/shared/assistant/asst-state.js +87 -24
  368. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  369. package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
  370. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
  371. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  372. package/neural-interface/public/shared/automation-context.js +5 -2
  373. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  374. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  375. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  376. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  377. package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
  378. package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
  379. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  380. package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
  381. package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
  382. package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
  383. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  384. package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
  385. package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
  386. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  387. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  388. package/neural-interface/public/shared/cli-install-view.js +11 -12
  389. package/neural-interface/public/shared/cli-status.js +11 -10
  390. package/neural-interface/public/shared/constants.js +5 -4
  391. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  392. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  393. package/neural-interface/public/shared/context-menu-model.js +367 -0
  394. package/neural-interface/public/shared/context-menu.js +372 -0
  395. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  396. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  397. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  398. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  399. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  400. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  401. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  402. package/neural-interface/public/shared/desktop-update-view.js +21 -0
  403. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  404. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  405. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  406. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  407. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  408. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  409. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  410. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  411. package/neural-interface/public/shared/html-shell.js +30 -15
  412. package/neural-interface/public/shared/i18n.js +2 -0
  413. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  414. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  415. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  416. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  417. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  418. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  419. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  420. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  421. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  422. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  423. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  424. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
  425. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  426. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  427. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  428. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
  429. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  430. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  431. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  432. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  433. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  434. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  435. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  436. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
  437. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  438. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
  439. package/neural-interface/public/shared/page-session.js +96 -0
  440. package/neural-interface/public/shared/provider-icons.js +4 -2
  441. package/neural-interface/public/shared/registry.js +5 -3
  442. package/neural-interface/public/shared/retired-2d.js +3 -0
  443. package/neural-interface/public/shared/schedules-view.js +195 -61
  444. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  445. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  446. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  447. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  448. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  449. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  450. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  451. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  452. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  453. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  454. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  455. package/neural-interface/public/shared/skills/ss-creation.js +222 -0
  456. package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
  457. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  458. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  459. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  460. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  461. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  462. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  463. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  464. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  465. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  466. package/neural-interface/public/shared/skills/ss-lint.js +149 -0
  467. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  468. package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
  469. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  470. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  471. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  472. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  473. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  474. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  475. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  476. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
  477. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  478. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  479. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  480. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  481. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  482. package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
  483. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  484. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  485. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  486. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  487. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  488. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  489. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  490. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  491. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
  492. package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
  493. package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
  494. package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
  495. package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
  496. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  497. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  498. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  499. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  500. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  501. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  502. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  503. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  504. package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
  505. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  506. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  507. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  508. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
  509. package/neural-interface/public/shared/state.js +1 -1
  510. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  511. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  512. package/neural-interface/public/shared/styles.css +1822 -1756
  513. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  514. package/neural-interface/public/shared/text-cache.js +29 -0
  515. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  516. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  517. package/neural-interface/public/shared/tool-list-view.js +112 -0
  518. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  519. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  520. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  521. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  522. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  523. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  524. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  525. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  526. package/neural-interface/public/shared/tour/tour.css +144 -0
  527. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  528. package/neural-interface/public/shared/ui-assistant.js +2 -0
  529. package/neural-interface/public/shared/ui-automation-studio.js +111 -140
  530. package/neural-interface/public/shared/ui-claude-panel.js +376 -82
  531. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  532. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  533. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  534. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  535. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  536. package/neural-interface/public/shared/ui-detail.js +15 -3
  537. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  538. package/neural-interface/public/shared/ui-file-links.js +2 -2
  539. package/neural-interface/public/shared/ui-games.js +2 -2
  540. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  541. package/neural-interface/public/shared/ui-judgments.js +1 -1
  542. package/neural-interface/public/shared/ui-keybinds.js +33 -2
  543. package/neural-interface/public/shared/ui-link.js +3 -3
  544. package/neural-interface/public/shared/ui-menubar.js +10 -20
  545. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  546. package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
  547. package/neural-interface/public/shared/ui-navbar.js +49 -24
  548. package/neural-interface/public/shared/ui-notifications.js +1 -0
  549. package/neural-interface/public/shared/ui-panels.js +96 -65
  550. package/neural-interface/public/shared/ui-resume.js +9 -7
  551. package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
  552. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  553. package/neural-interface/public/shared/ui-sessions.js +27 -15
  554. package/neural-interface/public/shared/ui-settings.js +683 -526
  555. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  556. package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
  557. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  558. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  559. package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
  560. package/neural-interface/public/shared/ui-skills.js +33 -2830
  561. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  562. package/neural-interface/public/shared/ui-sync.js +15 -5
  563. package/neural-interface/public/shared/ui-terminal.js +270 -190
  564. package/neural-interface/public/shared/ui-tool-updates.js +6 -4
  565. package/neural-interface/public/shared/ui-tooltip.js +81 -51
  566. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  567. package/neural-interface/public/shared/ui-trash.js +6 -4
  568. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  569. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  570. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  571. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  572. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  573. package/neural-interface/public/shared/ui-update.js +38 -2
  574. package/neural-interface/public/shared/ui-whiteboard.js +312 -59
  575. package/neural-interface/public/shared/ui-worker-end.js +5 -1
  576. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  577. package/neural-interface/public/shared/view-variant.js +66 -0
  578. package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
  579. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  580. package/neural-interface/public/shared/worker-end.js +10 -3
  581. package/neural-interface/public/shared/worker-questions.js +34 -0
  582. package/neural-interface/public/shared/worker-restart.js +12 -0
  583. package/neural-interface/public/sidepanel-session.html +2 -2
  584. package/neural-interface/public/variant/2d/background.js +2 -1
  585. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  586. package/neural-interface/public/variant/2d/graph.js +42 -20
  587. package/neural-interface/public/variant/2d/hulls.js +14 -9
  588. package/neural-interface/public/variant/2d/lasso.js +11 -7
  589. package/neural-interface/public/variant/2d/main.js +32 -1
  590. package/neural-interface/public/variant/2d/minimap.js +11 -13
  591. package/neural-interface/public/variant/3d/main.js +69 -6
  592. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  593. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  594. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  595. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  596. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  597. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  598. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  599. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  600. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  601. package/neural-interface/scripts/build-vendor.mjs +32 -0
  602. package/neural-interface/server.js +4553 -2855
  603. package/neural-interface/templates/rulesets/README.md +1 -1
  604. package/neural-interface/templates/rulesets/core.md +1 -1
  605. package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
  606. package/neural-interface/templates/rulesets/manifest.json +8 -8
  607. package/npm-shrinkwrap.json +34 -12
  608. package/package.json +17 -3
  609. package/postinstall.js +9 -4
  610. package/preuninstall.js +1 -1
  611. package/setup.js +97 -322
  612. package/skills/skill-forge/SKILL.md +177 -0
  613. package/skills/skill-forge/evals/evals.json +54 -0
  614. package/skills/skill-forge/references/package.md +105 -0
  615. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  616. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  617. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  618. package/skills/synabun/SKILL.md +10 -7
  619. package/skills/synabun/modules/audit.md +1 -5
  620. package/skills/synabun/modules/changelog.md +1 -5
  621. package/skills/synabun/modules/idea.md +1 -1
  622. package/skills/synabun/modules/memorize.md +6 -9
  623. package/skills/synabun/modules/schedule.md +6 -6
  624. package/mcp-server/dist/services/profiles.d.ts +0 -120
  625. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  626. package/mcp-server/dist/services/profiles.js +0 -387
  627. package/mcp-server/dist/services/profiles.js.map +0 -1
  628. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  629. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  630. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  631. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  632. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  633. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  634. package/mcp-server/dist/tools/leonardo.js +0 -21
  635. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  636. package/mcp-server/dist/tools/profile.d.ts +0 -17
  637. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  638. package/mcp-server/dist/tools/profile.js +0 -114
  639. package/mcp-server/dist/tools/profile.js.map +0 -1
  640. package/mcp-server/src/services/profiles.ts +0 -428
  641. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  642. package/mcp-server/src/tools/leonardo.ts +0 -30
  643. package/mcp-server/src/tools/profile.ts +0 -122
  644. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  645. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  646. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  647. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  648. package/skills/leonardo/SKILL.md +0 -168
  649. package/skills/leonardo/icon.svg +0 -1
  650. package/skills/leonardo/modules/browser-guide.md +0 -378
  651. package/skills/leonardo/modules/image-prompter.md +0 -283
  652. package/skills/leonardo/modules/model-advisor.md +0 -100
  653. package/skills/leonardo/modules/prompt-library.md +0 -110
  654. package/skills/leonardo/modules/style-guide.md +0 -186
  655. package/skills/leonardo/modules/video-prompter.md +0 -317
  656. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -0,0 +1,748 @@
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
+ import { issuesOf } from './sx-fixes.js';
22
+ import { openFixPanel, fixAllSafe, fixNoticeHTML, unreadableFingerprint } from './sx-fixes-view.js';
23
+
24
+ let _life = null; // the listeners of the drawn header (filters): they end with the next drawing
25
+ let _lastChecked = null;
26
+
27
+ const RT_LETTER = { claude: 'C', codex: 'X', opencode: 'O', antigravity: 'A' };
28
+ const DIAG_REASONS = ['no-skill-md', 'unreadable-head', 'link', 'too-deep', 'unreadable', 'outside-project', 'not-a-folder'];
29
+
30
+ /** Where a row comes from, in words: a label and a detail. */
31
+ export function sourceLabel(row, t) {
32
+ const detail = (() => {
33
+ switch (row.source) {
34
+ case 'plugin': return row.copies > 1 ? plural(t, 'skillsStudio.source.pluginCopies', row.copies, { plugin: row.pluginId || '' }) : (row.pluginId || '');
35
+ case 'project': return row.projectLabel || '';
36
+ case 'mine': return row.accounts.filter((a) => a !== 'default').length ? plural(t, 'skillsStudio.source.accounts', row.accounts.length) : '';
37
+ case 'library': return row.skill?.revisions ? plural(t, 'skillsStudio.source.libraryVersions', row.skill.revisions) : t('skillsStudio.source.libraryDraft');
38
+ case 'synabun': return row.installedCopies?.length ? plural(t, 'skillsStudio.source.installedCopies', row.installedCopies.length) : '';
39
+ default: return '';
40
+ }
41
+ })();
42
+ const label = row.source === 'mine' && row.scope === 'user' ? t('skillsStudio.source.mine')
43
+ : row.orphanBridge ? t('skillsStudio.source.legacyBridge')
44
+ : t(`skillsStudio.source.${row.source}`);
45
+ return { label, detail };
46
+ }
47
+
48
+ /** The words of a health item. */
49
+ export function healthWords(h, t) {
50
+ switch (h.code) {
51
+ case 'invalid': return plural(t, 'skillsStudio.health.invalid', h.count);
52
+ case 'warnings': return plural(t, 'skillsStudio.health.warnings', h.count);
53
+ case 'twice': return t('skillsStudio.health.twice');
54
+ case 'shadowed': return t('skillsStudio.health.shadowed');
55
+ case 'same-description': return t('skillsStudio.health.sameDescription');
56
+ default: return t(`skillsStudio.health.${h.code}`);
57
+ }
58
+ }
59
+
60
+ /** A health capsule: the worst item's word and icon, with the rest named in its title. */
61
+ /** Why a health item is there, for the capsule's title: which runtime does what, or which skills look the same. */
62
+ export function healthWhy(h, t) {
63
+ const runtime = h.runtime ? t(`skillsStudio.runtime.${h.runtime}`) : '';
64
+ if (h.code === 'shadowed' && runtime) return t('skillsStudio.health.shadowedWhy', { runtime });
65
+ if (h.code === 'twice' && runtime) return t('skillsStudio.health.twiceWhy', { runtime });
66
+ if (h.code === 'same-description' && h.names?.length) return t('skillsStudio.health.sameDescriptionWhy', { names: h.names.map((n) => revealHidden(String(n))).join(', ') });
67
+ return '';
68
+ }
69
+
70
+ /**
71
+ * `fix`: the capsule opens the row's fix panel (sx-fixes-view.js) when it holds a problem: in the Library a
72
+ * `data-fix` button, in a workspace a `data-ws="fix-issues"` one. A capsule with nothing to fix stays a label.
73
+ */
74
+ export function healthCapsule(row, t, { fix = null } = {}) {
75
+ const worst = worstHealth(row);
76
+ const rest = row.health.filter((h) => h !== worst && h.sev !== 'ok');
77
+ const all = row.health.map((h) => { const why = healthWhy(h, t); return why ? `${healthWords(h, t)}: ${why}` : healthWords(h, t); }).join(' · ');
78
+ const inner = html`${healthIcon(worst.code)}<span>${healthWords(worst, t)}</span>${rest.length ? html`<span class="sx-health-more">+${rest.length}</span>` : ''}`;
79
+ if (fix && issuesOf(row, { open: true }).length) {
80
+ const label = t('skillsStudio.fix.open', { problems: all });
81
+ return fix === 'library'
82
+ ? html`<button type="button" class="sx-health sx-health--${worst.sev} sx-health--fix" data-fix="${row.key}" tabindex="-1" title="${label}" aria-label="${label}">${inner}</button>`
83
+ : html`<button type="button" class="sx-health sx-health--${worst.sev} sx-health--fix" data-ws="fix-issues" title="${label}" aria-label="${label}">${inner}</button>`;
84
+ }
85
+ return html`<span class="sx-health sx-health--${worst.sev}" title="${all}">${inner}</span>`;
86
+ }
87
+
88
+ /**
89
+ * The one status of a library skill (sx-install.js installStatus) as a pill: draft, in review, changes asked for,
90
+ * approved · not installed, installed (where, in its title), installed with an update, not installed. A button
91
+ * when it opens something (`button`: the data-ws action).
92
+ */
93
+ export function statusPill(status, ctx, { button = null, id = null } = {}) {
94
+ const { t } = ctx;
95
+ const where = status.where.map((w) => {
96
+ const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`);
97
+ if (w.scope === 'project') return t('skillsStudio.publish.targetProject', { runtime, project: ctx.projects?.find((p) => p.path === w.projectPath)?.label || w.projectPath || '' });
98
+ if (w.accountId && w.accountId !== 'default') return t('skillsStudio.publish.targetAccount', { runtime, account: w.accountId });
99
+ return t('skillsStudio.publish.targetUser', { runtime });
100
+ });
101
+ const runtimes = [...new Set(status.where.map((w) => t(`skillsStudio.runtime.${['claude', 'codex', 'opencode', 'antigravity', 'agents'].includes(w.runtime) ? w.runtime : 'claude'}`)))].join(', ');
102
+ 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}`);
103
+ const title = where.length ? where.join(', ') : t(`skillsStudio.status.help.${status.code}`);
104
+ // Not installed is no tick: the dashed circle (healthIcon has no 'off' and would fall through to the tick).
105
+ 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;
106
+ const inner = html`${icon}<span>${text}</span>`;
107
+ return button
108
+ ? html`<button type="button" class="sx-status sx-status--${status.code}" data-ws="${button}"${id ? html` id="${id}"` : ''} title="${title}">${inner}</button>`
109
+ : html`<span class="sx-status sx-status--${status.code}"${id ? html` id="${id}"` : ''} title="${title}">${inner}</span>`;
110
+ }
111
+
112
+ /**
113
+ * The status and health pills of a row, once each, in the Library cell and the workspace head alike: a library skill
114
+ * shows its status, and its health only when something is wrong (an info item such as "draft" or "in review" is
115
+ * what the status already says); anything else shows its health.
116
+ */
117
+ export function statusPills(row, ctx, { status = installStatus({ skill: row.skill, row }), button = null, id = null, fix = null } = {}) {
118
+ const how = ctx.guest ? null : fix;
119
+ if (!status) return healthCapsule(row, ctx.t, { fix: how });
120
+ const sev = worstHealth(row).sev;
121
+ // An installation older than the latest version is said too: the status reads "installed" for it.
122
+ const stale = row.health.some((h) => h.code === 'stale-install');
123
+ return html`${statusPill(status, ctx, { button, id })}${sev === 'err' || sev === 'warn' || stale ? healthCapsule(row, ctx.t, { fix: how }) : ''}`;
124
+ }
125
+
126
+ /** The runtime capsules of a row (C X O A), each named in full. */
127
+ export function runtimeCapsules(row, t) {
128
+ return html`<span class="sx-rts">${RUNTIMES.map((rt) => {
129
+ const st = row.runtimes[rt]?.state || 'none';
130
+ const label = `${t(`skillsStudio.runtime.${rt}`)}: ${t(`skillsStudio.runtimeState.${st}`)}`;
131
+ return html`<span class="sx-rt sx-rt--${st}" role="img" aria-label="${label}" title="${label}">${RT_LETTER[rt]}</span>`;
132
+ })}</span>`;
133
+ }
134
+
135
+ const kindLabel = (row, t) => (row.kind === 'command' ? t('skillsStudio.kind.commandLegacy') : t(`skillsStudio.kind.${row.kind}`));
136
+
137
+ function filterHeadHTML(ctx) {
138
+ const { t, lib, counts } = ctx;
139
+ const c = counts || { kinds: {}, sources: {}, runtimes: {}, health: {}, total: 0 };
140
+ const entries = (key, values, countOf, words) => [
141
+ { value: 'all', label: t(`skillsStudio.library.filter.${key}All`), selected: lib[key] === 'all' || (key === 'health' && lib.health === 'any') },
142
+ ...values.map((v) => ({ value: v, label: `${words(v)} · ${countOf[v] ?? 0}`, selected: lib[key] === v })),
143
+ ];
144
+ const kind = entries('kind', KINDS, c.kinds, (v) => (v === 'command' ? t('skillsStudio.kind.commandLegacy') : t(`skillsStudio.kind.${v}`)));
145
+ const source = entries('source', SOURCES, c.sources, (v) => t(`skillsStudio.source.${v}`));
146
+ const runtime = entries('runtime', RUNTIMES, c.runtimes, (v) => t(`skillsStudio.runtime.${v}`));
147
+ 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 }))];
148
+ const sort = SORTS.map((v) => ({ value: v, label: t(`skillsStudio.library.sort.${v}`), selected: lib.sort === v }));
149
+ 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>`;
150
+ return html`
151
+ <div class="sx-place-head">
152
+ <h3 class="sx-place-title" id="sx-lib-title" tabindex="-1">${t('skillsStudio.library.title')}</h3>
153
+ <div class="sx-search">
154
+ ${ICONS.search}
155
+ <label class="sx-sr" for="sx-lib-search">${t('skillsStudio.library.searchLabel')}</label>
156
+ <input id="sx-lib-search" type="search" autocomplete="off" spellcheck="false" placeholder="${t('skillsStudio.library.searchPlaceholder')}" value="${lib.q}" data-autofocus>
157
+ </div>
158
+ ${dd('sx-f-kind', t('skillsStudio.library.filter.kind'), kind)}
159
+ ${dd('sx-f-source', t('skillsStudio.library.filter.source'), source)}
160
+ ${dd('sx-f-runtime', t('skillsStudio.library.filter.runtime'), runtime)}
161
+ ${dd('sx-f-health', t('skillsStudio.library.filter.health'), health)}
162
+ <span class="sx-spacer"></span>
163
+ ${dd('sx-f-sort', t('skillsStudio.library.sort.label'), sort)}
164
+ ${ctx.guest ? '' : html`<button type="button" class="sx-btn sx-btn--primary" data-lib="new">${ICONS.plus}<span>${t('skillsStudio.library.new')}</span></button>`}
165
+ </div>`;
166
+ }
167
+
168
+ /** Whether the server reads when each skill changed (catalog `updatedAt`): a server older than the page does not. */
169
+ const knowsUpdated = (ctx) => !ctx.capsKnown || ctx.caps.has('catalog-updated');
170
+
171
+ function rowHTML(row, ctx, selected, first) {
172
+ const { t, locale } = ctx;
173
+ const src = sourceLabel(row, t);
174
+ const name = row.label;
175
+ const description = row.shownDescription;
176
+ const updated = row.updatedAt ? relativeTime(row.updatedAt, locale) : '';
177
+ const unknown = knowsUpdated(ctx) ? t('skillsStudio.library.unknownDate') : t('skillsStudio.library.unknownDateRestart');
178
+ return html`<tr class="sx-lib-row${selected ? ' is-selected' : ''}" data-key="${row.key}">
179
+ <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>
180
+ <td class="sx-col-name">
181
+ <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>
182
+ <span class="sx-desc" id="sx-desc-${row.key.replace(/[^A-Za-z0-9_-]/g, '_')}" title="${description}">${description || t('skillsStudio.library.noDescription')}</span>
183
+ </td>
184
+ <td class="sx-col-kind">${kindLabel(row, t)}</td>
185
+ <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>
186
+ <td class="sx-col-health">${healthCellHTML(row, ctx)}</td>
187
+ <td class="sx-col-rt">${runtimeCapsules(row, t)}</td>
188
+ <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>
189
+ </tr>`;
190
+ }
191
+
192
+ /**
193
+ * The health cell: a library skill shows its one status (the same as its workspace header) and its health only when
194
+ * something is wrong, with Install when it is approved and not installed yet; the rest shows its health.
195
+ */
196
+ function healthCellHTML(row, ctx) {
197
+ const { t } = ctx;
198
+ const status = installStatus({ skill: row.skill, row });
199
+ if (!status) return healthCapsule(row, t, { fix: ctx.guest ? null : 'library' });
200
+ // Installed for one assistant and not the others in effect: one press from the row adds them.
201
+ const also = ['installed', 'outdated'].includes(status.code) && !ctx.guest ? alsoInstall(ctx, { skill: row.skill, row }) : { targets: [], words: '' };
202
+ return html`<span class="sx-health-cell">${statusPills(row, ctx, { status, fix: 'library' })}${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>`;
203
+ }
204
+
205
+ /**
206
+ * The start-up move of the old Codex prompt / OpenCode command bridges (lib/skills/migration.js autoMigrate), said
207
+ * once: what moved, with Undo (the bridges come back, the folders it made go) and OK; and the bridge files of shipped
208
+ * skills that SynaBun did not move by itself because they are not proven its own, each with "Move it too" (the owner
209
+ * confirms exactly those bytes). Drawn from GET /v2/migrations/notice (read with the Library); nothing on an older server.
210
+ */
211
+ export function migrationNoticeHTML(ctx) {
212
+ const n = ctx.data?.notice;
213
+ if (!n || ctx.guest) return '';
214
+ const { t, tp } = ctx;
215
+ const out = [];
216
+ if (n.pending) {
217
+ const label = (m) => `${t(`skillsStudio.runtime.${['claude', 'codex', 'opencode'].includes(m.runtime) ? m.runtime : 'claude'}`)} /${m.name}`;
218
+ const moved = (n.pending.migrated || []).map(label);
219
+ // Moved by itself because its bytes were exactly an earlier SynaBun version's bridge (proof generated-previous).
220
+ const earlier = (n.pending.migrated || []).filter((m) => m.proof === 'generated-previous').map(label);
221
+ const failed = (n.pending.notMoved || []).map((m) => `/${m.name}`);
222
+ out.push(html`<div class="sx-notice sx-notice--info" role="status" data-notice-box="auto">
223
+ ${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>
224
+ <button type="button" class="sx-btn" data-notice="undo" data-journal="${n.pending.journalId}">${t('skillsStudio.notice.undo')}</button>
225
+ <button type="button" class="sx-btn sx-btn--ghost" data-notice="dismiss">${t('skillsStudio.notice.ok')}</button>
226
+ </div>`);
227
+ }
228
+ for (const o of n.offers || []) {
229
+ const runtime = t(`skillsStudio.runtime.${['claude', 'codex', 'opencode'].includes(o.runtime) ? o.runtime : 'claude'}`);
230
+ out.push(html`<div class="sx-notice sx-notice--warn" role="note" data-notice-box="offer" data-bridge="${o.bridgeId}">
231
+ ${ICONS.warn}<span class="sx-notice-text">${t('skillsStudio.notice.offer', { runtime, name: `/${o.name}`, path: shortPath(o.path) })}</span>
232
+ <button type="button" class="sx-btn" data-notice="move" data-bridge="${o.bridgeId}" data-digest="${o.digest}">${t('skillsStudio.notice.moveToo')}</button>
233
+ </div>`);
234
+ }
235
+ return html`${out}`;
236
+ }
237
+
238
+ /**
239
+ * What the row's "Also install for …" did, kept above the list until OK: where it went and, per runtime, what a
240
+ * session needs to see it (SynaBun's own panels list it at once; a terminal session needs a new one). A toast alone
241
+ * would be gone before those lines are read.
242
+ */
243
+ function installNoticeHTML(ctx) {
244
+ const n = ctx.lib.installNotice;
245
+ if (!n || ctx.guest) return '';
246
+ return html`<div class="sx-notice sx-notice--${n.kind === 'info' ? 'info' : 'warn'}" role="status" data-notice-box="install">
247
+ ${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>
248
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="install-notice-ok">${ctx.t('skillsStudio.notice.ok')}</button>
249
+ </div>`;
250
+ }
251
+
252
+ /** The home folder as ~ (the full path stays in the element's title). */
253
+ function shortPath(path) { return String(path || '').replace(/^\/Users\/[^/]+(?=\/)/, '~').replace(/^\/home\/[^/]+(?=\/)/, '~'); }
254
+
255
+ /** 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). */
256
+ async function onNoticeAction(btn, ctx) {
257
+ const { t } = ctx;
258
+ const act = btn.dataset.notice;
259
+ const box = btn.closest('[data-notice-box]');
260
+ for (const b of box?.querySelectorAll('button') || []) b.disabled = true;
261
+ const gen = ctx.gen();
262
+ try {
263
+ if (act === 'dismiss') { await ctx.api.dismissSkillsMigrationNotice(); }
264
+ else if (act === 'undo') {
265
+ const out = await ctx.api.revertSkillsMigration(btn.dataset.journal);
266
+ if (ctx.alive(gen)) ctx.toast(out.status === 'reverted' ? t('skillsStudio.notice.undone') : t('skillsStudio.notice.undonePartly'), out.status === 'reverted' ? 'info' : 'error');
267
+ } else if (act === 'move') {
268
+ const confirm = { [btn.dataset.bridge]: btn.dataset.digest };
269
+ const planned = await ctx.api.planSkillsBridgeMigrationConfirmed(confirm);
270
+ const out = await ctx.api.runSkillsBridgeMigration(planned.plan.planDigest, confirm);
271
+ if (ctx.alive(gen)) ctx.toast(out.status === 'done' ? t('skillsStudio.notice.moved') : t('skillsStudio.notice.movedPartly'), out.status === 'done' ? 'info' : 'error');
272
+ }
273
+ } catch (err) {
274
+ if (ctx.alive(gen)) ctx.toast(t('skillsStudio.notice.failed', { error: studioErrorText(ctx.t, err) }), 'error');
275
+ }
276
+ if (ctx.alive(gen)) ctx.reload();
277
+ }
278
+
279
+ function keptNoticeHTML(ctx) {
280
+ const kept = Object.entries(ctx.keptDrafts()).filter(([, d]) => !ctx.workspaces.has(d.wsKey));
281
+ if (!kept.length) return '';
282
+ const known = kept.filter(([, d]) => ctx.rows.some((r) => r.key === d.wsKey));
283
+ // Named whether or not their skill is listed now (one deleted, or from another data home): only those listed reopen.
284
+ const names = [...new Set([...known, ...kept.filter((k) => !known.includes(k))].map(([, d]) => String(d.name || d.label || d.path || '?')))].slice(0, 4);
285
+ return html`<div class="sx-notice sx-notice--info" role="status">
286
+ ${ICONS.draft}<span class="sx-notice-text">${ctx.tp('skillsStudio.library.kept', kept.length, { names: names.join(', ') })}</span>
287
+ ${known.length ? html`<button type="button" class="sx-btn" data-lib="kept-reopen">${ctx.t('skillsStudio.library.keptReopen')}</button>` : ''}
288
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="kept-discard">${ctx.t('skillsStudio.library.keptDiscard')}</button>
289
+ </div>`;
290
+ }
291
+
292
+ function summaryHTML(ctx, shown) {
293
+ const { t, tp, counts, data } = ctx;
294
+ const total = counts?.total ?? 0;
295
+ const parts = [t('skillsStudio.library.shown', { shown: shown.length, total })];
296
+ if (counts?.views?.attention) parts.push(tp('skillsStudio.library.attentionCount', counts.views.attention));
297
+ const diag = data?.catalog?.diagnostics || [];
298
+ // Folders the owner said to leave as they are are not said again until their number changes.
299
+ const diagSaid = diag.length && !ctx.dismissed?.().has(unreadableFingerprint(diag.length));
300
+ // A server older than the page reads no change times: the dashes are said once, with what brings them.
301
+ const restart = knowsUpdated(ctx) ? '' : html` · <span class="sx-note-warn" id="sx-lib-updated-restart">${t('skillsStudio.library.updatedRestart')}</span>`;
302
+ return html`<p class="sx-summary" role="status">${parts.join(' · ')}${diagSaid ? 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>`;
303
+ }
304
+
305
+ /**
306
+ * What a bulk Delete does with a row: `disk` removes it from where it is (an agent, a command, a folder of the
307
+ * owner's not in the library), `library` takes a library skill out of the library (a folder it came from stays; one
308
+ * installed somewhere is left out: its installations are removed in Publish first), null leaves it.
309
+ */
310
+ export function deleteKind(ctx, row, s) {
311
+ if (ctx.guest) return null;
312
+ if ((s.mode === 'inplace' || s.mode === 'adopt') && s.v1Id) return 'disk';
313
+ if (s.mode === 'library' && row.skill?.skillId && row.skill.origin?.kind !== 'bundled' && !(row.skill.installations || []).length && ctx.caps.has('library-delete')) return 'library';
314
+ return null;
315
+ }
316
+
317
+ function bulkHTML(ctx, rows) {
318
+ const { t, tp, lib } = ctx;
319
+ if (!lib.selected.size || ctx.guest) return '';
320
+ const chosen = rows.filter((r) => lib.selected.has(r.key));
321
+ const subjects = chosen.map((r) => ({ row: r, s: subjectOf(r, { guest: ctx.guest, caps: ctx.caps }) }));
322
+ const adoptable = subjects.filter((x) => x.s.mode === 'adopt').length;
323
+ const exportable = subjects.filter((x) => x.s.v1Id || (x.row.skill?.latest && ctx.caps.has('export'))).length;
324
+ const deletable = subjects.filter((x) => deleteKind(ctx, x.row, x.s)).length;
325
+ return html`<div class="sx-bulk" role="region" aria-label="${t('skillsStudio.library.bulk.region')}">
326
+ <span class="sx-bulk-count">${tp('skillsStudio.library.bulk.selected', lib.selected.size)}</span>
327
+ <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>
328
+ <button type="button" class="sx-btn" data-lib="bulk-export"${exportable ? '' : ' disabled'}>${ICONS.download}<span>${t('skillsStudio.library.bulk.export', { count: exportable })}</span></button>
329
+ <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>
330
+ <button type="button" class="sx-btn sx-btn--ghost" data-lib="bulk-clear">${t('skillsStudio.library.bulk.clear')}</button>
331
+ </div>`;
332
+ }
333
+
334
+ /** The Archived view: the skills deleted from the library, each with Restore (its versions never left). */
335
+ function archivedHTML(ctx) {
336
+ const { t, locale } = ctx;
337
+ const list = Array.isArray(ctx.data?.archived) ? ctx.data.archived : [];
338
+ 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>`;
339
+ return html`<section class="sx-archived" aria-labelledby="sx-archived-title">
340
+ <h4 class="sx-section-title" id="sx-archived-title">${t('skillsStudio.library.archivedTitle')}</h4>
341
+ <p class="sx-help">${t('skillsStudio.library.archivedHelp')}</p>
342
+ <ul class="sx-installs sx-archived-list">${list.map((a) => html`<li class="sx-install-row" data-archived="${a.skillId}">
343
+ <span class="sx-install-where"><strong>${revealHidden(String(a.name || a.skillId))}</strong></span>
344
+ <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>
345
+ ${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>`}
346
+ </li>`)}</ul>
347
+ </section>`;
348
+ }
349
+
350
+ /** The row's "Also install for …": one press, the installed version to the assistants that lack it. */
351
+ async function onAlsoInstall(btn, host, ctx) {
352
+ const row = ctx.rows.find((r) => r.key === btn.dataset.alsoInstall);
353
+ if (!row?.skill) return;
354
+ btn.disabled = true;
355
+ const gen = ctx.gen();
356
+ const words = await alsoInstallNow(ctx, { skill: row.skill, row });
357
+ if (!ctx.alive(gen)) return;
358
+ if (words) {
359
+ ctx.lib.installNotice = { kind: words.kind, name: row.name || row.skill.name || '', line: words.line, lines: Array.isArray(words.lines) ? words.lines : [] };
360
+ ctx.toast(words.line, words.kind);
361
+ ctx.announce([words.line, ...ctx.lib.installNotice.lines].join(' '));
362
+ }
363
+ ctx.reload();
364
+ }
365
+
366
+ /** Restore from the Archived view: the record and its draft are back, the versions never left. */
367
+ async function onRestore(btn, host, ctx) {
368
+ const { t } = ctx;
369
+ btn.disabled = true;
370
+ const gen = ctx.gen();
371
+ try {
372
+ const out = await ctx.api.restoreSkillsSkill(btn.dataset.restore);
373
+ if (!ctx.alive(gen)) return;
374
+ ctx.toast(t('skillsStudio.library.restored', { name: out.name || '' }));
375
+ ctx.announce(t('skillsStudio.library.restored', { name: out.name || '' }));
376
+ } catch (err) {
377
+ if (!ctx.alive(gen)) return;
378
+ ctx.toast(t('skillsStudio.library.restoreFailed', { error: studioErrorText(ctx.t, err) }), 'error');
379
+ }
380
+ ctx.reload();
381
+ }
382
+
383
+ function tableHTML(ctx, shown) {
384
+ const { t, lib } = ctx;
385
+ if (lib.view === 'archived') return archivedHTML(ctx);
386
+ if (ctx.loading) return html`<div class="sx-empty" role="status"><p>${t('skillsStudio.library.loading')}</p></div>`;
387
+ if (ctx.loadError && !ctx.rows.length) {
388
+ 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>`;
389
+ }
390
+ if (!ctx.rows.length) {
391
+ 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>`;
392
+ }
393
+ if (!shown.length) {
394
+ 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>`;
395
+ }
396
+ const allChecked = shown.every((r) => lib.selected.has(r.key));
397
+ return html`<table class="sx-lib">
398
+ <caption class="sx-sr">${t('skillsStudio.library.caption')}</caption>
399
+ <thead><tr>
400
+ <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>
401
+ <th class="sx-col-name" scope="col">${t('skillsStudio.library.col.name')}</th>
402
+ <th class="sx-col-kind" scope="col">${t('skillsStudio.library.col.kind')}</th>
403
+ <th class="sx-col-source" scope="col">${t('skillsStudio.library.col.source')}</th>
404
+ <th class="sx-col-health" scope="col">${t('skillsStudio.library.col.health')}</th>
405
+ <th class="sx-col-rt" scope="col">${t('skillsStudio.library.col.runtimes')}</th>
406
+ <th class="sx-col-updated" scope="col">${t('skillsStudio.library.col.updated')}</th>
407
+ </tr></thead>
408
+ <tbody>${shown.map((r, i) => rowHTML(r, ctx, lib.selected.has(r.key), lib.focus ? r.key === lib.focus : i === 0))}</tbody>
409
+ </table>`;
410
+ }
411
+
412
+ const visible = (ctx) => sortRows(filterRows(ctx.rows, ctx.lib), ctx.lib.sort);
413
+
414
+ /** Draw the Library place. */
415
+ /** The Library is left for another place: its listeners on the shared main area end. */
416
+ export function leaveLibraryPlace() { _life?.abort(); _life = null; }
417
+
418
+ export function renderLibraryPlace(host, ctx) {
419
+ _life?.abort();
420
+ _life = new AbortController();
421
+ const signal = _life.signal;
422
+ const shown = visible(ctx);
423
+ setHTML(host, html`<section class="sx-place sx-place--library" aria-labelledby="sx-lib-title">
424
+ ${filterHeadHTML(ctx)}
425
+ <div class="sx-place-notices">${fixNoticeHTML(ctx, deleteKind)}${installNoticeHTML(ctx)}${migrationNoticeHTML(ctx)}${keptNoticeHTML(ctx)}${updateNoticeHTML(ctx)}</div>
426
+ <div class="sx-place-scroll" id="sx-lib-scroll">
427
+ <div id="sx-lib-summary">${summaryHTML(ctx, shown)}</div>
428
+ <div id="sx-lib-table">${tableHTML(ctx, shown)}</div>
429
+ </div>
430
+ <div id="sx-lib-bulk">${bulkHTML(ctx, shown)}</div>
431
+ </section>`);
432
+ wireDropdowns(host, { signal });
433
+ 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']]) {
434
+ host.querySelector(`#${id}`)?.addEventListener('change', (e) => {
435
+ ctx.lib[key] = e.target.value;
436
+ if (key !== 'sort') ctx.lib.view = 'all';
437
+ ctx.saveLibraryView();
438
+ redrawRows(host, ctx);
439
+ }, { signal });
440
+ }
441
+ const search = host.querySelector('#sx-lib-search');
442
+ 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 });
443
+ host.addEventListener('click', (e) => onClick(e, host, ctx), { signal });
444
+ host.addEventListener('change', (e) => onCheck(e, host, ctx), { signal });
445
+ host.addEventListener('keydown', (e) => onRowKey(e, host, ctx), { signal });
446
+ const table = host.querySelector('#sx-lib-table');
447
+ if (table) rovingFocus(table, '.sx-name-btn', { signal, onMove: (el) => { ctx.lib.focus = el.dataset.open; el.closest('tr')?.scrollIntoView({ block: 'nearest' }); } });
448
+ }
449
+
450
+ /** The table, its summary and the bulk bar again, after a filter or the selection changed (the header stays). */
451
+ function redrawRows(host, ctx) {
452
+ const shown = visible(ctx);
453
+ setHTML(host.querySelector('#sx-lib-summary'), summaryHTML(ctx, shown));
454
+ setHTML(host.querySelector('#sx-lib-table'), tableHTML(ctx, shown));
455
+ setHTML(host.querySelector('#sx-lib-bulk'), bulkHTML(ctx, shown));
456
+ const table = host.querySelector('#sx-lib-table');
457
+ if (table) rovingFocus(table, '.sx-name-btn', { signal: _life?.signal, onMove: (el) => { ctx.lib.focus = el.dataset.open; } });
458
+ ctx.announce(ctx.t('skillsStudio.library.shown', { shown: shown.length, total: ctx.counts?.total ?? 0 }));
459
+ }
460
+
461
+ /** Put the keyboard in the Library's search field (⌘/Ctrl+P). */
462
+ export function focusLibrary(panel) {
463
+ requestAnimationFrame(() => panel?.querySelector('#sx-lib-search')?.focus());
464
+ }
465
+
466
+ function updateNoticeHTML(ctx) {
467
+ if (!ctx.updates.length || ctx.lib.view !== 'all') return '';
468
+ 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>
469
+ <button type="button" class="sx-btn" data-act="place" data-place="review">${ctx.t('skillsStudio.library.reviewUpdates')}</button></div>`;
470
+ }
471
+
472
+ async function onClick(e, host, ctx) {
473
+ const notice = e.target.closest?.('[data-notice]');
474
+ if (notice && !notice.disabled) { onNoticeAction(notice, ctx); return; }
475
+ const install = e.target.closest?.('[data-install]');
476
+ if (install && host.contains(install)) { ctx.lib.focus = install.dataset.install; ctx.openRow(install.dataset.install, { tab: 'publish' }); return; }
477
+ const also = e.target.closest?.('[data-also-install]');
478
+ if (also && host.contains(also) && !also.disabled) { onAlsoInstall(also, host, ctx); return; }
479
+ const restore = e.target.closest?.('[data-restore]');
480
+ if (restore && host.contains(restore) && !restore.disabled) { onRestore(restore, host, ctx); return; }
481
+ const fix = e.target.closest?.('[data-fix]');
482
+ if (fix && host.contains(fix)) { const row = ctx.rows.find((r) => r.key === fix.dataset.fix); ctx.lib.focus = fix.dataset.fix; if (row) openFixPanel(ctx, row, { deleteKind, bulkDelete }); return; }
483
+ const open = e.target.closest?.('[data-open]');
484
+ if (open && host.contains(open)) { ctx.lib.focus = open.dataset.open; ctx.openRow(open.dataset.open); return; }
485
+ const btn = e.target.closest?.('[data-lib]');
486
+ if (!btn || !host.contains(btn) || btn.disabled) return;
487
+ const act = btn.dataset.lib;
488
+ if (act === 'new') ctx.goPlace('create');
489
+ else if (act === 'retry') ctx.reload();
490
+ else if (act === 'install-notice-ok') { ctx.lib.installNotice = null; ctx.render(); }
491
+ else if (act === 'fix-notice-ok') { ctx.lib.fixNotice = null; ctx.render(); }
492
+ else if (act === 'fix-all') fixAllSafe(ctx, ctx.rows, deleteKind);
493
+ else if (act === 'fix-show') { Object.assign(ctx.lib, { view: 'all', health: 'attention' }); ctx.saveLibraryView(); ctx.render(); }
494
+ else if (act === 'clear-filters') { Object.assign(ctx.lib, { view: 'all', kind: 'all', source: 'all', runtime: 'all', health: 'any', q: '' }); ctx.saveLibraryView(); ctx.render(); }
495
+ else if (act === 'bulk-clear') { ctx.lib.selected.clear(); redrawRows(host, ctx); }
496
+ else if (act === 'diagnostics') showDiagnostics(ctx);
497
+ else if (act === 'kept-reopen') reopenKept(ctx);
498
+ else if (act === 'kept-discard') discardKept(ctx);
499
+ else if (act === 'bulk-adopt') bulkAdopt(ctx);
500
+ else if (act === 'bulk-export') bulkExport(ctx);
501
+ else if (act === 'bulk-delete') bulkDelete(ctx);
502
+ }
503
+
504
+ function onCheck(e, host, ctx) {
505
+ const box = e.target;
506
+ if (!(box instanceof HTMLInputElement) || box.type !== 'checkbox') return;
507
+ const shown = visible(ctx);
508
+ if (box.dataset.checkAll) {
509
+ 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);
510
+ } else if (box.dataset.check) {
511
+ const key = box.dataset.check;
512
+ // Shift-click selects every row between the last one ticked and this one.
513
+ if (e.shiftKey && _lastChecked) {
514
+ const keys = shown.map((r) => r.key);
515
+ const [a, b] = [keys.indexOf(_lastChecked), keys.indexOf(key)].sort((x, y) => x - y);
516
+ 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); }
517
+ } else if (box.checked) ctx.lib.selected.add(key); else ctx.lib.selected.delete(key);
518
+ _lastChecked = key;
519
+ } else return;
520
+ const focusKey = box.dataset.check;
521
+ redrawRows(host, ctx);
522
+ if (focusKey) host.querySelector(`[data-check="${CSS.escape(focusKey)}"]`)?.focus();
523
+ }
524
+
525
+ function onRowKey(e, host, ctx) {
526
+ const btn = e.target.closest?.('.sx-name-btn');
527
+ if (!btn || e.key !== ' ' || ctx.guest) return;
528
+ e.preventDefault();
529
+ const key = btn.dataset.open;
530
+ if (ctx.lib.selected.has(key)) ctx.lib.selected.delete(key); else ctx.lib.selected.add(key);
531
+ ctx.lib.focus = key;
532
+ redrawRows(host, ctx);
533
+ host.querySelector(`.sx-name-btn[data-open="${CSS.escape(key)}"]`)?.focus();
534
+ }
535
+
536
+ // ── Notices and bulk actions ──
537
+
538
+ async function showDiagnostics(ctx) {
539
+ const { t } = ctx;
540
+ const diag = ctx.data?.catalog?.diagnostics || [];
541
+ const list = document.createElement('ul');
542
+ list.className = 'sx-plain-list';
543
+ for (const d of diag.slice(0, 50)) {
544
+ const li = document.createElement('li');
545
+ const key = `skillsStudio.library.diagReason.${d.reason}`;
546
+ const reason = DIAG_REASONS.includes(d.reason) ? t(key) : (d.message || d.reason || '');
547
+ li.textContent = `${d.path || t('skillsStudio.library.diagRoot')}: ${reason}`;
548
+ li.title = d.message || '';
549
+ list.append(li);
550
+ }
551
+ const buttons = ctx.guest ? [{ id: 'ok', label: t('skillsStudio.dialog.close'), kind: 'primary' }]
552
+ : [{ id: 'dismiss', label: t('skillsStudio.fix.action.dismiss') }, { id: 'ok', label: t('skillsStudio.dialog.close') }, { id: 'rescan', label: t('skillsStudio.fix.action.rescan'), kind: 'primary' }];
553
+ const answer = await ctx.ask({ title: t('skillsStudio.library.diagnosticsTitle'), lines: [t('skillsStudio.library.diagnosticsHelp'), t('skillsStudio.fix.explain.unreadable')], extra: list, buttons, cancelId: 'ok', wide: true });
554
+ if (answer === 'dismiss') { ctx.dismissIssues([unreadableFingerprint(diag.length)]); ctx.toast(t('skillsStudio.fix.dismissed')); return; }
555
+ if (answer !== 'rescan') return;
556
+ const gen = ctx.gen();
557
+ try {
558
+ await ctx.api.rescanSkillsCatalog();
559
+ if (!ctx.alive(gen)) return;
560
+ ctx.toast(t('skillsStudio.fix.rescanned'));
561
+ } catch (err) { if (ctx.alive(gen)) ctx.toast(t('skillsStudio.fix.failed', { error: studioErrorText(ctx.t, err) }), 'error'); }
562
+ if (ctx.alive(gen)) await ctx.reload();
563
+ }
564
+
565
+ async function reopenKept(ctx) {
566
+ const kept = Object.values(ctx.keptDrafts()).filter((d) => !ctx.workspaces.has(d.wsKey) && ctx.rows.some((r) => r.key === d.wsKey));
567
+ const first = kept[0];
568
+ if (!first) return;
569
+ await ctx.openRow(first.wsKey, { tab: 'edit', file: first.path });
570
+ const others = new Set(kept.slice(1).map((d) => d.wsKey));
571
+ if (others.size) ctx.toast(ctx.tp('skillsStudio.library.keptMore', others.size));
572
+ }
573
+
574
+ async function discardKept(ctx) {
575
+ const { t, tp } = ctx;
576
+ const kept = Object.entries(ctx.keptDrafts()).filter(([, d]) => !ctx.workspaces.has(d.wsKey));
577
+ const answer = await ctx.ask({
578
+ title: t('skillsStudio.library.keptDiscardTitle'),
579
+ lines: [tp('skillsStudio.library.keptDiscardHelp', kept.length)],
580
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'discard', label: t('skillsStudio.dialog.discard'), kind: 'danger' }],
581
+ focus: 'cancel',
582
+ });
583
+ if (answer !== 'discard') return;
584
+ dropKept(kept.map(([key]) => key));
585
+ ctx.render();
586
+ }
587
+
588
+ async function bulkAdopt(ctx) {
589
+ const { t, tp, api } = ctx;
590
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key) && subjectOf(r, { caps: ctx.caps }).mode === 'adopt');
591
+ const answer = await ctx.ask({
592
+ title: tp('skillsStudio.library.bulk.adoptTitle', chosen.length),
593
+ lines: [t('skillsStudio.library.bulk.adoptExplain'), ...chosen.slice(0, 8).map((r) => `• ${r.label}`)],
594
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'adopt', label: t('skillsStudio.library.bulk.adoptConfirm'), kind: 'primary' }],
595
+ });
596
+ if (answer !== 'adopt') return;
597
+ const gen = ctx.gen();
598
+ let done = 0; const failed = [];
599
+ for (const row of chosen) {
600
+ try { await api.adoptSkillsArtifact(row.primary.id, { idempotencyKey: ctx.key('adopt') }); done++; } catch (err) { failed.push(`${row.label}: ${studioErrorText(ctx.t, err)}`); }
601
+ }
602
+ if (!ctx.alive(gen)) return;
603
+ 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');
604
+ ctx.lib.selected.clear();
605
+ await ctx.reload();
606
+ }
607
+
608
+ function bulkExport(ctx) {
609
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key));
610
+ let n = 0;
611
+ for (const row of chosen) {
612
+ const s = subjectOf(row, { caps: ctx.caps });
613
+ const url = row.skill?.latest && ctx.caps.has('export') ? ctx.api.getSkillsRevisionExportUrl(row.skill.latest.revisionId, { format: 'bundle' })
614
+ : s.v1Id ? ctx.api.getSkillsExportUrl(s.v1Id) : null;
615
+ if (!url) continue;
616
+ const a = document.createElement('a');
617
+ a.href = url;
618
+ a.download = '';
619
+ a.rel = 'noopener';
620
+ document.body.append(a);
621
+ a.click();
622
+ a.remove();
623
+ n++;
624
+ }
625
+ ctx.toast(ctx.tp('skillsStudio.library.bulk.exported', n));
626
+ }
627
+
628
+ export async function bulkDelete(ctx) {
629
+ const { t, tp, api } = ctx;
630
+ const chosen = ctx.rows.filter((r) => ctx.lib.selected.has(r.key)).map((r) => ({ row: r, s: subjectOf(r, { caps: ctx.caps }) }))
631
+ .map((x) => ({ ...x, kind: deleteKind(ctx, x.row, x.s) })).filter((x) => x.kind);
632
+ if (!chosen.length) return;
633
+ const onDisk = chosen.filter((x) => x.kind === 'disk');
634
+ const inLibrary = chosen.filter((x) => x.kind === 'library');
635
+ // Unsaved text in an open workspace of one of them goes with it only when the owner says so.
636
+ const unsaved = chosen.filter((x) => ctx.workspaces.get(x.row.key)?.tabs.some((tab) => tab.dirty));
637
+ const word = t('skillsStudio.library.bulk.deleteWord');
638
+ const { box, input, err } = deleteConfirmBox(ctx, chosen, { onDisk, inLibrary, unsaved, word });
639
+ const answer = await ctx.ask({
640
+ title: tp('skillsStudio.library.bulk.deleteTitle', chosen.length),
641
+ extra: box,
642
+ className: 'ss-dlg--confirm',
643
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'delete', label: t('skillsStudio.library.bulk.deleteConfirm'), kind: 'danger', disabled: true }],
644
+ focus: 'cancel',
645
+ // Delete stays off until the word is typed; Enter in the field answers once it is.
646
+ onOpen: (ui) => {
647
+ input.addEventListener('input', () => { ui.setDisabled('delete', !deleteWordMatches(input.value, word)); if (deleteWordMatches(input.value, word)) err.textContent = ''; });
648
+ input.addEventListener('keydown', (e) => {
649
+ if (e.key !== 'Enter' || e.isComposing) return;
650
+ e.preventDefault();
651
+ if (deleteWordMatches(input.value, word)) ui.done('delete'); else err.textContent = t('skillsStudio.library.bulk.deleteTypeError');
652
+ });
653
+ input.focus({ preventScroll: true });
654
+ },
655
+ accept: (id) => { if (id === 'delete' && !deleteWordMatches(input.value, word)) { err.textContent = t('skillsStudio.library.bulk.deleteTypeError'); return false; } return true; },
656
+ });
657
+ if (answer !== 'delete') return;
658
+ const gen = ctx.gen();
659
+ let done = 0; const failed = [];
660
+ for (const x of chosen) {
661
+ try {
662
+ if (x.kind === 'disk') await api.deleteSkillsArtifact(x.s.v1Id);
663
+ else await api.deleteLibrarySkill(x.row.skill.skillId);
664
+ done++;
665
+ const ws = ctx.workspaces.get(x.row.key);
666
+ if (ws) { for (const tab of ws.tabs) dropDraft(ws, tab); ctx.closeWorkspace(ws); }
667
+ } catch (e) { failed.push(`${x.row.label}: ${studioErrorText(ctx.t, e)}`); }
668
+ }
669
+ if (!ctx.alive(gen)) return;
670
+ 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');
671
+ ctx.lib.selected.clear();
672
+ await ctx.reload();
673
+ }
674
+
675
+ /** Whether what was typed is the confirmation word (case and surrounding spaces aside). */
676
+ export function deleteWordMatches(value, word) {
677
+ return String(value ?? '').trim().toLocaleLowerCase() === String(word ?? '').trim().toLocaleLowerCase() && !!String(word ?? '').trim();
678
+ }
679
+
680
+ /**
681
+ * The body of the delete question: one tidy row per item (its name, its kind, where it is, shortened, with the full
682
+ * path on hover, and what happens to it), what that means, and the field the word is typed in.
683
+ */
684
+ export function deleteConfirmBox(ctx, chosen, { onDisk = [], inLibrary = [], unsaved = [], word }) {
685
+ const { t, tp } = ctx;
686
+ const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text !== undefined) n.textContent = text; return n; };
687
+ const box = el('div', 'sx-del');
688
+ const list = el('ul', 'sx-del-list');
689
+ list.setAttribute('aria-label', t('skillsStudio.library.bulk.deleteListLabel'));
690
+ const SHOWN = 6;
691
+ for (const x of chosen.slice(0, SHOWN)) {
692
+ const li = el('li', 'sx-del-item');
693
+ const icon = el('span', 'sx-del-icon');
694
+ icon.setAttribute('aria-hidden', 'true');
695
+ setHTML(icon, x.row.kind === 'skill' ? ICONS.folder : ICONS.file);
696
+ const main = el('span', 'sx-del-main');
697
+ main.append(el('span', 'sx-del-name', x.row.label));
698
+ const where = x.kind === 'disk' ? shortPath(x.row.path || '') : t('skillsStudio.library.bulk.deleteInLibrary');
699
+ const path = el('span', 'sx-del-path', where);
700
+ if (x.kind === 'disk' && x.row.path) path.title = x.row.path;
701
+ main.append(path);
702
+ const kind = el('span', 'sx-del-kind', kindLabel(x.row, t));
703
+ const effect = el('span', `sx-del-effect sx-del-effect--${x.kind}`, x.kind === 'disk' ? t('skillsStudio.library.bulk.deleteEffectDisk') : t('skillsStudio.library.bulk.deleteEffectLibrary'));
704
+ li.append(icon, main, kind, effect);
705
+ list.append(li);
706
+ }
707
+ if (chosen.length > SHOWN) list.append(el('li', 'sx-del-more', t('skillsStudio.library.bulk.deleteMore', { count: chosen.length - SHOWN })));
708
+ box.append(list);
709
+ // Two kinds, said apart: what is removed from where it is (for good), and what only leaves the library.
710
+ if (onDisk.length) {
711
+ const note = el('p', 'sx-del-note sx-del-note--danger');
712
+ setHTML(note, html`${ICONS.warn}<span>${t('skillsStudio.library.bulk.deleteExplain')}</span>`);
713
+ box.append(note);
714
+ }
715
+ if (inLibrary.length) {
716
+ const note = el('p', 'sx-del-note');
717
+ setHTML(note, html`${ICONS.info}<span>${t('skillsStudio.library.bulk.deleteLibraryExplain')}</span>`);
718
+ box.append(note);
719
+ }
720
+ if (unsaved.length) {
721
+ const note = el('p', 'sx-del-note sx-del-note--danger');
722
+ setHTML(note, html`${ICONS.draft}<span>${tp('skillsStudio.library.bulk.deleteUnsaved', unsaved.length)}</span>`);
723
+ box.append(note);
724
+ }
725
+ const field = el('div', 'sx-del-field');
726
+ const label = el('label', 'sx-del-label');
727
+ label.htmlFor = 'sx-del-confirm';
728
+ // "Type {word} to confirm", with the word set apart as what is to be typed.
729
+ const sentence = t('skillsStudio.library.bulk.deleteType', { word: '\u0000' });
730
+ const [before, after = ''] = sentence.split('\u0000');
731
+ label.append(before, el('code', 'sx-del-word', word), after);
732
+ const input = el('input', 'sx-del-input');
733
+ input.id = 'sx-del-confirm';
734
+ input.type = 'text';
735
+ input.autocomplete = 'off';
736
+ input.spellcheck = false;
737
+ input.setAttribute('autocapitalize', 'off');
738
+ input.setAttribute('aria-describedby', 'sx-del-error');
739
+ const err = el('p', 'ss-dlg-error');
740
+ err.id = 'sx-del-error';
741
+ err.setAttribute('role', 'alert');
742
+ field.append(label, input, err);
743
+ box.append(field);
744
+ return { box, input, err };
745
+ }
746
+
747
+ /** The P0 id of a row's entry (exported for the workspace's Export of an agent or a command). */
748
+ export const v1ExportId = (entry) => (entry?.path ? encodeArtifactId(`${entry.kind}:${entry.path}`) : null);