agent-yes 1.85.0 → 1.87.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-CawnsTw2.js → SUPPORTED_CLIS-DfGbBLZy.js} +3 -3
- package/dist/cli.js +5 -5
- package/dist/index.js +2 -2
- package/dist/remotes-CFrho898.js +131 -0
- package/dist/remotes-kfUzk-JT.js +3 -0
- package/dist/serve-8dWQHSBu.js +303 -0
- package/dist/subcommands-B2lxwpQn.js +6 -0
- package/dist/{subcommands-BwWcA9uo.js → subcommands-BltyYaEs.js} +332 -7
- package/dist/{tray-CH_G7aXM.js → tray-DHuD0nEk.js} +1 -1
- package/dist/{ts-D0ddYVke.js → ts-CSMhtgoi.js} +2 -2
- package/dist/{versionChecker-ftOiNICT.js → versionChecker-gyE00XVe.js} +2 -2
- package/package.json +1 -1
- package/ts/remotes.ts +161 -0
- package/ts/serve.ts +373 -0
- package/ts/subcommands.spec.ts +103 -0
- package/ts/subcommands.ts +421 -10
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import "./logger-B9h0djqx.js";
|
|
2
1
|
import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
|
|
2
|
+
import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-CFrho898.js";
|
|
3
|
+
import ms from "ms";
|
|
3
4
|
import yargs from "yargs";
|
|
4
5
|
import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
5
6
|
import { homedir } from "os";
|
|
@@ -126,7 +127,9 @@ const SUBCOMMANDS = new Set([
|
|
|
126
127
|
"head",
|
|
127
128
|
"send",
|
|
128
129
|
"restart",
|
|
129
|
-
"note"
|
|
130
|
+
"note",
|
|
131
|
+
"serve",
|
|
132
|
+
"remote"
|
|
130
133
|
]);
|
|
131
134
|
const IDLE_THRESHOLD_MS = 60 * 1e3;
|
|
132
135
|
function isSubcommand(name) {
|
|
@@ -153,6 +156,14 @@ async function runSubcommand(argv) {
|
|
|
153
156
|
case "send": return await cmdSend(rest);
|
|
154
157
|
case "restart": return await cmdRestart(rest);
|
|
155
158
|
case "note": return await cmdNote(rest);
|
|
159
|
+
case "serve": {
|
|
160
|
+
const { cmdServe } = await import("./serve-8dWQHSBu.js");
|
|
161
|
+
return cmdServe(rest);
|
|
162
|
+
}
|
|
163
|
+
case "remote": {
|
|
164
|
+
const { cmdRemote } = await import("./remotes-kfUzk-JT.js");
|
|
165
|
+
return cmdRemote(rest);
|
|
166
|
+
}
|
|
156
167
|
default: return null;
|
|
157
168
|
}
|
|
158
169
|
} catch (err) {
|
|
@@ -199,6 +210,254 @@ async function resolveOne(keyword, opts) {
|
|
|
199
210
|
const lines = matches.slice(0, 10).map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`).join("\n");
|
|
200
211
|
throw new Error(`keyword "${keyword}" matched ${matches.length} agents — disambiguate by pid or pass --latest:\n${lines}`);
|
|
201
212
|
}
|
|
213
|
+
async function remoteGet(remote, pathname) {
|
|
214
|
+
return fetch(`${remote.url}${pathname}`, { headers: { Authorization: `Bearer ${remote.token}` } });
|
|
215
|
+
}
|
|
216
|
+
async function remotePost(remote, pathname, body) {
|
|
217
|
+
return fetch(`${remote.url}${pathname}`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: {
|
|
220
|
+
Authorization: `Bearer ${remote.token}`,
|
|
221
|
+
"Content-Type": "application/json"
|
|
222
|
+
},
|
|
223
|
+
body: JSON.stringify(body)
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
async function runRemoteLs(remote, opts) {
|
|
227
|
+
const params = new URLSearchParams();
|
|
228
|
+
if (remote.keyword) params.set("keyword", remote.keyword);
|
|
229
|
+
if (opts.all) params.set("all", "1");
|
|
230
|
+
if (opts.active) params.set("active", "1");
|
|
231
|
+
const res = await remoteGet(remote, `/api/ls?${params}`);
|
|
232
|
+
if (!res.ok) {
|
|
233
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
234
|
+
return 1;
|
|
235
|
+
}
|
|
236
|
+
const records = await res.json();
|
|
237
|
+
if (records.length === 0) {
|
|
238
|
+
process.stderr.write(remote.keyword ? `no agents matched "${remote.keyword}" on ${remote.url}\n` : `no running agents on ${remote.url}\n`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
process.stderr.write(`[remote ${remote.url}]\n`);
|
|
242
|
+
const termWidth = process.stdout.columns ?? 120;
|
|
243
|
+
const widths = {
|
|
244
|
+
pid: Math.max(3, ...records.map((r) => String(r.pid).length)),
|
|
245
|
+
cli: Math.max(3, ...records.map((r) => String(r.cli).length)),
|
|
246
|
+
status: Math.max(6, ...records.map((r) => String(r.status).length)),
|
|
247
|
+
cwd: Math.max(3, ...records.map((r) => String(r.cwd).length))
|
|
248
|
+
};
|
|
249
|
+
const fixedWidth = widths.pid + widths.cli + widths.status + widths.cwd + 8;
|
|
250
|
+
const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
|
|
251
|
+
const header = [
|
|
252
|
+
"PID".padEnd(widths.pid),
|
|
253
|
+
"CLI".padEnd(widths.cli),
|
|
254
|
+
"STATUS".padEnd(widths.status),
|
|
255
|
+
"CWD".padEnd(widths.cwd),
|
|
256
|
+
"PROMPT"
|
|
257
|
+
].join(" ") + "\n";
|
|
258
|
+
process.stdout.write(header);
|
|
259
|
+
for (const r of records) {
|
|
260
|
+
const label = r.prompt ? truncate(`→ ${r.prompt}`, promptBudget) : "";
|
|
261
|
+
process.stdout.write([
|
|
262
|
+
String(r.pid).padEnd(widths.pid),
|
|
263
|
+
String(r.cli).padEnd(widths.cli),
|
|
264
|
+
String(r.status).padEnd(widths.status),
|
|
265
|
+
String(r.cwd).padEnd(widths.cwd),
|
|
266
|
+
label
|
|
267
|
+
].join(" ") + "\n");
|
|
268
|
+
}
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
async function runRemoteRead(remote, mode, follow, n, reconnectTimeoutMs = 12e4) {
|
|
272
|
+
const keyword = remote.keyword ?? "";
|
|
273
|
+
if (!keyword) {
|
|
274
|
+
process.stderr.write("remote tail/cat/head requires a keyword (e.g. token@host:port:keyword)\n");
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
if (mode === "tail" && follow) {
|
|
278
|
+
const ac = new AbortController();
|
|
279
|
+
process.on("SIGINT", () => ac.abort());
|
|
280
|
+
const deadline = Date.now() + reconnectTimeoutMs;
|
|
281
|
+
let delay = 1e3;
|
|
282
|
+
let attempt = 0;
|
|
283
|
+
process.stderr.write(`[remote ${remote.url} ${keyword}]\nfollowing... (Ctrl-C to stop, timeout: ${Math.round(reconnectTimeoutMs / 1e3)}s)\n`);
|
|
284
|
+
while (!ac.signal.aborted) try {
|
|
285
|
+
const res = await fetch(`${remote.url}/api/tail/${encodeURIComponent(keyword)}`, {
|
|
286
|
+
headers: {
|
|
287
|
+
Authorization: `Bearer ${remote.token}`,
|
|
288
|
+
Accept: "text/event-stream"
|
|
289
|
+
},
|
|
290
|
+
signal: ac.signal
|
|
291
|
+
});
|
|
292
|
+
if (!res.ok) {
|
|
293
|
+
if (res.status === 401 || res.status === 404) {
|
|
294
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
297
|
+
throw new Error(`HTTP ${res.status}`);
|
|
298
|
+
}
|
|
299
|
+
if (attempt > 0) process.stderr.write("remote: reconnected\n");
|
|
300
|
+
delay = 1e3;
|
|
301
|
+
const reader = res.body.getReader();
|
|
302
|
+
const dec = new TextDecoder();
|
|
303
|
+
let buf = "";
|
|
304
|
+
while (true) {
|
|
305
|
+
const { done, value } = await reader.read();
|
|
306
|
+
if (done) break;
|
|
307
|
+
buf += dec.decode(value, { stream: true });
|
|
308
|
+
const lines = buf.split("\n");
|
|
309
|
+
buf = lines.pop() ?? "";
|
|
310
|
+
for (const line of lines) {
|
|
311
|
+
if (!line.startsWith("data: ")) continue;
|
|
312
|
+
try {
|
|
313
|
+
const text = JSON.parse(line.slice(6));
|
|
314
|
+
process.stdout.write(text);
|
|
315
|
+
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
316
|
+
} catch {}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
} catch (e) {
|
|
321
|
+
if (e.name === "AbortError" || ac.signal.aborted) return 0;
|
|
322
|
+
if (Date.now() >= deadline) {
|
|
323
|
+
process.stderr.write(`remote: timeout after ${Math.round(reconnectTimeoutMs / 1e3)}s, giving up\n`);
|
|
324
|
+
return 1;
|
|
325
|
+
}
|
|
326
|
+
process.stderr.write(`remote: disconnected (${e.message}), retrying in ${delay / 1e3}s…\n`);
|
|
327
|
+
await new Promise((resolve, reject) => {
|
|
328
|
+
const t = setTimeout(resolve, delay);
|
|
329
|
+
ac.signal.addEventListener("abort", () => {
|
|
330
|
+
clearTimeout(t);
|
|
331
|
+
reject(/* @__PURE__ */ new Error("abort"));
|
|
332
|
+
});
|
|
333
|
+
}).catch(() => {});
|
|
334
|
+
if (ac.signal.aborted) return 0;
|
|
335
|
+
delay = Math.min(delay * 2, 3e4);
|
|
336
|
+
attempt++;
|
|
337
|
+
}
|
|
338
|
+
return 0;
|
|
339
|
+
}
|
|
340
|
+
const params = new URLSearchParams({
|
|
341
|
+
mode,
|
|
342
|
+
n: String(n)
|
|
343
|
+
});
|
|
344
|
+
const res = await remoteGet(remote, `/api/read/${encodeURIComponent(keyword)}?${params}`);
|
|
345
|
+
if (!res.ok) {
|
|
346
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
const text = await res.text();
|
|
350
|
+
process.stderr.write(`[remote ${remote.url} ${keyword}]\n`);
|
|
351
|
+
process.stdout.write(text);
|
|
352
|
+
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
async function runRemoteSend(remote, msg, code) {
|
|
356
|
+
const keyword = remote.keyword ?? "";
|
|
357
|
+
if (!keyword) {
|
|
358
|
+
process.stderr.write("remote send requires a keyword (e.g. token@host:port:keyword)\n");
|
|
359
|
+
return 1;
|
|
360
|
+
}
|
|
361
|
+
const res = await remotePost(remote, "/api/send", {
|
|
362
|
+
keyword,
|
|
363
|
+
msg,
|
|
364
|
+
code
|
|
365
|
+
});
|
|
366
|
+
if (!res.ok) {
|
|
367
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
const data = await res.json();
|
|
371
|
+
process.stdout.write(`sent to remote pid ${data.pid} (${remote.url} ${keyword})\n`);
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
374
|
+
async function runRemoteStatus(remote) {
|
|
375
|
+
const keyword = remote.keyword ?? "";
|
|
376
|
+
if (!keyword) {
|
|
377
|
+
process.stderr.write("remote status requires a keyword (e.g. token@host:port:keyword)\n");
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
380
|
+
const res = await remoteGet(remote, `/api/status/${encodeURIComponent(keyword)}`);
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
383
|
+
return 1;
|
|
384
|
+
}
|
|
385
|
+
process.stdout.write(JSON.stringify(await res.json(), null, 2) + "\n");
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
388
|
+
async function fetchRemoteRecordsRaw(url, token, opts) {
|
|
389
|
+
const params = new URLSearchParams();
|
|
390
|
+
if (opts.all) params.set("all", "1");
|
|
391
|
+
if (opts.active) params.set("active", "1");
|
|
392
|
+
if (opts.keyword) params.set("keyword", opts.keyword);
|
|
393
|
+
try {
|
|
394
|
+
const res = await fetch(`${url}/api/ls?${params}`, {
|
|
395
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
396
|
+
signal: AbortSignal.timeout(5e3)
|
|
397
|
+
});
|
|
398
|
+
if (!res.ok) return [];
|
|
399
|
+
return await res.json();
|
|
400
|
+
} catch {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function runAllRemotesLs(opts) {
|
|
405
|
+
const remotes = await readRemotes();
|
|
406
|
+
const localOpts = {
|
|
407
|
+
all: opts.all,
|
|
408
|
+
active: opts.active,
|
|
409
|
+
json: true,
|
|
410
|
+
latest: false,
|
|
411
|
+
cwdScope: null
|
|
412
|
+
};
|
|
413
|
+
const [localResult, ...remoteResults] = await Promise.allSettled([listRecords(opts.keyword, localOpts).then((recs) => ({
|
|
414
|
+
host: "local",
|
|
415
|
+
records: recs
|
|
416
|
+
})), ...Array.from(remotes.entries()).map(([alias, cfg]) => fetchRemoteRecordsRaw(cfg.url, cfg.token, opts).then((records) => ({
|
|
417
|
+
host: alias,
|
|
418
|
+
records
|
|
419
|
+
})))]);
|
|
420
|
+
const rows = [];
|
|
421
|
+
if (localResult.status === "fulfilled") for (const r of localResult.value.records) rows.push({
|
|
422
|
+
host: "local",
|
|
423
|
+
rec: r
|
|
424
|
+
});
|
|
425
|
+
for (const res of remoteResults) if (res.status === "fulfilled") for (const r of res.value.records) rows.push({
|
|
426
|
+
host: res.value.host,
|
|
427
|
+
rec: r
|
|
428
|
+
});
|
|
429
|
+
if (rows.length === 0) {
|
|
430
|
+
process.stderr.write("no running agents\n");
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
const termWidth = process.stdout.columns ?? 120;
|
|
434
|
+
const hostW = Math.max(4, ...rows.map((r) => r.host.length));
|
|
435
|
+
const pidW = Math.max(3, ...rows.map((r) => String(r.rec.pid).length));
|
|
436
|
+
const cliW = Math.max(3, ...rows.map((r) => String(r.rec.cli).length));
|
|
437
|
+
const statusW = Math.max(6, ...rows.map((r) => String(r.rec.status).length));
|
|
438
|
+
const cwdW = Math.max(3, ...rows.map((r) => shortenPath(String(r.rec.cwd)).length));
|
|
439
|
+
const promptBudget = Math.max(20, termWidth - hostW - pidW - cliW - statusW - cwdW - 10 - 1);
|
|
440
|
+
process.stdout.write([
|
|
441
|
+
"HOST".padEnd(hostW),
|
|
442
|
+
"PID".padEnd(pidW),
|
|
443
|
+
"CLI".padEnd(cliW),
|
|
444
|
+
"STATUS".padEnd(statusW),
|
|
445
|
+
"CWD".padEnd(cwdW),
|
|
446
|
+
"PROMPT"
|
|
447
|
+
].join(" ") + "\n");
|
|
448
|
+
for (const { host, rec } of rows) {
|
|
449
|
+
const label = rec.prompt ? truncate(`→ ${rec.prompt}`, promptBudget) : "";
|
|
450
|
+
process.stdout.write([
|
|
451
|
+
host.padEnd(hostW),
|
|
452
|
+
String(rec.pid).padEnd(pidW),
|
|
453
|
+
String(rec.cli).padEnd(cliW),
|
|
454
|
+
String(rec.status).padEnd(statusW),
|
|
455
|
+
shortenPath(String(rec.cwd)).padEnd(cwdW),
|
|
456
|
+
label
|
|
457
|
+
].join(" ") + "\n");
|
|
458
|
+
}
|
|
459
|
+
return 0;
|
|
460
|
+
}
|
|
202
461
|
async function cmdLs(rest) {
|
|
203
462
|
const y = yargs(rest).usage("Usage: ay ls [keyword] [options]\n ay list [keyword] [options]\n ay ps [keyword] [options]\n\nList running agents. Optionally filter by keyword (pid, cwd substring, or prompt substring).").option("all", {
|
|
204
463
|
type: "boolean",
|
|
@@ -219,18 +478,34 @@ async function cmdLs(rest) {
|
|
|
219
478
|
}).option("cwd", {
|
|
220
479
|
type: "string",
|
|
221
480
|
description: "Restrict to agents whose cwd starts with dir"
|
|
481
|
+
}).option("all-remotes", {
|
|
482
|
+
type: "boolean",
|
|
483
|
+
default: false,
|
|
484
|
+
description: "Include agents from all configured remotes (remotes.yaml)"
|
|
222
485
|
}).option("help", {
|
|
223
486
|
alias: "h",
|
|
224
487
|
type: "boolean",
|
|
225
488
|
default: false,
|
|
226
489
|
description: "Show this help"
|
|
227
|
-
}).example("ay ls", "list running agents").example("ay ls --all", "include exited agents").example("ay ls --json", "machine-readable output").example("ay ls symval", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
|
|
490
|
+
}).example("ay ls", "list running agents").example("ay ls --all-remotes", "include all configured remote machines").example("ay ls --all", "include exited agents").example("ay ls --json", "machine-readable output").example("ay ls symval", "filter by cwd/prompt keyword").help(false).version(false).exitProcess(false);
|
|
228
491
|
const argv = await y.parseAsync();
|
|
229
492
|
if (argv.help || argv.h) {
|
|
230
493
|
process.stdout.write(await y.getHelp() + "\n");
|
|
231
494
|
return 0;
|
|
232
495
|
}
|
|
496
|
+
if (argv["all-remotes"]) return runAllRemotesLs({
|
|
497
|
+
all: argv.all,
|
|
498
|
+
active: argv.active,
|
|
499
|
+
keyword: argv._[0] !== void 0 ? String(argv._[0]) : void 0
|
|
500
|
+
});
|
|
233
501
|
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
502
|
+
if (keyword) {
|
|
503
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
504
|
+
if (remote) return runRemoteLs(remote, {
|
|
505
|
+
all: argv.all,
|
|
506
|
+
active: argv.active
|
|
507
|
+
});
|
|
508
|
+
}
|
|
234
509
|
const opts = {
|
|
235
510
|
all: argv.all,
|
|
236
511
|
active: argv.active,
|
|
@@ -309,6 +584,7 @@ async function cmdLs(rest) {
|
|
|
309
584
|
if (alive) {
|
|
310
585
|
hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
|
|
311
586
|
hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
|
|
587
|
+
hints.push(` ay status ${alive.pid} --wait-idle # block until state == idle\n`);
|
|
312
588
|
hints.push(` ay tail ${alive.pid} # view latest output\n`);
|
|
313
589
|
hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
|
|
314
590
|
hints.push(` ay send ${alive.pid} "next: ..." # send a prompt (keyword: pid, cwd, or prompt substring)\n`);
|
|
@@ -360,6 +636,10 @@ async function cmdRead(rest, { mode }) {
|
|
|
360
636
|
}).option("cwd", {
|
|
361
637
|
type: "string",
|
|
362
638
|
description: "Restrict to agents under this dir"
|
|
639
|
+
}).option("reconnect-timeout", {
|
|
640
|
+
type: "number",
|
|
641
|
+
default: 120,
|
|
642
|
+
description: "Seconds before giving up reconnecting remote SSE (default: 120)"
|
|
363
643
|
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
364
644
|
const opts = {
|
|
365
645
|
all: argv.all,
|
|
@@ -369,6 +649,13 @@ async function cmdRead(rest, { mode }) {
|
|
|
369
649
|
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
370
650
|
};
|
|
371
651
|
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
652
|
+
if (keyword) {
|
|
653
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
654
|
+
const nFlag2 = argv.n;
|
|
655
|
+
const n2 = nFlag2 !== void 0 && Number.isFinite(nFlag2) && nFlag2 > 0 ? Math.floor(nFlag2) : mode === "cat" ? 0 : 96;
|
|
656
|
+
const reconnectTimeoutMs = (argv["reconnect-timeout"] ?? 120) * 1e3;
|
|
657
|
+
if (remote) return runRemoteRead(remote, mode, argv.follow, n2, reconnectTimeoutMs);
|
|
658
|
+
}
|
|
372
659
|
const follow = argv.follow;
|
|
373
660
|
const nFlag = argv.n;
|
|
374
661
|
const n = nFlag !== void 0 && Number.isFinite(nFlag) && nFlag > 0 ? Math.floor(nFlag) : mode === "cat" ? 0 : 96;
|
|
@@ -555,7 +842,12 @@ async function cmdSend(rest) {
|
|
|
555
842
|
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
556
843
|
const rawMessage = argv._.slice(1).map(String).join(" ");
|
|
557
844
|
if (!keyword) throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
558
|
-
const
|
|
845
|
+
const codeName = argv.code.toLowerCase();
|
|
846
|
+
{
|
|
847
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
848
|
+
if (remote) return runRemoteSend(remote, rawMessage, codeName);
|
|
849
|
+
}
|
|
850
|
+
const trailing = controlCodeFromName(codeName);
|
|
559
851
|
const record = await resolveOne(keyword, opts);
|
|
560
852
|
const fifoPath = record.fifo_file;
|
|
561
853
|
if (!fifoPath) throw new Error(`pid ${record.pid}: no fifo_file recorded — agent was not started with --stdpush (or was spawned by Rust which doesn't yet support FIFO IPC; see ROADMAP item 10)`);
|
|
@@ -722,6 +1014,13 @@ async function cmdStatus(rest) {
|
|
|
722
1014
|
type: "boolean",
|
|
723
1015
|
default: false,
|
|
724
1016
|
description: "Stream changes as JSON"
|
|
1017
|
+
}).option("wait-idle", {
|
|
1018
|
+
type: "boolean",
|
|
1019
|
+
default: false,
|
|
1020
|
+
description: "Block until state == idle. Exit 0 idle, 1 stopped, 2 timeout"
|
|
1021
|
+
}).option("timeout", {
|
|
1022
|
+
type: "string",
|
|
1023
|
+
description: "Timeout for --wait-idle (e.g. 30s, 5m). Default: no timeout"
|
|
725
1024
|
}).option("interval", {
|
|
726
1025
|
type: "number",
|
|
727
1026
|
default: 2,
|
|
@@ -742,10 +1041,17 @@ async function cmdStatus(rest) {
|
|
|
742
1041
|
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
743
1042
|
};
|
|
744
1043
|
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
745
|
-
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--
|
|
1044
|
+
if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait-idle] [--timeout=Ns]");
|
|
1045
|
+
{
|
|
1046
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
1047
|
+
if (remote) return runRemoteStatus(remote);
|
|
1048
|
+
}
|
|
746
1049
|
const watch = argv.watch;
|
|
1050
|
+
const waitIdle = argv["wait-idle"];
|
|
747
1051
|
const intervalFlag = argv.interval;
|
|
748
1052
|
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1e3);
|
|
1053
|
+
const timeoutMs = typeof argv.timeout === "string" && argv.timeout.length > 0 ? ms(argv.timeout) ?? NaN : null;
|
|
1054
|
+
if (timeoutMs !== null && !Number.isFinite(timeoutMs)) throw new Error(`invalid --timeout value: ${argv.timeout}`);
|
|
749
1055
|
const record = await resolveOne(keyword, opts);
|
|
750
1056
|
const emit = (snap, ts) => {
|
|
751
1057
|
const out = ts !== void 0 ? {
|
|
@@ -754,6 +1060,25 @@ async function cmdStatus(rest) {
|
|
|
754
1060
|
} : snap;
|
|
755
1061
|
process.stdout.write(JSON.stringify(out) + "\n");
|
|
756
1062
|
};
|
|
1063
|
+
if (waitIdle) {
|
|
1064
|
+
const startedAt = Date.now();
|
|
1065
|
+
for (;;) {
|
|
1066
|
+
const snap = await snapshotStatus(record);
|
|
1067
|
+
if (snap.state === "idle") {
|
|
1068
|
+
emit(snap);
|
|
1069
|
+
return 0;
|
|
1070
|
+
}
|
|
1071
|
+
if (snap.state === "stopped") {
|
|
1072
|
+
emit(snap);
|
|
1073
|
+
return 1;
|
|
1074
|
+
}
|
|
1075
|
+
if (timeoutMs !== null && Date.now() - startedAt >= timeoutMs) {
|
|
1076
|
+
emit(snap);
|
|
1077
|
+
return 2;
|
|
1078
|
+
}
|
|
1079
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
757
1082
|
if (!watch) {
|
|
758
1083
|
emit(await snapshotStatus(record));
|
|
759
1084
|
return 0;
|
|
@@ -783,5 +1108,5 @@ async function cmdStatus(rest) {
|
|
|
783
1108
|
}
|
|
784
1109
|
|
|
785
1110
|
//#endregion
|
|
786
|
-
export {
|
|
787
|
-
//# sourceMappingURL=subcommands-
|
|
1111
|
+
export { matchKeyword as a, resolveOne as c, writeToIpc as d, listRecords as i, runSubcommand as l, isPidAlive as n, readNotes as o, isSubcommand as r, renderRawLog as s, controlCodeFromName as t, snapshotStatus as u };
|
|
1112
|
+
//# sourceMappingURL=subcommands-BltyYaEs.js.map
|
|
@@ -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-gyE00XVe.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-C1JXxoPi.js";
|
|
5
5
|
import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
|
|
@@ -1693,4 +1693,4 @@ function sleep(ms) {
|
|
|
1693
1693
|
|
|
1694
1694
|
//#endregion
|
|
1695
1695
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1696
|
-
//# sourceMappingURL=ts-
|
|
1696
|
+
//# sourceMappingURL=ts-CSMhtgoi.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.87.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-gyE00XVe.js.map
|
package/package.json
CHANGED
package/ts/remotes.ts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import yaml from "yaml";
|
|
5
|
+
|
|
6
|
+
function remotesPath(): string {
|
|
7
|
+
const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
8
|
+
return path.join(dir, "remotes.yaml");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RemoteConfig {
|
|
12
|
+
url: string; // e.g. "http://192.168.1.5:7432"
|
|
13
|
+
token: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ResolvedRemote {
|
|
17
|
+
url: string;
|
|
18
|
+
token: string;
|
|
19
|
+
keyword?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function readRemotes(): Promise<Map<string, RemoteConfig>> {
|
|
23
|
+
let raw: string;
|
|
24
|
+
try {
|
|
25
|
+
raw = await readFile(remotesPath(), "utf-8");
|
|
26
|
+
} catch {
|
|
27
|
+
return new Map();
|
|
28
|
+
}
|
|
29
|
+
const doc = yaml.parse(raw) ?? {};
|
|
30
|
+
const remotes = doc.remotes ?? {};
|
|
31
|
+
const map = new Map<string, RemoteConfig>();
|
|
32
|
+
for (const [alias, cfg] of Object.entries(remotes)) {
|
|
33
|
+
if (cfg && typeof (cfg as any).url === "string" && typeof (cfg as any).token === "string") {
|
|
34
|
+
map.set(alias, { url: (cfg as any).url, token: (cfg as any).token });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return map;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function writeRemoteAlias(alias: string, config: RemoteConfig): Promise<void> {
|
|
41
|
+
const remotes = await readRemotes();
|
|
42
|
+
remotes.set(alias, config);
|
|
43
|
+
const doc: Record<string, any> = {};
|
|
44
|
+
for (const [k, v] of remotes) doc[k] = v;
|
|
45
|
+
await mkdir(path.dirname(remotesPath()), { recursive: true });
|
|
46
|
+
await writeFile(remotesPath(), yaml.stringify({ remotes: doc }));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function deleteRemoteAlias(alias: string): Promise<void> {
|
|
50
|
+
const remotes = await readRemotes();
|
|
51
|
+
remotes.delete(alias);
|
|
52
|
+
const doc: Record<string, any> = {};
|
|
53
|
+
for (const [k, v] of remotes) doc[k] = v;
|
|
54
|
+
await writeFile(remotesPath(), yaml.stringify({ remotes: doc }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Parse token@host:port[:keyword] — the `@` is a hard signal this is remote. */
|
|
58
|
+
export function parseDirectRemoteSpec(
|
|
59
|
+
spec: string,
|
|
60
|
+
): { token: string; host: string; port: number; keyword?: string; baseUrl: string } | null {
|
|
61
|
+
const m = /^([^@]+)@([^:@]+):(\d+)(?::(.+))?$/.exec(spec);
|
|
62
|
+
if (!m) return null;
|
|
63
|
+
const host = m[2]!;
|
|
64
|
+
const port = parseInt(m[3]!, 10);
|
|
65
|
+
return {
|
|
66
|
+
token: m[1]!,
|
|
67
|
+
host,
|
|
68
|
+
port,
|
|
69
|
+
keyword: m[4] || undefined,
|
|
70
|
+
baseUrl: `http://${host}:${port}`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a spec to connection details.
|
|
76
|
+
* Accepts:
|
|
77
|
+
* token@host:port[:keyword] — direct
|
|
78
|
+
* alias[:keyword] — looked up in ~/.agent-yes/remotes.yaml
|
|
79
|
+
* Returns null if the spec doesn't match any remote.
|
|
80
|
+
*/
|
|
81
|
+
export async function resolveRemoteSpec(spec: string): Promise<ResolvedRemote | null> {
|
|
82
|
+
const direct = parseDirectRemoteSpec(spec);
|
|
83
|
+
if (direct) {
|
|
84
|
+
return { url: direct.baseUrl, token: direct.token, keyword: direct.keyword };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// alias[:keyword]
|
|
88
|
+
const colonIdx = spec.indexOf(":");
|
|
89
|
+
const alias = colonIdx >= 0 ? spec.slice(0, colonIdx) : spec;
|
|
90
|
+
const keyword = colonIdx >= 0 ? spec.slice(colonIdx + 1) || undefined : undefined;
|
|
91
|
+
|
|
92
|
+
const remotes = await readRemotes();
|
|
93
|
+
const cfg = remotes.get(alias);
|
|
94
|
+
if (!cfg) return null;
|
|
95
|
+
return { url: cfg.url, token: cfg.token, keyword };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// ay remote subcommand
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
export async function cmdRemote(rest: string[]): Promise<number> {
|
|
103
|
+
const sub = rest[0];
|
|
104
|
+
|
|
105
|
+
if (!sub || sub === "ls" || sub === "list") {
|
|
106
|
+
const remotes = await readRemotes();
|
|
107
|
+
if (remotes.size === 0) {
|
|
108
|
+
process.stdout.write("no remotes configured\n");
|
|
109
|
+
process.stderr.write(
|
|
110
|
+
"\n" +
|
|
111
|
+
" ay remote add <alias> <url> <token> # add a remote\n" +
|
|
112
|
+
" ay serve # start server (prints token)\n",
|
|
113
|
+
);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
for (const [alias, cfg] of remotes) {
|
|
117
|
+
const preview = cfg.token.length > 8 ? cfg.token.slice(0, 8) + "..." : cfg.token;
|
|
118
|
+
process.stdout.write(`${alias}\t${cfg.url}\ttoken:${preview}\n`);
|
|
119
|
+
}
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (sub === "add") {
|
|
124
|
+
const [, alias, url, token] = rest;
|
|
125
|
+
if (!alias || !url || !token) {
|
|
126
|
+
process.stderr.write("usage: ay remote add <alias> <url> <token>\n");
|
|
127
|
+
process.stderr.write(
|
|
128
|
+
" example: ay remote add work-mac http://192.168.1.5:7432 mytoken123\n",
|
|
129
|
+
);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
await writeRemoteAlias(alias, { url, token });
|
|
133
|
+
process.stdout.write(`remote '${alias}' added → ${url}\n`);
|
|
134
|
+
process.stderr.write(`\n ay ls ${alias} # list agents on ${alias}\n`);
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (sub === "rm" || sub === "remove" || sub === "delete") {
|
|
139
|
+
const alias = rest[1];
|
|
140
|
+
if (!alias) {
|
|
141
|
+
process.stderr.write("usage: ay remote rm <alias>\n");
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
const remotes = await readRemotes();
|
|
145
|
+
if (!remotes.has(alias)) {
|
|
146
|
+
process.stderr.write(`remote '${alias}' not found\n`);
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
await deleteRemoteAlias(alias);
|
|
150
|
+
process.stdout.write(`remote '${alias}' removed\n`);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
process.stderr.write(`ay remote: unknown subcommand '${sub}'\n`);
|
|
155
|
+
process.stderr.write(
|
|
156
|
+
" ay remote ls # list configured remotes\n" +
|
|
157
|
+
" ay remote add <alias> <url> <token> # add a remote\n" +
|
|
158
|
+
" ay remote rm <alias> # remove a remote\n",
|
|
159
|
+
);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|