agent-yes 1.97.0 → 1.99.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-BGUPuqya.js +8 -0
- package/dist/{SUPPORTED_CLIS-eD-UlqO_.js → SUPPORTED_CLIS-eIjVu8HF.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/serve-SQYFRbm3.js +554 -0
- package/dist/{share-DUhUA1Pi.js → share-BsCeIfQM.js} +21 -4
- package/dist/{subcommands-B4gXEu5I.js → subcommands-D3Z9cD9u.js} +1 -1
- package/dist/{subcommands-K242usI5.js → subcommands-z8Y8gcD_.js} +8 -3
- package/dist/{ts-BAc4Jcrw.js → ts-BECoCPV1.js} +2 -2
- package/dist/{versionChecker-MNvA73o9.js → versionChecker-pct2j3wR.js} +2 -2
- package/lab/ui/index.html +1645 -0
- package/lab/ui/room-client.js +520 -0
- package/package.json +4 -2
- package/ts/serve.ts +463 -311
- package/ts/share.ts +49 -17
- package/ts/subcommands.ts +6 -0
- package/dist/SUPPORTED_CLIS-CNO_pj9f.js +0 -8
- package/dist/serve-CKcbVPy6.js +0 -451
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-BECoCPV1.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-pct2j3wR.js";
|
|
4
|
+
import "./pidStore-DBjlqzo8.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-eIjVu8HF.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-BECoCPV1.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-eIjVu8HF.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-pct2j3wR.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-D3Z9cD9u.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-BGUPuqya.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-BECoCPV1.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-pct2j3wR.js";
|
|
4
4
|
import "./pidStore-DBjlqzo8.js";
|
|
5
5
|
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
6
|
|
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
import "./ts-BECoCPV1.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-pct2j3wR.js";
|
|
4
|
+
import "./pidStore-DBjlqzo8.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-eIjVu8HF.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-z8Y8gcD_.js";
|
|
9
|
+
import yargs from "yargs";
|
|
10
|
+
import { mkdir, open, readFile, writeFile } from "fs/promises";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { watch } from "node:fs";
|
|
15
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
16
|
+
|
|
17
|
+
//#region ts/serve.ts
|
|
18
|
+
const DEFAULT_PORT = 7432;
|
|
19
|
+
function agentYesHome() {
|
|
20
|
+
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
21
|
+
}
|
|
22
|
+
function tokenPath() {
|
|
23
|
+
return path.join(agentYesHome(), ".serve-token");
|
|
24
|
+
}
|
|
25
|
+
async function loadOrCreateToken(tokenFlag) {
|
|
26
|
+
if (tokenFlag) return tokenFlag;
|
|
27
|
+
try {
|
|
28
|
+
return (await readFile(tokenPath(), "utf-8")).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
const token = randomBytes(20).toString("hex");
|
|
31
|
+
await mkdir(agentYesHome(), { recursive: true });
|
|
32
|
+
await writeFile(tokenPath(), token, { mode: 384 });
|
|
33
|
+
return token;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function tokenEqual(provided, expectedToken) {
|
|
37
|
+
const maxLen = Math.max(provided.length, expectedToken.length);
|
|
38
|
+
return timingSafeEqual(Buffer.from(provided.padEnd(maxLen, "\0")), Buffer.from(expectedToken.padEnd(maxLen, "\0"))) && provided.length === expectedToken.length;
|
|
39
|
+
}
|
|
40
|
+
function checkAuth(req, expectedToken) {
|
|
41
|
+
const auth = req.headers.get("authorization") ?? "";
|
|
42
|
+
if (auth.startsWith("Bearer ")) return tokenEqual(auth.slice(7), expectedToken);
|
|
43
|
+
const q = new URL(req.url).searchParams.get("token");
|
|
44
|
+
return q ? tokenEqual(q, expectedToken) : false;
|
|
45
|
+
}
|
|
46
|
+
const defaultOpts = (overrides = {}) => ({
|
|
47
|
+
all: false,
|
|
48
|
+
active: false,
|
|
49
|
+
json: true,
|
|
50
|
+
latest: true,
|
|
51
|
+
cwdScope: null,
|
|
52
|
+
...overrides
|
|
53
|
+
});
|
|
54
|
+
const DAEMON_NAME = "agent-yes";
|
|
55
|
+
async function cmdServeDaemon(sub, args) {
|
|
56
|
+
const oxmgrBin = Bun.which("oxmgr");
|
|
57
|
+
if (!oxmgrBin) {
|
|
58
|
+
process.stderr.write("ay serve install: oxmgr not found\n install with: cargo install oxmgr\n or: bun add -g oxmgr\n");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
if (sub === "install") {
|
|
62
|
+
const token = await loadOrCreateToken(void 0);
|
|
63
|
+
const ayBin = Bun.which("ay");
|
|
64
|
+
const serveCmd = [
|
|
65
|
+
...ayBin ? [process.execPath, ayBin] : ["ay"],
|
|
66
|
+
"serve",
|
|
67
|
+
...args
|
|
68
|
+
].join(" ");
|
|
69
|
+
const code = await Bun.spawn([
|
|
70
|
+
oxmgrBin,
|
|
71
|
+
"start",
|
|
72
|
+
serveCmd,
|
|
73
|
+
"--name",
|
|
74
|
+
DAEMON_NAME,
|
|
75
|
+
"--restart",
|
|
76
|
+
"always"
|
|
77
|
+
], { stdio: [
|
|
78
|
+
"ignore",
|
|
79
|
+
"inherit",
|
|
80
|
+
"inherit"
|
|
81
|
+
] }).exited;
|
|
82
|
+
if (code === 0) {
|
|
83
|
+
const portM = /--port[=\s](\d+)/.exec(args.join(" "));
|
|
84
|
+
const port = portM ? Number(portM[1]) : DEFAULT_PORT;
|
|
85
|
+
const webrtcish = args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"));
|
|
86
|
+
const httpish = args.some((a) => a.startsWith("--http") || a.startsWith("--share")) || !args.some((a) => a.startsWith("--webrtc"));
|
|
87
|
+
process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
|
|
88
|
+
process.stdout.write(`token: ${token}\n\n`);
|
|
89
|
+
if (httpish) {
|
|
90
|
+
process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
|
|
91
|
+
process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
|
|
92
|
+
}
|
|
93
|
+
process.stdout.write(` ay serve logs # view server logs\n`);
|
|
94
|
+
process.stdout.write(` ay serve uninstall # remove daemon\n`);
|
|
95
|
+
if (webrtcish) process.stdout.write("\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n(the room persists in ~/.agent-yes/.share-room, so the link survives restarts)\n");
|
|
96
|
+
}
|
|
97
|
+
return code ?? 1;
|
|
98
|
+
}
|
|
99
|
+
if (sub === "uninstall") return await Bun.spawn([
|
|
100
|
+
oxmgrBin,
|
|
101
|
+
"delete",
|
|
102
|
+
DAEMON_NAME
|
|
103
|
+
], { stdio: [
|
|
104
|
+
"ignore",
|
|
105
|
+
"inherit",
|
|
106
|
+
"inherit"
|
|
107
|
+
] }).exited ?? 1;
|
|
108
|
+
if (sub === "logs") return await Bun.spawn([
|
|
109
|
+
oxmgrBin,
|
|
110
|
+
"logs",
|
|
111
|
+
DAEMON_NAME,
|
|
112
|
+
...args
|
|
113
|
+
], { stdio: [
|
|
114
|
+
"ignore",
|
|
115
|
+
"inherit",
|
|
116
|
+
"inherit"
|
|
117
|
+
] }).exited ?? 1;
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
async function cmdServe(rest) {
|
|
121
|
+
if (rest.includes("-h") || rest.includes("--help")) {
|
|
122
|
+
process.stdout.write(`Usage: ay serve [options]
|
|
123
|
+
|
|
124
|
+
Start an API server (HTTP and/or WebRTC) so browsers and remote machines
|
|
125
|
+
can list/tail/send agents.
|
|
126
|
+
|
|
127
|
+
Modes (default: --http):
|
|
128
|
+
--http HTTP API + web console on --port; no WebRTC
|
|
129
|
+
--webrtc [URL] Share over WebRTC (bare flag mints a room+link on
|
|
130
|
+
agent-yes.com, or pass webrtc://room:token@host).
|
|
131
|
+
Alone it needs NO port — combine with --http for both.
|
|
132
|
+
The minted room persists in ~/.agent-yes/.share-room
|
|
133
|
+
(stable link across restarts; delete the file to rotate).
|
|
134
|
+
--share [URL] Legacy alias for --http --webrtc
|
|
135
|
+
|
|
136
|
+
Options:
|
|
137
|
+
--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 -d, --daemon Install these flags as a background daemon via oxmgr\n (same as: ay serve install <flags>)\n --allow-spawn Deprecated no-op — the console can always spawn agents\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`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
const sub = rest[0];
|
|
141
|
+
if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
|
|
142
|
+
const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
|
|
143
|
+
type: "number",
|
|
144
|
+
default: DEFAULT_PORT,
|
|
145
|
+
description: "Port to listen on"
|
|
146
|
+
}).option("host", {
|
|
147
|
+
type: "string",
|
|
148
|
+
default: "127.0.0.1",
|
|
149
|
+
description: "Interface to bind (use 0.0.0.0 to expose)"
|
|
150
|
+
}).option("token", {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Auth token (auto-generated if omitted)"
|
|
153
|
+
}).option("tls-cert", {
|
|
154
|
+
type: "string",
|
|
155
|
+
description: "TLS certificate file (PEM)"
|
|
156
|
+
}).option("tls-key", {
|
|
157
|
+
type: "string",
|
|
158
|
+
description: "TLS private key file (PEM)"
|
|
159
|
+
}).option("http", {
|
|
160
|
+
type: "boolean",
|
|
161
|
+
description: "Serve the HTTP API + web console on --port (default mode)"
|
|
162
|
+
}).option("webrtc", {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Share over WebRTC: bare flag mints a room+link, or pass webrtc://room:token@host. Needs no port unless combined with --http"
|
|
165
|
+
}).option("share", {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "Legacy alias for --http --webrtc"
|
|
168
|
+
}).option("daemon", {
|
|
169
|
+
alias: "d",
|
|
170
|
+
type: "boolean",
|
|
171
|
+
default: false,
|
|
172
|
+
description: "Install as a background daemon via oxmgr (same as: ay serve install <flags>)"
|
|
173
|
+
}).option("allow-spawn", {
|
|
174
|
+
type: "boolean",
|
|
175
|
+
default: false,
|
|
176
|
+
description: "Deprecated no-op — the console can always spawn agents"
|
|
177
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
178
|
+
if (argv.daemon) return cmdServeDaemon("install", rest.filter((a) => a !== "--daemon" && a !== "-d"));
|
|
179
|
+
const port = argv.port ?? DEFAULT_PORT;
|
|
180
|
+
const host = argv.host ?? "127.0.0.1";
|
|
181
|
+
const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
|
|
182
|
+
const certPath = typeof argv["tls-cert"] === "string" ? argv["tls-cert"] : void 0;
|
|
183
|
+
const keyPath = typeof argv["tls-key"] === "string" ? argv["tls-key"] : void 0;
|
|
184
|
+
if (certPath && !keyPath || !certPath && keyPath) {
|
|
185
|
+
process.stderr.write("ay serve: --tls-cert and --tls-key must both be provided\n");
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
const useHttps = !!(certPath && keyPath);
|
|
189
|
+
const scheme = useHttps ? "https" : "http";
|
|
190
|
+
const wantWebrtc = argv.webrtc !== void 0 || argv.share !== void 0;
|
|
191
|
+
const wantHttp = argv.http === true || argv.share !== void 0 || argv.webrtc === void 0;
|
|
192
|
+
if (wantHttp && 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");
|
|
193
|
+
const token = await loadOrCreateToken(tokenFlag);
|
|
194
|
+
const titleCache = /* @__PURE__ */ new Map();
|
|
195
|
+
const logTitle = async (logFile) => {
|
|
196
|
+
if (!logFile) return null;
|
|
197
|
+
try {
|
|
198
|
+
const fh = await open(logFile, "r");
|
|
199
|
+
try {
|
|
200
|
+
const { size, mtimeMs } = await fh.stat();
|
|
201
|
+
const hit = titleCache.get(logFile);
|
|
202
|
+
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.title;
|
|
203
|
+
const len = Math.min(size, 65536);
|
|
204
|
+
const buf = Buffer.allocUnsafe(len);
|
|
205
|
+
const { bytesRead } = await fh.read(buf, 0, len, size - len);
|
|
206
|
+
const text = buf.toString("utf-8", 0, bytesRead);
|
|
207
|
+
const oscTitleRe = /\x1b\][02];([^\x07\x1b]*)(?:\x07|\x1b\\)/g;
|
|
208
|
+
let title = null;
|
|
209
|
+
for (let m; m = oscTitleRe.exec(text);) if (m[1].trim()) title = m[1].trim();
|
|
210
|
+
titleCache.set(logFile, {
|
|
211
|
+
size,
|
|
212
|
+
mtimeMs,
|
|
213
|
+
title
|
|
214
|
+
});
|
|
215
|
+
return title;
|
|
216
|
+
} finally {
|
|
217
|
+
await fh.close();
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const apiFetch = async (req) => {
|
|
224
|
+
if (!checkAuth(req, token)) return new Response("Unauthorized", { status: 401 });
|
|
225
|
+
const url = new URL(req.url);
|
|
226
|
+
const p = url.pathname;
|
|
227
|
+
if (req.method === "GET" && p === "/api/ls") {
|
|
228
|
+
const keyword = url.searchParams.get("keyword") ?? void 0;
|
|
229
|
+
const opts = defaultOpts({
|
|
230
|
+
all: url.searchParams.get("all") === "1",
|
|
231
|
+
active: url.searchParams.get("active") === "1"
|
|
232
|
+
});
|
|
233
|
+
try {
|
|
234
|
+
const records = await listRecords(keyword, opts);
|
|
235
|
+
const withTitles = await Promise.all(records.map(async (r) => ({
|
|
236
|
+
...r,
|
|
237
|
+
title: await logTitle(r.log_file)
|
|
238
|
+
})));
|
|
239
|
+
return Response.json(withTitles);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
return new Response(e.message, { status: 500 });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (req.method === "GET" && p === "/api/notes") {
|
|
245
|
+
const notes = await readNotes();
|
|
246
|
+
return Response.json(Object.fromEntries(notes));
|
|
247
|
+
}
|
|
248
|
+
const statusM = /^\/api\/status\/(.+)$/.exec(p);
|
|
249
|
+
if (req.method === "GET" && statusM) {
|
|
250
|
+
const keyword = decodeURIComponent(statusM[1]);
|
|
251
|
+
try {
|
|
252
|
+
const snap = await snapshotStatus(await resolveOne(keyword, defaultOpts({ all: true })));
|
|
253
|
+
return Response.json(snap);
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return new Response(e.message, { status: 404 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const readM = /^\/api\/read\/(.+)$/.exec(p);
|
|
259
|
+
if (req.method === "GET" && readM) {
|
|
260
|
+
const keyword = decodeURIComponent(readM[1]);
|
|
261
|
+
const mode = url.searchParams.get("mode") ?? "tail";
|
|
262
|
+
const n = parseInt(url.searchParams.get("n") ?? "96", 10) || 96;
|
|
263
|
+
try {
|
|
264
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
265
|
+
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
266
|
+
const text = await renderRawLog(await readFile(record.log_file), {
|
|
267
|
+
mode,
|
|
268
|
+
n
|
|
269
|
+
});
|
|
270
|
+
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return new Response(e.message, { status: 404 });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const sizeM = /^\/api\/size\/(.+)$/.exec(p);
|
|
276
|
+
if (req.method === "GET" && sizeM) {
|
|
277
|
+
const keyword = decodeURIComponent(sizeM[1]);
|
|
278
|
+
try {
|
|
279
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
280
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
281
|
+
let cols = null;
|
|
282
|
+
let rows = null;
|
|
283
|
+
try {
|
|
284
|
+
const [c = 0, r = 0] = (await readFile(path.join(ayHome, "ptysize", String(record.pid)), "utf-8")).trim().split(/\s+/).map(Number);
|
|
285
|
+
if (c > 0 && r > 0) {
|
|
286
|
+
cols = c;
|
|
287
|
+
rows = r;
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
290
|
+
return Response.json({
|
|
291
|
+
pid: record.pid,
|
|
292
|
+
cols,
|
|
293
|
+
rows
|
|
294
|
+
});
|
|
295
|
+
} catch (e) {
|
|
296
|
+
return new Response(e.message, { status: 404 });
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const tailM = /^\/api\/tail\/(.+)$/.exec(p);
|
|
300
|
+
if (req.method === "GET" && tailM) {
|
|
301
|
+
const keyword = decodeURIComponent(tailM[1]);
|
|
302
|
+
const raw = url.searchParams.get("raw") === "1";
|
|
303
|
+
try {
|
|
304
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
305
|
+
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
306
|
+
const logPath = record.log_file;
|
|
307
|
+
const stream = new ReadableStream({ async start(ctrl) {
|
|
308
|
+
const enc = new TextEncoder();
|
|
309
|
+
const send = (text) => ctrl.enqueue(enc.encode(`data: ${JSON.stringify(text)}\n\n`));
|
|
310
|
+
const ping = () => ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
311
|
+
const initBuf = await readFile(logPath).catch(() => Buffer.alloc(0));
|
|
312
|
+
if (raw) send(new TextDecoder().decode(initBuf.slice(Math.max(0, initBuf.length - 65536))));
|
|
313
|
+
else send(await renderRawLog(initBuf, {
|
|
314
|
+
mode: "tail",
|
|
315
|
+
n: 96
|
|
316
|
+
}));
|
|
317
|
+
let offset = initBuf.length;
|
|
318
|
+
let closed = false;
|
|
319
|
+
const heartbeat = setInterval(() => {
|
|
320
|
+
if (closed) {
|
|
321
|
+
clearInterval(heartbeat);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
ping();
|
|
325
|
+
}, 15e3);
|
|
326
|
+
const ansiRe = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
327
|
+
const ctrlRe = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
328
|
+
const fh = await open(logPath, "r").catch(() => null);
|
|
329
|
+
let reading = false;
|
|
330
|
+
const flush = async () => {
|
|
331
|
+
if (closed || reading || !fh) return;
|
|
332
|
+
reading = true;
|
|
333
|
+
try {
|
|
334
|
+
const { size } = await fh.stat();
|
|
335
|
+
if (size < offset) offset = size;
|
|
336
|
+
if (size > offset) {
|
|
337
|
+
const len = size - offset;
|
|
338
|
+
const buf = Buffer.allocUnsafe(len);
|
|
339
|
+
const { bytesRead } = await fh.read(buf, 0, len, offset);
|
|
340
|
+
offset += bytesRead;
|
|
341
|
+
const chunk = buf.subarray(0, bytesRead);
|
|
342
|
+
if (raw) send(new TextDecoder().decode(chunk));
|
|
343
|
+
else {
|
|
344
|
+
const text = new TextDecoder().decode(chunk).replace(ansiRe, "").replace(ctrlRe, "");
|
|
345
|
+
if (text.trim()) send(text.trimStart());
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
} catch {} finally {
|
|
349
|
+
reading = false;
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
let watcher = null;
|
|
353
|
+
try {
|
|
354
|
+
watcher = watch(logPath, () => void flush());
|
|
355
|
+
} catch {}
|
|
356
|
+
const poller = setInterval(() => void flush(), 60);
|
|
357
|
+
req.signal.addEventListener("abort", () => {
|
|
358
|
+
closed = true;
|
|
359
|
+
clearInterval(heartbeat);
|
|
360
|
+
clearInterval(poller);
|
|
361
|
+
try {
|
|
362
|
+
watcher?.close();
|
|
363
|
+
} catch {}
|
|
364
|
+
fh?.close().catch(() => {});
|
|
365
|
+
try {
|
|
366
|
+
ctrl.close();
|
|
367
|
+
} catch {}
|
|
368
|
+
});
|
|
369
|
+
} });
|
|
370
|
+
return new Response(stream, { headers: {
|
|
371
|
+
"Content-Type": "text/event-stream",
|
|
372
|
+
"Cache-Control": "no-cache",
|
|
373
|
+
Connection: "keep-alive"
|
|
374
|
+
} });
|
|
375
|
+
} catch (e) {
|
|
376
|
+
return new Response(e.message, { status: 404 });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (req.method === "POST" && p === "/api/send") {
|
|
380
|
+
let body;
|
|
381
|
+
try {
|
|
382
|
+
body = await req.json();
|
|
383
|
+
} catch {
|
|
384
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
385
|
+
}
|
|
386
|
+
const { keyword, msg = "", code = "enter" } = body;
|
|
387
|
+
if (!keyword || typeof keyword !== "string") return new Response("missing keyword", { status: 400 });
|
|
388
|
+
try {
|
|
389
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
390
|
+
if (!record.fifo_file) return new Response(`pid ${record.pid}: no fifo_file`, { status: 409 });
|
|
391
|
+
const trailing = controlCodeFromName(code.toLowerCase());
|
|
392
|
+
if (msg && trailing) {
|
|
393
|
+
await writeToIpc(record.fifo_file, msg);
|
|
394
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
395
|
+
await writeToIpc(record.fifo_file, trailing);
|
|
396
|
+
} else await writeToIpc(record.fifo_file, msg + trailing);
|
|
397
|
+
return Response.json({
|
|
398
|
+
ok: true,
|
|
399
|
+
pid: record.pid
|
|
400
|
+
});
|
|
401
|
+
} catch (e) {
|
|
402
|
+
return new Response(e.message, { status: 404 });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const resizeM = /^\/api\/resize\/(.+)$/.exec(p);
|
|
406
|
+
if (req.method === "POST" && resizeM) {
|
|
407
|
+
const keyword = decodeURIComponent(resizeM[1]);
|
|
408
|
+
let body;
|
|
409
|
+
try {
|
|
410
|
+
body = await req.json();
|
|
411
|
+
} catch {
|
|
412
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
413
|
+
}
|
|
414
|
+
const cols = Math.max(1, Math.floor(Number(body.cols) || 0));
|
|
415
|
+
const rows = Math.max(1, Math.floor(Number(body.rows) || 0));
|
|
416
|
+
if (!cols || !rows) return new Response("missing cols/rows", { status: 400 });
|
|
417
|
+
try {
|
|
418
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
419
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
420
|
+
const winsizeDir = path.join(ayHome, "winsize");
|
|
421
|
+
await mkdir(winsizeDir, { recursive: true });
|
|
422
|
+
await writeFile(path.join(winsizeDir, String(record.pid)), `${cols} ${rows} ${Date.now()}\n`);
|
|
423
|
+
try {
|
|
424
|
+
process.kill(record.pid, "SIGWINCH");
|
|
425
|
+
} catch {}
|
|
426
|
+
return Response.json({
|
|
427
|
+
ok: true,
|
|
428
|
+
pid: record.pid,
|
|
429
|
+
cols,
|
|
430
|
+
rows
|
|
431
|
+
});
|
|
432
|
+
} catch (e) {
|
|
433
|
+
return new Response(e.message, { status: 404 });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (req.method === "POST" && p === "/api/spawn") {
|
|
437
|
+
let body;
|
|
438
|
+
try {
|
|
439
|
+
body = await req.json();
|
|
440
|
+
} catch {
|
|
441
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
442
|
+
}
|
|
443
|
+
const cli = String(body.cli ?? "claude");
|
|
444
|
+
if (!SUPPORTED_CLIS.includes(cli)) return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
445
|
+
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
446
|
+
const prompt = String(body.prompt ?? "");
|
|
447
|
+
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd})\n`);
|
|
448
|
+
try {
|
|
449
|
+
const child = Bun.spawn([
|
|
450
|
+
"ay",
|
|
451
|
+
cli,
|
|
452
|
+
...prompt ? ["--", prompt] : []
|
|
453
|
+
], {
|
|
454
|
+
cwd,
|
|
455
|
+
stdin: "ignore",
|
|
456
|
+
stdout: "ignore",
|
|
457
|
+
stderr: "ignore"
|
|
458
|
+
});
|
|
459
|
+
child.unref();
|
|
460
|
+
return Response.json({
|
|
461
|
+
ok: true,
|
|
462
|
+
pid: child.pid,
|
|
463
|
+
cli,
|
|
464
|
+
cwd
|
|
465
|
+
});
|
|
466
|
+
} catch (e) {
|
|
467
|
+
return new Response(e.message, { status: 500 });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return new Response("Not Found", { status: 404 });
|
|
471
|
+
};
|
|
472
|
+
const uiDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "lab", "ui");
|
|
473
|
+
const serveUiFile = async (name, type) => {
|
|
474
|
+
try {
|
|
475
|
+
const buf = await readFile(path.join(uiDir, name));
|
|
476
|
+
return new Response(buf, { headers: { "Content-Type": type } });
|
|
477
|
+
} catch {
|
|
478
|
+
return new Response("UI assets not found in this install — use the /api endpoints", { status: 404 });
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const httpFetch = async (req) => {
|
|
482
|
+
const p = new URL(req.url).pathname;
|
|
483
|
+
if (req.method === "GET" && (p === "/" || p === "/index.html")) return serveUiFile("index.html", "text/html; charset=utf-8");
|
|
484
|
+
if (req.method === "GET" && p === "/room-client.js") return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
|
|
485
|
+
if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
|
|
486
|
+
return apiFetch(req);
|
|
487
|
+
};
|
|
488
|
+
const serverOpts = {
|
|
489
|
+
hostname: host,
|
|
490
|
+
port,
|
|
491
|
+
idleTimeout: 0,
|
|
492
|
+
fetch: httpFetch
|
|
493
|
+
};
|
|
494
|
+
if (useHttps) serverOpts.tls = {
|
|
495
|
+
cert: Bun.file(certPath),
|
|
496
|
+
key: Bun.file(keyPath)
|
|
497
|
+
};
|
|
498
|
+
let server = null;
|
|
499
|
+
if (wantHttp) {
|
|
500
|
+
try {
|
|
501
|
+
server = Bun.serve(serverOpts);
|
|
502
|
+
} catch (e) {
|
|
503
|
+
if (e.code === "EADDRINUSE") {
|
|
504
|
+
process.stderr.write(`ay serve: port ${port} is already in use — pick another with --port N,\nor run a port-free WebRTC-only share with: ay serve --webrtc\n`);
|
|
505
|
+
return 1;
|
|
506
|
+
}
|
|
507
|
+
throw e;
|
|
508
|
+
}
|
|
509
|
+
const uiHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
510
|
+
process.stdout.write(`ay serve ${scheme}://${host}:${port}\n`);
|
|
511
|
+
process.stdout.write(`token: ${token}\n\n`);
|
|
512
|
+
process.stdout.write(`web console (token in the # is eaten on open):\n`);
|
|
513
|
+
process.stdout.write(` ${scheme}://${uiHost}:${port}/#k=${token}\n\n`);
|
|
514
|
+
process.stdout.write(`connect from another machine:\n`);
|
|
515
|
+
process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
|
|
516
|
+
process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
|
|
517
|
+
process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
|
|
518
|
+
process.stdout.write(`save as alias:\n`);
|
|
519
|
+
process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
|
|
520
|
+
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");
|
|
521
|
+
}
|
|
522
|
+
if (wantWebrtc) {
|
|
523
|
+
const webrtcVal = argv.webrtc ?? argv.share;
|
|
524
|
+
const explicitUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
|
|
525
|
+
try {
|
|
526
|
+
const { startShare, loadOrCreateShareRoom } = await import("./share-BsCeIfQM.js");
|
|
527
|
+
const { link } = await startShare({
|
|
528
|
+
url: explicitUrl ?? await loadOrCreateShareRoom(),
|
|
529
|
+
localFetch: apiFetch,
|
|
530
|
+
apiToken: token
|
|
531
|
+
});
|
|
532
|
+
process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n` + (explicitUrl ? "\n" : ` (persistent room — same link across restarts; delete ~/.agent-yes/.share-room to rotate)\n\n`));
|
|
533
|
+
} catch (e) {
|
|
534
|
+
process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
|
|
535
|
+
if (!wantHttp) return 1;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
process.stdout.write(`(Ctrl-C to stop)\n`);
|
|
539
|
+
await new Promise((resolve) => {
|
|
540
|
+
process.on("SIGINT", () => {
|
|
541
|
+
server?.stop();
|
|
542
|
+
resolve();
|
|
543
|
+
});
|
|
544
|
+
process.on("SIGTERM", () => {
|
|
545
|
+
server?.stop();
|
|
546
|
+
resolve();
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
//#endregion
|
|
553
|
+
export { cmdServe };
|
|
554
|
+
//# sourceMappingURL=serve-SQYFRbm3.js.map
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import path from "path";
|
|
1
4
|
import { randomBytes } from "crypto";
|
|
2
5
|
|
|
3
6
|
//#region ts/share.ts
|
|
@@ -5,6 +8,20 @@ const SUB = "ay-signal-1";
|
|
|
5
8
|
const ICE = [{ urls: "stun:stun.l.google.com:19302" }];
|
|
6
9
|
const MAX_CHUNK = 15e3;
|
|
7
10
|
const DEFAULT_SIGHOST = "s.agent-yes.com";
|
|
11
|
+
function shareRoomPath() {
|
|
12
|
+
const home = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
13
|
+
return path.join(home, ".share-room");
|
|
14
|
+
}
|
|
15
|
+
async function loadOrCreateShareRoom(sighost = DEFAULT_SIGHOST) {
|
|
16
|
+
try {
|
|
17
|
+
const url = (await readFile(shareRoomPath(), "utf-8")).trim();
|
|
18
|
+
if (url.startsWith("webrtc://")) return url;
|
|
19
|
+
} catch {}
|
|
20
|
+
const url = `webrtc://${"r" + randomBytes(3).toString("hex")}:${randomBytes(32).toString("hex")}@${sighost}`;
|
|
21
|
+
await mkdir(path.dirname(shareRoomPath()), { recursive: true });
|
|
22
|
+
await writeFile(shareRoomPath(), url, { mode: 384 });
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
8
25
|
function parseShareUrl(s) {
|
|
9
26
|
const m = /^webrtc:\/\/([^:@/]+):([^@/]+)@(.+)$/.exec(s);
|
|
10
27
|
if (!m) throw new Error(`bad --share url: ${s} (want webrtc://room:token@host)`);
|
|
@@ -131,7 +148,7 @@ async function startShare(opts) {
|
|
|
131
148
|
const ac = new AbortController();
|
|
132
149
|
aborts.set(id, ac);
|
|
133
150
|
try {
|
|
134
|
-
const res = await
|
|
151
|
+
const res = await opts.localFetch(new Request(`http://ay.local${p}`, {
|
|
135
152
|
method,
|
|
136
153
|
headers: {
|
|
137
154
|
Authorization: `Bearer ${opts.apiToken}`,
|
|
@@ -139,7 +156,7 @@ async function startShare(opts) {
|
|
|
139
156
|
},
|
|
140
157
|
body: body ?? void 0,
|
|
141
158
|
signal: ac.signal
|
|
142
|
-
});
|
|
159
|
+
}));
|
|
143
160
|
send(dc, {
|
|
144
161
|
t: "res",
|
|
145
162
|
id,
|
|
@@ -180,5 +197,5 @@ async function startShare(opts) {
|
|
|
180
197
|
}
|
|
181
198
|
|
|
182
199
|
//#endregion
|
|
183
|
-
export { startShare };
|
|
184
|
-
//# sourceMappingURL=share-
|
|
200
|
+
export { loadOrCreateShareRoom, startShare };
|
|
201
|
+
//# sourceMappingURL=share-BsCeIfQM.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
2
|
import "./globalPidIndex-yVd3mbsV.js";
|
|
3
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-
|
|
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-z8Y8gcD_.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|