shadow-claw 1.34.5 → 1.35.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 (193) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +5 -1
  4. package/dist/cli/chunk-BCLxzLO92.js +5 -0
  5. package/dist/cli/chunk-BHNCi3ui2.js +1 -0
  6. package/dist/cli/chunk-BMPhLSn8.js +1 -0
  7. package/dist/cli/chunk-BMW6QB5-2.js +27 -0
  8. package/dist/cli/chunk-BQCWtrzh.js +1 -0
  9. package/dist/cli/chunk-BQCWtrzh2.js +1 -0
  10. package/dist/cli/chunk-BdJUjMuz.js +1 -0
  11. package/dist/cli/chunk-C47WdxKx2.js +1 -0
  12. package/dist/cli/{chunk-D9dku_3t.js → chunk-CAqvQuGr.js} +1 -1
  13. package/dist/cli/{chunk-DoVxObGa2.js → chunk-CHzhUS1N2.js} +1 -1
  14. package/dist/cli/chunk-CUMlq7Jw2.js +263 -0
  15. package/dist/cli/chunk-CXi9LU9e2.js +110 -0
  16. package/dist/cli/chunk-ChefnX-r.js +110 -0
  17. package/dist/cli/{chunk-zvi71-77.js → chunk-D3JxbzjI2.js} +15 -15
  18. package/dist/cli/chunk-D7VYMl092.js +1 -0
  19. package/dist/cli/chunk-DNIduiJ82.js +91 -0
  20. package/dist/cli/{chunk-C4UfzPK4.js → chunk-DTbrldk_.js} +1 -1
  21. package/dist/cli/chunk-DZ8YBXax.js +969 -0
  22. package/dist/cli/chunk-DduRvw8B2.js +1 -0
  23. package/dist/cli/chunk-DnYxGjdu2.js +1 -0
  24. package/dist/cli/chunk-Ea7nXmgb2.js +1 -0
  25. package/dist/cli/{chunk-BRiGkTIi.js → chunk-EohNY8vW.js} +1 -1
  26. package/dist/cli/chunk-LIIpZlwP2.js +1 -0
  27. package/dist/cli/chunk-LIJZHJml2.js +1 -0
  28. package/dist/cli/chunk-MHBh2BgF2.js +91 -0
  29. package/dist/cli/{chunk-BQ7lHA7c.js → chunk-SKgLGyTY.js} +1 -1
  30. package/dist/cli/chunk-kcJSXcbq2.js +1 -0
  31. package/dist/cli/cli.js +12 -11
  32. package/dist/headless-agent.js +191 -187
  33. package/dist/lib/components/index.js +21 -21
  34. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  35. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  36. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  37. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  38. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  39. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  40. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  41. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  42. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  43. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  44. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  45. package/dist/lib/components/shadow-claw-channels.js +1 -1
  46. package/dist/lib/components/shadow-claw-chat.js +1 -1
  47. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  48. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  49. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  50. package/dist/lib/components/shadow-claw-files.js +1 -1
  51. package/dist/lib/components/shadow-claw-git.js +1 -1
  52. package/dist/lib/components/shadow-claw-llm.js +1 -1
  53. package/dist/lib/components/shadow-claw-networking.js +1 -1
  54. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  55. package/dist/lib/components/shadow-claw-pages.js +1 -1
  56. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  57. package/dist/lib/components/shadow-claw-settings.js +1 -1
  58. package/dist/lib/components/shadow-claw-storage.js +1 -1
  59. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  60. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  61. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  62. package/dist/lib/components/shadow-claw-tools.js +1 -1
  63. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  64. package/dist/lib/components/shadow-claw.js +1 -1
  65. package/dist/lib/{configurePeerJs-DSRcNp3v.js → configurePeerJs-CdPzmpyI.js} +2 -2
  66. package/dist/lib/{configurePeerJs-DSRcNp3v.js.map → configurePeerJs-CdPzmpyI.js.map} +1 -1
  67. package/dist/lib/index.js +21 -21
  68. package/dist/lib/{initControlPlane-CWfE_Oan.js → initControlPlane-BLymRW34.js} +7 -7
  69. package/dist/lib/{initControlPlane-CWfE_Oan.js.map → initControlPlane-BLymRW34.js.map} +1 -1
  70. package/dist/lib/{orchestrator-BEUug1TW.js → orchestrator-DZkhJj7f.js} +13 -2
  71. package/dist/lib/{orchestrator-BEUug1TW.js.map → orchestrator-DZkhJj7f.js.map} +1 -1
  72. package/dist/lib/{orchestrator-CGUzfGbE.js → orchestrator-ZsQTRknI.js} +5 -5
  73. package/dist/lib/{orchestrator-CGUzfGbE.js.map → orchestrator-ZsQTRknI.js.map} +1 -1
  74. package/dist/lib/{peerjs-BSD88rT3.js → peerjs-CJYfnR5V.js} +3 -3
  75. package/dist/lib/{peerjs-BSD88rT3.js.map → peerjs-CJYfnR5V.js.map} +1 -1
  76. package/dist/lib/{push-client-CC3qifgc.js → push-client-C9hIPqLZ.js} +2 -2
  77. package/dist/lib/{push-client-CC3qifgc.js.map → push-client-C9hIPqLZ.js.map} +1 -1
  78. package/dist/lib/{shadow-claw-BWS-UVnE.js → shadow-claw-8qL2-QoK.js} +4 -4
  79. package/dist/lib/{shadow-claw-BWS-UVnE.js.map → shadow-claw-8qL2-QoK.js.map} +1 -1
  80. package/dist/lib/{shadow-claw-channel-config-DwMn3nFJ.js → shadow-claw-channel-config-CvnbqGBy.js} +3 -3
  81. package/dist/lib/{shadow-claw-channel-config-DwMn3nFJ.js.map → shadow-claw-channel-config-CvnbqGBy.js.map} +1 -1
  82. package/dist/lib/{shadow-claw-channels-BdE4cUlx.js → shadow-claw-channels-CkCvjWn1.js} +2 -2
  83. package/dist/lib/{shadow-claw-channels-BdE4cUlx.js.map → shadow-claw-channels-CkCvjWn1.js.map} +1 -1
  84. package/dist/lib/{shadow-claw-chat-DqaMoE9C.js → shadow-claw-chat-B1YEHKN3.js} +3 -3
  85. package/dist/lib/{shadow-claw-chat-DqaMoE9C.js.map → shadow-claw-chat-B1YEHKN3.js.map} +1 -1
  86. package/dist/lib/{shadow-claw-control-plane-D3DUDO29.js → shadow-claw-control-plane-jjo789IE.js} +6 -6
  87. package/dist/lib/{shadow-claw-control-plane-D3DUDO29.js.map → shadow-claw-control-plane-jjo789IE.js.map} +1 -1
  88. package/dist/lib/{shadow-claw-conversations-XT-OXogj.js → shadow-claw-conversations-MMT7wYH3.js} +2 -2
  89. package/dist/lib/{shadow-claw-conversations-XT-OXogj.js.map → shadow-claw-conversations-MMT7wYH3.js.map} +1 -1
  90. package/dist/lib/{shadow-claw-file-viewer-DJqxyd97.js → shadow-claw-file-viewer-qSkD1RQK.js} +2 -2
  91. package/dist/lib/{shadow-claw-file-viewer-DJqxyd97.js.map → shadow-claw-file-viewer-qSkD1RQK.js.map} +1 -1
  92. package/dist/lib/{shadow-claw-files-CUEJlA1o.js → shadow-claw-files-Cg8ycQ_N.js} +2 -2
  93. package/dist/lib/{shadow-claw-files-CUEJlA1o.js.map → shadow-claw-files-Cg8ycQ_N.js.map} +1 -1
  94. package/dist/lib/{shadow-claw-git-D_iVcb86.js → shadow-claw-git-gaClz-ZC.js} +3 -3
  95. package/dist/lib/{shadow-claw-git-D_iVcb86.js.map → shadow-claw-git-gaClz-ZC.js.map} +1 -1
  96. package/dist/lib/{shadow-claw-llm-C2msAsyU.js → shadow-claw-llm-DyOz0qzT.js} +2 -2
  97. package/dist/lib/{shadow-claw-llm-C2msAsyU.js.map → shadow-claw-llm-DyOz0qzT.js.map} +1 -1
  98. package/dist/lib/{shadow-claw-networking-D2NvodC-.js → shadow-claw-networking-B2xx0UUR.js} +2 -2
  99. package/dist/lib/{shadow-claw-networking-D2NvodC-.js.map → shadow-claw-networking-B2xx0UUR.js.map} +1 -1
  100. package/dist/lib/{shadow-claw-notifications-BcvVn_vu.js → shadow-claw-notifications-BjKfZy0k.js} +2 -2
  101. package/dist/lib/{shadow-claw-notifications-BcvVn_vu.js.map → shadow-claw-notifications-BjKfZy0k.js.map} +1 -1
  102. package/dist/lib/{shadow-claw-pages-CTMXRhEX.js → shadow-claw-pages-CJ2tsMtL.js} +2 -2
  103. package/dist/lib/{shadow-claw-pages-CTMXRhEX.js.map → shadow-claw-pages-CJ2tsMtL.js.map} +1 -1
  104. package/dist/lib/{shadow-claw-peerjs-C2YvypqM.js → shadow-claw-peerjs-CazCkXQ1.js} +3 -3
  105. package/dist/lib/{shadow-claw-peerjs-C2YvypqM.js.map → shadow-claw-peerjs-CazCkXQ1.js.map} +1 -1
  106. package/dist/lib/{shadow-claw-settings-B0hiDwEt.js → shadow-claw-settings-qro6pO9I.js} +2 -2
  107. package/dist/lib/{shadow-claw-settings-B0hiDwEt.js.map → shadow-claw-settings-qro6pO9I.js.map} +1 -1
  108. package/dist/lib/{shadow-claw-storage-Dt4yg4Q4.js → shadow-claw-storage-ewDdvCGa.js} +2 -2
  109. package/dist/lib/{shadow-claw-storage-Dt4yg4Q4.js.map → shadow-claw-storage-ewDdvCGa.js.map} +1 -1
  110. package/dist/lib/{shadow-claw-task-server-B2gbZaz4.js → shadow-claw-task-server-DpqQcNIs.js} +2 -2
  111. package/dist/lib/{shadow-claw-task-server-B2gbZaz4.js.map → shadow-claw-task-server-DpqQcNIs.js.map} +1 -1
  112. package/dist/lib/{shadow-claw-tasks-Bxq-niuM.js → shadow-claw-tasks-B2fMF4qE.js} +2 -2
  113. package/dist/lib/{shadow-claw-tasks-Bxq-niuM.js.map → shadow-claw-tasks-B2fMF4qE.js.map} +1 -1
  114. package/dist/lib/{shadow-claw-terminal-BfbW3xtg.js → shadow-claw-terminal-B25kQJC3.js} +2 -2
  115. package/dist/lib/{shadow-claw-terminal-BfbW3xtg.js.map → shadow-claw-terminal-B25kQJC3.js.map} +1 -1
  116. package/dist/lib/{shadow-claw-tools-BNSAaK0Y.js → shadow-claw-tools-B0y8JzoA.js} +10 -3
  117. package/dist/lib/{shadow-claw-tools-BNSAaK0Y.js.map → shadow-claw-tools-B0y8JzoA.js.map} +1 -1
  118. package/dist/lib/{shadow-claw-webvm-Dc2KmEXq.js → shadow-claw-webvm-CoCedIza.js} +2 -2
  119. package/dist/lib/{shadow-claw-webvm-Dc2KmEXq.js.map → shadow-claw-webvm-CoCedIza.js.map} +1 -1
  120. package/dist/lib/{syncWebMcpRegistration-BIL83P0Q.js → syncWebMcpRegistration-JKLALq4e.js} +2 -2
  121. package/dist/lib/{syncWebMcpRegistration-BIL83P0Q.js.map → syncWebMcpRegistration-JKLALq4e.js.map} +1 -1
  122. package/dist/lib/worker/utils/sandboxedEval.d.ts +8 -0
  123. package/dist/public/.well-known/mcp/server-card.json +1 -1
  124. package/dist/public/.well-known/mcp.json +1 -1
  125. package/dist/public/AGENTS.md +1 -1
  126. package/dist/public/README.md +5 -1
  127. package/dist/public/agent.worker.js +1 -1
  128. package/dist/public/components/shadow-claw-tools/shadow-claw-tools.html +1 -1
  129. package/dist/public/{configurePeerJs-B925t9C7.js → configurePeerJs-BrHeJGw1.js} +1 -1
  130. package/dist/public/docs/decisions/headless-cli-agent-participant.md +1 -1
  131. package/dist/public/docs/example/article/index.html +1 -1
  132. package/dist/public/docs/publishing/index.html +1 -1
  133. package/dist/public/docs/skill-creator/index.html +1 -1
  134. package/dist/public/docs/subsystems/cli.md +33 -23
  135. package/dist/public/docs/subsystems/skills.md +17 -0
  136. package/dist/public/docs/subsystems/tools.md +1 -1
  137. package/dist/public/e2e/README.md +2 -2
  138. package/dist/public/{e2e-bridge-CE1xleGn.js → e2e-bridge-D_lyH__p.js} +1 -1
  139. package/dist/public/{executeTool-DtTrsZHV.js → executeTool-b6DnRhcv.js} +2 -1
  140. package/dist/public/{file-viewer-LEEStSq-.js → file-viewer-CKrQoxZi.js} +1 -1
  141. package/dist/public/index.html +1 -1
  142. package/dist/public/index.js +1 -1
  143. package/dist/public/{initControlPlane-e2JgxXEb.js → initControlPlane-Dn-XFuxg.js} +1 -1
  144. package/dist/public/llms.txt +2 -0
  145. package/dist/public/main/index.html +1 -1
  146. package/dist/public/main/memory/index.html +1 -1
  147. package/dist/public/{orchestrator-Baif6kmu.js → orchestrator-0BnBtxHO.js} +3 -2
  148. package/dist/public/{orchestrator-TndREZPL.js → orchestrator-C3GKZriH.js} +1 -1
  149. package/dist/public/{peerjs-DE6WF3h2.js → peerjs-CgfLEp2N.js} +1 -1
  150. package/dist/public/{push-client-B_IkTpNm.js → push-client-D6DLiY1s.js} +1 -1
  151. package/dist/public/service-worker.js +1 -1
  152. package/dist/public/{shadow-claw-channels-BCGz36Zl.js → shadow-claw-channels-DSijinZ9.js} +1 -1
  153. package/dist/public/{shadow-claw-chat-H9U9jbqD.js → shadow-claw-chat-BeHlAjnA.js} +1 -1
  154. package/dist/public/{shadow-claw-control-plane-CeKh64s2.js → shadow-claw-control-plane-BfF_AZSg.js} +2 -2
  155. package/dist/public/{shadow-claw-conversations-C26t1HXN.js → shadow-claw-conversations-Bb_2bVLJ.js} +1 -1
  156. package/dist/public/{shadow-claw-DBeXILmk.js → shadow-claw-f71DlRdV.js} +2 -2
  157. package/dist/public/{shadow-claw-file-viewer-DWUoTjkr.js → shadow-claw-file-viewer-B67000Mm.js} +1 -1
  158. package/dist/public/{shadow-claw-files-BQ8DSX-z.js → shadow-claw-files-DeZZYt01.js} +1 -1
  159. package/dist/public/{shadow-claw-git-CIXyISw-.js → shadow-claw-git-Ccy25LuW.js} +2 -2
  160. package/dist/public/{shadow-claw-llm-D_GpOSZE.js → shadow-claw-llm-XjYcTlXT.js} +1 -1
  161. package/dist/public/{shadow-claw-networking-BYp5K3B7.js → shadow-claw-networking-efXQtydQ.js} +1 -1
  162. package/dist/public/{shadow-claw-notifications-ZiXmPweY.js → shadow-claw-notifications-Cq2ZSeJa.js} +1 -1
  163. package/dist/public/{shadow-claw-pages-BHXwf3BD.js → shadow-claw-pages-BVFNSSb1.js} +1 -1
  164. package/dist/public/{shadow-claw-settings-BfvDvt9c.js → shadow-claw-settings-B3sHxPh9.js} +2 -2
  165. package/dist/public/{shadow-claw-storage-Bvn85DII.js → shadow-claw-storage-DNdlzCgC.js} +1 -1
  166. package/dist/public/{shadow-claw-task-server-DZL_d8FC.js → shadow-claw-task-server-BJq0rpju.js} +1 -1
  167. package/dist/public/{shadow-claw-tasks-Cy8jzd8q.js → shadow-claw-tasks-CkF1uMeZ.js} +1 -1
  168. package/dist/public/{shadow-claw-terminal-BgEXr82T.js → shadow-claw-terminal-CnHhcpA-.js} +1 -1
  169. package/dist/public/{shadow-claw-tools-BjnssRSi.js → shadow-claw-tools-iw0fljud.js} +8 -1
  170. package/dist/public/{shadow-claw-webvm-tZ1VDoVA.js → shadow-claw-webvm-Bliu4V__.js} +1 -1
  171. package/dist/public/{syncWebMcpRegistration-CLMvJ4kV.js → syncWebMcpRegistration-D91usEMu.js} +1 -1
  172. package/package.json +1 -1
  173. package/src/cli/cli.ts +26 -2
  174. package/src/cli/commands/agent.test.ts +421 -0
  175. package/src/cli/commands/agent.ts +270 -20
  176. package/src/components/shadow-claw-tools/shadow-claw-tools.html +7 -0
  177. package/src/worker/headless-agent.ts +14 -0
  178. package/src/worker/headless-types.ts +39 -0
  179. package/src/worker/utils/native-eval-executor.test.ts +36 -0
  180. package/src/worker/utils/native-eval-executor.ts +58 -0
  181. package/src/worker/utils/sandboxedEval.ts +36 -0
  182. package/dist/cli/chunk-1BmpU7jO2.js +0 -1
  183. package/dist/cli/chunk-54Euq8Ji2.js +0 -1
  184. package/dist/cli/chunk-BTitwCgT2.js +0 -88
  185. package/dist/cli/chunk-Bjh76Dai.js +0 -1081
  186. package/dist/cli/chunk-C2HeZQ6K.js +0 -1
  187. package/dist/cli/chunk-CxdpafWg.js +0 -1
  188. package/dist/cli/chunk-DEy72ZNV2.js +0 -88
  189. package/dist/cli/chunk-Da1BZyqf.js +0 -1
  190. package/dist/cli/chunk-FHsJD-Sf2.js +0 -27
  191. package/dist/cli/chunk-aWgoVB7x2.js +0 -1
  192. package/dist/cli/chunk-hOceZVB92.js +0 -371
  193. package/dist/cli/chunk-pwNc6VtC2.js +0 -1
@@ -1855,3 +1855,424 @@ describe("runAgentRun — SIGINT cancellation and process cleanup", () => {
1855
1855
  expect(cleanupCalled).toBe(true);
1856
1856
  });
1857
1857
  });
1858
+
1859
+ /**
1860
+ * TDD tests for the `agent import` subcommand.
1861
+ */
1862
+ describe("agent import", () => {
1863
+ let tmpDir: string;
1864
+
1865
+ beforeEach(async () => {
1866
+ tmpDir = await mkdtemp(path.join(tmpdir(), "sc-agent-import-test-"));
1867
+ });
1868
+
1869
+ afterEach(async () => {
1870
+ const { closeSqliteDatabase } =
1871
+ await import("../../db/sqlite/openSqliteDatabase.js").catch(() => ({
1872
+ closeSqliteDatabase: () => {},
1873
+ }));
1874
+ closeSqliteDatabase?.();
1875
+ await rm(tmpDir, { recursive: true, force: true });
1876
+ });
1877
+
1878
+ it("requires a URL argument and returns error if missing", async () => {
1879
+ const { runAgentCommand, runAgentImport } = await import("./agent.js");
1880
+ const resultCommand = await runAgentCommand("import", [], {
1881
+ workspace: tmpDir,
1882
+ quiet: true,
1883
+ });
1884
+ expect(resultCommand.success).toBe(false);
1885
+ expect(resultCommand.error).toMatch(/required/i);
1886
+
1887
+ const resultDirect = await runAgentImport("", {
1888
+ workspace: tmpDir,
1889
+ quiet: true,
1890
+ });
1891
+ expect(resultDirect.success).toBe(false);
1892
+ expect(resultDirect.error).toMatch(/required/i);
1893
+ });
1894
+
1895
+ it("fetches manifest and imports all tools, skills, and scripts by default", async () => {
1896
+ const { runAgentImport, runAgentTools, runAgentSkills } =
1897
+ await import("./agent.js");
1898
+
1899
+ const manifest = {
1900
+ $schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
1901
+ name: "Weather Station Hub",
1902
+ description: "Weather station declarative tools and skills",
1903
+ skills: [
1904
+ {
1905
+ name: "weather-briefing",
1906
+ type: "skill-md",
1907
+ description: "Daily weather briefing",
1908
+ url: "../../.agents/skills/main/weather-briefing/SKILL.md",
1909
+ },
1910
+ ],
1911
+ tools: [
1912
+ {
1913
+ name: "get_current_weather",
1914
+ description: "Fetch real-time weather",
1915
+ url: "../../.agents/tools/main/get_current_weather.json",
1916
+ },
1917
+ ],
1918
+ scripts: [
1919
+ {
1920
+ name: "weather_helper",
1921
+ url: "../../.agents/scripts/main/weather_helper.js",
1922
+ },
1923
+ ],
1924
+ };
1925
+
1926
+ const skillContent = `---
1927
+ name: weather-briefing
1928
+ description: Daily weather briefing
1929
+ user-invocable: true
1930
+ ---
1931
+ # Weather Briefing
1932
+ Fetch today's weather.
1933
+ `;
1934
+
1935
+ const toolContent = JSON.stringify({
1936
+ name: "get_current_weather",
1937
+ description: "Fetch real-time weather",
1938
+ input_schema: {
1939
+ type: "object",
1940
+ properties: {
1941
+ location: { type: "string" },
1942
+ },
1943
+ },
1944
+ execution: {
1945
+ type: "javascript",
1946
+ code: "return JSON.stringify({ temp: 21, condition: 'Sunny' });",
1947
+ },
1948
+ });
1949
+
1950
+ const scriptContent = 'export function helper() { return "weather"; }';
1951
+
1952
+ const mockFetch = jest.fn<any>().mockImplementation(async (url: any) => {
1953
+ const urlStr = String(url);
1954
+ if (urlStr.endsWith("index.json") || urlStr.includes(".well-known")) {
1955
+ return {
1956
+ ok: true,
1957
+ status: 200,
1958
+ statusText: "OK",
1959
+ json: async () => manifest,
1960
+ text: async () => JSON.stringify(manifest),
1961
+ };
1962
+ }
1963
+ if (urlStr.endsWith("get_current_weather.json")) {
1964
+ return {
1965
+ ok: true,
1966
+ status: 200,
1967
+ statusText: "OK",
1968
+ text: async () => toolContent,
1969
+ };
1970
+ }
1971
+ if (urlStr.endsWith("SKILL.md")) {
1972
+ return {
1973
+ ok: true,
1974
+ status: 200,
1975
+ statusText: "OK",
1976
+ text: async () => skillContent,
1977
+ };
1978
+ }
1979
+ if (urlStr.endsWith("weather_helper.js")) {
1980
+ return {
1981
+ ok: true,
1982
+ status: 200,
1983
+ statusText: "OK",
1984
+ text: async () => scriptContent,
1985
+ };
1986
+ }
1987
+ return {
1988
+ ok: false,
1989
+ status: 404,
1990
+ statusText: "Not Found",
1991
+ text: async () => "Not found",
1992
+ };
1993
+ });
1994
+
1995
+ const result = await runAgentImport("https://example.com/weather", {
1996
+ workspace: tmpDir,
1997
+ fetchFn: mockFetch as any,
1998
+ quiet: true,
1999
+ });
2000
+
2001
+ expect(result.success).toBe(true);
2002
+ expect(result.importedCount).toBe(3);
2003
+ expect(result.failedCount).toBe(0);
2004
+
2005
+ // Verify files were written to disk in workspace
2006
+ const toolFile = path.join(
2007
+ tmpDir,
2008
+ ".agents/tools/main/get_current_weather.json",
2009
+ );
2010
+ const skillFile = path.join(
2011
+ tmpDir,
2012
+ ".agents/skills/main/weather-briefing/SKILL.md",
2013
+ );
2014
+ const scriptFile = path.join(
2015
+ tmpDir,
2016
+ ".agents/scripts/main/weather_helper.js",
2017
+ );
2018
+
2019
+ expect(JSON.parse(await readFile(toolFile, "utf8"))).toEqual(
2020
+ JSON.parse(toolContent),
2021
+ );
2022
+ expect(await readFile(skillFile, "utf8")).toBe(skillContent);
2023
+ expect(await readFile(scriptFile, "utf8")).toBe(scriptContent);
2024
+
2025
+ // Verify declarative tools are listed in agent tools
2026
+ const toolsResult = await runAgentTools({ workspace: tmpDir, quiet: true });
2027
+ const importedTool = toolsResult.tools.find(
2028
+ (t: any) => t.name === "get_current_weather",
2029
+ );
2030
+ expect(importedTool).toBeDefined();
2031
+ expect(importedTool.description).toBe("Fetch real-time weather");
2032
+ expect(importedTool.headlessSafe).toBe(true);
2033
+
2034
+ // Verify skills are listed in agent skills
2035
+ const skillsResult = await runAgentSkills({
2036
+ workspace: tmpDir,
2037
+ quiet: true,
2038
+ });
2039
+ const importedSkill = skillsResult.skills.find(
2040
+ (s: any) => s.name === "weather-briefing",
2041
+ );
2042
+ expect(importedSkill).toBeDefined();
2043
+ });
2044
+
2045
+ it("supports selective filtering using options.tools", async () => {
2046
+ const { runAgentImport } = await import("./agent.js");
2047
+
2048
+ const manifest = {
2049
+ $schema: "https://schemas.agentskills.io/discovery/0.2.0/schema.json",
2050
+ name: "Selective Station",
2051
+ tools: [
2052
+ {
2053
+ name: "tool_alpha",
2054
+ description: "Tool Alpha",
2055
+ url: "../../.agents/tools/main/tool_alpha.json",
2056
+ },
2057
+ {
2058
+ name: "tool_beta",
2059
+ description: "Tool Beta",
2060
+ url: "../../.agents/tools/main/tool_beta.json",
2061
+ },
2062
+ ],
2063
+ };
2064
+
2065
+ const makeTool = (name: string) =>
2066
+ JSON.stringify({
2067
+ name,
2068
+ description: `Description of ${name}`,
2069
+ input_schema: { type: "object", properties: {} },
2070
+ execution: { type: "javascript", code: "return 'ok';" },
2071
+ });
2072
+
2073
+ const mockFetch = jest.fn<any>().mockImplementation(async (url: any) => {
2074
+ const urlStr = String(url);
2075
+ if (urlStr.includes(".well-known")) {
2076
+ return {
2077
+ ok: true,
2078
+ status: 200,
2079
+ statusText: "OK",
2080
+ json: async () => manifest,
2081
+ text: async () => JSON.stringify(manifest),
2082
+ };
2083
+ }
2084
+ if (urlStr.endsWith("tool_alpha.json")) {
2085
+ return {
2086
+ ok: true,
2087
+ status: 200,
2088
+ statusText: "OK",
2089
+ text: async () => makeTool("tool_alpha"),
2090
+ };
2091
+ }
2092
+ if (urlStr.endsWith("tool_beta.json")) {
2093
+ return {
2094
+ ok: true,
2095
+ status: 200,
2096
+ statusText: "OK",
2097
+ text: async () => makeTool("tool_beta"),
2098
+ };
2099
+ }
2100
+ return { ok: false, status: 404, statusText: "Not Found" };
2101
+ });
2102
+
2103
+ const result = await runAgentImport("https://example.com/hub", {
2104
+ workspace: tmpDir,
2105
+ tools: "tool_alpha",
2106
+ fetchFn: mockFetch as any,
2107
+ quiet: true,
2108
+ });
2109
+
2110
+ expect(result.success).toBe(true);
2111
+ expect(result.importedCount).toBe(1);
2112
+ expect(result.result?.tools[0].name).toBe("tool_alpha");
2113
+ expect(result.result?.tools[0].status).toBe("imported");
2114
+
2115
+ // tool_alpha exists, tool_beta does not
2116
+ await expect(
2117
+ access(
2118
+ path.join(tmpDir, ".agents/tools/main/tool_alpha.json"),
2119
+ constants.F_OK,
2120
+ ),
2121
+ ).resolves.toBeUndefined();
2122
+ await expect(
2123
+ access(
2124
+ path.join(tmpDir, ".agents/tools/main/tool_beta.json"),
2125
+ constants.F_OK,
2126
+ ),
2127
+ ).rejects.toThrow();
2128
+ });
2129
+
2130
+ it("skips existing files unless overwrite is true", async () => {
2131
+ const { runAgentImport } = await import("./agent.js");
2132
+
2133
+ const manifest = {
2134
+ name: "Overwrite Station",
2135
+ tools: [
2136
+ {
2137
+ name: "tool_target",
2138
+ description: "Target tool",
2139
+ url: "../../.agents/tools/main/tool_target.json",
2140
+ },
2141
+ ],
2142
+ };
2143
+
2144
+ const toolV1 = JSON.stringify({
2145
+ name: "tool_target",
2146
+ description: "Version 1",
2147
+ input_schema: { type: "object", properties: {} },
2148
+ execution: { type: "javascript", code: "return 1;" },
2149
+ });
2150
+
2151
+ const toolV2 = JSON.stringify({
2152
+ name: "tool_target",
2153
+ description: "Version 2",
2154
+ input_schema: { type: "object", properties: {} },
2155
+ execution: { type: "javascript", code: "return 2;" },
2156
+ });
2157
+
2158
+ let currentTool = toolV1;
2159
+ const mockFetch = jest.fn<any>().mockImplementation(async (url: any) => {
2160
+ const urlStr = String(url);
2161
+ if (urlStr.includes(".well-known")) {
2162
+ return {
2163
+ ok: true,
2164
+ status: 200,
2165
+ json: async () => manifest,
2166
+ text: async () => JSON.stringify(manifest),
2167
+ };
2168
+ }
2169
+ return { ok: true, status: 200, text: async () => currentTool };
2170
+ });
2171
+
2172
+ // 1st import: imported
2173
+ const res1 = await runAgentImport("https://example.com", {
2174
+ workspace: tmpDir,
2175
+ fetchFn: mockFetch as any,
2176
+ quiet: true,
2177
+ });
2178
+ expect(res1.importedCount).toBe(1);
2179
+
2180
+ // 2nd import without overwrite: skipped
2181
+ currentTool = toolV2;
2182
+ const res2 = await runAgentImport("https://example.com", {
2183
+ workspace: tmpDir,
2184
+ fetchFn: mockFetch as any,
2185
+ quiet: true,
2186
+ });
2187
+ expect(res2.skippedCount).toBe(1);
2188
+ expect(res2.importedCount).toBe(0);
2189
+ const contentUnchanged = JSON.parse(
2190
+ await readFile(
2191
+ path.join(tmpDir, ".agents/tools/main/tool_target.json"),
2192
+ "utf8",
2193
+ ),
2194
+ );
2195
+ expect(contentUnchanged.description).toBe("Version 1");
2196
+
2197
+ // 3rd import with overwrite: true: imported
2198
+ const res3 = await runAgentImport("https://example.com", {
2199
+ workspace: tmpDir,
2200
+ overwrite: true,
2201
+ fetchFn: mockFetch as any,
2202
+ quiet: true,
2203
+ });
2204
+ expect(res3.importedCount).toBe(1);
2205
+ const contentUpdated = JSON.parse(
2206
+ await readFile(
2207
+ path.join(tmpDir, ".agents/tools/main/tool_target.json"),
2208
+ "utf8",
2209
+ ),
2210
+ );
2211
+ expect(contentUpdated.description).toBe("Version 2");
2212
+ });
2213
+
2214
+ it("handles network failure gracefully", async () => {
2215
+ const { runAgentImport } = await import("./agent.js");
2216
+
2217
+ const mockFetch = jest
2218
+ .fn<any>()
2219
+ .mockRejectedValue(new Error("Network unreachable"));
2220
+
2221
+ const result = await runAgentImport("https://broken.example.com", {
2222
+ workspace: tmpDir,
2223
+ fetchFn: mockFetch as any,
2224
+ quiet: true,
2225
+ });
2226
+
2227
+ expect(result.success).toBe(false);
2228
+ expect(result.error).toMatch(/Network unreachable/i);
2229
+ });
2230
+
2231
+ it("dispatches via runAgentCommand('import', ...)", async () => {
2232
+ const { runAgentCommand } = await import("./agent.js");
2233
+
2234
+ const manifest = {
2235
+ name: "Dispatch Station",
2236
+ tools: [
2237
+ {
2238
+ name: "dispatch_tool",
2239
+ description: "Dispatch tool",
2240
+ url: "../../.agents/tools/main/dispatch_tool.json",
2241
+ },
2242
+ ],
2243
+ };
2244
+
2245
+ const toolJson = JSON.stringify({
2246
+ name: "dispatch_tool",
2247
+ description: "Dispatch test",
2248
+ input_schema: { type: "object", properties: {} },
2249
+ execution: { type: "javascript", code: "return 'dispatched';" },
2250
+ });
2251
+
2252
+ const mockFetch = jest.fn<any>().mockImplementation(async (url: any) => {
2253
+ const urlStr = String(url);
2254
+ if (urlStr.includes(".well-known")) {
2255
+ return {
2256
+ ok: true,
2257
+ status: 200,
2258
+ json: async () => manifest,
2259
+ text: async () => JSON.stringify(manifest),
2260
+ };
2261
+ }
2262
+ return { ok: true, status: 200, text: async () => toolJson };
2263
+ });
2264
+
2265
+ const result = await runAgentCommand(
2266
+ "import",
2267
+ ["https://example.com/dispatch"],
2268
+ {
2269
+ workspace: tmpDir,
2270
+ fetchFn: mockFetch as any,
2271
+ quiet: true,
2272
+ },
2273
+ );
2274
+
2275
+ expect(result.success).toBe(true);
2276
+ expect(result.importedCount).toBe(1);
2277
+ });
2278
+ });