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.
@@ -1,6 +1,6 @@
1
- import { t as CLIS_CONFIG } from "./ts-Bw6gQKyU.js";
1
+ import { t as CLIS_CONFIG } from "./ts-BTCVz_nZ.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-CspuhOwO.js";
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-DM0fJTMR.js.map
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-CspuhOwO.js";
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-DjO8lthH.js");
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-DM0fJTMR.js");
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-Bw6gQKyU.js";
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-CspuhOwO.js";
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 { flags, positional } = parseArgs(rest);
247
- const opts = commonOpts(flags);
248
- const keyword = positional[0];
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 { flags, positional } = parseArgs(rest);
353
- const opts = commonOpts(flags);
354
- const keyword = positional[0];
355
- const follow = !!(flags.f || flags.follow);
356
- const nFlag = typeof flags.n === "string" ? Number(flags.n) : void 0;
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 { flags, positional } = parseArgs(rest);
516
- const opts = commonOpts(flags);
517
- const keyword = positional[0];
518
- const rawMessage = positional.slice(1).join(" ");
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(typeof flags.code === "string" ? flags.code.toLowerCase() : "enter");
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 { flags, positional } = parseArgs(rest);
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
- ...commonOpts(flags),
600
- all: true
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 keyword = positional[0];
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 { flags, positional } = parseArgs(rest);
625
- const opts = commonOpts(flags);
626
- const keyword = positional[0];
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
- ...opts,
631
- all: true
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 { flags, positional } = parseArgs(rest);
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
- ...commonOpts(flags),
674
- all: true
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 = positional[0];
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 = !!(flags.watch || flags.w);
679
- const intervalFlag = typeof flags.interval === "string" ? Number(flags.interval) : 2;
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-DjO8lthH.js.map
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-CspuhOwO.js";
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-Bw6gQKyU.js.map
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.84.0";
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-CspuhOwO.js.map
224
+ //# sourceMappingURL=versionChecker-Cir2ZoFL.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.84.0",
3
+ "version": "1.85.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -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 { flags, positional } = parseArgs(rest);
335
- const opts = commonOpts(flags);
336
- const keyword = positional[0];
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 { flags, positional } = parseArgs(rest);
492
- const opts = commonOpts(flags);
493
- const keyword = positional[0];
494
- const follow = !!(flags.f || flags.follow);
495
-
496
- const nFlag = typeof flags.n === "string" ? Number(flags.n) : undefined;
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 { flags, positional } = parseArgs(rest);
736
- const opts = commonOpts(flags);
737
- const keyword = positional[0];
738
- const rawMessage = positional.slice(1).join(" ");
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 = typeof flags.code === "string" ? flags.code.toLowerCase() : "enter";
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 { flags, positional } = parseArgs(rest);
859
- const opts = { ...commonOpts(flags), all: true }; // search stopped agents too
860
- const keyword = positional[0];
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 { flags, positional } = parseArgs(rest);
894
- const opts = commonOpts(flags);
895
- const keyword = positional[0];
896
- const note = positional.slice(1).join(" ");
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, { ...opts, all: true });
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 { flags, positional } = parseArgs(rest);
971
- const opts = { ...commonOpts(flags), all: true };
972
- const keyword = positional[0];
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 = !!(flags.watch || flags.w);
977
- const intervalFlag = typeof flags.interval === "string" ? Number(flags.interval) : 2;
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);