agent-yes 1.96.0 → 1.98.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-BtLklR5y.js +8 -0
- package/dist/{SUPPORTED_CLIS-B2FAlgXF.js → SUPPORTED_CLIS-C0a9K6I5.js} +2 -2
- package/dist/cli.js +6 -6
- package/dist/index.js +3 -3
- package/dist/pidStore-9b3YTuf4.js +5 -0
- package/dist/{pidStore-DTzl6zeh.js → pidStore-DBjlqzo8.js} +22 -22
- package/dist/{runningLock-C22d9SRJ.js → runningLock-CJxsoGdb.js} +1 -1
- package/dist/serve-DPY37v0u.js +546 -0
- package/dist/{share-DUhUA1Pi.js → share-D-r6y3xD.js} +3 -3
- package/dist/{subcommands-CcOYsLYD.js → subcommands-D4Muugfr.js} +8 -3
- package/dist/{subcommands-EieqoC9Z.js → subcommands-fCkYXyTe.js} +1 -1
- package/dist/{tray-D4cJA4UH.js → tray-CWQe9DMY.js} +2 -2
- package/dist/{ts-DkjQJTcB.js → ts-BuFWTNL9.js} +13 -4
- package/dist/{versionChecker-xqnqyGKE.js → versionChecker-CpNUvHBx.js} +2 -2
- package/lab/ui/index.html +1577 -0
- package/lab/ui/room-client.js +449 -0
- package/package.json +4 -2
- package/ts/index.ts +16 -0
- package/ts/serve.ts +437 -279
- package/ts/share.ts +20 -16
- package/ts/subcommands.ts +6 -0
- package/dist/SUPPORTED_CLIS-G8izHOJP.js +0 -8
- package/dist/pidStore-iJY3JFTn.js +0 -5
- package/dist/serve-CuAPBK4y.js +0 -425
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
import "./ts-BuFWTNL9.js";
|
|
2
|
+
import "./logger-B9h0djqx.js";
|
|
3
|
+
import "./versionChecker-CpNUvHBx.js";
|
|
4
|
+
import "./pidStore-DBjlqzo8.js";
|
|
5
|
+
import "./globalPidIndex-yVd3mbsV.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-C0a9K6I5.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-D4Muugfr.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 serveCmd = [
|
|
64
|
+
"ay",
|
|
65
|
+
"serve",
|
|
66
|
+
...args
|
|
67
|
+
].join(" ");
|
|
68
|
+
const code = await Bun.spawn([
|
|
69
|
+
oxmgrBin,
|
|
70
|
+
"start",
|
|
71
|
+
serveCmd,
|
|
72
|
+
"--name",
|
|
73
|
+
DAEMON_NAME,
|
|
74
|
+
"--restart",
|
|
75
|
+
"always"
|
|
76
|
+
], { stdio: [
|
|
77
|
+
"ignore",
|
|
78
|
+
"inherit",
|
|
79
|
+
"inherit"
|
|
80
|
+
] }).exited;
|
|
81
|
+
if (code === 0) {
|
|
82
|
+
const portM = /--port[=\s](\d+)/.exec(args.join(" "));
|
|
83
|
+
const port = portM ? Number(portM[1]) : DEFAULT_PORT;
|
|
84
|
+
process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
|
|
85
|
+
process.stdout.write(`token: ${token}\n\n`);
|
|
86
|
+
process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
|
|
87
|
+
process.stdout.write(` ay remote add <alias> http://${token}@<host>:${port}\n`);
|
|
88
|
+
process.stdout.write(` ay serve logs # view server logs\n`);
|
|
89
|
+
process.stdout.write(` ay serve uninstall # remove daemon\n`);
|
|
90
|
+
if (args.some((a) => a.startsWith("--webrtc") || a.startsWith("--share"))) process.stdout.write(`\nthe WebRTC share link is printed by the daemon — see: ay serve logs\n`);
|
|
91
|
+
}
|
|
92
|
+
return code ?? 1;
|
|
93
|
+
}
|
|
94
|
+
if (sub === "uninstall") return await Bun.spawn([
|
|
95
|
+
oxmgrBin,
|
|
96
|
+
"delete",
|
|
97
|
+
DAEMON_NAME
|
|
98
|
+
], { stdio: [
|
|
99
|
+
"ignore",
|
|
100
|
+
"inherit",
|
|
101
|
+
"inherit"
|
|
102
|
+
] }).exited ?? 1;
|
|
103
|
+
if (sub === "logs") return await Bun.spawn([
|
|
104
|
+
oxmgrBin,
|
|
105
|
+
"logs",
|
|
106
|
+
DAEMON_NAME,
|
|
107
|
+
...args
|
|
108
|
+
], { stdio: [
|
|
109
|
+
"ignore",
|
|
110
|
+
"inherit",
|
|
111
|
+
"inherit"
|
|
112
|
+
] }).exited ?? 1;
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
async function cmdServe(rest) {
|
|
116
|
+
if (rest.includes("-h") || rest.includes("--help")) {
|
|
117
|
+
process.stdout.write(`Usage: ay serve [options]
|
|
118
|
+
|
|
119
|
+
Start an API server (HTTP and/or WebRTC) so browsers and remote machines
|
|
120
|
+
can list/tail/send agents.
|
|
121
|
+
|
|
122
|
+
Modes (default: --http):
|
|
123
|
+
--http HTTP API + web console on --port; no WebRTC
|
|
124
|
+
--webrtc [URL] Share over WebRTC (bare flag mints a room+link on
|
|
125
|
+
agent-yes.com, or pass webrtc://room:token@host).
|
|
126
|
+
Alone it needs NO port — combine with --http for both.
|
|
127
|
+
--share [URL] Legacy alias for --http --webrtc
|
|
128
|
+
|
|
129
|
+
Options:
|
|
130
|
+
--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 --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`);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
const sub = rest[0];
|
|
134
|
+
if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
|
|
135
|
+
const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
|
|
136
|
+
type: "number",
|
|
137
|
+
default: DEFAULT_PORT,
|
|
138
|
+
description: "Port to listen on"
|
|
139
|
+
}).option("host", {
|
|
140
|
+
type: "string",
|
|
141
|
+
default: "127.0.0.1",
|
|
142
|
+
description: "Interface to bind (use 0.0.0.0 to expose)"
|
|
143
|
+
}).option("token", {
|
|
144
|
+
type: "string",
|
|
145
|
+
description: "Auth token (auto-generated if omitted)"
|
|
146
|
+
}).option("tls-cert", {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "TLS certificate file (PEM)"
|
|
149
|
+
}).option("tls-key", {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "TLS private key file (PEM)"
|
|
152
|
+
}).option("http", {
|
|
153
|
+
type: "boolean",
|
|
154
|
+
description: "Serve the HTTP API + web console on --port (default mode)"
|
|
155
|
+
}).option("webrtc", {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Share over WebRTC: bare flag mints a room+link, or pass webrtc://room:token@host. Needs no port unless combined with --http"
|
|
158
|
+
}).option("share", {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Legacy alias for --http --webrtc"
|
|
161
|
+
}).option("daemon", {
|
|
162
|
+
alias: "d",
|
|
163
|
+
type: "boolean",
|
|
164
|
+
default: false,
|
|
165
|
+
description: "Install as a background daemon via oxmgr (same as: ay serve install <flags>)"
|
|
166
|
+
}).option("allow-spawn", {
|
|
167
|
+
type: "boolean",
|
|
168
|
+
default: false,
|
|
169
|
+
description: "Deprecated no-op — the console can always spawn agents"
|
|
170
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
171
|
+
const port = argv.port ?? DEFAULT_PORT;
|
|
172
|
+
const host = argv.host ?? "127.0.0.1";
|
|
173
|
+
const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
|
|
174
|
+
const certPath = typeof argv["tls-cert"] === "string" ? argv["tls-cert"] : void 0;
|
|
175
|
+
const keyPath = typeof argv["tls-key"] === "string" ? argv["tls-key"] : void 0;
|
|
176
|
+
if (certPath && !keyPath || !certPath && keyPath) {
|
|
177
|
+
process.stderr.write("ay serve: --tls-cert and --tls-key must both be provided\n");
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
const useHttps = !!(certPath && keyPath);
|
|
181
|
+
const scheme = useHttps ? "https" : "http";
|
|
182
|
+
const wantWebrtc = argv.webrtc !== void 0 || argv.share !== void 0;
|
|
183
|
+
const wantHttp = argv.http === true || argv.share !== void 0 || argv.webrtc === void 0;
|
|
184
|
+
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");
|
|
185
|
+
const token = await loadOrCreateToken(tokenFlag);
|
|
186
|
+
const titleCache = /* @__PURE__ */ new Map();
|
|
187
|
+
const logTitle = async (logFile) => {
|
|
188
|
+
if (!logFile) return null;
|
|
189
|
+
try {
|
|
190
|
+
const fh = await open(logFile, "r");
|
|
191
|
+
try {
|
|
192
|
+
const { size, mtimeMs } = await fh.stat();
|
|
193
|
+
const hit = titleCache.get(logFile);
|
|
194
|
+
if (hit && hit.size === size && hit.mtimeMs === mtimeMs) return hit.title;
|
|
195
|
+
const len = Math.min(size, 65536);
|
|
196
|
+
const buf = Buffer.allocUnsafe(len);
|
|
197
|
+
const { bytesRead } = await fh.read(buf, 0, len, size - len);
|
|
198
|
+
const text = buf.toString("utf-8", 0, bytesRead);
|
|
199
|
+
const oscTitleRe = /\x1b\][02];([^\x07\x1b]*)(?:\x07|\x1b\\)/g;
|
|
200
|
+
let title = null;
|
|
201
|
+
for (let m; m = oscTitleRe.exec(text);) if (m[1].trim()) title = m[1].trim();
|
|
202
|
+
titleCache.set(logFile, {
|
|
203
|
+
size,
|
|
204
|
+
mtimeMs,
|
|
205
|
+
title
|
|
206
|
+
});
|
|
207
|
+
return title;
|
|
208
|
+
} finally {
|
|
209
|
+
await fh.close();
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
const apiFetch = async (req) => {
|
|
216
|
+
if (!checkAuth(req, token)) return new Response("Unauthorized", { status: 401 });
|
|
217
|
+
const url = new URL(req.url);
|
|
218
|
+
const p = url.pathname;
|
|
219
|
+
if (req.method === "GET" && p === "/api/ls") {
|
|
220
|
+
const keyword = url.searchParams.get("keyword") ?? void 0;
|
|
221
|
+
const opts = defaultOpts({
|
|
222
|
+
all: url.searchParams.get("all") === "1",
|
|
223
|
+
active: url.searchParams.get("active") === "1"
|
|
224
|
+
});
|
|
225
|
+
try {
|
|
226
|
+
const records = await listRecords(keyword, opts);
|
|
227
|
+
const withTitles = await Promise.all(records.map(async (r) => ({
|
|
228
|
+
...r,
|
|
229
|
+
title: await logTitle(r.log_file)
|
|
230
|
+
})));
|
|
231
|
+
return Response.json(withTitles);
|
|
232
|
+
} catch (e) {
|
|
233
|
+
return new Response(e.message, { status: 500 });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (req.method === "GET" && p === "/api/notes") {
|
|
237
|
+
const notes = await readNotes();
|
|
238
|
+
return Response.json(Object.fromEntries(notes));
|
|
239
|
+
}
|
|
240
|
+
const statusM = /^\/api\/status\/(.+)$/.exec(p);
|
|
241
|
+
if (req.method === "GET" && statusM) {
|
|
242
|
+
const keyword = decodeURIComponent(statusM[1]);
|
|
243
|
+
try {
|
|
244
|
+
const snap = await snapshotStatus(await resolveOne(keyword, defaultOpts({ all: true })));
|
|
245
|
+
return Response.json(snap);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
return new Response(e.message, { status: 404 });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const readM = /^\/api\/read\/(.+)$/.exec(p);
|
|
251
|
+
if (req.method === "GET" && readM) {
|
|
252
|
+
const keyword = decodeURIComponent(readM[1]);
|
|
253
|
+
const mode = url.searchParams.get("mode") ?? "tail";
|
|
254
|
+
const n = parseInt(url.searchParams.get("n") ?? "96", 10) || 96;
|
|
255
|
+
try {
|
|
256
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
257
|
+
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
258
|
+
const text = await renderRawLog(await readFile(record.log_file), {
|
|
259
|
+
mode,
|
|
260
|
+
n
|
|
261
|
+
});
|
|
262
|
+
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
|
263
|
+
} catch (e) {
|
|
264
|
+
return new Response(e.message, { status: 404 });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const sizeM = /^\/api\/size\/(.+)$/.exec(p);
|
|
268
|
+
if (req.method === "GET" && sizeM) {
|
|
269
|
+
const keyword = decodeURIComponent(sizeM[1]);
|
|
270
|
+
try {
|
|
271
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
272
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
273
|
+
let cols = null;
|
|
274
|
+
let rows = null;
|
|
275
|
+
try {
|
|
276
|
+
const [c = 0, r = 0] = (await readFile(path.join(ayHome, "ptysize", String(record.pid)), "utf-8")).trim().split(/\s+/).map(Number);
|
|
277
|
+
if (c > 0 && r > 0) {
|
|
278
|
+
cols = c;
|
|
279
|
+
rows = r;
|
|
280
|
+
}
|
|
281
|
+
} catch {}
|
|
282
|
+
return Response.json({
|
|
283
|
+
pid: record.pid,
|
|
284
|
+
cols,
|
|
285
|
+
rows
|
|
286
|
+
});
|
|
287
|
+
} catch (e) {
|
|
288
|
+
return new Response(e.message, { status: 404 });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const tailM = /^\/api\/tail\/(.+)$/.exec(p);
|
|
292
|
+
if (req.method === "GET" && tailM) {
|
|
293
|
+
const keyword = decodeURIComponent(tailM[1]);
|
|
294
|
+
const raw = url.searchParams.get("raw") === "1";
|
|
295
|
+
try {
|
|
296
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
297
|
+
if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
|
|
298
|
+
const logPath = record.log_file;
|
|
299
|
+
const stream = new ReadableStream({ async start(ctrl) {
|
|
300
|
+
const enc = new TextEncoder();
|
|
301
|
+
const send = (text) => ctrl.enqueue(enc.encode(`data: ${JSON.stringify(text)}\n\n`));
|
|
302
|
+
const ping = () => ctrl.enqueue(enc.encode(": ping\n\n"));
|
|
303
|
+
const initBuf = await readFile(logPath).catch(() => Buffer.alloc(0));
|
|
304
|
+
if (raw) send(new TextDecoder().decode(initBuf.slice(Math.max(0, initBuf.length - 65536))));
|
|
305
|
+
else send(await renderRawLog(initBuf, {
|
|
306
|
+
mode: "tail",
|
|
307
|
+
n: 96
|
|
308
|
+
}));
|
|
309
|
+
let offset = initBuf.length;
|
|
310
|
+
let closed = false;
|
|
311
|
+
const heartbeat = setInterval(() => {
|
|
312
|
+
if (closed) {
|
|
313
|
+
clearInterval(heartbeat);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
ping();
|
|
317
|
+
}, 15e3);
|
|
318
|
+
const ansiRe = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
319
|
+
const ctrlRe = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
320
|
+
const fh = await open(logPath, "r").catch(() => null);
|
|
321
|
+
let reading = false;
|
|
322
|
+
const flush = async () => {
|
|
323
|
+
if (closed || reading || !fh) return;
|
|
324
|
+
reading = true;
|
|
325
|
+
try {
|
|
326
|
+
const { size } = await fh.stat();
|
|
327
|
+
if (size < offset) offset = size;
|
|
328
|
+
if (size > offset) {
|
|
329
|
+
const len = size - offset;
|
|
330
|
+
const buf = Buffer.allocUnsafe(len);
|
|
331
|
+
const { bytesRead } = await fh.read(buf, 0, len, offset);
|
|
332
|
+
offset += bytesRead;
|
|
333
|
+
const chunk = buf.subarray(0, bytesRead);
|
|
334
|
+
if (raw) send(new TextDecoder().decode(chunk));
|
|
335
|
+
else {
|
|
336
|
+
const text = new TextDecoder().decode(chunk).replace(ansiRe, "").replace(ctrlRe, "");
|
|
337
|
+
if (text.trim()) send(text.trimStart());
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch {} finally {
|
|
341
|
+
reading = false;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
let watcher = null;
|
|
345
|
+
try {
|
|
346
|
+
watcher = watch(logPath, () => void flush());
|
|
347
|
+
} catch {}
|
|
348
|
+
const poller = setInterval(() => void flush(), 60);
|
|
349
|
+
req.signal.addEventListener("abort", () => {
|
|
350
|
+
closed = true;
|
|
351
|
+
clearInterval(heartbeat);
|
|
352
|
+
clearInterval(poller);
|
|
353
|
+
try {
|
|
354
|
+
watcher?.close();
|
|
355
|
+
} catch {}
|
|
356
|
+
fh?.close().catch(() => {});
|
|
357
|
+
try {
|
|
358
|
+
ctrl.close();
|
|
359
|
+
} catch {}
|
|
360
|
+
});
|
|
361
|
+
} });
|
|
362
|
+
return new Response(stream, { headers: {
|
|
363
|
+
"Content-Type": "text/event-stream",
|
|
364
|
+
"Cache-Control": "no-cache",
|
|
365
|
+
Connection: "keep-alive"
|
|
366
|
+
} });
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return new Response(e.message, { status: 404 });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (req.method === "POST" && p === "/api/send") {
|
|
372
|
+
let body;
|
|
373
|
+
try {
|
|
374
|
+
body = await req.json();
|
|
375
|
+
} catch {
|
|
376
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
377
|
+
}
|
|
378
|
+
const { keyword, msg = "", code = "enter" } = body;
|
|
379
|
+
if (!keyword || typeof keyword !== "string") return new Response("missing keyword", { status: 400 });
|
|
380
|
+
try {
|
|
381
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
382
|
+
if (!record.fifo_file) return new Response(`pid ${record.pid}: no fifo_file`, { status: 409 });
|
|
383
|
+
const trailing = controlCodeFromName(code.toLowerCase());
|
|
384
|
+
if (msg && trailing) {
|
|
385
|
+
await writeToIpc(record.fifo_file, msg);
|
|
386
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
387
|
+
await writeToIpc(record.fifo_file, trailing);
|
|
388
|
+
} else await writeToIpc(record.fifo_file, msg + trailing);
|
|
389
|
+
return Response.json({
|
|
390
|
+
ok: true,
|
|
391
|
+
pid: record.pid
|
|
392
|
+
});
|
|
393
|
+
} catch (e) {
|
|
394
|
+
return new Response(e.message, { status: 404 });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const resizeM = /^\/api\/resize\/(.+)$/.exec(p);
|
|
398
|
+
if (req.method === "POST" && resizeM) {
|
|
399
|
+
const keyword = decodeURIComponent(resizeM[1]);
|
|
400
|
+
let body;
|
|
401
|
+
try {
|
|
402
|
+
body = await req.json();
|
|
403
|
+
} catch {
|
|
404
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
405
|
+
}
|
|
406
|
+
const cols = Math.max(1, Math.floor(Number(body.cols) || 0));
|
|
407
|
+
const rows = Math.max(1, Math.floor(Number(body.rows) || 0));
|
|
408
|
+
if (!cols || !rows) return new Response("missing cols/rows", { status: 400 });
|
|
409
|
+
try {
|
|
410
|
+
const record = await resolveOne(keyword, defaultOpts());
|
|
411
|
+
const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
412
|
+
const winsizeDir = path.join(ayHome, "winsize");
|
|
413
|
+
await mkdir(winsizeDir, { recursive: true });
|
|
414
|
+
await writeFile(path.join(winsizeDir, String(record.pid)), `${cols} ${rows} ${Date.now()}\n`);
|
|
415
|
+
try {
|
|
416
|
+
process.kill(record.pid, "SIGWINCH");
|
|
417
|
+
} catch {}
|
|
418
|
+
return Response.json({
|
|
419
|
+
ok: true,
|
|
420
|
+
pid: record.pid,
|
|
421
|
+
cols,
|
|
422
|
+
rows
|
|
423
|
+
});
|
|
424
|
+
} catch (e) {
|
|
425
|
+
return new Response(e.message, { status: 404 });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (req.method === "POST" && p === "/api/spawn") {
|
|
429
|
+
let body;
|
|
430
|
+
try {
|
|
431
|
+
body = await req.json();
|
|
432
|
+
} catch {
|
|
433
|
+
return new Response("invalid JSON body", { status: 400 });
|
|
434
|
+
}
|
|
435
|
+
const cli = String(body.cli ?? "claude");
|
|
436
|
+
if (!SUPPORTED_CLIS.includes(cli)) return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
437
|
+
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
438
|
+
const prompt = String(body.prompt ?? "");
|
|
439
|
+
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd})\n`);
|
|
440
|
+
try {
|
|
441
|
+
const child = Bun.spawn([
|
|
442
|
+
"ay",
|
|
443
|
+
cli,
|
|
444
|
+
...prompt ? ["--", prompt] : []
|
|
445
|
+
], {
|
|
446
|
+
cwd,
|
|
447
|
+
stdin: "ignore",
|
|
448
|
+
stdout: "ignore",
|
|
449
|
+
stderr: "ignore"
|
|
450
|
+
});
|
|
451
|
+
child.unref();
|
|
452
|
+
return Response.json({
|
|
453
|
+
ok: true,
|
|
454
|
+
pid: child.pid,
|
|
455
|
+
cli,
|
|
456
|
+
cwd
|
|
457
|
+
});
|
|
458
|
+
} catch (e) {
|
|
459
|
+
return new Response(e.message, { status: 500 });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return new Response("Not Found", { status: 404 });
|
|
463
|
+
};
|
|
464
|
+
const uiDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "lab", "ui");
|
|
465
|
+
const serveUiFile = async (name, type) => {
|
|
466
|
+
try {
|
|
467
|
+
const buf = await readFile(path.join(uiDir, name));
|
|
468
|
+
return new Response(buf, { headers: { "Content-Type": type } });
|
|
469
|
+
} catch {
|
|
470
|
+
return new Response("UI assets not found in this install — use the /api endpoints", { status: 404 });
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
const httpFetch = async (req) => {
|
|
474
|
+
const p = new URL(req.url).pathname;
|
|
475
|
+
if (req.method === "GET" && (p === "/" || p === "/index.html")) return serveUiFile("index.html", "text/html; charset=utf-8");
|
|
476
|
+
if (req.method === "GET" && p === "/room-client.js") return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
|
|
477
|
+
if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
|
|
478
|
+
return apiFetch(req);
|
|
479
|
+
};
|
|
480
|
+
const serverOpts = {
|
|
481
|
+
hostname: host,
|
|
482
|
+
port,
|
|
483
|
+
idleTimeout: 0,
|
|
484
|
+
fetch: httpFetch
|
|
485
|
+
};
|
|
486
|
+
if (useHttps) serverOpts.tls = {
|
|
487
|
+
cert: Bun.file(certPath),
|
|
488
|
+
key: Bun.file(keyPath)
|
|
489
|
+
};
|
|
490
|
+
let server = null;
|
|
491
|
+
if (wantHttp) {
|
|
492
|
+
try {
|
|
493
|
+
server = Bun.serve(serverOpts);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
if (e.code === "EADDRINUSE") {
|
|
496
|
+
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`);
|
|
497
|
+
return 1;
|
|
498
|
+
}
|
|
499
|
+
throw e;
|
|
500
|
+
}
|
|
501
|
+
const uiHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
502
|
+
process.stdout.write(`ay serve ${scheme}://${host}:${port}\n`);
|
|
503
|
+
process.stdout.write(`token: ${token}\n\n`);
|
|
504
|
+
process.stdout.write(`web console (token in the # is eaten on open):\n`);
|
|
505
|
+
process.stdout.write(` ${scheme}://${uiHost}:${port}/#k=${token}\n\n`);
|
|
506
|
+
process.stdout.write(`connect from another machine:\n`);
|
|
507
|
+
process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
|
|
508
|
+
process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
|
|
509
|
+
process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
|
|
510
|
+
process.stdout.write(`save as alias:\n`);
|
|
511
|
+
process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
|
|
512
|
+
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");
|
|
513
|
+
}
|
|
514
|
+
if (wantWebrtc) {
|
|
515
|
+
const webrtcVal = argv.webrtc ?? argv.share;
|
|
516
|
+
const shareUrl = typeof webrtcVal === "string" && webrtcVal.startsWith("webrtc://") ? webrtcVal : void 0;
|
|
517
|
+
try {
|
|
518
|
+
const { startShare } = await import("./share-D-r6y3xD.js");
|
|
519
|
+
const { link } = await startShare({
|
|
520
|
+
url: shareUrl,
|
|
521
|
+
localFetch: apiFetch,
|
|
522
|
+
apiToken: token
|
|
523
|
+
});
|
|
524
|
+
process.stdout.write(`${wantHttp ? "\n" : ""}shared over WebRTC — open this link (the token is eaten from the URL on open):\n ${link}\n\n`);
|
|
525
|
+
} catch (e) {
|
|
526
|
+
process.stderr.write(`ay serve --webrtc failed: ${e.message}\n`);
|
|
527
|
+
if (!wantHttp) return 1;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
process.stdout.write(`(Ctrl-C to stop)\n`);
|
|
531
|
+
await new Promise((resolve) => {
|
|
532
|
+
process.on("SIGINT", () => {
|
|
533
|
+
server?.stop();
|
|
534
|
+
resolve();
|
|
535
|
+
});
|
|
536
|
+
process.on("SIGTERM", () => {
|
|
537
|
+
server?.stop();
|
|
538
|
+
resolve();
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
//#endregion
|
|
545
|
+
export { cmdServe };
|
|
546
|
+
//# sourceMappingURL=serve-DPY37v0u.js.map
|
|
@@ -131,7 +131,7 @@ async function startShare(opts) {
|
|
|
131
131
|
const ac = new AbortController();
|
|
132
132
|
aborts.set(id, ac);
|
|
133
133
|
try {
|
|
134
|
-
const res = await
|
|
134
|
+
const res = await opts.localFetch(new Request(`http://ay.local${p}`, {
|
|
135
135
|
method,
|
|
136
136
|
headers: {
|
|
137
137
|
Authorization: `Bearer ${opts.apiToken}`,
|
|
@@ -139,7 +139,7 @@ async function startShare(opts) {
|
|
|
139
139
|
},
|
|
140
140
|
body: body ?? void 0,
|
|
141
141
|
signal: ac.signal
|
|
142
|
-
});
|
|
142
|
+
}));
|
|
143
143
|
send(dc, {
|
|
144
144
|
t: "res",
|
|
145
145
|
id,
|
|
@@ -181,4 +181,4 @@ async function startShare(opts) {
|
|
|
181
181
|
|
|
182
182
|
//#endregion
|
|
183
183
|
export { startShare };
|
|
184
|
-
//# sourceMappingURL=share-
|
|
184
|
+
//# sourceMappingURL=share-D-r6y3xD.js.map
|
|
@@ -131,6 +131,7 @@ const SUBCOMMANDS = new Set([
|
|
|
131
131
|
"restart",
|
|
132
132
|
"note",
|
|
133
133
|
"serve",
|
|
134
|
+
"setup",
|
|
134
135
|
"remote",
|
|
135
136
|
"help"
|
|
136
137
|
]);
|
|
@@ -162,9 +163,13 @@ async function runSubcommand(argv) {
|
|
|
162
163
|
case "restart": return await cmdRestart(rest);
|
|
163
164
|
case "note": return await cmdNote(rest);
|
|
164
165
|
case "serve": {
|
|
165
|
-
const { cmdServe } = await import("./serve-
|
|
166
|
+
const { cmdServe } = await import("./serve-DPY37v0u.js");
|
|
166
167
|
return cmdServe(rest);
|
|
167
168
|
}
|
|
169
|
+
case "setup": {
|
|
170
|
+
const { cmdSetup } = await import("./setup.ts");
|
|
171
|
+
return cmdSetup(rest);
|
|
172
|
+
}
|
|
168
173
|
case "remote": {
|
|
169
174
|
const { cmdRemote } = await import("./remotes-C9WMt5PY.js");
|
|
170
175
|
return cmdRemote(rest);
|
|
@@ -179,7 +184,7 @@ async function runSubcommand(argv) {
|
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
function cmdHelp() {
|
|
182
|
-
process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n\nRemote:\n ay serve [--port N] start HTTP API server (prints token)\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
|
|
187
|
+
process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n\nRemote:\n ay setup guided setup: pick a workspace, share to agent-yes.com\n ay serve [--port N] start HTTP API server (prints token)\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples at https://github.com/snomiao/agent-yes/tree/main/lab):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
|
|
183
188
|
return 0;
|
|
184
189
|
}
|
|
185
190
|
function matchKeyword(record, keyword) {
|
|
@@ -1447,4 +1452,4 @@ async function cmdStatus(rest) {
|
|
|
1447
1452
|
|
|
1448
1453
|
//#endregion
|
|
1449
1454
|
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-
|
|
1455
|
+
//# sourceMappingURL=subcommands-D4Muugfr.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-D4Muugfr.js";
|
|
5
5
|
|
|
6
6
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as getRunningAgentCount } from "./runningLock-
|
|
1
|
+
import { n as getRunningAgentCount } from "./runningLock-CJxsoGdb.js";
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
4
4
|
import { homedir } from "os";
|
|
@@ -175,4 +175,4 @@ async function startTray() {
|
|
|
175
175
|
|
|
176
176
|
//#endregion
|
|
177
177
|
export { ensureTray, startTray };
|
|
178
|
-
//# sourceMappingURL=tray-
|
|
178
|
+
//# sourceMappingURL=tray-CWQe9DMY.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-CpNUvHBx.js";
|
|
3
|
+
import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
|
|
4
|
+
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
|
|
5
5
|
import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
|
|
6
6
|
import { arch, platform } from "process";
|
|
7
7
|
import { execSync } from "child_process";
|
|
@@ -1423,10 +1423,19 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1423
1423
|
notifyWebhook("EXIT", `${exitReason} exitCode=${exitCode ?? "?"}`, workingDir).catch(() => null);
|
|
1424
1424
|
return pendingExitCode.resolve(exitCode);
|
|
1425
1425
|
});
|
|
1426
|
+
const writeCurrentPtysize = (cols, rows) => {
|
|
1427
|
+
const dir = path.join(agentYesHome(), "ptysize");
|
|
1428
|
+
mkdir(dir, { recursive: true }).then(() => writeFile(path.join(dir, String(process.pid)), `${cols} ${rows}\n`)).catch(() => null);
|
|
1429
|
+
};
|
|
1430
|
+
{
|
|
1431
|
+
const { cols, rows } = getTerminalDimensions();
|
|
1432
|
+
writeCurrentPtysize(cols, rows);
|
|
1433
|
+
}
|
|
1426
1434
|
process.stdout.on("resize", () => {
|
|
1427
1435
|
const { cols, rows } = getTerminalDimensions();
|
|
1428
1436
|
shell.resize(cols, rows);
|
|
1429
1437
|
xtermProxy.resize(cols, rows);
|
|
1438
|
+
writeCurrentPtysize(cols, rows);
|
|
1430
1439
|
});
|
|
1431
1440
|
const isStillWorkingQ = () => {
|
|
1432
1441
|
const rendered = xtermProxy.tail(24).replace(/\s+/g, " ");
|
|
@@ -1705,4 +1714,4 @@ function sleep(ms) {
|
|
|
1705
1714
|
|
|
1706
1715
|
//#endregion
|
|
1707
1716
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1708
|
-
//# sourceMappingURL=ts-
|
|
1717
|
+
//# sourceMappingURL=ts-BuFWTNL9.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.98.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-CpNUvHBx.js.map
|