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.
- package/README.md +42 -5
- package/bin/assert-version-bump.mjs +60 -38
- package/bin/assert-version-bump.test.mjs +74 -11
- package/bin/cli.mjs +166 -0
- package/bin/cli.test.mjs +60 -0
- package/bin/commands/backup.mjs +70 -0
- package/bin/commands/clients.mjs +41 -0
- package/bin/commands/peer-id.mjs +76 -0
- package/bin/commands/peer-id.test.mjs +104 -0
- package/bin/commands/send.mjs +51 -0
- package/bin/commands/tasks.mjs +59 -0
- package/bin/commands/webrtc-listen.mjs +254 -0
- package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
- package/bin/rolldown.config.test.mjs +401 -8
- package/bin/utils/control-client.mjs +243 -0
- package/bin/utils/control-client.test.mjs +83 -0
- package/bin/utils/ulid.mjs +45 -0
- package/bin/utils/webrtc-control-client.mjs +699 -0
- package/bin/utils/webrtc-control-client.test.mjs +318 -0
- package/dist/electron/main.cjs +1 -1
- package/dist/public/AGENTS.md +5 -3
- package/dist/public/README.md +42 -5
- package/dist/public/agent.worker.js +6 -6
- package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
- package/dist/public/backup-controller-C_s9in2t.js +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
- package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
- package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
- package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
- package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
- package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
- package/dist/public/config-CrHchneq.js +1 -0
- package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
- package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
- package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
- package/dist/public/docs/README.md +3 -1
- package/dist/public/docs/architecture/overview.md +12 -12
- package/dist/public/docs/architecture/storage.md +5 -2
- package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
- package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
- package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
- package/dist/public/docs/example/article/index.html +2 -2
- package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
- package/dist/public/docs/guides/server-development-configuration.md +24 -0
- package/dist/public/docs/publishing/index.html +19 -76
- package/dist/public/docs/skill-creator/index.html +2 -2
- package/dist/public/docs/subsystems/backup.md +56 -0
- package/dist/public/docs/subsystems/cli.md +124 -0
- package/dist/public/docs/subsystems/control-plane.md +158 -0
- package/dist/public/docs/subsystems/notifications.md +54 -2
- package/dist/public/docs/subsystems/reactive-ui.md +1 -0
- package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
- package/dist/public/e2e/README.md +5 -1
- package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
- package/dist/public/file-viewer-D0eflgt6.js +1 -0
- package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
- package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
- package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
- package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
- package/dist/public/initControlPlane-C9jpnWev.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +2 -2
- package/dist/public/main/memory/index.html +2 -2
- package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
- package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
- package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
- package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
- package/dist/public/orchestrator-nJj59RqD.js +15 -0
- package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
- package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker/push-handler.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
- package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
- package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
- package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
- package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
- package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
- package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
- package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
- package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
- package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
- package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
- package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
- package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
- package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
- package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
- package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
- package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
- package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
- package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
- package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
- package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
- package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
- package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
- package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
- package/dist/public/static-main-manifest.json +1 -1
- package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
- package/dist/server.js +286 -189
- package/package.json +2 -1
- package/src/__mocks__/jszip.cjs +15 -5
- package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
- package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
- package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
- package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
- package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
- package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
- package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
- package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
- package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
- package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
- package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
- package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
- package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
- package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
- package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
- package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
- package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
- package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
- package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
- package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
- package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
- package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
- package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
- package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
- package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
- package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
- package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
- package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
- package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
- package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
- package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
- package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
- package/src/config/config.test.ts +9 -0
- package/src/config/config.ts +15 -0
- package/src/core/app-routes.test.ts +56 -0
- package/src/core/app-routes.ts +8 -1
- package/src/core/backup-controller.test.ts +101 -0
- package/src/core/backup-controller.ts +137 -0
- package/src/core/control-plane-client.test.ts +408 -0
- package/src/core/control-plane-client.ts +425 -0
- package/src/core/orchestrator/orchestrator.ts +4 -0
- package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
- package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
- package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
- package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
- package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
- package/src/core/utils/initControlPlane.test.ts +276 -0
- package/src/core/utils/initControlPlane.ts +437 -0
- package/src/core/utils/initializeApp.ts +18 -0
- package/src/db/getSession.test.ts +13 -1
- package/src/db/openDatabase.test.ts +10 -1
- package/src/db/openDatabase.ts +3 -1
- package/src/server/app.test.ts +12 -0
- package/src/server/app.ts +18 -8
- package/src/server/client-registry.test.ts +250 -0
- package/src/server/client-registry.ts +477 -0
- package/src/server/config.ts +15 -0
- package/src/server/control-plane-types.ts +120 -0
- package/src/server/control-plane.test.ts +472 -0
- package/src/server/control-plane.ts +581 -0
- package/src/server/routes/backup.test.ts +263 -0
- package/src/server/routes/backup.ts +262 -0
- package/src/server/routes/integrations-email.test.ts +532 -0
- package/src/server/routes/llamafile.test.ts +172 -48
- package/src/server/routes/mesh-llm.test.ts +192 -0
- package/src/server/routes/ollama.test.ts +49 -0
- package/src/server/server-peer.test.ts +86 -0
- package/src/server/server-peer.ts +330 -0
- package/src/server/server.ts +35 -0
- package/src/server/services/llamafile-manager.test.ts +230 -6
- package/src/server/services/transformers-runtime.test.ts +186 -18
- package/src/server/webrtc.test.ts +21 -0
- package/src/server/webrtc.ts +72 -0
- package/src/service-worker/fetch-proxy.test.ts +64 -1
- package/src/service-worker/push-handler.ts +39 -0
- package/src/shell/vm.test.ts +744 -12
- package/src/storage/ensureMainGroupIndex.test.ts +153 -0
- package/src/storage/getGroupFile.test.ts +66 -0
- package/src/storage/memoryStorage.test.ts +82 -2
- package/src/storage/migrateLegacyOpfs.test.ts +327 -0
- package/src/storage/migrateLegacyOpfs.ts +117 -0
- package/src/storage/writeGroupFileBytes.test.ts +143 -0
- package/src/stores/chat-ui.test.ts +20 -0
- package/src/stores/orchestrator.test.ts +134 -0
- package/src/stores/toast.test.ts +50 -0
- package/src/stores/tools.test.ts +147 -10
- package/src/subsystems/channels/imessage.test.ts +59 -0
- package/src/subsystems/channels/peerjs.test.ts +500 -62
- package/src/subsystems/channels/peerjs.ts +71 -4
- package/src/subsystems/channels/telegram.test.ts +177 -0
- package/src/subsystems/email/catalog.test.ts +1 -0
- package/src/subsystems/email/connections.test.ts +39 -1
- package/src/subsystems/git/credentials.test.ts +160 -825
- package/src/subsystems/git/git.test.ts +463 -0
- package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
- package/src/subsystems/notifications/push-routes.test.ts +33 -0
- package/src/subsystems/notifications/push-routes.ts +23 -0
- package/src/subsystems/notifications/push-store.ts +1 -1
- package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
- package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
- package/src/subsystems/providers/model-registry.test.ts +115 -0
- package/src/subsystems/providers/providers.test.ts +94 -1
- package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
- package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
- package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
- package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
- package/src/subsystems/skills/activateSkill.test.ts +101 -0
- package/src/subsystems/skills/discoverSkills.test.ts +251 -0
- package/src/subsystems/tools/cron.test.ts +64 -0
- package/src/subsystems/tools/declarative.test.ts +255 -89
- package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
- package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
- package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
- package/src/testing/jest-setup.ts +328 -40
- package/src/testing/mock-v86.mjs +49 -0
- package/src/ui/a2ui/types.ts +4 -3
- package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
- package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
- package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
- package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
- package/src/worker/tools/email/email.test.ts +193 -8
- package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
- package/src/worker/tools/git/git.test.ts +214 -3
- package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
- package/src/worker/tools/rooms/leave-room.test.ts +40 -0
- package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
- package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
- package/src/worker/tools/ui/ask-user.test.ts +50 -0
- package/src/worker/tools/ui/clear-chat.test.ts +19 -0
- package/src/worker/tools/ui/get-current-time.test.ts +19 -0
- package/src/worker/tools/ui/javascript.test.ts +116 -0
- package/src/worker/tools/ui/list-components.test.ts +10 -0
- package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
- package/src/worker/tools/ui/manage-tools.test.ts +34 -0
- package/src/worker/tools/ui/render-component.test.ts +346 -0
- package/src/worker/tools/ui/send-notification.test.ts +40 -0
- package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
- package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
- package/src/worker/tools/workspace/attach-file.test.ts +84 -0
- package/src/worker/tools/workspace/copy-file.test.ts +69 -0
- package/src/worker/tools/workspace/create-directory.test.ts +40 -0
- package/src/worker/tools/workspace/delete-file.test.ts +73 -0
- package/src/worker/tools/workspace/diff-files.test.ts +94 -0
- package/src/worker/tools/workspace/list-files.test.ts +36 -0
- package/src/worker/tools/workspace/move-file.test.ts +66 -0
- package/src/worker/tools/workspace/open-file.test.ts +57 -0
- package/src/worker/tools/workspace/patch-file.test.ts +81 -0
- package/src/worker/tools/workspace/read-file.test.ts +116 -0
- package/src/worker/tools/workspace/send-file.test.ts +82 -0
- package/src/worker/tools/workspace/update-memory.test.ts +29 -0
- package/src/worker/tools/workspace/write-file.test.ts +28 -0
- package/src/worker/utils/rate-limit.test.ts +199 -0
- package/src/worker/utils/tool-state.test.ts +29 -0
- package/src/worker/utils/transformers-js-utils.test.ts +59 -0
- package/dist/public/config-AL77QAx4.js +0 -1
- package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
- package/dist/public/file-viewer-CCSABMsE.js +0 -1
- package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
- package/dist/public/orchestrator-4su7iyWj.js +0 -15
- package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
- package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
- package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
- 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
|
});
|