agent-yes 1.94.2 → 1.96.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-B2FAlgXF.js +8 -0
- package/dist/SUPPORTED_CLIS-G8izHOJP.js +8 -0
- package/dist/cli.js +5 -5
- package/dist/index.js +2 -2
- package/dist/{remotes-Bjp2GYPz.js → remotes-C3xPRtfg.js} +1 -1
- package/dist/{remotes-oNI1fR7_.js → remotes-C9WMt5PY.js} +1 -1
- package/dist/{serve-CixOwENN.js → serve-CuAPBK4y.js} +117 -7
- package/dist/share-DUhUA1Pi.js +184 -0
- package/dist/{subcommands-D9wmaZ3U.js → subcommands-CcOYsLYD.js} +4 -4
- package/dist/{subcommands-DjrOWqD9.js → subcommands-EieqoC9Z.js} +2 -2
- package/dist/{tray-DHuD0nEk.js → tray-D4cJA4UH.js} +1 -1
- package/dist/{ts-DtwVuD8n.js → ts-DkjQJTcB.js} +2 -2
- package/dist/{versionChecker-BY5g27iW.js → versionChecker-xqnqyGKE.js} +2 -2
- package/package.json +6 -2
- package/scripts/cf.ts +51 -0
- package/ts/rustBinary.ts +10 -35
- package/ts/serve.ts +139 -8
- package/ts/share.ts +190 -0
- package/dist/SUPPORTED_CLIS-Bo4qbT_0.js +0 -12
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-DkjQJTcB.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-xqnqyGKE.js";
|
|
4
|
+
import "./pidStore-DTzl6zeh.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B2FAlgXF.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
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-xqnqyGKE.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-EieqoC9Z.js");
|
|
486
486
|
if (isHelpFlag && process.argv.length === 3) {
|
|
487
487
|
cmdHelp();
|
|
488
488
|
process.exit(0);
|
|
@@ -496,12 +496,12 @@ await checkAndAutoUpdate();
|
|
|
496
496
|
logger.info(versionString());
|
|
497
497
|
const config = parseCliArgs(process.argv);
|
|
498
498
|
if (config.tray) {
|
|
499
|
-
const { startTray } = await import("./tray-
|
|
499
|
+
const { startTray } = await import("./tray-D4cJA4UH.js");
|
|
500
500
|
await startTray();
|
|
501
501
|
await new Promise(() => {});
|
|
502
502
|
}
|
|
503
503
|
{
|
|
504
|
-
const { ensureTray } = await import("./tray-
|
|
504
|
+
const { ensureTray } = await import("./tray-D4cJA4UH.js");
|
|
505
505
|
ensureTray();
|
|
506
506
|
}
|
|
507
507
|
if (config.useRust) {
|
|
@@ -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-G8izHOJP.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-DkjQJTcB.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-xqnqyGKE.js";
|
|
4
4
|
import "./pidStore-DTzl6zeh.js";
|
|
5
5
|
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
6
|
|
|
@@ -147,4 +147,4 @@ async function cmdRemote(rest) {
|
|
|
147
147
|
|
|
148
148
|
//#endregion
|
|
149
149
|
export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
|
|
150
|
-
//# sourceMappingURL=remotes-
|
|
150
|
+
//# sourceMappingURL=remotes-C3xPRtfg.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-
|
|
1
|
+
import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-C3xPRtfg.js";
|
|
2
2
|
|
|
3
3
|
export { cmdRemote };
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import "./ts-DkjQJTcB.js";
|
|
1
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-xqnqyGKE.js";
|
|
4
|
+
import "./pidStore-DTzl6zeh.js";
|
|
2
5
|
import "./globalPidIndex-yVd3mbsV.js";
|
|
3
|
-
import "./
|
|
4
|
-
import
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B2FAlgXF.js";
|
|
7
|
+
import "./remotes-C3xPRtfg.js";
|
|
8
|
+
import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-CcOYsLYD.js";
|
|
5
9
|
import yargs from "yargs";
|
|
6
10
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
7
11
|
import { homedir } from "os";
|
|
@@ -107,7 +111,7 @@ async function cmdServe(rest) {
|
|
|
107
111
|
Start an HTTP API server so remote machines can list/tail/send agents.
|
|
108
112
|
|
|
109
113
|
Options:
|
|
110
|
-
--port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
|
|
114
|
+
--port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n --share [URL] Share over WebRTC to agent-yes.com (bare flag mints a room+link)\n --allow-spawn Let the shared console launch new agents (asks y/N per request)\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
|
|
111
115
|
return 0;
|
|
112
116
|
}
|
|
113
117
|
const sub = rest[0];
|
|
@@ -129,6 +133,13 @@ Options:
|
|
|
129
133
|
}).option("tls-key", {
|
|
130
134
|
type: "string",
|
|
131
135
|
description: "TLS private key file (PEM)"
|
|
136
|
+
}).option("share", {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "Share over WebRTC: bare flag mints a room+link, or pass webrtc://room:token@host"
|
|
139
|
+
}).option("allow-spawn", {
|
|
140
|
+
type: "boolean",
|
|
141
|
+
default: false,
|
|
142
|
+
description: "Allow the shared console to spawn new agents (asks y/N per request on a TTY)"
|
|
132
143
|
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
133
144
|
const port = argv.port ?? DEFAULT_PORT;
|
|
134
145
|
const host = argv.host ?? "127.0.0.1";
|
|
@@ -143,6 +154,20 @@ Options:
|
|
|
143
154
|
const scheme = useHttps ? "https" : "http";
|
|
144
155
|
if (host !== "127.0.0.1" && host !== "localhost") process.stderr.write("ay serve: warning: binding to non-loopback — ensure your network is trusted or use Tailscale/VPN\n");
|
|
145
156
|
const token = await loadOrCreateToken(tokenFlag);
|
|
157
|
+
const allowSpawn = argv["allow-spawn"] === true;
|
|
158
|
+
const spawnQueue = [];
|
|
159
|
+
let stdinWired = false;
|
|
160
|
+
const confirmSpawn = (cli, cwd, prompt) => {
|
|
161
|
+
if (!process.stdin.isTTY) return Promise.resolve(true);
|
|
162
|
+
if (!stdinWired) {
|
|
163
|
+
stdinWired = true;
|
|
164
|
+
process.stdin.setEncoding("utf8");
|
|
165
|
+
process.stdin.on("data", (d) => spawnQueue.shift()?.(/^y/i.test(d.trim())));
|
|
166
|
+
process.stdin.resume();
|
|
167
|
+
}
|
|
168
|
+
process.stdout.write(`\n⚠ console requests spawn: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""}\n cwd: ${cwd}\n allow? [y/N] `);
|
|
169
|
+
return new Promise((res) => spawnQueue.push(res));
|
|
170
|
+
};
|
|
146
171
|
const serverOpts = {
|
|
147
172
|
hostname: host,
|
|
148
173
|
port,
|
|
@@ -197,6 +222,7 @@ Options:
|
|
|
197
222
|
const tailM = /^\/api\/tail\/(.+)$/.exec(p);
|
|
198
223
|
if (req.method === "GET" && tailM) {
|
|
199
224
|
const keyword = decodeURIComponent(tailM[1]);
|
|
225
|
+
const raw = url.searchParams.get("raw") === "1";
|
|
200
226
|
try {
|
|
201
227
|
const record = await resolveOne(keyword, defaultOpts());
|
|
202
228
|
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
@@ -206,7 +232,8 @@ Options:
|
|
|
206
232
|
const send = (text) => ctrl.enqueue(enc.encode(`data: ${JSON.stringify(text)}\n\n`));
|
|
207
233
|
const ping = () => ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
208
234
|
const initBuf = await readFile(logPath).catch(() => Buffer.alloc(0));
|
|
209
|
-
send(
|
|
235
|
+
if (raw) send(new TextDecoder().decode(initBuf.slice(Math.max(0, initBuf.length - 65536))));
|
|
236
|
+
else send(await renderRawLog(initBuf, {
|
|
210
237
|
mode: "tail",
|
|
211
238
|
n: 96
|
|
212
239
|
}));
|
|
@@ -231,8 +258,11 @@ Options:
|
|
|
231
258
|
if (full.length <= offset) return;
|
|
232
259
|
const chunk = full.slice(offset);
|
|
233
260
|
offset = full.length;
|
|
234
|
-
|
|
235
|
-
|
|
261
|
+
if (raw) send(new TextDecoder().decode(chunk));
|
|
262
|
+
else {
|
|
263
|
+
const text = new TextDecoder().decode(chunk).replace(ansiRe, "").replace(ctrlRe, "");
|
|
264
|
+
if (text.trim()) send(text.trimStart());
|
|
265
|
+
}
|
|
236
266
|
} catch {}
|
|
237
267
|
}, 300);
|
|
238
268
|
req.signal.addEventListener("abort", () => {
|
|
@@ -279,6 +309,72 @@ Options:
|
|
|
279
309
|
return new Response(e.message, { status: 404 });
|
|
280
310
|
}
|
|
281
311
|
}
|
|
312
|
+
const resizeM = /^\/api\/resize\/(.+)$/.exec(p);
|
|
313
|
+
if (req.method === "POST" && resizeM) {
|
|
314
|
+
const keyword = decodeURIComponent(resizeM[1]);
|
|
315
|
+
let body;
|
|
316
|
+
try {
|
|
317
|
+
body = await req.json();
|
|
318
|
+
} catch {
|
|
319
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
320
|
+
}
|
|
321
|
+
const cols = Math.max(1, Math.floor(Number(body.cols) || 0));
|
|
322
|
+
const rows = Math.max(1, Math.floor(Number(body.rows) || 0));
|
|
323
|
+
if (!cols || !rows) return new Response("missing cols/rows", { status: 400 });
|
|
324
|
+
try {
|
|
325
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
326
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
327
|
+
const winsizeDir = path.join(ayHome, "winsize");
|
|
328
|
+
await mkdir(winsizeDir, { recursive: true });
|
|
329
|
+
await writeFile(path.join(winsizeDir, String(record.pid)), `${cols} ${rows} ${Date.now()}\n`);
|
|
330
|
+
try {
|
|
331
|
+
process.kill(record.pid, "SIGWINCH");
|
|
332
|
+
} catch {}
|
|
333
|
+
return Response.json({
|
|
334
|
+
ok: true,
|
|
335
|
+
pid: record.pid,
|
|
336
|
+
cols,
|
|
337
|
+
rows
|
|
338
|
+
});
|
|
339
|
+
} catch (e) {
|
|
340
|
+
return new Response(e.message, { status: 404 });
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (req.method === "POST" && p === "/api/spawn") {
|
|
344
|
+
if (!allowSpawn) return new Response("spawning disabled — start: ay serve --share --allow-spawn", { status: 403 });
|
|
345
|
+
let body;
|
|
346
|
+
try {
|
|
347
|
+
body = await req.json();
|
|
348
|
+
} catch {
|
|
349
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
350
|
+
}
|
|
351
|
+
const cli = String(body.cli ?? "claude");
|
|
352
|
+
if (!SUPPORTED_CLIS.includes(cli)) return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
353
|
+
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
354
|
+
const prompt = String(body.prompt ?? "");
|
|
355
|
+
if (!await confirmSpawn(cli, cwd, prompt)) return new Response("denied by host", { status: 403 });
|
|
356
|
+
try {
|
|
357
|
+
const child = Bun.spawn([
|
|
358
|
+
"ay",
|
|
359
|
+
cli,
|
|
360
|
+
...prompt ? ["--", prompt] : []
|
|
361
|
+
], {
|
|
362
|
+
cwd,
|
|
363
|
+
stdin: "ignore",
|
|
364
|
+
stdout: "ignore",
|
|
365
|
+
stderr: "ignore"
|
|
366
|
+
});
|
|
367
|
+
child.unref();
|
|
368
|
+
return Response.json({
|
|
369
|
+
ok: true,
|
|
370
|
+
pid: child.pid,
|
|
371
|
+
cli,
|
|
372
|
+
cwd
|
|
373
|
+
});
|
|
374
|
+
} catch (e) {
|
|
375
|
+
return new Response(e.message, { status: 500 });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
282
378
|
return new Response("Not Found", { status: 404 });
|
|
283
379
|
}
|
|
284
380
|
};
|
|
@@ -296,6 +392,20 @@ Options:
|
|
|
296
392
|
process.stdout.write(`save as alias:\n`);
|
|
297
393
|
process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
|
|
298
394
|
if (!useHttps) process.stdout.write("for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'\n\n");
|
|
395
|
+
if (argv.share !== void 0) {
|
|
396
|
+
const shareUrl = typeof argv.share === "string" && argv.share.startsWith("webrtc://") ? argv.share : void 0;
|
|
397
|
+
try {
|
|
398
|
+
const { startShare } = await import("./share-DUhUA1Pi.js");
|
|
399
|
+
const { link } = await startShare({
|
|
400
|
+
url: shareUrl,
|
|
401
|
+
apiUrl: `http://127.0.0.1:${port}`,
|
|
402
|
+
apiToken: token
|
|
403
|
+
});
|
|
404
|
+
process.stdout.write(`\nshared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n\n`);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
process.stderr.write(`ay serve --share failed: ${e.message}\n`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
299
409
|
process.stdout.write(`(Ctrl-C to stop)\n`);
|
|
300
410
|
await new Promise((resolve) => {
|
|
301
411
|
process.on("SIGINT", () => {
|
|
@@ -312,4 +422,4 @@ Options:
|
|
|
312
422
|
|
|
313
423
|
//#endregion
|
|
314
424
|
export { cmdServe };
|
|
315
|
-
//# sourceMappingURL=serve-
|
|
425
|
+
//# sourceMappingURL=serve-CuAPBK4y.js.map
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
|
|
3
|
+
//#region ts/share.ts
|
|
4
|
+
const SUB = "ay-signal-1";
|
|
5
|
+
const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
6
|
+
const MAX_CHUNK = 15e3;
|
|
7
|
+
const DEFAULT_SIGHOST = "s.agent-yes.com";
|
|
8
|
+
function parseShareUrl(s) {
|
|
9
|
+
const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
|
|
10
|
+
if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
|
|
11
|
+
return {
|
|
12
|
+
room: m[1],
|
|
13
|
+
token: m[2],
|
|
14
|
+
host: m[3]
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async function importRTC() {
|
|
18
|
+
try {
|
|
19
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
20
|
+
} catch {
|
|
21
|
+
try {
|
|
22
|
+
const { existsSync, symlinkSync, mkdirSync, readdirSync } = await import("fs");
|
|
23
|
+
const path = (await import("path")).default;
|
|
24
|
+
const { createRequire } = await import("module");
|
|
25
|
+
const require = createRequire(import.meta.url);
|
|
26
|
+
const pkg = path.dirname(require.resolve("node-datachannel/package.json"));
|
|
27
|
+
const bin = path.join(pkg, "build", "Release", "node_datachannel.node");
|
|
28
|
+
const cacheRoot = path.join((await import("os")).homedir(), ".bun", "install", "cache");
|
|
29
|
+
if (existsSync(bin) && existsSync(cacheRoot)) for (const d of readdirSync(cacheRoot)) {
|
|
30
|
+
if (!d.startsWith("node-datachannel@")) continue;
|
|
31
|
+
const dst = path.join(cacheRoot, d, "build", "Release");
|
|
32
|
+
mkdirSync(dst, { recursive: true });
|
|
33
|
+
const link = path.join(dst, "node_datachannel.node");
|
|
34
|
+
if (!existsSync(link)) symlinkSync(bin, link);
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Start the share bridge. Resolves once signaling is connected; runs until the
|
|
41
|
+
* process exits, reconnecting signaling on drop. Returns the shareable link. */
|
|
42
|
+
async function startShare(opts) {
|
|
43
|
+
opts.url;
|
|
44
|
+
const sighost = opts.sighost ?? DEFAULT_SIGHOST;
|
|
45
|
+
const { room, token, host } = opts.url ? parseShareUrl(opts.url) : {
|
|
46
|
+
room: "r" + randomBytes(3).toString("hex"),
|
|
47
|
+
token: randomBytes(32).toString("hex"),
|
|
48
|
+
host: sighost
|
|
49
|
+
};
|
|
50
|
+
const RTCPeerConnection = await importRTC();
|
|
51
|
+
const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
|
|
52
|
+
const link = `${host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778"}/#${room}:${token}${host === "s.agent-yes.com" ? "" : "@" + host}`;
|
|
53
|
+
const peers = /* @__PURE__ */ new Map();
|
|
54
|
+
const connectSignaling = (onReady) => {
|
|
55
|
+
const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
|
|
56
|
+
let ready = false;
|
|
57
|
+
ws.onopen = () => {
|
|
58
|
+
ws.send(JSON.stringify({
|
|
59
|
+
type: "hello",
|
|
60
|
+
role: "host",
|
|
61
|
+
token
|
|
62
|
+
}));
|
|
63
|
+
ready = true;
|
|
64
|
+
onReady();
|
|
65
|
+
};
|
|
66
|
+
ws.onmessage = async (ev) => {
|
|
67
|
+
const m = JSON.parse(ev.data);
|
|
68
|
+
if (m.type === "peer-join") startPeer(ws, m.peer);
|
|
69
|
+
else if (m.type === "answer") await peers.get(m.from)?.pc.setRemoteDescription({
|
|
70
|
+
type: "answer",
|
|
71
|
+
sdp: m.sdp
|
|
72
|
+
});
|
|
73
|
+
else if (m.type === "candidate") await peers.get(m.from)?.pc.addIceCandidate(m.candidate).catch(() => {});
|
|
74
|
+
else if (m.type === "peer-leave") closePeer(m.peer);
|
|
75
|
+
};
|
|
76
|
+
ws.onclose = () => {
|
|
77
|
+
setTimeout(() => connectSignaling(() => {}), ready ? 1500 : 4e3);
|
|
78
|
+
};
|
|
79
|
+
ws.onerror = () => {};
|
|
80
|
+
return ws;
|
|
81
|
+
};
|
|
82
|
+
function startPeer(ws, peerId) {
|
|
83
|
+
const pc = new RTCPeerConnection({ iceServers: ICE });
|
|
84
|
+
const aborts = /* @__PURE__ */ new Map();
|
|
85
|
+
peers.set(peerId, {
|
|
86
|
+
pc,
|
|
87
|
+
aborts
|
|
88
|
+
});
|
|
89
|
+
pc.onicecandidate = (e) => {
|
|
90
|
+
if (e.candidate) ws.send(JSON.stringify({
|
|
91
|
+
type: "candidate",
|
|
92
|
+
to: peerId,
|
|
93
|
+
candidate: e.candidate
|
|
94
|
+
}));
|
|
95
|
+
};
|
|
96
|
+
pc.onconnectionstatechange = () => {
|
|
97
|
+
if ([
|
|
98
|
+
"failed",
|
|
99
|
+
"closed",
|
|
100
|
+
"disconnected"
|
|
101
|
+
].includes(pc.connectionState)) closePeer(peerId);
|
|
102
|
+
};
|
|
103
|
+
const dc = pc.createDataChannel("api");
|
|
104
|
+
dc.onmessage = (e) => onReq(dc, aborts, JSON.parse(e.data));
|
|
105
|
+
pc.createOffer().then((o) => pc.setLocalDescription(o)).then(() => ws.send(JSON.stringify({
|
|
106
|
+
type: "offer",
|
|
107
|
+
to: peerId,
|
|
108
|
+
sdp: pc.localDescription.sdp
|
|
109
|
+
})));
|
|
110
|
+
}
|
|
111
|
+
function closePeer(peerId) {
|
|
112
|
+
const p = peers.get(peerId);
|
|
113
|
+
if (!p) return;
|
|
114
|
+
for (const a of p.aborts.values()) a.abort();
|
|
115
|
+
try {
|
|
116
|
+
p.pc.close();
|
|
117
|
+
} catch {}
|
|
118
|
+
peers.delete(peerId);
|
|
119
|
+
}
|
|
120
|
+
function send(dc, obj) {
|
|
121
|
+
if (dc.readyState === "open") dc.send(JSON.stringify(obj));
|
|
122
|
+
}
|
|
123
|
+
async function onReq(dc, aborts, req) {
|
|
124
|
+
if (req.t === "abort") {
|
|
125
|
+
aborts.get(req.id)?.abort();
|
|
126
|
+
aborts.delete(req.id);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (req.t !== "req") return;
|
|
130
|
+
const { id, method, path: p, body } = req;
|
|
131
|
+
const ac = new AbortController();
|
|
132
|
+
aborts.set(id, ac);
|
|
133
|
+
try {
|
|
134
|
+
const res = await fetch(opts.apiUrl + p, {
|
|
135
|
+
method,
|
|
136
|
+
headers: {
|
|
137
|
+
Authorization: `Bearer ${opts.apiToken}`,
|
|
138
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
139
|
+
},
|
|
140
|
+
body: body ?? void 0,
|
|
141
|
+
signal: ac.signal
|
|
142
|
+
});
|
|
143
|
+
send(dc, {
|
|
144
|
+
t: "res",
|
|
145
|
+
id,
|
|
146
|
+
status: res.status,
|
|
147
|
+
ct: res.headers.get("content-type") ?? ""
|
|
148
|
+
});
|
|
149
|
+
const reader = res.body.getReader();
|
|
150
|
+
const dec = new TextDecoder();
|
|
151
|
+
for (;;) {
|
|
152
|
+
const { done, value } = await reader.read();
|
|
153
|
+
if (done) break;
|
|
154
|
+
const text = dec.decode(value, { stream: true });
|
|
155
|
+
for (let i = 0; i < text.length; i += MAX_CHUNK) send(dc, {
|
|
156
|
+
t: "data",
|
|
157
|
+
id,
|
|
158
|
+
chunk: text.slice(i, i + MAX_CHUNK)
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
send(dc, {
|
|
162
|
+
t: "end",
|
|
163
|
+
id
|
|
164
|
+
});
|
|
165
|
+
} catch (e) {
|
|
166
|
+
if (e.name !== "AbortError") send(dc, {
|
|
167
|
+
t: "end",
|
|
168
|
+
id,
|
|
169
|
+
error: String(e)
|
|
170
|
+
});
|
|
171
|
+
} finally {
|
|
172
|
+
aborts.delete(id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
await new Promise((resolve) => connectSignaling(resolve));
|
|
176
|
+
return {
|
|
177
|
+
room,
|
|
178
|
+
link
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
export { startShare };
|
|
184
|
+
//# sourceMappingURL=share-DUhUA1Pi.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
|
|
2
|
-
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-
|
|
2
|
+
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-C3xPRtfg.js";
|
|
3
3
|
import ms from "ms";
|
|
4
4
|
import yargs from "yargs";
|
|
5
5
|
import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
@@ -162,11 +162,11 @@ async function runSubcommand(argv) {
|
|
|
162
162
|
case "restart": return await cmdRestart(rest);
|
|
163
163
|
case "note": return await cmdNote(rest);
|
|
164
164
|
case "serve": {
|
|
165
|
-
const { cmdServe } = await import("./serve-
|
|
165
|
+
const { cmdServe } = await import("./serve-CuAPBK4y.js");
|
|
166
166
|
return cmdServe(rest);
|
|
167
167
|
}
|
|
168
168
|
case "remote": {
|
|
169
|
-
const { cmdRemote } = await import("./remotes-
|
|
169
|
+
const { cmdRemote } = await import("./remotes-C9WMt5PY.js");
|
|
170
170
|
return cmdRemote(rest);
|
|
171
171
|
}
|
|
172
172
|
case "help": return cmdHelp();
|
|
@@ -1447,4 +1447,4 @@ async function cmdStatus(rest) {
|
|
|
1447
1447
|
|
|
1448
1448
|
//#endregion
|
|
1449
1449
|
export { isSubcommand as a, readNotes as c, runSubcommand as d, snapshotStatus as f, isPidAlive as i, renderRawLog as l, writeToIpc as m, cmdHelp as n, listRecords as o, stopTipForCli as p, controlCodeFromName as r, matchKeyword as s, GRACEFUL_EXIT_COMMANDS as t, resolveOne as u };
|
|
1450
|
-
//# sourceMappingURL=subcommands-
|
|
1450
|
+
//# sourceMappingURL=subcommands-CcOYsLYD.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
2
|
import "./globalPidIndex-yVd3mbsV.js";
|
|
3
|
-
import "./remotes-
|
|
4
|
-
import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-
|
|
3
|
+
import "./remotes-C3xPRtfg.js";
|
|
4
|
+
import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-CcOYsLYD.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-xqnqyGKE.js";
|
|
3
3
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-C22d9SRJ.js";
|
|
4
4
|
import { t as PidStore } from "./pidStore-DTzl6zeh.js";
|
|
5
5
|
import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
|
|
@@ -1705,4 +1705,4 @@ function sleep(ms) {
|
|
|
1705
1705
|
|
|
1706
1706
|
//#endregion
|
|
1707
1707
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1708
|
-
//# sourceMappingURL=ts-
|
|
1708
|
+
//# sourceMappingURL=ts-DkjQJTcB.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "agent-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.96.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -221,4 +221,4 @@ async function displayVersion() {
|
|
|
221
221
|
|
|
222
222
|
//#endregion
|
|
223
223
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
224
|
-
//# sourceMappingURL=versionChecker-
|
|
224
|
+
//# sourceMappingURL=versionChecker-xqnqyGKE.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.0",
|
|
4
4
|
"description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "tsdown",
|
|
72
|
+
"cf": "bun scripts/cf.ts",
|
|
72
73
|
"build:rs": "cargo install --path rs --features swarm",
|
|
73
74
|
"postbuild": "bun ./ts/postbuild.ts",
|
|
74
75
|
"demo": "bun run build && bun link && claude-yes -- demo",
|
|
@@ -92,6 +93,7 @@
|
|
|
92
93
|
"execa": "^9.6.1",
|
|
93
94
|
"from-node-stream": "^0.2.0",
|
|
94
95
|
"ms": "^2.1.3",
|
|
96
|
+
"node-datachannel": "^0.32.3",
|
|
95
97
|
"phpdie": "^1.7.0",
|
|
96
98
|
"proper-lockfile": "^4.1.2",
|
|
97
99
|
"sflow": "^1.27.0",
|
|
@@ -142,5 +144,7 @@
|
|
|
142
144
|
"engines": {
|
|
143
145
|
"node": ">=22.0.0"
|
|
144
146
|
},
|
|
145
|
-
"trustedDependencies": [
|
|
147
|
+
"trustedDependencies": [
|
|
148
|
+
"node-datachannel"
|
|
149
|
+
]
|
|
146
150
|
}
|
package/scripts/cf.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Thin wrapper that runs `wrangler` against the SNOLAB Cloudflare account using
|
|
3
|
+
// the API token saved in .env.local — so we never depend on `wrangler login`
|
|
4
|
+
// state (which points at a different account) and never pass the token on the
|
|
5
|
+
// CLI. Usage: bun scripts/cf.ts <wrangler args...>
|
|
6
|
+
// e.g. bun scripts/cf.ts whoami
|
|
7
|
+
// bun scripts/cf.ts pages deploy ./dist --project-name agent-yes
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { existsSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
// SNOLAB account — agent-yes.com lives here. Account id is not a secret.
|
|
14
|
+
const SNOLAB_ACCOUNT_ID = "0beef4cd2d2da6befa47d8d149d6e157";
|
|
15
|
+
|
|
16
|
+
const root = path.join(import.meta.dir, "..");
|
|
17
|
+
const env: Record<string, string | undefined> = { ...process.env };
|
|
18
|
+
|
|
19
|
+
// Load .env.local (bun also auto-loads it, but be explicit so this works from
|
|
20
|
+
// any cwd and is obvious to a reader).
|
|
21
|
+
try {
|
|
22
|
+
for (const line of readFileSync(path.join(root, ".env.local"), "utf8").split("\n")) {
|
|
23
|
+
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
|
|
24
|
+
if (m && !(m[1] in env)) env[m[1]] = m[2];
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
/* no .env.local — fall through to the check below */
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!env.CLOUDFLARE_API_TOKEN) {
|
|
31
|
+
console.error("CLOUDFLARE_API_TOKEN is missing — add it to .env.local (see scripts/cf.ts).");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
env.CLOUDFLARE_ACCOUNT_ID = SNOLAB_ACCOUNT_ID;
|
|
35
|
+
|
|
36
|
+
// wrangler otherwise prefers a stored OAuth login over CLOUDFLARE_API_TOKEN and
|
|
37
|
+
// pins the OAuth account (Axon), ignoring CLOUDFLARE_ACCOUNT_ID. Two sources to
|
|
38
|
+
// neutralise: the global OAuth config (~/.wrangler) and a project-level account
|
|
39
|
+
// cache (.wrangler/wrangler-account.json) that pins whatever account first
|
|
40
|
+
// deployed. Drop the cache, move the OAuth config aside for the run, restore it.
|
|
41
|
+
rmSync(path.join(root, ".wrangler/wrangler-account.json"), { force: true });
|
|
42
|
+
const oauthCfg = path.join(homedir(), ".wrangler/config/default.toml");
|
|
43
|
+
const oauthBak = oauthCfg + ".cf-bak";
|
|
44
|
+
const hadOauth = existsSync(oauthCfg);
|
|
45
|
+
if (hadOauth) renameSync(oauthCfg, oauthBak);
|
|
46
|
+
try {
|
|
47
|
+
const r = spawnSync("bunx", ["wrangler", ...process.argv.slice(2)], { stdio: "inherit", env });
|
|
48
|
+
process.exitCode = r.status ?? 1;
|
|
49
|
+
} finally {
|
|
50
|
+
if (hadOauth && existsSync(oauthBak)) renameSync(oauthBak, oauthCfg);
|
|
51
|
+
}
|
package/ts/rustBinary.ts
CHANGED
|
@@ -41,10 +41,7 @@ export function getBinaryName(): string {
|
|
|
41
41
|
*/
|
|
42
42
|
export function getBinDir(): string {
|
|
43
43
|
// First check for binaries in the npm package
|
|
44
|
-
const packageBinDir = path.resolve(
|
|
45
|
-
import.meta.dirname ?? import.meta.dir,
|
|
46
|
-
"../bin",
|
|
47
|
-
);
|
|
44
|
+
const packageBinDir = path.resolve(import.meta.dirname ?? import.meta.dir, "../bin");
|
|
48
45
|
if (existsSync(packageBinDir)) {
|
|
49
46
|
return packageBinDir;
|
|
50
47
|
}
|
|
@@ -61,8 +58,7 @@ export function getBinDir(): string {
|
|
|
61
58
|
const cacheDir =
|
|
62
59
|
process.env.AGENT_YES_CACHE_DIR ||
|
|
63
60
|
path.join(
|
|
64
|
-
process.env.XDG_CACHE_HOME ||
|
|
65
|
-
path.join(process.env.HOME || "/tmp", ".cache"),
|
|
61
|
+
process.env.XDG_CACHE_HOME || path.join(process.env.HOME || "/tmp", ".cache"),
|
|
66
62
|
"agent-yes",
|
|
67
63
|
);
|
|
68
64
|
|
|
@@ -78,14 +74,8 @@ export function findRustBinary(verbose = false): string | undefined {
|
|
|
78
74
|
const ext = process.platform === "win32" ? ".exe" : "";
|
|
79
75
|
const searchPaths = [
|
|
80
76
|
// 1. Check relative to this script (in the repo during development)
|
|
81
|
-
path.resolve(
|
|
82
|
-
|
|
83
|
-
`../rs/target/release/agent-yes${ext}`,
|
|
84
|
-
),
|
|
85
|
-
path.resolve(
|
|
86
|
-
import.meta.dirname ?? import.meta.dir,
|
|
87
|
-
`../rs/target/debug/agent-yes${ext}`,
|
|
88
|
-
),
|
|
77
|
+
path.resolve(import.meta.dirname ?? import.meta.dir, `../rs/target/release/agent-yes${ext}`),
|
|
78
|
+
path.resolve(import.meta.dirname ?? import.meta.dir, `../rs/target/debug/agent-yes${ext}`),
|
|
89
79
|
|
|
90
80
|
// 2. Check in npm package bin directory
|
|
91
81
|
path.join(getBinDir(), binaryName),
|
|
@@ -149,9 +139,7 @@ export async function downloadBinary(verbose = false): Promise<string> {
|
|
|
149
139
|
|
|
150
140
|
const response = await fetch(url);
|
|
151
141
|
if (!response.ok) {
|
|
152
|
-
throw new Error(
|
|
153
|
-
`Failed to download binary: ${response.status} ${response.statusText}`,
|
|
154
|
-
);
|
|
142
|
+
throw new Error(`Failed to download binary: ${response.status} ${response.statusText}`);
|
|
155
143
|
}
|
|
156
144
|
|
|
157
145
|
const isWindows = process.platform === "win32";
|
|
@@ -243,19 +231,14 @@ function getRustBinaryVersion(binaryPath: string): string | null {
|
|
|
243
231
|
*/
|
|
244
232
|
function autoRebuildIfOutdated(binaryPath: string, verbose: boolean): boolean {
|
|
245
233
|
// Only auto-rebuild for local dev builds (target/release or target/debug)
|
|
246
|
-
if (
|
|
247
|
-
!binaryPath.includes("/target/release") &&
|
|
248
|
-
!binaryPath.includes("/target/debug")
|
|
249
|
-
) {
|
|
234
|
+
if (!binaryPath.includes("/target/release") && !binaryPath.includes("/target/debug")) {
|
|
250
235
|
return true; // not a dev build, skip
|
|
251
236
|
}
|
|
252
237
|
|
|
253
238
|
const binaryVersion = getRustBinaryVersion(binaryPath);
|
|
254
239
|
const pkgVersion = getInstalledPackage().version;
|
|
255
240
|
if (verbose) {
|
|
256
|
-
console.log(
|
|
257
|
-
`[rust] Binary version: ${binaryVersion}, package version: ${pkgVersion}`,
|
|
258
|
-
);
|
|
241
|
+
console.log(`[rust] Binary version: ${binaryVersion}, package version: ${pkgVersion}`);
|
|
259
242
|
}
|
|
260
243
|
|
|
261
244
|
if (binaryVersion === pkgVersion) {
|
|
@@ -263,15 +246,9 @@ function autoRebuildIfOutdated(binaryPath: string, verbose: boolean): boolean {
|
|
|
263
246
|
}
|
|
264
247
|
|
|
265
248
|
// Find the rs/ directory relative to the binary (binary is at rs/target/release/agent-yes)
|
|
266
|
-
const rsDir = binaryPath.replace(
|
|
267
|
-
/\/target\/(release|debug)\/agent-yes.*$/,
|
|
268
|
-
"",
|
|
269
|
-
);
|
|
249
|
+
const rsDir = binaryPath.replace(/\/target\/(release|debug)\/agent-yes.*$/, "");
|
|
270
250
|
if (!existsSync(path.join(rsDir, "Cargo.toml"))) {
|
|
271
|
-
if (verbose)
|
|
272
|
-
console.log(
|
|
273
|
-
`[rust] Cannot find Cargo.toml at ${rsDir}, skipping rebuild`,
|
|
274
|
-
);
|
|
251
|
+
if (verbose) console.log(`[rust] Cannot find Cargo.toml at ${rsDir}, skipping rebuild`);
|
|
275
252
|
return true; // can't rebuild, use as-is
|
|
276
253
|
}
|
|
277
254
|
|
|
@@ -299,9 +276,7 @@ function autoRebuildIfOutdated(binaryPath: string, verbose: boolean): boolean {
|
|
|
299
276
|
process.stderr.write(`\x1b[32m[rust] Rebuild complete\x1b[0m\n`);
|
|
300
277
|
return true;
|
|
301
278
|
} catch {
|
|
302
|
-
process.stderr.write(
|
|
303
|
-
`\x1b[31m[rust] Auto-rebuild failed, using outdated binary\x1b[0m\n`,
|
|
304
|
-
);
|
|
279
|
+
process.stderr.write(`\x1b[31m[rust] Auto-rebuild failed, using outdated binary\x1b[0m\n`);
|
|
305
280
|
return true; // still usable, just old
|
|
306
281
|
}
|
|
307
282
|
}
|
package/ts/serve.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
writeToIpc,
|
|
14
14
|
type CommonOpts,
|
|
15
15
|
} from "./subcommands.ts";
|
|
16
|
+
import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_PORT = 7432;
|
|
18
19
|
|
|
@@ -123,6 +124,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
123
124
|
` --port N Port to listen on (default: ${DEFAULT_PORT})\n` +
|
|
124
125
|
` --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n` +
|
|
125
126
|
` --token TOKEN Auth token (auto-generated and saved if omitted)\n` +
|
|
127
|
+
` --share [URL] Share over WebRTC to agent-yes.com (bare flag mints a room+link)\n` +
|
|
128
|
+
` --allow-spawn Let the shared console launch new agents (asks y/N per request)\n` +
|
|
126
129
|
` --tls-cert FILE TLS certificate PEM\n` +
|
|
127
130
|
` --tls-key FILE TLS private key PEM\n\n` +
|
|
128
131
|
`Subcommands:\n` +
|
|
@@ -153,6 +156,16 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
153
156
|
.option("token", { type: "string", description: "Auth token (auto-generated if omitted)" })
|
|
154
157
|
.option("tls-cert", { type: "string", description: "TLS certificate file (PEM)" })
|
|
155
158
|
.option("tls-key", { type: "string", description: "TLS private key file (PEM)" })
|
|
159
|
+
.option("share", {
|
|
160
|
+
type: "string",
|
|
161
|
+
description:
|
|
162
|
+
"Share over WebRTC: bare flag mints a room+link, or pass webrtc://room:token@host",
|
|
163
|
+
})
|
|
164
|
+
.option("allow-spawn", {
|
|
165
|
+
type: "boolean",
|
|
166
|
+
default: false,
|
|
167
|
+
description: "Allow the shared console to spawn new agents (asks y/N per request on a TTY)",
|
|
168
|
+
})
|
|
156
169
|
.help(false)
|
|
157
170
|
.version(false)
|
|
158
171
|
.exitProcess(false);
|
|
@@ -178,6 +191,26 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
178
191
|
}
|
|
179
192
|
|
|
180
193
|
const token = await loadOrCreateToken(tokenFlag);
|
|
194
|
+
const allowSpawn = argv["allow-spawn"] === true;
|
|
195
|
+
|
|
196
|
+
// Spawn confirmation: launch requests are gated by --allow-spawn AND, on a TTY,
|
|
197
|
+
// an interactive y/N per request (a leaked launch link can't silently spawn).
|
|
198
|
+
const spawnQueue: Array<(ok: boolean) => void> = [];
|
|
199
|
+
let stdinWired = false;
|
|
200
|
+
const confirmSpawn = (cli: string, cwd: string, prompt: string): Promise<boolean> => {
|
|
201
|
+
if (!process.stdin.isTTY) return Promise.resolve(true); // flag is the consent when headless
|
|
202
|
+
if (!stdinWired) {
|
|
203
|
+
stdinWired = true;
|
|
204
|
+
process.stdin.setEncoding("utf8");
|
|
205
|
+
process.stdin.on("data", (d: string) => spawnQueue.shift()?.(/^y/i.test(d.trim())));
|
|
206
|
+
process.stdin.resume();
|
|
207
|
+
}
|
|
208
|
+
process.stdout.write(
|
|
209
|
+
`\n⚠ console requests spawn: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""}\n` +
|
|
210
|
+
` cwd: ${cwd}\n allow? [y/N] `,
|
|
211
|
+
);
|
|
212
|
+
return new Promise((res) => spawnQueue.push(res));
|
|
213
|
+
};
|
|
181
214
|
|
|
182
215
|
const serverOpts: any = {
|
|
183
216
|
hostname: host,
|
|
@@ -246,6 +279,9 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
246
279
|
const tailM = /^\/api\/tail\/(.+)$/.exec(p);
|
|
247
280
|
if (req.method === "GET" && tailM) {
|
|
248
281
|
const keyword = decodeURIComponent(tailM[1]!);
|
|
282
|
+
// raw=1 streams the unmodified PTY bytes (ANSI/cursor control intact) so a
|
|
283
|
+
// browser xterm.js can render the real terminal; default stays ANSI-stripped.
|
|
284
|
+
const raw = url.searchParams.get("raw") === "1";
|
|
249
285
|
try {
|
|
250
286
|
const record = await resolveOne(keyword, defaultOpts());
|
|
251
287
|
if (!record.log_file)
|
|
@@ -259,10 +295,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
259
295
|
ctrl.enqueue(enc.encode(`data: ${JSON.stringify(text)}\n\n`));
|
|
260
296
|
const ping = () => ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
261
297
|
|
|
262
|
-
// Initial tail
|
|
298
|
+
// Initial tail. Raw: replay the last ~64 KB of PTY bytes (enough to
|
|
299
|
+
// contain a recent full-screen redraw so xterm converges fast).
|
|
263
300
|
const initBuf = await readFile(logPath).catch(() => Buffer.alloc(0));
|
|
264
|
-
|
|
265
|
-
|
|
301
|
+
if (raw)
|
|
302
|
+
send(new TextDecoder().decode(initBuf.slice(Math.max(0, initBuf.length - 65536))));
|
|
303
|
+
else send(await renderRawLog(initBuf, { mode: "tail", n: 96 }));
|
|
266
304
|
|
|
267
305
|
let offset = initBuf.length;
|
|
268
306
|
let closed = false;
|
|
@@ -291,11 +329,15 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
291
329
|
if (full.length <= offset) return;
|
|
292
330
|
const chunk = full.slice(offset);
|
|
293
331
|
offset = full.length;
|
|
294
|
-
|
|
295
|
-
.decode(chunk)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
332
|
+
if (raw) {
|
|
333
|
+
send(new TextDecoder().decode(chunk));
|
|
334
|
+
} else {
|
|
335
|
+
const text = new TextDecoder()
|
|
336
|
+
.decode(chunk)
|
|
337
|
+
.replace(ansiRe, "")
|
|
338
|
+
.replace(ctrlRe, "");
|
|
339
|
+
if (text.trim()) send(text.trimStart());
|
|
340
|
+
}
|
|
299
341
|
} catch {
|
|
300
342
|
/* log gone */
|
|
301
343
|
}
|
|
@@ -356,6 +398,74 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
356
398
|
}
|
|
357
399
|
}
|
|
358
400
|
|
|
401
|
+
// POST /api/resize/:keyword body {cols, rows} — drive the agent's PTY size.
|
|
402
|
+
// Mirrors `ay attach`: write ~/.agent-yes/winsize/<pid> then SIGWINCH; the
|
|
403
|
+
// agent's resize listener picks it up and reflows its TUI to that width.
|
|
404
|
+
const resizeM = /^\/api\/resize\/(.+)$/.exec(p);
|
|
405
|
+
if (req.method === "POST" && resizeM) {
|
|
406
|
+
const keyword = decodeURIComponent(resizeM[1]!);
|
|
407
|
+
let body: { cols?: number; rows?: number };
|
|
408
|
+
try {
|
|
409
|
+
body = await req.json();
|
|
410
|
+
} catch {
|
|
411
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
412
|
+
}
|
|
413
|
+
const cols = Math.max(1, Math.floor(Number(body.cols) || 0));
|
|
414
|
+
const rows = Math.max(1, Math.floor(Number(body.rows) || 0));
|
|
415
|
+
if (!cols || !rows) return new Response("missing cols/rows", { status: 400 });
|
|
416
|
+
try {
|
|
417
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
418
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
419
|
+
const winsizeDir = path.join(ayHome, "winsize");
|
|
420
|
+
await mkdir(winsizeDir, { recursive: true });
|
|
421
|
+
await writeFile(
|
|
422
|
+
path.join(winsizeDir, String(record.pid)),
|
|
423
|
+
`${cols} ${rows} ${Date.now()}\n`,
|
|
424
|
+
);
|
|
425
|
+
try {
|
|
426
|
+
process.kill(record.pid, "SIGWINCH");
|
|
427
|
+
} catch {
|
|
428
|
+
/* agent gone */
|
|
429
|
+
}
|
|
430
|
+
return Response.json({ ok: true, pid: record.pid, cols, rows });
|
|
431
|
+
} catch (e) {
|
|
432
|
+
return new Response((e as Error).message, { status: 404 });
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// POST /api/spawn body {cli, cwd, prompt} — launch a new agent (gated)
|
|
437
|
+
if (req.method === "POST" && p === "/api/spawn") {
|
|
438
|
+
if (!allowSpawn)
|
|
439
|
+
return new Response("spawning disabled — start: ay serve --share --allow-spawn", {
|
|
440
|
+
status: 403,
|
|
441
|
+
});
|
|
442
|
+
let body: { cli?: string; cwd?: string; prompt?: string };
|
|
443
|
+
try {
|
|
444
|
+
body = await req.json();
|
|
445
|
+
} catch {
|
|
446
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
447
|
+
}
|
|
448
|
+
const cli = String(body.cli ?? "claude");
|
|
449
|
+
if (!SUPPORTED_CLIS.includes(cli as never))
|
|
450
|
+
return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
451
|
+
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
452
|
+
const prompt = String(body.prompt ?? "");
|
|
453
|
+
if (!(await confirmSpawn(cli, cwd, prompt)))
|
|
454
|
+
return new Response("denied by host", { status: 403 });
|
|
455
|
+
try {
|
|
456
|
+
const child = Bun.spawn(["ay", cli, ...(prompt ? ["--", prompt] : [])], {
|
|
457
|
+
cwd,
|
|
458
|
+
stdin: "ignore",
|
|
459
|
+
stdout: "ignore",
|
|
460
|
+
stderr: "ignore",
|
|
461
|
+
});
|
|
462
|
+
child.unref();
|
|
463
|
+
return Response.json({ ok: true, pid: child.pid, cli, cwd });
|
|
464
|
+
} catch (e) {
|
|
465
|
+
return new Response((e as Error).message, { status: 500 });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
359
469
|
return new Response("Not Found", { status: 404 });
|
|
360
470
|
},
|
|
361
471
|
};
|
|
@@ -380,6 +490,27 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
380
490
|
` openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'\n\n`,
|
|
381
491
|
);
|
|
382
492
|
}
|
|
493
|
+
// --share: bridge this local server to a WebRTC room so the agent-yes.com
|
|
494
|
+
// console can reach it peer-to-peer. Bare flag mints a room; a webrtc:// value
|
|
495
|
+
// joins an explicit one.
|
|
496
|
+
if (argv.share !== undefined) {
|
|
497
|
+
const shareUrl =
|
|
498
|
+
typeof argv.share === "string" && argv.share.startsWith("webrtc://") ? argv.share : undefined;
|
|
499
|
+
try {
|
|
500
|
+
const { startShare } = await import("./share.ts");
|
|
501
|
+
const { link } = await startShare({
|
|
502
|
+
url: shareUrl,
|
|
503
|
+
apiUrl: `http://127.0.0.1:${port}`,
|
|
504
|
+
apiToken: token,
|
|
505
|
+
});
|
|
506
|
+
process.stdout.write(
|
|
507
|
+
`\nshared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n\n`,
|
|
508
|
+
);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
process.stderr.write(`ay serve --share failed: ${(e as Error).message}\n`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
383
514
|
process.stdout.write(`(Ctrl-C to stop)\n`);
|
|
384
515
|
|
|
385
516
|
await new Promise<void>((resolve) => {
|
package/ts/share.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// `ay serve --share` host peer: connect to the signaling server as a room host
|
|
2
|
+
// and bridge each browser peer's WebRTC DataChannel to this machine's local
|
|
3
|
+
// `ay serve` HTTP API. The browser (agent-yes.com) thus reaches local agents
|
|
4
|
+
// peer-to-peer — no public port, no tunnel. See lab/ui/cf/worker.ts for the
|
|
5
|
+
// signaling protocol and lab/ui/index.html for the browser side.
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
7
|
+
|
|
8
|
+
const SUB = "ay-signal-1";
|
|
9
|
+
const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
10
|
+
const MAX_CHUNK = 15_000; // keep DataChannel messages under the SCTP limit
|
|
11
|
+
const DEFAULT_SIGHOST = "s.agent-yes.com";
|
|
12
|
+
|
|
13
|
+
export interface ShareOpts {
|
|
14
|
+
/** webrtc://room:token@host, or undefined to mint a fresh room+token */
|
|
15
|
+
url?: string;
|
|
16
|
+
/** signaling host when minting (default s.agent-yes.com) */
|
|
17
|
+
sighost?: string;
|
|
18
|
+
/** local ay-serve base URL the channel bridges to */
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
/** bearer token for the local ay-serve API */
|
|
21
|
+
apiToken: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseShareUrl(s: string): { room: string; token: string; host: string } {
|
|
25
|
+
const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
|
|
26
|
+
if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
|
|
27
|
+
return { room: m[1]!, token: m[2]!, host: m[3]! };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// node-datachannel ships a native addon. Under Bun the module sometimes resolves
|
|
31
|
+
// from the global cache where the prebuilt .node isn't linked; this best-effort
|
|
32
|
+
// shim symlinks it in before we import. In a normal npm/bunx install the binary
|
|
33
|
+
// resolves from node_modules and the first import just works.
|
|
34
|
+
async function importRTC(): Promise<any> {
|
|
35
|
+
try {
|
|
36
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
37
|
+
} catch {
|
|
38
|
+
try {
|
|
39
|
+
const { existsSync, symlinkSync, mkdirSync, readdirSync } = await import("fs");
|
|
40
|
+
const path = (await import("path")).default;
|
|
41
|
+
const { createRequire } = await import("module");
|
|
42
|
+
const require = createRequire(import.meta.url);
|
|
43
|
+
const pkg = path.dirname(require.resolve("node-datachannel/package.json"));
|
|
44
|
+
const bin = path.join(pkg, "build", "Release", "node_datachannel.node");
|
|
45
|
+
const cacheRoot = path.join((await import("os")).homedir(), ".bun", "install", "cache");
|
|
46
|
+
if (existsSync(bin) && existsSync(cacheRoot)) {
|
|
47
|
+
for (const d of readdirSync(cacheRoot)) {
|
|
48
|
+
if (!d.startsWith("node-datachannel@")) continue;
|
|
49
|
+
const dst = path.join(cacheRoot, d, "build", "Release");
|
|
50
|
+
mkdirSync(dst, { recursive: true });
|
|
51
|
+
const link = path.join(dst, "node_datachannel.node");
|
|
52
|
+
if (!existsSync(link)) symlinkSync(bin, link);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
/* fall through — rethrow the original import error below */
|
|
57
|
+
}
|
|
58
|
+
return (await import("node-datachannel/polyfill")).RTCPeerConnection;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Start the share bridge. Resolves once signaling is connected; runs until the
|
|
63
|
+
* process exits, reconnecting signaling on drop. Returns the shareable link. */
|
|
64
|
+
export async function startShare(opts: ShareOpts): Promise<{ room: string; link: string }> {
|
|
65
|
+
const minted = !opts.url;
|
|
66
|
+
const sighost = opts.sighost ?? DEFAULT_SIGHOST;
|
|
67
|
+
const { room, token, host } = opts.url
|
|
68
|
+
? parseShareUrl(opts.url)
|
|
69
|
+
: {
|
|
70
|
+
room: "r" + randomBytes(3).toString("hex"),
|
|
71
|
+
token: randomBytes(32).toString("hex"),
|
|
72
|
+
host: sighost,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const RTCPeerConnection = await importRTC();
|
|
76
|
+
const wsScheme = host.startsWith("localhost") || host.startsWith("127.") ? "ws" : "wss";
|
|
77
|
+
const ui = host === "s.agent-yes.com" ? "https://agent-yes.com" : "http://localhost:7778";
|
|
78
|
+
const suffix = host === "s.agent-yes.com" ? "" : "@" + host;
|
|
79
|
+
const link = `${ui}/#${room}:${token}${suffix}`;
|
|
80
|
+
|
|
81
|
+
type Peer = { pc: any; aborts: Map<number, AbortController> };
|
|
82
|
+
const peers = new Map<string, Peer>();
|
|
83
|
+
|
|
84
|
+
const connectSignaling = (onReady: () => void) => {
|
|
85
|
+
const ws = new WebSocket(`${wsScheme}://${host}/${room}`, [SUB]);
|
|
86
|
+
let ready = false;
|
|
87
|
+
ws.onopen = () => {
|
|
88
|
+
ws.send(JSON.stringify({ type: "hello", role: "host", token }));
|
|
89
|
+
ready = true;
|
|
90
|
+
onReady();
|
|
91
|
+
};
|
|
92
|
+
ws.onmessage = async (ev) => {
|
|
93
|
+
const m = JSON.parse(ev.data as string);
|
|
94
|
+
if (m.type === "peer-join") startPeer(ws, m.peer);
|
|
95
|
+
else if (m.type === "answer")
|
|
96
|
+
await peers.get(m.from)?.pc.setRemoteDescription({ type: "answer", sdp: m.sdp });
|
|
97
|
+
else if (m.type === "candidate")
|
|
98
|
+
await peers
|
|
99
|
+
.get(m.from)
|
|
100
|
+
?.pc.addIceCandidate(m.candidate)
|
|
101
|
+
.catch(() => {});
|
|
102
|
+
else if (m.type === "peer-leave") closePeer(m.peer);
|
|
103
|
+
};
|
|
104
|
+
ws.onclose = () => {
|
|
105
|
+
// Keep established WebRTC peers; just re-establish the rendezvous so new
|
|
106
|
+
// browsers can still join. Backoff a little to avoid hot-looping.
|
|
107
|
+
setTimeout(() => connectSignaling(() => {}), ready ? 1500 : 4000);
|
|
108
|
+
};
|
|
109
|
+
ws.onerror = () => {};
|
|
110
|
+
return ws;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
function startPeer(ws: WebSocket, peerId: string) {
|
|
114
|
+
const pc = new RTCPeerConnection({ iceServers: ICE });
|
|
115
|
+
const aborts = new Map<number, AbortController>();
|
|
116
|
+
peers.set(peerId, { pc, aborts });
|
|
117
|
+
pc.onicecandidate = (e: any) => {
|
|
118
|
+
if (e.candidate)
|
|
119
|
+
ws.send(JSON.stringify({ type: "candidate", to: peerId, candidate: e.candidate }));
|
|
120
|
+
};
|
|
121
|
+
pc.onconnectionstatechange = () => {
|
|
122
|
+
if (["failed", "closed", "disconnected"].includes(pc.connectionState)) closePeer(peerId);
|
|
123
|
+
};
|
|
124
|
+
const dc = pc.createDataChannel("api");
|
|
125
|
+
dc.onmessage = (e: any) => onReq(dc, aborts, JSON.parse(e.data));
|
|
126
|
+
pc.createOffer()
|
|
127
|
+
.then((o: any) => pc.setLocalDescription(o))
|
|
128
|
+
.then(() =>
|
|
129
|
+
ws.send(JSON.stringify({ type: "offer", to: peerId, sdp: pc.localDescription.sdp })),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function closePeer(peerId: string) {
|
|
134
|
+
const p = peers.get(peerId);
|
|
135
|
+
if (!p) return;
|
|
136
|
+
for (const a of p.aborts.values()) a.abort();
|
|
137
|
+
try {
|
|
138
|
+
p.pc.close();
|
|
139
|
+
} catch {
|
|
140
|
+
/* already closed */
|
|
141
|
+
}
|
|
142
|
+
peers.delete(peerId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function send(dc: any, obj: object) {
|
|
146
|
+
if (dc.readyState === "open") dc.send(JSON.stringify(obj));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function onReq(dc: any, aborts: Map<number, AbortController>, req: any) {
|
|
150
|
+
if (req.t === "abort") {
|
|
151
|
+
aborts.get(req.id)?.abort();
|
|
152
|
+
aborts.delete(req.id);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (req.t !== "req") return;
|
|
156
|
+
const { id, method, path: p, body } = req;
|
|
157
|
+
const ac = new AbortController();
|
|
158
|
+
aborts.set(id, ac);
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch(opts.apiUrl + p, {
|
|
161
|
+
method,
|
|
162
|
+
headers: {
|
|
163
|
+
Authorization: `Bearer ${opts.apiToken}`,
|
|
164
|
+
...(body ? { "Content-Type": "application/json" } : {}),
|
|
165
|
+
},
|
|
166
|
+
body: body ?? undefined,
|
|
167
|
+
signal: ac.signal,
|
|
168
|
+
});
|
|
169
|
+
send(dc, { t: "res", id, status: res.status, ct: res.headers.get("content-type") ?? "" });
|
|
170
|
+
const reader = res.body!.getReader();
|
|
171
|
+
const dec = new TextDecoder();
|
|
172
|
+
for (;;) {
|
|
173
|
+
const { done, value } = await reader.read();
|
|
174
|
+
if (done) break;
|
|
175
|
+
const text = dec.decode(value, { stream: true });
|
|
176
|
+
for (let i = 0; i < text.length; i += MAX_CHUNK)
|
|
177
|
+
send(dc, { t: "data", id, chunk: text.slice(i, i + MAX_CHUNK) });
|
|
178
|
+
}
|
|
179
|
+
send(dc, { t: "end", id });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
if ((e as Error).name !== "AbortError") send(dc, { t: "end", id, error: String(e) });
|
|
182
|
+
} finally {
|
|
183
|
+
aborts.delete(id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await new Promise<void>((resolve) => connectSignaling(resolve));
|
|
188
|
+
void minted; // (informational) caller decides how to surface the link
|
|
189
|
+
return { room, link };
|
|
190
|
+
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-DtwVuD8n.js";
|
|
2
|
-
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-BY5g27iW.js";
|
|
4
|
-
import "./pidStore-DTzl6zeh.js";
|
|
5
|
-
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
-
|
|
7
|
-
//#region ts/SUPPORTED_CLIS.ts
|
|
8
|
-
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
9
|
-
|
|
10
|
-
//#endregion
|
|
11
|
-
export { SUPPORTED_CLIS };
|
|
12
|
-
//# sourceMappingURL=SUPPORTED_CLIS-Bo4qbT_0.js.map
|