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
package/README.md
CHANGED
|
@@ -2,23 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://deepwiki.com/xt-ml/shadow-claw)
|
|
4
4
|
|
|
5
|
-
A browser-native, fully-featured AI assistant. TypeScript + Web Components + IndexedDB + File System API.
|
|
5
|
+
A browser-native, fully-featured AI assistant — runs as a PWA, a native desktop app, a Node.js server, and via the `shadow-claw` CLI. TypeScript + Web Components + IndexedDB + File System API.
|
|
6
6
|
|
|
7
7
|
[](https://xt-ml.github.io/shadow-claw/)
|
|
8
8
|
|
|
9
9
|
## Quick Start
|
|
10
10
|
|
|
11
|
+
### 1. Launch ShadowClaw instantly without cloning the repo
|
|
12
|
+
|
|
11
13
|
```bash
|
|
12
|
-
|
|
14
|
+
npx shadow-claw dev --open
|
|
13
15
|
```
|
|
14
16
|
|
|
15
|
-
Open Settings, select
|
|
17
|
+
Open Settings, select your AI provider (or use the in-browser Prompt API / local models), and start chatting.
|
|
18
|
+
|
|
19
|
+
### 2. Scaffold a Project or Knowledge Hub
|
|
20
|
+
|
|
21
|
+
Create a custom template with pages and site configuration:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx shadow-claw init my-assistant
|
|
25
|
+
cd my-assistant
|
|
26
|
+
npx shadow-claw dev --open
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. From Source (Contributing / Development)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/xt-ml/shadow-claw.git
|
|
33
|
+
cd shadow-claw
|
|
34
|
+
npm install && npm run dev # Dev server → http://localhost:8888
|
|
35
|
+
```
|
|
16
36
|
|
|
17
|
-
**Desktop:** `npm run electron` or `npm run electron:build` for distributable.
|
|
37
|
+
**Desktop App:** `npm run electron` or `npm run electron:build` for distributable installer.
|
|
18
38
|
|
|
19
39
|
## What is ShadowClaw?
|
|
20
40
|
|
|
21
|
-
A fully-functional agent runtime
|
|
41
|
+
A fully-functional agent runtime built on browser-native technology — the orchestration and tool-use loop run client-side, with AI inference routing to remote APIs, local servers, or in-browser models depending on your configuration. Built with TypeScript, it provides:
|
|
22
42
|
|
|
23
43
|
- **Multi-model support**: OpenRouter, Anthropic, Google Gemini, AWS Bedrock, Ollama, Llamafile, Mesh LLM, Transformers.js, and browser-native Prompt API
|
|
24
44
|
- **Web Components UI**: Native Custom Elements + TC39 Signals for reactive updates
|
|
@@ -378,6 +398,23 @@ npm run electron:build:win # Build Electron for Windows
|
|
|
378
398
|
npm run electron:build:mac # Build Electron for macOS
|
|
379
399
|
```
|
|
380
400
|
|
|
401
|
+
### CLI Runtime Commands
|
|
402
|
+
|
|
403
|
+
The `shadow-claw` CLI connects to a running server or browser clients to interact with active sessions:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
npx shadow-claw clients # List connected browser/Electron clients
|
|
407
|
+
npx shadow-claw send "your prompt" --client <id> # Send a message to a connected client
|
|
408
|
+
npx shadow-claw backup # Trigger OPFS workspace backup
|
|
409
|
+
npx shadow-claw backup list # List available backup snapshots
|
|
410
|
+
npx shadow-claw backup delete --backup-id <id> # Delete a backup snapshot
|
|
411
|
+
npx shadow-claw tasks --client <id> # List scheduled tasks on a client
|
|
412
|
+
npx shadow-claw webrtc listen # Start headless WebRTC DataChannel daemon
|
|
413
|
+
npx shadow-claw peer-id # Get or generate persistent CLI Peer ID
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Commands support `--transport webrtc` for direct peer-to-peer DataChannel execution with connected browser clients. Control plane authentication uses `SHADOWCLAW_CONTROL_TOKEN` (env) or `--token` flag. The control plane endpoint and token are printed to the console on server start.
|
|
417
|
+
|
|
381
418
|
## License
|
|
382
419
|
|
|
383
420
|
AGPLv3. Core logic derived from [openbrowserclaw](https://github.com/sachaa/openbrowserclaw) (MIT).
|
|
@@ -4,12 +4,12 @@ import { execSync } from "node:child_process";
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import process from "node:process";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
|
|
9
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const rootDir = path.resolve(__dirname, "..");
|
|
11
11
|
|
|
12
|
-
function parseSemver(v) {
|
|
12
|
+
export function parseSemver(v) {
|
|
13
13
|
const clean = String(v || "")
|
|
14
14
|
.trim()
|
|
15
15
|
.replace(/^[v^~]/, "");
|
|
@@ -26,7 +26,7 @@ function parseSemver(v) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function compareSemver(a, b) {
|
|
29
|
+
export function compareSemver(a, b) {
|
|
30
30
|
const pa = parseSemver(a);
|
|
31
31
|
const pb = parseSemver(b);
|
|
32
32
|
if (!pa || !pb) return String(a).localeCompare(String(b));
|
|
@@ -43,6 +43,47 @@ function compareSemver(a, b) {
|
|
|
43
43
|
return 0;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export async function assertVersionBump({
|
|
47
|
+
localPkgVersion,
|
|
48
|
+
localLockVersion,
|
|
49
|
+
pkgName,
|
|
50
|
+
publishedVersion,
|
|
51
|
+
silent = false,
|
|
52
|
+
}) {
|
|
53
|
+
if (!silent) {
|
|
54
|
+
console.log(`Checking version bump for package "${pkgName}"...`);
|
|
55
|
+
console.log(`- Local package.json version : ${localPkgVersion}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (localLockVersion) {
|
|
59
|
+
if (!silent) {
|
|
60
|
+
console.log(`- Local package-lock.json version: ${localLockVersion}`);
|
|
61
|
+
}
|
|
62
|
+
if (localPkgVersion !== localLockVersion) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Version mismatch: package.json (${localPkgVersion}) does not match package-lock.json (${localLockVersion})!`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (publishedVersion) {
|
|
70
|
+
if (!silent) {
|
|
71
|
+
console.log(`- Published npm registry version : ${publishedVersion}`);
|
|
72
|
+
}
|
|
73
|
+
const cmp = compareSemver(localPkgVersion, publishedVersion);
|
|
74
|
+
if (cmp <= 0) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Version assertion failed: Local version (${localPkgVersion}) must be greater than published npm version (${publishedVersion}). Please bump the version before publishing.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!silent) {
|
|
80
|
+
console.log(
|
|
81
|
+
`✓ Version assertion passed: ${localPkgVersion} > ${publishedVersion}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
46
87
|
async function main() {
|
|
47
88
|
const pkgJsonPath = path.join(rootDir, "package.json");
|
|
48
89
|
const lockJsonPath = path.join(rootDir, "package-lock.json");
|
|
@@ -57,21 +98,6 @@ async function main() {
|
|
|
57
98
|
const localPkgVersion = pkg.version;
|
|
58
99
|
const localLockVersion = lock?.version;
|
|
59
100
|
|
|
60
|
-
console.log(`Checking version bump for package "${pkgName}"...`);
|
|
61
|
-
console.log(`- Local package.json version : ${localPkgVersion}`);
|
|
62
|
-
if (localLockVersion) {
|
|
63
|
-
console.log(`- Local package-lock.json version: ${localLockVersion}`);
|
|
64
|
-
if (localPkgVersion !== localLockVersion) {
|
|
65
|
-
console.error(
|
|
66
|
-
`\n[FATAL] Version mismatch: package.json (${localPkgVersion}) does not match package-lock.json (${localLockVersion})!`,
|
|
67
|
-
);
|
|
68
|
-
console.error(
|
|
69
|
-
`Run "npm install" or "npm version" to synchronize package-lock.json.`,
|
|
70
|
-
);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
101
|
let publishedVersion = null;
|
|
76
102
|
try {
|
|
77
103
|
const rawOut = execSync(`npm view ${pkgName} version --json`, {
|
|
@@ -98,25 +124,21 @@ async function main() {
|
|
|
98
124
|
);
|
|
99
125
|
}
|
|
100
126
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
);
|
|
108
|
-
console.error(
|
|
109
|
-
`Please bump the version in package.json and package-lock.json (e.g. "npm version patch|minor|major") before publishing.\n`,
|
|
110
|
-
);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
console.log(
|
|
114
|
-
`✓ Version assertion passed: ${localPkgVersion} > ${publishedVersion}`,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
127
|
+
await assertVersionBump({
|
|
128
|
+
localPkgVersion,
|
|
129
|
+
localLockVersion,
|
|
130
|
+
pkgName,
|
|
131
|
+
publishedVersion,
|
|
132
|
+
});
|
|
117
133
|
}
|
|
118
134
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
process.
|
|
122
|
-
|
|
135
|
+
const isMainModule =
|
|
136
|
+
process.argv[1] &&
|
|
137
|
+
import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
|
138
|
+
|
|
139
|
+
if (isMainModule) {
|
|
140
|
+
main().catch((err) => {
|
|
141
|
+
console.error(`\n[FATAL] ${err.message}\n`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -1,16 +1,79 @@
|
|
|
1
1
|
import { describe, it, expect } from "@jest/globals";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const scriptPath = path.join(__dirname, "assert-version-bump.mjs");
|
|
2
|
+
import {
|
|
3
|
+
parseSemver,
|
|
4
|
+
compareSemver,
|
|
5
|
+
assertVersionBump,
|
|
6
|
+
} from "./assert-version-bump.mjs";
|
|
10
7
|
|
|
11
8
|
describe("assert-version-bump", () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
describe("parseSemver", () => {
|
|
10
|
+
it("parses valid semver strings correctly", () => {
|
|
11
|
+
expect(parseSemver("1.23.4")).toEqual({
|
|
12
|
+
major: 1,
|
|
13
|
+
minor: 23,
|
|
14
|
+
patch: 4,
|
|
15
|
+
prerelease: null,
|
|
16
|
+
raw: "1.23.4",
|
|
17
|
+
});
|
|
18
|
+
expect(parseSemver("v2.0.0-beta.1")).toEqual({
|
|
19
|
+
major: 2,
|
|
20
|
+
minor: 0,
|
|
21
|
+
patch: 0,
|
|
22
|
+
prerelease: "beta.1",
|
|
23
|
+
raw: "2.0.0-beta.1",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("returns null for invalid semver strings", () => {
|
|
28
|
+
expect(parseSemver("invalid")).toBeNull();
|
|
29
|
+
expect(parseSemver("")).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("compareSemver", () => {
|
|
34
|
+
it("correctly compares semver versions", () => {
|
|
35
|
+
expect(compareSemver("1.23.4", "1.23.3")).toBe(1);
|
|
36
|
+
expect(compareSemver("2.0.0", "1.99.99")).toBe(1);
|
|
37
|
+
expect(compareSemver("1.23.3", "1.23.3")).toBe(0);
|
|
38
|
+
expect(compareSemver("1.23.2", "1.23.3")).toBe(-1);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("assertVersionBump", () => {
|
|
43
|
+
it("passes when local version is greater than published version", async () => {
|
|
44
|
+
await expect(
|
|
45
|
+
assertVersionBump({
|
|
46
|
+
localPkgVersion: "1.23.4",
|
|
47
|
+
localLockVersion: "1.23.4",
|
|
48
|
+
pkgName: "shadow-claw",
|
|
49
|
+
publishedVersion: "1.23.3",
|
|
50
|
+
silent: true,
|
|
51
|
+
}),
|
|
52
|
+
).resolves.toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("throws when local version is equal to published version", async () => {
|
|
56
|
+
await expect(
|
|
57
|
+
assertVersionBump({
|
|
58
|
+
localPkgVersion: "1.23.3",
|
|
59
|
+
localLockVersion: "1.23.3",
|
|
60
|
+
pkgName: "shadow-claw",
|
|
61
|
+
publishedVersion: "1.23.3",
|
|
62
|
+
silent: true,
|
|
63
|
+
}),
|
|
64
|
+
).rejects.toThrow("Version assertion failed");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("throws when package.json does not match package-lock.json", async () => {
|
|
68
|
+
await expect(
|
|
69
|
+
assertVersionBump({
|
|
70
|
+
localPkgVersion: "1.23.4",
|
|
71
|
+
localLockVersion: "1.23.3",
|
|
72
|
+
pkgName: "shadow-claw",
|
|
73
|
+
publishedVersion: "1.23.2",
|
|
74
|
+
silent: true,
|
|
75
|
+
}),
|
|
76
|
+
).rejects.toThrow("Version mismatch");
|
|
77
|
+
});
|
|
15
78
|
});
|
|
16
79
|
});
|
package/bin/cli.mjs
CHANGED
|
@@ -8,6 +8,12 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
import { runBuild } from "./build/build.mjs";
|
|
11
|
+
import { runClientsCommand } from "./commands/clients.mjs";
|
|
12
|
+
import { runSendCommand } from "./commands/send.mjs";
|
|
13
|
+
import { runBackupCommand } from "./commands/backup.mjs";
|
|
14
|
+
import { runTasksCommand } from "./commands/tasks.mjs";
|
|
15
|
+
import { runPeerIdCommand } from "./commands/peer-id.mjs";
|
|
16
|
+
import { runWebRtcListenCommand } from "./commands/webrtc-listen.mjs";
|
|
11
17
|
|
|
12
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
19
|
const __dirname = path.dirname(__filename);
|
|
@@ -421,4 +427,164 @@ slug: "home"
|
|
|
421
427
|
}
|
|
422
428
|
});
|
|
423
429
|
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// PEER-ID / WEBRTC COMMAND
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
program
|
|
434
|
+
.command("peer-id [action] [customId]")
|
|
435
|
+
.alias("webrtc-peer-id")
|
|
436
|
+
.description(
|
|
437
|
+
"Get, generate, or renew the WebRTC CLI peer ID stored in .cache/cli-peer-id",
|
|
438
|
+
)
|
|
439
|
+
.option(
|
|
440
|
+
"-r, --renew",
|
|
441
|
+
"Force renewal / generation of a new WebRTC CLI peer ID",
|
|
442
|
+
)
|
|
443
|
+
.option("--set <id>", "Set a specific custom WebRTC CLI peer ID")
|
|
444
|
+
.option("--cache-dir <dir>", "Custom cache directory (defaults to .cache)")
|
|
445
|
+
.option("-q, --quiet", "Output only the peer ID string")
|
|
446
|
+
.option("--json", "Output peer ID and file path as JSON")
|
|
447
|
+
.action(async (action, customId, options) => {
|
|
448
|
+
const opts = { ...options };
|
|
449
|
+
if (customId && !opts.set) {
|
|
450
|
+
opts.set = customId;
|
|
451
|
+
}
|
|
452
|
+
await runPeerIdCommand(action, opts);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
program
|
|
456
|
+
.command("webrtc [action] [customId]")
|
|
457
|
+
.description(
|
|
458
|
+
"Manage WebRTC CLI configuration and peer identity (.cache/cli-peer-id)\n\n" +
|
|
459
|
+
" Actions:\n" +
|
|
460
|
+
" (default) Show or generate the CLI peer ID\n" +
|
|
461
|
+
" listen Register as a live PeerJS peer — browser tabs can then\n" +
|
|
462
|
+
" connect directly without a control-plane connection",
|
|
463
|
+
)
|
|
464
|
+
.option(
|
|
465
|
+
"-r, --renew",
|
|
466
|
+
"Force renewal / generation of a new WebRTC CLI peer ID",
|
|
467
|
+
)
|
|
468
|
+
.option("--set <id>", "Set a specific custom WebRTC CLI peer ID")
|
|
469
|
+
.option("--cache-dir <dir>", "Custom cache directory (defaults to .cache)")
|
|
470
|
+
.option("-q, --quiet", "Output only the peer ID string")
|
|
471
|
+
.option("--json", "Output peer ID and file path as JSON")
|
|
472
|
+
// listen-specific options
|
|
473
|
+
.option("--host <host>", "PeerJS signaling server host (for listen)")
|
|
474
|
+
.option("--port <port>", "PeerJS signaling server port (for listen)", "8888")
|
|
475
|
+
.option("--path <path>", "PeerJS signaling server path (for listen)", "/")
|
|
476
|
+
.option(
|
|
477
|
+
"--secure",
|
|
478
|
+
"Use TLS (wss://) for the signaling server (for listen)",
|
|
479
|
+
false,
|
|
480
|
+
)
|
|
481
|
+
.option(
|
|
482
|
+
"--trusted-peer <id>",
|
|
483
|
+
"Accept connections only from this peer ID (repeatable, for listen)",
|
|
484
|
+
(v, prev) => (prev ? [...prev, v] : [v]),
|
|
485
|
+
[],
|
|
486
|
+
)
|
|
487
|
+
.option("--verbose", "Verbose connection logging (for listen)", false)
|
|
488
|
+
.option("--renew-peer-id", "Renew CLI peer ID before listening", false)
|
|
489
|
+
.action(async (action, customId, options) => {
|
|
490
|
+
if (action === "listen") {
|
|
491
|
+
await runWebRtcListenCommand(options);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const opts = { ...options };
|
|
495
|
+
if (customId && !opts.set) {
|
|
496
|
+
opts.set = customId;
|
|
497
|
+
}
|
|
498
|
+
await runPeerIdCommand(action, opts);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
// CLIENTS COMMAND
|
|
503
|
+
// ---------------------------------------------------------------------------
|
|
504
|
+
program
|
|
505
|
+
.command("clients")
|
|
506
|
+
.description("List connected / registered browser and Electron clients")
|
|
507
|
+
.option("--host <host>", "Control plane host")
|
|
508
|
+
.option("--port <port>", "Control plane port")
|
|
509
|
+
.option("--token <token>", "Control token")
|
|
510
|
+
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
511
|
+
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
512
|
+
.option(
|
|
513
|
+
"--renew-peer-id",
|
|
514
|
+
"Renew WebRTC CLI peer ID before connecting",
|
|
515
|
+
false,
|
|
516
|
+
)
|
|
517
|
+
.option("--cache-dir <dir>", "Custom cache directory")
|
|
518
|
+
.action(async (options) => {
|
|
519
|
+
await runClientsCommand(options);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// ---------------------------------------------------------------------------
|
|
523
|
+
// SEND COMMAND
|
|
524
|
+
// ---------------------------------------------------------------------------
|
|
525
|
+
program
|
|
526
|
+
.command("send <message>")
|
|
527
|
+
.description("Send a message/prompt to a connected client")
|
|
528
|
+
.option("--client <id>", "Target client ID (defaults to first available)")
|
|
529
|
+
.option("--group <groupId>", "Target conversation group ID")
|
|
530
|
+
.option("--host <host>", "Control plane host")
|
|
531
|
+
.option("--port <port>", "Control plane port")
|
|
532
|
+
.option("--token <token>", "Control token")
|
|
533
|
+
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
534
|
+
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
535
|
+
.option("--renew-peer-id", "Renew WebRTC CLI peer ID before sending", false)
|
|
536
|
+
.option("--cache-dir <dir>", "Custom cache directory")
|
|
537
|
+
.action(async (message, options) => {
|
|
538
|
+
await runSendCommand(message, options);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// ---------------------------------------------------------------------------
|
|
542
|
+
// BACKUP COMMAND
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
program
|
|
545
|
+
.command("backup [action]")
|
|
546
|
+
.description(
|
|
547
|
+
"Trigger or manage client workspace backups (trigger | list | delete)",
|
|
548
|
+
)
|
|
549
|
+
.option("--client <id>", "Client ID")
|
|
550
|
+
.option("--backup-id <id>", "Backup ID (for delete)")
|
|
551
|
+
.option("--group <groupId>", "Specific workspace group ID")
|
|
552
|
+
.option("--host <host>", "Control plane host")
|
|
553
|
+
.option("--port <port>", "Control plane port")
|
|
554
|
+
.option("--token <token>", "Control token")
|
|
555
|
+
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
556
|
+
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
557
|
+
.option(
|
|
558
|
+
"--renew-peer-id",
|
|
559
|
+
"Renew WebRTC CLI peer ID before connecting",
|
|
560
|
+
false,
|
|
561
|
+
)
|
|
562
|
+
.option("--cache-dir <dir>", "Custom cache directory")
|
|
563
|
+
.action(async (action, options) => {
|
|
564
|
+
await runBackupCommand(action || "trigger", options);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// ---------------------------------------------------------------------------
|
|
568
|
+
// TASKS COMMAND
|
|
569
|
+
// ---------------------------------------------------------------------------
|
|
570
|
+
program
|
|
571
|
+
.command("tasks")
|
|
572
|
+
.description("List scheduled tasks on a connected client")
|
|
573
|
+
.option("--client <id>", "Target client ID")
|
|
574
|
+
.option("--group <groupId>", "Filter tasks by conversation group ID")
|
|
575
|
+
.option("--host <host>", "Control plane host")
|
|
576
|
+
.option("--port <port>", "Control plane port")
|
|
577
|
+
.option("--token <token>", "Control token")
|
|
578
|
+
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
579
|
+
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
580
|
+
.option(
|
|
581
|
+
"--renew-peer-id",
|
|
582
|
+
"Renew WebRTC CLI peer ID before connecting",
|
|
583
|
+
false,
|
|
584
|
+
)
|
|
585
|
+
.option("--cache-dir <dir>", "Custom cache directory")
|
|
586
|
+
.action(async (options) => {
|
|
587
|
+
await runTasksCommand(options);
|
|
588
|
+
});
|
|
589
|
+
|
|
424
590
|
program.parse(process.argv);
|
package/bin/cli.test.mjs
CHANGED
|
@@ -49,6 +49,66 @@ describe("shadow-claw CLI", () => {
|
|
|
49
49
|
expect(stdout).toContain("run");
|
|
50
50
|
expect(stdout).toContain("serve");
|
|
51
51
|
expect(stdout).toContain("init");
|
|
52
|
+
expect(stdout).toContain("clients");
|
|
53
|
+
expect(stdout).toContain("send");
|
|
54
|
+
expect(stdout).toContain("backup");
|
|
55
|
+
expect(stdout).toContain("tasks");
|
|
56
|
+
expect(stdout).toContain("peer-id");
|
|
57
|
+
expect(stdout).toContain("webrtc");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("manages WebRTC CLI peer ID with peer-id command", async () => {
|
|
61
|
+
const cacheDir = path.join(tempDir, ".cache");
|
|
62
|
+
|
|
63
|
+
// 1. Get/generate new peer ID
|
|
64
|
+
const { stdout: out1 } = await execFileAsync(process.execPath, [
|
|
65
|
+
cliPath,
|
|
66
|
+
"peer-id",
|
|
67
|
+
"--cache-dir",
|
|
68
|
+
cacheDir,
|
|
69
|
+
"--json",
|
|
70
|
+
]);
|
|
71
|
+
const json1 = JSON.parse(out1);
|
|
72
|
+
expect(json1.peerId).toMatch(/^cli-[0-9a-z]+$/);
|
|
73
|
+
expect(json1.renewed).toBe(true);
|
|
74
|
+
|
|
75
|
+
// 2. Subsequent call returns existing peer ID
|
|
76
|
+
const { stdout: out2 } = await execFileAsync(process.execPath, [
|
|
77
|
+
cliPath,
|
|
78
|
+
"peer-id",
|
|
79
|
+
"--cache-dir",
|
|
80
|
+
cacheDir,
|
|
81
|
+
"-q",
|
|
82
|
+
]);
|
|
83
|
+
expect(out2.trim()).toBe(json1.peerId);
|
|
84
|
+
|
|
85
|
+
// 3. Renew peer ID
|
|
86
|
+
const { stdout: out3 } = await execFileAsync(process.execPath, [
|
|
87
|
+
cliPath,
|
|
88
|
+
"peer-id",
|
|
89
|
+
"--renew",
|
|
90
|
+
"--cache-dir",
|
|
91
|
+
cacheDir,
|
|
92
|
+
"--json",
|
|
93
|
+
]);
|
|
94
|
+
const json3 = JSON.parse(out3);
|
|
95
|
+
expect(json3.peerId).not.toBe(json1.peerId);
|
|
96
|
+
expect(json3.renewed).toBe(true);
|
|
97
|
+
|
|
98
|
+
// 4. Set custom peer ID
|
|
99
|
+
const { stdout: out4 } = await execFileAsync(process.execPath, [
|
|
100
|
+
cliPath,
|
|
101
|
+
"peer-id",
|
|
102
|
+
"--set",
|
|
103
|
+
"my-custom-cli-peer",
|
|
104
|
+
"--cache-dir",
|
|
105
|
+
cacheDir,
|
|
106
|
+
"-q",
|
|
107
|
+
]);
|
|
108
|
+
expect(out4.trim()).toBe("my-custom-cli-peer");
|
|
109
|
+
|
|
110
|
+
const saved = await readFile(path.join(cacheDir, "cli-peer-id"), "utf8");
|
|
111
|
+
expect(saved.trim()).toBe("my-custom-cli-peer");
|
|
52
112
|
});
|
|
53
113
|
|
|
54
114
|
it("initializes a new template with init command", async () => {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowClaw CLI — `backup` command
|
|
3
|
+
* Triggers or lists workspace backups.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CliControlClient } from "../utils/control-client.mjs";
|
|
7
|
+
|
|
8
|
+
export async function runBackupCommand(action = "trigger", options = {}) {
|
|
9
|
+
const client = new CliControlClient(options);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
if (action === "list") {
|
|
13
|
+
const backups = await client.listBackups(options.client);
|
|
14
|
+
if (!backups || backups.length === 0) {
|
|
15
|
+
console.log("No backups found.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log(`Available backups (${backups.length}):\n`);
|
|
20
|
+
backups.forEach((b, idx) => {
|
|
21
|
+
const dateStr = new Date(b.timestamp).toISOString();
|
|
22
|
+
const sizeMb = (b.totalBytes / (1024 * 1024)).toFixed(2);
|
|
23
|
+
console.log(` ${idx + 1}. [${b.id}] Client: ${b.clientId}`);
|
|
24
|
+
console.log(` Date: ${dateStr}`);
|
|
25
|
+
console.log(` Files: ${b.fileCount} (${sizeMb} MB)\n`);
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (action === "delete") {
|
|
31
|
+
if (!options.backupId) {
|
|
32
|
+
console.error("Error: --backup-id is required for delete action.");
|
|
33
|
+
process.exitCode = 1;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
await client.deleteBackup(options.backupId, options.client);
|
|
37
|
+
console.log(`Backup ${options.backupId} deleted.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Default: Trigger backup
|
|
42
|
+
let targetClientId = options.client;
|
|
43
|
+
if (!targetClientId) {
|
|
44
|
+
const clients = await client.listClients();
|
|
45
|
+
if (!clients || clients.length === 0) {
|
|
46
|
+
console.error("Error: No clients are connected to back up.");
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
targetClientId = clients[0].clientId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(`Requesting backup from client ${targetClientId}...`);
|
|
54
|
+
const result = await client.sendCommand(targetClientId, "trigger-backup", {
|
|
55
|
+
groupId: options.group,
|
|
56
|
+
token: client.token,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (result.success) {
|
|
60
|
+
console.log("Backup completed successfully.");
|
|
61
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
console.error(`Backup failed: ${result.error}`);
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.error(`Error executing backup command: ${err.message}`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShadowClaw CLI — `clients` command
|
|
3
|
+
* Lists connected clients and their capabilities.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CliControlClient } from "../utils/control-client.mjs";
|
|
7
|
+
|
|
8
|
+
export async function runClientsCommand(options = {}) {
|
|
9
|
+
const client = new CliControlClient(options);
|
|
10
|
+
try {
|
|
11
|
+
const clients = await client.listClients();
|
|
12
|
+
|
|
13
|
+
if (!clients || clients.length === 0) {
|
|
14
|
+
console.log("No clients currently registered or connected.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`Connected / registered clients (${clients.length}):\n`);
|
|
19
|
+
clients.forEach((c, idx) => {
|
|
20
|
+
const lastSeenSec = Math.max(
|
|
21
|
+
0,
|
|
22
|
+
Math.floor((Date.now() - c.lastSeen) / 1000),
|
|
23
|
+
);
|
|
24
|
+
const lastSeenStr =
|
|
25
|
+
lastSeenSec < 60
|
|
26
|
+
? `${lastSeenSec}s ago`
|
|
27
|
+
: `${Math.floor(lastSeenSec / 60)}m ago`;
|
|
28
|
+
|
|
29
|
+
console.log(` ${idx + 1}. ${c.deviceLabel} (id: ${c.clientId})`);
|
|
30
|
+
if (c.peerId) {
|
|
31
|
+
console.log(` Peer ID: ${c.peerId}`);
|
|
32
|
+
}
|
|
33
|
+
console.log(` Capabilities: ${c.capabilities.join(", ") || "none"}`);
|
|
34
|
+
console.log(` Version: ${c.version}`);
|
|
35
|
+
console.log(` Last seen: ${lastSeenStr}\n`);
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`Error fetching clients: ${err.message}`);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
}
|