shadow-claw 1.24.0 → 1.25.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 +2 -1
- package/bin/cli.mjs +111 -2
- package/bin/commands/webrtc-listen.mjs +21 -1
- package/bin/utils/control-client.mjs +44 -30
- package/bin/utils/control-client.test.mjs +10 -0
- package/bin/utils/webrtc-control-client.mjs +23 -4
- package/dist/public/AGENTS.md +8 -1
- package/dist/public/README.md +2 -1
- package/dist/public/agent.worker.js +49 -49
- package/dist/public/backup-controller-CvzDOq4T.js +1 -0
- package/dist/public/{configurePeerJs-CxpZeDJJ.js → configurePeerJs-UfQ6HUlv.js} +1 -1
- package/dist/public/docs/README.md +1 -1
- package/dist/public/docs/example/article/index.html +1 -1
- package/dist/public/docs/guides/server-development-configuration.md +28 -0
- package/dist/public/docs/publishing/index.html +1 -1
- package/dist/public/docs/skill-creator/index.html +1 -1
- package/dist/public/docs/subsystems/cli.md +25 -19
- package/dist/public/docs/subsystems/control-plane.md +11 -0
- package/dist/public/{downloadGroupFile-By0GR6eV.js → downloadGroupFile-9niHBj9n.js} +1 -1
- package/dist/public/{e2e-bridge-IGWsRl4i.js → e2e-bridge-D28tOggc.js} +1 -1
- package/dist/public/{file-viewer-D0eflgt6.js → file-viewer-BVg3B_JH.js} +1 -1
- package/dist/public/index.html +1 -1
- package/dist/public/index.js +1 -1
- package/dist/public/initControlPlane-D5kHKcB5.js +1 -0
- package/dist/public/llms.txt +1 -1
- package/dist/public/main/index.html +1 -1
- package/dist/public/main/memory/index.html +1 -1
- package/dist/public/{markdown-BnpKxSxl.js → markdown-SiFq0PTy.js} +1 -1
- package/dist/public/{orchestrator-nJj59RqD.js → orchestrator-CnwkoBpe.js} +1 -1
- package/dist/public/{orchestrator-3h9xxHUm.js → orchestrator-tHWrqi_F.js} +5 -5
- package/dist/public/{peerjs-BP4laMXj.js → peerjs-CuSJUH-r.js} +1 -1
- package/dist/public/push-client-D0lkwrK0.js +1 -0
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-CbwETBWf.js → shadow-claw-CUbhMmcB.js} +2 -2
- package/dist/public/{shadow-claw-channels-BDWG5Qa3.js → shadow-claw-channels-mY-JMvib.js} +1 -1
- package/dist/public/{shadow-claw-chat-Crv0DTCY.js → shadow-claw-chat-BN85a1xk.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-CX1IGbh9.js → shadow-claw-control-plane-C0lSxLpW.js} +2 -2
- package/dist/public/{shadow-claw-conversations-_9qAaRXk.js → shadow-claw-conversations-0Q-VC_7N.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-Blf6yGVD.js → shadow-claw-file-viewer-AZfG4D-u.js} +1 -1
- package/dist/public/{shadow-claw-files-CYySBDCl.js → shadow-claw-files-Bx4v0Ggp.js} +1 -1
- package/dist/public/{shadow-claw-git-v8S0JjuR.js → shadow-claw-git-DD7iQRxq.js} +2 -2
- package/dist/public/{shadow-claw-llm-GWgoVfhI.js → shadow-claw-llm-CfeDBZKB.js} +1 -1
- package/dist/public/{shadow-claw-networking-qHbP95z9.js → shadow-claw-networking-e0EarDVs.js} +1 -1
- package/dist/public/{shadow-claw-notifications-YyHOK2Fr.js → shadow-claw-notifications-BkxuDEQI.js} +3 -3
- package/dist/public/{shadow-claw-pages-DFZHhzFZ.js → shadow-claw-pages-BWSE91I1.js} +1 -1
- package/dist/public/{shadow-claw-settings-DhxrKIfA.js → shadow-claw-settings-BZWrAoQA.js} +2 -2
- package/dist/public/{shadow-claw-storage-BFynbP0j.js → shadow-claw-storage-Crs6LY15.js} +1 -1
- package/dist/public/{shadow-claw-task-server-3BIeb5oZ.js → shadow-claw-task-server-CKm_Ennl.js} +1 -1
- package/dist/public/{shadow-claw-tasks-Caxc9atL.js → shadow-claw-tasks-Czx6Ucf5.js} +1 -1
- package/dist/public/{shadow-claw-terminal-Beb5qF-U.js → shadow-claw-terminal-Dmtr9KY1.js} +1 -1
- package/dist/public/{shadow-claw-tools-BfJx3A9W.js → shadow-claw-tools-t3Qq4TDO.js} +1 -1
- package/dist/public/{shadow-claw-webvm-BCS8ykeP.js → shadow-claw-webvm-CIuSywD_.js} +1 -1
- package/dist/public/{syncWebMcpRegistration-B_M2fCjH.js → syncWebMcpRegistration-DzC80wVG.js} +1 -1
- package/dist/server.js +203 -203
- package/package.json +1 -1
- package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.test.ts +3 -0
- package/src/components/settings/shadow-claw-notifications/shadow-claw-notifications.ts +43 -9
- package/src/core/backup-controller.ts +33 -4
- package/src/core/control-plane-client.test.ts +9 -2
- package/src/core/control-plane-client.ts +18 -2
- package/src/core/orchestrator/utils/operations/task.ts +46 -15
- package/src/core/utils/initControlPlane.ts +2 -0
- package/src/server/app.ts +2 -0
- package/src/server/config.test.ts +49 -0
- package/src/server/config.ts +47 -0
- package/src/server/control-plane.test.ts +36 -0
- package/src/server/control-plane.ts +41 -14
- package/src/server/middleware/pna.test.ts +15 -3
- package/src/server/middleware/pna.ts +6 -2
- package/src/server/peer.ts +6 -2
- package/src/server/routes/backup.test.ts +14 -0
- package/src/server/routes/backup.ts +44 -15
- package/src/server/server.test.ts +118 -0
- package/src/server/server.ts +22 -5
- package/src/server/tls.test.ts +266 -0
- package/src/server/tls.ts +178 -0
- package/src/service-worker/fetch-proxy-rules.test.ts +41 -0
- package/src/service-worker/fetch-proxy-rules.ts +15 -8
- package/src/service-worker/fetch-proxy.ts +18 -2
- package/src/subsystems/notifications/push-client.test.ts +5 -1
- package/src/subsystems/notifications/push-client.ts +50 -12
- package/dist/public/backup-controller-C_s9in2t.js +0 -1
- package/dist/public/initControlPlane-C9jpnWev.js +0 -1
- package/dist/public/push-client-CrjEXK3U.js +0 -1
package/README.md
CHANGED
|
@@ -383,6 +383,7 @@ E2E test architecture: [`e2e/README.md`](e2e/README.md)
|
|
|
383
383
|
|
|
384
384
|
```bash
|
|
385
385
|
npm run dev # Dev server (watch mode)
|
|
386
|
+
npm run dev -- --https # Dev server with opt-in HTTPS (auto-generates self-signed cert)
|
|
386
387
|
npm start # Express server
|
|
387
388
|
npm test # Jest (*.test.ts files live next to source)
|
|
388
389
|
npm run e2e # Playwright E2E tests (e2e/*.test.ts)
|
|
@@ -413,7 +414,7 @@ npx shadow-claw webrtc listen # Start headless WebRTC Dat
|
|
|
413
414
|
npx shadow-claw peer-id # Get or generate persistent CLI Peer ID
|
|
414
415
|
```
|
|
415
416
|
|
|
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
|
+
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, and supports HTTPS endpoints via `--https` (and `--insecure` for self-signed certs). The control plane endpoint and token are printed to the console on server start.
|
|
417
418
|
|
|
418
419
|
## License
|
|
419
420
|
|
package/bin/cli.mjs
CHANGED
|
@@ -173,6 +173,36 @@ async function handleDev(portArg, options) {
|
|
|
173
173
|
.filter(Boolean),
|
|
174
174
|
);
|
|
175
175
|
|
|
176
|
+
const isHttps = Boolean(
|
|
177
|
+
options.https ||
|
|
178
|
+
["1", "true", "yes"].includes(
|
|
179
|
+
(process.env.SHADOWCLAW_HTTPS || "").toLowerCase().trim(),
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
const certPath =
|
|
183
|
+
options.cert ||
|
|
184
|
+
(
|
|
185
|
+
process.env.SHADOWCLAW_TLS_CERT ||
|
|
186
|
+
process.env.SHADOWCLAW_CERT ||
|
|
187
|
+
""
|
|
188
|
+
).trim() ||
|
|
189
|
+
undefined;
|
|
190
|
+
const keyPath =
|
|
191
|
+
options.key ||
|
|
192
|
+
(
|
|
193
|
+
process.env.SHADOWCLAW_TLS_KEY ||
|
|
194
|
+
process.env.SHADOWCLAW_KEY ||
|
|
195
|
+
""
|
|
196
|
+
).trim() ||
|
|
197
|
+
undefined;
|
|
198
|
+
const sslDir = path.resolve(
|
|
199
|
+
contentRoot,
|
|
200
|
+
options.sslDir ||
|
|
201
|
+
process.env.SHADOWCLAW_SSL_DIR ||
|
|
202
|
+
process.env.SHADOWCLAW_TLS_DIR ||
|
|
203
|
+
".cache/tls",
|
|
204
|
+
);
|
|
205
|
+
|
|
176
206
|
await startServer({
|
|
177
207
|
port,
|
|
178
208
|
bindHost: host,
|
|
@@ -183,9 +213,14 @@ async function handleDev(portArg, options) {
|
|
|
183
213
|
rootPath: distPublicDir,
|
|
184
214
|
databaseDir,
|
|
185
215
|
allowPrivateProxy: Boolean(options.allowPrivateProxy),
|
|
216
|
+
https: isHttps,
|
|
217
|
+
certPath,
|
|
218
|
+
keyPath,
|
|
219
|
+
sslDir,
|
|
186
220
|
});
|
|
187
221
|
|
|
188
|
-
const
|
|
222
|
+
const protocol = isHttps ? "https" : "http";
|
|
223
|
+
const localUrl = `${protocol}://${host}:${port}`;
|
|
189
224
|
if (options.open) {
|
|
190
225
|
openBrowser(localUrl);
|
|
191
226
|
}
|
|
@@ -218,6 +253,13 @@ program
|
|
|
218
253
|
"Allow proxy to reach private/loopback addresses",
|
|
219
254
|
false,
|
|
220
255
|
)
|
|
256
|
+
.option("--https", "Enable HTTPS dev server", false)
|
|
257
|
+
.option("--cert <path>", "Path to existing TLS certificate file")
|
|
258
|
+
.option("--key <path>", "Path to existing TLS private key file")
|
|
259
|
+
.option(
|
|
260
|
+
"--ssl-dir <path>",
|
|
261
|
+
"Directory for TLS certificate generation/storage",
|
|
262
|
+
)
|
|
221
263
|
.option("-v, --verbose", "Enable verbose request/proxy logging", false)
|
|
222
264
|
.option("--open", "Automatically open default browser", false)
|
|
223
265
|
.action(handleDev);
|
|
@@ -245,6 +287,13 @@ program
|
|
|
245
287
|
"Allow proxy to reach private/loopback addresses",
|
|
246
288
|
false,
|
|
247
289
|
)
|
|
290
|
+
.option("--https", "Enable HTTPS dev server", false)
|
|
291
|
+
.option("--cert <path>", "Path to existing TLS certificate file")
|
|
292
|
+
.option("--key <path>", "Path to existing TLS private key file")
|
|
293
|
+
.option(
|
|
294
|
+
"--ssl-dir <path>",
|
|
295
|
+
"Directory for TLS certificate generation/storage",
|
|
296
|
+
)
|
|
248
297
|
.option("-v, --verbose", "Enable verbose request/proxy logging", false)
|
|
249
298
|
.option("--open", "Automatically open default browser", false)
|
|
250
299
|
.action(handleDev);
|
|
@@ -276,6 +325,13 @@ program
|
|
|
276
325
|
"Allow proxy to reach private/loopback addresses",
|
|
277
326
|
false,
|
|
278
327
|
)
|
|
328
|
+
.option("--https", "Enable HTTPS dev server", false)
|
|
329
|
+
.option("--cert <path>", "Path to existing TLS certificate file")
|
|
330
|
+
.option("--key <path>", "Path to existing TLS private key file")
|
|
331
|
+
.option(
|
|
332
|
+
"--ssl-dir <path>",
|
|
333
|
+
"Directory for TLS certificate generation/storage",
|
|
334
|
+
)
|
|
279
335
|
.option("-v, --verbose", "Enable verbose request/proxy logging", false)
|
|
280
336
|
.option("--open", "Automatically open default browser", false)
|
|
281
337
|
.action(async (portArg, options) => {
|
|
@@ -328,6 +384,36 @@ program
|
|
|
328
384
|
.filter(Boolean),
|
|
329
385
|
);
|
|
330
386
|
|
|
387
|
+
const isHttps = Boolean(
|
|
388
|
+
options.https ||
|
|
389
|
+
["1", "true", "yes"].includes(
|
|
390
|
+
(process.env.SHADOWCLAW_HTTPS || "").toLowerCase().trim(),
|
|
391
|
+
),
|
|
392
|
+
);
|
|
393
|
+
const certPath =
|
|
394
|
+
options.cert ||
|
|
395
|
+
(
|
|
396
|
+
process.env.SHADOWCLAW_TLS_CERT ||
|
|
397
|
+
process.env.SHADOWCLAW_CERT ||
|
|
398
|
+
""
|
|
399
|
+
).trim() ||
|
|
400
|
+
undefined;
|
|
401
|
+
const keyPath =
|
|
402
|
+
options.key ||
|
|
403
|
+
(
|
|
404
|
+
process.env.SHADOWCLAW_TLS_KEY ||
|
|
405
|
+
process.env.SHADOWCLAW_KEY ||
|
|
406
|
+
""
|
|
407
|
+
).trim() ||
|
|
408
|
+
undefined;
|
|
409
|
+
const sslDir = path.resolve(
|
|
410
|
+
contentRoot,
|
|
411
|
+
options.sslDir ||
|
|
412
|
+
process.env.SHADOWCLAW_SSL_DIR ||
|
|
413
|
+
process.env.SHADOWCLAW_TLS_DIR ||
|
|
414
|
+
".cache/tls",
|
|
415
|
+
);
|
|
416
|
+
|
|
331
417
|
await startServer({
|
|
332
418
|
port,
|
|
333
419
|
bindHost: host,
|
|
@@ -338,9 +424,14 @@ program
|
|
|
338
424
|
rootPath: distPublicDir,
|
|
339
425
|
databaseDir,
|
|
340
426
|
allowPrivateProxy: Boolean(options.allowPrivateProxy),
|
|
427
|
+
https: isHttps,
|
|
428
|
+
certPath,
|
|
429
|
+
keyPath,
|
|
430
|
+
sslDir,
|
|
341
431
|
});
|
|
342
432
|
|
|
343
|
-
const
|
|
433
|
+
const protocol = isHttps ? "https" : "http";
|
|
434
|
+
const localUrl = `${protocol}://${host}:${port}`;
|
|
344
435
|
if (options.open) {
|
|
345
436
|
openBrowser(localUrl);
|
|
346
437
|
}
|
|
@@ -478,6 +569,11 @@ program
|
|
|
478
569
|
"Use TLS (wss://) for the signaling server (for listen)",
|
|
479
570
|
false,
|
|
480
571
|
)
|
|
572
|
+
.option(
|
|
573
|
+
"--https",
|
|
574
|
+
"Alias for --secure: use TLS (wss://) for the signaling server",
|
|
575
|
+
false,
|
|
576
|
+
)
|
|
481
577
|
.option(
|
|
482
578
|
"--trusted-peer <id>",
|
|
483
579
|
"Accept connections only from this peer ID (repeatable, for listen)",
|
|
@@ -486,6 +582,11 @@ program
|
|
|
486
582
|
)
|
|
487
583
|
.option("--verbose", "Verbose connection logging (for listen)", false)
|
|
488
584
|
.option("--renew-peer-id", "Renew CLI peer ID before listening", false)
|
|
585
|
+
.option(
|
|
586
|
+
"-k, --insecure",
|
|
587
|
+
"Allow self-signed TLS certificates for the signaling server (wss://)",
|
|
588
|
+
true,
|
|
589
|
+
)
|
|
489
590
|
.action(async (action, customId, options) => {
|
|
490
591
|
if (action === "listen") {
|
|
491
592
|
await runWebRtcListenCommand(options);
|
|
@@ -507,6 +608,8 @@ program
|
|
|
507
608
|
.option("--host <host>", "Control plane host")
|
|
508
609
|
.option("--port <port>", "Control plane port")
|
|
509
610
|
.option("--token <token>", "Control token")
|
|
611
|
+
.option("--https", "Connect to server via HTTPS", false)
|
|
612
|
+
.option("-k, --insecure", "Allow self-signed TLS certificates", true)
|
|
510
613
|
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
511
614
|
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
512
615
|
.option(
|
|
@@ -530,6 +633,8 @@ program
|
|
|
530
633
|
.option("--host <host>", "Control plane host")
|
|
531
634
|
.option("--port <port>", "Control plane port")
|
|
532
635
|
.option("--token <token>", "Control token")
|
|
636
|
+
.option("--https", "Connect to server via HTTPS", false)
|
|
637
|
+
.option("-k, --insecure", "Allow self-signed TLS certificates", true)
|
|
533
638
|
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
534
639
|
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
535
640
|
.option("--renew-peer-id", "Renew WebRTC CLI peer ID before sending", false)
|
|
@@ -552,6 +657,8 @@ program
|
|
|
552
657
|
.option("--host <host>", "Control plane host")
|
|
553
658
|
.option("--port <port>", "Control plane port")
|
|
554
659
|
.option("--token <token>", "Control token")
|
|
660
|
+
.option("--https", "Connect to server via HTTPS", false)
|
|
661
|
+
.option("-k, --insecure", "Allow self-signed TLS certificates", true)
|
|
555
662
|
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
556
663
|
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
557
664
|
.option(
|
|
@@ -575,6 +682,8 @@ program
|
|
|
575
682
|
.option("--host <host>", "Control plane host")
|
|
576
683
|
.option("--port <port>", "Control plane port")
|
|
577
684
|
.option("--token <token>", "Control token")
|
|
685
|
+
.option("--https", "Connect to server via HTTPS", false)
|
|
686
|
+
.option("-k, --insecure", "Allow self-signed TLS certificates", true)
|
|
578
687
|
.option("--transport <transport>", "Transport to use: http | webrtc", "http")
|
|
579
688
|
.option("--peer-id <id>", "Custom WebRTC CLI peer ID")
|
|
580
689
|
.option(
|
|
@@ -156,7 +156,23 @@ export async function runWebRtcListenCommand(options = {}) {
|
|
|
156
156
|
? parseInt(options.port, 10)
|
|
157
157
|
: parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
|
|
158
158
|
const peerPath = options.path || "/";
|
|
159
|
-
const secure = Boolean(
|
|
159
|
+
const secure = Boolean(
|
|
160
|
+
options.secure ||
|
|
161
|
+
options.https ||
|
|
162
|
+
["1", "true", "yes"].includes(
|
|
163
|
+
(process.env.SHADOWCLAW_HTTPS || "").toLowerCase().trim(),
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
// --insecure / -k: allow self-signed TLS certificates (default: true for dev)
|
|
167
|
+
const rejectUnauthorized =
|
|
168
|
+
options.insecure === false ||
|
|
169
|
+
["0", "false", "no"].includes(
|
|
170
|
+
(process.env.SHADOWCLAW_TLS_REJECT_UNAUTHORIZED || "")
|
|
171
|
+
.toLowerCase()
|
|
172
|
+
.trim(),
|
|
173
|
+
)
|
|
174
|
+
? true
|
|
175
|
+
: false;
|
|
160
176
|
const trustedPeerIds = options.trustedPeer
|
|
161
177
|
? Array.isArray(options.trustedPeer)
|
|
162
178
|
? options.trustedPeer
|
|
@@ -169,6 +185,9 @@ export async function runWebRtcListenCommand(options = {}) {
|
|
|
169
185
|
console.log(
|
|
170
186
|
`Signaling server : ${secure ? "wss" : "ws"}://${host}:${port}${peerPath}`,
|
|
171
187
|
);
|
|
188
|
+
if (secure && !rejectUnauthorized) {
|
|
189
|
+
console.log(`TLS verification : disabled (self-signed cert allowed)`);
|
|
190
|
+
}
|
|
172
191
|
if (trustedPeerIds.length > 0) {
|
|
173
192
|
console.log(`Trusted peers : ${trustedPeerIds.join(", ")}`);
|
|
174
193
|
} else {
|
|
@@ -186,6 +205,7 @@ export async function runWebRtcListenCommand(options = {}) {
|
|
|
186
205
|
port,
|
|
187
206
|
path: peerPath,
|
|
188
207
|
secure,
|
|
208
|
+
rejectUnauthorized,
|
|
189
209
|
trustedPeerIds,
|
|
190
210
|
peerId: options.peerId,
|
|
191
211
|
cacheDir: options.cacheDir,
|
|
@@ -72,7 +72,17 @@ export class CliControlClient {
|
|
|
72
72
|
this.port =
|
|
73
73
|
options.port || parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
|
|
74
74
|
this.token = options.token || resolveControlToken(options.token);
|
|
75
|
-
|
|
75
|
+
const isHttps = Boolean(
|
|
76
|
+
options.https ||
|
|
77
|
+
["1", "true", "yes"].includes(
|
|
78
|
+
(process.env.SHADOWCLAW_HTTPS || "").toLowerCase().trim(),
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
this.protocol = options.protocol || (isHttps ? "https" : "http");
|
|
82
|
+
this.rejectUnauthorized =
|
|
83
|
+
options.rejectUnauthorized !== undefined
|
|
84
|
+
? Boolean(options.rejectUnauthorized)
|
|
85
|
+
: Boolean(options.insecure === false);
|
|
76
86
|
this.transport = options.transport || "http";
|
|
77
87
|
this._webrtcClient =
|
|
78
88
|
this.transport === "webrtc"
|
|
@@ -81,6 +91,7 @@ export class CliControlClient {
|
|
|
81
91
|
port: this.port,
|
|
82
92
|
path: options.peerPath || "/",
|
|
83
93
|
secure: this.protocol === "https",
|
|
94
|
+
rejectUnauthorized: this.rejectUnauthorized,
|
|
84
95
|
peerId: options.peerId,
|
|
85
96
|
cacheDir: options.cacheDir,
|
|
86
97
|
renewPeerId: Boolean(options.renewPeerId),
|
|
@@ -99,35 +110,38 @@ export class CliControlClient {
|
|
|
99
110
|
headers["x-control-token"] = this.token;
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
const reqOptions = {
|
|
114
|
+
hostname: this.host,
|
|
115
|
+
port: this.port,
|
|
116
|
+
path: reqPath,
|
|
117
|
+
method,
|
|
118
|
+
headers,
|
|
119
|
+
timeout,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (this.protocol === "https") {
|
|
123
|
+
reqOptions.rejectUnauthorized = this.rejectUnauthorized;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const req = client.request(reqOptions, (res) => {
|
|
127
|
+
let data = "";
|
|
128
|
+
res.on("data", (chunk) => (data += chunk));
|
|
129
|
+
res.on("end", () => {
|
|
130
|
+
let parsed = data;
|
|
131
|
+
try {
|
|
132
|
+
parsed = JSON.parse(data);
|
|
133
|
+
} catch (_) {}
|
|
134
|
+
|
|
135
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
136
|
+
resolve(parsed);
|
|
137
|
+
} else {
|
|
138
|
+
const errMsg =
|
|
139
|
+
parsed?.error ||
|
|
140
|
+
`HTTP request failed with status ${res.statusCode}: ${data}`;
|
|
141
|
+
reject(new Error(errMsg));
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
131
145
|
|
|
132
146
|
req.on("error", (err) => {
|
|
133
147
|
reject(
|
|
@@ -80,4 +80,14 @@ describe("CliControlClient", () => {
|
|
|
80
80
|
const backups = await client.listBackups();
|
|
81
81
|
expect(Array.isArray(backups)).toBe(true);
|
|
82
82
|
});
|
|
83
|
+
|
|
84
|
+
it("detects HTTPS protocol from options and environment", () => {
|
|
85
|
+
const client1 = new CliControlClient({ https: true });
|
|
86
|
+
expect(client1.protocol).toBe("https");
|
|
87
|
+
|
|
88
|
+
process.env.SHADOWCLAW_HTTPS = "1";
|
|
89
|
+
const client2 = new CliControlClient();
|
|
90
|
+
expect(client2.protocol).toBe("https");
|
|
91
|
+
delete process.env.SHADOWCLAW_HTTPS;
|
|
92
|
+
});
|
|
83
93
|
});
|
|
@@ -203,6 +203,7 @@ export class CliWebRtcControlClient {
|
|
|
203
203
|
options.port || parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
|
|
204
204
|
this.path = options.path || "/";
|
|
205
205
|
this.secure = options.secure ?? false;
|
|
206
|
+
this.rejectUnauthorized = options.rejectUnauthorized ?? false;
|
|
206
207
|
this.peer = null;
|
|
207
208
|
this.cacheDir = options.cacheDir;
|
|
208
209
|
this.cliPeerId =
|
|
@@ -221,12 +222,19 @@ export class CliWebRtcControlClient {
|
|
|
221
222
|
const Peer = mod.default?.Peer || mod.default || mod.Peer;
|
|
222
223
|
|
|
223
224
|
return new Promise((resolve, reject) => {
|
|
224
|
-
const
|
|
225
|
+
const peerConfig = {
|
|
225
226
|
host: this.host,
|
|
226
227
|
port: this.port,
|
|
227
228
|
path: this.path,
|
|
228
229
|
secure: this.secure,
|
|
229
|
-
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
if (this.secure) {
|
|
233
|
+
peerConfig.config = { iceServers: [] };
|
|
234
|
+
peerConfig.wsOptions = { rejectUnauthorized: this.rejectUnauthorized };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const peer = new Peer(this.cliPeerId, peerConfig);
|
|
230
238
|
|
|
231
239
|
this.peer = peer;
|
|
232
240
|
|
|
@@ -409,6 +417,7 @@ export class CliWebRtcListener {
|
|
|
409
417
|
options.port || parseInt(process.env.SHADOWCLAW_PORT || "8888", 10);
|
|
410
418
|
this.path = options.path || "/";
|
|
411
419
|
this.secure = options.secure ?? false;
|
|
420
|
+
this.rejectUnauthorized = options.rejectUnauthorized ?? false;
|
|
412
421
|
this.cacheDir = options.cacheDir;
|
|
413
422
|
this.verbose = options.verbose ?? false;
|
|
414
423
|
this.handlers = options.handlers || {};
|
|
@@ -602,12 +611,22 @@ export class CliWebRtcListener {
|
|
|
602
611
|
return new Promise((resolve, reject) => {
|
|
603
612
|
let isOpened = false;
|
|
604
613
|
|
|
605
|
-
const
|
|
614
|
+
const peerConfig = {
|
|
606
615
|
host: this.host,
|
|
607
616
|
port: this.port,
|
|
608
617
|
path: this.path,
|
|
609
618
|
secure: this.secure,
|
|
610
|
-
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
// When using self-signed certificates (typical for local dev), Node's
|
|
622
|
+
// WebSocket client rejects the connection by default. Pass rejectUnauthorized
|
|
623
|
+
// via PeerJS's ws-specific config option to allow self-signed certs.
|
|
624
|
+
if (this.secure) {
|
|
625
|
+
peerConfig.config = { iceServers: [] };
|
|
626
|
+
peerConfig.wsOptions = { rejectUnauthorized: this.rejectUnauthorized };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const peer = new Peer(this.cliPeerId, peerConfig);
|
|
611
630
|
|
|
612
631
|
this._peer = peer;
|
|
613
632
|
this._running = true;
|
package/dist/public/AGENTS.md
CHANGED
|
@@ -106,6 +106,13 @@ To prevent infinite execution loops, the system enforces a strict recursion guar
|
|
|
106
106
|
- Do not call `executeTool` from provider loops without passing the active enabled tool list (`allowedTools`). Runtime allowlist checks are enforced in `executeTool` and are required even when schemas already constrain generation.
|
|
107
107
|
- Conversation-level `pinnedMaxTokens` overrides are optional and must remain model-aware (clamped to provider/model output limits).
|
|
108
108
|
|
|
109
|
+
### Private Network Access & Loopback Connections
|
|
110
|
+
|
|
111
|
+
- **Service Worker Loopback Bypass:** The Service Worker fetch proxy (`src/service-worker/fetch-proxy-rules.ts`) unconditionally bypasses all loopback (`localhost`, `127.0.0.1`, `::1`, `[::1]`) and private IP ranges (`10.x`, `172.16-31.x`, `192.168.x`) so browser tabs hosted on static platforms (such as GitHub Pages or Cloudflare Pages) can connect directly to local servers without Service Worker interception.
|
|
112
|
+
- **Client `targetAddressSpace` Signaling:** Outgoing client fetch requests targeting local or private network endpoints (`control-plane-client.ts`, `backup-controller.ts`, `push-client.ts`, `task.ts`, and `fetch-proxy.ts`) must supply `targetAddressSpace: 'loopback'` (or `'private'`) for Chromium Private Network Access (PNA) compliance.
|
|
113
|
+
- **Server PNA & Cross-Origin Headers:** Server endpoints set `Access-Control-Allow-Private-Network: true` on both preflight (`OPTIONS`) and cross-origin responses, and trust `.github.io` / `.pages.dev` / `allowedOrigins` origins for browser integration.
|
|
114
|
+
|
|
115
|
+
|
|
109
116
|
### HTML Sanitization & Trusted Types
|
|
110
117
|
|
|
111
118
|
- **Explicit Pre-Sanitization:** All dynamically rendered HTML, inline SVGs, or iframe `srcdoc` values must be sanitized using DOMPurify (e.g., `sanitizeToTrustedHtml` or `sanitizeSrcdocHtml`) **before** being passed to the Trusted Types policy.
|
|
@@ -152,7 +159,7 @@ Markdown and HTML preview work should preserve the Settings-backed iframe host a
|
|
|
152
159
|
### CLI & Dual-Root Build Pipeline
|
|
153
160
|
|
|
154
161
|
- **Dual-Root Path Resolution:** The build toolchain (`bin/build/build.mjs`) cleanly decouples `toolchainRoot` (the ShadowClaw package/repo root) from `contentRoot` (the consumer template project). In-repo builds (`resolve(contentRoot) === resolve(toolchainRoot)`) preserve the standalone in-tree compilation path. CLI/template consumer builds read pre-bundled web assets from `toolchainRoot/dist/public` and inject `pages/`, `site-config.json`, `assets/`, `.agents/`, and pretty routes from `contentRoot`, outputting to `<contentRoot>/dist/public`.
|
|
155
|
-
- **CLI Commands (`bin/cli.mjs`):** The `shadow-claw` / `shadowclaw` CLI provides `build`, `dev`, `run`, `serve`, `init`, `clients`, `send`, `backup`, `tasks`, `webrtc`, and `peer-id` commands. It supports running dev servers programmatically via `startServer` (`src/server/server.ts`) with custom `--root-path` and `--database-dir` arguments.
|
|
162
|
+
- **CLI Commands (`bin/cli.mjs`):** The `shadow-claw` / `shadowclaw` CLI provides `build`, `dev`, `run`, `serve`, `init`, `clients`, `send`, `backup`, `tasks`, `webrtc`, and `peer-id` commands. It supports running dev servers programmatically via `startServer` (`src/server/server.ts`) with custom `--root-path` and `--database-dir` arguments. Dev/run/serve commands accept `--https`, `--cert <path>`, `--key <path>`, and `--ssl-dir <path>` for opt-in HTTPS with auto-generated self-signed certs; control plane commands (`clients`, `send`, `backup`, `tasks`) accept `--https` and `-k, --insecure` to reach an HTTPS control plane server.
|
|
156
163
|
- **Naming Conventions:** Refer to the product/brand in prose and documentation as **ShadowClaw**. Use kebab-case **`shadow-claw`** for package name, CLI commands (`npx shadow-claw`), repositories, directory paths, and custom elements.
|
|
157
164
|
|
|
158
165
|
## What to Avoid
|
package/dist/public/README.md
CHANGED
|
@@ -383,6 +383,7 @@ E2E test architecture: [`e2e/README.md`](e2e/README.md)
|
|
|
383
383
|
|
|
384
384
|
```bash
|
|
385
385
|
npm run dev # Dev server (watch mode)
|
|
386
|
+
npm run dev -- --https # Dev server with opt-in HTTPS (auto-generates self-signed cert)
|
|
386
387
|
npm start # Express server
|
|
387
388
|
npm test # Jest (*.test.ts files live next to source)
|
|
388
389
|
npm run e2e # Playwright E2E tests (e2e/*.test.ts)
|
|
@@ -413,7 +414,7 @@ npx shadow-claw webrtc listen # Start headless WebRTC Dat
|
|
|
413
414
|
npx shadow-claw peer-id # Get or generate persistent CLI Peer ID
|
|
414
415
|
```
|
|
415
416
|
|
|
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
|
+
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, and supports HTTPS endpoints via `--https` (and `--insecure` for self-signed certs). The control plane endpoint and token are printed to the console on server start.
|
|
417
418
|
|
|
418
419
|
## License
|
|
419
420
|
|