shadow-claw 1.26.0 → 1.27.1

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 (230) hide show
  1. package/.agents/skills/main/skill-creator/SKILL.md +39 -0
  2. package/.agents/skills/main/skill-creator/references/declarative-pipelines.md +44 -0
  3. package/.agents/skills/main/skill-creator/references/declarative-tools.md +35 -0
  4. package/.agents/skills/main/skill-creator/references/frontmatter-spec.md +50 -0
  5. package/.well-known/agent-skills/index.json +14 -0
  6. package/README.md +5 -3
  7. package/bin/build/build.mjs +148 -15
  8. package/bin/build/build.test.mjs +128 -0
  9. package/bin/cli.mjs +35 -0
  10. package/bin/commands/mcp.mjs +277 -55
  11. package/bin/commands/mcp.test.mjs +275 -1
  12. package/bin/commands/skills-index.mjs +447 -0
  13. package/bin/commands/skills-index.test.mjs +243 -0
  14. package/bin/site-config/utils/custom-elements/get-approved-custom-element-scripts.test.mjs +2 -0
  15. package/bin/site-config/utils/custom-elements/sanitize-embedded-custom-element-scripts.test.mjs +10 -1
  16. package/dist/lib/components/index.js +25 -25
  17. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  18. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  19. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  20. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  21. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  22. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  23. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  24. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  25. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  26. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  27. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  28. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  29. package/dist/lib/components/shadow-claw-a2ui.js +1 -1
  30. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  31. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  32. package/dist/lib/components/shadow-claw-channels.js +1 -1
  33. package/dist/lib/components/shadow-claw-chat.js +1 -1
  34. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  35. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  36. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  37. package/dist/lib/components/shadow-claw-files.js +1 -1
  38. package/dist/lib/components/shadow-claw-git.js +1 -1
  39. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  40. package/dist/lib/components/shadow-claw-llm.js +1 -1
  41. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  42. package/dist/lib/components/shadow-claw-networking.js +1 -1
  43. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  44. package/dist/lib/components/shadow-claw-pages.js +1 -1
  45. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  46. package/dist/lib/components/shadow-claw-settings.js +1 -1
  47. package/dist/lib/components/shadow-claw-storage.js +1 -1
  48. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  49. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  50. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  51. package/dist/lib/components/shadow-claw-tools.js +1 -1
  52. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  53. package/dist/lib/components/shadow-claw.js +1 -1
  54. package/dist/lib/{configurePeerJs-CrlGan0m.js → configurePeerJs-oODzltaC.js} +2 -2
  55. package/dist/lib/{configurePeerJs-CrlGan0m.js.map → configurePeerJs-oODzltaC.js.map} +1 -1
  56. package/dist/lib/core/control-plane-client.d.ts +1 -0
  57. package/dist/lib/{downloadGroupFile-DboUjmp2.js → downloadGroupFile-DmcBQU1n.js} +2 -2
  58. package/dist/lib/{downloadGroupFile-DboUjmp2.js.map → downloadGroupFile-DmcBQU1n.js.map} +1 -1
  59. package/dist/lib/{file-viewer-BrK2Wxgh.js → file-viewer-C3YKnq_-.js} +4 -3
  60. package/dist/lib/{file-viewer-BrK2Wxgh.js.map → file-viewer-C3YKnq_-.js.map} +1 -1
  61. package/dist/lib/index.js +26 -26
  62. package/dist/lib/{initControlPlane-CsO4UR6_.js → initControlPlane-C2zURVIS.js} +86 -13
  63. package/dist/lib/initControlPlane-C2zURVIS.js.map +1 -0
  64. package/dist/lib/{markdown-eeSTFnMA.js → markdown-C6EHl9zs.js} +3 -3
  65. package/dist/lib/{markdown-eeSTFnMA.js.map → markdown-C6EHl9zs.js.map} +1 -1
  66. package/dist/lib/{orchestrator-CJvoC2Uu.js → orchestrator-CACLa_Ro.js} +7 -6
  67. package/dist/lib/{orchestrator-CJvoC2Uu.js.map → orchestrator-CACLa_Ro.js.map} +1 -1
  68. package/dist/lib/{orchestrator-DCfomsn9.js → orchestrator-KRjx0eba.js} +6 -534
  69. package/dist/lib/{orchestrator-DCfomsn9.js.map → orchestrator-KRjx0eba.js.map} +1 -1
  70. package/dist/lib/parsePath-DwC_YZjw.js +16 -0
  71. package/dist/lib/parsePath-DwC_YZjw.js.map +1 -0
  72. package/dist/lib/{peerjs-Dp02bXcG.js → peerjs-DFWOBcXf.js} +4 -4
  73. package/dist/lib/{peerjs-Dp02bXcG.js.map → peerjs-DFWOBcXf.js.map} +1 -1
  74. package/dist/lib/{readGroupFileBytes-Bve-e5e_.js → readGroupFileBytes-C8oOe8Iu.js} +3 -16
  75. package/dist/lib/readGroupFileBytes-C8oOe8Iu.js.map +1 -0
  76. package/dist/lib/security/custom-element-security.d.ts +8 -0
  77. package/dist/lib/server/control-plane-types.d.ts +1 -1
  78. package/dist/lib/{shadow-claw-D8xINM3c.js → shadow-claw-6UHdiy4a.js} +5 -5
  79. package/dist/lib/{shadow-claw-D8xINM3c.js.map → shadow-claw-6UHdiy4a.js.map} +1 -1
  80. package/dist/lib/{shadow-claw-a2ui-7rJqG4Ke.js → shadow-claw-a2ui-CpZUTzlh.js} +5 -4
  81. package/dist/lib/{shadow-claw-a2ui-7rJqG4Ke.js.map → shadow-claw-a2ui-CpZUTzlh.js.map} +1 -1
  82. package/dist/lib/{shadow-claw-accounts-CdruwN8A.js → shadow-claw-accounts-03oJ__XC.js} +2 -2
  83. package/dist/lib/{shadow-claw-accounts-CdruwN8A.js.map → shadow-claw-accounts-03oJ__XC.js.map} +1 -1
  84. package/dist/lib/{shadow-claw-channel-config-Bj0-Nk3t.js → shadow-claw-channel-config-DmfwJc6G.js} +3 -3
  85. package/dist/lib/{shadow-claw-channel-config-Bj0-Nk3t.js.map → shadow-claw-channel-config-DmfwJc6G.js.map} +1 -1
  86. package/dist/lib/{shadow-claw-channels-C_jI4KhP.js → shadow-claw-channels-1N6Jl-By.js} +3 -3
  87. package/dist/lib/{shadow-claw-channels-C_jI4KhP.js.map → shadow-claw-channels-1N6Jl-By.js.map} +1 -1
  88. package/dist/lib/{shadow-claw-chat-B16MspoP.js → shadow-claw-chat-BMoqQJRK.js} +10 -10
  89. package/dist/lib/{shadow-claw-chat-B16MspoP.js.map → shadow-claw-chat-BMoqQJRK.js.map} +1 -1
  90. package/dist/lib/{shadow-claw-control-plane-x8Ldid_h.js → shadow-claw-control-plane-UG0o7pCz.js} +6 -6
  91. package/dist/lib/{shadow-claw-control-plane-x8Ldid_h.js.map → shadow-claw-control-plane-UG0o7pCz.js.map} +1 -1
  92. package/dist/lib/{shadow-claw-conversations-Ct1kAYdB.js → shadow-claw-conversations-BwXep3iu.js} +4 -3
  93. package/dist/lib/{shadow-claw-conversations-Ct1kAYdB.js.map → shadow-claw-conversations-BwXep3iu.js.map} +1 -1
  94. package/dist/lib/{shadow-claw-file-viewer-BfDHHgpA.js → shadow-claw-file-viewer-DUxzVxDT.js} +14 -10
  95. package/dist/lib/shadow-claw-file-viewer-DUxzVxDT.js.map +1 -0
  96. package/dist/lib/{shadow-claw-files-EYhOPYIH.js → shadow-claw-files-DIREEnT2.js} +7 -7
  97. package/dist/lib/{shadow-claw-files-EYhOPYIH.js.map → shadow-claw-files-DIREEnT2.js.map} +1 -1
  98. package/dist/lib/{shadow-claw-git-tcSpT-P0.js → shadow-claw-git-Bg1SWgsF.js} +4 -4
  99. package/dist/lib/{shadow-claw-git-tcSpT-P0.js.map → shadow-claw-git-Bg1SWgsF.js.map} +1 -1
  100. package/dist/lib/{shadow-claw-integrations-NbcCx_ZI.js → shadow-claw-integrations-Ds-CLPhj.js} +2 -2
  101. package/dist/lib/{shadow-claw-integrations-NbcCx_ZI.js.map → shadow-claw-integrations-Ds-CLPhj.js.map} +1 -1
  102. package/dist/lib/{shadow-claw-llm-BazJdD5f.js → shadow-claw-llm-CazBrVpM.js} +3 -3
  103. package/dist/lib/{shadow-claw-llm-BazJdD5f.js.map → shadow-claw-llm-CazBrVpM.js.map} +1 -1
  104. package/dist/lib/{shadow-claw-mcp-remote-CYmhpg7a.js → shadow-claw-mcp-remote-CT5Q-54E.js} +2 -2
  105. package/dist/lib/{shadow-claw-mcp-remote-CYmhpg7a.js.map → shadow-claw-mcp-remote-CT5Q-54E.js.map} +1 -1
  106. package/dist/lib/{shadow-claw-networking-DImPxe0H.js → shadow-claw-networking-Df85tCbO.js} +2 -2
  107. package/dist/lib/{shadow-claw-networking-DImPxe0H.js.map → shadow-claw-networking-Df85tCbO.js.map} +1 -1
  108. package/dist/lib/{shadow-claw-page-header-CcHUfFUx.js → shadow-claw-page-header-Cti2fzf-.js} +2 -2
  109. package/dist/lib/{shadow-claw-page-header-CcHUfFUx.js.map → shadow-claw-page-header-Cti2fzf-.js.map} +1 -1
  110. package/dist/lib/{shadow-claw-pages-D54jF1Qq.js → shadow-claw-pages-BgPyIEZO.js} +15 -11
  111. package/dist/lib/shadow-claw-pages-BgPyIEZO.js.map +1 -0
  112. package/dist/lib/{shadow-claw-peerjs-B9-T8Nlx.js → shadow-claw-peerjs-xkBXILed.js} +3 -3
  113. package/dist/lib/{shadow-claw-peerjs-B9-T8Nlx.js.map → shadow-claw-peerjs-xkBXILed.js.map} +1 -1
  114. package/dist/lib/{shadow-claw-settings-Bt1VAe-1.js → shadow-claw-settings-CN89xRVU.js} +4 -4
  115. package/dist/lib/{shadow-claw-settings-Bt1VAe-1.js.map → shadow-claw-settings-CN89xRVU.js.map} +1 -1
  116. package/dist/lib/{shadow-claw-storage-CFOjGf1d.js → shadow-claw-storage-VnhcObCe.js} +3 -3
  117. package/dist/lib/{shadow-claw-storage-CFOjGf1d.js.map → shadow-claw-storage-VnhcObCe.js.map} +1 -1
  118. package/dist/lib/{shadow-claw-task-server-BiKa2GOm.js → shadow-claw-task-server-jgEC2hHB.js} +2 -2
  119. package/dist/lib/{shadow-claw-task-server-BiKa2GOm.js.map → shadow-claw-task-server-jgEC2hHB.js.map} +1 -1
  120. package/dist/lib/{shadow-claw-tasks-DDHd8W4d.js → shadow-claw-tasks-i_evIufy.js} +6 -6
  121. package/dist/lib/{shadow-claw-tasks-DDHd8W4d.js.map → shadow-claw-tasks-i_evIufy.js.map} +1 -1
  122. package/dist/lib/{shadow-claw-terminal-kB6_tWKz.js → shadow-claw-terminal-CwScROlu.js} +2 -2
  123. package/dist/lib/{shadow-claw-terminal-kB6_tWKz.js.map → shadow-claw-terminal-CwScROlu.js.map} +1 -1
  124. package/dist/lib/{shadow-claw-tools-DxXIm0ku.js → shadow-claw-tools-DD3fyXP4.js} +5 -4
  125. package/dist/lib/{shadow-claw-tools-DxXIm0ku.js.map → shadow-claw-tools-DD3fyXP4.js.map} +1 -1
  126. package/dist/lib/{shadow-claw-webvm-Dc8oo7ao.js → shadow-claw-webvm-DbJq4I3-.js} +2 -2
  127. package/dist/lib/{shadow-claw-webvm-Dc8oo7ao.js.map → shadow-claw-webvm-DbJq4I3-.js.map} +1 -1
  128. package/dist/lib/{syncWebMcpRegistration-jyRGbC9w.js → syncWebMcpRegistration-DNLnQ-jz.js} +3 -2
  129. package/dist/lib/{syncWebMcpRegistration-jyRGbC9w.js.map → syncWebMcpRegistration-DNLnQ-jz.js.map} +1 -1
  130. package/dist/lib/tools-DwwB9RBa.js +546 -0
  131. package/dist/lib/tools-DwwB9RBa.js.map +1 -0
  132. package/dist/lib/{trusted-types-CkLDpLiZ.js → trusted-types-Dx0RZlrd.js} +19 -8
  133. package/dist/lib/trusted-types-Dx0RZlrd.js.map +1 -0
  134. package/dist/lib/utils/index.js +1 -1
  135. package/dist/lib/utils/utils.js +1 -1
  136. package/dist/public/.well-known/agent-skills/index.json +14 -0
  137. package/dist/public/AGENTS.md +5 -3
  138. package/dist/public/README.md +5 -3
  139. package/dist/public/agent.worker.js +1 -1
  140. package/dist/public/{configurePeerJs-COcwE0E7.js → configurePeerJs-BqgeBjP7.js} +1 -1
  141. package/dist/public/custom-element-security-FU04Cq05.js +1 -0
  142. package/dist/public/dist-B9m1QM9v.js +58 -0
  143. package/dist/public/docs/README.md +31 -31
  144. package/dist/public/docs/example/article/index.html +1 -1
  145. package/dist/public/docs/publishing/index.html +1 -1
  146. package/dist/public/docs/skill-creator/index.html +1 -1
  147. package/dist/public/docs/subsystems/a2ui.md +11 -1
  148. package/dist/public/docs/subsystems/cli.md +32 -0
  149. package/dist/public/docs/subsystems/control-plane.md +8 -2
  150. package/dist/public/docs/subsystems/custom-element-security.md +19 -9
  151. package/dist/public/docs/subsystems/mcp-server.md +39 -15
  152. package/dist/public/docs/subsystems/skills.md +30 -0
  153. package/dist/public/docs/subsystems/webmcp.md +9 -0
  154. package/dist/public/downloadGroupFile-D8KRJGGL.js +1 -0
  155. package/dist/public/e2e/README.md +7 -4
  156. package/dist/public/{e2e-bridge-CHARpbpW.js → e2e-bridge-Ceqr5kGc.js} +1 -1
  157. package/dist/public/effect-BJCrpFdp.js +1 -0
  158. package/dist/public/file-viewer-C-6Jc_tb.js +1 -0
  159. package/dist/public/index.html +1 -1
  160. package/dist/public/index.js +1 -1
  161. package/dist/public/initControlPlane-BN9cWyO0.js +1 -0
  162. package/dist/public/main/index.html +1 -1
  163. package/dist/public/main/memory/index.html +1 -1
  164. package/dist/public/{markdown-1I3O6Mmd.js → markdown-C3M4OW_A.js} +1 -1
  165. package/dist/public/{orchestrator-Cl95w2vb.js → orchestrator-CMbCTjBf.js} +7 -7
  166. package/dist/public/{orchestrator-D-xlXG92.js → orchestrator-DHjdLtqa.js} +663 -663
  167. package/dist/public/peerjs-DUCKH-m9.js +1 -0
  168. package/dist/public/service-worker.js +1 -1
  169. package/dist/public/{shadow-claw-CaoKmIIB.js → shadow-claw-EAt2ml5Y.js} +3 -3
  170. package/dist/public/{shadow-claw-accounts-CJvYC4li.js → shadow-claw-accounts-xUOLBsQZ.js} +1 -1
  171. package/dist/public/{shadow-claw-channels-3K62sU5f.js → shadow-claw-channels-XsfuWFF8.js} +4 -4
  172. package/dist/public/{shadow-claw-chat-Cu2keU2I.js → shadow-claw-chat-CbSaOZ4q.js} +7 -7
  173. package/dist/public/{shadow-claw-control-plane-D1OwlQI_.js → shadow-claw-control-plane-DHvN3Ukg.js} +2 -2
  174. package/dist/public/{shadow-claw-conversations-DDtg3BEZ.js → shadow-claw-conversations-BWs5VVfs.js} +2 -2
  175. package/dist/public/{shadow-claw-file-viewer-BR__1z9S.js → shadow-claw-file-viewer-BWsoW5QS.js} +5 -4
  176. package/dist/public/{shadow-claw-files-BJSyZ-Bl.js → shadow-claw-files-CG7MqWiV.js} +8 -8
  177. package/dist/public/{shadow-claw-git-MAjhXRLc.js → shadow-claw-git-DBRPZNav.js} +6 -6
  178. package/dist/public/{shadow-claw-integrations-82UoFuDR.js → shadow-claw-integrations-BBVnO0Fc.js} +8 -8
  179. package/dist/public/{shadow-claw-llm-R8j0lwPP.js → shadow-claw-llm-DMvLgKXo.js} +2 -2
  180. package/dist/public/{shadow-claw-mcp-remote-BzZoz4LX.js → shadow-claw-mcp-remote-Bkx0gsmF.js} +2 -2
  181. package/dist/public/{shadow-claw-networking-DLEd9cNm.js → shadow-claw-networking-Dd8Rw31D.js} +2 -2
  182. package/dist/public/{shadow-claw-notifications-1vwR7TbT.js → shadow-claw-notifications-Dbevj-lq.js} +2 -2
  183. package/dist/public/{shadow-claw-page-header-DMKbkv3s.js → shadow-claw-page-header-BccSHItx.js} +1 -1
  184. package/dist/public/{shadow-claw-pages-CjyeIV__.js → shadow-claw-pages-3_lQAv0t.js} +4 -3
  185. package/dist/public/{shadow-claw-settings-BfbT4dBU.js → shadow-claw-settings-t8TIlgrM.js} +5 -5
  186. package/dist/public/{shadow-claw-storage-C-mWQQML.js → shadow-claw-storage-CfEXoSIE.js} +2 -2
  187. package/dist/public/{shadow-claw-task-server-D3lHp0Nc.js → shadow-claw-task-server-DxQSsgRZ.js} +2 -2
  188. package/dist/public/{shadow-claw-tasks-DVVWEm-c.js → shadow-claw-tasks-DNpicQwz.js} +14 -14
  189. package/dist/public/{shadow-claw-terminal-BwuTRp-O.js → shadow-claw-terminal-DpY6zU_r.js} +1 -1
  190. package/dist/public/{shadow-claw-toast-ClCwgzFG.js → shadow-claw-toast-BkrQIP-3.js} +1 -1
  191. package/dist/public/{shadow-claw-tools-zFLk2nbV.js → shadow-claw-tools-WSwWohnk.js} +2 -2
  192. package/dist/public/{shadow-claw-webvm-qR9X7fg7.js → shadow-claw-webvm-CP6oFjg5.js} +2 -2
  193. package/dist/public/syncWebMcpRegistration-DOn9gLnD.js +1 -0
  194. package/dist/public/toast-BXgUfbyh.js +1 -0
  195. package/dist/public/tools-DvckJZG2.js +1 -0
  196. package/dist/server.js +188 -188
  197. package/package.json +3 -1
  198. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.stories.ts +1380 -93
  199. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.ts +11 -6
  200. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +8 -1
  201. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +13 -5
  202. package/src/components/shadow-claw-pages/utils/buildHtmlPageSrcdoc.ts +13 -5
  203. package/src/core/control-plane-client.ts +15 -0
  204. package/src/core/utils/initControlPlane.test.ts +187 -0
  205. package/src/core/utils/initControlPlane.ts +194 -20
  206. package/src/security/custom-element-security.test.ts +51 -0
  207. package/src/security/custom-element-security.ts +33 -15
  208. package/src/server/control-plane-types.ts +1 -0
  209. package/src/server/control-plane.test.ts +69 -0
  210. package/src/server/control-plane.ts +45 -3
  211. package/src/server/mcp/tools/built-in-tools.ts +80 -5
  212. package/src/server/mcp/tools/client-tool-relay.ts +183 -26
  213. package/src/server/mcp/tools/mcp-tools.test.ts +140 -0
  214. package/src/server/middleware/static-files.test.ts +142 -0
  215. package/src/server/middleware/static-files.ts +66 -8
  216. package/src/testing/jest-setup.ts +14 -0
  217. package/src/worker/utils/handleMessage.ts +4 -1
  218. package/dist/lib/initControlPlane-CsO4UR6_.js.map +0 -1
  219. package/dist/lib/readGroupFileBytes-Bve-e5e_.js.map +0 -1
  220. package/dist/lib/shadow-claw-file-viewer-BfDHHgpA.js.map +0 -1
  221. package/dist/lib/shadow-claw-pages-D54jF1Qq.js.map +0 -1
  222. package/dist/lib/trusted-types-CkLDpLiZ.js.map +0 -1
  223. package/dist/public/custom-element-security-NCqQ5lmC.js +0 -1
  224. package/dist/public/downloadGroupFile-G0BZQgQg.js +0 -1
  225. package/dist/public/effect-rHYr4nfu.js +0 -1
  226. package/dist/public/file-viewer-8PdCURDD.js +0 -1
  227. package/dist/public/initControlPlane-CcGXax0A.js +0 -1
  228. package/dist/public/peerjs-BZdxWQFv.js +0 -1
  229. package/dist/public/syncWebMcpRegistration-DD42YsYV.js +0 -1
  230. package/dist/public/toast-HAq8XWFj.js +0 -58
@@ -0,0 +1,447 @@
1
+ /**
2
+ * ShadowClaw CLI — `skills:index` command
3
+ * Generates and updates .well-known/agent-skills/index.json per the
4
+ * Cloudflare Agent Skills Discovery RFC (v0.2.0).
5
+ */
6
+
7
+ import crypto from "node:crypto";
8
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
9
+ import path from "node:path";
10
+ import process from "node:process";
11
+ import { fileURLToPath } from "node:url";
12
+ import matter from "gray-matter";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ /**
18
+ * Computes a standardized sha256:{hex} digest from Buffer or string.
19
+ */
20
+ export function computeSha256(content) {
21
+ const hash = crypto.createHash("sha256").update(content).digest("hex");
22
+ return `sha256:${hash.toLowerCase()}`;
23
+ }
24
+
25
+ async function pathExists(p) {
26
+ try {
27
+ await stat(p);
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Recursively find all files matching a filter under a root directory.
36
+ */
37
+ async function findFilesRecursively(dir, filterFn) {
38
+ const results = [];
39
+ if (!(await pathExists(dir))) return results;
40
+
41
+ async function walk(currentDir) {
42
+ const entries = await readdir(currentDir, { withFileTypes: true });
43
+ for (const entry of entries) {
44
+ const fullPath = path.join(currentDir, entry.name);
45
+ if (entry.isDirectory()) {
46
+ await walk(fullPath);
47
+ } else if (
48
+ entry.isFile() &&
49
+ (!filterFn || filterFn(entry.name, fullPath))
50
+ ) {
51
+ results.push(fullPath);
52
+ }
53
+ }
54
+ }
55
+
56
+ await walk(dir);
57
+ return results.sort();
58
+ }
59
+
60
+ /**
61
+ * Generates the Agent Skills Discovery index object.
62
+ */
63
+ export async function generateSkillsIndex(
64
+ contentRoot = process.cwd(),
65
+ options = {},
66
+ ) {
67
+ const resolvedRoot = path.resolve(contentRoot);
68
+ const wellKnownDirRel = options.outDir || ".well-known/agent-skills";
69
+
70
+ // 1. Read metadata from shadow-claw.config.json or package.json
71
+ const metadataRoot = options.metadataRoot
72
+ ? path.resolve(options.metadataRoot)
73
+ : resolvedRoot;
74
+
75
+ let siteName = options.siteName;
76
+ let siteDescription = options.siteDescription;
77
+
78
+ const parent1 = path.dirname(metadataRoot);
79
+ const parent2 = path.dirname(parent1);
80
+
81
+ const configCandidates = [
82
+ options.configPath,
83
+ path.join(metadataRoot, "shadow-claw.config.json"),
84
+ path.join(metadataRoot, "shadow-claw-config.json"),
85
+ path.join(metadataRoot, "site-config.json"),
86
+ path.join(parent1, "shadow-claw.config.json"),
87
+ path.join(parent1, "shadow-claw-config.json"),
88
+ path.join(parent1, "site-config.json"),
89
+ path.join(parent2, "shadow-claw.config.json"),
90
+ path.join(parent2, "shadow-claw-config.json"),
91
+ path.join(parent2, "site-config.json"),
92
+ ].filter(Boolean);
93
+
94
+ let loadedConfig = null;
95
+ for (const candidate of configCandidates) {
96
+ if (await pathExists(candidate)) {
97
+ try {
98
+ const configStr = await readFile(candidate, "utf8");
99
+ loadedConfig = JSON.parse(configStr);
100
+ break;
101
+ } catch {}
102
+ }
103
+ }
104
+
105
+ if (loadedConfig) {
106
+ if (!siteName && loadedConfig.site?.title)
107
+ siteName = loadedConfig.site.title;
108
+ if (!siteDescription && loadedConfig.site?.description) {
109
+ siteDescription = loadedConfig.site.description;
110
+ }
111
+ }
112
+
113
+ if (!siteName || !siteDescription) {
114
+ const pkgCandidates = [
115
+ path.join(metadataRoot, "package.json"),
116
+ path.join(parent1, "package.json"),
117
+ path.join(parent2, "package.json"),
118
+ ];
119
+ for (const pkgPath of pkgCandidates) {
120
+ if (await pathExists(pkgPath)) {
121
+ try {
122
+ const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
123
+ if (!siteName && pkg.name) siteName = pkg.name;
124
+ if (!siteDescription && pkg.description) {
125
+ siteDescription = pkg.description;
126
+ }
127
+ break;
128
+ } catch {}
129
+ }
130
+ }
131
+ }
132
+
133
+ if (!siteName) {
134
+ siteName = path.basename(metadataRoot);
135
+ if (siteName === "public" || siteName === "dist") {
136
+ siteName = path.basename(parent1);
137
+ if (
138
+ (siteName === "public" || siteName === "dist") &&
139
+ path.basename(parent2)
140
+ ) {
141
+ siteName = path.basename(parent2);
142
+ }
143
+ }
144
+ }
145
+
146
+ if (!siteDescription) {
147
+ siteDescription =
148
+ "Agent skills and tools collection, powered by ShadowClaw.";
149
+ }
150
+
151
+ // 2. Discover Tools (.agents/tools/**/*.json)
152
+ const toolsDir = path.join(resolvedRoot, ".agents", "tools");
153
+ const toolFiles = await findFilesRecursively(toolsDir, (name) =>
154
+ name.endsWith(".json"),
155
+ );
156
+ const discoveredTools = [];
157
+
158
+ for (const fullToolPath of toolFiles) {
159
+ const relFromRoot = path.relative(resolvedRoot, fullToolPath);
160
+ const relUrl = path.posix.normalize(
161
+ path.posix.relative(
162
+ wellKnownDirRel,
163
+ relFromRoot.split(path.sep).join(path.posix.sep),
164
+ ),
165
+ );
166
+ const content = await readFile(fullToolPath);
167
+ const digest = computeSha256(content);
168
+
169
+ let name = path.basename(fullToolPath, ".json");
170
+ let description = "";
171
+
172
+ try {
173
+ const parsed = JSON.parse(content.toString("utf8"));
174
+ if (parsed.name) name = parsed.name;
175
+ if (parsed.description) description = parsed.description;
176
+ } catch {}
177
+
178
+ discoveredTools.push({
179
+ name,
180
+ description,
181
+ url: relUrl,
182
+ digest,
183
+ relFromRoot,
184
+ });
185
+ }
186
+
187
+ // 3. Discover Scripts (.agents/scripts/**/*.{js,mjs})
188
+ const scriptsDir = path.join(resolvedRoot, ".agents", "scripts");
189
+ const scriptFiles = await findFilesRecursively(
190
+ scriptsDir,
191
+ (name) => name.endsWith(".js") || name.endsWith(".mjs"),
192
+ );
193
+ const discoveredScripts = [];
194
+
195
+ for (const fullScriptPath of scriptFiles) {
196
+ const relFromRoot = path.relative(resolvedRoot, fullScriptPath);
197
+ const relUrl = path.posix.normalize(
198
+ path.posix.relative(
199
+ wellKnownDirRel,
200
+ relFromRoot.split(path.sep).join(path.posix.sep),
201
+ ),
202
+ );
203
+ const content = await readFile(fullScriptPath);
204
+ const digest = computeSha256(content);
205
+ const baseName = path.basename(
206
+ fullScriptPath,
207
+ path.extname(fullScriptPath),
208
+ );
209
+
210
+ discoveredScripts.push({
211
+ name: baseName,
212
+ description: `Portable ESM engine logic and tool execution handler for ${baseName}.`,
213
+ url: relUrl,
214
+ digest,
215
+ relFromRoot,
216
+ });
217
+ }
218
+
219
+ // 4. Discover Skills (.agents/skills/**/SKILL.md)
220
+ const skillsDir = path.join(resolvedRoot, ".agents", "skills");
221
+ const skillFiles = await findFilesRecursively(
222
+ skillsDir,
223
+ (name) => name === "SKILL.md",
224
+ );
225
+ const discoveredSkills = [];
226
+
227
+ for (const fullSkillPath of skillFiles) {
228
+ const relFromRoot = path.relative(resolvedRoot, fullSkillPath);
229
+ const relUrl = path.posix.normalize(
230
+ path.posix.relative(
231
+ wellKnownDirRel,
232
+ relFromRoot.split(path.sep).join(path.posix.sep),
233
+ ),
234
+ );
235
+ const rawContent = await readFile(fullSkillPath);
236
+ const digest = computeSha256(rawContent);
237
+
238
+ let frontmatter = {};
239
+ try {
240
+ const parsed = matter(rawContent.toString("utf8"));
241
+ frontmatter = parsed.data || {};
242
+ } catch {}
243
+
244
+ const parentDirName = path.basename(path.dirname(fullSkillPath));
245
+ const skillName = frontmatter.name || parentDirName;
246
+ const skillDesc = frontmatter.description || "";
247
+
248
+ // Identify associated tools
249
+ const associatedToolNames = new Set();
250
+ let hasExplicitTools = false;
251
+ if (Array.isArray(frontmatter.metadata?.execution?.tools)) {
252
+ for (const t of frontmatter.metadata.execution.tools) {
253
+ if (t?.name) {
254
+ associatedToolNames.add(t.name);
255
+ hasExplicitTools = true;
256
+ }
257
+ }
258
+ }
259
+ const allowedToolsRaw =
260
+ frontmatter.metadata?.["allowed-tools"] ||
261
+ frontmatter.metadata?.allowedTools ||
262
+ frontmatter["allowed-tools"];
263
+ if (typeof allowedToolsRaw === "string") {
264
+ allowedToolsRaw
265
+ .split(/[,\s]+/)
266
+ .filter(Boolean)
267
+ .forEach((t) => {
268
+ associatedToolNames.add(t);
269
+ hasExplicitTools = true;
270
+ });
271
+ }
272
+ // Only fallback if no explicit tools were declared in frontmatter
273
+ if (!hasExplicitTools) {
274
+ for (const tool of discoveredTools) {
275
+ const normTool = tool.name.replace(/[-_]/g, "");
276
+ const normSkill = skillName.replace(/[-_]/g, "");
277
+ if (normTool === normSkill) {
278
+ associatedToolNames.add(tool.name);
279
+ }
280
+ }
281
+ }
282
+
283
+ const matchingTools = discoveredTools
284
+ .filter((t) => associatedToolNames.has(t.name))
285
+ .map(({ name, url, digest }) => ({ name, url, digest }));
286
+
287
+ // Identify associated scripts
288
+ // Match by base name or if script base is a prefix/contains skill name base
289
+ const matchingScripts = discoveredScripts
290
+ .filter((s) => {
291
+ const normScript = s.name.toLowerCase().replace(/[-_]/g, "");
292
+ const normSkill = skillName.toLowerCase().replace(/[-_]/g, "");
293
+ return (
294
+ normSkill.startsWith(normScript) ||
295
+ normScript.startsWith(normSkill) ||
296
+ associatedToolNames.has(s.name)
297
+ );
298
+ })
299
+ .map(({ name, url, digest }) => ({ name, url, digest }));
300
+
301
+ discoveredSkills.push({
302
+ name: skillName,
303
+ type: "skill-md",
304
+ description: skillDesc,
305
+ url: relUrl,
306
+ digest,
307
+ ...(matchingTools.length ? { tools: matchingTools } : {}),
308
+ ...(matchingScripts.length ? { scripts: matchingScripts } : {}),
309
+ });
310
+ }
311
+
312
+ // 4b. Discover Bundled Skills from Toolchain (if requested or enabled)
313
+ const defaultToolchainRoot = path.resolve(__dirname, "../..");
314
+ const bundledSkillsDir = options.bundledSkillsDir
315
+ ? path.resolve(options.bundledSkillsDir)
316
+ : options.toolchainRoot
317
+ ? path.join(path.resolve(options.toolchainRoot), ".agents", "skills")
318
+ : options.includeBundled
319
+ ? path.join(defaultToolchainRoot, ".agents", "skills")
320
+ : null;
321
+
322
+ if (
323
+ bundledSkillsDir &&
324
+ (await pathExists(bundledSkillsDir)) &&
325
+ bundledSkillsDir !== skillsDir
326
+ ) {
327
+ const bundledSkillFiles = await findFilesRecursively(
328
+ bundledSkillsDir,
329
+ (name) => name === "SKILL.md",
330
+ );
331
+ for (const fullSkillPath of bundledSkillFiles) {
332
+ const relFromBundled = path.relative(bundledSkillsDir, fullSkillPath);
333
+ const relFromRoot = path.join(".agents", "skills", relFromBundled);
334
+ const relUrl = path.posix.normalize(
335
+ path.posix.relative(
336
+ wellKnownDirRel,
337
+ relFromRoot.split(path.sep).join(path.posix.sep),
338
+ ),
339
+ );
340
+ const rawContent = await readFile(fullSkillPath);
341
+ const digest = computeSha256(rawContent);
342
+
343
+ let frontmatter = {};
344
+ try {
345
+ const parsed = matter(rawContent.toString("utf8"));
346
+ frontmatter = parsed.data || {};
347
+ } catch {}
348
+
349
+ const parentDirName = path.basename(path.dirname(fullSkillPath));
350
+ const skillName = frontmatter.name || parentDirName;
351
+ const skillDesc = frontmatter.description || "";
352
+
353
+ if (discoveredSkills.some((s) => s.name === skillName)) {
354
+ continue;
355
+ }
356
+
357
+ discoveredSkills.push({
358
+ name: skillName,
359
+ type: "skill-md",
360
+ description: skillDesc,
361
+ url: relUrl,
362
+ digest,
363
+ });
364
+ }
365
+ }
366
+
367
+ // 5. Construct Index Document
368
+ const indexDoc = {
369
+ $schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
370
+ name: siteName,
371
+ description: siteDescription,
372
+ skills: discoveredSkills,
373
+ ...(discoveredTools.length
374
+ ? {
375
+ tools: discoveredTools.map(({ name, description, url, digest }) => ({
376
+ name,
377
+ ...(description ? { description } : {}),
378
+ url,
379
+ digest,
380
+ })),
381
+ }
382
+ : {}),
383
+ ...(discoveredScripts.length
384
+ ? {
385
+ scripts: discoveredScripts.map(
386
+ ({ name, description, url, digest }) => ({
387
+ name,
388
+ ...(description ? { description } : {}),
389
+ url,
390
+ digest,
391
+ }),
392
+ ),
393
+ }
394
+ : {}),
395
+ ...(discoveredTools.length || discoveredScripts.length
396
+ ? {
397
+ dependencies: {
398
+ ...(discoveredTools.length
399
+ ? { tools: discoveredTools.map((t) => t.url) }
400
+ : {}),
401
+ ...(discoveredScripts.length
402
+ ? { scripts: discoveredScripts.map((s) => s.url) }
403
+ : {}),
404
+ },
405
+ }
406
+ : {}),
407
+ };
408
+
409
+ // 6. Output Handling
410
+ if (options.stdout) {
411
+ console.log(JSON.stringify(indexDoc, null, 2));
412
+ }
413
+
414
+ if (options.write !== false) {
415
+ const targetFile = options.outFile
416
+ ? path.resolve(options.outFile)
417
+ : path.join(resolvedRoot, wellKnownDirRel, "index.json");
418
+
419
+ await mkdir(path.dirname(targetFile), { recursive: true });
420
+ await writeFile(
421
+ targetFile,
422
+ JSON.stringify(indexDoc, null, 2) + "\n",
423
+ "utf8",
424
+ );
425
+ }
426
+
427
+ return indexDoc;
428
+ }
429
+
430
+ /**
431
+ * CLI command runner for `shadow-claw skills:index [dir]`.
432
+ */
433
+ export async function runSkillsIndexCommand(dir, options = {}) {
434
+ const targetDir = dir ? path.resolve(dir) : process.cwd();
435
+ console.log(`Generating Agent Skills Discovery index for ${targetDir}...`);
436
+
437
+ const result = await generateSkillsIndex(targetDir, {
438
+ includeBundled: options.bundled !== false,
439
+ ...options,
440
+ configPath: options.config || options.configPath,
441
+ });
442
+ const count = result.skills ? result.skills.length : 0;
443
+ console.log(
444
+ `Indexed ${count} skill(s) into .well-known/agent-skills/index.json`,
445
+ );
446
+ return result;
447
+ }
@@ -0,0 +1,243 @@
1
+ import fs from "node:fs";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import {
6
+ jest,
7
+ describe,
8
+ it,
9
+ expect,
10
+ beforeEach,
11
+ afterEach,
12
+ } from "@jest/globals";
13
+ import {
14
+ computeSha256,
15
+ generateSkillsIndex,
16
+ runSkillsIndexCommand,
17
+ } from "./skills-index.mjs";
18
+
19
+ describe("skills-index command and generator", () => {
20
+ let tempDir;
21
+
22
+ beforeEach(async () => {
23
+ tempDir = await mkdtemp(path.join(os.tmpdir(), "sc-skills-index-test-"));
24
+ });
25
+
26
+ afterEach(async () => {
27
+ if (tempDir && fs.existsSync(tempDir)) {
28
+ await rm(tempDir, { recursive: true, force: true });
29
+ }
30
+ });
31
+
32
+ it("computes standard sha256 format", () => {
33
+ const hash = computeSha256("hello world");
34
+ expect(hash).toMatch(/^sha256:[a-f0-9]{64}$/);
35
+ expect(hash).toBe(
36
+ "sha256:b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
37
+ );
38
+ });
39
+
40
+ it("indexes skills, tools, and scripts into .well-known/agent-skills/index.json", async () => {
41
+ // 1. Setup mock repo layout
42
+ const skillDir = path.join(
43
+ tempDir,
44
+ ".agents",
45
+ "skills",
46
+ "main",
47
+ "test-skill",
48
+ );
49
+ const toolDir = path.join(tempDir, ".agents", "tools", "main");
50
+ const scriptDir = path.join(tempDir, ".agents", "scripts", "main");
51
+
52
+ await mkdir(skillDir, { recursive: true });
53
+ await mkdir(toolDir, { recursive: true });
54
+ await mkdir(scriptDir, { recursive: true });
55
+
56
+ const skillContent = `---
57
+ name: test-skill
58
+ description: A test skill for discovery index.
59
+ metadata:
60
+ allowed-tools: test_tool
61
+ execution:
62
+ type: tools
63
+ tools:
64
+ - name: test_tool
65
+ ---
66
+ # Test Skill Body
67
+ `;
68
+ await writeFile(path.join(skillDir, "SKILL.md"), skillContent, "utf8");
69
+
70
+ const toolContent = JSON.stringify({
71
+ name: "test_tool",
72
+ description: "A test tool description.",
73
+ input_schema: { type: "object" },
74
+ });
75
+ await writeFile(path.join(toolDir, "test_tool.json"), toolContent, "utf8");
76
+
77
+ const scriptContent = `export function run() { return 42; }`;
78
+ await writeFile(
79
+ path.join(scriptDir, "test-skill.js"),
80
+ scriptContent,
81
+ "utf8",
82
+ );
83
+
84
+ // 2. Run generator
85
+ const indexDoc = await generateSkillsIndex(tempDir);
86
+
87
+ expect(indexDoc.$schema).toBe(
88
+ "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
89
+ );
90
+ expect(indexDoc.skills).toHaveLength(1);
91
+
92
+ const skill = indexDoc.skills[0];
93
+ expect(skill.name).toBe("test-skill");
94
+ expect(skill.type).toBe("skill-md");
95
+ expect(skill.description).toBe("A test skill for discovery index.");
96
+ expect(skill.url).toBe("../../.agents/skills/main/test-skill/SKILL.md");
97
+ expect(skill.digest).toBe(computeSha256(skillContent));
98
+
99
+ // Check associated tools & scripts on skill
100
+ expect(skill.tools).toHaveLength(1);
101
+ expect(skill.tools[0].name).toBe("test_tool");
102
+ expect(skill.tools[0].url).toBe("../../.agents/tools/main/test_tool.json");
103
+ expect(skill.tools[0].digest).toBe(computeSha256(toolContent));
104
+
105
+ expect(skill.scripts).toHaveLength(1);
106
+ expect(skill.scripts[0].name).toBe("test-skill");
107
+ expect(skill.scripts[0].url).toBe(
108
+ "../../.agents/scripts/main/test-skill.js",
109
+ );
110
+ expect(skill.scripts[0].digest).toBe(computeSha256(scriptContent));
111
+
112
+ // Check top-level arrays
113
+ expect(indexDoc.tools).toHaveLength(1);
114
+ expect(indexDoc.scripts).toHaveLength(1);
115
+ expect(indexDoc.dependencies.tools).toContain(
116
+ "../../.agents/tools/main/test_tool.json",
117
+ );
118
+ expect(indexDoc.dependencies.scripts).toContain(
119
+ "../../.agents/scripts/main/test-skill.js",
120
+ );
121
+
122
+ // Verify written file on disk
123
+ const targetFile = path.join(
124
+ tempDir,
125
+ ".well-known",
126
+ "agent-skills",
127
+ "index.json",
128
+ );
129
+ expect(fs.existsSync(targetFile)).toBe(true);
130
+
131
+ const writtenJson = JSON.parse(
132
+ await fs.promises.readFile(targetFile, "utf8"),
133
+ );
134
+ expect(writtenJson.skills[0].name).toBe("test-skill");
135
+ });
136
+
137
+ it("respects write: false and custom output file options", async () => {
138
+ const skillDir = path.join(tempDir, ".agents", "skills", "main", "minimal");
139
+ await mkdir(skillDir, { recursive: true });
140
+ await writeFile(
141
+ path.join(skillDir, "SKILL.md"),
142
+ "---\nname: minimal\ndescription: Minimal.\n---\n",
143
+ "utf8",
144
+ );
145
+
146
+ const res = await generateSkillsIndex(tempDir, { write: false });
147
+ expect(res.skills).toHaveLength(1);
148
+
149
+ const standardPath = path.join(
150
+ tempDir,
151
+ ".well-known",
152
+ "agent-skills",
153
+ "index.json",
154
+ );
155
+ expect(fs.existsSync(standardPath)).toBe(false);
156
+
157
+ const customOut = path.join(tempDir, "custom", "index.json");
158
+ await generateSkillsIndex(tempDir, { outFile: customOut });
159
+ expect(fs.existsSync(customOut)).toBe(true);
160
+ });
161
+
162
+ it("resolves site metadata from parent directory when indexing a subfolder like dist/public", async () => {
163
+ await writeFile(
164
+ path.join(tempDir, "shadow-claw.config.json"),
165
+ JSON.stringify({
166
+ site: {
167
+ title: "My Custom Knowledge Hub",
168
+ description: "A custom description for testing metadata resolution.",
169
+ },
170
+ }),
171
+ "utf8",
172
+ );
173
+
174
+ const distPublicDir = path.join(tempDir, "dist", "public");
175
+ const skillDir = path.join(
176
+ distPublicDir,
177
+ ".agents",
178
+ "skills",
179
+ "main",
180
+ "custom",
181
+ );
182
+ await mkdir(skillDir, { recursive: true });
183
+ await writeFile(
184
+ path.join(skillDir, "SKILL.md"),
185
+ "---\nname: custom\ndescription: Custom skill.\n---\n",
186
+ "utf8",
187
+ );
188
+
189
+ const res = await generateSkillsIndex(distPublicDir, { write: false });
190
+ expect(res.name).toBe("My Custom Knowledge Hub");
191
+ expect(res.description).toBe(
192
+ "A custom description for testing metadata resolution.",
193
+ );
194
+ expect(res.skills).toHaveLength(1);
195
+ expect(res.skills[0].name).toBe("custom");
196
+ expect(res.skills[0].url).toBe("../../.agents/skills/main/custom/SKILL.md");
197
+ });
198
+
199
+ it("discovers bundled skills when toolchainRoot is provided and merges them without duplicates", async () => {
200
+ const mockToolchain = path.join(tempDir, "toolchain");
201
+ const bundledSkillDir = path.join(
202
+ mockToolchain,
203
+ ".agents",
204
+ "skills",
205
+ "main",
206
+ "skill-creator",
207
+ );
208
+ await mkdir(bundledSkillDir, { recursive: true });
209
+ await writeFile(
210
+ path.join(bundledSkillDir, "SKILL.md"),
211
+ "---\nname: skill-creator\ndescription: Bundled skill creator.\n---\n",
212
+ "utf8",
213
+ );
214
+
215
+ const mockContent = path.join(tempDir, "content");
216
+ const contentSkillDir = path.join(
217
+ mockContent,
218
+ ".agents",
219
+ "skills",
220
+ "main",
221
+ "my-skill",
222
+ );
223
+ await mkdir(contentSkillDir, { recursive: true });
224
+ await writeFile(
225
+ path.join(contentSkillDir, "SKILL.md"),
226
+ "---\nname: my-skill\ndescription: Custom skill.\n---\n",
227
+ "utf8",
228
+ );
229
+
230
+ const res = await generateSkillsIndex(mockContent, {
231
+ toolchainRoot: mockToolchain,
232
+ write: false,
233
+ });
234
+ expect(res.skills).toHaveLength(2);
235
+ expect(res.skills.map((s) => s.name)).toEqual(
236
+ expect.arrayContaining(["my-skill", "skill-creator"]),
237
+ );
238
+ const creatorSkill = res.skills.find((s) => s.name === "skill-creator");
239
+ expect(creatorSkill.url).toBe(
240
+ "../../.agents/skills/main/skill-creator/SKILL.md",
241
+ );
242
+ });
243
+ });
@@ -9,6 +9,7 @@ describe("getApprovedCustomElementScripts", () => {
9
9
  "https://bad.example.com/x.mjs",
10
10
  "https://allowed.example.com/x.mjs",
11
11
  { src: "local.mjs" },
12
+ { src: "adapter.mjs", hasInit: true },
12
13
  ],
13
14
  ["allowed.example.com"],
14
15
  (message) => warns.push(message),
@@ -17,6 +18,7 @@ describe("getApprovedCustomElementScripts", () => {
17
18
  expect(approved).toEqual([
18
19
  "https://allowed.example.com/x.mjs",
19
20
  { src: "local.mjs" },
21
+ { src: "adapter.mjs", hasInit: true },
20
22
  ]);
21
23
  expect(warns).toEqual([
22
24
  "[Security] Skipping script from unapproved domain during build: https://bad.example.com/x.mjs",