agent-yes 1.120.0 → 1.121.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/dist/SUPPORTED_CLIS-CegJgoEf.js +8 -0
- package/dist/{SUPPORTED_CLIS-BD8zWc7O.js → SUPPORTED_CLIS-O57LGUEG.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{serve-6RqphTG0.js → serve-D2czcYNC.js} +18 -9
- package/dist/{setup-B5TPF2MV.js → setup-f1FIFcZm.js} +2 -2
- package/dist/share-B6QVr5D1.js +522 -0
- package/dist/{subcommands-CCgzXvQ-.js → subcommands-CzpZQHO6.js} +3 -3
- package/dist/{subcommands-BVcos4UW.js → subcommands-DobVXouH.js} +1 -1
- package/dist/{ts-C78N0K4F.js → ts-D91dm1E0.js} +2 -2
- package/dist/{versionChecker-CYZtJKMG.js → versionChecker-CAtpgnoQ.js} +2 -2
- package/lab/ui/blog/e2ee-share-links/index.html +299 -0
- package/lab/ui/e2e.d.ts +47 -0
- package/lab/ui/e2e.js +245 -0
- package/lab/ui/index.html +180 -26
- package/package.json +6 -2
- package/scripts/check-e2e.ts +40 -0
- package/ts/e2e-crypto.spec.ts +235 -0
- package/ts/serve.ts +31 -9
- package/ts/share.ts +205 -32
- package/dist/SUPPORTED_CLIS-CoYWGWbP.js +0 -8
- package/dist/share-B7J79Wq9.js +0 -254
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-D91dm1E0.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-CAtpgnoQ.js";
|
|
4
|
+
import "./pidStore-B5vBu8Px.js";
|
|
5
|
+
import "./globalPidIndex-gZuTvTBs.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-O57LGUEG.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-D91dm1E0.js";
|
|
2
2
|
|
|
3
3
|
//#region ts/SUPPORTED_CLIS.ts
|
|
4
4
|
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
5
5
|
|
|
6
6
|
//#endregion
|
|
7
7
|
export { SUPPORTED_CLIS as t };
|
|
8
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
8
|
+
//# sourceMappingURL=SUPPORTED_CLIS-O57LGUEG.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { n as logger } from "./logger-B9h0djqx.js";
|
|
3
|
-
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-
|
|
3
|
+
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-CAtpgnoQ.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
482
482
|
{
|
|
483
483
|
const rawArg = process.argv[2];
|
|
484
484
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
485
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
485
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DobVXouH.js");
|
|
486
486
|
if (isHelpFlag && process.argv.length === 3) {
|
|
487
487
|
cmdHelp();
|
|
488
488
|
process.exit(0);
|
|
@@ -515,7 +515,7 @@ if (config.useRust) {
|
|
|
515
515
|
}
|
|
516
516
|
}
|
|
517
517
|
if (rustBinary) {
|
|
518
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
518
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CegJgoEf.js");
|
|
519
519
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
520
520
|
if (config.verbose) {
|
|
521
521
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-D91dm1E0.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-CAtpgnoQ.js";
|
|
4
4
|
import "./pidStore-B5vBu8Px.js";
|
|
5
5
|
import "./globalPidIndex-gZuTvTBs.js";
|
|
6
6
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-D91dm1E0.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-CAtpgnoQ.js";
|
|
4
4
|
import "./pidStore-B5vBu8Px.js";
|
|
5
5
|
import "./globalPidIndex-gZuTvTBs.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-O57LGUEG.js";
|
|
7
7
|
import "./remotes-BufkGk0e.js";
|
|
8
|
-
import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-
|
|
8
|
+
import { c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, m as snapshotStatus, r as controlCodeFromName, u as readNotes } from "./subcommands-CzpZQHO6.js";
|
|
9
9
|
import yargs from "yargs";
|
|
10
10
|
import { mkdir, open, readFile, writeFile } from "fs/promises";
|
|
11
11
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -248,7 +248,7 @@ async function cmdServeDaemon(sub, args) {
|
|
|
248
248
|
}
|
|
249
249
|
process.stdout.write(` ay serve logs # view server logs\n`);
|
|
250
250
|
process.stdout.write(` ay serve uninstall # remove daemon\n`);
|
|
251
|
-
if (webrtcish) process.stdout.write("\nthe WebRTC share link
|
|
251
|
+
if (webrtcish) process.stdout.write("\nthe WebRTC share link carries a secret, so the daemon does NOT log it —\nread it from ~/.agent-yes/.share-link (mode 0600). The room persists in\n~/.agent-yes/.share-room, so the link survives restarts.\n");
|
|
252
252
|
}
|
|
253
253
|
return code ?? 1;
|
|
254
254
|
}
|
|
@@ -780,6 +780,7 @@ Options:
|
|
|
780
780
|
if (req.method === "GET" && (p === "/" || p === "/index.html")) return serveUiFile("index.html", "text/html; charset=utf-8");
|
|
781
781
|
if (req.method === "GET" && p === "/room-client.js") return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
|
|
782
782
|
if (req.method === "GET" && p === "/console-logic.js") return serveUiFile("console-logic.js", "text/javascript; charset=utf-8");
|
|
783
|
+
if (req.method === "GET" && p === "/e2e.js") return serveUiFile("e2e.js", "text/javascript; charset=utf-8");
|
|
783
784
|
if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
|
|
784
785
|
return apiFetch(req);
|
|
785
786
|
};
|
|
@@ -822,14 +823,22 @@ Options:
|
|
|
822
823
|
const webrtcVal = argv.webrtc ?? argv.share;
|
|
823
824
|
const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
|
|
824
825
|
try {
|
|
825
|
-
const { startShare, loadOrCreateShareRoom } = await import("./share-
|
|
826
|
-
const { link, close } = await startShare({
|
|
826
|
+
const { startShare, loadOrCreateShareRoom } = await import("./share-B6QVr5D1.js");
|
|
827
|
+
const { room, link, close } = await startShare({
|
|
827
828
|
url: explicitUrl ?? await loadOrCreateShareRoom(),
|
|
828
829
|
localFetch: apiFetch,
|
|
829
830
|
apiToken: token
|
|
830
831
|
});
|
|
831
832
|
closeShare = close;
|
|
832
|
-
|
|
833
|
+
const persistNote = explicitUrl ? "\n" : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`;
|
|
834
|
+
if (process.stdout.isTTY) process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` + persistNote);
|
|
835
|
+
else {
|
|
836
|
+
const linkFile = path.join(process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes"), ".share-link");
|
|
837
|
+
try {
|
|
838
|
+
await writeFile(linkFile, link + "\n", { mode: 384 });
|
|
839
|
+
} catch {}
|
|
840
|
+
process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC · room ${room} — the link carries a secret, so it is NOT logged.\n read it from ${linkFile} (mode 0600); delete ~/.agent-yes/.share-room to rotate\n\n`);
|
|
841
|
+
}
|
|
833
842
|
} catch (e) {
|
|
834
843
|
process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
|
|
835
844
|
if (!wantHttp) return 1;
|
|
@@ -853,4 +862,4 @@ Options:
|
|
|
853
862
|
|
|
854
863
|
//#endregion
|
|
855
864
|
export { cmdServe };
|
|
856
|
-
//# sourceMappingURL=serve-
|
|
865
|
+
//# sourceMappingURL=serve-D2czcYNC.js.map
|
|
@@ -69,7 +69,7 @@ async function cmdSetup(rest) {
|
|
|
69
69
|
if (!existsSync$1(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
|
|
70
70
|
if (noShare) return 0;
|
|
71
71
|
process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
|
|
72
|
-
const { cmdServe } = await import("./serve-
|
|
72
|
+
const { cmdServe } = await import("./serve-D2czcYNC.js");
|
|
73
73
|
return cmdServe([
|
|
74
74
|
"install",
|
|
75
75
|
"--share",
|
|
@@ -79,4 +79,4 @@ async function cmdSetup(rest) {
|
|
|
79
79
|
|
|
80
80
|
//#endregion
|
|
81
81
|
export { cmdSetup };
|
|
82
|
-
//# sourceMappingURL=setup-
|
|
82
|
+
//# sourceMappingURL=setup-f1FIFcZm.js.map
|
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { randomBytes } from "crypto";
|
|
5
|
+
|
|
6
|
+
//#region lab/ui/e2e.js
|
|
7
|
+
const V = 1;
|
|
8
|
+
const PROTO = `ay-e2e-${V}`;
|
|
9
|
+
const MARKER = `e${V}.`;
|
|
10
|
+
const INFO_AUTH = `ay/${PROTO}/auth`;
|
|
11
|
+
const INFO_H2C = `ay/${PROTO}/key/host->client`;
|
|
12
|
+
const INFO_C2H = `ay/${PROTO}/key/client->host`;
|
|
13
|
+
const MAX_CHUNK = 12e3;
|
|
14
|
+
const CONFIRM_TIMEOUT_MS = 5e3;
|
|
15
|
+
const VER = 1;
|
|
16
|
+
const FLAG_CONFIRM = 1;
|
|
17
|
+
const HEADER_LEN = 14;
|
|
18
|
+
const NONCE_LEN = 12;
|
|
19
|
+
const TAG_LEN = 16;
|
|
20
|
+
const COUNTER_MAX = (1n << 64n) - 1n;
|
|
21
|
+
if (PROTO !== `ay-e2e-${V}` || MARKER !== `e${V}.` || !INFO_AUTH.startsWith(`ay/${PROTO}/`)) throw new Error("e2e: version constants disagree");
|
|
22
|
+
const subtle = globalThis.crypto.subtle;
|
|
23
|
+
const enc = new TextEncoder();
|
|
24
|
+
const dec = new TextDecoder();
|
|
25
|
+
const HEX64 = /^[0-9a-f]{64}$/;
|
|
26
|
+
function concatBytes(...arrs) {
|
|
27
|
+
let len = 0;
|
|
28
|
+
for (const a of arrs) len += a.length;
|
|
29
|
+
const out = new Uint8Array(len);
|
|
30
|
+
let o = 0;
|
|
31
|
+
for (const a of arrs) {
|
|
32
|
+
out.set(a, o);
|
|
33
|
+
o += a.length;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function hexToBytes(hex) {
|
|
38
|
+
const out = new Uint8Array(hex.length / 2);
|
|
39
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function bytesToHex(b) {
|
|
43
|
+
let s = "";
|
|
44
|
+
for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
|
|
45
|
+
return s;
|
|
46
|
+
}
|
|
47
|
+
async function sha256(bytes) {
|
|
48
|
+
return new Uint8Array(await subtle.digest("SHA-256", bytes));
|
|
49
|
+
}
|
|
50
|
+
async function hkdf32(ikm, salt, info) {
|
|
51
|
+
const base = await subtle.importKey("raw", ikm, "HKDF", false, ["deriveBits"]);
|
|
52
|
+
const bits = await subtle.deriveBits({
|
|
53
|
+
name: "HKDF",
|
|
54
|
+
hash: "SHA-256",
|
|
55
|
+
salt,
|
|
56
|
+
info: enc.encode(info)
|
|
57
|
+
}, base, 256);
|
|
58
|
+
return new Uint8Array(bits);
|
|
59
|
+
}
|
|
60
|
+
function validateS(s) {
|
|
61
|
+
if (typeof s !== "string" || !HEX64.test(s)) throw new Error("invalid share token");
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
function ikmFromS(s) {
|
|
65
|
+
return hexToBytes(validateS(s));
|
|
66
|
+
}
|
|
67
|
+
async function deriveAuthToken(s, room, sighost) {
|
|
68
|
+
const salt = await sha256(enc.encode(`${room}\n${sighost}`));
|
|
69
|
+
return bytesToHex(await hkdf32(ikmFromS(s), salt, INFO_AUTH));
|
|
70
|
+
}
|
|
71
|
+
async function importAesKey(raw) {
|
|
72
|
+
return subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
73
|
+
}
|
|
74
|
+
async function deriveDirKeys(s, transcriptHash) {
|
|
75
|
+
const ikm = ikmFromS(s);
|
|
76
|
+
const h2c = await hkdf32(ikm, transcriptHash, INFO_H2C);
|
|
77
|
+
const c2h = await hkdf32(ikm, transcriptHash, INFO_C2H);
|
|
78
|
+
return {
|
|
79
|
+
keyH2C: await importAesKey(h2c),
|
|
80
|
+
keyC2H: await importAesKey(c2h)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function allFingerprints(sdp) {
|
|
84
|
+
const out = [];
|
|
85
|
+
const re = /^a=fingerprint:(.*)$/gim;
|
|
86
|
+
let m;
|
|
87
|
+
while (m = re.exec(sdp)) out.push(m[1].trim().toLowerCase());
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function firstAttr(sdp, name) {
|
|
91
|
+
const m = new RegExp(`^a=${name}:(.*)$`, "im").exec(sdp);
|
|
92
|
+
return m ? m[1].trim().toLowerCase() : "";
|
|
93
|
+
}
|
|
94
|
+
async function computeTranscriptHash(offerSdp, answerSdp) {
|
|
95
|
+
const offerFps = allFingerprints(offerSdp).sort();
|
|
96
|
+
const answerFps = allFingerprints(answerSdp).sort();
|
|
97
|
+
if (!offerFps.length || !answerFps.length) throw new Error("e2e: missing DTLS fingerprint");
|
|
98
|
+
for (const fp of offerFps.concat(answerFps)) if (!fp.startsWith("sha-256")) throw new Error("e2e: non-sha-256 DTLS fingerprint");
|
|
99
|
+
const input = `${PROTO}\noffer=${offerFps.join(",")};setup=${firstAttr(offerSdp, "setup")};ufrag=${firstAttr(offerSdp, "ice-ufrag")}\nanswer=${answerFps.join(",")};setup=${firstAttr(answerSdp, "setup")};ufrag=${firstAttr(answerSdp, "ice-ufrag")}`;
|
|
100
|
+
return await sha256(enc.encode(input));
|
|
101
|
+
}
|
|
102
|
+
function nonceFromCounter(ctr) {
|
|
103
|
+
const n = new Uint8Array(NONCE_LEN);
|
|
104
|
+
new DataView(n.buffer).setBigUint64(4, ctr, false);
|
|
105
|
+
return n;
|
|
106
|
+
}
|
|
107
|
+
async function seal(key, sendState, flags, transcriptHash, plaintext) {
|
|
108
|
+
const ctr = sendState.sendCtr;
|
|
109
|
+
if (ctr >= COUNTER_MAX) throw new Error("e2e: nonce counter overflow");
|
|
110
|
+
sendState.sendCtr = ctr + 1n;
|
|
111
|
+
const nonce = nonceFromCounter(ctr);
|
|
112
|
+
const header = new Uint8Array(HEADER_LEN);
|
|
113
|
+
header[0] = VER;
|
|
114
|
+
header[1] = flags & 255;
|
|
115
|
+
header.set(nonce, 2);
|
|
116
|
+
const aad = concatBytes(header, transcriptHash);
|
|
117
|
+
return concatBytes(header, new Uint8Array(await subtle.encrypt({
|
|
118
|
+
name: "AES-GCM",
|
|
119
|
+
iv: nonce,
|
|
120
|
+
additionalData: aad,
|
|
121
|
+
tagLength: 128
|
|
122
|
+
}, key, plaintext))).buffer;
|
|
123
|
+
}
|
|
124
|
+
async function open$1(key, frame, transcriptHash, recvState) {
|
|
125
|
+
const buf = frame instanceof Uint8Array ? frame : new Uint8Array(frame);
|
|
126
|
+
if (buf.length < HEADER_LEN + TAG_LEN) throw new Error("e2e: short frame");
|
|
127
|
+
if (buf[0] !== VER) throw new Error("e2e: bad version");
|
|
128
|
+
const header = buf.subarray(0, HEADER_LEN);
|
|
129
|
+
const nonce = buf.subarray(2, HEADER_LEN);
|
|
130
|
+
const ndv = new DataView(nonce.buffer, nonce.byteOffset, NONCE_LEN);
|
|
131
|
+
if (ndv.getUint32(0, false) !== 0) throw new Error("e2e: bad epoch");
|
|
132
|
+
const ctr = ndv.getBigUint64(4, false);
|
|
133
|
+
const sealed = buf.subarray(HEADER_LEN);
|
|
134
|
+
const aad = concatBytes(header, transcriptHash);
|
|
135
|
+
const ptBuf = await subtle.decrypt({
|
|
136
|
+
name: "AES-GCM",
|
|
137
|
+
iv: nonce,
|
|
138
|
+
additionalData: aad,
|
|
139
|
+
tagLength: 128
|
|
140
|
+
}, key, sealed);
|
|
141
|
+
if (recvState.lastSeen === -1n && ctr !== 0n) throw new Error("e2e: first frame must be counter-0");
|
|
142
|
+
if (ctr <= recvState.lastSeen) throw new Error("e2e: replay/reorder");
|
|
143
|
+
recvState.lastSeen = ctr;
|
|
144
|
+
return {
|
|
145
|
+
counter: ctr,
|
|
146
|
+
flags: header[1],
|
|
147
|
+
plaintext: new Uint8Array(ptBuf)
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function packEnvelope(obj) {
|
|
151
|
+
return enc.encode(JSON.stringify(obj));
|
|
152
|
+
}
|
|
153
|
+
function unpackEnvelope(bytes) {
|
|
154
|
+
return JSON.parse(dec.decode(bytes));
|
|
155
|
+
}
|
|
156
|
+
function parseSecret(token) {
|
|
157
|
+
const mk = /^e(\d+)\.(.*)$/.exec(token);
|
|
158
|
+
if (mk) {
|
|
159
|
+
if (mk[1] !== String(V)) throw new Error("update required");
|
|
160
|
+
if (!HEX64.test(mk[2])) throw new Error("malformed encrypted link");
|
|
161
|
+
return {
|
|
162
|
+
s: mk[2],
|
|
163
|
+
v2: true
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (/^e\d/i.test(token)) throw new Error("malformed encrypted link");
|
|
167
|
+
return {
|
|
168
|
+
s: token,
|
|
169
|
+
v2: false
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function randomHex(n) {
|
|
173
|
+
const b = new Uint8Array(n);
|
|
174
|
+
globalThis.crypto.getRandomValues(b);
|
|
175
|
+
return bytesToHex(b);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region ts/share.ts
|
|
180
|
+
const SUB = "ay-signal-1";
|
|
181
|
+
const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
182
|
+
const DEFAULT_SIGHOST = "s.agent-yes.com";
|
|
183
|
+
function shareRoomPath() {
|
|
184
|
+
const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
185
|
+
return path.join(home, ".share-room");
|
|
186
|
+
}
|
|
187
|
+
async function loadOrCreateShareRoom(sighost = DEFAULT_SIGHOST) {
|
|
188
|
+
try {
|
|
189
|
+
const url = (await readFile(shareRoomPath(), "utf-8")).trim();
|
|
190
|
+
if (url.startsWith("webrtc://")) {
|
|
191
|
+
if (parseShareUrl(url).token.startsWith(MARKER)) return url;
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
const url = `webrtc://${"r" + randomBytes(3).toString("hex")}:${MARKER}${randomBytes(32).toString("hex")}@${sighost}`;
|
|
195
|
+
await mkdir(path.dirname(shareRoomPath()), { recursive: true });
|
|
196
|
+
await writeFile(shareRoomPath(), url, { mode: 384 });
|
|
197
|
+
return url;
|
|
198
|
+
}
|
|
199
|
+
function parseShareUrl(s) {
|
|
200
|
+
const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
|
|
201
|
+
if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
|
|
202
|
+
return {
|
|
203
|
+
room: m[1],
|
|
204
|
+
token: m[2],
|
|
205
|
+
host: m[3]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async function linkFromBunCache() {
|
|
209
|
+
const { existsSync, symlinkSync, mkdirSync, readdirSync } = await import("fs");
|
|
210
|
+
const path = (await import("path")).default;
|
|
211
|
+
const { createRequire } = await import("module");
|
|
212
|
+
const require = createRequire(import.meta.url);
|
|
213
|
+
const pkg = path.dirname(require.resolve("node-datachannel/package.json"));
|
|
214
|
+
const bin = path.join(pkg, "build", "Release", "node_datachannel.node");
|
|
215
|
+
const cacheRoot = path.join((await import("os")).homedir(), ".bun", "install", "cache");
|
|
216
|
+
if (existsSync(bin) && existsSync(cacheRoot)) for (const d of readdirSync(cacheRoot)) {
|
|
217
|
+
if (!d.startsWith("node-datachannel@")) continue;
|
|
218
|
+
const dst = path.join(cacheRoot, d, "build", "Release");
|
|
219
|
+
mkdirSync(dst, { recursive: true });
|
|
220
|
+
const link = path.join(dst, "node_datachannel.node");
|
|
221
|
+
if (!existsSync(link)) symlinkSync(bin, link);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function ndPackageDir() {
|
|
225
|
+
try {
|
|
226
|
+
const path = (await import("path")).default;
|
|
227
|
+
const { createRequire } = await import("module");
|
|
228
|
+
const require = createRequire(import.meta.url);
|
|
229
|
+
return path.dirname(require.resolve("node-datachannel/package.json"));
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function ensureAddon(ndDir) {
|
|
235
|
+
const { existsSync } = await import("fs");
|
|
236
|
+
const path = (await import("path")).default;
|
|
237
|
+
if (existsSync(path.join(ndDir, "build", "Release", "node_datachannel.node"))) return;
|
|
238
|
+
try {
|
|
239
|
+
const { createRequire } = await import("module");
|
|
240
|
+
const binJs = createRequire(import.meta.url).resolve("prebuild-install/bin.js", { paths: [ndDir] });
|
|
241
|
+
const { spawnSync } = await import("child_process");
|
|
242
|
+
process.stderr.write("fetching node-datachannel prebuilt binary (one-time)…\n");
|
|
243
|
+
spawnSync(process.execPath, [
|
|
244
|
+
binJs,
|
|
245
|
+
"-r",
|
|
246
|
+
"napi"
|
|
247
|
+
], {
|
|
248
|
+
cwd: ndDir,
|
|
249
|
+
stdio: "ignore"
|
|
250
|
+
});
|
|
251
|
+
} catch {}
|
|
252
|
+
}
|
|
253
|
+
async function importRTC() {
|
|
254
|
+
const ndDir = await ndPackageDir();
|
|
255
|
+
if (ndDir) await ensureAddon(ndDir);
|
|
256
|
+
try {
|
|
257
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
258
|
+
} catch (firstErr) {
|
|
259
|
+
await linkFromBunCache().catch(() => {});
|
|
260
|
+
try {
|
|
261
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
262
|
+
} catch {
|
|
263
|
+
throw firstErr;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** Start the share bridge. Resolves once signaling is connected; runs until the
|
|
268
|
+
* process exits, reconnecting signaling on drop. Returns the shareable link. */
|
|
269
|
+
async function startShare(opts) {
|
|
270
|
+
opts.url;
|
|
271
|
+
const sighost = opts.sighost ?? DEFAULT_SIGHOST;
|
|
272
|
+
const { room, token, host } = opts.url ? parseShareUrl(opts.url) : {
|
|
273
|
+
room: "r" + randomBytes(3).toString("hex"),
|
|
274
|
+
token: `${MARKER}${randomBytes(32).toString("hex")}`,
|
|
275
|
+
host: sighost
|
|
276
|
+
};
|
|
277
|
+
const { s: S, v2 } = parseSecret(token);
|
|
278
|
+
if (!v2) throw new Error("refusing to host an unencrypted room — delete ~/.agent-yes/.share-room to rotate to an encrypted link");
|
|
279
|
+
const authToken = await deriveAuthToken(S, room, host);
|
|
280
|
+
const RTCPeerConnection = await importRTC();
|
|
281
|
+
const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
|
|
282
|
+
const link = `${host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778"}/#${room}:${MARKER}${S}${host === "s.agent-yes.com" ? "" : "@" + host}`;
|
|
283
|
+
const peers = /* @__PURE__ */ new Map();
|
|
284
|
+
let closed = false;
|
|
285
|
+
let currentWs;
|
|
286
|
+
const connectSignaling = (onReady) => {
|
|
287
|
+
if (closed) return;
|
|
288
|
+
const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
|
|
289
|
+
currentWs = ws;
|
|
290
|
+
let ready = false;
|
|
291
|
+
ws.onopen = () => {
|
|
292
|
+
ws.send(JSON.stringify({
|
|
293
|
+
type: "hello",
|
|
294
|
+
role: "host",
|
|
295
|
+
v: 2,
|
|
296
|
+
token: authToken
|
|
297
|
+
}));
|
|
298
|
+
ready = true;
|
|
299
|
+
onReady();
|
|
300
|
+
};
|
|
301
|
+
ws.onmessage = async (ev) => {
|
|
302
|
+
if (closed) return;
|
|
303
|
+
const m = JSON.parse(ev.data);
|
|
304
|
+
if (m.type === "peer-join") startPeer(ws, m.peer);
|
|
305
|
+
else if (m.type === "answer") {
|
|
306
|
+
const peer = peers.get(m.from);
|
|
307
|
+
if (!peer) return;
|
|
308
|
+
try {
|
|
309
|
+
await peer.pc.setRemoteDescription({
|
|
310
|
+
type: "answer",
|
|
311
|
+
sdp: m.sdp
|
|
312
|
+
});
|
|
313
|
+
peer.th = await computeTranscriptHash(peer.pc.localDescription.sdp, peer.pc.remoteDescription.sdp);
|
|
314
|
+
const { keyH2C, keyC2H } = await deriveDirKeys(S, peer.th);
|
|
315
|
+
peer.keyH2C = keyH2C;
|
|
316
|
+
peer.keyC2H = keyC2H;
|
|
317
|
+
peer.resolveKeys();
|
|
318
|
+
} catch {
|
|
319
|
+
closePeer(m.from);
|
|
320
|
+
}
|
|
321
|
+
} else if (m.type === "candidate") await peers.get(m.from)?.pc.addIceCandidate(m.candidate).catch(() => {});
|
|
322
|
+
else if (m.type === "peer-leave") closePeer(m.peer);
|
|
323
|
+
};
|
|
324
|
+
ws.onclose = (ev) => {
|
|
325
|
+
if (closed) return;
|
|
326
|
+
if (ev?.code === 1008) {
|
|
327
|
+
closed = true;
|
|
328
|
+
process.stderr.write("[share] room rejected by signaling server — delete ~/.agent-yes/.share-room to rotate the room\n");
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
setTimeout(() => connectSignaling(() => {}), ready ? 1500 : 4e3);
|
|
332
|
+
};
|
|
333
|
+
ws.onerror = () => {};
|
|
334
|
+
return ws;
|
|
335
|
+
};
|
|
336
|
+
function startPeer(ws, peerId) {
|
|
337
|
+
const pc = new RTCPeerConnection({ iceServers: ICE });
|
|
338
|
+
let resolveKeys;
|
|
339
|
+
const keysReady = new Promise((r) => resolveKeys = r);
|
|
340
|
+
const peer = {
|
|
341
|
+
pc,
|
|
342
|
+
aborts: /* @__PURE__ */ new Map(),
|
|
343
|
+
send: { sendCtr: 0n },
|
|
344
|
+
recv: { lastSeen: -1n },
|
|
345
|
+
keysReady,
|
|
346
|
+
resolveKeys,
|
|
347
|
+
myNonce: randomHex(16),
|
|
348
|
+
confirmedIn: false,
|
|
349
|
+
confirmedOut: false,
|
|
350
|
+
confirmed: false,
|
|
351
|
+
recvChain: Promise.resolve(),
|
|
352
|
+
sendChain: Promise.resolve()
|
|
353
|
+
};
|
|
354
|
+
peers.set(peerId, peer);
|
|
355
|
+
pc.onicecandidate = (e) => {
|
|
356
|
+
if (e.candidate) ws.send(JSON.stringify({
|
|
357
|
+
type: "candidate",
|
|
358
|
+
to: peerId,
|
|
359
|
+
candidate: e.candidate
|
|
360
|
+
}));
|
|
361
|
+
};
|
|
362
|
+
pc.onconnectionstatechange = () => {
|
|
363
|
+
if ([
|
|
364
|
+
"failed",
|
|
365
|
+
"closed",
|
|
366
|
+
"disconnected"
|
|
367
|
+
].includes(pc.connectionState)) closePeer(peerId);
|
|
368
|
+
};
|
|
369
|
+
const dc = pc.createDataChannel("api");
|
|
370
|
+
dc.binaryType = "arraybuffer";
|
|
371
|
+
dc.onopen = async () => {
|
|
372
|
+
try {
|
|
373
|
+
await peer.keysReady;
|
|
374
|
+
enqueueSeal(peerId, dc, peer, FLAG_CONFIRM, {
|
|
375
|
+
t: "confirm",
|
|
376
|
+
nonce: peer.myNonce
|
|
377
|
+
});
|
|
378
|
+
peer.confirmTimer = setTimeout(() => {
|
|
379
|
+
if (!peer.confirmed) closePeer(peerId);
|
|
380
|
+
}, CONFIRM_TIMEOUT_MS);
|
|
381
|
+
} catch {
|
|
382
|
+
closePeer(peerId);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
dc.onmessage = (e) => {
|
|
386
|
+
peer.recvChain = peer.recvChain.then(() => onFrame(peerId, dc, peer, e.data)).catch(() => {});
|
|
387
|
+
};
|
|
388
|
+
pc.createOffer().then((o) => pc.setLocalDescription(o)).then(() => ws.send(JSON.stringify({
|
|
389
|
+
type: "offer",
|
|
390
|
+
to: peerId,
|
|
391
|
+
sdp: pc.localDescription.sdp
|
|
392
|
+
})));
|
|
393
|
+
}
|
|
394
|
+
function closePeer(peerId) {
|
|
395
|
+
const p = peers.get(peerId);
|
|
396
|
+
if (!p) return;
|
|
397
|
+
if (p.confirmTimer) clearTimeout(p.confirmTimer);
|
|
398
|
+
for (const a of p.aborts.values()) a.abort();
|
|
399
|
+
try {
|
|
400
|
+
p.pc.close();
|
|
401
|
+
} catch {}
|
|
402
|
+
peers.delete(peerId);
|
|
403
|
+
}
|
|
404
|
+
function enqueueSeal(peerId, dc, peer, flags, obj) {
|
|
405
|
+
peer.sendChain = peer.sendChain.then(async () => {
|
|
406
|
+
if (dc.readyState !== "open" || !peer.keyH2C || !peer.th) return;
|
|
407
|
+
let frame;
|
|
408
|
+
try {
|
|
409
|
+
frame = await seal(peer.keyH2C, peer.send, flags, peer.th, packEnvelope(obj));
|
|
410
|
+
} catch {
|
|
411
|
+
closePeer(peerId);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
dc.send(frame);
|
|
416
|
+
} catch {}
|
|
417
|
+
});
|
|
418
|
+
return peer.sendChain;
|
|
419
|
+
}
|
|
420
|
+
async function onFrame(peerId, dc, peer, data) {
|
|
421
|
+
if (!peers.has(peerId)) return;
|
|
422
|
+
if (typeof data === "string" || !peer.keyC2H || !peer.th) return closePeer(peerId);
|
|
423
|
+
let env;
|
|
424
|
+
try {
|
|
425
|
+
const { plaintext } = await open$1(peer.keyC2H, data, peer.th, peer.recv);
|
|
426
|
+
env = unpackEnvelope(plaintext);
|
|
427
|
+
} catch {
|
|
428
|
+
return closePeer(peerId);
|
|
429
|
+
}
|
|
430
|
+
if (!peer.confirmed) {
|
|
431
|
+
if (!env || env.t !== "confirm") return closePeer(peerId);
|
|
432
|
+
if (typeof env.nonce === "string" && !peer.confirmedOut) {
|
|
433
|
+
await enqueueSeal(peerId, dc, peer, FLAG_CONFIRM, {
|
|
434
|
+
t: "confirm",
|
|
435
|
+
nonce: peer.myNonce,
|
|
436
|
+
echo: env.nonce
|
|
437
|
+
});
|
|
438
|
+
peer.confirmedOut = true;
|
|
439
|
+
}
|
|
440
|
+
if (env.echo && env.echo === peer.myNonce) peer.confirmedIn = true;
|
|
441
|
+
if (peer.confirmedIn && peer.confirmedOut) {
|
|
442
|
+
peer.confirmed = true;
|
|
443
|
+
if (peer.confirmTimer) clearTimeout(peer.confirmTimer);
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (!env || env.t === "confirm") return;
|
|
448
|
+
onReq(peerId, dc, peer, env);
|
|
449
|
+
}
|
|
450
|
+
async function onReq(peerId, dc, peer, req) {
|
|
451
|
+
if (req.t === "abort") {
|
|
452
|
+
peer.aborts.get(req.id)?.abort();
|
|
453
|
+
peer.aborts.delete(req.id);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (req.t !== "req") return;
|
|
457
|
+
const { id, method, path: p, body } = req;
|
|
458
|
+
const ac = new AbortController();
|
|
459
|
+
peer.aborts.set(id, ac);
|
|
460
|
+
try {
|
|
461
|
+
const res = await opts.localFetch(new Request(`http://ay.local${p}`, {
|
|
462
|
+
method,
|
|
463
|
+
headers: {
|
|
464
|
+
Authorization: `Bearer ${opts.apiToken}`,
|
|
465
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
466
|
+
},
|
|
467
|
+
body: body ?? void 0,
|
|
468
|
+
signal: ac.signal
|
|
469
|
+
}));
|
|
470
|
+
enqueueSeal(peerId, dc, peer, 0, {
|
|
471
|
+
t: "res",
|
|
472
|
+
id,
|
|
473
|
+
status: res.status,
|
|
474
|
+
ct: res.headers.get("content-type") ?? ""
|
|
475
|
+
});
|
|
476
|
+
const reader = res.body.getReader();
|
|
477
|
+
const dec = new TextDecoder();
|
|
478
|
+
let seq = 0;
|
|
479
|
+
for (;;) {
|
|
480
|
+
const { done, value } = await reader.read();
|
|
481
|
+
if (done) break;
|
|
482
|
+
const text = dec.decode(value, { stream: true });
|
|
483
|
+
for (let i = 0; i < text.length; i += MAX_CHUNK) enqueueSeal(peerId, dc, peer, 0, {
|
|
484
|
+
t: "data",
|
|
485
|
+
id,
|
|
486
|
+
seq: seq++,
|
|
487
|
+
chunk: text.slice(i, i + MAX_CHUNK)
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
enqueueSeal(peerId, dc, peer, 0, {
|
|
491
|
+
t: "end",
|
|
492
|
+
id,
|
|
493
|
+
seq
|
|
494
|
+
});
|
|
495
|
+
} catch (e) {
|
|
496
|
+
if (e.name !== "AbortError") enqueueSeal(peerId, dc, peer, 0, {
|
|
497
|
+
t: "end",
|
|
498
|
+
id,
|
|
499
|
+
error: String(e.message ?? e)
|
|
500
|
+
});
|
|
501
|
+
} finally {
|
|
502
|
+
peer.aborts.delete(id);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
await new Promise((resolve) => connectSignaling(resolve));
|
|
506
|
+
const close = () => {
|
|
507
|
+
closed = true;
|
|
508
|
+
try {
|
|
509
|
+
currentWs?.close();
|
|
510
|
+
} catch {}
|
|
511
|
+
for (const peerId of [...peers.keys()]) closePeer(peerId);
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
room,
|
|
515
|
+
link,
|
|
516
|
+
close
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
export { loadOrCreateShareRoom, startShare };
|
|
522
|
+
//# sourceMappingURL=share-B6QVr5D1.js.map
|
|
@@ -231,11 +231,11 @@ async function runSubcommand(argv) {
|
|
|
231
231
|
case "restart": return await cmdRestart(rest);
|
|
232
232
|
case "note": return await cmdNote(rest);
|
|
233
233
|
case "serve": {
|
|
234
|
-
const { cmdServe } = await import("./serve-
|
|
234
|
+
const { cmdServe } = await import("./serve-D2czcYNC.js");
|
|
235
235
|
return cmdServe(rest);
|
|
236
236
|
}
|
|
237
237
|
case "setup": {
|
|
238
|
-
const { cmdSetup } = await import("./setup-
|
|
238
|
+
const { cmdSetup } = await import("./setup-f1FIFcZm.js");
|
|
239
239
|
return cmdSetup(rest);
|
|
240
240
|
}
|
|
241
241
|
case "remote": {
|
|
@@ -1680,4 +1680,4 @@ async function cmdStatus(rest) {
|
|
|
1680
1680
|
|
|
1681
1681
|
//#endregion
|
|
1682
1682
|
export { finalizedLines as a, listRecords as c, renderRawLog as d, resolveOne as f, writeToIpc as g, stopTipForCli as h, cursorAbs as i, matchKeyword as l, snapshotStatus as m, cmdHelp as n, isPidAlive as o, runSubcommand as p, controlCodeFromName as r, isSubcommand as s, GRACEFUL_EXIT_COMMANDS as t, readNotes as u };
|
|
1683
|
-
//# sourceMappingURL=subcommands-
|
|
1683
|
+
//# sourceMappingURL=subcommands-CzpZQHO6.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
2
|
import "./globalPidIndex-gZuTvTBs.js";
|
|
3
3
|
import "./remotes-BufkGk0e.js";
|
|
4
|
-
import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-
|
|
4
|
+
import { a as finalizedLines, c as listRecords, d as renderRawLog, f as resolveOne, g as writeToIpc, h as stopTipForCli, i as cursorAbs, l as matchKeyword, m as snapshotStatus, n as cmdHelp, o as isPidAlive, p as runSubcommand, r as controlCodeFromName, s as isSubcommand, t as GRACEFUL_EXIT_COMMANDS, u as readNotes } from "./subcommands-CzpZQHO6.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|