agent-yes 1.85.0 → 1.86.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-BlWg00sP.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-D0NnTXRD.js +303 -0
- package/dist/subcommands-BDiS305D.js +6 -0
- package/dist/{subcommands-BwWcA9uo.js → subcommands-BpGEGOQM.js} +300 -6
- package/dist/{tray-CH_G7aXM.js → tray-DHuD0nEk.js} +1 -1
- package/dist/{ts-D0ddYVke.js → ts-DWuvdSWr.js} +2 -2
- package/dist/{versionChecker-ftOiNICT.js → versionChecker-BCrJk4Zj.js} +2 -2
- package/package.json +1 -1
- package/ts/remotes.ts +161 -0
- package/ts/serve.ts +373 -0
- package/ts/subcommands.ts +380 -9
package/ts/subcommands.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { homedir } from "os";
|
|
|
16
16
|
import path from "path";
|
|
17
17
|
import { type GlobalPidRecord, readGlobalPids } from "./globalPidIndex.ts";
|
|
18
18
|
import yargs from "yargs";
|
|
19
|
+
import { type ResolvedRemote, readRemotes, resolveRemoteSpec } from "./remotes.ts";
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// notes store (~/.agent-yes/notes.jsonl)
|
|
@@ -26,7 +27,7 @@ function notesPath(): string {
|
|
|
26
27
|
return path.join(dir, "notes.jsonl");
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
async function readNotes(): Promise<Map<number, string>> {
|
|
30
|
+
export async function readNotes(): Promise<Map<number, string>> {
|
|
30
31
|
let raw: string;
|
|
31
32
|
try {
|
|
32
33
|
raw = await readFile(notesPath(), "utf-8");
|
|
@@ -136,6 +137,8 @@ const SUBCOMMANDS = new Set([
|
|
|
136
137
|
"send",
|
|
137
138
|
"restart",
|
|
138
139
|
"note",
|
|
140
|
+
"serve",
|
|
141
|
+
"remote",
|
|
139
142
|
]);
|
|
140
143
|
|
|
141
144
|
const IDLE_THRESHOLD_MS = 60 * 1000;
|
|
@@ -175,6 +178,14 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
175
178
|
return await cmdRestart(rest);
|
|
176
179
|
case "note":
|
|
177
180
|
return await cmdNote(rest);
|
|
181
|
+
case "serve": {
|
|
182
|
+
const { cmdServe } = await import("./serve.ts");
|
|
183
|
+
return cmdServe(rest);
|
|
184
|
+
}
|
|
185
|
+
case "remote": {
|
|
186
|
+
const { cmdRemote } = await import("./remotes.ts");
|
|
187
|
+
return cmdRemote(rest);
|
|
188
|
+
}
|
|
178
189
|
default:
|
|
179
190
|
return null;
|
|
180
191
|
}
|
|
@@ -189,7 +200,7 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
189
200
|
// shared helpers
|
|
190
201
|
// ---------------------------------------------------------------------------
|
|
191
202
|
|
|
192
|
-
interface CommonOpts {
|
|
203
|
+
export interface CommonOpts {
|
|
193
204
|
all: boolean;
|
|
194
205
|
active: boolean;
|
|
195
206
|
cwdScope: string | null;
|
|
@@ -211,7 +222,7 @@ export function matchKeyword(record: GlobalPidRecord, keyword: string): boolean
|
|
|
211
222
|
return false;
|
|
212
223
|
}
|
|
213
224
|
|
|
214
|
-
async function listRecords(
|
|
225
|
+
export async function listRecords(
|
|
215
226
|
keyword: string | undefined,
|
|
216
227
|
opts: CommonOpts,
|
|
217
228
|
): Promise<GlobalPidRecord[]> {
|
|
@@ -240,7 +251,7 @@ async function listRecords(
|
|
|
240
251
|
return records;
|
|
241
252
|
}
|
|
242
253
|
|
|
243
|
-
function isPidAlive(pid: number): boolean {
|
|
254
|
+
export function isPidAlive(pid: number): boolean {
|
|
244
255
|
try {
|
|
245
256
|
process.kill(pid, 0);
|
|
246
257
|
return true;
|
|
@@ -249,7 +260,10 @@ function isPidAlive(pid: number): boolean {
|
|
|
249
260
|
}
|
|
250
261
|
}
|
|
251
262
|
|
|
252
|
-
async function resolveOne(
|
|
263
|
+
export async function resolveOne(
|
|
264
|
+
keyword: string | undefined,
|
|
265
|
+
opts: CommonOpts,
|
|
266
|
+
): Promise<GlobalPidRecord> {
|
|
253
267
|
if (!keyword) {
|
|
254
268
|
throw new Error("keyword required (pid, cwd substring, cli name, or prompt substring)");
|
|
255
269
|
}
|
|
@@ -268,6 +282,319 @@ async function resolveOne(keyword: string | undefined, opts: CommonOpts): Promis
|
|
|
268
282
|
);
|
|
269
283
|
}
|
|
270
284
|
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// remote routing helpers
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
async function remoteGet(remote: ResolvedRemote, pathname: string): Promise<Response> {
|
|
290
|
+
return fetch(`${remote.url}${pathname}`, {
|
|
291
|
+
headers: { Authorization: `Bearer ${remote.token}` },
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function remotePost(
|
|
296
|
+
remote: ResolvedRemote,
|
|
297
|
+
pathname: string,
|
|
298
|
+
body: unknown,
|
|
299
|
+
): Promise<Response> {
|
|
300
|
+
return fetch(`${remote.url}${pathname}`, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: { Authorization: `Bearer ${remote.token}`, "Content-Type": "application/json" },
|
|
303
|
+
body: JSON.stringify(body),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function runRemoteLs(
|
|
308
|
+
remote: ResolvedRemote,
|
|
309
|
+
opts: { all: boolean; active: boolean },
|
|
310
|
+
): Promise<number> {
|
|
311
|
+
const params = new URLSearchParams();
|
|
312
|
+
if (remote.keyword) params.set("keyword", remote.keyword);
|
|
313
|
+
if (opts.all) params.set("all", "1");
|
|
314
|
+
if (opts.active) params.set("active", "1");
|
|
315
|
+
const res = await remoteGet(remote, `/api/ls?${params}`);
|
|
316
|
+
if (!res.ok) {
|
|
317
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
318
|
+
return 1;
|
|
319
|
+
}
|
|
320
|
+
const records = (await res.json()) as any[];
|
|
321
|
+
if (records.length === 0) {
|
|
322
|
+
process.stderr.write(
|
|
323
|
+
remote.keyword
|
|
324
|
+
? `no agents matched "${remote.keyword}" on ${remote.url}\n`
|
|
325
|
+
: `no running agents on ${remote.url}\n`,
|
|
326
|
+
);
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
process.stderr.write(`[remote ${remote.url}]\n`);
|
|
330
|
+
const termWidth = (process.stdout as any).columns ?? 120;
|
|
331
|
+
const widths = {
|
|
332
|
+
pid: Math.max(3, ...records.map((r: any) => String(r.pid).length)),
|
|
333
|
+
cli: Math.max(3, ...records.map((r: any) => String(r.cli).length)),
|
|
334
|
+
status: Math.max(6, ...records.map((r: any) => String(r.status).length)),
|
|
335
|
+
cwd: Math.max(3, ...records.map((r: any) => String(r.cwd).length)),
|
|
336
|
+
};
|
|
337
|
+
const fixedWidth = widths.pid + widths.cli + widths.status + widths.cwd + 4 * 2;
|
|
338
|
+
const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
|
|
339
|
+
const header =
|
|
340
|
+
[
|
|
341
|
+
"PID".padEnd(widths.pid),
|
|
342
|
+
"CLI".padEnd(widths.cli),
|
|
343
|
+
"STATUS".padEnd(widths.status),
|
|
344
|
+
"CWD".padEnd(widths.cwd),
|
|
345
|
+
"PROMPT",
|
|
346
|
+
].join(" ") + "\n";
|
|
347
|
+
process.stdout.write(header);
|
|
348
|
+
for (const r of records) {
|
|
349
|
+
const label = r.prompt ? truncate(`→ ${r.prompt}`, promptBudget) : "";
|
|
350
|
+
process.stdout.write(
|
|
351
|
+
[
|
|
352
|
+
String(r.pid).padEnd(widths.pid),
|
|
353
|
+
String(r.cli).padEnd(widths.cli),
|
|
354
|
+
String(r.status).padEnd(widths.status),
|
|
355
|
+
String(r.cwd).padEnd(widths.cwd),
|
|
356
|
+
label,
|
|
357
|
+
].join(" ") + "\n",
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function runRemoteRead(
|
|
364
|
+
remote: ResolvedRemote,
|
|
365
|
+
mode: "cat" | "tail" | "head",
|
|
366
|
+
follow: boolean,
|
|
367
|
+
n: number,
|
|
368
|
+
reconnectTimeoutMs = 120_000,
|
|
369
|
+
): Promise<number> {
|
|
370
|
+
const keyword = remote.keyword ?? "";
|
|
371
|
+
if (!keyword) {
|
|
372
|
+
process.stderr.write(
|
|
373
|
+
"remote tail/cat/head requires a keyword (e.g. token@host:port:keyword)\n",
|
|
374
|
+
);
|
|
375
|
+
return 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (mode === "tail" && follow) {
|
|
379
|
+
const ac = new AbortController();
|
|
380
|
+
process.on("SIGINT", () => ac.abort());
|
|
381
|
+
const deadline = Date.now() + reconnectTimeoutMs;
|
|
382
|
+
let delay = 1_000;
|
|
383
|
+
let attempt = 0;
|
|
384
|
+
|
|
385
|
+
process.stderr.write(
|
|
386
|
+
`[remote ${remote.url} ${keyword}]\nfollowing... (Ctrl-C to stop, timeout: ${Math.round(reconnectTimeoutMs / 1000)}s)\n`,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
while (!ac.signal.aborted) {
|
|
390
|
+
try {
|
|
391
|
+
const res = await fetch(`${remote.url}/api/tail/${encodeURIComponent(keyword)}`, {
|
|
392
|
+
headers: { Authorization: `Bearer ${remote.token}`, Accept: "text/event-stream" },
|
|
393
|
+
signal: ac.signal,
|
|
394
|
+
});
|
|
395
|
+
if (!res.ok) {
|
|
396
|
+
// 401/404 are permanent failures — no point retrying
|
|
397
|
+
if (res.status === 401 || res.status === 404) {
|
|
398
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
399
|
+
return 1;
|
|
400
|
+
}
|
|
401
|
+
throw new Error(`HTTP ${res.status}`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (attempt > 0) process.stderr.write("remote: reconnected\n");
|
|
405
|
+
delay = 1_000; // reset backoff on successful connect
|
|
406
|
+
|
|
407
|
+
const reader = res.body!.getReader();
|
|
408
|
+
const dec = new TextDecoder();
|
|
409
|
+
let buf = "";
|
|
410
|
+
while (true) {
|
|
411
|
+
const { done, value } = await reader.read();
|
|
412
|
+
if (done) break;
|
|
413
|
+
buf += dec.decode(value, { stream: true });
|
|
414
|
+
const lines = buf.split("\n");
|
|
415
|
+
buf = lines.pop() ?? "";
|
|
416
|
+
for (const line of lines) {
|
|
417
|
+
if (!line.startsWith("data: ")) continue;
|
|
418
|
+
try {
|
|
419
|
+
const text = JSON.parse(line.slice(6)) as string;
|
|
420
|
+
process.stdout.write(text);
|
|
421
|
+
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
422
|
+
} catch {
|
|
423
|
+
/* skip non-JSON */
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
break; // stream ended cleanly
|
|
428
|
+
} catch (e: any) {
|
|
429
|
+
if (e.name === "AbortError" || ac.signal.aborted) return 0;
|
|
430
|
+
if (Date.now() >= deadline) {
|
|
431
|
+
process.stderr.write(
|
|
432
|
+
`remote: timeout after ${Math.round(reconnectTimeoutMs / 1000)}s, giving up\n`,
|
|
433
|
+
);
|
|
434
|
+
return 1;
|
|
435
|
+
}
|
|
436
|
+
process.stderr.write(
|
|
437
|
+
`remote: disconnected (${e.message}), retrying in ${delay / 1000}s…\n`,
|
|
438
|
+
);
|
|
439
|
+
await new Promise<void>((resolve, reject) => {
|
|
440
|
+
const t = setTimeout(resolve, delay);
|
|
441
|
+
ac.signal.addEventListener("abort", () => {
|
|
442
|
+
clearTimeout(t);
|
|
443
|
+
reject(new Error("abort"));
|
|
444
|
+
});
|
|
445
|
+
}).catch(() => {});
|
|
446
|
+
if (ac.signal.aborted) return 0;
|
|
447
|
+
delay = Math.min(delay * 2, 30_000);
|
|
448
|
+
attempt++;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return 0;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Static read (cat/head/tail without -f)
|
|
455
|
+
const params = new URLSearchParams({ mode, n: String(n) });
|
|
456
|
+
const res = await remoteGet(remote, `/api/read/${encodeURIComponent(keyword)}?${params}`);
|
|
457
|
+
if (!res.ok) {
|
|
458
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
459
|
+
return 1;
|
|
460
|
+
}
|
|
461
|
+
const text = await res.text();
|
|
462
|
+
process.stderr.write(`[remote ${remote.url} ${keyword}]\n`);
|
|
463
|
+
process.stdout.write(text);
|
|
464
|
+
if (!text.endsWith("\n")) process.stdout.write("\n");
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async function runRemoteSend(remote: ResolvedRemote, msg: string, code: string): Promise<number> {
|
|
469
|
+
const keyword = remote.keyword ?? "";
|
|
470
|
+
if (!keyword) {
|
|
471
|
+
process.stderr.write("remote send requires a keyword (e.g. token@host:port:keyword)\n");
|
|
472
|
+
return 1;
|
|
473
|
+
}
|
|
474
|
+
const res = await remotePost(remote, "/api/send", { keyword, msg, code });
|
|
475
|
+
if (!res.ok) {
|
|
476
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
477
|
+
return 1;
|
|
478
|
+
}
|
|
479
|
+
const data = (await res.json()) as any;
|
|
480
|
+
process.stdout.write(`sent to remote pid ${data.pid} (${remote.url} ${keyword})\n`);
|
|
481
|
+
return 0;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async function runRemoteStatus(remote: ResolvedRemote): Promise<number> {
|
|
485
|
+
const keyword = remote.keyword ?? "";
|
|
486
|
+
if (!keyword) {
|
|
487
|
+
process.stderr.write("remote status requires a keyword (e.g. token@host:port:keyword)\n");
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
const res = await remoteGet(remote, `/api/status/${encodeURIComponent(keyword)}`);
|
|
491
|
+
if (!res.ok) {
|
|
492
|
+
process.stderr.write(`remote error ${res.status}: ${await res.text()}\n`);
|
|
493
|
+
return 1;
|
|
494
|
+
}
|
|
495
|
+
process.stdout.write(JSON.stringify(await res.json(), null, 2) + "\n");
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
// --all-remotes helpers
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
async function fetchRemoteRecordsRaw(
|
|
504
|
+
url: string,
|
|
505
|
+
token: string,
|
|
506
|
+
opts: { all: boolean; active: boolean; keyword?: string },
|
|
507
|
+
): Promise<any[]> {
|
|
508
|
+
const params = new URLSearchParams();
|
|
509
|
+
if (opts.all) params.set("all", "1");
|
|
510
|
+
if (opts.active) params.set("active", "1");
|
|
511
|
+
if (opts.keyword) params.set("keyword", opts.keyword);
|
|
512
|
+
try {
|
|
513
|
+
const res = await fetch(`${url}/api/ls?${params}`, {
|
|
514
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
515
|
+
signal: AbortSignal.timeout(5000),
|
|
516
|
+
});
|
|
517
|
+
if (!res.ok) return [];
|
|
518
|
+
return (await res.json()) as any[];
|
|
519
|
+
} catch {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function runAllRemotesLs(opts: {
|
|
525
|
+
all: boolean;
|
|
526
|
+
active: boolean;
|
|
527
|
+
keyword?: string;
|
|
528
|
+
}): Promise<number> {
|
|
529
|
+
const remotes = await readRemotes();
|
|
530
|
+
const localOpts: CommonOpts = {
|
|
531
|
+
all: opts.all,
|
|
532
|
+
active: opts.active,
|
|
533
|
+
json: true,
|
|
534
|
+
latest: false,
|
|
535
|
+
cwdScope: null,
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const [localResult, ...remoteResults] = await Promise.allSettled([
|
|
539
|
+
listRecords(opts.keyword, localOpts).then((recs) => ({
|
|
540
|
+
host: "local",
|
|
541
|
+
records: recs as any[],
|
|
542
|
+
})),
|
|
543
|
+
...Array.from(remotes.entries()).map(([alias, cfg]) =>
|
|
544
|
+
fetchRemoteRecordsRaw(cfg.url, cfg.token, opts).then((records) => ({ host: alias, records })),
|
|
545
|
+
),
|
|
546
|
+
]);
|
|
547
|
+
|
|
548
|
+
type HostedRow = { host: string; rec: any };
|
|
549
|
+
const rows: HostedRow[] = [];
|
|
550
|
+
if (localResult.status === "fulfilled") {
|
|
551
|
+
for (const r of localResult.value.records) rows.push({ host: "local", rec: r });
|
|
552
|
+
}
|
|
553
|
+
for (const res of remoteResults) {
|
|
554
|
+
if (res.status === "fulfilled") {
|
|
555
|
+
for (const r of res.value.records) rows.push({ host: res.value.host, rec: r });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (rows.length === 0) {
|
|
560
|
+
process.stderr.write("no running agents\n");
|
|
561
|
+
return 0;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const termWidth = (process.stdout as any).columns ?? 120;
|
|
565
|
+
const hostW = Math.max(4, ...rows.map((r) => r.host.length));
|
|
566
|
+
const pidW = Math.max(3, ...rows.map((r) => String(r.rec.pid).length));
|
|
567
|
+
const cliW = Math.max(3, ...rows.map((r) => String(r.rec.cli).length));
|
|
568
|
+
const statusW = Math.max(6, ...rows.map((r) => String(r.rec.status).length));
|
|
569
|
+
const cwdW = Math.max(3, ...rows.map((r) => shortenPath(String(r.rec.cwd)).length));
|
|
570
|
+
const promptBudget = Math.max(20, termWidth - hostW - pidW - cliW - statusW - cwdW - 5 * 2 - 1);
|
|
571
|
+
|
|
572
|
+
process.stdout.write(
|
|
573
|
+
[
|
|
574
|
+
"HOST".padEnd(hostW),
|
|
575
|
+
"PID".padEnd(pidW),
|
|
576
|
+
"CLI".padEnd(cliW),
|
|
577
|
+
"STATUS".padEnd(statusW),
|
|
578
|
+
"CWD".padEnd(cwdW),
|
|
579
|
+
"PROMPT",
|
|
580
|
+
].join(" ") + "\n",
|
|
581
|
+
);
|
|
582
|
+
for (const { host, rec } of rows) {
|
|
583
|
+
const label = rec.prompt ? truncate(`→ ${rec.prompt}`, promptBudget) : "";
|
|
584
|
+
process.stdout.write(
|
|
585
|
+
[
|
|
586
|
+
host.padEnd(hostW),
|
|
587
|
+
String(rec.pid).padEnd(pidW),
|
|
588
|
+
String(rec.cli).padEnd(cliW),
|
|
589
|
+
String(rec.status).padEnd(statusW),
|
|
590
|
+
shortenPath(String(rec.cwd)).padEnd(cwdW),
|
|
591
|
+
label,
|
|
592
|
+
].join(" ") + "\n",
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
return 0;
|
|
596
|
+
}
|
|
597
|
+
|
|
271
598
|
// ---------------------------------------------------------------------------
|
|
272
599
|
// ay ls
|
|
273
600
|
// ---------------------------------------------------------------------------
|
|
@@ -297,8 +624,14 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
297
624
|
description: "Show only the most recent agent",
|
|
298
625
|
})
|
|
299
626
|
.option("cwd", { type: "string", description: "Restrict to agents whose cwd starts with dir" })
|
|
627
|
+
.option("all-remotes", {
|
|
628
|
+
type: "boolean",
|
|
629
|
+
default: false,
|
|
630
|
+
description: "Include agents from all configured remotes (remotes.yaml)",
|
|
631
|
+
})
|
|
300
632
|
.option("help", { alias: "h", type: "boolean", default: false, description: "Show this help" })
|
|
301
633
|
.example("ay ls", "list running agents")
|
|
634
|
+
.example("ay ls --all-remotes", "include all configured remote machines")
|
|
302
635
|
.example("ay ls --all", "include exited agents")
|
|
303
636
|
.example("ay ls --json", "machine-readable output")
|
|
304
637
|
.example("ay ls symval", "filter by cwd/prompt keyword")
|
|
@@ -313,7 +646,19 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
313
646
|
return 0;
|
|
314
647
|
}
|
|
315
648
|
|
|
649
|
+
if (argv["all-remotes"]) {
|
|
650
|
+
return runAllRemotesLs({
|
|
651
|
+
all: argv.all,
|
|
652
|
+
active: argv.active,
|
|
653
|
+
keyword: argv._[0] !== undefined ? String(argv._[0]) : undefined,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
316
657
|
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
658
|
+
if (keyword) {
|
|
659
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
660
|
+
if (remote) return runRemoteLs(remote, { all: argv.all, active: argv.active });
|
|
661
|
+
}
|
|
317
662
|
const opts: CommonOpts = {
|
|
318
663
|
all: argv.all,
|
|
319
664
|
active: argv.active,
|
|
@@ -491,6 +836,11 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
491
836
|
description: "Use most recent match when multiple match",
|
|
492
837
|
})
|
|
493
838
|
.option("cwd", { type: "string", description: "Restrict to agents under this dir" })
|
|
839
|
+
.option("reconnect-timeout", {
|
|
840
|
+
type: "number",
|
|
841
|
+
default: 120,
|
|
842
|
+
description: "Seconds before giving up reconnecting remote SSE (default: 120)",
|
|
843
|
+
})
|
|
494
844
|
.help(false)
|
|
495
845
|
.version(false)
|
|
496
846
|
.exitProcess(false);
|
|
@@ -504,6 +854,18 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
504
854
|
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
505
855
|
};
|
|
506
856
|
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
857
|
+
if (keyword) {
|
|
858
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
859
|
+
const nFlag2 = argv.n;
|
|
860
|
+
const n2 =
|
|
861
|
+
nFlag2 !== undefined && Number.isFinite(nFlag2) && nFlag2 > 0
|
|
862
|
+
? Math.floor(nFlag2)
|
|
863
|
+
: mode === "cat"
|
|
864
|
+
? 0
|
|
865
|
+
: 96;
|
|
866
|
+
const reconnectTimeoutMs = ((argv["reconnect-timeout"] as number) ?? 120) * 1000;
|
|
867
|
+
if (remote) return runRemoteRead(remote, mode, argv.follow, n2, reconnectTimeoutMs);
|
|
868
|
+
}
|
|
507
869
|
const follow = argv.follow;
|
|
508
870
|
const nFlag = argv.n;
|
|
509
871
|
const n =
|
|
@@ -579,7 +941,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
579
941
|
* Feed the raw PTY bytes through @xterm/headless and emit plain text.
|
|
580
942
|
* Same approach as koho's renderTerminalBuffer + agent-yes's XtermProxy.
|
|
581
943
|
*/
|
|
582
|
-
async function renderRawLog(
|
|
944
|
+
export async function renderRawLog(
|
|
583
945
|
buf: Uint8Array,
|
|
584
946
|
{ mode, n }: { mode: "cat" | "tail" | "head"; n: number },
|
|
585
947
|
): Promise<string> {
|
|
@@ -773,6 +1135,10 @@ async function cmdSend(rest: string[]): Promise<number> {
|
|
|
773
1135
|
throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
774
1136
|
|
|
775
1137
|
const codeName = argv.code.toLowerCase();
|
|
1138
|
+
{
|
|
1139
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
1140
|
+
if (remote) return runRemoteSend(remote, rawMessage, codeName);
|
|
1141
|
+
}
|
|
776
1142
|
const trailing = controlCodeFromName(codeName);
|
|
777
1143
|
|
|
778
1144
|
const record = await resolveOne(keyword, opts);
|
|
@@ -851,7 +1217,7 @@ export function controlCodeFromName(name: string): string {
|
|
|
851
1217
|
}
|
|
852
1218
|
}
|
|
853
1219
|
|
|
854
|
-
async function writeToIpc(ipcPath: string, payload: string): Promise<void> {
|
|
1220
|
+
export async function writeToIpc(ipcPath: string, payload: string): Promise<void> {
|
|
855
1221
|
if (process.platform === "win32") {
|
|
856
1222
|
const { connect } = await import("net");
|
|
857
1223
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -974,7 +1340,7 @@ async function cmdNote(rest: string[]): Promise<number> {
|
|
|
974
1340
|
// ay status
|
|
975
1341
|
// ---------------------------------------------------------------------------
|
|
976
1342
|
|
|
977
|
-
interface StatusSnapshot {
|
|
1343
|
+
export interface StatusSnapshot {
|
|
978
1344
|
pid: number;
|
|
979
1345
|
cli: string;
|
|
980
1346
|
cwd: string;
|
|
@@ -989,7 +1355,7 @@ interface StatusSnapshot {
|
|
|
989
1355
|
log_file: string | null;
|
|
990
1356
|
}
|
|
991
1357
|
|
|
992
|
-
async function snapshotStatus(record: GlobalPidRecord): Promise<StatusSnapshot> {
|
|
1358
|
+
export async function snapshotStatus(record: GlobalPidRecord): Promise<StatusSnapshot> {
|
|
993
1359
|
const alive = isPidAlive(record.pid);
|
|
994
1360
|
let state: "active" | "idle" | "stopped";
|
|
995
1361
|
let logMtimeMs: number | null = null;
|
|
@@ -1051,6 +1417,11 @@ async function cmdStatus(rest: string[]): Promise<number> {
|
|
|
1051
1417
|
|
|
1052
1418
|
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
|
|
1053
1419
|
|
|
1420
|
+
{
|
|
1421
|
+
const remote = await resolveRemoteSpec(keyword);
|
|
1422
|
+
if (remote) return runRemoteStatus(remote);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1054
1425
|
const watch = argv.watch;
|
|
1055
1426
|
const intervalFlag = argv.interval;
|
|
1056
1427
|
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1000);
|