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
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ShadowClaw CLI — `peer-id` command
3
+ * Manages the WebRTC CLI peer ID stored in .cache/cli-peer-id.
4
+ */
5
+
6
+ import {
7
+ getCliPeerIdFilePath,
8
+ getOrCreateCliPeerId,
9
+ readCliPeerId,
10
+ renewCliPeerId,
11
+ } from "../utils/webrtc-control-client.mjs";
12
+
13
+ export async function runPeerIdCommand(action = "get", options = {}) {
14
+ const cacheDir = options.cacheDir;
15
+ const filePath = getCliPeerIdFilePath(cacheDir);
16
+
17
+ const shouldRenew = Boolean(
18
+ options.renew ||
19
+ action === "renew" ||
20
+ action === "generate" ||
21
+ action === "new",
22
+ );
23
+ const customSetId =
24
+ options.set || (action === "set" && options.id ? options.id : null);
25
+
26
+ let peerId = "";
27
+ let isRenewed = false;
28
+
29
+ if (customSetId) {
30
+ peerId = renewCliPeerId(customSetId, cacheDir);
31
+ isRenewed = true;
32
+ } else if (shouldRenew) {
33
+ peerId = renewCliPeerId(undefined, cacheDir);
34
+ isRenewed = true;
35
+ } else {
36
+ const existing = readCliPeerId(cacheDir);
37
+ if (existing) {
38
+ peerId = existing;
39
+ isRenewed = false;
40
+ } else {
41
+ peerId = getOrCreateCliPeerId(undefined, cacheDir);
42
+ isRenewed = true;
43
+ }
44
+ }
45
+
46
+ if (options.quiet) {
47
+ console.log(peerId);
48
+ return { peerId, filePath, isRenewed };
49
+ }
50
+
51
+ if (options.json) {
52
+ console.log(
53
+ JSON.stringify(
54
+ {
55
+ peerId,
56
+ filePath,
57
+ renewed: isRenewed,
58
+ },
59
+ null,
60
+ 2,
61
+ ),
62
+ );
63
+ return { peerId, filePath, isRenewed };
64
+ }
65
+
66
+ if (customSetId) {
67
+ console.log(`WebRTC CLI Peer ID set to: ${peerId}`);
68
+ } else if (isRenewed) {
69
+ console.log(`WebRTC CLI Peer ID created/renewed: ${peerId}`);
70
+ } else {
71
+ console.log(`WebRTC CLI Peer ID: ${peerId}`);
72
+ }
73
+ console.log(`Stored in: ${filePath}`);
74
+
75
+ return { peerId, filePath, isRenewed };
76
+ }
@@ -0,0 +1,104 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import {
5
+ jest,
6
+ describe,
7
+ it,
8
+ expect,
9
+ beforeEach,
10
+ afterEach,
11
+ } from "@jest/globals";
12
+ import { runPeerIdCommand } from "./peer-id.mjs";
13
+ import {
14
+ getCliPeerIdFilePath,
15
+ readCliPeerId,
16
+ } from "../utils/webrtc-control-client.mjs";
17
+
18
+ describe("runPeerIdCommand", () => {
19
+ let tempDir;
20
+ let logSpy;
21
+
22
+ beforeEach(() => {
23
+ tempDir = path.join(
24
+ os.tmpdir(),
25
+ "test-cli-peer-id-" +
26
+ Date.now() +
27
+ "-" +
28
+ Math.random().toString(36).slice(2),
29
+ );
30
+ logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
31
+ });
32
+
33
+ afterEach(() => {
34
+ if (logSpy) logSpy.mockRestore();
35
+ try {
36
+ if (fs.existsSync(tempDir)) {
37
+ fs.rmSync(tempDir, { recursive: true, force: true });
38
+ }
39
+ } catch (_) {}
40
+ });
41
+
42
+ it("creates and retrieves a new peer ID if none exists", async () => {
43
+ const result = await runPeerIdCommand("get", { cacheDir: tempDir });
44
+ expect(result.peerId).toMatch(/^cli-[0-9a-z]+$/);
45
+ expect(result.isRenewed).toBe(true);
46
+
47
+ const saved = readCliPeerId(tempDir);
48
+ expect(saved).toBe(result.peerId);
49
+ });
50
+
51
+ it("returns existing peer ID without changing it on subsequent calls", async () => {
52
+ const res1 = await runPeerIdCommand("get", { cacheDir: tempDir });
53
+ const res2 = await runPeerIdCommand("get", { cacheDir: tempDir });
54
+
55
+ expect(res2.peerId).toBe(res1.peerId);
56
+ expect(res2.isRenewed).toBe(false);
57
+ });
58
+
59
+ it("renews peer ID when action is 'renew'", async () => {
60
+ const res1 = await runPeerIdCommand("get", { cacheDir: tempDir });
61
+ const res2 = await runPeerIdCommand("renew", { cacheDir: tempDir });
62
+
63
+ expect(res2.peerId).not.toBe(res1.peerId);
64
+ expect(res2.isRenewed).toBe(true);
65
+ expect(readCliPeerId(tempDir)).toBe(res2.peerId);
66
+ });
67
+
68
+ it("renews peer ID when --renew flag is passed", async () => {
69
+ const res1 = await runPeerIdCommand("get", { cacheDir: tempDir });
70
+ const res2 = await runPeerIdCommand("get", {
71
+ cacheDir: tempDir,
72
+ renew: true,
73
+ });
74
+
75
+ expect(res2.peerId).not.toBe(res1.peerId);
76
+ expect(res2.isRenewed).toBe(true);
77
+ });
78
+
79
+ it("sets a custom peer ID when --set is passed", async () => {
80
+ const res = await runPeerIdCommand("get", {
81
+ cacheDir: tempDir,
82
+ set: "custom-my-peer",
83
+ });
84
+
85
+ expect(res.peerId).toBe("custom-my-peer");
86
+ expect(res.isRenewed).toBe(true);
87
+ expect(readCliPeerId(tempDir)).toBe("custom-my-peer");
88
+ });
89
+
90
+ it("supports quiet mode and json mode", async () => {
91
+ const resQuiet = await runPeerIdCommand("get", {
92
+ cacheDir: tempDir,
93
+ quiet: true,
94
+ });
95
+ expect(resQuiet.peerId).toBeDefined();
96
+
97
+ const resJson = await runPeerIdCommand("get", {
98
+ cacheDir: tempDir,
99
+ json: true,
100
+ });
101
+ expect(resJson.peerId).toBe(resQuiet.peerId);
102
+ expect(resJson.filePath).toBe(getCliPeerIdFilePath(tempDir));
103
+ });
104
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ShadowClaw CLI — `send` command
3
+ * Dispatches a prompt / message to a connected client.
4
+ */
5
+
6
+ import { CliControlClient } from "../utils/control-client.mjs";
7
+
8
+ export async function runSendCommand(message, options = {}) {
9
+ if (!message || typeof message !== "string" || !message.trim()) {
10
+ console.error("Error: Message cannot be empty.");
11
+ process.exitCode = 1;
12
+ return;
13
+ }
14
+
15
+ const client = new CliControlClient(options);
16
+ try {
17
+ let targetClientId = options.client;
18
+
19
+ if (!targetClientId) {
20
+ const clients = await client.listClients();
21
+ if (!clients || clients.length === 0) {
22
+ console.error("Error: No clients are connected.");
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ targetClientId = clients[0].clientId;
27
+ console.log(
28
+ `Targeting client: ${clients[0].deviceLabel} (${targetClientId})`,
29
+ );
30
+ }
31
+
32
+ console.log(`Sending message to ${targetClientId}...`);
33
+ const result = await client.sendCommand(targetClientId, "send-message", {
34
+ text: message.trim(),
35
+ groupId: options.group,
36
+ });
37
+
38
+ if (result.success) {
39
+ console.log("Message successfully dispatched.");
40
+ if (result.data) {
41
+ console.log(JSON.stringify(result.data, null, 2));
42
+ }
43
+ } else {
44
+ console.error(`Error from client: ${result.error}`);
45
+ process.exitCode = 1;
46
+ }
47
+ } catch (err) {
48
+ console.error(`Error sending message: ${err.message}`);
49
+ process.exitCode = 1;
50
+ }
51
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ShadowClaw CLI — `tasks` command
3
+ * Lists scheduled / active tasks on a connected client.
4
+ */
5
+
6
+ import { CliControlClient } from "../utils/control-client.mjs";
7
+
8
+ export async function runTasksCommand(options = {}) {
9
+ const client = new CliControlClient(options);
10
+
11
+ try {
12
+ let targetClientId = options.client;
13
+ if (!targetClientId) {
14
+ const clients = await client.listClients();
15
+ if (!clients || clients.length === 0) {
16
+ console.error("Error: No clients connected.");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ targetClientId = clients[0].clientId;
21
+ }
22
+
23
+ console.log(
24
+ `Fetching tasks from client ${targetClientId}${options.group ? ` (group: ${options.group})` : ""}...`,
25
+ );
26
+ const result = await client.sendCommand(targetClientId, "list-tasks", {
27
+ groupId: options.group,
28
+ });
29
+
30
+ if (result.success) {
31
+ const tasks = result.data?.tasks || [];
32
+ if (tasks.length === 0) {
33
+ console.log(
34
+ options.group
35
+ ? `No tasks configured on client for group "${options.group}".`
36
+ : "No tasks configured on client.",
37
+ );
38
+ return;
39
+ }
40
+
41
+ console.log(
42
+ `Tasks on ${targetClientId}${options.group ? ` (group: ${options.group})` : ""} (${tasks.length}):\n`,
43
+ );
44
+ tasks.forEach((t, idx) => {
45
+ const status = t.enabled ? "[enabled]" : "[disabled]";
46
+ const name = t.name || t.prompt?.slice(0, 30) || "Untitled task";
47
+ console.log(
48
+ ` ${idx + 1}. ${status} ${name} (${t.schedule || "manual"})`,
49
+ );
50
+ });
51
+ } else {
52
+ console.error(`Failed to list tasks: ${result.error}`);
53
+ process.exitCode = 1;
54
+ }
55
+ } catch (err) {
56
+ console.error(`Error executing tasks command: ${err.message}`);
57
+ process.exitCode = 1;
58
+ }
59
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * ShadowClaw CLI — `webrtc listen` command
3
+ *
4
+ * Registers the CLI as a live PeerJS peer on the signaling server so that
5
+ * browser tabs can initiate WebRTC DataChannel connections to the CLI without
6
+ * needing a control-plane (SSE/WebSocket) connection.
7
+ *
8
+ * Also starts a local IPC HTTP server on a Unix domain socket
9
+ * (.cache/webrtc-ipc.sock) so that concurrent `send --transport webrtc`
10
+ * invocations can route through the already-established DataChannel instead
11
+ * of trying to register the same peer ID a second time.
12
+ * No port selection, no TCP, no collision.
13
+ */
14
+
15
+ import fs from "node:fs";
16
+ import http from "node:http";
17
+ import path from "node:path";
18
+ import {
19
+ CliWebRtcListener,
20
+ getOrCreateCliPeerId,
21
+ getIpcSocketPath,
22
+ clearIpcFile,
23
+ } from "../utils/webrtc-control-client.mjs";
24
+
25
+ function startIpcServer(listener, cacheDir) {
26
+ return new Promise((resolve, reject) => {
27
+ const socketPath = getIpcSocketPath(cacheDir);
28
+
29
+ // Remove stale socket from a previous run
30
+ try {
31
+ fs.unlinkSync(socketPath);
32
+ } catch (_) {}
33
+
34
+ // Ensure .cache dir exists
35
+ try {
36
+ fs.mkdirSync(path.dirname(socketPath), { recursive: true });
37
+ } catch (_) {}
38
+
39
+ const server = http.createServer(async (req, res) => {
40
+ // Health check
41
+ if (req.method === "GET" && req.url === "/ping") {
42
+ res.writeHead(200, { "Content-Type": "application/json" });
43
+ res.end(JSON.stringify({ ok: true, peerId: listener.cliPeerId }));
44
+ return;
45
+ }
46
+
47
+ // List connected WebRTC peers
48
+ if (req.method === "GET" && req.url === "/clients") {
49
+ const connectedPeers = Array.from(listener._connections.keys());
50
+ const clients = connectedPeers.map((peerId) => {
51
+ const card = listener._peerCards?.get(peerId);
52
+ return {
53
+ clientId: peerId,
54
+ peerId,
55
+ deviceLabel: card?.name || `Browser Peer (${peerId})`,
56
+ capabilities: card?.capabilities || ["webrtc", "peerjs"],
57
+ version: card?.version || "1.0.0",
58
+ transport: "webrtc",
59
+ connectedAt: Date.now(),
60
+ lastSeen: Date.now(),
61
+ };
62
+ });
63
+ res.writeHead(200, { "Content-Type": "application/json" });
64
+ res.end(JSON.stringify({ ok: true, clients }));
65
+ return;
66
+ }
67
+
68
+ // Forward command over the established DataChannel
69
+ if (req.method === "POST" && req.url === "/command") {
70
+ let body = "";
71
+ req.on("data", (c) => (body += c));
72
+ req.on("end", async () => {
73
+ try {
74
+ const { targetPeerId, action, args, timeoutMs } = JSON.parse(body);
75
+
76
+ if (!targetPeerId) {
77
+ res.writeHead(400, { "Content-Type": "application/json" });
78
+ res.end(JSON.stringify({ error: "targetPeerId is required" }));
79
+ return;
80
+ }
81
+
82
+ const conn = listener._connections.get(targetPeerId);
83
+ if (!conn) {
84
+ const connectedPeers = Array.from(listener._connections.keys());
85
+ res.writeHead(503, { "Content-Type": "application/json" });
86
+ res.end(
87
+ JSON.stringify({
88
+ error:
89
+ `No DataChannel open to peer "${targetPeerId}". ` +
90
+ `Make sure the browser has connected to this listener first.` +
91
+ (connectedPeers.length
92
+ ? ` Connected peers: ${connectedPeers.join(", ")}`
93
+ : " No peers are currently connected."),
94
+ }),
95
+ );
96
+ return;
97
+ }
98
+
99
+ const commandId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
100
+ const message = {
101
+ id: commandId,
102
+ type: "command:execute",
103
+ payload: { commandId, action, args: args || {} },
104
+ };
105
+
106
+ const result = await new Promise((cmdResolve, cmdReject) => {
107
+ const timer = setTimeout(() => {
108
+ listener._pendingIpc.delete(commandId);
109
+ cmdReject(
110
+ new Error(
111
+ `Command "${action}" timed out after ${timeoutMs || 30000}ms`,
112
+ ),
113
+ );
114
+ }, timeoutMs || 30000);
115
+
116
+ listener._pendingIpc.set(commandId, (payload) => {
117
+ clearTimeout(timer);
118
+ cmdResolve(payload);
119
+ });
120
+
121
+ try {
122
+ conn.send(message);
123
+ } catch (err) {
124
+ clearTimeout(timer);
125
+ listener._pendingIpc.delete(commandId);
126
+ cmdReject(err);
127
+ }
128
+ });
129
+
130
+ res.writeHead(200, { "Content-Type": "application/json" });
131
+ res.end(JSON.stringify(result));
132
+ } catch (err) {
133
+ res.writeHead(500, { "Content-Type": "application/json" });
134
+ res.end(JSON.stringify({ error: err.message }));
135
+ }
136
+ });
137
+ return;
138
+ }
139
+
140
+ res.writeHead(404);
141
+ res.end();
142
+ });
143
+
144
+ server.listen(socketPath, () => {
145
+ console.log(`[webrtc-listen] IPC socket: ${socketPath}`);
146
+ resolve(server);
147
+ });
148
+
149
+ server.on("error", reject);
150
+ });
151
+ }
152
+
153
+ export async function runWebRtcListenCommand(options = {}) {
154
+ const host = options.host || process.env.SHADOWCLAW_HOST || "127.0.0.1";
155
+ const port = options.port
156
+ ? parseInt(options.port, 10)
157
+ : parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
158
+ const peerPath = options.path || "/";
159
+ const secure = Boolean(options.secure);
160
+ const trustedPeerIds = options.trustedPeer
161
+ ? Array.isArray(options.trustedPeer)
162
+ ? options.trustedPeer
163
+ : [options.trustedPeer]
164
+ : [];
165
+
166
+ const cliPeerId = getOrCreateCliPeerId(options.peerId, options.cacheDir);
167
+
168
+ console.log(`WebRTC CLI Peer ID : ${cliPeerId}`);
169
+ console.log(
170
+ `Signaling server : ${secure ? "wss" : "ws"}://${host}:${port}${peerPath}`,
171
+ );
172
+ if (trustedPeerIds.length > 0) {
173
+ console.log(`Trusted peers : ${trustedPeerIds.join(", ")}`);
174
+ } else {
175
+ console.log(
176
+ `Trusted peers : (any — add --trusted-peer <id> to restrict)`,
177
+ );
178
+ }
179
+ console.log("");
180
+ console.log("Add this CLI Peer ID as a trusted peer in the browser:");
181
+ console.log(` Settings → WebRTC/PeerJS → Trusted Peer IDs → "${cliPeerId}"`);
182
+ console.log("");
183
+
184
+ const listener = new CliWebRtcListener({
185
+ host,
186
+ port,
187
+ path: peerPath,
188
+ secure,
189
+ trustedPeerIds,
190
+ peerId: options.peerId,
191
+ cacheDir: options.cacheDir,
192
+ renewPeerId: Boolean(options.renewPeerId),
193
+ verbose: Boolean(options.verbose),
194
+ });
195
+
196
+ // Map for pending IPC-dispatched commands awaiting DataChannel responses
197
+ listener._pendingIpc = new Map();
198
+
199
+ // Intercept _setupConnection to wire up IPC command result handling
200
+ const originalSetup = listener._setupConnection.bind(listener);
201
+ listener._setupConnection = function (conn, remotePeerId) {
202
+ originalSetup(conn, remotePeerId);
203
+ conn.on("data", (rawData) => {
204
+ let msg = rawData;
205
+ if (typeof rawData === "string") {
206
+ try {
207
+ msg = JSON.parse(rawData);
208
+ } catch (_) {
209
+ return;
210
+ }
211
+ }
212
+ if (!msg || typeof msg !== "object") return;
213
+ if (msg.type === "command:result") {
214
+ const payload = msg.payload || {};
215
+ const cb = listener._pendingIpc.get(payload.commandId);
216
+ if (cb) {
217
+ listener._pendingIpc.delete(payload.commandId);
218
+ cb(payload);
219
+ }
220
+ }
221
+ });
222
+ };
223
+
224
+ const shutdown = (ipcServer) => {
225
+ console.log("\n[webrtc-listen] Shutting down…");
226
+ clearIpcFile(options.cacheDir);
227
+ if (ipcServer) {
228
+ try {
229
+ ipcServer.close();
230
+ } catch (_) {}
231
+ }
232
+ listener.close();
233
+ process.exit(0);
234
+ };
235
+
236
+ try {
237
+ await listener.start();
238
+ const ipcServer = await startIpcServer(listener, options.cacheDir);
239
+
240
+ process.on("SIGINT", () => shutdown(ipcServer));
241
+ process.on("SIGTERM", () => shutdown(ipcServer));
242
+
243
+ console.log(
244
+ `[webrtc-listen] Ready. Run commands with:\n` +
245
+ ` node bin/cli.mjs send --transport webrtc --client <browser-peer-id> "message"`,
246
+ );
247
+
248
+ await new Promise(() => {}); // keep alive
249
+ } catch (err) {
250
+ clearIpcFile(options.cacheDir);
251
+ console.error(`Error starting WebRTC listener: ${err.message}`);
252
+ process.exitCode = 1;
253
+ }
254
+ }
@@ -1,15 +1,19 @@
1
1
  import {
2
+ applySidebarVisibilityToTemplate,
2
3
  applyStaticPagesContent,
3
4
  collectPageSources,
4
5
  escapeJsonForHtmlScript,
6
+ extractTemplateContent,
5
7
  injectPageHeaderDsd,
6
8
  injectStaticManifestScript,
9
+ isPageFile,
7
10
  markNoSeedPrerenderHost,
8
11
  minifyDsdTemplateHtml,
9
12
  normalizePrerenderPagesOption,
10
13
  prerenderDsdShell,
11
14
  renderPageHtml,
12
15
  sortPagePaths,
16
+ splitFrontmatterWithGrayMatter,
13
17
  } from "./prerender-dsd-shell.mjs";
14
18
 
15
19
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
@@ -478,4 +482,46 @@ describe("prerenderDsdShell purge flag pages", () => {
478
482
  expect(manifestJson.purgeId).toBe("build-test-999");
479
483
  expect(manifestJson.preRenderedStaticPages["br-main"]).toBeDefined();
480
484
  });
485
+
486
+ describe("prerender helper functions", () => {
487
+ it("splits frontmatter with gray-matter", () => {
488
+ const src = "---\ntitle: Hello\n---\n# Content";
489
+ const res = splitFrontmatterWithGrayMatter(src);
490
+ expect(res.data.title).toBe("Hello");
491
+ expect(res.content.trim()).toBe("# Content");
492
+ });
493
+
494
+ it("extracts template content and throws on missing template", () => {
495
+ const html = "<template><div>Hello</div></template>";
496
+ expect(extractTemplateContent(html)).toBe("<div>Hello</div>");
497
+ expect(() => extractTemplateContent("<div>No template</div>")).toThrow(
498
+ "Template wrapper not found",
499
+ );
500
+ });
501
+
502
+ it("identifies page files by extension", () => {
503
+ expect(isPageFile("index.md")).toBe(true);
504
+ expect(isPageFile("page.html")).toBe(true);
505
+ expect(isPageFile("script.js")).toBe(false);
506
+ });
507
+
508
+ it("applies sidebar visibility config to template", () => {
509
+ const tpl =
510
+ '<li class="nav-item" data-page="chat">Chat</li><li class="nav-item" data-page="files">Files</li>';
511
+ const modified = applySidebarVisibilityToTemplate(tpl, {
512
+ chatHidden: true,
513
+ });
514
+ expect(modified).toContain('data-page="chat" hidden aria-hidden="true"');
515
+ expect(modified).not.toContain('data-page="files" hidden');
516
+ });
517
+
518
+ it("minifies DSD template HTML removing excess whitespace and comments", () => {
519
+ const html =
520
+ '<template shadowrootmode="open">\n<div>\n <span>Text</span>\n</div>\n</template>';
521
+ const minified = minifyDsdTemplateHtml(html);
522
+ expect(minified).toBe(
523
+ '<template shadowrootmode="open"><div><span>Text</span></div></template>',
524
+ );
525
+ });
526
+ });
481
527
  });