claude-yes 1.84.0 → 1.85.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-DM0fJTMR.js → SUPPORTED_CLIS-DnCV2Fj1.js} +3 -3
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{subcommands-DjO8lthH.js → subcommands-BwWcA9uo.js} +143 -75
- package/dist/{ts-Bw6gQKyU.js → ts-BTCVz_nZ.js} +2 -2
- package/dist/{versionChecker-CspuhOwO.js → versionChecker-Cir2ZoFL.js} +2 -2
- package/package.json +1 -1
- package/ts/subcommands.spec.ts +478 -35
- package/ts/subcommands.ts +164 -86
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-BTCVz_nZ.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-Cir2ZoFL.js";
|
|
4
4
|
import "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
6
|
|
|
@@ -9,4 +9,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
9
9
|
|
|
10
10
|
//#endregion
|
|
11
11
|
export { SUPPORTED_CLIS };
|
|
12
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
12
|
+
//# sourceMappingURL=SUPPORTED_CLIS-DnCV2Fj1.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-Cir2ZoFL.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -480,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
{
|
|
483
|
-
const { isSubcommand, runSubcommand } = await import("./subcommands-
|
|
483
|
+
const { isSubcommand, runSubcommand } = await import("./subcommands-BwWcA9uo.js");
|
|
484
484
|
if (isSubcommand(process.argv[2])) {
|
|
485
485
|
const code = await runSubcommand(process.argv);
|
|
486
486
|
process.exit(code ?? 0);
|
|
@@ -509,7 +509,7 @@ if (config.useRust) {
|
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
if (rustBinary) {
|
|
512
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
512
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DnCV2Fj1.js");
|
|
513
513
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
514
514
|
if (config.verbose) {
|
|
515
515
|
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-BTCVz_nZ.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-Cir2ZoFL.js";
|
|
4
4
|
import "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
6
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./logger-B9h0djqx.js";
|
|
2
2
|
import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
|
|
3
|
+
import yargs from "yargs";
|
|
3
4
|
import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
4
5
|
import { homedir } from "os";
|
|
5
6
|
import path from "path";
|
|
@@ -160,50 +161,6 @@ async function runSubcommand(argv) {
|
|
|
160
161
|
return 1;
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
|
-
function parseArgs(rest) {
|
|
164
|
-
const flags = {};
|
|
165
|
-
const positional = [];
|
|
166
|
-
for (let i = 0; i < rest.length; i++) {
|
|
167
|
-
const arg = rest[i];
|
|
168
|
-
if (arg.startsWith("--")) {
|
|
169
|
-
const eq = arg.indexOf("=");
|
|
170
|
-
if (eq >= 0) flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
171
|
-
else {
|
|
172
|
-
const key = arg.slice(2);
|
|
173
|
-
const next = rest[i + 1];
|
|
174
|
-
if ([
|
|
175
|
-
"all",
|
|
176
|
-
"active",
|
|
177
|
-
"follow",
|
|
178
|
-
"json",
|
|
179
|
-
"latest",
|
|
180
|
-
"watch"
|
|
181
|
-
].includes(key) || !next || next.startsWith("-")) flags[key] = true;
|
|
182
|
-
else {
|
|
183
|
-
flags[key] = next;
|
|
184
|
-
i++;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
} else if (arg.startsWith("-") && arg.length > 1) if (arg === "-n") {
|
|
188
|
-
flags["n"] = rest[i + 1] ?? "";
|
|
189
|
-
i++;
|
|
190
|
-
} else flags[arg.slice(1)] = true;
|
|
191
|
-
else positional.push(arg);
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
flags,
|
|
195
|
-
positional
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
function commonOpts(flags) {
|
|
199
|
-
return {
|
|
200
|
-
all: !!flags.all,
|
|
201
|
-
active: !!flags.active,
|
|
202
|
-
cwdScope: typeof flags.cwd === "string" ? path.resolve(flags.cwd) : flags.cwd === true ? process.cwd() : null,
|
|
203
|
-
latest: !!flags.latest,
|
|
204
|
-
json: !!flags.json
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
164
|
function matchKeyword(record, keyword) {
|
|
208
165
|
if (!keyword) return true;
|
|
209
166
|
const kw = keyword.toLowerCase();
|
|
@@ -243,9 +200,44 @@ async function resolveOne(keyword, opts) {
|
|
|
243
200
|
throw new Error(`keyword "${keyword}" matched ${matches.length} agents — disambiguate by pid or pass --latest:\n${lines}`);
|
|
244
201
|
}
|
|
245
202
|
async function cmdLs(rest) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
203
|
+
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
|
+
type: "boolean",
|
|
205
|
+
default: false,
|
|
206
|
+
description: "Show all agents including exited ones"
|
|
207
|
+
}).option("active", {
|
|
208
|
+
type: "boolean",
|
|
209
|
+
default: false,
|
|
210
|
+
description: "Only show agents with an alive process"
|
|
211
|
+
}).option("json", {
|
|
212
|
+
type: "boolean",
|
|
213
|
+
default: false,
|
|
214
|
+
description: "Output as JSON array"
|
|
215
|
+
}).option("latest", {
|
|
216
|
+
type: "boolean",
|
|
217
|
+
default: false,
|
|
218
|
+
description: "Show only the most recent agent"
|
|
219
|
+
}).option("cwd", {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "Restrict to agents whose cwd starts with dir"
|
|
222
|
+
}).option("help", {
|
|
223
|
+
alias: "h",
|
|
224
|
+
type: "boolean",
|
|
225
|
+
default: false,
|
|
226
|
+
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);
|
|
228
|
+
const argv = await y.parseAsync();
|
|
229
|
+
if (argv.help || argv.h) {
|
|
230
|
+
process.stdout.write(await y.getHelp() + "\n");
|
|
231
|
+
return 0;
|
|
232
|
+
}
|
|
233
|
+
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
234
|
+
const opts = {
|
|
235
|
+
all: argv.all,
|
|
236
|
+
active: argv.active,
|
|
237
|
+
json: argv.json,
|
|
238
|
+
latest: argv.latest,
|
|
239
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
240
|
+
};
|
|
249
241
|
const records = await listRecords(keyword, opts);
|
|
250
242
|
if (opts.json) {
|
|
251
243
|
process.stdout.write(JSON.stringify(records, null, 2) + "\n");
|
|
@@ -349,11 +341,36 @@ function truncate(s, n) {
|
|
|
349
341
|
return s.slice(0, n - 1) + "…";
|
|
350
342
|
}
|
|
351
343
|
async function cmdRead(rest, { mode }) {
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
344
|
+
const argv = await yargs(rest).usage("Usage: ay read/cat/tail/head <keyword> [options]").option("follow", {
|
|
345
|
+
alias: "f",
|
|
346
|
+
type: "boolean",
|
|
347
|
+
default: false,
|
|
348
|
+
description: "Follow log output (Ctrl-C to stop)"
|
|
349
|
+
}).option("n", {
|
|
350
|
+
type: "number",
|
|
351
|
+
description: "Number of lines (default: 96 for tail/head)"
|
|
352
|
+
}).option("all", {
|
|
353
|
+
type: "boolean",
|
|
354
|
+
default: false,
|
|
355
|
+
description: "Include exited agents"
|
|
356
|
+
}).option("latest", {
|
|
357
|
+
type: "boolean",
|
|
358
|
+
default: false,
|
|
359
|
+
description: "Use most recent match when multiple match"
|
|
360
|
+
}).option("cwd", {
|
|
361
|
+
type: "string",
|
|
362
|
+
description: "Restrict to agents under this dir"
|
|
363
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
364
|
+
const opts = {
|
|
365
|
+
all: argv.all,
|
|
366
|
+
active: false,
|
|
367
|
+
json: false,
|
|
368
|
+
latest: argv.latest,
|
|
369
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
370
|
+
};
|
|
371
|
+
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
372
|
+
const follow = argv.follow;
|
|
373
|
+
const nFlag = argv.n;
|
|
357
374
|
const n = nFlag !== void 0 && Number.isFinite(nFlag) && nFlag > 0 ? Math.floor(nFlag) : mode === "cat" ? 0 : 96;
|
|
358
375
|
const record = await resolveOne(keyword, opts);
|
|
359
376
|
const logPath = record.log_file;
|
|
@@ -512,12 +529,33 @@ function extractActivityFromLines(lines) {
|
|
|
512
529
|
return null;
|
|
513
530
|
}
|
|
514
531
|
async function cmdSend(rest) {
|
|
515
|
-
const
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
532
|
+
const argv = await yargs(rest).usage("Usage: ay send <keyword> <msg|-> [options]").option("code", {
|
|
533
|
+
type: "string",
|
|
534
|
+
default: "enter",
|
|
535
|
+
description: "Trailing control code (enter|esc|ctrl-c|ctrl-y|tab|none)"
|
|
536
|
+
}).option("all", {
|
|
537
|
+
type: "boolean",
|
|
538
|
+
default: false,
|
|
539
|
+
description: "Include exited agents"
|
|
540
|
+
}).option("latest", {
|
|
541
|
+
type: "boolean",
|
|
542
|
+
default: false,
|
|
543
|
+
description: "Use most recent match"
|
|
544
|
+
}).option("cwd", {
|
|
545
|
+
type: "string",
|
|
546
|
+
description: "Restrict to agents under this dir"
|
|
547
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
548
|
+
const opts = {
|
|
549
|
+
all: argv.all,
|
|
550
|
+
active: false,
|
|
551
|
+
json: false,
|
|
552
|
+
latest: argv.latest,
|
|
553
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
554
|
+
};
|
|
555
|
+
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
556
|
+
const rawMessage = argv._.slice(1).map(String).join(" ");
|
|
519
557
|
if (!keyword) throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
520
|
-
const trailing = controlCodeFromName(
|
|
558
|
+
const trailing = controlCodeFromName(argv.code.toLowerCase());
|
|
521
559
|
const record = await resolveOne(keyword, opts);
|
|
522
560
|
const fifoPath = record.fifo_file;
|
|
523
561
|
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)`);
|
|
@@ -594,13 +632,22 @@ async function writeToIpc(ipcPath, payload) {
|
|
|
594
632
|
}
|
|
595
633
|
}
|
|
596
634
|
async function cmdRestart(rest) {
|
|
597
|
-
const
|
|
635
|
+
const argv = await yargs(rest).usage("Usage: ay restart <keyword>").option("latest", {
|
|
636
|
+
type: "boolean",
|
|
637
|
+
default: false,
|
|
638
|
+
description: "Use most recent match"
|
|
639
|
+
}).option("cwd", {
|
|
640
|
+
type: "string",
|
|
641
|
+
description: "Restrict to agents under this dir"
|
|
642
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
598
643
|
const opts = {
|
|
599
|
-
|
|
600
|
-
|
|
644
|
+
all: true,
|
|
645
|
+
active: false,
|
|
646
|
+
json: false,
|
|
647
|
+
latest: argv.latest,
|
|
648
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
601
649
|
};
|
|
602
|
-
const
|
|
603
|
-
const record = await resolveOne(keyword, opts);
|
|
650
|
+
const record = await resolveOne(argv._[0] !== void 0 ? String(argv._[0]) : void 0, opts);
|
|
604
651
|
if (isPidAlive(record.pid)) {
|
|
605
652
|
process.stderr.write(`pid ${record.pid} is still running — stop it first or use ay send\n`);
|
|
606
653
|
return 1;
|
|
@@ -621,14 +668,16 @@ async function cmdRestart(rest) {
|
|
|
621
668
|
return 0;
|
|
622
669
|
}
|
|
623
670
|
async function cmdNote(rest) {
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
-
const note = positional.slice(1).join(" ");
|
|
671
|
+
const argv = await yargs(rest).usage("Usage: ay note <keyword> [\"note text\"]").help(false).version(false).exitProcess(false).parseAsync();
|
|
672
|
+
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
673
|
+
const note = argv._.slice(1).map(String).join(" ");
|
|
628
674
|
if (!keyword) throw new Error("usage: ay note <keyword> [\"note text\"] (omit text to clear)");
|
|
629
675
|
const record = await resolveOne(keyword, {
|
|
630
|
-
|
|
631
|
-
|
|
676
|
+
all: true,
|
|
677
|
+
active: false,
|
|
678
|
+
json: false,
|
|
679
|
+
latest: false,
|
|
680
|
+
cwdScope: null
|
|
632
681
|
});
|
|
633
682
|
if (!note) {
|
|
634
683
|
await writeNote(record.pid, "");
|
|
@@ -668,15 +717,34 @@ async function snapshotStatus(record) {
|
|
|
668
717
|
};
|
|
669
718
|
}
|
|
670
719
|
async function cmdStatus(rest) {
|
|
671
|
-
const
|
|
720
|
+
const argv = await yargs(rest).usage("Usage: ay status <keyword> [options]").option("watch", {
|
|
721
|
+
alias: "w",
|
|
722
|
+
type: "boolean",
|
|
723
|
+
default: false,
|
|
724
|
+
description: "Stream changes as JSON"
|
|
725
|
+
}).option("interval", {
|
|
726
|
+
type: "number",
|
|
727
|
+
default: 2,
|
|
728
|
+
description: "Poll interval in seconds"
|
|
729
|
+
}).option("latest", {
|
|
730
|
+
type: "boolean",
|
|
731
|
+
default: false,
|
|
732
|
+
description: "Use most recent match"
|
|
733
|
+
}).option("cwd", {
|
|
734
|
+
type: "string",
|
|
735
|
+
description: "Restrict to agents under this dir"
|
|
736
|
+
}).help(false).version(false).exitProcess(false).parseAsync();
|
|
672
737
|
const opts = {
|
|
673
|
-
|
|
674
|
-
|
|
738
|
+
all: true,
|
|
739
|
+
active: false,
|
|
740
|
+
json: false,
|
|
741
|
+
latest: argv.latest,
|
|
742
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
|
|
675
743
|
};
|
|
676
|
-
const keyword =
|
|
744
|
+
const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
|
|
677
745
|
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
|
|
678
|
-
const watch =
|
|
679
|
-
const intervalFlag =
|
|
746
|
+
const watch = argv.watch;
|
|
747
|
+
const intervalFlag = argv.interval;
|
|
680
748
|
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1e3);
|
|
681
749
|
const record = await resolveOne(keyword, opts);
|
|
682
750
|
const emit = (snap, ts) => {
|
|
@@ -716,4 +784,4 @@ async function cmdStatus(rest) {
|
|
|
716
784
|
|
|
717
785
|
//#endregion
|
|
718
786
|
export { isSubcommand, runSubcommand };
|
|
719
|
-
//# sourceMappingURL=subcommands-
|
|
787
|
+
//# sourceMappingURL=subcommands-BwWcA9uo.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-Cir2ZoFL.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-BTCVz_nZ.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "claude-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.85.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-Cir2ZoFL.js.map
|
package/package.json
CHANGED
package/ts/subcommands.spec.ts
CHANGED
|
@@ -27,41 +27,6 @@ async function loadModule() {
|
|
|
27
27
|
return await import("./subcommands.ts");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
describe("subcommands.parseArgs", () => {
|
|
31
|
-
it("collects positional args and bare flags", async () => {
|
|
32
|
-
const { parseArgs } = await loadModule();
|
|
33
|
-
const out = parseArgs(["foo", "bar", "--all"]);
|
|
34
|
-
expect(out.positional).toEqual(["foo", "bar"]);
|
|
35
|
-
expect(out.flags.all).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("parses --key=value form", async () => {
|
|
39
|
-
const { parseArgs } = await loadModule();
|
|
40
|
-
const out = parseArgs(["--code=enter"]);
|
|
41
|
-
expect(out.flags.code).toBe("enter");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("parses --key value form for non-boolean keys", async () => {
|
|
45
|
-
const { parseArgs } = await loadModule();
|
|
46
|
-
const out = parseArgs(["--cwd", "/tmp/foo"]);
|
|
47
|
-
expect(out.flags.cwd).toBe("/tmp/foo");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("treats well-known boolean flags as boolean even with a following positional", async () => {
|
|
51
|
-
const { parseArgs } = await loadModule();
|
|
52
|
-
const out = parseArgs(["--all", "claude"]);
|
|
53
|
-
expect(out.flags.all).toBe(true);
|
|
54
|
-
expect(out.positional).toEqual(["claude"]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("supports -n N short form", async () => {
|
|
58
|
-
const { parseArgs } = await loadModule();
|
|
59
|
-
const out = parseArgs(["-n", "50", "keyword"]);
|
|
60
|
-
expect(out.flags.n).toBe("50");
|
|
61
|
-
expect(out.positional).toEqual(["keyword"]);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
30
|
describe("subcommands.controlCodeFromName", () => {
|
|
66
31
|
it("maps named codes to the right control bytes", async () => {
|
|
67
32
|
const { controlCodeFromName } = await loadModule();
|
|
@@ -525,6 +490,484 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
|
|
|
525
490
|
});
|
|
526
491
|
});
|
|
527
492
|
|
|
493
|
+
// ---------------------------------------------------------------------------
|
|
494
|
+
// cmdLs additional arg coverage
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
describe("subcommands.cmdLs -h / --help", () => {
|
|
498
|
+
function captureStdout() {
|
|
499
|
+
const chunks: string[] = [];
|
|
500
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
501
|
+
(process.stdout as any).write = (s: any) => {
|
|
502
|
+
chunks.push(String(s));
|
|
503
|
+
return true;
|
|
504
|
+
};
|
|
505
|
+
return {
|
|
506
|
+
get text() {
|
|
507
|
+
return chunks.join("");
|
|
508
|
+
},
|
|
509
|
+
restore() {
|
|
510
|
+
process.stdout.write = orig;
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
it("ay ls -h prints usage to stdout and exits 0", async () => {
|
|
516
|
+
const { runSubcommand } = await loadModule();
|
|
517
|
+
const cap = captureStdout();
|
|
518
|
+
let code: number | null;
|
|
519
|
+
try {
|
|
520
|
+
code = await runSubcommand(["bun", "cli.js", "ls", "-h"]);
|
|
521
|
+
} finally {
|
|
522
|
+
cap.restore();
|
|
523
|
+
}
|
|
524
|
+
expect(code).toBe(0);
|
|
525
|
+
expect(cap.text).toMatch(/Usage:/);
|
|
526
|
+
expect(cap.text).toMatch(/--all/);
|
|
527
|
+
expect(cap.text).toMatch(/--json/);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("ay ls --help prints usage to stdout and exits 0", async () => {
|
|
531
|
+
const { runSubcommand } = await loadModule();
|
|
532
|
+
const cap = captureStdout();
|
|
533
|
+
let code: number | null;
|
|
534
|
+
try {
|
|
535
|
+
code = await runSubcommand(["bun", "cli.js", "ls", "--help"]);
|
|
536
|
+
} finally {
|
|
537
|
+
cap.restore();
|
|
538
|
+
}
|
|
539
|
+
expect(code).toBe(0);
|
|
540
|
+
expect(cap.text).toMatch(/Usage:/);
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
describe("subcommands.cmdLs --all / --active / keyword filter / aliases", () => {
|
|
545
|
+
function captureOutput() {
|
|
546
|
+
const out: string[] = [];
|
|
547
|
+
const err: string[] = [];
|
|
548
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
549
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
550
|
+
(process.stdout as any).write = (s: any) => {
|
|
551
|
+
out.push(String(s));
|
|
552
|
+
return true;
|
|
553
|
+
};
|
|
554
|
+
(process.stderr as any).write = (s: any) => {
|
|
555
|
+
err.push(String(s));
|
|
556
|
+
return true;
|
|
557
|
+
};
|
|
558
|
+
return {
|
|
559
|
+
get stdout() {
|
|
560
|
+
return out.join("");
|
|
561
|
+
},
|
|
562
|
+
get stderr() {
|
|
563
|
+
return err.join("");
|
|
564
|
+
},
|
|
565
|
+
restore() {
|
|
566
|
+
process.stdout.write = origOut;
|
|
567
|
+
process.stderr.write = origErr;
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
it("--all shows exited agents", async () => {
|
|
573
|
+
const mod = await loadModule();
|
|
574
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
575
|
+
await appendGlobalPid({
|
|
576
|
+
pid: 1, // pid 1 is almost never the test process, so isPidAlive returns false
|
|
577
|
+
cli: "claude",
|
|
578
|
+
prompt: "exited agent",
|
|
579
|
+
cwd: process.cwd(),
|
|
580
|
+
log_file: null,
|
|
581
|
+
status: "exited",
|
|
582
|
+
exit_code: 0,
|
|
583
|
+
exit_reason: "done",
|
|
584
|
+
started_at: Date.now() - 10_000,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const cap = captureOutput();
|
|
588
|
+
let code: number | null;
|
|
589
|
+
try {
|
|
590
|
+
code = await mod.runSubcommand(["bun", "cli.js", "ls", "--all", "--json"]);
|
|
591
|
+
} finally {
|
|
592
|
+
cap.restore();
|
|
593
|
+
}
|
|
594
|
+
expect(code).toBe(0);
|
|
595
|
+
const parsed = JSON.parse(cap.stdout);
|
|
596
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
597
|
+
expect(parsed.some((r: any) => r.prompt === "exited agent")).toBe(true);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("keyword filter restricts results to matching agents", async () => {
|
|
601
|
+
const mod = await loadModule();
|
|
602
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
603
|
+
await appendGlobalPid({
|
|
604
|
+
pid: process.pid,
|
|
605
|
+
cli: "claude",
|
|
606
|
+
prompt: "unique-xyzzy-prompt",
|
|
607
|
+
cwd: process.cwd(),
|
|
608
|
+
log_file: null,
|
|
609
|
+
status: "active",
|
|
610
|
+
exit_code: null,
|
|
611
|
+
exit_reason: null,
|
|
612
|
+
started_at: Date.now(),
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
const cap = captureOutput();
|
|
616
|
+
let code: number | null;
|
|
617
|
+
try {
|
|
618
|
+
code = await mod.runSubcommand(["bun", "cli.js", "ls", "--json", "unique-xyzzy-prompt"]);
|
|
619
|
+
} finally {
|
|
620
|
+
cap.restore();
|
|
621
|
+
}
|
|
622
|
+
expect(code).toBe(0);
|
|
623
|
+
const parsed = JSON.parse(cap.stdout);
|
|
624
|
+
expect(parsed.every((r: any) => r.prompt?.includes("unique-xyzzy-prompt"))).toBe(true);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("keyword filter returns 'no running agents' when nothing matches", async () => {
|
|
628
|
+
const { runSubcommand } = await loadModule();
|
|
629
|
+
const stderr: string[] = [];
|
|
630
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
631
|
+
(process.stderr as any).write = (s: any) => {
|
|
632
|
+
stderr.push(String(s));
|
|
633
|
+
return true;
|
|
634
|
+
};
|
|
635
|
+
try {
|
|
636
|
+
const code = await runSubcommand(["bun", "cli.js", "ls", "no-match-zzzzzz"]);
|
|
637
|
+
expect(code).toBe(0);
|
|
638
|
+
expect(stderr.join("")).toMatch(/no running agents matched/);
|
|
639
|
+
} finally {
|
|
640
|
+
process.stderr.write = orig;
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("list alias routes to cmdLs", async () => {
|
|
645
|
+
const { runSubcommand } = await loadModule();
|
|
646
|
+
const stderr: string[] = [];
|
|
647
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
648
|
+
(process.stderr as any).write = (s: any) => {
|
|
649
|
+
stderr.push(String(s));
|
|
650
|
+
return true;
|
|
651
|
+
};
|
|
652
|
+
try {
|
|
653
|
+
const code = await runSubcommand(["bun", "cli.js", "list"]);
|
|
654
|
+
expect(code).toBe(0);
|
|
655
|
+
} finally {
|
|
656
|
+
process.stderr.write = orig;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("ps alias routes to cmdLs", async () => {
|
|
661
|
+
const { runSubcommand } = await loadModule();
|
|
662
|
+
const stderr: string[] = [];
|
|
663
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
664
|
+
(process.stderr as any).write = (s: any) => {
|
|
665
|
+
stderr.push(String(s));
|
|
666
|
+
return true;
|
|
667
|
+
};
|
|
668
|
+
try {
|
|
669
|
+
const code = await runSubcommand(["bun", "cli.js", "ps"]);
|
|
670
|
+
expect(code).toBe(0);
|
|
671
|
+
} finally {
|
|
672
|
+
process.stderr.write = orig;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ---------------------------------------------------------------------------
|
|
678
|
+
// cmdRead — head and cat modes
|
|
679
|
+
// ---------------------------------------------------------------------------
|
|
680
|
+
|
|
681
|
+
describe("subcommands.cmdRead head and cat modes", () => {
|
|
682
|
+
it("head emits first N lines", async () => {
|
|
683
|
+
const { runSubcommand } = await loadModule();
|
|
684
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
685
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "ay-head-log-"));
|
|
686
|
+
try {
|
|
687
|
+
const logPath = path.join(tmp, "x.raw.log");
|
|
688
|
+
const lines: string[] = [];
|
|
689
|
+
for (let i = 0; i < 50; i++) lines.push(`line-${i}`);
|
|
690
|
+
await writeFile(logPath, lines.join("\r\n") + "\r\n");
|
|
691
|
+
|
|
692
|
+
await appendGlobalPid({
|
|
693
|
+
pid: process.pid,
|
|
694
|
+
cli: "claude",
|
|
695
|
+
prompt: null,
|
|
696
|
+
cwd: process.cwd(),
|
|
697
|
+
log_file: logPath,
|
|
698
|
+
status: "active",
|
|
699
|
+
exit_code: null,
|
|
700
|
+
exit_reason: null,
|
|
701
|
+
started_at: Date.now(),
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
const stdout: string[] = [];
|
|
705
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
706
|
+
(process.stdout as any).write = (s: any) => {
|
|
707
|
+
stdout.push(String(s));
|
|
708
|
+
return true;
|
|
709
|
+
};
|
|
710
|
+
const stderr_chunks: string[] = [];
|
|
711
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
712
|
+
(process.stderr as any).write = (s: any) => {
|
|
713
|
+
stderr_chunks.push(String(s));
|
|
714
|
+
return true;
|
|
715
|
+
};
|
|
716
|
+
try {
|
|
717
|
+
const code = await runSubcommand(["bun", "cli.js", "head", String(process.pid), "-n", "5"]);
|
|
718
|
+
expect(code).toBe(0);
|
|
719
|
+
} finally {
|
|
720
|
+
process.stdout.write = orig;
|
|
721
|
+
process.stderr.write = origErr;
|
|
722
|
+
}
|
|
723
|
+
const text = stdout.join("");
|
|
724
|
+
expect(text).toMatch(/line-0/);
|
|
725
|
+
expect(text).toMatch(/line-4/);
|
|
726
|
+
expect(text).not.toMatch(/line-10\b/);
|
|
727
|
+
} finally {
|
|
728
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => null);
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("cat emits all lines", async () => {
|
|
733
|
+
const { runSubcommand } = await loadModule();
|
|
734
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
735
|
+
const tmp = await mkdtemp(path.join(tmpdir(), "ay-cat-log-"));
|
|
736
|
+
try {
|
|
737
|
+
const logPath = path.join(tmp, "x.raw.log");
|
|
738
|
+
await writeFile(logPath, "alpha\r\nbeta\r\ngamma\r\n");
|
|
739
|
+
|
|
740
|
+
await appendGlobalPid({
|
|
741
|
+
pid: process.pid,
|
|
742
|
+
cli: "claude",
|
|
743
|
+
prompt: null,
|
|
744
|
+
cwd: process.cwd(),
|
|
745
|
+
log_file: logPath,
|
|
746
|
+
status: "active",
|
|
747
|
+
exit_code: null,
|
|
748
|
+
exit_reason: null,
|
|
749
|
+
started_at: Date.now(),
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
const stdout: string[] = [];
|
|
753
|
+
const orig = process.stdout.write.bind(process.stdout);
|
|
754
|
+
(process.stdout as any).write = (s: any) => {
|
|
755
|
+
stdout.push(String(s));
|
|
756
|
+
return true;
|
|
757
|
+
};
|
|
758
|
+
const stderr_chunks: string[] = [];
|
|
759
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
760
|
+
(process.stderr as any).write = (s: any) => {
|
|
761
|
+
stderr_chunks.push(String(s));
|
|
762
|
+
return true;
|
|
763
|
+
};
|
|
764
|
+
try {
|
|
765
|
+
const code = await runSubcommand(["bun", "cli.js", "cat", String(process.pid)]);
|
|
766
|
+
expect(code).toBe(0);
|
|
767
|
+
} finally {
|
|
768
|
+
process.stdout.write = orig;
|
|
769
|
+
process.stderr.write = origErr;
|
|
770
|
+
}
|
|
771
|
+
const text = stdout.join("");
|
|
772
|
+
expect(text).toMatch(/alpha/);
|
|
773
|
+
expect(text).toMatch(/beta/);
|
|
774
|
+
expect(text).toMatch(/gamma/);
|
|
775
|
+
} finally {
|
|
776
|
+
await rm(tmp, { recursive: true, force: true }).catch(() => null);
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// ---------------------------------------------------------------------------
|
|
782
|
+
// cmdNote
|
|
783
|
+
// ---------------------------------------------------------------------------
|
|
784
|
+
|
|
785
|
+
describe("subcommands.cmdNote", () => {
|
|
786
|
+
it("throws usage error when no keyword given", async () => {
|
|
787
|
+
const { runSubcommand } = await loadModule();
|
|
788
|
+
const stderr: string[] = [];
|
|
789
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
790
|
+
(process.stderr as any).write = (s: any) => {
|
|
791
|
+
stderr.push(String(s));
|
|
792
|
+
return true;
|
|
793
|
+
};
|
|
794
|
+
try {
|
|
795
|
+
const code = await runSubcommand(["bun", "cli.js", "note"]);
|
|
796
|
+
expect(code).toBe(1);
|
|
797
|
+
expect(stderr.join("")).toMatch(/usage:/i);
|
|
798
|
+
} finally {
|
|
799
|
+
process.stderr.write = orig;
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("sets a note on a matched agent", async () => {
|
|
804
|
+
const mod = await loadModule();
|
|
805
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
806
|
+
await appendGlobalPid({
|
|
807
|
+
pid: process.pid,
|
|
808
|
+
cli: "claude",
|
|
809
|
+
prompt: "note-target",
|
|
810
|
+
cwd: process.cwd(),
|
|
811
|
+
log_file: null,
|
|
812
|
+
status: "active",
|
|
813
|
+
exit_code: null,
|
|
814
|
+
exit_reason: null,
|
|
815
|
+
started_at: Date.now(),
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
const stdout: string[] = [];
|
|
819
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
820
|
+
(process.stdout as any).write = (s: any) => {
|
|
821
|
+
stdout.push(String(s));
|
|
822
|
+
return true;
|
|
823
|
+
};
|
|
824
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
825
|
+
(process.stderr as any).write = () => true;
|
|
826
|
+
try {
|
|
827
|
+
const code = await mod.runSubcommand([
|
|
828
|
+
"bun",
|
|
829
|
+
"cli.js",
|
|
830
|
+
"note",
|
|
831
|
+
String(process.pid),
|
|
832
|
+
"my note text",
|
|
833
|
+
]);
|
|
834
|
+
expect(code).toBe(0);
|
|
835
|
+
expect(stdout.join("")).toMatch(/note set/);
|
|
836
|
+
} finally {
|
|
837
|
+
process.stdout.write = origOut;
|
|
838
|
+
process.stderr.write = origErr;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
it("clears a note when no text given", async () => {
|
|
843
|
+
const mod = await loadModule();
|
|
844
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
845
|
+
await appendGlobalPid({
|
|
846
|
+
pid: process.pid,
|
|
847
|
+
cli: "claude",
|
|
848
|
+
prompt: "note-clear-target",
|
|
849
|
+
cwd: process.cwd(),
|
|
850
|
+
log_file: null,
|
|
851
|
+
status: "active",
|
|
852
|
+
exit_code: null,
|
|
853
|
+
exit_reason: null,
|
|
854
|
+
started_at: Date.now(),
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
const stdout: string[] = [];
|
|
858
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
859
|
+
(process.stdout as any).write = (s: any) => {
|
|
860
|
+
stdout.push(String(s));
|
|
861
|
+
return true;
|
|
862
|
+
};
|
|
863
|
+
(process.stderr as any).write = () => true;
|
|
864
|
+
try {
|
|
865
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "note", String(process.pid)]);
|
|
866
|
+
expect(code).toBe(0);
|
|
867
|
+
expect(stdout.join("")).toMatch(/cleared note/);
|
|
868
|
+
} finally {
|
|
869
|
+
process.stdout.write = origOut;
|
|
870
|
+
process.stderr.write = process.stderr.write; // no-op restore (silenced above)
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
// ---------------------------------------------------------------------------
|
|
876
|
+
// cmdStatus
|
|
877
|
+
// ---------------------------------------------------------------------------
|
|
878
|
+
|
|
879
|
+
describe("subcommands.cmdStatus", () => {
|
|
880
|
+
it("throws usage error when no keyword given", async () => {
|
|
881
|
+
const { runSubcommand } = await loadModule();
|
|
882
|
+
const stderr: string[] = [];
|
|
883
|
+
const orig = process.stderr.write.bind(process.stderr);
|
|
884
|
+
(process.stderr as any).write = (s: any) => {
|
|
885
|
+
stderr.push(String(s));
|
|
886
|
+
return true;
|
|
887
|
+
};
|
|
888
|
+
try {
|
|
889
|
+
const code = await runSubcommand(["bun", "cli.js", "status"]);
|
|
890
|
+
expect(code).toBe(1);
|
|
891
|
+
expect(stderr.join("")).toMatch(/usage:/i);
|
|
892
|
+
} finally {
|
|
893
|
+
process.stderr.write = orig;
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
it("emits JSON snapshot for a matched agent", async () => {
|
|
898
|
+
const mod = await loadModule();
|
|
899
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
900
|
+
await appendGlobalPid({
|
|
901
|
+
pid: process.pid,
|
|
902
|
+
cli: "claude",
|
|
903
|
+
prompt: "status-test",
|
|
904
|
+
cwd: process.cwd(),
|
|
905
|
+
log_file: null,
|
|
906
|
+
status: "active",
|
|
907
|
+
exit_code: null,
|
|
908
|
+
exit_reason: null,
|
|
909
|
+
started_at: Date.now() - 1000,
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
const stdout: string[] = [];
|
|
913
|
+
const origOut = process.stdout.write.bind(process.stdout);
|
|
914
|
+
(process.stdout as any).write = (s: any) => {
|
|
915
|
+
stdout.push(String(s));
|
|
916
|
+
return true;
|
|
917
|
+
};
|
|
918
|
+
(process.stderr as any).write = () => true;
|
|
919
|
+
try {
|
|
920
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "status", String(process.pid)]);
|
|
921
|
+
expect(code).toBe(0);
|
|
922
|
+
} finally {
|
|
923
|
+
process.stdout.write = origOut;
|
|
924
|
+
}
|
|
925
|
+
const snap = JSON.parse(stdout.join(""));
|
|
926
|
+
expect(snap).toMatchObject({ pid: process.pid, cli: "claude" });
|
|
927
|
+
expect(typeof snap.age_ms).toBe("number");
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// ---------------------------------------------------------------------------
|
|
932
|
+
// cmdRestart
|
|
933
|
+
// ---------------------------------------------------------------------------
|
|
934
|
+
|
|
935
|
+
describe("subcommands.cmdRestart", () => {
|
|
936
|
+
it("returns 1 and warns when the agent is still alive", async () => {
|
|
937
|
+
const mod = await loadModule();
|
|
938
|
+
const { appendGlobalPid } = await import("./globalPidIndex.ts");
|
|
939
|
+
await appendGlobalPid({
|
|
940
|
+
pid: process.pid,
|
|
941
|
+
cli: "claude",
|
|
942
|
+
prompt: "restart-live-test",
|
|
943
|
+
cwd: process.cwd(),
|
|
944
|
+
log_file: null,
|
|
945
|
+
status: "active",
|
|
946
|
+
exit_code: null,
|
|
947
|
+
exit_reason: null,
|
|
948
|
+
started_at: Date.now(),
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const stderr: string[] = [];
|
|
952
|
+
const origErr = process.stderr.write.bind(process.stderr);
|
|
953
|
+
(process.stderr as any).write = (s: any) => {
|
|
954
|
+
stderr.push(String(s));
|
|
955
|
+
return true;
|
|
956
|
+
};
|
|
957
|
+
try {
|
|
958
|
+
const code = await mod.runSubcommand(["bun", "cli.js", "restart", String(process.pid)]);
|
|
959
|
+
expect(code).toBe(1);
|
|
960
|
+
expect(stderr.join("")).toMatch(/still running/);
|
|
961
|
+
} finally {
|
|
962
|
+
process.stderr.write = origErr;
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
// ---------------------------------------------------------------------------
|
|
968
|
+
// listRecords merges per-cwd TS file with global
|
|
969
|
+
// ---------------------------------------------------------------------------
|
|
970
|
+
|
|
528
971
|
describe("subcommands.listRecords merges per-cwd TS file with global", () => {
|
|
529
972
|
it("includes records from <cwd>/.agent-yes/pid-records.jsonl", async () => {
|
|
530
973
|
// Write a fake per-cwd file that uses the live process pid so liveOnly
|
package/ts/subcommands.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises"
|
|
|
15
15
|
import { homedir } from "os";
|
|
16
16
|
import path from "path";
|
|
17
17
|
import { type GlobalPidRecord, readGlobalPids } from "./globalPidIndex.ts";
|
|
18
|
+
import yargs from "yargs";
|
|
18
19
|
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// notes store (~/.agent-yes/notes.jsonl)
|
|
@@ -196,65 +197,6 @@ interface CommonOpts {
|
|
|
196
197
|
json: boolean;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
interface ParsedArgs {
|
|
200
|
-
flags: Record<string, string | boolean>;
|
|
201
|
-
positional: string[];
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function parseArgs(rest: string[]): ParsedArgs {
|
|
205
|
-
const flags: Record<string, string | boolean> = {};
|
|
206
|
-
const positional: string[] = [];
|
|
207
|
-
for (let i = 0; i < rest.length; i++) {
|
|
208
|
-
const arg = rest[i]!;
|
|
209
|
-
if (arg.startsWith("--")) {
|
|
210
|
-
const eq = arg.indexOf("=");
|
|
211
|
-
if (eq >= 0) {
|
|
212
|
-
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
213
|
-
} else {
|
|
214
|
-
const key = arg.slice(2);
|
|
215
|
-
const next = rest[i + 1];
|
|
216
|
-
// Boolean flags: --all, --json, --latest
|
|
217
|
-
if (
|
|
218
|
-
["all", "active", "follow", "json", "latest", "watch"].includes(key) ||
|
|
219
|
-
!next ||
|
|
220
|
-
next.startsWith("-")
|
|
221
|
-
) {
|
|
222
|
-
flags[key] = true;
|
|
223
|
-
} else {
|
|
224
|
-
flags[key] = next;
|
|
225
|
-
i++;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
} else if (arg.startsWith("-") && arg.length > 1) {
|
|
229
|
-
// -n N short flag
|
|
230
|
-
if (arg === "-n") {
|
|
231
|
-
flags["n"] = rest[i + 1] ?? "";
|
|
232
|
-
i++;
|
|
233
|
-
} else {
|
|
234
|
-
flags[arg.slice(1)] = true;
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
positional.push(arg);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
return { flags, positional };
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function commonOpts(flags: Record<string, string | boolean>): CommonOpts {
|
|
244
|
-
return {
|
|
245
|
-
all: !!flags.all,
|
|
246
|
-
active: !!flags.active,
|
|
247
|
-
cwdScope:
|
|
248
|
-
typeof flags.cwd === "string"
|
|
249
|
-
? path.resolve(flags.cwd)
|
|
250
|
-
: flags.cwd === true
|
|
251
|
-
? process.cwd()
|
|
252
|
-
: null,
|
|
253
|
-
latest: !!flags.latest,
|
|
254
|
-
json: !!flags.json,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
200
|
export function matchKeyword(record: GlobalPidRecord, keyword: string): boolean {
|
|
259
201
|
if (!keyword) return true;
|
|
260
202
|
const kw = keyword.toLowerCase();
|
|
@@ -331,9 +273,54 @@ async function resolveOne(keyword: string | undefined, opts: CommonOpts): Promis
|
|
|
331
273
|
// ---------------------------------------------------------------------------
|
|
332
274
|
|
|
333
275
|
async function cmdLs(rest: string[]): Promise<number> {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
276
|
+
const y = yargs(rest)
|
|
277
|
+
.usage(
|
|
278
|
+
"Usage: ay ls [keyword] [options]\n" +
|
|
279
|
+
" ay list [keyword] [options]\n" +
|
|
280
|
+
" ay ps [keyword] [options]\n\n" +
|
|
281
|
+
"List running agents. Optionally filter by keyword (pid, cwd substring, or prompt substring).",
|
|
282
|
+
)
|
|
283
|
+
.option("all", {
|
|
284
|
+
type: "boolean",
|
|
285
|
+
default: false,
|
|
286
|
+
description: "Show all agents including exited ones",
|
|
287
|
+
})
|
|
288
|
+
.option("active", {
|
|
289
|
+
type: "boolean",
|
|
290
|
+
default: false,
|
|
291
|
+
description: "Only show agents with an alive process",
|
|
292
|
+
})
|
|
293
|
+
.option("json", { type: "boolean", default: false, description: "Output as JSON array" })
|
|
294
|
+
.option("latest", {
|
|
295
|
+
type: "boolean",
|
|
296
|
+
default: false,
|
|
297
|
+
description: "Show only the most recent agent",
|
|
298
|
+
})
|
|
299
|
+
.option("cwd", { type: "string", description: "Restrict to agents whose cwd starts with dir" })
|
|
300
|
+
.option("help", { alias: "h", type: "boolean", default: false, description: "Show this help" })
|
|
301
|
+
.example("ay ls", "list running agents")
|
|
302
|
+
.example("ay ls --all", "include exited agents")
|
|
303
|
+
.example("ay ls --json", "machine-readable output")
|
|
304
|
+
.example("ay ls symval", "filter by cwd/prompt keyword")
|
|
305
|
+
.help(false)
|
|
306
|
+
.version(false)
|
|
307
|
+
.exitProcess(false);
|
|
308
|
+
|
|
309
|
+
const argv = await y.parseAsync();
|
|
310
|
+
|
|
311
|
+
if (argv.help || argv.h) {
|
|
312
|
+
process.stdout.write((await y.getHelp()) + "\n");
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
317
|
+
const opts: CommonOpts = {
|
|
318
|
+
all: argv.all,
|
|
319
|
+
active: argv.active,
|
|
320
|
+
json: argv.json,
|
|
321
|
+
latest: argv.latest,
|
|
322
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
323
|
+
};
|
|
337
324
|
const records = await listRecords(keyword, opts);
|
|
338
325
|
|
|
339
326
|
if (opts.json) {
|
|
@@ -488,12 +475,37 @@ interface ReadOpts {
|
|
|
488
475
|
}
|
|
489
476
|
|
|
490
477
|
async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
478
|
+
const y = yargs(rest)
|
|
479
|
+
.usage("Usage: ay read/cat/tail/head <keyword> [options]")
|
|
480
|
+
.option("follow", {
|
|
481
|
+
alias: "f",
|
|
482
|
+
type: "boolean",
|
|
483
|
+
default: false,
|
|
484
|
+
description: "Follow log output (Ctrl-C to stop)",
|
|
485
|
+
})
|
|
486
|
+
.option("n", { type: "number", description: "Number of lines (default: 96 for tail/head)" })
|
|
487
|
+
.option("all", { type: "boolean", default: false, description: "Include exited agents" })
|
|
488
|
+
.option("latest", {
|
|
489
|
+
type: "boolean",
|
|
490
|
+
default: false,
|
|
491
|
+
description: "Use most recent match when multiple match",
|
|
492
|
+
})
|
|
493
|
+
.option("cwd", { type: "string", description: "Restrict to agents under this dir" })
|
|
494
|
+
.help(false)
|
|
495
|
+
.version(false)
|
|
496
|
+
.exitProcess(false);
|
|
497
|
+
|
|
498
|
+
const argv = await y.parseAsync();
|
|
499
|
+
const opts: CommonOpts = {
|
|
500
|
+
all: argv.all,
|
|
501
|
+
active: false,
|
|
502
|
+
json: false,
|
|
503
|
+
latest: argv.latest,
|
|
504
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
505
|
+
};
|
|
506
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
507
|
+
const follow = argv.follow;
|
|
508
|
+
const nFlag = argv.n;
|
|
497
509
|
const n =
|
|
498
510
|
nFlag !== undefined && Number.isFinite(nFlag) && nFlag > 0
|
|
499
511
|
? Math.floor(nFlag)
|
|
@@ -732,15 +744,35 @@ function extractActivityFromLines(lines: string[]): string | null {
|
|
|
732
744
|
// ---------------------------------------------------------------------------
|
|
733
745
|
|
|
734
746
|
async function cmdSend(rest: string[]): Promise<number> {
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
747
|
+
const y = yargs(rest)
|
|
748
|
+
.usage("Usage: ay send <keyword> <msg|-> [options]")
|
|
749
|
+
.option("code", {
|
|
750
|
+
type: "string",
|
|
751
|
+
default: "enter",
|
|
752
|
+
description: "Trailing control code (enter|esc|ctrl-c|ctrl-y|tab|none)",
|
|
753
|
+
})
|
|
754
|
+
.option("all", { type: "boolean", default: false, description: "Include exited agents" })
|
|
755
|
+
.option("latest", { type: "boolean", default: false, description: "Use most recent match" })
|
|
756
|
+
.option("cwd", { type: "string", description: "Restrict to agents under this dir" })
|
|
757
|
+
.help(false)
|
|
758
|
+
.version(false)
|
|
759
|
+
.exitProcess(false);
|
|
760
|
+
|
|
761
|
+
const argv = await y.parseAsync();
|
|
762
|
+
const opts: CommonOpts = {
|
|
763
|
+
all: argv.all,
|
|
764
|
+
active: false,
|
|
765
|
+
json: false,
|
|
766
|
+
latest: argv.latest,
|
|
767
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
768
|
+
};
|
|
769
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
770
|
+
const rawMessage = argv._.slice(1).map(String).join(" ");
|
|
739
771
|
|
|
740
772
|
if (!keyword)
|
|
741
773
|
throw new Error("usage: ay send <keyword> <msg|-> [--code=enter|esc|ctrl-c|ctrl-y|tab|none]");
|
|
742
774
|
|
|
743
|
-
const codeName =
|
|
775
|
+
const codeName = argv.code.toLowerCase();
|
|
744
776
|
const trailing = controlCodeFromName(codeName);
|
|
745
777
|
|
|
746
778
|
const record = await resolveOne(keyword, opts);
|
|
@@ -855,9 +887,23 @@ async function writeToIpc(ipcPath: string, payload: string): Promise<void> {
|
|
|
855
887
|
// ---------------------------------------------------------------------------
|
|
856
888
|
|
|
857
889
|
async function cmdRestart(rest: string[]): Promise<number> {
|
|
858
|
-
const
|
|
859
|
-
|
|
860
|
-
|
|
890
|
+
const y = yargs(rest)
|
|
891
|
+
.usage("Usage: ay restart <keyword>")
|
|
892
|
+
.option("latest", { type: "boolean", default: false, description: "Use most recent match" })
|
|
893
|
+
.option("cwd", { type: "string", description: "Restrict to agents under this dir" })
|
|
894
|
+
.help(false)
|
|
895
|
+
.version(false)
|
|
896
|
+
.exitProcess(false);
|
|
897
|
+
|
|
898
|
+
const argv = await y.parseAsync();
|
|
899
|
+
const opts: CommonOpts = {
|
|
900
|
+
all: true,
|
|
901
|
+
active: false,
|
|
902
|
+
json: false,
|
|
903
|
+
latest: argv.latest,
|
|
904
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
905
|
+
};
|
|
906
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
861
907
|
const record = await resolveOne(keyword, opts);
|
|
862
908
|
|
|
863
909
|
if (isPidAlive(record.pid)) {
|
|
@@ -890,14 +936,25 @@ async function cmdRestart(rest: string[]): Promise<number> {
|
|
|
890
936
|
// ---------------------------------------------------------------------------
|
|
891
937
|
|
|
892
938
|
async function cmdNote(rest: string[]): Promise<number> {
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
939
|
+
const y = yargs(rest)
|
|
940
|
+
.usage('Usage: ay note <keyword> ["note text"]')
|
|
941
|
+
.help(false)
|
|
942
|
+
.version(false)
|
|
943
|
+
.exitProcess(false);
|
|
944
|
+
|
|
945
|
+
const argv = await y.parseAsync();
|
|
946
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
947
|
+
const note = argv._.slice(1).map(String).join(" ");
|
|
897
948
|
|
|
898
949
|
if (!keyword) throw new Error('usage: ay note <keyword> ["note text"] (omit text to clear)');
|
|
899
950
|
|
|
900
|
-
const record = await resolveOne(keyword, {
|
|
951
|
+
const record = await resolveOne(keyword, {
|
|
952
|
+
all: true,
|
|
953
|
+
active: false,
|
|
954
|
+
json: false,
|
|
955
|
+
latest: false,
|
|
956
|
+
cwdScope: null,
|
|
957
|
+
});
|
|
901
958
|
|
|
902
959
|
if (!note) {
|
|
903
960
|
// clear
|
|
@@ -967,14 +1024,35 @@ async function snapshotStatus(record: GlobalPidRecord): Promise<StatusSnapshot>
|
|
|
967
1024
|
}
|
|
968
1025
|
|
|
969
1026
|
async function cmdStatus(rest: string[]): Promise<number> {
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
1027
|
+
const y = yargs(rest)
|
|
1028
|
+
.usage("Usage: ay status <keyword> [options]")
|
|
1029
|
+
.option("watch", {
|
|
1030
|
+
alias: "w",
|
|
1031
|
+
type: "boolean",
|
|
1032
|
+
default: false,
|
|
1033
|
+
description: "Stream changes as JSON",
|
|
1034
|
+
})
|
|
1035
|
+
.option("interval", { type: "number", default: 2, description: "Poll interval in seconds" })
|
|
1036
|
+
.option("latest", { type: "boolean", default: false, description: "Use most recent match" })
|
|
1037
|
+
.option("cwd", { type: "string", description: "Restrict to agents under this dir" })
|
|
1038
|
+
.help(false)
|
|
1039
|
+
.version(false)
|
|
1040
|
+
.exitProcess(false);
|
|
1041
|
+
|
|
1042
|
+
const argv = await y.parseAsync();
|
|
1043
|
+
const opts: CommonOpts = {
|
|
1044
|
+
all: true,
|
|
1045
|
+
active: false,
|
|
1046
|
+
json: false,
|
|
1047
|
+
latest: argv.latest,
|
|
1048
|
+
cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null,
|
|
1049
|
+
};
|
|
1050
|
+
const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
|
|
973
1051
|
|
|
974
1052
|
if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
|
|
975
1053
|
|
|
976
|
-
const watch =
|
|
977
|
-
const intervalFlag =
|
|
1054
|
+
const watch = argv.watch;
|
|
1055
|
+
const intervalFlag = argv.interval;
|
|
978
1056
|
const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1000);
|
|
979
1057
|
|
|
980
1058
|
const record = await resolveOne(keyword, opts);
|