synabun 2.0.10 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (517) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +25 -2
  3. package/THIRD-PARTY-LICENSES.md +22 -0
  4. package/bin/synabun.js +71 -24
  5. package/docs/whatsapp.md +2 -2
  6. package/hooks/claude-code/pre-websearch.mjs +12 -1
  7. package/hooks/claude-code/shared.mjs +5 -0
  8. package/hooks/claude-code/stop.mjs +1 -1
  9. package/lib/claude-hooks.js +172 -18
  10. package/lib/client-config-repair.js +17 -8
  11. package/lib/desktop-app.js +7 -1
  12. package/lib/external-tools.js +1 -1
  13. package/lib/first-launch-protection.js +57 -12
  14. package/lib/mcp-dist.js +176 -0
  15. package/lib/release-npm-ci.mjs +138 -0
  16. package/lib/release-publish.mjs +91 -0
  17. package/lib/release-scan.mjs +50 -0
  18. package/lib/server-crash-record.js +123 -0
  19. package/lib/server-exit.js +318 -0
  20. package/lib/server-supervisor.js +508 -0
  21. package/lib/start-launcher.js +37 -4
  22. package/lib/tools-cli.js +45 -0
  23. package/lib/update-snapshot.js +228 -0
  24. package/lib/virustotal.mjs +192 -0
  25. package/lib/windows-crash-event.js +269 -0
  26. package/mcp-server/dist/http.d.ts +20 -1
  27. package/mcp-server/dist/http.d.ts.map +1 -1
  28. package/mcp-server/dist/http.js +59 -12
  29. package/mcp-server/dist/http.js.map +1 -1
  30. package/mcp-server/dist/index.d.ts +33 -27
  31. package/mcp-server/dist/index.d.ts.map +1 -1
  32. package/mcp-server/dist/index.js +128 -143
  33. package/mcp-server/dist/index.js.map +1 -1
  34. package/mcp-server/dist/preload.js +1 -1
  35. package/mcp-server/dist/preload.js.map +1 -1
  36. package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
  37. package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
  38. package/mcp-server/dist/services/codex-config-heal.js +81 -28
  39. package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
  40. package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
  41. package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
  42. package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
  43. package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
  44. package/mcp-server/dist/services/neural-interface.d.ts +16 -10
  45. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  46. package/mcp-server/dist/services/neural-interface.js +67 -11
  47. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  48. package/mcp-server/dist/services/server-instructions.d.ts +7 -2
  49. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  50. package/mcp-server/dist/services/server-instructions.js +20 -62
  51. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  52. package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
  53. package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
  54. package/mcp-server/dist/services/tool-catalog.js +339 -0
  55. package/mcp-server/dist/services/tool-catalog.js.map +1 -0
  56. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
  57. package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
  58. package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
  59. package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
  60. package/mcp-server/dist/services/tool-list.d.ts +108 -0
  61. package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
  62. package/mcp-server/dist/services/tool-list.js +334 -0
  63. package/mcp-server/dist/services/tool-list.js.map +1 -0
  64. package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
  65. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
  66. package/mcp-server/dist/services/tool-runtime.js +400 -0
  67. package/mcp-server/dist/services/tool-runtime.js.map +1 -0
  68. package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
  69. package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
  70. package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
  71. package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
  72. package/mcp-server/dist/tools/agents.d.ts +10 -10
  73. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  74. package/mcp-server/dist/tools/agents.js +22 -13
  75. package/mcp-server/dist/tools/agents.js.map +1 -1
  76. package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
  77. package/mcp-server/dist/tools/image-tools.d.ts +1 -1
  78. package/mcp-server/dist/tools/image-tools.js +1 -1
  79. package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
  80. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
  81. package/mcp-server/dist/tools/skill-tools.js +639 -0
  82. package/mcp-server/dist/tools/skill-tools.js.map +1 -0
  83. package/mcp-server/dist/tools/skill.d.ts +7 -0
  84. package/mcp-server/dist/tools/skill.d.ts.map +1 -0
  85. package/mcp-server/dist/tools/skill.js +11 -0
  86. package/mcp-server/dist/tools/skill.js.map +1 -0
  87. package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
  88. package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
  89. package/mcp-server/dist/tools/tool-groups.js +147 -0
  90. package/mcp-server/dist/tools/tool-groups.js.map +1 -0
  91. package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
  92. package/mcp-server/dist/tools/whiteboard-format.js +7 -4
  93. package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
  94. package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
  95. package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
  96. package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
  97. package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
  98. package/mcp-server/package-lock.json +2 -2
  99. package/mcp-server/package.json +1 -1
  100. package/mcp-server/src/http.ts +75 -12
  101. package/mcp-server/src/index.ts +147 -154
  102. package/mcp-server/src/preload.ts +12 -12
  103. package/mcp-server/src/services/codex-config-heal.ts +80 -29
  104. package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
  105. package/mcp-server/src/services/neural-interface.ts +79 -20
  106. package/mcp-server/src/services/server-instructions.ts +27 -63
  107. package/mcp-server/src/services/tool-catalog.ts +403 -0
  108. package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
  109. package/mcp-server/src/services/tool-list.ts +410 -0
  110. package/mcp-server/src/services/tool-runtime.ts +457 -0
  111. package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
  112. package/mcp-server/src/tools/agents.ts +22 -14
  113. package/mcp-server/src/tools/image-tools.ts +1 -1
  114. package/mcp-server/src/tools/skill-tools.ts +589 -0
  115. package/mcp-server/src/tools/skill.ts +13 -0
  116. package/mcp-server/src/tools/tool-groups.ts +164 -0
  117. package/mcp-server/src/tools/whiteboard-format.ts +9 -3
  118. package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
  119. package/neural-interface/i18n/en.json +3463 -200
  120. package/neural-interface/i18n/pt-BR.json +3411 -151
  121. package/neural-interface/lib/assistant-api.js +127 -28
  122. package/neural-interface/lib/assistant-brains/claude.js +4 -3
  123. package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
  124. package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
  125. package/neural-interface/lib/assistant-brains/codex.js +9 -5
  126. package/neural-interface/lib/assistant-brains/opencode.js +120 -54
  127. package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
  128. package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
  129. package/neural-interface/lib/assistant-catalog.js +6 -7
  130. package/neural-interface/lib/assistant-dispatch.js +871 -156
  131. package/neural-interface/lib/assistant-envelope.js +3 -1
  132. package/neural-interface/lib/assistant-persona.js +80 -11
  133. package/neural-interface/lib/assistant-plan-permissions.js +10 -9
  134. package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
  135. package/neural-interface/lib/assistant-route-gate.js +6 -4
  136. package/neural-interface/lib/assistant-router.js +13 -8
  137. package/neural-interface/lib/assistant-runtime.js +56 -18
  138. package/neural-interface/lib/assistant-task-prompt.js +78 -14
  139. package/neural-interface/lib/assistant-tools.js +51 -0
  140. package/neural-interface/lib/backup-service.js +620 -3
  141. package/neural-interface/lib/backup-zip-reader.js +10 -5
  142. package/neural-interface/lib/backup-zip-writer.js +257 -0
  143. package/neural-interface/lib/browser-execution.js +10 -2
  144. package/neural-interface/lib/browser-guard-cdp.js +83 -0
  145. package/neural-interface/lib/browser-network-guard.js +284 -0
  146. package/neural-interface/lib/browser-policy-roots.js +64 -0
  147. package/neural-interface/lib/browser-tool-policy.js +186 -31
  148. package/neural-interface/lib/claude-agent-bridge.js +4 -2
  149. package/neural-interface/lib/claude-hooks-status.js +103 -0
  150. package/neural-interface/lib/claude-temporary.js +5 -11
  151. package/neural-interface/lib/cli-install.js +18 -138
  152. package/neural-interface/lib/cli-resolver.js +4 -1
  153. package/neural-interface/lib/cli-update-planner.js +32 -31
  154. package/neural-interface/lib/cloudflared/tunnel.js +42 -2
  155. package/neural-interface/lib/codex-mcp-approvals.js +11 -0
  156. package/neural-interface/lib/codex-worker-app-server.js +172 -0
  157. package/neural-interface/lib/confined-fs.js +30 -3
  158. package/neural-interface/lib/crash-notice-api.js +98 -0
  159. package/neural-interface/lib/editor-completion-opencode.js +68 -68
  160. package/neural-interface/lib/exec-loop-command.js +4 -8
  161. package/neural-interface/lib/http-guards.js +8 -2
  162. package/neural-interface/lib/install-details.js +4 -1
  163. package/neural-interface/lib/mcp-agent-servers.js +67 -0
  164. package/neural-interface/lib/mcp-client-config.js +0 -29
  165. package/neural-interface/lib/mcp-external-servers.js +323 -0
  166. package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
  167. package/neural-interface/lib/morelogin.js +30 -0
  168. package/neural-interface/lib/native-loop-providers.js +547 -47
  169. package/neural-interface/lib/native-loop-runtime.js +268 -41
  170. package/neural-interface/lib/opencode-config.js +557 -0
  171. package/neural-interface/lib/opencode-db-schema.js +17 -34
  172. package/neural-interface/lib/opencode-history.js +48 -218
  173. package/neural-interface/lib/opencode-mcp-ready.js +108 -0
  174. package/neural-interface/lib/opencode-native-boundary.js +44 -0
  175. package/neural-interface/lib/opencode-protocol.js +249 -112
  176. package/neural-interface/lib/opencode-runtime-config.js +32 -22
  177. package/neural-interface/lib/opencode-serve-records.js +39 -21
  178. package/neural-interface/lib/opencode-v2-client.js +48 -153
  179. package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
  180. package/neural-interface/lib/opencode-worker-data.js +27 -0
  181. package/neural-interface/lib/opencode2-client.js +73 -37
  182. package/neural-interface/lib/opencode2-proxy.js +23 -63
  183. package/neural-interface/lib/opencode2-translate.js +51 -2
  184. package/neural-interface/lib/owner-authority.js +249 -0
  185. package/neural-interface/lib/page-session.js +275 -0
  186. package/neural-interface/lib/peer-identity.js +763 -0
  187. package/neural-interface/lib/request-gate.js +17 -2
  188. package/neural-interface/lib/rulesets/api.js +1 -1
  189. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  190. package/neural-interface/lib/rulesets/installer.js +92 -27
  191. package/neural-interface/lib/rulesets/json-edit.js +1 -1
  192. package/neural-interface/lib/session-cache-builder.js +17 -30
  193. package/neural-interface/lib/session-title-generator.js +58 -36
  194. package/neural-interface/lib/skills/api-v2.js +495 -0
  195. package/neural-interface/lib/skills/bundle.js +418 -0
  196. package/neural-interface/lib/skills/catalog.js +294 -0
  197. package/neural-interface/lib/skills/creation-api.js +191 -0
  198. package/neural-interface/lib/skills/creation-brief.js +247 -0
  199. package/neural-interface/lib/skills/creation-sources.js +267 -0
  200. package/neural-interface/lib/skills/creation.js +843 -0
  201. package/neural-interface/lib/skills/deploy.js +564 -0
  202. package/neural-interface/lib/skills/digest.js +119 -0
  203. package/neural-interface/lib/skills/discover.js +111 -0
  204. package/neural-interface/lib/skills/draft-confinement.js +214 -0
  205. package/neural-interface/lib/skills/eval-confinement.js +397 -0
  206. package/neural-interface/lib/skills/eval-sandbox.js +280 -0
  207. package/neural-interface/lib/skills/eval-trace.js +119 -0
  208. package/neural-interface/lib/skills/evals-api.js +212 -0
  209. package/neural-interface/lib/skills/evals.js +1573 -0
  210. package/neural-interface/lib/skills/export.js +200 -0
  211. package/neural-interface/lib/skills/frontmatter.js +64 -0
  212. package/neural-interface/lib/skills/health.js +75 -0
  213. package/neural-interface/lib/skills/json-edit.js +165 -0
  214. package/neural-interface/lib/skills/known-bridge-digests.json +387 -0
  215. package/neural-interface/lib/skills/known-bridges.js +96 -0
  216. package/neural-interface/lib/skills/library.js +823 -0
  217. package/neural-interface/lib/skills/local-store.js +21 -0
  218. package/neural-interface/lib/skills/migration.js +433 -0
  219. package/neural-interface/lib/skills/operations.js +45 -0
  220. package/neural-interface/lib/skills/ownership.js +427 -0
  221. package/neural-interface/lib/skills/packages.js +88 -0
  222. package/neural-interface/lib/skills/reviews.js +212 -0
  223. package/neural-interface/lib/skills/revision.js +80 -0
  224. package/neural-interface/lib/skills/roots.js +262 -0
  225. package/neural-interface/lib/skills/runtime-checks.js +355 -0
  226. package/neural-interface/lib/skills/scan-assets.js +252 -0
  227. package/neural-interface/lib/skills/scan-context.js +26 -0
  228. package/neural-interface/lib/skills/scan-shell.js +324 -0
  229. package/neural-interface/lib/skills/scan.js +364 -0
  230. package/neural-interface/lib/skills/scanners.js +54 -0
  231. package/neural-interface/lib/skills/service.js +522 -0
  232. package/neural-interface/lib/skills/telemetry.js +113 -0
  233. package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
  234. package/neural-interface/lib/tool-lists.js +124 -0
  235. package/neural-interface/lib/whatsapp/bridge.js +6 -3
  236. package/neural-interface/lib/worker-confinement.js +1192 -0
  237. package/neural-interface/lib/worker-guard-setting.js +56 -0
  238. package/neural-interface/lib/worker-mcp-channel.js +175 -0
  239. package/neural-interface/lib/worker-relay-ports.js +227 -0
  240. package/neural-interface/lib/worker-resume.js +229 -0
  241. package/neural-interface/lib/worker-sandbox.js +299 -0
  242. package/neural-interface/package-lock.json +18 -12
  243. package/neural-interface/package.json +2 -2
  244. package/neural-interface/public/index.html +3 -1
  245. package/neural-interface/public/index2d.html +19 -11
  246. package/neural-interface/public/onboarding.html +2 -2
  247. package/neural-interface/public/shared/account-menu.js +8 -5
  248. package/neural-interface/public/shared/agent-runtime-options.js +35 -19
  249. package/neural-interface/public/shared/api.js +1527 -1336
  250. package/neural-interface/public/shared/app-frame.js +192 -0
  251. package/neural-interface/public/shared/assistant/asst-agents-dock.js +33 -2
  252. package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
  253. package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
  254. package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
  255. package/neural-interface/public/shared/assistant/asst-brain-picker.js +49 -35
  256. package/neural-interface/public/shared/assistant/asst-control.js +157 -26
  257. package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
  258. package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
  259. package/neural-interface/public/shared/assistant/asst-limit-dock.js +40 -0
  260. package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -0
  261. package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
  262. package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
  263. package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
  264. package/neural-interface/public/shared/assistant/asst-panel.js +106 -11
  265. package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
  266. package/neural-interface/public/shared/assistant/asst-render.js +401 -77
  267. package/neural-interface/public/shared/assistant/asst-route.js +83 -12
  268. package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
  269. package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
  270. package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
  271. package/neural-interface/public/shared/assistant/asst-state.js +11 -15
  272. package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
  273. package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
  274. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
  275. package/neural-interface/public/shared/automation-context.js +5 -2
  276. package/neural-interface/public/shared/cc-dropdown.js +89 -19
  277. package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
  278. package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
  279. package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
  280. package/neural-interface/public/shared/cdx/cdx-output.js +8 -7
  281. package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
  282. package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
  283. package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
  284. package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
  285. package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
  286. package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
  287. package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
  288. package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
  289. package/neural-interface/public/shared/cli-install-view.js +4 -8
  290. package/neural-interface/public/shared/cli-status.js +2 -2
  291. package/neural-interface/public/shared/constants.js +2 -2
  292. package/neural-interface/public/shared/context-menu-areas.js +34 -0
  293. package/neural-interface/public/shared/context-menu-logic.js +243 -0
  294. package/neural-interface/public/shared/context-menu-model.js +366 -0
  295. package/neural-interface/public/shared/context-menu.js +372 -0
  296. package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
  297. package/neural-interface/public/shared/crash-notice-view.js +168 -0
  298. package/neural-interface/public/shared/desktop-update-view.js +20 -0
  299. package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
  300. package/neural-interface/public/shared/html-shell.js +9 -11
  301. package/neural-interface/public/shared/i18n.js +2 -0
  302. package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
  303. package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
  304. package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
  305. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
  306. package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
  307. package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
  308. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
  309. package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
  310. package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
  311. package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
  312. package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
  313. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +47 -252
  314. package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
  315. package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
  316. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +6 -21
  317. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
  318. package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
  319. package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
  320. package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
  321. package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
  322. package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
  323. package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
  324. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +0 -21
  325. package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
  326. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +14 -47
  327. package/neural-interface/public/shared/page-session.js +96 -0
  328. package/neural-interface/public/shared/registry.js +5 -3
  329. package/neural-interface/public/shared/retired-2d.js +3 -0
  330. package/neural-interface/public/shared/schedules-view.js +195 -61
  331. package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
  332. package/neural-interface/public/shared/settings/settings-data.js +3 -1
  333. package/neural-interface/public/shared/settings/settings-ia.js +6 -6
  334. package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
  335. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
  336. package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
  337. package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
  338. package/neural-interface/public/shared/skills/ss-args.js +96 -0
  339. package/neural-interface/public/shared/skills/ss-creation.js +219 -0
  340. package/neural-interface/public/shared/skills/ss-dialog.js +130 -0
  341. package/neural-interface/public/shared/skills/ss-doc.js +173 -0
  342. package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
  343. package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
  344. package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
  345. package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
  346. package/neural-interface/public/shared/skills/ss-evals.js +417 -0
  347. package/neural-interface/public/shared/skills/ss-form.js +159 -0
  348. package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
  349. package/neural-interface/public/shared/skills/ss-ids.js +32 -0
  350. package/neural-interface/public/shared/skills/ss-lint.js +141 -0
  351. package/neural-interface/public/shared/skills/ss-presets.js +117 -0
  352. package/neural-interface/public/shared/skills/ss-profiles.js +475 -0
  353. package/neural-interface/public/shared/skills/ss-resources.js +165 -0
  354. package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
  355. package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
  356. package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
  357. package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
  358. package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
  359. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
  360. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +236 -0
  361. package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
  362. package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
  363. package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
  364. package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
  365. package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
  366. package/neural-interface/public/shared/skills/studio/sx-dom.js +229 -0
  367. package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
  368. package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
  369. package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
  370. package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
  371. package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
  372. package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
  373. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +279 -0
  374. package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
  375. package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
  376. package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
  377. package/neural-interface/public/shared/skills/studio/sx-main.js +681 -0
  378. package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
  379. package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
  380. package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
  381. package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
  382. package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
  383. package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
  384. package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
  385. package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
  386. package/neural-interface/public/shared/skills/studio/sx-subject.js +248 -0
  387. package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
  388. package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
  389. package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
  390. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +425 -0
  391. package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
  392. package/neural-interface/public/shared/styles.css +1525 -1779
  393. package/neural-interface/public/shared/synabun-mascot.js +1115 -9
  394. package/neural-interface/public/shared/tool-list-picker.js +55 -0
  395. package/neural-interface/public/shared/tool-list-storage.js +24 -0
  396. package/neural-interface/public/shared/tool-list-view.js +112 -0
  397. package/neural-interface/public/shared/tour/tour-actor.js +268 -0
  398. package/neural-interface/public/shared/tour/tour-beats.js +336 -0
  399. package/neural-interface/public/shared/tour/tour-engine.js +884 -0
  400. package/neural-interface/public/shared/tour/tour-ink.js +390 -0
  401. package/neural-interface/public/shared/tour/tour-stage.js +440 -0
  402. package/neural-interface/public/shared/tour/tour-state.js +40 -0
  403. package/neural-interface/public/shared/tour/tour-tips.js +99 -0
  404. package/neural-interface/public/shared/tour/tour.css +144 -0
  405. package/neural-interface/public/shared/ui-automation-studio.js +91 -128
  406. package/neural-interface/public/shared/ui-claude-panel.js +77 -5
  407. package/neural-interface/public/shared/ui-command-runner.js +6 -3
  408. package/neural-interface/public/shared/ui-context-menu.js +295 -0
  409. package/neural-interface/public/shared/ui-cost-widget.js +57 -59
  410. package/neural-interface/public/shared/ui-crash-notice.js +187 -0
  411. package/neural-interface/public/shared/ui-detail.js +15 -3
  412. package/neural-interface/public/shared/ui-image-gallery.js +50 -47
  413. package/neural-interface/public/shared/ui-keybinds.js +26 -1
  414. package/neural-interface/public/shared/ui-menubar.js +8 -18
  415. package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
  416. package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
  417. package/neural-interface/public/shared/ui-navbar.js +4 -24
  418. package/neural-interface/public/shared/ui-panels.js +96 -65
  419. package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
  420. package/neural-interface/public/shared/ui-sessions.js +5 -3
  421. package/neural-interface/public/shared/ui-settings.js +594 -462
  422. package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
  423. package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
  424. package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
  425. package/neural-interface/public/shared/ui-skills.js +33 -2830
  426. package/neural-interface/public/shared/ui-styleguide.js +5 -2
  427. package/neural-interface/public/shared/ui-sync.js +15 -5
  428. package/neural-interface/public/shared/ui-terminal.js +124 -180
  429. package/neural-interface/public/shared/ui-tool-updates.js +3 -1
  430. package/neural-interface/public/shared/ui-tooltip.js +10 -5
  431. package/neural-interface/public/shared/ui-trash.js +6 -4
  432. package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
  433. package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
  434. package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
  435. package/neural-interface/public/shared/ui-tutorial.css +47 -100
  436. package/neural-interface/public/shared/ui-tutorial.js +285 -509
  437. package/neural-interface/public/shared/ui-update.js +38 -2
  438. package/neural-interface/public/shared/ui-whiteboard.js +311 -58
  439. package/neural-interface/public/shared/ui-worker-end.js +4 -0
  440. package/neural-interface/public/shared/ui-workspaces.js +3 -2
  441. package/neural-interface/public/shared/view-variant.js +66 -0
  442. package/neural-interface/public/shared/whatsapp/wa-view.js +1 -1
  443. package/neural-interface/public/shared/whiteboard-list.js +172 -0
  444. package/neural-interface/public/shared/worker-end.js +10 -3
  445. package/neural-interface/public/shared/worker-restart.js +12 -0
  446. package/neural-interface/public/sidepanel-session.html +1 -1
  447. package/neural-interface/public/variant/2d/background.js +2 -1
  448. package/neural-interface/public/variant/2d/context-menu.js +8 -2
  449. package/neural-interface/public/variant/2d/graph.js +42 -20
  450. package/neural-interface/public/variant/2d/hulls.js +14 -9
  451. package/neural-interface/public/variant/2d/lasso.js +11 -7
  452. package/neural-interface/public/variant/2d/main.js +32 -1
  453. package/neural-interface/public/variant/2d/minimap.js +11 -13
  454. package/neural-interface/public/variant/3d/main.js +69 -6
  455. package/neural-interface/public/variant/3d/map-controls.js +24 -9
  456. package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
  457. package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
  458. package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
  459. package/neural-interface/public/vendor/caveat/caveat.css +17 -0
  460. package/neural-interface/public/vendor/caveat/manifest.json +12 -0
  461. package/neural-interface/public/vendor/yaml/LICENSE +13 -0
  462. package/neural-interface/public/vendor/yaml/manifest.json +4 -0
  463. package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
  464. package/neural-interface/scripts/build-vendor.mjs +32 -0
  465. package/neural-interface/server.js +3934 -2454
  466. package/neural-interface/templates/rulesets/core.md +1 -1
  467. package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
  468. package/neural-interface/templates/rulesets/manifest.json +8 -8
  469. package/npm-shrinkwrap.json +18 -12
  470. package/package.json +16 -3
  471. package/postinstall.js +9 -4
  472. package/preuninstall.js +1 -1
  473. package/setup.js +97 -322
  474. package/skills/skill-forge/SKILL.md +177 -0
  475. package/skills/skill-forge/evals/evals.json +54 -0
  476. package/skills/skill-forge/references/package.md +105 -0
  477. package/skills/skill-forge/references/review-and-recovery.md +63 -0
  478. package/skills/skill-forge/references/runtime-profiles.md +20 -0
  479. package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
  480. package/skills/synabun/SKILL.md +10 -7
  481. package/skills/synabun/modules/audit.md +1 -5
  482. package/skills/synabun/modules/changelog.md +1 -5
  483. package/skills/synabun/modules/idea.md +1 -1
  484. package/skills/synabun/modules/memorize.md +6 -9
  485. package/skills/synabun/modules/schedule.md +4 -4
  486. package/mcp-server/dist/services/profiles.d.ts +0 -120
  487. package/mcp-server/dist/services/profiles.d.ts.map +0 -1
  488. package/mcp-server/dist/services/profiles.js +0 -387
  489. package/mcp-server/dist/services/profiles.js.map +0 -1
  490. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
  491. package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
  492. package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
  493. package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
  494. package/mcp-server/dist/tools/leonardo.d.ts +0 -13
  495. package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
  496. package/mcp-server/dist/tools/leonardo.js +0 -21
  497. package/mcp-server/dist/tools/leonardo.js.map +0 -1
  498. package/mcp-server/dist/tools/profile.d.ts +0 -17
  499. package/mcp-server/dist/tools/profile.d.ts.map +0 -1
  500. package/mcp-server/dist/tools/profile.js +0 -114
  501. package/mcp-server/dist/tools/profile.js.map +0 -1
  502. package/mcp-server/src/services/profiles.ts +0 -428
  503. package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
  504. package/mcp-server/src/tools/leonardo.ts +0 -30
  505. package/mcp-server/src/tools/profile.ts +0 -122
  506. package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
  507. package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
  508. package/neural-interface/lib/mcp-profile-servers.js +0 -120
  509. package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
  510. package/skills/leonardo/SKILL.md +0 -168
  511. package/skills/leonardo/icon.svg +0 -1
  512. package/skills/leonardo/modules/browser-guide.md +0 -378
  513. package/skills/leonardo/modules/image-prompter.md +0 -283
  514. package/skills/leonardo/modules/model-advisor.md +0 -100
  515. package/skills/leonardo/modules/prompt-library.md +0 -110
  516. package/skills/leonardo/modules/style-guide.md +0 -186
  517. package/skills/leonardo/modules/video-prompter.md +0 -317
@@ -0,0 +1,193 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: what the editor shows about a document
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The facts of the status bar (lines, line endings, byte-order mark, bytes, an estimate of tokens with how it is
6
+ // estimated), the description helper (how much of each runtime's budget a description uses, and whether it says
7
+ // what the skill does and when to use it), the keys the form does not edit ("kept as written", the Advanced
8
+ // section), and what kind of file a path is (Markdown, YAML, an image the viewer can show, other binary).
9
+ // Measurements are named for what they are: characters, bytes, lines and an estimate of tokens (characters ÷ 4,
10
+ // said as an estimate) are kept apart (master plan §3.2). No DOM; imports only what the page and the server share.
11
+
12
+ import { PROFILES } from '../ss-profiles.js';
13
+ import { FORM_FIELDS } from '../ss-form.js';
14
+
15
+ /** Characters per token of the estimate (the usual rule of thumb for English text; said as "≈" with its method). */
16
+ export const CHARS_PER_TOKEN = 4;
17
+ /** The guidance for a SKILL.md body (Agent Skills and Claude Code best practice), not an upload limit. */
18
+ export const BODY_GUIDANCE = Object.freeze({ lines: PROFILES['portable-six-field'].limits.bodyLinesGuidance, tokens: PROFILES['portable-six-field'].limits.bodyTokensGuidance });
19
+
20
+ /** An estimate of tokens: characters ÷ 4, rounded up. */
21
+ export const estimateTokens = (text) => Math.ceil(String(text ?? '').length / CHARS_PER_TOKEN);
22
+
23
+ /** UTF-8 length of a text, without a TextEncoder (the page and Node count the same). */
24
+ export function utf8Length(text) {
25
+ const s = String(text ?? '');
26
+ let n = 0;
27
+ for (let i = 0; i < s.length; i++) {
28
+ const c = s.charCodeAt(i);
29
+ if (c < 0x80) n += 1;
30
+ else if (c < 0x800) n += 2;
31
+ else if (c >= 0xD800 && c <= 0xDBFF && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xFC00) === 0xDC00) { n += 4; i++; }
32
+ else n += 3;
33
+ }
34
+ return n;
35
+ }
36
+
37
+ const EXT = (path) => { const base = String(path ?? '').split('/').pop() || ''; const at = base.lastIndexOf('.'); return at > 0 ? base.slice(at + 1).toLowerCase() : ''; };
38
+ const IMAGE_EXT = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp', svg: 'image/svg+xml' };
39
+
40
+ /** What a file is, for the tree and the viewer. */
41
+ export function fileKind(path, { binary = false } = {}) {
42
+ const ext = EXT(path);
43
+ if (IMAGE_EXT[ext]) return { kind: 'image', mime: IMAGE_EXT[ext], ext };
44
+ if (binary) return { kind: 'binary', mime: 'application/octet-stream', ext };
45
+ if (ext === 'md' || ext === 'markdown') return { kind: 'markdown', mime: 'text/markdown', ext };
46
+ if (ext === 'yaml' || ext === 'yml') return { kind: 'yaml', mime: 'text/yaml', ext };
47
+ if (ext === 'json') return { kind: 'json', mime: 'application/json', ext };
48
+ if (['sh', 'bash', 'zsh', 'py', 'js', 'mjs', 'ts', 'rb', 'ps1'].includes(ext)) return { kind: 'script', mime: 'text/plain', ext };
49
+ return { kind: 'text', mime: 'text/plain', ext };
50
+ }
51
+
52
+ /** The line endings of a document, as the status bar says them. */
53
+ export function eolLabel({ eol = '\n', mixed = false } = {}) {
54
+ if (mixed) return 'mixed';
55
+ return eol === '\r\n' ? 'CRLF' : eol === '\r' ? 'CR' : 'LF';
56
+ }
57
+
58
+ /**
59
+ * The facts of the status bar for one document.
60
+ * @param {{ path: string|null, text: string, eol?: string, mixed?: boolean, bom?: boolean, binary?: boolean,
61
+ * size?: number, head?: boolean }} doc `head`: the skill's SKILL.md (its body is measured against the guidance)
62
+ */
63
+ export function documentFacts(doc) {
64
+ const text = String(doc?.text ?? '');
65
+ const lines = text === '' ? 1 : text.split('\n').length;
66
+ const facts = {
67
+ binary: !!doc?.binary,
68
+ lines: doc?.binary ? null : lines,
69
+ chars: doc?.binary ? null : text.length,
70
+ bytes: doc?.binary ? (Number(doc?.size) || 0) : utf8Length(text) + (doc?.bom ? 3 : 0),
71
+ tokens: doc?.binary ? null : estimateTokens(text),
72
+ eol: doc?.binary ? null : eolLabel(doc || {}),
73
+ bom: !!doc?.bom,
74
+ kind: fileKind(doc?.path || 'SKILL.md', { binary: !!doc?.binary }).kind,
75
+ guidance: null,
76
+ };
77
+ if (doc?.head && !doc?.binary) {
78
+ const body = bodyOf(text);
79
+ const bodyLines = body.text === '' ? 0 : body.text.split('\n').length;
80
+ const bodyTokens = estimateTokens(body.text);
81
+ facts.guidance = {
82
+ lines: bodyLines, tokens: bodyTokens, maxLines: BODY_GUIDANCE.lines, maxTokens: BODY_GUIDANCE.tokens,
83
+ ratio: Math.min(1, Math.max(bodyLines / BODY_GUIDANCE.lines, bodyTokens / BODY_GUIDANCE.tokens)),
84
+ over: bodyLines > BODY_GUIDANCE.lines || bodyTokens > BODY_GUIDANCE.tokens,
85
+ };
86
+ }
87
+ return facts;
88
+ }
89
+
90
+ /** The body of a document (after its frontmatter), with the line it starts on. */
91
+ export function bodyOf(text) {
92
+ const s = String(text ?? '').replace(/^/, '');
93
+ const m = /^---[ \t]*\n[\s\S]*?\n---[ \t]*(?:\n|$)/.exec(s);
94
+ if (!m) return { text: s, line: 1 };
95
+ return { text: s.slice(m[0].length), line: m[0].split('\n').length - (m[0].endsWith('\n') ? 1 : 0) + 1 };
96
+ }
97
+
98
+ // ── The description helper ──
99
+
100
+ /** The description budgets of each runtime: characters used and allowed. Claude Code lists description and when_to_use together. */
101
+ export function descriptionBudgets(data) {
102
+ const d = typeof data?.description === 'string' ? data.description : '';
103
+ const when = typeof data?.when_to_use === 'string' ? data.when_to_use : '';
104
+ const rows = [
105
+ { id: 'portable-six-field', runtime: 'portable', limit: PROFILES['portable-six-field'].limits.descriptionChars, used: d.length },
106
+ { id: 'claude-code', runtime: 'claude', limit: PROFILES['claude-code'].limits.listingChars, used: d.length + when.length, listing: true },
107
+ { id: 'codex-native', runtime: 'codex', limit: PROFILES['codex-native'].limits.descriptionChars, used: d.length },
108
+ { id: 'opencode-native', runtime: 'opencode', limit: PROFILES['opencode-native'].limits.descriptionChars, used: d.length },
109
+ ];
110
+ return rows.map((r) => ({ ...r, over: r.used > r.limit, ratio: r.limit ? Math.min(1, r.used / r.limit) : 0 }));
111
+ }
112
+
113
+ // Words that say when a skill applies (English and Portuguese), the cue a model reads to decide.
114
+ const WHEN_CUES = [
115
+ /\buse (?:it |this(?: skill)? |me )?(?:when|whenever|for|if|to)\b/i,
116
+ /\bwhen(?:ever)? (?:the user|you|a user|someone|asked|working|preparing|writing|creating|editing|reviewing)\b/i,
117
+ /\btriggers? (?:on|when)\b/i,
118
+ /\b(?:invoke|apply|run) (?:it |this )?(?:when|whenever|for)\b/i,
119
+ /\bquando (?:o usuário|a usuária|você|alguém|for|precisar|pedir)\b/i,
120
+ /\buse (?:quando|para|se)\b/i,
121
+ ];
122
+
123
+ /** Whether a description says when the skill applies (the cue a model reads to pick it). */
124
+ export const describesWhen = (text) => WHEN_CUES.some((re) => re.test(String(text ?? '')));
125
+
126
+ /** Whether a description says what the skill does: more than a few words of its own. */
127
+ export function describesWhat(text) {
128
+ const words = String(text ?? '').trim().split(/\s+/).filter((w) => /[\p{L}\p{N}]/u.test(w));
129
+ return words.length >= 4;
130
+ }
131
+
132
+ /**
133
+ * What the description helper says: the budgets, and whether "what" and "when" are there.
134
+ * @returns {{ budgets: object[], what: boolean, when: boolean, empty: boolean, advice: 'ok'|'add-when'|'add-what'|'write-one'|'too-long' }}
135
+ */
136
+ export function descriptionHelp(data) {
137
+ const text = typeof data?.description === 'string' ? data.description : '';
138
+ const budgets = descriptionBudgets(data);
139
+ const empty = !text.trim();
140
+ const what = !empty && describesWhat(text);
141
+ const when = !empty && (describesWhen(text) || (typeof data?.when_to_use === 'string' && data.when_to_use.trim() !== ''));
142
+ const advice = empty ? 'write-one' : budgets.some((b) => b.over) ? 'too-long' : !what ? 'add-what' : !when ? 'add-when' : 'ok';
143
+ return { budgets, what, when, empty, advice };
144
+ }
145
+
146
+ // ── Keys the form leaves alone ──
147
+
148
+ /**
149
+ * The frontmatter keys the form has no field for, each with the lines it is written on: the Advanced section
150
+ * lists them so it is plain they are kept, byte for byte.
151
+ * @param {'skill'|'agent'|'command'} type
152
+ * @param {object} parsed ss-frontmatter.js parse()
153
+ * @param {string} text the document ("\n" line breaks)
154
+ */
155
+ export function keptKeys(type, parsed, text) {
156
+ const known = new Set((FORM_FIELDS[type] || []).map((f) => f.key));
157
+ const keys = Array.isArray(parsed?.keys) ? parsed.keys : [];
158
+ const lines = String(text ?? '').split('\n');
159
+ const close = (() => {
160
+ if (!parsed?.hasFrontmatter) return 0;
161
+ for (let i = 1; i < lines.length; i++) if (/^---[ \t]*$/.test(lines[i])) return i + 1; // 1-based line of the closing ---
162
+ return lines.length;
163
+ })();
164
+ const out = [];
165
+ keys.forEach((k, i) => {
166
+ if (known.has(k.key)) return;
167
+ const from = k.line;
168
+ const to = (keys[i + 1]?.line ?? close) - 1;
169
+ const body = lines.slice(from - 1, Math.max(from, to)).join('\n').replace(/\s+$/, '');
170
+ out.push({ key: k.key, line: from, kind: k.kind, text: body.length > 600 ? `${body.slice(0, 600)}…` : body });
171
+ });
172
+ return out;
173
+ }
174
+
175
+ /** The frontmatter block as written (for the YAML view), or '' when there is none. */
176
+ export function frontmatterText(text) {
177
+ const s = String(text ?? '').replace(/^/, '');
178
+ const m = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*(?:\n|$)/.exec(s);
179
+ return m ? m[1] : '';
180
+ }
181
+
182
+ /** A line and column (1-based) of a diagnostic, kept inside the document. */
183
+ export function clampLine(line, text) {
184
+ const n = String(text ?? '').split('\n').length;
185
+ const l = Number.isInteger(line) && line > 0 ? line : 1;
186
+ return Math.min(l, n);
187
+ }
188
+
189
+ /** The short revision label of a digest (`sha256:abcdef…` → `abcdef1`), or ''. */
190
+ export function shortDigest(digest) {
191
+ const m = /^sha256:([0-9a-f]{7,})/.exec(String(digest ?? ''));
192
+ return m ? m[1].slice(0, 7) : '';
193
+ }
@@ -0,0 +1,22 @@
1
+ // Server failures are translated by code. Unknown codes remain useful diagnostics without leaking server prose.
2
+ export function studioErrorText(t, error) {
3
+ const code = error?.code || error?.body?.code;
4
+ const keys = {
5
+ WORKER_BROWSER: 'workerBrowser', GUEST_FORBIDDEN: 'owner', UI_ONLY: 'owner', OWNER_REQUIRED: 'owner',
6
+ WORKERS_NOT_PERMITTED: 'owner', ASSISTANT_CREDENTIAL_REQUIRED: 'owner',
7
+ PAGE_SECRET_REQUIRED: 'pageSession', UI_HEADER_REQUIRED: 'pageSession',
8
+ revision_conflict: 'conflict', draft_conflict: 'conflict', etag_conflict: 'conflict',
9
+ revision_not_found: 'missing', artifact_not_found: 'missing', not_found: 'missing',
10
+ scan_incomplete: 'scanIncomplete', scan_blocked: 'scanBlocked', export_blocked: 'exportBlocked',
11
+ catalog_incomplete: 'catalogIncomplete', source_changed: 'sourceChanged', unsafe_source: 'unsafeSource',
12
+ export_review_required: 'exportReviewRequired', invalid_name: 'invalidName', invalid_format: 'invalidFormat',
13
+ invalid_profile: 'invalidProfile', export_transform_failed: 'exportTransformFailed',
14
+ blob_missing: 'revisionDamaged', blob_corrupt: 'revisionDamaged', revision_corrupt: 'revisionDamaged',
15
+ export_incompatible: 'exportIncompatible', invalid_frontmatter: 'invalidFrontmatter',
16
+ source_disabled: 'sourceDisabled', source_unavailable: 'sourceUnavailable',
17
+ };
18
+ const body = error?.body || error;
19
+ if (code === 'export_blocked' && body?.reason === 'lint') return t('skillsStudio.errors.exportLint', { detail: (body.diagnostics || []).slice(0, 5).map((d) => [d.file, d.line, d.message].filter(Boolean).join(': ')).join('; ') });
20
+ if (keys[code]) return t(`skillsStudio.errors.${keys[code]}`);
21
+ return t('skillsStudio.errors.request', { code: typeof code === 'string' && /^[a-zA-Z0-9_-]{1,80}$/.test(code) ? code : 'network' });
22
+ }
@@ -0,0 +1,82 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: where the window is, and where a link takes it
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // The window opens where it was left (the shared panel system saves a moved or resized window, ui-panels.js), at
6
+ // min(1180px, 94% of the width) × min(760px, 86% of the height) the first time, and is kept inside the viewport
7
+ // below the menu bar. It can be docked: then it fills the work area beside the side panels (like the code editor)
8
+ // and leaves them usable. Its own width decides the layout (CSS container queries at 900 / 760 / 560 px):
9
+ // the rail folds to icons, the editor shows one pane at a time, the Library's rows become cards.
10
+ // A link into the Studio (`skills:open` with a detail) names a place, a skill, a review, a tab, a file and a line,
11
+ // or an AI drafting (a creation) to follow in Create.
12
+ // No DOM and no imports.
13
+
14
+ export const DEFAULT_SIZE = Object.freeze({ width: 1180, height: 760, widthShare: 0.94, heightShare: 0.86 });
15
+ export const MIN_SIZE = Object.freeze({ width: 380, height: 320 });
16
+ export const LAYOUTS = Object.freeze([{ name: 'compact', max: 560 }, { name: 'narrow', max: 760 }, { name: 'medium', max: 900 }]);
17
+ export const PLACES = Object.freeze(['library', 'review', 'create', 'discover', 'health']);
18
+ export const TABS = Object.freeze(['edit', 'test', 'versions', 'publish']);
19
+
20
+ const px = (v) => { const n = Number.parseFloat(v); return Number.isFinite(n) ? n : null; };
21
+
22
+ /**
23
+ * The window's first rectangle: what was saved (kept inside the viewport), else the default size, centred.
24
+ * `frame` is the app frame's rect when the page has one (app-frame.js frameRect()): the window stays inside it, up to
25
+ * its edges. Without it, the window below the menu bar.
26
+ * @param {{ vw: number, vh: number, navbar?: number, saved?: { left?, top?, width?, height? } | null,
27
+ * frame?: { left: number, top: number, right: number, bottom: number } | null }} o
28
+ */
29
+ export function initialRect({ vw, vh, navbar = 48, saved = null, frame = null }) {
30
+ const b = frame || { left: 0, top: navbar, right: vw, bottom: vh };
31
+ const bw = b.right - b.left, bh = b.bottom - b.top;
32
+ const room = frame
33
+ ? { width: Math.max(MIN_SIZE.width, bw), height: Math.max(MIN_SIZE.height, bh) }
34
+ : { width: Math.max(MIN_SIZE.width, vw - 20), height: Math.max(MIN_SIZE.height, vh - navbar - 10) };
35
+ let width = px(saved?.width) ?? Math.min(DEFAULT_SIZE.width, Math.round((frame ? bw : vw) * DEFAULT_SIZE.widthShare));
36
+ let height = px(saved?.height) ?? Math.min(DEFAULT_SIZE.height, Math.round((frame ? bh : vh) * DEFAULT_SIZE.heightShare));
37
+ width = Math.round(Math.max(MIN_SIZE.width, Math.min(width, room.width)));
38
+ height = Math.round(Math.max(MIN_SIZE.height, Math.min(height, room.height)));
39
+ let left = px(saved?.left);
40
+ let top = px(saved?.top);
41
+ if (left === null) left = Math.round(b.left + (bw - width) / 2);
42
+ if (top === null) top = Math.round(b.top + Math.max(0, (bh - height) / 2));
43
+ left = Math.round(Math.max(b.left, Math.min(left, b.right - width)));
44
+ top = Math.round(Math.max(b.top, Math.min(top, b.bottom - height)));
45
+ return { left, top, width, height };
46
+ }
47
+
48
+ /** The layout a width of the window gets (the same thresholds as the CSS container queries). */
49
+ export function layoutOf(width) {
50
+ for (const l of LAYOUTS) if (width <= l.max) return l.name;
51
+ return 'wide';
52
+ }
53
+
54
+ /**
55
+ * A link into the Studio, normalised: unknown values fall back, ids are kept only when they have their shape.
56
+ * @param {object} detail what `skills:open` carried
57
+ * @returns {{ place: string, tab: string|null, skillId: string|null, artifactId: string|null, reviewId: string|null,
58
+ * name: string|null, file: string|null, line: number|null, create: string|null, creationId: string|null }}
59
+ */
60
+ export function parseOpen(detail) {
61
+ const d = detail && typeof detail === 'object' ? detail : {};
62
+ const shaped = (v, re) => (typeof v === 'string' && re.test(v) ? v : null);
63
+ const out = {
64
+ place: PLACES.includes(d.place) ? d.place : null,
65
+ tab: TABS.includes(d.tab) ? d.tab : null,
66
+ skillId: shaped(d.skillId, /^skl_[0-9a-f]{20}$/),
67
+ artifactId: shaped(d.artifactId, /^art_[0-9a-f]{20}$/),
68
+ reviewId: shaped(d.reviewId, /^rvw_[0-9a-f]{20}$/),
69
+ name: typeof d.name === 'string' && d.name.length <= 128 && !/[\\/]/.test(d.name) ? d.name : null,
70
+ file: typeof d.file === 'string' && d.file.length <= 512 && !d.file.split('/').includes('..') && !d.file.startsWith('/') ? d.file : null,
71
+ line: Number.isInteger(d.line) && d.line > 0 ? d.line : null,
72
+ create: d.create === 'blank' || d.create === 'import' ? d.create : null,
73
+ creationId: shaped(d.creationId, /^crt_[0-9a-f]{20}$/),
74
+ };
75
+ if (!out.place) out.place = out.reviewId ? 'review' : (out.create || out.creationId) ? 'create' : 'library';
76
+ return out;
77
+ }
78
+
79
+ /** The keys of the Studio (also the help it shows): ⌘/Ctrl on a Mac or not. */
80
+ export function shortcutLabel(key, mac) {
81
+ return key.replace(/Mod/g, mac ? '⌘' : 'Ctrl').replace(/Alt/g, mac ? '⌥' : 'Alt').replace(/Shift/g, mac ? '⇧' : 'Shift').replace(/Enter/g, mac ? '↵' : 'Enter').replace(/\+/g, mac ? '' : '+');
82
+ }
@@ -0,0 +1,262 @@
1
+ // ═══════════════════════════════════════════
2
+ // SynaBun Neural Interface — Skills Studio 2.0: importing a bundle
3
+ // ═══════════════════════════════════════════
4
+ //
5
+ // A bundle is checked and staged by the server first, outside every folder a runtime reads skills from (nothing of
6
+ // it runs or loads there), and scanned. The owner sees what it is and what it can do, then installs exactly those
7
+ // bytes (the staged digest), or discards it. The scope is the one they pick: global, or a project they choose;
8
+ // never a default. Files already there are replaced only when the owner ticks the box (a backup is kept). A file
9
+ // is picked in the click that asked for it (the browser's rule); what the server can do is asked once it is chosen.
10
+
11
+ import { studioErrorText } from './sx-errors.js';
12
+ import { dropdownHTML, wireDropdowns } from '../../cc-dropdown.js';
13
+ import { revealHidden } from '../ss-unicode.js';
14
+ import { decodeArtifactId } from '../ss-ids.js';
15
+
16
+ const SEVERITY_MARK = { critical: '⛔', high: '✕', medium: '⚠', low: '•', info: '•' };
17
+ const MAX_BUNDLE_BYTES = 24 * 1024 * 1024;
18
+
19
+ const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text !== undefined) n.textContent = text; return n; };
20
+
21
+ /**
22
+ * Pick a bundle and import it.
23
+ * @param {object} ctx the Studio's context
24
+ * @param {() => Promise<Set<string>>} capabilities what the server can do, once known
25
+ */
26
+ export function triggerImport(ctx, capabilities) {
27
+ if (ctx.guest) { ctx.toast(ctx.t('skillsStudio.import.guest'), 'error'); return; }
28
+ const input = document.createElement('input');
29
+ input.type = 'file';
30
+ input.accept = BUNDLE_ACCEPT;
31
+ input.addEventListener('change', () => { const file = input.files?.[0]; if (file) importFile(ctx, file, capabilities); });
32
+ input.click();
33
+ }
34
+
35
+ /** The file types the picker offers. */
36
+ export const BUNDLE_ACCEPT = '.json,.skill.json,.command.json,.agent.json';
37
+
38
+ /** A bundle file someone picked (here, or from the menu before the Studio was loaded): read, checked, imported. */
39
+ export async function importFile(ctx, file, capabilities) {
40
+ const { t } = ctx;
41
+ if (ctx.guest) { ctx.toast(t('skillsStudio.import.guest'), 'error'); return; }
42
+ try { await capabilities(); } catch { ctx.toast(t('skillsStudio.import.serverUnknown'), 'error'); return; }
43
+ if (file.size > MAX_BUNDLE_BYTES) { ctx.toast(t('skillsStudio.import.tooLarge'), 'error'); return; }
44
+ let bundle;
45
+ try { bundle = JSON.parse(await file.text()); } catch { ctx.toast(t('skillsStudio.import.notJson'), 'error'); return; }
46
+ if (!bundle || bundle.format !== 'synabun-skill-bundle') { ctx.toast(t('skillsStudio.import.notBundle'), 'error'); return; }
47
+ if (ctx.panel?.isConnected) await runImport(ctx, bundle, file.name);
48
+ }
49
+
50
+ function scopePicker(ctx, prefix) {
51
+ const { t } = ctx;
52
+ const projects = [{ value: '', label: t('skillsStudio.import.chooseProject'), disabled: true, selected: true }, ...ctx.projects.map((p) => ({ value: p.path, label: p.label }))];
53
+ const wrap = el('div', 'sx-dlg-form');
54
+ const group = el('div', 'sx-choices');
55
+ group.setAttribute('role', 'radiogroup');
56
+ group.setAttribute('aria-label', t('skillsStudio.import.where'));
57
+ for (const [value, words] of [['global', t('skillsStudio.import.scopeGlobal')], ['project', t('skillsStudio.import.scopeProject')]]) {
58
+ const label = el('label', 'sx-choice');
59
+ const radio = el('input');
60
+ radio.type = 'radio';
61
+ radio.name = `${prefix}-scope`;
62
+ radio.value = value;
63
+ if (value === 'global') radio.checked = true;
64
+ if (value === 'project' && !ctx.projects.length) radio.disabled = true;
65
+ label.append(radio, document.createTextNode(` ${words}`));
66
+ group.append(label);
67
+ }
68
+ const field = el('div', 'sx-field');
69
+ field.hidden = true;
70
+ field.id = `${prefix}-project-field`;
71
+ const fieldLabel = el('span', 'sx-field-label', t('skillsStudio.import.project'));
72
+ fieldLabel.id = `${prefix}-project-label`;
73
+ const dd = el('div');
74
+ dd.innerHTML = dropdownHTML(`${prefix}-project`, projects, { labelledBy: `${prefix}-project-label` });
75
+ field.append(fieldLabel, ...dd.childNodes);
76
+ const error = el('p', 'ss-dlg-error');
77
+ error.id = `${prefix}-error`;
78
+ error.setAttribute('role', 'alert');
79
+ wrap.append(group, field, error);
80
+ return wrap;
81
+ }
82
+
83
+ /** Where it goes, staged and scanned, the owner's review, then exactly those bytes installed (or nothing). */
84
+ export async function runImport(ctx, bundle, fileName) {
85
+ const { t, tp, api } = ctx;
86
+ const type = ['skill', 'command', 'agent'].includes(bundle.type) ? bundle.type : 'skill';
87
+ const files = bundle.files && typeof bundle.files === 'object' ? Object.keys(bundle.files).length : 0;
88
+ // 1. Where it goes.
89
+ const life = new AbortController();
90
+ const extra = scopePicker(ctx, 'ss-imp');
91
+ wireDropdowns(extra, { signal: life.signal });
92
+ extra.addEventListener('change', (e) => { if (e.target.name === 'ss-imp-scope') extra.querySelector('#ss-imp-project-field').hidden = e.target.value !== 'project'; }, { signal: life.signal });
93
+ let place = null;
94
+ const where = await ctx.ask({
95
+ title: t('skillsStudio.import.title', { kind: t(`skillsStudio.kind.${type}`), name: revealHidden(String(bundle.name ?? '')) }),
96
+ lines: [tp('skillsStudio.import.intro', files, { file: fileName })],
97
+ extra,
98
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.cancel') }, { id: 'check', label: t('skillsStudio.import.check'), kind: 'primary' }],
99
+ accept: () => {
100
+ const scope = extra.querySelector('input[name="ss-imp-scope"]:checked')?.value || 'global';
101
+ const projectPath = scope === 'project' ? (extra.querySelector('#ss-imp-project')?.value || '') : '';
102
+ const error = scope === 'project' && !projectPath ? t('skillsStudio.import.chooseProjectError') : '';
103
+ extra.querySelector('#ss-imp-error').textContent = error;
104
+ if (error) return false;
105
+ place = { scope, projectPath };
106
+ return true;
107
+ },
108
+ });
109
+ life.abort();
110
+ if (where !== 'check' || !place || !ctx.panel?.isConnected) return;
111
+
112
+ // 2. Staged and scanned by the server.
113
+ let staged;
114
+ try {
115
+ staged = (await api.stageSkillsImport(bundle, place.scope, place.projectPath)).staged;
116
+ } catch (err) {
117
+ const problems = Array.isArray(err?.body?.problems) ? err.body.problems : [];
118
+ if (problems.length && ctx.panel?.isConnected) {
119
+ await ctx.ask({
120
+ title: t('skillsStudio.import.refusedTitle'),
121
+ lines: [t('skillsStudio.import.refusedIntro'), ...problems.slice(0, 12).map((p) => `• ${p.path ? `${revealHidden(p.path)}: ` : ''}${p.message}`), problems.length > 12 ? `• ${t('skillsStudio.dialog.andMore', { count: problems.length - 12 })}` : ''],
122
+ buttons: [{ id: 'cancel', label: t('skillsStudio.dialog.close'), kind: 'primary' }],
123
+ });
124
+ } else ctx.toast(t('skillsStudio.import.failed', { error: studioErrorText(ctx.t, err) }), 'error');
125
+ return;
126
+ }
127
+ if (!ctx.panel?.isConnected) { api.discardSkillsImport(staged.stagingId).catch(() => {}); return; }
128
+
129
+ // 3. The review: what it is, what it can do, what it would replace.
130
+ const review = buildImportReview(ctx, staged);
131
+ const lintErrors = staged.lint?.errors || 0;
132
+ const skillExists = staged.type === 'skill' && staged.collisions?.length > 0;
133
+ const blocked = staged.scan?.gate === 'blocked';
134
+ const answer = await ctx.ask({
135
+ wide: true,
136
+ title: t('skillsStudio.import.reviewTitle', { name: revealHidden(staged.name) }),
137
+ lines: [],
138
+ extra: review,
139
+ buttons: [{ id: 'discard', label: t('skillsStudio.import.discard'), kind: 'danger' }, { id: 'install', label: t('skillsStudio.import.install'), kind: 'primary', disabled: !!(lintErrors || skillExists || blocked) }],
140
+ focus: 'discard',
141
+ cancelId: 'discard',
142
+ accept: (choice) => {
143
+ if (choice !== 'install') return true;
144
+ const ack = review.querySelector('#ss-imp-ack');
145
+ const over = review.querySelector('#ss-imp-overwrite');
146
+ const error = ack && !ack.checked ? t('skillsStudio.import.confirmReviewed') : over && !over.checked ? t('skillsStudio.import.confirmReplace') : '';
147
+ const line = review.querySelector('#ss-imp-review-error');
148
+ if (line) line.textContent = error;
149
+ return !error;
150
+ },
151
+ });
152
+ if (answer !== 'install') { api.discardSkillsImport(staged.stagingId).catch(() => {}); return; }
153
+
154
+ // 4. Installed: exactly the staged bytes, or nothing.
155
+ try {
156
+ const res = await api.commitSkillsImport({
157
+ stagingId: staged.stagingId, expectedDigest: staged.digest,
158
+ acknowledgedDigest: staged.scan?.gate === 'review' ? staged.digest : undefined,
159
+ overwrite: !!review.querySelector('#ss-imp-overwrite')?.checked,
160
+ });
161
+ ctx.toast(t('skillsStudio.import.done', { name: res.name }));
162
+ await ctx.reload();
163
+ const id = decodeArtifactId(res.id);
164
+ const path = id.slice(id.indexOf(':') + 1);
165
+ const row = ctx.rows.find((r) => r.entries.some((e) => e.path === path));
166
+ if (row && ctx.panel?.isConnected) ctx.openRow(row.key, { tab: 'edit' });
167
+ } catch (err) {
168
+ ctx.toast(t('skillsStudio.import.failed', { error: studioErrorText(ctx.t, err) }), 'error');
169
+ }
170
+ }
171
+
172
+ /** The review of a staged import, built as text nodes: nothing of the bundle is ever markup here. */
173
+ export function buildImportReview(ctx, staged) {
174
+ const { t, tp } = ctx;
175
+ const root = el('div', 'ss-review');
176
+ const row = (label, value, mono = false) => {
177
+ const r = el('div', 'ss-review-row');
178
+ r.append(el('span', 'ss-review-label', label), el(mono ? 'code' : 'span', 'ss-review-value', value));
179
+ root.append(r);
180
+ };
181
+ const size = (staged.files || []).reduce((n, f) => n + (Number(f.size) || 0), 0);
182
+ row(t('skillsStudio.import.goesTo'), staged.destination, true);
183
+ row(t('skillsStudio.import.files'), tp('skillsStudio.import.fileCount', staged.files?.length || 0, { size: size > 1024 ? `${Math.round(size / 1024)} KB` : `${size} B` }));
184
+ row(t('skillsStudio.import.digest'), staged.digest ? `${staged.digest.slice(0, 19)}…${staged.digest.slice(-6)}` : '', true);
185
+ const fileList = el('details', 'ss-review-files');
186
+ fileList.append(el('summary', '', t('skillsStudio.import.showFiles')));
187
+ const ul = el('ul');
188
+ for (const f of staged.files || []) ul.append(el('li', '', `${revealHidden(f.path)}${f.executable ? ` (${t('skillsStudio.import.executable')})` : ''}`));
189
+ for (const x of staged.excluded || []) ul.append(el('li', 'ss-review-muted', t('skillsStudio.import.leftOut', { path: revealHidden(x.path), reason: x.reason })));
190
+ fileList.append(ul);
191
+ root.append(fileList);
192
+
193
+ if (staged.type === 'skill' && staged.collisions?.length) root.append(el('p', 'ss-review-warn', t('skillsStudio.import.skillExists', { name: staged.name })));
194
+ if (staged.type !== 'skill' && staged.collisions?.length) {
195
+ const label = el('label', 'ss-review-check');
196
+ const box = el('input');
197
+ box.type = 'checkbox';
198
+ box.id = 'ss-imp-overwrite';
199
+ label.append(box, document.createTextNode(` ${tp('skillsStudio.import.replaceBox', staged.collisions.length, { files: staged.collisions.join(', ') })}`));
200
+ root.append(label);
201
+ }
202
+
203
+ // Checks of the head file
204
+ const lint = staged.lint || { diagnostics: [] };
205
+ const shownLint = (lint.diagnostics || []).filter((d) => d.severity !== 'info');
206
+ const checks = el('div', 'ss-review-section');
207
+ checks.append(el('div', 'ss-review-head', shownLint.length ? t('skillsStudio.import.checks', { errors: lint.errors || 0, warnings: lint.warnings || 0 }) : t('skillsStudio.import.checksOk')));
208
+ if (shownLint.length) {
209
+ const list = el('ul', 'ss-review-list');
210
+ for (const d of shownLint.slice(0, 20)) list.append(el('li', `ss-review-item ss-review-item--${d.severity === 'error' ? 'high' : 'medium'}`, `${d.message}${d.line ? ` (${t('skillsStudio.edit.line', { line: d.line })})` : ''}`));
211
+ checks.append(list);
212
+ }
213
+ if (lint.errors) checks.append(el('p', 'ss-review-warn', t('skillsStudio.import.blockedByProblems')));
214
+ root.append(checks);
215
+
216
+ // What it can do
217
+ const scan = staged.scan || {};
218
+ const auth = scan.authority || {};
219
+ const section = el('div', 'ss-review-section');
220
+ const found = scan.findings?.length || 0;
221
+ const status = scan.status === 'no_findings' ? t('skillsStudio.import.scanClean')
222
+ : scan.status === 'error' ? t('skillsStudio.import.scanError', { error: scan.error || 'error' })
223
+ : scan.status === 'incomplete' ? tp('skillsStudio.import.scanIncomplete', found)
224
+ : tp('skillsStudio.import.scanFindings', found);
225
+ section.append(el('div', 'ss-review-head', `${t('skillsStudio.import.canDo')} ${status}${scan.gate === 'blocked' ? ` (${t('skillsStudio.import.gateBlocked')})` : scan.gate === 'review' ? ` (${t('skillsStudio.import.gateReview')})` : ''}`));
226
+ const facts = [];
227
+ if (auth.dynamicContext?.length) facts.push(tp('skillsStudio.import.fact.dynamic', auth.dynamicContext.length));
228
+ if (auth.hooks?.length) facts.push(t('skillsStudio.import.fact.hooks', { events: auth.hooks.map((h) => h.event).join(', ') }));
229
+ if (auth.toolGrants?.length) facts.push(t('skillsStudio.import.fact.tools', { tools: auth.toolGrants.map((g) => g.tool).join(', ') }));
230
+ if (auth.scripts?.length) facts.push(tp('skillsStudio.import.fact.scripts', auth.scripts.length));
231
+ if (auth.binaries?.length) facts.push(tp('skillsStudio.import.fact.binaries', auth.binaries.length));
232
+ if (auth.domains?.length) facts.push(t('skillsStudio.import.fact.domains', { domains: auth.domains.slice(0, 6).join(', ') + (auth.domains.length > 6 ? ` +${auth.domains.length - 6}` : '') }));
233
+ if (facts.length) { const ul2 = el('ul', 'ss-review-facts'); for (const f of facts) ul2.append(el('li', '', f)); section.append(ul2); }
234
+ if (scan.findings?.length) {
235
+ const list = el('ul', 'ss-review-list');
236
+ for (const f of scan.findings.slice(0, 30)) {
237
+ const li = el('li', `ss-review-item ss-review-item--${f.severity}`);
238
+ li.append(el('span', 'ss-review-sev', `${SEVERITY_MARK[f.severity] || '•'} ${t(`skillsStudio.review.severity.${['critical', 'high', 'medium', 'low'].includes(f.severity) ? f.severity : 'low'}`)}`), el('span', '', ` ${f.message}`));
239
+ if (f.file) li.append(el('span', 'ss-review-where', ` ${revealHidden(f.file)}${f.line ? `:${f.line}` : ''}`));
240
+ if (f.evidence) li.append(el('code', 'ss-review-evidence', f.evidence));
241
+ list.append(li);
242
+ }
243
+ if (scan.findings.length > 30) list.append(el('li', 'ss-review-muted', t('skillsStudio.dialog.andMore', { count: scan.findings.length - 30 })));
244
+ section.append(list);
245
+ }
246
+ if (scan.disclosure) section.append(el('p', 'ss-review-muted', scan.disclosure));
247
+ root.append(section);
248
+
249
+ if (scan.gate === 'review') {
250
+ const label = el('label', 'ss-review-check');
251
+ const box = el('input');
252
+ box.type = 'checkbox';
253
+ box.id = 'ss-imp-ack';
254
+ label.append(box, document.createTextNode(` ${t('skillsStudio.import.reviewedBox')}`));
255
+ root.append(label);
256
+ }
257
+ const err = el('p', 'ss-dlg-error', '');
258
+ err.id = 'ss-imp-review-error';
259
+ err.setAttribute('role', 'alert');
260
+ root.append(err);
261
+ return root;
262
+ }