claude-yes 1.86.0 → 1.88.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-DVlEOYHB.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CDrRTQFz.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-XivGWuhd.js";
3
+ import "./versionChecker-BjRotlWY.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-5njwpFxr.js.map
12
+ //# sourceMappingURL=SUPPORTED_CLIS-VbHPdSIt.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-XivGWuhd.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BjRotlWY.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -480,8 +480,14 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
480
480
  }
481
481
  }
482
482
  {
483
- const { isSubcommand, runSubcommand } = await import("./subcommands-BDiS305D.js");
484
- if (isSubcommand(process.argv[2])) {
483
+ const rawArg = process.argv[2];
484
+ const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-Dnh67Qo3.js");
486
+ if (isHelpFlag && process.argv.length === 3) {
487
+ cmdHelp();
488
+ process.exit(0);
489
+ }
490
+ if (isSubcommand(rawArg)) {
485
491
  const code = await runSubcommand(process.argv);
486
492
  process.exit(code ?? 0);
487
493
  }
@@ -509,7 +515,7 @@ if (config.useRust) {
509
515
  }
510
516
  }
511
517
  if (rustBinary) {
512
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-5njwpFxr.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-VbHPdSIt.js");
513
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
514
520
  if (config.verbose) {
515
521
  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-DVlEOYHB.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CDrRTQFz.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-XivGWuhd.js";
3
+ import "./versionChecker-BjRotlWY.js";
4
4
  import "./pidStore-C1JXxoPi.js";
5
5
  import "./globalPidIndex-Cr-g75QF.js";
6
6
 
@@ -79,11 +79,15 @@ async function resolveRemoteSpec(spec) {
79
79
  }
80
80
  async function cmdRemote(rest) {
81
81
  const sub = rest[0];
82
+ if (sub === "-h" || sub === "--help") {
83
+ process.stdout.write("Usage: ay remote <subcommand>\n\nManage saved remote server aliases.\n\nSubcommands:\n ay remote ls list configured remotes\n ay remote add <alias> http://<token>@<host>:<port> add a remote\n ay remote rm <alias> remove a remote\n\nOnce added, use the alias anywhere a keyword is accepted:\n ay ls <alias>\n ay tail <alias>:<keyword>\n ay send <alias>:<keyword> \"message\"\n");
84
+ return 0;
85
+ }
82
86
  if (!sub || sub === "ls" || sub === "list") {
83
87
  const remotes = await readRemotes();
84
88
  if (remotes.size === 0) {
85
89
  process.stdout.write("no remotes configured\n");
86
- process.stderr.write("\n ay remote add <alias> <url> <token> # add a remote\n ay serve # start server (prints token)\n");
90
+ process.stderr.write("\n ay remote add <alias> http://<token>@<host>:<port> # add a remote\n ay serve # start server (prints token)\n");
87
91
  return 0;
88
92
  }
89
93
  for (const [alias, cfg] of remotes) {
@@ -93,10 +97,25 @@ async function cmdRemote(rest) {
93
97
  return 0;
94
98
  }
95
99
  if (sub === "add") {
96
- const [, alias, url, token] = rest;
97
- if (!alias || !url || !token) {
98
- process.stderr.write("usage: ay remote add <alias> <url> <token>\n");
99
- process.stderr.write(" example: ay remote add work-mac http://192.168.1.5:7432 mytoken123\n");
100
+ const [, alias, rawUrl] = rest;
101
+ if (!alias || !rawUrl) {
102
+ process.stderr.write("usage: ay remote add <alias> http://<token>@<host>:<port>\n");
103
+ process.stderr.write(" example: ay remote add work-mac http://mytoken123@192.168.1.5:7432\n");
104
+ return 1;
105
+ }
106
+ let url, token;
107
+ try {
108
+ const parsed = new URL(rawUrl);
109
+ token = parsed.username;
110
+ parsed.username = "";
111
+ parsed.password = "";
112
+ url = parsed.toString().replace(/\/$/, "");
113
+ } catch {
114
+ process.stderr.write(`ay remote add: invalid URL '${rawUrl}'\n`);
115
+ return 1;
116
+ }
117
+ if (!token) {
118
+ process.stderr.write(`ay remote add: no token in URL — expected http://<token>@<host>:<port>\n`);
100
119
  return 1;
101
120
  }
102
121
  await writeRemoteAlias(alias, {
@@ -122,10 +141,10 @@ async function cmdRemote(rest) {
122
141
  return 0;
123
142
  }
124
143
  process.stderr.write(`ay remote: unknown subcommand '${sub}'\n`);
125
- process.stderr.write(" ay remote ls # list configured remotes\n ay remote add <alias> <url> <token> # add a remote\n ay remote rm <alias> # remove a remote\n");
144
+ process.stderr.write(" ay remote ls # list configured remotes\n ay remote add <alias> http://<token>@<host>:<port> # add a remote\n ay remote rm <alias> # remove a remote\n");
126
145
  return 1;
127
146
  }
128
147
 
129
148
  //#endregion
130
149
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
131
- //# sourceMappingURL=remotes-CFrho898.js.map
150
+ //# sourceMappingURL=remotes-Bjp2GYPz.js.map
@@ -1,3 +1,3 @@
1
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-CFrho898.js";
1
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-Bjp2GYPz.js";
2
2
 
3
3
  export { cmdRemote };
@@ -1,7 +1,7 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-Cr-g75QF.js";
3
- import "./remotes-CFrho898.js";
4
- import { c as resolveOne, d as writeToIpc, i as listRecords, o as readNotes, s as renderRawLog, t as controlCodeFromName, u as snapshotStatus } from "./subcommands-BpGEGOQM.js";
3
+ import "./remotes-Bjp2GYPz.js";
4
+ import { a as listRecords, c as renderRawLog, d as snapshotStatus, f as writeToIpc, l as resolveOne, n as controlCodeFromName, s as readNotes } from "./subcommands-DIworIYh.js";
5
5
  import yargs from "yargs";
6
6
  import { mkdir, readFile, writeFile } from "fs/promises";
7
7
  import { homedir } from "os";
@@ -72,7 +72,8 @@ async function cmdServeDaemon(sub, args) {
72
72
  if (code === 0) {
73
73
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
74
74
  process.stdout.write(`token: ${token}\n\n`);
75
- process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
75
+ process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
76
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${DEFAULT_PORT}\n`);
76
77
  process.stdout.write(` ay serve logs # view server logs\n`);
77
78
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
78
79
  }
@@ -100,6 +101,15 @@ async function cmdServeDaemon(sub, args) {
100
101
  return 1;
101
102
  }
102
103
  async function cmdServe(rest) {
104
+ if (rest.includes("-h") || rest.includes("--help")) {
105
+ process.stdout.write(`Usage: ay serve [options]
106
+
107
+ Start an HTTP API server so remote machines can list/tail/send agents.
108
+
109
+ Options:
110
+ --port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
111
+ return 0;
112
+ }
103
113
  const sub = rest[0];
104
114
  if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
105
115
  const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
@@ -283,6 +293,8 @@ async function cmdServe(rest) {
283
293
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
284
294
  process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
285
295
  process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
296
+ process.stdout.write(`save as alias:\n`);
297
+ process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
286
298
  if (!useHttps) process.stdout.write("for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'\n\n");
287
299
  process.stdout.write(`(Ctrl-C to stop)\n`);
288
300
  await new Promise((resolve) => {
@@ -300,4 +312,4 @@ async function cmdServe(rest) {
300
312
 
301
313
  //#endregion
302
314
  export { cmdServe };
303
- //# sourceMappingURL=serve-D0NnTXRD.js.map
315
+ //# sourceMappingURL=serve-CAEEkFLd.js.map
@@ -1,5 +1,6 @@
1
1
  import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
2
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-CFrho898.js";
2
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-Bjp2GYPz.js";
3
+ import ms from "ms";
3
4
  import yargs from "yargs";
4
5
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
5
6
  import { homedir } from "os";
@@ -128,7 +129,8 @@ const SUBCOMMANDS = new Set([
128
129
  "restart",
129
130
  "note",
130
131
  "serve",
131
- "remote"
132
+ "remote",
133
+ "help"
132
134
  ]);
133
135
  const IDLE_THRESHOLD_MS = 60 * 1e3;
134
136
  function isSubcommand(name) {
@@ -156,13 +158,14 @@ async function runSubcommand(argv) {
156
158
  case "restart": return await cmdRestart(rest);
157
159
  case "note": return await cmdNote(rest);
158
160
  case "serve": {
159
- const { cmdServe } = await import("./serve-D0NnTXRD.js");
161
+ const { cmdServe } = await import("./serve-CAEEkFLd.js");
160
162
  return cmdServe(rest);
161
163
  }
162
164
  case "remote": {
163
- const { cmdRemote } = await import("./remotes-kfUzk-JT.js");
165
+ const { cmdRemote } = await import("./remotes-oNI1fR7_.js");
164
166
  return cmdRemote(rest);
165
167
  }
168
+ case "help": return cmdHelp();
166
169
  default: return null;
167
170
  }
168
171
  } catch (err) {
@@ -171,6 +174,10 @@ async function runSubcommand(argv) {
171
174
  return 1;
172
175
  }
173
176
  }
177
+ function cmdHelp() {
178
+ process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay status <keyword> agent status snapshot\n\nRemote:\n ay serve [--port N] start HTTP API server (prints token)\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples in ./lab/):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
179
+ return 0;
180
+ }
174
181
  function matchKeyword(record, keyword) {
175
182
  if (!keyword) return true;
176
183
  const kw = keyword.toLowerCase();
@@ -200,12 +207,70 @@ function isPidAlive(pid) {
200
207
  return false;
201
208
  }
202
209
  }
210
+ async function pickInteractive(matches) {
211
+ const list = matches.slice(0, 10);
212
+ let sel = 0;
213
+ const render = () => {
214
+ for (let i = 0; i < list.length; i++) {
215
+ const r = list[i];
216
+ const marker = i === sel ? "\x1B[36m>\x1B[0m" : " ";
217
+ process.stderr.write(`${marker} ${r.pid} ${r.cli} ${r.cwd}\n`);
218
+ }
219
+ };
220
+ process.stderr.write(`Multiple matches — select with ↑↓ Enter (or type 1-${list.length}):\n`);
221
+ render();
222
+ return new Promise((resolve) => {
223
+ process.stdin.setRawMode(true);
224
+ process.stdin.resume();
225
+ process.stdin.setEncoding("utf8");
226
+ const redraw = () => {
227
+ process.stderr.write(`\x1b[${list.length}A\x1b[0J`);
228
+ render();
229
+ };
230
+ const cleanup = () => {
231
+ process.stdin.off("data", onData);
232
+ try {
233
+ process.stdin.setRawMode(false);
234
+ } catch {}
235
+ process.stdin.pause();
236
+ };
237
+ const onData = (key) => {
238
+ if (key === "") {
239
+ cleanup();
240
+ process.stderr.write("\n");
241
+ resolve(null);
242
+ } else if (key === "\r" || key === "\n") {
243
+ cleanup();
244
+ process.stderr.write("\n");
245
+ resolve(list[sel]);
246
+ } else if (key === "\x1B[A") {
247
+ sel = Math.max(0, sel - 1);
248
+ redraw();
249
+ } else if (key === "\x1B[B") {
250
+ sel = Math.min(list.length - 1, sel + 1);
251
+ redraw();
252
+ } else if (key >= "1" && key <= String(list.length)) {
253
+ sel = parseInt(key, 10) - 1;
254
+ redraw();
255
+ cleanup();
256
+ process.stderr.write("\n");
257
+ resolve(list[sel]);
258
+ }
259
+ };
260
+ process.stdin.on("data", onData);
261
+ });
262
+ }
203
263
  async function resolveOne(keyword, opts) {
204
264
  if (!keyword) throw new Error("keyword required (pid, cwd substring, cli name, or prompt substring)");
205
265
  const matches = await listRecords(keyword, opts);
206
266
  if (matches.length === 0) throw new Error(`no agent matched "${keyword}"`);
207
267
  if (matches.length === 1) return matches[0];
208
268
  if (opts.latest) return matches[0];
269
+ if (process.stderr.isTTY && process.stdin.isTTY) {
270
+ const chosen = await pickInteractive(matches);
271
+ if (chosen) return chosen;
272
+ throw new Error("no agent selected");
273
+ }
209
274
  const lines = matches.slice(0, 10).map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`).join("\n");
210
275
  throw new Error(`keyword "${keyword}" matched ${matches.length} agents — disambiguate by pid or pass --latest:\n${lines}`);
211
276
  }
@@ -583,6 +648,7 @@ async function cmdLs(rest) {
583
648
  if (alive) {
584
649
  hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
585
650
  hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
651
+ hints.push(` ay status ${alive.pid} --wait-idle # block until state == idle\n`);
586
652
  hints.push(` ay tail ${alive.pid} # view latest output\n`);
587
653
  hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
588
654
  hints.push(` ay send ${alive.pid} "next: ..." # send a prompt (keyword: pid, cwd, or prompt substring)\n`);
@@ -1012,6 +1078,13 @@ async function cmdStatus(rest) {
1012
1078
  type: "boolean",
1013
1079
  default: false,
1014
1080
  description: "Stream changes as JSON"
1081
+ }).option("wait-idle", {
1082
+ type: "boolean",
1083
+ default: false,
1084
+ description: "Block until state == idle. Exit 0 idle, 1 stopped, 2 timeout"
1085
+ }).option("timeout", {
1086
+ type: "string",
1087
+ description: "Timeout for --wait-idle (e.g. 30s, 5m). Default: no timeout"
1015
1088
  }).option("interval", {
1016
1089
  type: "number",
1017
1090
  default: 2,
@@ -1032,14 +1105,17 @@ async function cmdStatus(rest) {
1032
1105
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
1033
1106
  };
1034
1107
  const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
1035
- if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
1108
+ if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait-idle] [--timeout=Ns]");
1036
1109
  {
1037
1110
  const remote = await resolveRemoteSpec(keyword);
1038
1111
  if (remote) return runRemoteStatus(remote);
1039
1112
  }
1040
1113
  const watch = argv.watch;
1114
+ const waitIdle = argv["wait-idle"];
1041
1115
  const intervalFlag = argv.interval;
1042
1116
  const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1e3);
1117
+ const timeoutMs = typeof argv.timeout === "string" && argv.timeout.length > 0 ? ms(argv.timeout) ?? NaN : null;
1118
+ if (timeoutMs !== null && !Number.isFinite(timeoutMs)) throw new Error(`invalid --timeout value: ${argv.timeout}`);
1043
1119
  const record = await resolveOne(keyword, opts);
1044
1120
  const emit = (snap, ts) => {
1045
1121
  const out = ts !== void 0 ? {
@@ -1048,6 +1124,25 @@ async function cmdStatus(rest) {
1048
1124
  } : snap;
1049
1125
  process.stdout.write(JSON.stringify(out) + "\n");
1050
1126
  };
1127
+ if (waitIdle) {
1128
+ const startedAt = Date.now();
1129
+ for (;;) {
1130
+ const snap = await snapshotStatus(record);
1131
+ if (snap.state === "idle") {
1132
+ emit(snap);
1133
+ return 0;
1134
+ }
1135
+ if (snap.state === "stopped") {
1136
+ emit(snap);
1137
+ return 1;
1138
+ }
1139
+ if (timeoutMs !== null && Date.now() - startedAt >= timeoutMs) {
1140
+ emit(snap);
1141
+ return 2;
1142
+ }
1143
+ await new Promise((r) => setTimeout(r, intervalMs));
1144
+ }
1145
+ }
1051
1146
  if (!watch) {
1052
1147
  emit(await snapshotStatus(record));
1053
1148
  return 0;
@@ -1077,5 +1172,5 @@ async function cmdStatus(rest) {
1077
1172
  }
1078
1173
 
1079
1174
  //#endregion
1080
- export { matchKeyword as a, resolveOne as c, writeToIpc as d, listRecords as i, runSubcommand as l, isPidAlive as n, readNotes as o, isSubcommand as r, renderRawLog as s, controlCodeFromName as t, snapshotStatus as u };
1081
- //# sourceMappingURL=subcommands-BpGEGOQM.js.map
1175
+ export { listRecords as a, renderRawLog as c, snapshotStatus as d, writeToIpc as f, isSubcommand as i, resolveOne as l, controlCodeFromName as n, matchKeyword as o, isPidAlive as r, readNotes as s, cmdHelp as t, runSubcommand as u };
1176
+ //# sourceMappingURL=subcommands-DIworIYh.js.map
@@ -0,0 +1,6 @@
1
+ import "./logger-B9h0djqx.js";
2
+ import "./globalPidIndex-Cr-g75QF.js";
3
+ import "./remotes-Bjp2GYPz.js";
4
+ import { a as listRecords, c as renderRawLog, d as snapshotStatus, f as writeToIpc, i as isSubcommand, l as resolveOne, n as controlCodeFromName, o as matchKeyword, r as isPidAlive, s as readNotes, t as cmdHelp, u as runSubcommand } from "./subcommands-DIworIYh.js";
5
+
6
+ export { cmdHelp, isSubcommand, runSubcommand };
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-XivGWuhd.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-BjRotlWY.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-DVlEOYHB.js.map
1696
+ //# sourceMappingURL=ts-CDrRTQFz.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.86.0";
10
+ var version = "1.88.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-XivGWuhd.js.map
224
+ //# sourceMappingURL=versionChecker-BjRotlWY.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.86.0",
3
+ "version": "1.88.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",
package/ts/cli.ts CHANGED
@@ -20,8 +20,15 @@ import { buildRustArgs } from "./buildRustArgs.ts";
20
20
  // agent-spawn machinery (and the --rust dispatch) and operate on the global
21
21
  // pid index instead. Must run before checkAndAutoUpdate / yargs / Rust spawn.
22
22
  {
23
- const { isSubcommand, runSubcommand } = await import("./subcommands.ts");
24
- if (isSubcommand(process.argv[2])) {
23
+ const rawArg = process.argv[2];
24
+ // Intercept bare -h/--help so we show TS subcommands, not just Rust agent-runner options.
25
+ const isHelpFlag = rawArg === "-h" || rawArg === "--help";
26
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands.ts");
27
+ if (isHelpFlag && process.argv.length === 3) {
28
+ cmdHelp();
29
+ process.exit(0);
30
+ }
31
+ if (isSubcommand(rawArg)) {
25
32
  const code = await runSubcommand(process.argv);
26
33
  process.exit(code ?? 0);
27
34
  }
package/ts/remotes.ts CHANGED
@@ -102,14 +102,30 @@ export async function resolveRemoteSpec(spec: string): Promise<ResolvedRemote |
102
102
  export async function cmdRemote(rest: string[]): Promise<number> {
103
103
  const sub = rest[0];
104
104
 
105
+ if (sub === "-h" || sub === "--help") {
106
+ process.stdout.write(
107
+ `Usage: ay remote <subcommand>\n\n` +
108
+ `Manage saved remote server aliases.\n\n` +
109
+ `Subcommands:\n` +
110
+ ` ay remote ls list configured remotes\n` +
111
+ ` ay remote add <alias> http://<token>@<host>:<port> add a remote\n` +
112
+ ` ay remote rm <alias> remove a remote\n\n` +
113
+ `Once added, use the alias anywhere a keyword is accepted:\n` +
114
+ ` ay ls <alias>\n` +
115
+ ` ay tail <alias>:<keyword>\n` +
116
+ ` ay send <alias>:<keyword> "message"\n`,
117
+ );
118
+ return 0;
119
+ }
120
+
105
121
  if (!sub || sub === "ls" || sub === "list") {
106
122
  const remotes = await readRemotes();
107
123
  if (remotes.size === 0) {
108
124
  process.stdout.write("no remotes configured\n");
109
125
  process.stderr.write(
110
126
  "\n" +
111
- " ay remote add <alias> <url> <token> # add a remote\n" +
112
- " ay serve # start server (prints token)\n",
127
+ " ay remote add <alias> http://<token>@<host>:<port> # add a remote\n" +
128
+ " ay serve # start server (prints token)\n",
113
129
  );
114
130
  return 0;
115
131
  }
@@ -121,11 +137,28 @@ export async function cmdRemote(rest: string[]): Promise<number> {
121
137
  }
122
138
 
123
139
  if (sub === "add") {
124
- const [, alias, url, token] = rest;
125
- if (!alias || !url || !token) {
126
- process.stderr.write("usage: ay remote add <alias> <url> <token>\n");
140
+ const [, alias, rawUrl] = rest;
141
+ if (!alias || !rawUrl) {
142
+ process.stderr.write("usage: ay remote add <alias> http://<token>@<host>:<port>\n");
143
+ process.stderr.write(
144
+ " example: ay remote add work-mac http://mytoken123@192.168.1.5:7432\n",
145
+ );
146
+ return 1;
147
+ }
148
+ let url: string, token: string;
149
+ try {
150
+ const parsed = new URL(rawUrl);
151
+ token = parsed.username;
152
+ parsed.username = "";
153
+ parsed.password = "";
154
+ url = parsed.toString().replace(/\/$/, "");
155
+ } catch {
156
+ process.stderr.write(`ay remote add: invalid URL '${rawUrl}'\n`);
157
+ return 1;
158
+ }
159
+ if (!token) {
127
160
  process.stderr.write(
128
- " example: ay remote add work-mac http://192.168.1.5:7432 mytoken123\n",
161
+ `ay remote add: no token in URL — expected http://<token>@<host>:<port>\n`,
129
162
  );
130
163
  return 1;
131
164
  }
@@ -153,9 +186,9 @@ export async function cmdRemote(rest: string[]): Promise<number> {
153
186
 
154
187
  process.stderr.write(`ay remote: unknown subcommand '${sub}'\n`);
155
188
  process.stderr.write(
156
- " ay remote ls # list configured remotes\n" +
157
- " ay remote add <alias> <url> <token> # add a remote\n" +
158
- " ay remote rm <alias> # remove a remote\n",
189
+ " ay remote ls # list configured remotes\n" +
190
+ " ay remote add <alias> http://<token>@<host>:<port> # add a remote\n" +
191
+ " ay remote rm <alias> # remove a remote\n",
159
192
  );
160
193
  return 1;
161
194
  }
package/ts/serve.ts CHANGED
@@ -85,7 +85,8 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
85
85
  if (code === 0) {
86
86
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
87
87
  process.stdout.write(`token: ${token}\n\n`);
88
- process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
88
+ process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
89
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${DEFAULT_PORT}\n`);
89
90
  process.stdout.write(` ay serve logs # view server logs\n`);
90
91
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
91
92
  }
@@ -114,6 +115,27 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
114
115
  // ---------------------------------------------------------------------------
115
116
 
116
117
  export async function cmdServe(rest: string[]): Promise<number> {
118
+ if (rest.includes("-h") || rest.includes("--help")) {
119
+ process.stdout.write(
120
+ `Usage: ay serve [options]\n\n` +
121
+ `Start an HTTP API server so remote machines can list/tail/send agents.\n\n` +
122
+ `Options:\n` +
123
+ ` --port N Port to listen on (default: ${DEFAULT_PORT})\n` +
124
+ ` --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n` +
125
+ ` --token TOKEN Auth token (auto-generated and saved if omitted)\n` +
126
+ ` --tls-cert FILE TLS certificate PEM\n` +
127
+ ` --tls-key FILE TLS private key PEM\n\n` +
128
+ `Subcommands:\n` +
129
+ ` ay serve install install as background daemon via oxmgr\n` +
130
+ ` ay serve uninstall remove daemon\n` +
131
+ ` ay serve logs view daemon logs\n\n` +
132
+ `Once running, connect from another machine:\n` +
133
+ ` ay ls <token>@<host>:${DEFAULT_PORT}\n` +
134
+ ` ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`,
135
+ );
136
+ return 0;
137
+ }
138
+
117
139
  // Daemon subcommands
118
140
  const sub = rest[0];
119
141
  if (sub === "install" || sub === "uninstall" || sub === "logs") {
@@ -350,6 +372,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
350
372
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
351
373
  process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
352
374
  process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
375
+ process.stdout.write(`save as alias:\n`);
376
+ process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
353
377
  if (!useHttps) {
354
378
  process.stdout.write(
355
379
  `for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n` +
@@ -926,6 +926,109 @@ describe("subcommands.cmdStatus", () => {
926
926
  expect(snap).toMatchObject({ pid: process.pid, cli: "claude" });
927
927
  expect(typeof snap.age_ms).toBe("number");
928
928
  });
929
+
930
+ it("--wait-idle returns 0 immediately for an idle agent", async () => {
931
+ const mod = await loadModule();
932
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
933
+ const logFile = path.join(testHome, "idle.raw.log");
934
+ await writeFile(logFile, "old\n");
935
+ // Stale mtime: > IDLE_THRESHOLD_MS (60s) in the past
936
+ const stale = (Date.now() - 5 * 60 * 1000) / 1000;
937
+ const { utimes } = await import("fs/promises");
938
+ await utimes(logFile, stale, stale);
939
+ await appendGlobalPid({
940
+ pid: process.pid,
941
+ cli: "claude",
942
+ prompt: "wait-idle-test",
943
+ cwd: process.cwd(),
944
+ log_file: logFile,
945
+ status: "active",
946
+ exit_code: null,
947
+ exit_reason: null,
948
+ started_at: Date.now() - 10_000,
949
+ });
950
+
951
+ const stdout: string[] = [];
952
+ (process.stdout as any).write = (s: any) => {
953
+ stdout.push(String(s));
954
+ return true;
955
+ };
956
+ (process.stderr as any).write = () => true;
957
+ const code = await mod.runSubcommand([
958
+ "bun",
959
+ "cli.js",
960
+ "status",
961
+ String(process.pid),
962
+ "--wait-idle",
963
+ "--timeout=2s",
964
+ "--interval=0.5",
965
+ ]);
966
+ expect(code).toBe(0);
967
+ const snap = JSON.parse(stdout.join("").trim().split("\n").pop()!);
968
+ expect(snap.state).toBe("idle");
969
+ });
970
+
971
+ it("--wait-idle returns 1 when the agent is stopped", async () => {
972
+ const mod = await loadModule();
973
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
974
+ // Pick a pid that is almost certainly not alive.
975
+ const deadPid = 999_999;
976
+ await appendGlobalPid({
977
+ pid: deadPid,
978
+ cli: "claude",
979
+ prompt: "wait-idle-stopped",
980
+ cwd: process.cwd(),
981
+ log_file: null,
982
+ status: "active",
983
+ exit_code: null,
984
+ exit_reason: null,
985
+ started_at: Date.now() - 10_000,
986
+ });
987
+
988
+ (process.stdout as any).write = () => true;
989
+ (process.stderr as any).write = () => true;
990
+ const code = await mod.runSubcommand([
991
+ "bun",
992
+ "cli.js",
993
+ "status",
994
+ String(deadPid),
995
+ "--wait-idle",
996
+ "--interval=0.5",
997
+ ]);
998
+ expect(code).toBe(1);
999
+ });
1000
+
1001
+ it("--wait-idle returns 2 on timeout while still active", async () => {
1002
+ const mod = await loadModule();
1003
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
1004
+ const logFile = path.join(testHome, "active.raw.log");
1005
+ await writeFile(logFile, "fresh\n");
1006
+ // Fresh mtime keeps state = active
1007
+ await appendGlobalPid({
1008
+ pid: process.pid,
1009
+ cli: "claude",
1010
+ prompt: "wait-idle-timeout",
1011
+ cwd: process.cwd(),
1012
+ log_file: logFile,
1013
+ status: "active",
1014
+ exit_code: null,
1015
+ exit_reason: null,
1016
+ started_at: Date.now() - 10_000,
1017
+ });
1018
+
1019
+ (process.stdout as any).write = () => true;
1020
+ (process.stderr as any).write = () => true;
1021
+ const code = await mod.runSubcommand([
1022
+ "bun",
1023
+ "cli.js",
1024
+ "status",
1025
+ String(process.pid),
1026
+ "--wait-idle",
1027
+ "--timeout=600ms",
1028
+ "--interval=0.5",
1029
+ ]);
1030
+ expect(code).toBe(2);
1031
+ });
929
1032
  });
930
1033
 
931
1034
  // ---------------------------------------------------------------------------
package/ts/subcommands.ts CHANGED
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
15
+ import ms from "ms";
15
16
  import { homedir } from "os";
16
17
  import path from "path";
17
18
  import { type GlobalPidRecord, readGlobalPids } from "./globalPidIndex.ts";
@@ -139,6 +140,7 @@ const SUBCOMMANDS = new Set([
139
140
  "note",
140
141
  "serve",
141
142
  "remote",
143
+ "help",
142
144
  ]);
143
145
 
144
146
  const IDLE_THRESHOLD_MS = 60 * 1000;
@@ -186,6 +188,8 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
186
188
  const { cmdRemote } = await import("./remotes.ts");
187
189
  return cmdRemote(rest);
188
190
  }
191
+ case "help":
192
+ return cmdHelp();
189
193
  default:
190
194
  return null;
191
195
  }
@@ -196,6 +200,42 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
196
200
  }
197
201
  }
198
202
 
203
+ // ---------------------------------------------------------------------------
204
+ // ay help
205
+ // ---------------------------------------------------------------------------
206
+
207
+ export function cmdHelp(): number {
208
+ process.stdout.write(
209
+ `ay - agent-yes CLI\n` +
210
+ `\n` +
211
+ `Management:\n` +
212
+ ` ay ls [keyword] list running agents\n` +
213
+ ` ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n` +
214
+ ` ay cat <keyword> full log\n` +
215
+ ` ay head <keyword> first N lines\n` +
216
+ ` ay send <keyword> <msg> send a message\n` +
217
+ ` ay status <keyword> agent status snapshot\n` +
218
+ `\n` +
219
+ `Remote:\n` +
220
+ ` ay serve [--port N] start HTTP API server (prints token)\n` +
221
+ ` ay remote add <alias> http://<token>@<host>:<port>\n` +
222
+ ` ay remote ls / rm <alias> manage saved remotes\n` +
223
+ ` ay ls <token>@<host>:<port> connect inline (no alias needed)\n` +
224
+ ` ay send <token>@<host>:<port>:<kw> <msg>\n` +
225
+ `\n` +
226
+ `Run an agent:\n` +
227
+ ` ay [claude|codex|gemini|...] [options] -- [prompt]\n` +
228
+ ` ay claude -- "fix the bug in auth.ts"\n` +
229
+ ` ay claude --help full agent-runner options\n` +
230
+ `\n` +
231
+ `Labs (examples in ./lab/):\n` +
232
+ ` local-role-play/ designer + builder on one machine\n` +
233
+ ` http-remote/ ay serve remote access demo\n` +
234
+ ` p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n`,
235
+ );
236
+ return 0;
237
+ }
238
+
199
239
  // ---------------------------------------------------------------------------
200
240
  // shared helpers
201
241
  // ---------------------------------------------------------------------------
@@ -260,6 +300,69 @@ export function isPidAlive(pid: number): boolean {
260
300
  }
261
301
  }
262
302
 
303
+ async function pickInteractive(matches: GlobalPidRecord[]): Promise<GlobalPidRecord | null> {
304
+ const list = matches.slice(0, 10);
305
+ let sel = 0;
306
+
307
+ const render = () => {
308
+ for (let i = 0; i < list.length; i++) {
309
+ const r = list[i]!;
310
+ const marker = i === sel ? "\x1b[36m>\x1b[0m" : " ";
311
+ process.stderr.write(`${marker} ${r.pid} ${r.cli} ${r.cwd}\n`);
312
+ }
313
+ };
314
+
315
+ process.stderr.write(`Multiple matches — select with ↑↓ Enter (or type 1-${list.length}):\n`);
316
+ render();
317
+
318
+ return new Promise((resolve) => {
319
+ process.stdin.setRawMode(true);
320
+ process.stdin.resume();
321
+ process.stdin.setEncoding("utf8");
322
+
323
+ const redraw = () => {
324
+ process.stderr.write(`\x1b[${list.length}A\x1b[0J`);
325
+ render();
326
+ };
327
+
328
+ const cleanup = () => {
329
+ process.stdin.off("data", onData);
330
+ try {
331
+ process.stdin.setRawMode(false);
332
+ } catch {
333
+ /* not a tty */
334
+ }
335
+ process.stdin.pause();
336
+ };
337
+
338
+ const onData = (key: string) => {
339
+ if (key === "\x03") {
340
+ cleanup();
341
+ process.stderr.write("\n");
342
+ resolve(null);
343
+ } else if (key === "\r" || key === "\n") {
344
+ cleanup();
345
+ process.stderr.write("\n");
346
+ resolve(list[sel]!);
347
+ } else if (key === "\x1b[A") {
348
+ sel = Math.max(0, sel - 1);
349
+ redraw();
350
+ } else if (key === "\x1b[B") {
351
+ sel = Math.min(list.length - 1, sel + 1);
352
+ redraw();
353
+ } else if (key >= "1" && key <= String(list.length)) {
354
+ sel = parseInt(key, 10) - 1;
355
+ redraw();
356
+ cleanup();
357
+ process.stderr.write("\n");
358
+ resolve(list[sel]!);
359
+ }
360
+ };
361
+
362
+ process.stdin.on("data", onData);
363
+ });
364
+ }
365
+
263
366
  export async function resolveOne(
264
367
  keyword: string | undefined,
265
368
  opts: CommonOpts,
@@ -273,6 +376,11 @@ export async function resolveOne(
273
376
  }
274
377
  if (matches.length === 1) return matches[0]!;
275
378
  if (opts.latest) return matches[0]!; // already sorted newest-first
379
+ if (process.stderr.isTTY && process.stdin.isTTY) {
380
+ const chosen = await pickInteractive(matches);
381
+ if (chosen) return chosen;
382
+ throw new Error("no agent selected");
383
+ }
276
384
  const lines = matches
277
385
  .slice(0, 10)
278
386
  .map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`)
@@ -767,6 +875,7 @@ async function cmdLs(rest: string[]): Promise<number> {
767
875
  if (alive) {
768
876
  hints.push(` ay status ${alive.pid} # JSON status snapshot\n`);
769
877
  hints.push(` ay status ${alive.pid} --watch # stream changes as JSON\n`);
878
+ hints.push(` ay status ${alive.pid} --wait-idle # block until state == idle\n`);
770
879
  hints.push(` ay tail ${alive.pid} # view latest output\n`);
771
880
  hints.push(` ay tail -f ${alive.pid} # follow live output\n`);
772
881
  hints.push(
@@ -1398,6 +1507,15 @@ async function cmdStatus(rest: string[]): Promise<number> {
1398
1507
  default: false,
1399
1508
  description: "Stream changes as JSON",
1400
1509
  })
1510
+ .option("wait-idle", {
1511
+ type: "boolean",
1512
+ default: false,
1513
+ description: "Block until state == idle. Exit 0 idle, 1 stopped, 2 timeout",
1514
+ })
1515
+ .option("timeout", {
1516
+ type: "string",
1517
+ description: "Timeout for --wait-idle (e.g. 30s, 5m). Default: no timeout",
1518
+ })
1401
1519
  .option("interval", { type: "number", default: 2, description: "Poll interval in seconds" })
1402
1520
  .option("latest", { type: "boolean", default: false, description: "Use most recent match" })
1403
1521
  .option("cwd", { type: "string", description: "Restrict to agents under this dir" })
@@ -1415,7 +1533,8 @@ async function cmdStatus(rest: string[]): Promise<number> {
1415
1533
  };
1416
1534
  const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
1417
1535
 
1418
- if (!keyword) throw new Error("usage: ay status <keyword> [--watch] [--interval=N]");
1536
+ if (!keyword)
1537
+ throw new Error("usage: ay status <keyword> [--watch | --wait-idle] [--timeout=Ns]");
1419
1538
 
1420
1539
  {
1421
1540
  const remote = await resolveRemoteSpec(keyword);
@@ -1423,8 +1542,16 @@ async function cmdStatus(rest: string[]): Promise<number> {
1423
1542
  }
1424
1543
 
1425
1544
  const watch = argv.watch;
1545
+ const waitIdle = argv["wait-idle"];
1426
1546
  const intervalFlag = argv.interval;
1427
1547
  const intervalMs = Math.max(500, (Number.isFinite(intervalFlag) ? intervalFlag : 2) * 1000);
1548
+ const timeoutMs =
1549
+ typeof argv.timeout === "string" && argv.timeout.length > 0
1550
+ ? (ms(argv.timeout) ?? Number.NaN)
1551
+ : null;
1552
+ if (timeoutMs !== null && !Number.isFinite(timeoutMs)) {
1553
+ throw new Error(`invalid --timeout value: ${argv.timeout}`);
1554
+ }
1428
1555
 
1429
1556
  const record = await resolveOne(keyword, opts);
1430
1557
 
@@ -1433,6 +1560,26 @@ async function cmdStatus(rest: string[]): Promise<number> {
1433
1560
  process.stdout.write(JSON.stringify(out) + "\n");
1434
1561
  };
1435
1562
 
1563
+ if (waitIdle) {
1564
+ const startedAt = Date.now();
1565
+ for (;;) {
1566
+ const snap = await snapshotStatus(record);
1567
+ if (snap.state === "idle") {
1568
+ emit(snap);
1569
+ return 0;
1570
+ }
1571
+ if (snap.state === "stopped") {
1572
+ emit(snap);
1573
+ return 1;
1574
+ }
1575
+ if (timeoutMs !== null && Date.now() - startedAt >= timeoutMs) {
1576
+ emit(snap);
1577
+ return 2;
1578
+ }
1579
+ await new Promise((r) => setTimeout(r, intervalMs));
1580
+ }
1581
+ }
1582
+
1436
1583
  if (!watch) {
1437
1584
  emit(await snapshotStatus(record));
1438
1585
  return 0;
@@ -1,6 +0,0 @@
1
- import "./logger-B9h0djqx.js";
2
- import "./globalPidIndex-Cr-g75QF.js";
3
- import "./remotes-CFrho898.js";
4
- import { a as matchKeyword, c as resolveOne, d as writeToIpc, i as listRecords, l as runSubcommand, n as isPidAlive, o as readNotes, r as isSubcommand, s as renderRawLog, t as controlCodeFromName, u as snapshotStatus } from "./subcommands-BpGEGOQM.js";
5
-
6
- export { isSubcommand, runSubcommand };