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,364 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { isEditableText } from '../../public/shared/skills/ss-doc.js';
3
+ import { ASSET_EXT, inspectAsset, assetBudget, scriptMarkers } from './scan-assets.js';
4
+ import { shellFindings, shellReviewFindings, looksLikeShell } from './scan-shell.js';
5
+ // ── What a skill can do, read without running anything ──
6
+ // A local, deterministic look at every file of a skill before it is installed: what it would run and when
7
+ // (scripts, Claude Code dynamic context, hooks that stay for the session, tool grants), what it reaches (URLs,
8
+ // sensitive paths, credentials), and what hides from a reader (invisible characters, look-alike names). Nothing
9
+ // is executed, fetched or sent anywhere; no configuration of the skill's own is honoured; time and bytes are
10
+ // bounded.
11
+ //
12
+ // The result is a record bound to the digest of the exact bytes scanned. Its `status` is `no_findings`,
13
+ // `findings`, `error` (the scan failed: never a pass) or `incomplete` (part of the skill could not be read:
14
+ // never a pass either), and its `gate` is what an install may do: `allowed`, `review` (a person confirms the
15
+ // findings for this digest) or `blocked`. A finding is evidence of capability, not a verdict of malice: a backup
16
+ // skill legitimately reads files and uploads them; it is shown so the person installing it knows.
17
+
18
+ import { redactSecrets } from '../../../lib/log-redact.js';
19
+ import { findHiddenChars, inspectName, revealHidden } from '../../public/shared/skills/ss-unicode.js';
20
+ const utf8 = new TextDecoder('utf-8', { fatal: true });
21
+ function isScannableText(bytes) {
22
+ if (bytes.includes(0)) return false;
23
+ try { utf8.decode(bytes); return true; } catch { return false; }
24
+ }
25
+ function colourSGR(text, at) {
26
+ const match = /^\x1b\[([0-9;:]*)m/.exec(text.slice(at, at + 100));
27
+ if (!match) return false;
28
+ const codes = match[1].replace(/::/g, ':').split(/[;:]/).map((v) => v === '' ? 0 : Number(v));
29
+ for (let i = 0; i < codes.length; i++) {
30
+ const n = codes[i];
31
+ if ([38, 48, 58].includes(n)) {
32
+ const mode = codes[++i], count = mode === 5 ? 1 : mode === 2 ? 3 : -1;
33
+ if (count < 0 || i + count >= codes.length) return false;
34
+ for (let k = 0; k < count; k++) if (!(codes[++i] >= 0 && codes[i] <= 255)) return false;
35
+ } else if (![0, 1, 2, 3, 4, 5, 7, 9, 21, 22, 23, 24, 25, 27, 28, 29, 39, 49, 59].includes(n) && !(n >= 30 && n <= 37 || n >= 40 && n <= 47 || n >= 90 && n <= 97 || n >= 100 && n <= 107)) return false;
36
+ }
37
+ return true;
38
+ }
39
+
40
+ export const SCAN_ENGINE = 'synabun-static';
41
+ export const SCAN_ENGINE_VERSION = '1.7.5';
42
+ // skills-p1: typosquats of a shipped skill's name (S-13), calibrated on tests/fixtures/skills-scan/corpus.json;
43
+ // 1.2.0: a name that is exactly a shipped skill's, on something that is not that skill (S-13-shipped-name).
44
+ export const SCAN_POLICY = 'skills-p5-review-2026-10-27';
45
+ export const SCAN_LIMITS = Object.freeze({ maxFileChars: 2 * 1024 * 1024, maxTotalChars: 16 * 1024 * 1024, maxLineChars: 20000, maxFindings: 400 });
46
+
47
+ const SEVERITY_RANK = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
48
+
49
+ const SCRIPT_EXT = /\.(?:sh|bash|zsh|fish|ps1|psm1|bat|cmd|py|js|mjs|cjs|ts|rb|pl|php|lua|applescript|scpt)$/i;
50
+ const INSTRUCTION_EXT = /\.(?:md|markdown|txt|mdx)$/i;
51
+ const MAGIC = [
52
+ [[0x7F, 0x45, 0x4C, 0x46], 'ELF program'],
53
+ [[0xCF, 0xFA, 0xED, 0xFE], 'Mach-O program'], [[0xCE, 0xFA, 0xED, 0xFE], 'Mach-O program'],
54
+ [[0xCA, 0xFE, 0xBA, 0xBE], 'Mach-O universal program'],
55
+ [[0x4D, 0x5A], 'Windows program'],
56
+ [[0x50, 0x4B, 0x03, 0x04], 'ZIP archive'], [[0x1F, 0x8B], 'gzip archive'], [[0x37, 0x7A, 0xBC, 0xAF], '7z archive'],
57
+ [[0x00, 0x61, 0x73, 0x6D], 'WebAssembly module'],
58
+ ];
59
+
60
+ // Each rule: id, severity, a pattern tested on one line, the message, what to do. Patterns are linear (no nested
61
+ // quantifiers over overlapping classes) and run on lines cut at maxLineChars.
62
+ const LINE_RULES = [
63
+ { id: 'S-08-remote-pipe', severity: 'high', re: /\b(?:curl|wget|fetch|iwr|irm|Invoke-WebRequest|Invoke-RestMethod)\b[^|\n]{0,400}\|\s*(?:sudo\s+)?(?:ba|z|da|k|fi)?sh\b|\b(?:curl|wget)\b[^|\n]{0,400}\|\s*(?:sudo\s+)?(?:python3?|node|perl|ruby|iex|Invoke-Expression)\b/i,
64
+ message: 'Downloads a script and runs it in one step.', remediation: 'Pin what is downloaded (a version, a checksum) and show it before it runs, or drop the step.' },
65
+ { id: 'S-08-remote-subshell', severity: 'high', re: /\b(?:ba|z)?sh\s+(?:-c\s+)?["']?\$?\(\s*(?:curl|wget)\b|\bsource\s+<\(\s*(?:curl|wget)\b|\.\s+<\(\s*(?:curl|wget)\b/i,
66
+ message: 'Runs a script fetched from the network.', remediation: 'Ship the script with the skill, or pin and verify it.' },
67
+ { id: 'S-08-installer', severity: 'medium', re: /\b(?:npm\s+(?:i|install)\s+(?:-g|--global)\b|npx\s+\S|pnpm\s+dlx\b|pip3?\s+install\b|pipx\s+install\b|brew\s+install\b|gem\s+install\b|cargo\s+install\b|go\s+install\b|apt(?:-get)?\s+install\b)/i,
68
+ message: 'Installs software on the machine that runs it.', remediation: 'Say which package and version, and why; a reviewer decides.' },
69
+ { id: 'S-09-decode-exec', severity: 'high', re: /\bbase64\s+(?:-d|--decode|-D)\b[^\n]{0,200}\|\s*(?:sudo\s+)?(?:ba|z)?sh\b|\becho\s+[A-Za-z0-9+/=]{24,}\s*\|\s*base64\b|\b(?:eval|exec)\s*\(\s*(?:atob|Buffer\.from|base64\.b64decode|bytes\.fromhex|codecs\.decode)\b|FromBase64String\s*\([^)]{0,200}\)[^\n]{0,200}(?:Invoke-Expression|iex)\b|powershell(?:\.exe)?\s+[^\n]{0,80}-(?:e|en|enc|encodedcommand)\s+[A-Za-z0-9+/=]{16,}/i,
70
+ message: 'Decodes hidden text and runs it.', remediation: 'Write the command out in plain text, or remove it.' },
71
+ { id: 'S-09-eval', severity: 'low', re: /\beval\s+["'`$(]|\beval\s*\(/,
72
+ message: 'Builds a command at run time and evaluates it.', remediation: 'Prefer fixed commands a reader can check.' },
73
+ { id: 'S-10-sensitive-path', severity: 'medium', re: /(?:~|\$HOME|%USERPROFILE%|\/Users\/[^/\s]+|\/home\/[^/\s]+)\/(?:\.ssh|\.aws\/credentials|\.netrc|\.gnupg|\.kube\/config|\.docker\/config\.json|\.config\/gcloud|Library\/Keychains|Library\/Cookies|Library\/Application Support\/(?:Google\/Chrome|BraveSoftware|Firefox|Microsoft Edge|com\.apple\.TCC))|\bid_(?:rsa|ed25519|ecdsa)\b|\blogin\.keychain|\bsecurity\s+find-(?:generic|internet)-password\b|\/etc\/(?:shadow|sudoers)\b|\bLogin Data\b/i,
74
+ message: 'Reads credentials or other private data of the user.', remediation: 'Say why it needs them; a reviewer decides.' },
75
+ { id: 'S-05-authority-spoof', severity: 'medium', re: /\bignore\s+(?:all\s+)?(?:the\s+)?(?:previous|prior|above|earlier)\s+(?:instructions|rules|prompts?)\b|\byou\s+are\s+now\s+(?:in\s+)?(?:developer|dan|god|jailbreak|unrestricted)\b|<\/?\s*system\s*>|\[\s*system\s*\]\s*:|\bdo\s+not\s+(?:tell|inform|show|mention\s+(?:this\s+)?to)\s+the\s+user\b|\bwithout\s+(?:asking|telling|informing)\s+the\s+user\b|\b(?:disable|bypass|turn\s+off|ignore)\s+(?:the\s+)?(?:safety|security|permission|sandbox|approval)\s+(?:checks?|prompts?|rules?|settings?|mode)\b/i,
76
+ message: 'Text that tries to override instructions, hide what it does, or switch off a safeguard.', remediation: 'Remove it, or make sure it is quoted as an example and not an instruction.' },
77
+ { id: 'S-07-persistent-write', severity: 'high', re: /(?:>>?\s*|\btee\s+(?:-a\s+)?|\bcp\s+[^\n]{0,200}\s|\bmv\s+[^\n]{0,200}\s|\bwrite(?:File)?\w*\s*\([^)]{0,200})["']?(?:~|\$HOME)?\/?[^\s"']{0,300}(?:\.bashrc|\.zshrc|\.bash_profile|\.profile|\.zprofile|\.config\/fish\/config\.fish|crontab|LaunchAgents\/|\.claude\/settings(?:\.local)?\.json|\.claude\/CLAUDE\.md|CLAUDE\.md|AGENTS\.md|\.codex\/config\.toml|\.mcp\.json|\.gitconfig)\b|\bcrontab\s+-[^l\s]|\blaunchctl\s+(?:load|bootstrap)\b/i,
78
+ message: 'Changes configuration that lasts beyond this task (shell start-up files, scheduled jobs, agent settings or instructions).', remediation: 'Say what it changes and why; a reviewer decides.' },
79
+ ];
80
+
81
+ const NETWORK_SINK = /\bcurl\b[^\n]{0,300}\s(?:-d|--data(?:-binary|-raw|-urlencode)?|-F|--form|-T|--upload-file)\b|\bwget\b[^\n]{0,300}--post-(?:data|file)\b|\b(?:nc|ncat|netcat)\s+[^\n]{0,100}\d+|\bscp\s+[^\n]{0,300}@|\brsync\s+[^\n]{0,300}@[^\s:]+:|Invoke-(?:WebRequest|RestMethod)\b[^\n]{0,300}-Method\s+(?:Post|Put)\b|\brequests\.(?:post|put)\s*\(|\bfetch\s*\([^)]{0,300}method\s*:\s*["'](?:POST|PUT)["']/i;
82
+ const SECRET_ENV = /\$\{?(?:[A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_?KEY|PRIVATE_?KEY|ACCESS_?KEY|CREDENTIALS?)[A-Z0-9_]*)\}?|\bprocess\.env\.[A-Z0-9_]*(?:TOKEN|SECRET|KEY|PASSWORD)\b|\bos\.environ\b/;
83
+ const URL_RE = /\bhttps?:\/\/[^\s<>"'`)\]]{1,300}/gi;
84
+
85
+ // Credentials written into a file: the value is never repeated in a finding.
86
+ const SECRET_PATTERNS = [
87
+ ['AWS access key', /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/],
88
+ ['GitHub token', /\bgh[pousr]_[A-Za-z0-9]{36,}\b|\bgithub_pat_[A-Za-z0-9_]{60,}\b/],
89
+ ['Slack token', /\bxox[abposr]-[A-Za-z0-9-]{10,}\b/],
90
+ ['Anthropic API key', /\bsk-ant-[A-Za-z0-9_-]{20,}\b/],
91
+ ['OpenAI API key', /\bsk-(?:proj-)?[A-Za-z0-9_-]{32,}\b/],
92
+ ['Google API key', /\bAIza[0-9A-Za-z_-]{35}\b/],
93
+ ['Stripe key', /\b(?:sk|rk)_live_[0-9A-Za-z]{20,}\b/],
94
+ ['private key', /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY(?: BLOCK)?-----/],
95
+ ['credential', /\b(?:api[_-]?key|secret|token|passwd|password|auth)\s*[:=]\s*["'][A-Za-z0-9_\-+/=.]{16,}["']/i],
96
+ ];
97
+
98
+ const clip = (s, n = 160) => (s.length > n ? `${s.slice(0, n - 1)}…` : s);
99
+
100
+ /** Edits (insert, delete, substitute, swap two neighbours) from one name to another, stopping above `max`. */
101
+ export function nameDistance(a, b, max = 3) {
102
+ const s = String(a); const t = String(b);
103
+ if (Math.abs(s.length - t.length) > max) return max + 1;
104
+ const d = Array.from({ length: s.length + 1 }, (_, i) => [i, ...Array(t.length).fill(0)]);
105
+ for (let j = 1; j <= t.length; j++) d[0][j] = j;
106
+ for (let i = 1; i <= s.length; i++) {
107
+ for (let j = 1; j <= t.length; j++) {
108
+ const cost = s[i - 1] === t[j - 1] ? 0 : 1;
109
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
110
+ if (i > 1 && j > 1 && s[i - 1] === t[j - 2] && s[i - 2] === t[j - 1]) d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
111
+ }
112
+ }
113
+ return d[s.length][t.length];
114
+ }
115
+
116
+ /** A known name this one is a slip away from (one edit; two for long names), or null. */
117
+ export function typosquatOf(name, known = []) {
118
+ const plain = String(name ?? '').toLowerCase();
119
+ if (plain.length < 4) return null;
120
+ for (const k of known || []) {
121
+ const ref = String(k ?? '').toLowerCase();
122
+ if (!ref || ref === plain) continue;
123
+ const limit = Math.min(ref.length, plain.length) >= 8 ? 2 : 1;
124
+ if (nameDistance(plain, ref, limit) <= limit) return k;
125
+ }
126
+ return null;
127
+ }
128
+ const evidenceOf = (line) => clip(revealHidden(String(line).trim(), { identifier: false }).replace(/[\u0000-\u0008\u000B-\u001F\u007F]/g, ' '));
129
+
130
+ function hostOf(url) {
131
+ try { return new URL(url).host.toLowerCase(); } catch { return null; }
132
+ }
133
+
134
+ /**
135
+ * Scan the files of one artifact.
136
+ * @param {object} o
137
+ * @param {'skill'|'command'|'agent'} o.type
138
+ * @param {string} o.name the folder (skill) or file name (command, agent) it would be installed under
139
+ * @param {{ path: string, bytes: Buffer, executable?: boolean }[]} o.files
140
+ * @param {object|null} [o.frontmatter] the head file's parsed metadata (for hooks and tool grants)
141
+ * @param {string} o.digest the digest of exactly these files
142
+ * @param {string[]} [o.knownNames] names it must not pass for (the bundled skills)
143
+ * @param {string[]} [o.reservedNames] the shipped names this artifact is not: under one of them it would replace or
144
+ * shadow SynaBun's own skill (the caller leaves out the artifact's own name when it is that shipped skill)
145
+ */
146
+ export function scanArtifact({ type, name, files, frontmatter = null, digest, knownNames = [], reservedNames = [] }) {
147
+ const findings = [];
148
+ const authority = { scripts: [], binaries: [], dynamicContext: [], hooks: [], toolGrants: [], domains: [], sensitive: [], persistentWrites: [], installers: [] };
149
+ const coverage = { scanned: 0, skipped: [], complete: true };
150
+ const add = (finding) => {
151
+ if (findings.length < SCAN_LIMITS.maxFindings) findings.push(finding);
152
+ else {
153
+ coverage.complete = false;
154
+ // A flood of lower-severity evidence must not hide a later high-severity class.
155
+ const lower = findings.findIndex((f) => SEVERITY_RANK[f.severity] < SEVERITY_RANK[finding.severity]);
156
+ if (lower >= 0) findings[lower] = finding;
157
+ }
158
+ };
159
+ const shellFiles = [];
160
+ const decompression = assetBudget();
161
+ const hiddenGroups = new Map();
162
+ try {
163
+ // The name it would be installed under.
164
+ const look = inspectName(name, knownNames);
165
+ if (look.hidden.length) add({ ruleId: 'S-12-hidden-name', severity: 'critical', file: null, line: null, message: `The name holds characters nobody can see (${look.hidden.map((h) => h.label).join(', ')}).`, evidence: revealHidden(name), remediation: 'Rename it with visible characters only.' });
166
+ if (look.confusable || look.scripts.length > 1) add({ ruleId: 'S-13-confusable-name', severity: 'medium', file: null, line: null, message: `The name mixes scripts or uses letters that look Latin (reads as "${look.skeleton}").`, evidence: revealHidden(name), remediation: 'Use a plain ASCII name.' });
167
+ if (look.impersonates) add({ ruleId: 'S-13-impersonation', severity: 'high', file: null, line: null, message: `The name looks like "${look.impersonates}", which SynaBun ships, but is not it.`, evidence: revealHidden(name), remediation: 'Rename it so it cannot pass for another skill.' });
168
+ const squat = look.impersonates ? null : typosquatOf(name, knownNames);
169
+ if (squat) add({ ruleId: 'S-13-impersonation', severity: 'high', file: null, line: null, message: `The name is a slip away from "${squat}", which SynaBun ships: it could be taken for it.`, evidence: revealHidden(name), remediation: 'Rename it so it cannot pass for another skill.' });
170
+ const taken = (reservedNames || []).find((k) => String(k ?? '').toLowerCase() === String(name ?? '').toLowerCase());
171
+ if (taken) add({ ruleId: 'S-13-shipped-name', severity: 'high', file: null, line: null, message: `It installs as "${taken}", the name of a skill SynaBun ships: it would replace or shadow SynaBun's own.`, evidence: revealHidden(name), remediation: 'Give it a name of its own, or customize the shipped skill in Skills Studio instead.' });
172
+
173
+ // Frontmatter: hooks that stay for the session, tool grants for the invoking turn.
174
+ const fm = frontmatter && typeof frontmatter === 'object' ? frontmatter : {};
175
+ if (fm.hooks && typeof fm.hooks === 'object') {
176
+ for (const [event, value] of Object.entries(fm.hooks)) {
177
+ const commands = [];
178
+ const walk = (v) => {
179
+ if (Array.isArray(v)) v.forEach(walk);
180
+ else if (v && typeof v === 'object') { if (typeof v.command === 'string') commands.push(v.command); Object.values(v).forEach((x) => { if (x && typeof x === 'object') walk(x); }); }
181
+ };
182
+ walk(value);
183
+ authority.hooks.push({ event, commands: commands.map((c) => clip(c, 200)) });
184
+ }
185
+ add({ ruleId: 'S-07-hooks', severity: 'high', file: 'frontmatter', line: null, message: `Registers hooks (${authority.hooks.map((h) => h.event).join(', ')}) that stay active for the rest of the session once the skill is used.`, evidence: clip(authority.hooks.flatMap((h) => h.commands).join(' · ') || Object.keys(fm.hooks).join(', ')), remediation: 'Check every hook command; a reviewer decides.' });
186
+ }
187
+ for (const key of ['allowed-tools', 'tools']) {
188
+ const value = fm[key];
189
+ const list = Array.isArray(value) ? value : (typeof value === 'string' ? value.split(/,\s*|\s+(?![^(]*\))/).filter(Boolean) : []);
190
+ for (const raw of list) {
191
+ if (typeof raw !== 'string') continue;
192
+ const tool = raw.trim();
193
+ const broad = /^(?:Bash|Shell|PowerShell)(?:\(\s*\*?\s*\))?$/i.test(tool) || /^\*$/.test(tool);
194
+ authority.toolGrants.push({ key, tool, broad });
195
+ }
196
+ }
197
+ const broad = authority.toolGrants.filter((g) => g.broad);
198
+ if (broad.length) add({ ruleId: 'S-06-broad-grant', severity: 'high', file: 'frontmatter', line: null, message: `Pre-approves ${broad.map((g) => g.tool).join(', ')} for the turn that invokes it: any shell command runs without asking.`, evidence: broad.map((g) => `${g.key}: ${g.tool}`).join(', '), remediation: 'Grant only the commands it needs, for example Bash(git status:*).' });
199
+
200
+ let total = 0;
201
+ for (const file of files) {
202
+ const path = String(file.path);
203
+ const bytes = file.bytes;
204
+ const lower = path.toLowerCase();
205
+ // Plugin parts run outside Claude's tool sandbox.
206
+ if (/(^|\/)\.claude-plugin\//.test(path) || /(^|\/)hooks\/hooks\.json$/.test(path) || /(^|\/)\.mcp\.json$/.test(path) || /^bin\//.test(path) || /(?:^|\/)(?:plugin|marketplace|mcp|lsp|monitor)\.json$/.test(path)) {
207
+ add({ ruleId: 'S-14-plugin-component', severity: 'high', file: path, line: null, message: 'A plugin component (hooks, MCP servers, programs) that runs outside the tool sandbox.', evidence: path, remediation: 'Install plugins as plugins, after reviewing them.' });
208
+ }
209
+ const magic = MAGIC.find(([sig]) => sig.every((b, i) => bytes[i] === b));
210
+ const container = /\.(?:zip|gz|tgz|tar|rar|xz|bz2|7z|zst|lz|lzma)$/i.test(path);
211
+ if (ASSET_EXT.test(path) || magic || container || !(isEditableText(bytes) || (SCRIPT_EXT.test(path) || INSTRUCTION_EXT.test(path) || !path.includes('.') || file.executable) && isScannableText(bytes))) {
212
+ authority.binaries.push({ path, size: bytes.length, kind: magic ? magic[1] : 'binary', executable: !!file.executable });
213
+ let assetHeuristic = null;
214
+ const assetReason = ASSET_EXT.test(path) ? inspectAsset(path, bytes, (text) => LINE_RULES.some((r) => r.re.test(text)) || SECRET_PATTERNS.some(([, re]) => re.test(text)) ? 'strong' : looksLikeShell(text) ? 'command' : null, { budget: decompression, heuristic: (text) => { assetHeuristic ||= text; } }) : null;
215
+ if (!assetReason && (assetHeuristic || file.executable && ASSET_EXT.test(path))) add({ ruleId: 'S-09-asset-command', severity: 'medium', file: path, line: null, message: 'An asset contains a command-shaped text line or is marked executable; review its use.', evidence: evidenceOf(assetHeuristic || path), remediation: 'Review the asset and the commands that use it.' });
216
+ if (magic || container || !ASSET_EXT.test(path) || assetReason) {
217
+ coverage.complete = false;
218
+ add({ ruleId: 'S-09-opaque-binary', severity: 'high', file: path, line: null, message: assetReason ? 'This asset has an invalid structure, trailing bytes, or script-like content; it cannot be treated as passive.' : `${magic ? `A ${magic[1]}` : 'An opaque file'} that could not be read: what it does is unknown.`, reason: assetReason, evidence: `${path} (${bytes.length} bytes)`, remediation: 'Ship source code instead, or remove it.' });
219
+ }
220
+ coverage.skipped.push({ path, reason: assetReason || 'binary' });
221
+ continue;
222
+ }
223
+ let text = bytes.toString('utf-8');
224
+ if (text.length > SCAN_LIMITS.maxFileChars || total + text.length > SCAN_LIMITS.maxTotalChars) {
225
+ coverage.skipped.push({ path, reason: 'too-large' });
226
+ coverage.complete = false;
227
+ continue;
228
+ }
229
+ total += text.length;
230
+ coverage.scanned++;
231
+ // SVG is source text: apply every text rule below as well as asset script markers.
232
+ if (/\.svg$/i.test(path) && scriptMarkers(text)) {
233
+ coverage.complete = false;
234
+ coverage.skipped.push({ path, reason: 'asset-script-content' });
235
+ add({ ruleId: 'S-09-opaque-binary', severity: 'high', file: path, line: null, reason: 'asset-script-content', message: 'This SVG contains script markers; it cannot be treated as passive.', evidence: path, remediation: 'Remove executable content from the SVG.' });
236
+ }
237
+ const isScript = SCRIPT_EXT.test(lower) || text.startsWith('#!') || !!file.executable;
238
+ if (isScript) authority.scripts.push({ path, executable: !!file.executable, interpreter: text.startsWith('#!') ? clip(text.slice(2, text.indexOf('\n') > 0 ? text.indexOf('\n') : 80).trim(), 80) : null });
239
+ const instructions = INSTRUCTION_EXT.test(lower) || (!isScript && !lower.includes('.'));
240
+
241
+ // Visit every code point. The sample budget limits display only, never coverage.
242
+ findHiddenChars(text, { limit: 0, onHit(hit) {
243
+ const colour = isScript && hit.codePoint === 0x1b && colourSGR(text, hit.index);
244
+ const high = hit.kind === 'bidi' || hit.kind === 'tag';
245
+ const groupKey = high ? `high:${hit.codePoint}` : colour ? 'low' : 'medium';
246
+ let group = hiddenGroups.get(groupKey);
247
+ if (!group) { group = { severity: high ? 'high' : colour ? 'low' : 'medium', total: 0, samples: [], file: path, line: hit.line, label: high ? hit.label : null }; hiddenGroups.set(groupKey, group); }
248
+ group.total++;
249
+ if (group.samples.length < 20) group.samples.push({ file: path, line: hit.line, column: hit.column, label: hit.label });
250
+ } });
251
+ shellFiles.push({ path, text: text.split('\n').map((line) => line.slice(0, SCAN_LIMITS.maxLineChars)).join('\n') });
252
+ const lines = text.split('\n');
253
+ let inBangFence = false;
254
+ let hasSensitive = false; let hasSink = false; let sinkLine = 0; let sensitiveLine = 0;
255
+ for (let i = 0; i < lines.length; i++) {
256
+ const line = lines[i].length > SCAN_LIMITS.maxLineChars ? lines[i].slice(0, SCAN_LIMITS.maxLineChars) : lines[i];
257
+ const n = i + 1;
258
+ if (lines[i].length > SCAN_LIMITS.maxLineChars) { coverage.complete = false; coverage.skipped.push({ path, line: n, reason: 'line-too-large' }); }
259
+ // Claude Code dynamic context: !`command` inline, and ```! fenced blocks. They run before the model reads the skill.
260
+ if (instructions) {
261
+ if (/^\s*```!/.test(line)) { inBangFence = true; authority.dynamicContext.push({ file: path, line: n, command: '(block)' }); add({ ruleId: 'S-06-dynamic-context', severity: 'high', file: path, line: n, message: 'A shell block that Claude Code runs when the skill is loaded, before the model reads it, without asking.', evidence: evidenceOf(line), remediation: 'Make sure every command in it is safe to run unattended; a reviewer decides.' }); continue; }
262
+ if (inBangFence) { if (/^\s*```\s*$/.test(line)) inBangFence = false; else if (authority.dynamicContext.length < 200) authority.dynamicContext.push({ file: path, line: n, command: clip(line.trim(), 200) }); }
263
+ for (const m of line.matchAll(/!`([^`\n]{1,500})`/g)) {
264
+ authority.dynamicContext.push({ file: path, line: n, command: clip(m[1], 200) });
265
+ add({ ruleId: 'S-06-dynamic-context', severity: 'high', file: path, line: n, message: 'A shell command Claude Code runs when the skill is loaded, before the model reads it, without asking.', evidence: evidenceOf(m[0]), remediation: 'Make sure it is safe to run unattended; a reviewer decides.' });
266
+ }
267
+ }
268
+ for (const rule of LINE_RULES) {
269
+ if (!rule.re.test(line)) continue;
270
+ add({ ruleId: rule.id, severity: rule.severity, file: path, line: n, message: rule.message, evidence: evidenceOf(line), remediation: rule.remediation });
271
+ if (rule.id === 'S-10-sensitive-path') { hasSensitive = true; sensitiveLine = sensitiveLine || n; authority.sensitive.push({ file: path, line: n }); }
272
+ if (rule.id === 'S-07-persistent-write') authority.persistentWrites.push({ file: path, line: n });
273
+ if (rule.id === 'S-08-installer' || rule.id.startsWith('S-08-remote')) authority.installers.push({ file: path, line: n });
274
+ }
275
+ if (SECRET_ENV.test(line)) { hasSensitive = true; sensitiveLine = sensitiveLine || n; }
276
+ if (NETWORK_SINK.test(line)) { hasSink = true; sinkLine = sinkLine || n; }
277
+ for (const [label, re] of SECRET_PATTERNS) {
278
+ const m = re.exec(line);
279
+ if (!m) continue;
280
+ const shown = `${m[0].slice(0, 4)}… (${m[0].length} characters)`;
281
+ add({ ruleId: 'S-10-embedded-secret', severity: 'high', file: path, line: n, message: `Looks like a ${label} written into the skill.`, evidence: shown, remediation: 'Remove it and revoke it; read secrets from the user\'s own configuration at run time.' });
282
+ break;
283
+ }
284
+ for (const m of line.matchAll(URL_RE)) {
285
+ const host = hostOf(m[0]);
286
+ if (host && !authority.domains.includes(host) && authority.domains.length < 200) authority.domains.push(host);
287
+ }
288
+ }
289
+ if (hasSensitive && hasSink) {
290
+ add({ ruleId: 'S-11-exfiltration', severity: 'high', file: path, line: sinkLine, message: `Reads private data or secrets (line ${sensitiveLine}) and sends data off the machine (line ${sinkLine}) in the same file.`, evidence: evidenceOf(lines[sinkLine - 1] || ''), remediation: 'Check exactly what is sent where; a reviewer decides.' });
291
+ }
292
+ }
293
+ for (const group of hiddenGroups.values()) add({ ruleId: 'S-12-hidden-chars', ...group,
294
+ message: `${group.total} hidden characters, first ${group.samples.length} listed${group.label ? `: ${group.label}` : ''}.`,
295
+ evidence: group.samples.map((s) => `${s.file}:${s.line}:${s.column} ${s.label}`).join('; '),
296
+ remediation: 'Review the listed characters and remove unintended invisible text.' });
297
+ const shell = shellFindings(shellFiles);
298
+ if (!shell.complete) { coverage.complete = false; coverage.skipped.push({ path: null, reason: 'shell-analysis-limit' }); }
299
+ for (const hit of [...shell.hits, ...shellReviewFindings(shellFiles)]) {
300
+ if (['S-09-asset-exec', 'S-08-remote-pipe'].includes(hit.ruleId) && findings.some((f) => f.ruleId === hit.ruleId && f.file === hit.file && f.line === hit.line)) continue;
301
+ add({ ...hit, evidence: evidenceOf(hit.evidence), severity: ['S-09-asset-exec', 'S-08-download-run'].includes(hit.ruleId) ? 'medium' : 'high', message: hit.ruleId === 'S-08-download-run' ? 'This skill mentions download or decode tools and execution; review both locations.' : hit.ruleId === 'S-09-asset-exec' ? 'Names an image, font or media asset together with a program that could run it, or makes it executable.' : hit.ruleId === 'S-08-remote-pipe' ? 'Downloads a script and runs it in one step.' : hit.ruleId === 'S-08-remote-file' ? 'Downloads a file then executes or sources it.' : 'Decodes a file then executes or sources it.', remediation: 'Ship readable source instead, or pin and verify the bytes before execution.' });
302
+ authority.installers.push({ file: hit.file, line: hit.line });
303
+ }
304
+
305
+ } catch (err) {
306
+ return record({ type, name, digest, status: 'error', findings, authority, coverage: { ...coverage, complete: false }, error: String(err?.message || err) });
307
+ }
308
+ const status = !coverage.complete ? 'incomplete' : (findings.length ? 'findings' : 'no_findings');
309
+ return record({ type, name, digest, status, findings, authority, coverage });
310
+ }
311
+
312
+ function record({ type, name, digest, status, findings, authority, coverage, error = null }) {
313
+ const top = findings.reduce((max, f) => Math.max(max, SEVERITY_RANK[f.severity] ?? 0), -1);
314
+ // A scan that failed or could not read everything never lets an install through on its own.
315
+ let gate = 'allowed';
316
+ if (status === 'error' || status === 'incomplete') gate = 'blocked';
317
+ else if (top >= SEVERITY_RANK.critical) gate = 'blocked';
318
+ else if (status === 'incomplete' || top >= SEVERITY_RANK.medium) gate = 'review';
319
+ const sorted = findings.slice().sort((a, b) => (SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]) || String(a.file).localeCompare(String(b.file)) || ((a.line || 0) - (b.line || 0)));
320
+ return {
321
+ engine: SCAN_ENGINE, engineVersion: SCAN_ENGINE_VERSION, policy: SCAN_POLICY,
322
+ type, name, digest, status, gate, error,
323
+ counts: Object.fromEntries(Object.keys(SEVERITY_RANK).map((s) => [s, findings.filter((f) => f.severity === s).length])),
324
+ findings: redactRecord(sorted), authority: redactRecord(authority), coverage,
325
+ disclosure: 'Scanned on this computer. No file left it.',
326
+ };
327
+ }
328
+
329
+ /** Whether an install may go ahead on this scan for these bytes, with what the person confirmed. */
330
+ export function scanAllows(scan, { digest, acknowledgedDigest = null } = {}) {
331
+ if (!scan || scan.digest !== digest) return { ok: false, code: 'scan_stale', message: 'The scan is not of these files: scan them again.' };
332
+ if (scan.status === 'error') return { ok: false, code: 'scan_error', message: `The scan failed (${scan.error || 'error'}): nothing is installed without one.` };
333
+ if (scan.coverage?.complete === false || scan.status === 'incomplete' || scan.status === 'skipped') return { ok: false, code: 'scan_incomplete', message: 'Required scan coverage is incomplete; an acknowledgement cannot make it pass.' };
334
+ if (scan.gate === 'blocked') return { ok: false, code: 'scan_blocked', message: 'The scan found something that blocks this install.' };
335
+ if (scan.gate === 'review' && acknowledgedDigest !== digest) return { ok: false, code: 'review_required', message: 'Review the findings and confirm them for these exact files first.' };
336
+ return { ok: true };
337
+ }
338
+
339
+ // Redact the entire result, including commands and domains, before it reaches disk or a page.
340
+ function redactRecord(value) {
341
+ if (typeof value === 'string') return redactSecrets(value);
342
+ if (Array.isArray(value)) return value.map(redactRecord);
343
+ if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactRecord(v)]));
344
+ return value;
345
+ }
346
+
347
+ // Exact evidence acknowledged by the owner; changing engines, policy or findings requires review again.
348
+ export function scanEvidenceKey(scan) {
349
+ return createHash('sha256').update(JSON.stringify([scan.digest, scan.engineVersion, scan.policy, scan.status, scan.gate, scan.findings, scan.external || []])).digest('hex');
350
+ }
351
+ export function withExternalScans(scan, results = []) {
352
+ const required = results.filter((r) => r.required);
353
+ if (!required.length) return scan;
354
+ const coverage = { ...scan.coverage };
355
+ const findings = [...scan.findings];
356
+ let error = scan.error;
357
+ for (const result of required) {
358
+ if (result.digest !== scan.digest || !['no_findings', 'findings'].includes(result.status) || result.coverage?.complete === false) coverage.complete = false;
359
+ if (result.status === 'error') error = 'external_scanner_failed';
360
+ for (const finding of result.findings || []) findings.push({ ...finding, engine: result.engine });
361
+ }
362
+ const status = error ? 'error' : !coverage.complete ? 'incomplete' : findings.length ? 'findings' : 'no_findings';
363
+ return { ...record({ ...scan, status, coverage, findings, error }), external: required };
364
+ }
@@ -0,0 +1,54 @@
1
+ import { localRecord } from './local-store.js';
2
+ // Optional engines have no ambient discovery, credentials, command fallback or automatic download.
3
+ // A verified host adapter is injected by the application. Unsupported contracts remain explicitly unavailable.
4
+ export const EXTERNAL_SCANNERS = Object.freeze([
5
+ { id: 'cisco', license: 'Apache-2.0 client', licenseCode: 'apacheClient', boundary: 'Local static/code/dataflow only. LLM, AI Defense and VirusTotal uploads require separate consent.', preferred: true },
6
+ { id: 'gitleaks', license: 'MIT core', licenseCode: 'mitCore', boundary: 'Local secret detection only; cannot certify prompt safety.' },
7
+ { id: 'snyk', license: 'Apache-2.0 client; hosted terms separate', licenseCode: 'apacheHosted', boundary: 'Skill content leaves the computer. Retention and registry-scale entitlement must be agreed first.' },
8
+ { id: 'socket', license: 'MIT CLI; hosted terms separate', licenseCode: 'mitHosted', boundary: 'No verified standalone skill analysis API. Dated registry badges are evidence only.' },
9
+ ]);
10
+ export function createExternalScanners({ settings, adapters = {}, timeoutMs = 30000, dataDir = null }) {
11
+ const store = dataDir ? localRecord(dataDir, 'external-scans', { records: {} }) : null;
12
+ const signature = () => JSON.stringify(list().filter((e) => e.enabled).map((e) => [e.id, adapters[e.id]?.version || null, e.available]));
13
+ function evidence(digest) {
14
+ const record = store?.read().records[digest];
15
+ if (record?.signature === signature()) return record.results;
16
+ return list().filter((e) => e.enabled).map((e) => ({ engine: e.id, digest, required: true, status: 'incomplete', reason: 'scan_required' }));
17
+ }
18
+ function list() { return EXTERNAL_SCANNERS.map((e) => ({ ...e, enabled: settings().scanners.includes(e.id), available: !!(adapters[e.id]?.version && adapters[e.id]?.isolated && !adapters[e.id]?.outbound && typeof adapters[e.id]?.scan === 'function'), status: !settings().scanners.includes(e.id) ? 'skipped' : adapters[e.id]?.version && adapters[e.id]?.isolated && !adapters[e.id]?.outbound && typeof adapters[e.id]?.scan === 'function' ? 'ready' : 'unavailable' })); }
19
+ async function scan(revision, folder) {
20
+ let results = [];
21
+ const startedSignature = signature();
22
+ for (const engine of list()) {
23
+ const base = { engine: engine.id, digest: revision.contentDigest, disclosure: engine.boundary, required: engine.enabled };
24
+ if (!engine.enabled) { results.push({ ...base, status: 'skipped', reason: 'owner_opt_in_required' }); continue; }
25
+ const adapter = adapters[engine.id];
26
+ if (!adapter?.scan || !adapter.version || !adapter.isolated || adapter.outbound) { results.push({ ...base, status: 'error', reason: 'verified_local_adapter_unavailable' }); continue; }
27
+ let timer;
28
+ const controller = new AbortController();
29
+ try {
30
+ const out = await Promise.race([
31
+ Promise.resolve().then(() => adapter.scan({ files: folder.files.map((f) => ({ ...f, bytes: Buffer.from(f.bytes) })), digest: revision.contentDigest, signal: controller.signal })),
32
+ new Promise((_, reject) => { timer = setTimeout(() => { controller.abort(); reject(new Error('scanner_timeout')); }, timeoutMs); }),
33
+ ]);
34
+ if (!out || out.digest !== revision.contentDigest || !['no_findings', 'findings', 'error', 'skipped', 'incomplete'].includes(out.status)) throw new Error('invalid_scanner_result');
35
+ if (out.status === 'findings' && !out.findings?.length || out.status === 'no_findings' && out.findings?.length || out.findings?.length > 400 || out.coverage?.complete === false) throw new Error('invalid_scanner_result');
36
+ results.push({ ...base, engineVersion: adapter.version, status: out.status, findings: (out.findings || []).slice(0, 400).map((f) => ({ ruleId: String(f.ruleId || 'external').slice(0, 100), severity: ['critical', 'high', 'medium', 'low', 'info'].includes(f.severity) ? f.severity : 'high', file: folder.files.some((v) => v.path === f.file) ? f.file : null, line: Number.isInteger(f.line) ? f.line : null })) });
37
+ } catch { results.push({ ...base, engineVersion: adapter.version, status: 'error', reason: controller.signal.aborted ? 'scanner_timeout' : 'scanner_failed' }); }
38
+ finally { clearTimeout(timer); }
39
+ }
40
+ if (signature() !== startedSignature) {
41
+ results = list().map((e) => ({ engine: e.id, digest: revision.contentDigest, required: e.enabled, status: e.enabled ? 'incomplete' : 'skipped', reason: 'scanner_configuration_changed' }));
42
+ }
43
+ if (store && results.some((r) => r.required)) {
44
+ const state = store.read();
45
+ delete state.records[revision.contentDigest];
46
+ state.records[revision.contentDigest] = { signature: signature(), results };
47
+ const keys = Object.keys(state.records);
48
+ for (const key of keys.slice(0, Math.max(0, keys.length - 200))) delete state.records[key];
49
+ store.write(state);
50
+ }
51
+ return results;
52
+ }
53
+ return { list, scan, evidence };
54
+ }