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
|
@@ -1,17 +1,410 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
|
11
|
-
(
|
|
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
|
-
|
|
15
|
-
|
|
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
|
+
}
|