shadow-claw 1.23.3 → 1.24.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 (286) hide show
  1. package/README.md +42 -5
  2. package/bin/assert-version-bump.mjs +60 -38
  3. package/bin/assert-version-bump.test.mjs +74 -11
  4. package/bin/cli.mjs +166 -0
  5. package/bin/cli.test.mjs +60 -0
  6. package/bin/commands/backup.mjs +70 -0
  7. package/bin/commands/clients.mjs +41 -0
  8. package/bin/commands/peer-id.mjs +76 -0
  9. package/bin/commands/peer-id.test.mjs +104 -0
  10. package/bin/commands/send.mjs +51 -0
  11. package/bin/commands/tasks.mjs +59 -0
  12. package/bin/commands/webrtc-listen.mjs +254 -0
  13. package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
  14. package/bin/rolldown.config.test.mjs +401 -8
  15. package/bin/utils/control-client.mjs +243 -0
  16. package/bin/utils/control-client.test.mjs +83 -0
  17. package/bin/utils/ulid.mjs +45 -0
  18. package/bin/utils/webrtc-control-client.mjs +699 -0
  19. package/bin/utils/webrtc-control-client.test.mjs +318 -0
  20. package/dist/electron/main.cjs +1 -1
  21. package/dist/public/AGENTS.md +5 -3
  22. package/dist/public/README.md +42 -5
  23. package/dist/public/agent.worker.js +6 -6
  24. package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
  25. package/dist/public/backup-controller-C_s9in2t.js +1 -0
  26. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
  27. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
  28. package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
  29. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
  30. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  31. package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
  32. package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
  33. package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
  34. package/dist/public/config-CrHchneq.js +1 -0
  35. package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
  36. package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
  37. package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
  38. package/dist/public/docs/README.md +3 -1
  39. package/dist/public/docs/architecture/overview.md +12 -12
  40. package/dist/public/docs/architecture/storage.md +5 -2
  41. package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
  42. package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
  43. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  44. package/dist/public/docs/example/article/index.html +2 -2
  45. package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
  46. package/dist/public/docs/guides/server-development-configuration.md +24 -0
  47. package/dist/public/docs/publishing/index.html +19 -76
  48. package/dist/public/docs/skill-creator/index.html +2 -2
  49. package/dist/public/docs/subsystems/backup.md +56 -0
  50. package/dist/public/docs/subsystems/cli.md +124 -0
  51. package/dist/public/docs/subsystems/control-plane.md +158 -0
  52. package/dist/public/docs/subsystems/notifications.md +54 -2
  53. package/dist/public/docs/subsystems/reactive-ui.md +1 -0
  54. package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
  55. package/dist/public/e2e/README.md +5 -1
  56. package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
  57. package/dist/public/file-viewer-D0eflgt6.js +1 -0
  58. package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
  59. package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
  60. package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
  61. package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
  62. package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
  63. package/dist/public/index.html +2 -2
  64. package/dist/public/index.js +1 -1
  65. package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
  66. package/dist/public/initControlPlane-C9jpnWev.js +1 -0
  67. package/dist/public/llms.txt +1 -1
  68. package/dist/public/main/index.html +2 -2
  69. package/dist/public/main/memory/index.html +2 -2
  70. package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
  71. package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
  72. package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
  73. package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
  74. package/dist/public/orchestrator-nJj59RqD.js +15 -0
  75. package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
  76. package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
  77. package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
  78. package/dist/public/service-worker/fetch-proxy.js +1 -1
  79. package/dist/public/service-worker/push-handler.js +1 -1
  80. package/dist/public/service-worker.js +1 -1
  81. package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
  82. package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
  83. package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
  84. package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
  85. package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
  86. package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
  87. package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
  88. package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
  89. package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
  90. package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
  91. package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
  92. package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
  93. package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
  94. package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
  95. package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
  96. package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
  97. package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
  98. package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
  99. package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
  100. package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
  101. package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
  102. package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
  103. package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
  104. package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
  105. package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
  106. package/dist/public/static-main-manifest.json +1 -1
  107. package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
  108. package/dist/server.js +286 -189
  109. package/package.json +2 -1
  110. package/src/__mocks__/jszip.cjs +15 -5
  111. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
  112. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
  113. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
  114. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
  115. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
  116. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
  117. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
  118. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
  119. package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
  120. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
  121. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
  122. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
  123. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
  124. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
  125. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
  126. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
  127. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
  128. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  129. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
  130. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
  131. package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
  132. package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
  133. package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
  134. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
  135. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
  136. package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
  137. package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
  138. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
  139. package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
  140. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
  141. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
  142. package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
  143. package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
  144. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
  145. package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
  146. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
  147. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
  148. package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
  149. package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
  150. package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
  151. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
  152. package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
  153. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
  154. package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
  155. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
  156. package/src/config/config.test.ts +9 -0
  157. package/src/config/config.ts +15 -0
  158. package/src/core/app-routes.test.ts +56 -0
  159. package/src/core/app-routes.ts +8 -1
  160. package/src/core/backup-controller.test.ts +101 -0
  161. package/src/core/backup-controller.ts +137 -0
  162. package/src/core/control-plane-client.test.ts +408 -0
  163. package/src/core/control-plane-client.ts +425 -0
  164. package/src/core/orchestrator/orchestrator.ts +4 -0
  165. package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
  166. package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
  167. package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
  168. package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
  169. package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
  170. package/src/core/utils/initControlPlane.test.ts +276 -0
  171. package/src/core/utils/initControlPlane.ts +437 -0
  172. package/src/core/utils/initializeApp.ts +18 -0
  173. package/src/db/getSession.test.ts +13 -1
  174. package/src/db/openDatabase.test.ts +10 -1
  175. package/src/db/openDatabase.ts +3 -1
  176. package/src/server/app.test.ts +12 -0
  177. package/src/server/app.ts +18 -8
  178. package/src/server/client-registry.test.ts +250 -0
  179. package/src/server/client-registry.ts +477 -0
  180. package/src/server/config.ts +15 -0
  181. package/src/server/control-plane-types.ts +120 -0
  182. package/src/server/control-plane.test.ts +472 -0
  183. package/src/server/control-plane.ts +581 -0
  184. package/src/server/routes/backup.test.ts +263 -0
  185. package/src/server/routes/backup.ts +262 -0
  186. package/src/server/routes/integrations-email.test.ts +532 -0
  187. package/src/server/routes/llamafile.test.ts +172 -48
  188. package/src/server/routes/mesh-llm.test.ts +192 -0
  189. package/src/server/routes/ollama.test.ts +49 -0
  190. package/src/server/server-peer.test.ts +86 -0
  191. package/src/server/server-peer.ts +330 -0
  192. package/src/server/server.ts +35 -0
  193. package/src/server/services/llamafile-manager.test.ts +230 -6
  194. package/src/server/services/transformers-runtime.test.ts +186 -18
  195. package/src/server/webrtc.test.ts +21 -0
  196. package/src/server/webrtc.ts +72 -0
  197. package/src/service-worker/fetch-proxy.test.ts +64 -1
  198. package/src/service-worker/push-handler.ts +39 -0
  199. package/src/shell/vm.test.ts +744 -12
  200. package/src/storage/ensureMainGroupIndex.test.ts +153 -0
  201. package/src/storage/getGroupFile.test.ts +66 -0
  202. package/src/storage/memoryStorage.test.ts +82 -2
  203. package/src/storage/migrateLegacyOpfs.test.ts +327 -0
  204. package/src/storage/migrateLegacyOpfs.ts +117 -0
  205. package/src/storage/writeGroupFileBytes.test.ts +143 -0
  206. package/src/stores/chat-ui.test.ts +20 -0
  207. package/src/stores/orchestrator.test.ts +134 -0
  208. package/src/stores/toast.test.ts +50 -0
  209. package/src/stores/tools.test.ts +147 -10
  210. package/src/subsystems/channels/imessage.test.ts +59 -0
  211. package/src/subsystems/channels/peerjs.test.ts +500 -62
  212. package/src/subsystems/channels/peerjs.ts +71 -4
  213. package/src/subsystems/channels/telegram.test.ts +177 -0
  214. package/src/subsystems/email/catalog.test.ts +1 -0
  215. package/src/subsystems/email/connections.test.ts +39 -1
  216. package/src/subsystems/git/credentials.test.ts +160 -825
  217. package/src/subsystems/git/git.test.ts +463 -0
  218. package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
  219. package/src/subsystems/notifications/push-routes.test.ts +33 -0
  220. package/src/subsystems/notifications/push-routes.ts +23 -0
  221. package/src/subsystems/notifications/push-store.ts +1 -1
  222. package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
  223. package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
  224. package/src/subsystems/providers/model-registry.test.ts +115 -0
  225. package/src/subsystems/providers/providers.test.ts +94 -1
  226. package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
  227. package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
  228. package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
  229. package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
  230. package/src/subsystems/skills/activateSkill.test.ts +101 -0
  231. package/src/subsystems/skills/discoverSkills.test.ts +251 -0
  232. package/src/subsystems/tools/cron.test.ts +64 -0
  233. package/src/subsystems/tools/declarative.test.ts +255 -89
  234. package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
  235. package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
  236. package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
  237. package/src/testing/jest-setup.ts +328 -40
  238. package/src/testing/mock-v86.mjs +49 -0
  239. package/src/ui/a2ui/types.ts +4 -3
  240. package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
  241. package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
  242. package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
  243. package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
  244. package/src/worker/tools/email/email.test.ts +193 -8
  245. package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
  246. package/src/worker/tools/git/git.test.ts +214 -3
  247. package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
  248. package/src/worker/tools/rooms/leave-room.test.ts +40 -0
  249. package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
  250. package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
  251. package/src/worker/tools/ui/ask-user.test.ts +50 -0
  252. package/src/worker/tools/ui/clear-chat.test.ts +19 -0
  253. package/src/worker/tools/ui/get-current-time.test.ts +19 -0
  254. package/src/worker/tools/ui/javascript.test.ts +116 -0
  255. package/src/worker/tools/ui/list-components.test.ts +10 -0
  256. package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
  257. package/src/worker/tools/ui/manage-tools.test.ts +34 -0
  258. package/src/worker/tools/ui/render-component.test.ts +346 -0
  259. package/src/worker/tools/ui/send-notification.test.ts +40 -0
  260. package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
  261. package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
  262. package/src/worker/tools/workspace/attach-file.test.ts +84 -0
  263. package/src/worker/tools/workspace/copy-file.test.ts +69 -0
  264. package/src/worker/tools/workspace/create-directory.test.ts +40 -0
  265. package/src/worker/tools/workspace/delete-file.test.ts +73 -0
  266. package/src/worker/tools/workspace/diff-files.test.ts +94 -0
  267. package/src/worker/tools/workspace/list-files.test.ts +36 -0
  268. package/src/worker/tools/workspace/move-file.test.ts +66 -0
  269. package/src/worker/tools/workspace/open-file.test.ts +57 -0
  270. package/src/worker/tools/workspace/patch-file.test.ts +81 -0
  271. package/src/worker/tools/workspace/read-file.test.ts +116 -0
  272. package/src/worker/tools/workspace/send-file.test.ts +82 -0
  273. package/src/worker/tools/workspace/update-memory.test.ts +29 -0
  274. package/src/worker/tools/workspace/write-file.test.ts +28 -0
  275. package/src/worker/utils/rate-limit.test.ts +199 -0
  276. package/src/worker/utils/tool-state.test.ts +29 -0
  277. package/src/worker/utils/transformers-js-utils.test.ts +59 -0
  278. package/dist/public/config-AL77QAx4.js +0 -1
  279. package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
  280. package/dist/public/file-viewer-CCSABMsE.js +0 -1
  281. package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
  282. package/dist/public/orchestrator-4su7iyWj.js +0 -15
  283. package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
  284. package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
  285. package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
  286. package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
@@ -1,17 +1,410 @@
1
- import { describe, expect, it } from "@jest/globals";
1
+ import fs from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ it,
10
+ jest,
11
+ } from "@jest/globals";
2
12
 
3
13
  import config from "../rolldown.config.mjs";
4
14
 
5
- describe("rolldown bundle splitting", () => {
6
- it("enables code splitting for frontend and agent worker bundles", () => {
7
- const frontend = config.find(
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+
18
+ describe("rolldown.config.mjs", () => {
19
+ describe("bundle configurations and splitting", () => {
20
+ it("exports an array of build configs", () => {
21
+ expect(Array.isArray(config)).toBe(true);
22
+ expect(config.length).toBeGreaterThanOrEqual(8);
23
+ });
24
+
25
+ it("enables code splitting for frontend and agent worker bundles", () => {
26
+ const frontend = config.find(
27
+ (entry) => entry.input === "src/core/index.ts",
28
+ );
29
+ const agentWorker = config.find(
30
+ (entry) => entry.input === "src/worker/worker.ts",
31
+ );
32
+
33
+ expect(frontend?.output?.codeSplitting).toBe(true);
34
+ expect(agentWorker?.output?.codeSplitting).toBe(true);
35
+ });
36
+
37
+ it("configures theme-init as iife and server/electron as node targets", () => {
38
+ const themeInit = config.find(
39
+ (entry) => entry.input === "src/core/theme-init.ts",
40
+ );
41
+ const server = config.find(
42
+ (entry) => entry.input === "src/server/server.ts",
43
+ );
44
+ const electron = config.find(
45
+ (entry) => entry.input === "electron/main.ts",
46
+ );
47
+
48
+ expect(themeInit?.output?.format).toBe("iife");
49
+ expect(themeInit?.output?.name).toBe("ShadowClawThemeInit");
50
+ expect(server?.platform).toBe("node");
51
+ expect(electron?.platform).toBe("node");
52
+ expect(server?.external).toContain("express");
53
+ expect(electron?.external).toContain("electron");
54
+ });
55
+ });
56
+
57
+ describe("aliasTurndownPlugin", () => {
58
+ const workerConfig = config.find(
59
+ (entry) => entry.input === "src/worker/worker.ts",
60
+ );
61
+ const turndownPlugin = workerConfig?.plugins?.find(
62
+ (p) => p && p.name === "alias-turndown-for-worker",
63
+ );
64
+
65
+ it("resolves turndown and @mixmark-io/domino IDs", () => {
66
+ expect(turndownPlugin).toBeDefined();
67
+ expect(turndownPlugin.resolveId("turndown")).toContain("turndown.es.js");
68
+ expect(turndownPlugin.resolveId("@mixmark-io/domino")).toContain(
69
+ "domino",
70
+ );
71
+ expect(turndownPlugin.resolveId("other-module")).toBeNull();
72
+ });
73
+
74
+ it("transforms turndown.es.js with domino ESM import", () => {
75
+ const code =
76
+ "var domino = require('@mixmark-io/domino');\nconsole.log(domino);";
77
+ const result = turndownPlugin.transform(
78
+ code,
79
+ "/path/to/node_modules/turndown/lib/turndown.es.js",
80
+ );
81
+ expect(result).not.toBeNull();
82
+ expect(result.code).toContain(
83
+ "import _domino_import from '@mixmark-io/domino';",
84
+ );
85
+ expect(result.code).toContain("var domino = _domino_import;");
86
+
87
+ // Non-matching id
88
+ expect(
89
+ turndownPlugin.transform(code, "/path/to/some-other-file.js"),
90
+ ).toBeNull();
91
+ });
92
+ });
93
+
94
+ describe("injectGlobalShimPlugin", () => {
95
+ const frontendConfig = config.find(
8
96
  (entry) => entry.input === "src/core/index.ts",
9
97
  );
10
- const agentWorker = config.find(
11
- (entry) => entry.input === "src/worker/worker.ts",
98
+ const globalShimPlugin = frontendConfig?.plugins?.find(
99
+ (p) => p && p.name === "inject-global-shim",
100
+ );
101
+
102
+ it("ignores non-node_modules or non-js files", () => {
103
+ expect(globalShimPlugin).toBeDefined();
104
+ expect(
105
+ globalShimPlugin.transform("global.test = 1;", "/src/app.ts"),
106
+ ).toBeNull();
107
+ expect(
108
+ globalShimPlugin.transform(
109
+ "global.test = 1;",
110
+ "/node_modules/pkg/index.mjs",
111
+ ),
112
+ ).toBeNull();
113
+ });
114
+
115
+ it("skips files without global keyword", () => {
116
+ expect(
117
+ globalShimPlugin.transform(
118
+ "const a = 1;",
119
+ "/node_modules/pkg/index.js",
120
+ ),
121
+ ).toBeNull();
122
+ });
123
+
124
+ it("replaces standalone global with globalThis in node_modules JS files", () => {
125
+ const input = "var g = typeof global !== 'undefined' ? global : window;";
126
+ const result = globalShimPlugin.transform(
127
+ input,
128
+ "/node_modules/randombytes/browser.js",
129
+ );
130
+ expect(result).not.toBeNull();
131
+ expect(result.code).toContain("globalThis");
132
+ });
133
+
134
+ it("returns null if global is only present as member expression (.global)", () => {
135
+ const input = "const x = window.global;";
136
+ const result = globalShimPlugin.transform(
137
+ input,
138
+ "/node_modules/pkg/index.js",
139
+ );
140
+ expect(result).toBeNull();
141
+ });
142
+ });
143
+
144
+ describe("patchGrayMatterEvalPlugin", () => {
145
+ const frontendConfig = config.find(
146
+ (entry) => entry.input === "src/core/index.ts",
147
+ );
148
+ const grayMatterPlugin = frontendConfig?.plugins?.find(
149
+ (p) => p && p.name === "patch-gray-matter-direct-eval",
150
+ );
151
+
152
+ it("ignores files not matching gray-matter/lib/engines.js", () => {
153
+ expect(grayMatterPlugin).toBeDefined();
154
+ expect(
155
+ grayMatterPlugin.transform(
156
+ "return eval(str) || {};",
157
+ "/src/engines.js",
158
+ ),
159
+ ).toBeNull();
160
+ });
161
+
162
+ it("replaces direct eval in gray-matter engines.js", () => {
163
+ const input = "function js(str) { return eval(str) || {}; }";
164
+ const result = grayMatterPlugin.transform(
165
+ input,
166
+ "/node_modules/gray-matter/lib/engines.js",
167
+ );
168
+ expect(result).not.toBeNull();
169
+ expect(result.code).toContain("return (0, eval)(str) || {};");
170
+ });
171
+
172
+ it("returns null if code in gray-matter engines.js does not contain target eval", () => {
173
+ const input = "function js(str) { return {}; }";
174
+ const result = grayMatterPlugin.transform(
175
+ input,
176
+ "/node_modules/gray-matter/lib/engines.js",
177
+ );
178
+ expect(result).toBeNull();
179
+ });
180
+ });
181
+
182
+ describe("replacePrerenderMainMemoryPlugin", () => {
183
+ const frontendConfig = config.find(
184
+ (entry) => entry.input === "src/core/index.ts",
185
+ );
186
+ const prerenderPlugin = frontendConfig?.plugins?.find(
187
+ (p) => p && p.name === "replace-prerender-main-memory",
12
188
  );
13
189
 
14
- expect(frontend?.output?.codeSplitting).toBe(true);
15
- expect(agentWorker?.output?.codeSplitting).toBe(true);
190
+ it("returns null if __PRERENDER_MAIN_MEMORY__ is absent", () => {
191
+ expect(prerenderPlugin).toBeDefined();
192
+ expect(prerenderPlugin.transform("const a = true;")).toBeNull();
193
+ });
194
+
195
+ it("replaces __PRERENDER_MAIN_MEMORY__ with true by default", () => {
196
+ const originalEnv = process.env.PRERENDER_MAIN_MEMORY;
197
+ delete process.env.PRERENDER_MAIN_MEMORY;
198
+ try {
199
+ const input = "if (__PRERENDER_MAIN_MEMORY__) { doSomething(); }";
200
+ const result = prerenderPlugin.transform(input);
201
+ expect(result).not.toBeNull();
202
+ expect(result.code).toBe("if (true) { doSomething(); }");
203
+ } finally {
204
+ if (originalEnv !== undefined) {
205
+ process.env.PRERENDER_MAIN_MEMORY = originalEnv;
206
+ }
207
+ }
208
+ });
209
+
210
+ it("replaces __PRERENDER_MAIN_MEMORY__ with false when env is 'false'", () => {
211
+ const originalEnv = process.env.PRERENDER_MAIN_MEMORY;
212
+ process.env.PRERENDER_MAIN_MEMORY = "false";
213
+ try {
214
+ const input = "if (__PRERENDER_MAIN_MEMORY__) { doSomething(); }";
215
+ const result = prerenderPlugin.transform(input);
216
+ expect(result).not.toBeNull();
217
+ expect(result.code).toBe("if (false) { doSomething(); }");
218
+ } finally {
219
+ if (originalEnv !== undefined) {
220
+ process.env.PRERENDER_MAIN_MEMORY = originalEnv;
221
+ } else {
222
+ delete process.env.PRERENDER_MAIN_MEMORY;
223
+ }
224
+ }
225
+ });
226
+ });
227
+
228
+ describe("rolldownImportAttributes", () => {
229
+ const frontendConfig = config.find(
230
+ (entry) => entry.input === "src/core/index.ts",
231
+ );
232
+ const importAttrPlugin = frontendConfig?.plugins?.find(
233
+ (p) => p && p.name === "rolldown-import-attributes",
234
+ );
235
+
236
+ it("returns null for non-html and non-css files", () => {
237
+ expect(importAttrPlugin).toBeDefined();
238
+ expect(importAttrPlugin.load("file.ts")).toBeNull();
239
+ expect(importAttrPlugin.load("file.js")).toBeNull();
240
+ });
241
+
242
+ it("loads and escapes .html templates with <template> tag", () => {
243
+ const tmpHtmlPath = join(__dirname, "__test_template.html");
244
+ fs.writeFileSync(
245
+ tmpHtmlPath,
246
+ '<template><div class="test">`Hello ${name}`</div></template>',
247
+ "utf-8",
248
+ );
249
+ try {
250
+ const result = importAttrPlugin.load(tmpHtmlPath);
251
+ expect(result).not.toBeNull();
252
+ expect(result.moduleType).toBe("js");
253
+ expect(result.code).toContain("DOMParser().parseFromString");
254
+ expect(result.code).toContain("templateEl.content.children");
255
+ expect(result.code).toContain("\\`Hello \\${name}\\`");
256
+ } finally {
257
+ fs.unlinkSync(tmpHtmlPath);
258
+ }
259
+ });
260
+
261
+ it("loads and escapes .html files without <template> tag", () => {
262
+ const tmpHtmlPath = join(__dirname, "__test_notemplate.html");
263
+ fs.writeFileSync(
264
+ tmpHtmlPath,
265
+ "<html><head><title>Test</title></head><body><p>Body</p></body></html>",
266
+ "utf-8",
267
+ );
268
+ try {
269
+ const result = importAttrPlugin.load(tmpHtmlPath);
270
+ expect(result).not.toBeNull();
271
+ expect(result.code).toContain(
272
+ "Array.from(doc.head.children).concat(Array.from(doc.body.children))",
273
+ );
274
+ } finally {
275
+ fs.unlinkSync(tmpHtmlPath);
276
+ }
277
+ });
278
+
279
+ it("loads and escapes .css stylesheets", () => {
280
+ const tmpCssPath = join(__dirname, "__test_style.css");
281
+ fs.writeFileSync(
282
+ tmpCssPath,
283
+ ':host { color: red; font-family: `Consolas`; content: "${var}"; }',
284
+ "utf-8",
285
+ );
286
+ try {
287
+ const result = importAttrPlugin.load(tmpCssPath);
288
+ expect(result).not.toBeNull();
289
+ expect(result.moduleType).toBe("js");
290
+ expect(result.code).toContain("new CSSStyleSheet()");
291
+ expect(result.code).toContain("sheet.replaceSync");
292
+ } finally {
293
+ fs.unlinkSync(tmpCssPath);
294
+ }
295
+ });
296
+ });
297
+
298
+ describe("swWatchPlugin", () => {
299
+ const frontendConfig = config.find(
300
+ (entry) => entry.input === "src/core/index.ts",
301
+ );
302
+ const watchPlugin = frontendConfig?.plugins?.find(
303
+ (p) => p && p.name === "sw-watch-plugin",
304
+ );
305
+
306
+ beforeEach(() => {
307
+ jest.useFakeTimers();
308
+ });
309
+
310
+ afterEach(() => {
311
+ jest.useRealTimers();
312
+ });
313
+
314
+ it("does nothing when --watch or -w is not in argv", () => {
315
+ expect(watchPlugin).toBeDefined();
316
+ const origArgv = [...process.argv];
317
+ process.argv = ["node", "rolldown"];
318
+ try {
319
+ watchPlugin.writeBundle();
320
+ expect(jest.getTimerCount()).toBe(0);
321
+ } finally {
322
+ process.argv = origArgv;
323
+ }
324
+ });
325
+
326
+ it("schedules post-build steps when --watch is in argv", () => {
327
+ const origArgv = [...process.argv];
328
+ process.argv = ["node", "rolldown", "--watch"];
329
+ try {
330
+ watchPlugin.writeBundle();
331
+ expect(jest.getTimerCount()).toBe(1);
332
+ // calling again resets the timer
333
+ watchPlugin.writeBundle();
334
+ expect(jest.getTimerCount()).toBe(1);
335
+ } finally {
336
+ process.argv = origArgv;
337
+ }
338
+ });
339
+ });
340
+
341
+ describe("copy plugin transforms and rename callbacks", () => {
342
+ const frontendConfig = config.find(
343
+ (entry) => entry.input === "src/core/index.ts",
344
+ );
345
+ const copyPlugin = frontendConfig?.plugins?.find(
346
+ (p) => p && p.name === "copy",
347
+ );
348
+
349
+ it("verifies copy targets and their transform/rename functions", async () => {
350
+ expect(copyPlugin).toBeDefined();
351
+
352
+ const cssTarget = frontendConfig.plugins
353
+ .flatMap((p) => (p && p.targets ? p.targets : []))
354
+ .find((t) => Array.isArray(t.src) && t.src.includes("src/**/*.css"));
355
+
356
+ if (cssTarget && typeof cssTarget.rename === "function") {
357
+ expect(
358
+ cssTarget.rename(
359
+ "shadow-claw.css",
360
+ "",
361
+ "src/components/shadow-claw/shadow-claw.css",
362
+ ),
363
+ ).toBe("components/shadow-claw/shadow-claw.css");
364
+ }
365
+
366
+ const htmlTarget = frontendConfig.plugins
367
+ .flatMap((p) => (p && p.targets ? p.targets : []))
368
+ .find((t) => Array.isArray(t.src) && t.src.includes("src/**/*.html"));
369
+
370
+ if (htmlTarget && typeof htmlTarget.rename === "function") {
371
+ expect(
372
+ htmlTarget.rename(
373
+ "shadow-claw.html",
374
+ "",
375
+ "src/components/shadow-claw/shadow-claw.html",
376
+ ),
377
+ ).toBe("components/shadow-claw/shadow-claw.html");
378
+ }
379
+ });
380
+
381
+ it("transforms files via copy plugin in development and production modes", async () => {
382
+ expect(copyPlugin).toBeDefined();
383
+
384
+ // Test dev mode buildEnd (already loaded with !isProduction)
385
+ if (typeof copyPlugin.buildEnd === "function") {
386
+ await copyPlugin.buildEnd();
387
+ }
388
+
389
+ // Test production mode buildEnd
390
+ const origEnv = process.env.NODE_ENV;
391
+ process.env.NODE_ENV = "production";
392
+ try {
393
+ const prodModule = await import(
394
+ `../rolldown.config.mjs?prodTest=${Date.now()}`
395
+ );
396
+ const prodFrontend = prodModule.default.find(
397
+ (entry) => entry.input === "src/core/index.ts",
398
+ );
399
+ const prodCopy = prodFrontend?.plugins?.find(
400
+ (p) => p && p.name === "copy",
401
+ );
402
+ if (prodCopy && typeof prodCopy.buildEnd === "function") {
403
+ await prodCopy.buildEnd();
404
+ }
405
+ } finally {
406
+ process.env.NODE_ENV = origEnv;
407
+ }
408
+ }, 30000);
16
409
  });
17
410
  });
@@ -0,0 +1,243 @@
1
+ /**
2
+ * ShadowClaw CLI — Shared Control Client
3
+ *
4
+ * Provides a client library for CLI commands to interact with the ShadowClaw
5
+ * server control plane and backup REST endpoints.
6
+ */
7
+
8
+ import http from "node:http";
9
+ import https from "node:https";
10
+ import path from "node:path";
11
+ import fs from "node:fs";
12
+ import { CliWebRtcControlClient } from "./webrtc-control-client.mjs";
13
+
14
+ export function resolveControlToken(customToken) {
15
+ if (customToken && typeof customToken === "string") {
16
+ return customToken.trim();
17
+ }
18
+
19
+ if (process.env.SHADOWCLAW_CONTROL_TOKEN) {
20
+ return process.env.SHADOWCLAW_CONTROL_TOKEN.trim();
21
+ }
22
+
23
+ // 1. Try reading from .cache/control-token.json
24
+ try {
25
+ const candidates = [
26
+ path.join(process.cwd(), ".cache", "control-token.json"),
27
+ path.join(process.cwd(), "control-token.json"),
28
+ ];
29
+ for (const filePath of candidates) {
30
+ if (fs.existsSync(filePath)) {
31
+ const raw = fs.readFileSync(filePath, "utf8");
32
+ const parsed = JSON.parse(raw);
33
+ if (parsed && typeof parsed.token === "string" && parsed.token.trim()) {
34
+ return parsed.token.trim();
35
+ }
36
+ }
37
+ }
38
+ } catch (_) {}
39
+
40
+ // 2. Try reading from SQLite clients.db if available locally
41
+ try {
42
+ let sqlite;
43
+ if (typeof process.getBuiltinModule === "function") {
44
+ sqlite = process.getBuiltinModule("node:sqlite");
45
+ }
46
+ if (sqlite && sqlite.DatabaseSync) {
47
+ const candidates = [
48
+ path.join(process.cwd(), "database", "clients.db"),
49
+ path.join(process.cwd(), ".cache", "database", "clients.db"),
50
+ ];
51
+ for (const dbPath of candidates) {
52
+ if (fs.existsSync(dbPath)) {
53
+ const db = new sqlite.DatabaseSync(dbPath);
54
+ const row = db
55
+ .prepare("SELECT value FROM metadata WHERE key = 'control_token'")
56
+ .get();
57
+ db.close();
58
+ if (row && row.value) {
59
+ return `${row.value}`;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ } catch (_) {}
65
+
66
+ return "";
67
+ }
68
+
69
+ export class CliControlClient {
70
+ constructor(options = {}) {
71
+ this.host = options.host || process.env.SHADOWCLAW_HOST || "127.0.0.1";
72
+ this.port =
73
+ options.port || parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
74
+ this.token = options.token || resolveControlToken(options.token);
75
+ this.protocol = options.protocol || "http";
76
+ this.transport = options.transport || "http";
77
+ this._webrtcClient =
78
+ this.transport === "webrtc"
79
+ ? new CliWebRtcControlClient({
80
+ host: this.host,
81
+ port: this.port,
82
+ path: options.peerPath || "/",
83
+ secure: this.protocol === "https",
84
+ peerId: options.peerId,
85
+ cacheDir: options.cacheDir,
86
+ renewPeerId: Boolean(options.renewPeerId),
87
+ })
88
+ : null;
89
+ }
90
+
91
+ _request({ method = "GET", path: reqPath, body = null, timeout = 30000 }) {
92
+ return new Promise((resolve, reject) => {
93
+ const client = this.protocol === "https" ? https : http;
94
+ const headers = {
95
+ "Content-Type": "application/json",
96
+ };
97
+
98
+ if (this.token) {
99
+ headers["x-control-token"] = this.token;
100
+ }
101
+
102
+ const req = client.request(
103
+ {
104
+ hostname: this.host,
105
+ port: this.port,
106
+ path: reqPath,
107
+ method,
108
+ headers,
109
+ timeout,
110
+ },
111
+ (res) => {
112
+ let data = "";
113
+ res.on("data", (chunk) => (data += chunk));
114
+ res.on("end", () => {
115
+ let parsed = data;
116
+ try {
117
+ parsed = JSON.parse(data);
118
+ } catch (_) {}
119
+
120
+ if (res.statusCode >= 200 && res.statusCode < 300) {
121
+ resolve(parsed);
122
+ } else {
123
+ const errMsg =
124
+ parsed?.error ||
125
+ `HTTP request failed with status ${res.statusCode}: ${data}`;
126
+ reject(new Error(errMsg));
127
+ }
128
+ });
129
+ },
130
+ );
131
+
132
+ req.on("error", (err) => {
133
+ reject(
134
+ new Error(
135
+ `Failed to connect to ShadowClaw server at ${this.host}:${this.port}: ${err.message}`,
136
+ ),
137
+ );
138
+ });
139
+
140
+ req.on("timeout", () => {
141
+ req.destroy();
142
+ reject(new Error(`Request timed out after ${timeout}ms`));
143
+ });
144
+
145
+ if (body) {
146
+ req.write(typeof body === "string" ? body : JSON.stringify(body));
147
+ }
148
+ req.end();
149
+ });
150
+ }
151
+
152
+ async listClients() {
153
+ if (this._webrtcClient) {
154
+ try {
155
+ const webrtcClients = await this._webrtcClient.listClients();
156
+ if (webrtcClients && webrtcClients.length > 0) {
157
+ return webrtcClients;
158
+ }
159
+ } catch (_) {}
160
+
161
+ // Fallback: query HTTP control plane registry if available
162
+ try {
163
+ const data = await this._request({
164
+ path: "/api/control/clients",
165
+ method: "GET",
166
+ });
167
+ return data.clients || [];
168
+ } catch (_) {
169
+ return [];
170
+ }
171
+ }
172
+
173
+ const data = await this._request({
174
+ path: "/api/control/clients",
175
+ method: "GET",
176
+ });
177
+ return data.clients || [];
178
+ }
179
+
180
+ async sendCommand(clientId, action, args = {}, timeoutMs = 30000) {
181
+ if (this._webrtcClient) {
182
+ let targetPeerId = clientId;
183
+ // If the target looks like a control-plane client ID ("client-*"), try to
184
+ // resolve the PeerJS peer ID via the HTTP registry. Otherwise treat it
185
+ // directly as a PeerJS peer ID so WebRTC works without a control plane.
186
+ if (clientId && clientId.startsWith("client-")) {
187
+ try {
188
+ const clients = await this.listClients();
189
+ const match = clients.find((c) => c.clientId === clientId);
190
+ if (match && match.peerId) {
191
+ targetPeerId = match.peerId;
192
+ } else if (match && !match.peerId) {
193
+ console.warn(
194
+ `Warning: client "${clientId}" has no PeerJS peer ID registered.\n` +
195
+ `Ensure the browser has PeerJS enabled in Settings → WebRTC/PeerJS and has connected to the signaling server at least once.\n` +
196
+ `Alternatively, pass the browser's PeerJS peer ID directly with --client <peerId>.`,
197
+ );
198
+ }
199
+ } catch (_) {
200
+ // Control plane unreachable — fall through and attempt direct connection
201
+ // (will fail if clientId is truly a client-* and not a PeerJS peer ID)
202
+ }
203
+ }
204
+ return this._webrtcClient.sendCommand(
205
+ targetPeerId,
206
+ action,
207
+ args,
208
+ timeoutMs,
209
+ );
210
+ }
211
+
212
+ const data = await this._request({
213
+ path: "/api/control/command",
214
+ method: "POST",
215
+ body: {
216
+ clientId,
217
+ action,
218
+ args,
219
+ timeoutMs,
220
+ },
221
+ timeout: timeoutMs + 5000,
222
+ });
223
+ return data;
224
+ }
225
+
226
+ async listBackups(clientId) {
227
+ const query = clientId ? `?clientId=${encodeURIComponent(clientId)}` : "";
228
+ const data = await this._request({
229
+ path: `/api/backup/list${query}`,
230
+ method: "GET",
231
+ });
232
+ return data.backups || [];
233
+ }
234
+
235
+ async deleteBackup(backupId, clientId) {
236
+ const query = clientId ? `?clientId=${encodeURIComponent(clientId)}` : "";
237
+ const data = await this._request({
238
+ path: `/api/backup/${encodeURIComponent(backupId)}${query}`,
239
+ method: "DELETE",
240
+ });
241
+ return data;
242
+ }
243
+ }