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.
- package/dist/{SUPPORTED_CLIS-5njwpFxr.js → SUPPORTED_CLIS-VbHPdSIt.js} +3 -3
- package/dist/cli.js +10 -4
- package/dist/index.js +2 -2
- package/dist/{remotes-CFrho898.js → remotes-Bjp2GYPz.js} +26 -7
- package/dist/{remotes-kfUzk-JT.js → remotes-oNI1fR7_.js} +1 -1
- package/dist/{serve-D0NnTXRD.js → serve-CAEEkFLd.js} +16 -4
- package/dist/{subcommands-BpGEGOQM.js → subcommands-DIworIYh.js} +102 -7
- package/dist/subcommands-Dnh67Qo3.js +6 -0
- package/dist/{ts-DVlEOYHB.js → ts-CDrRTQFz.js} +2 -2
- package/dist/{versionChecker-XivGWuhd.js → versionChecker-BjRotlWY.js} +2 -2
- package/package.json +1 -1
- package/ts/cli.ts +9 -2
- package/ts/remotes.ts +42 -9
- package/ts/serve.ts +25 -1
- package/ts/subcommands.spec.ts +103 -0
- package/ts/subcommands.ts +148 -1
- package/dist/subcommands-BDiS305D.js +0 -6
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-CDrRTQFz.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
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-
|
|
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-
|
|
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
|
|
484
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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>
|
|
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,
|
|
97
|
-
if (!alias || !
|
|
98
|
-
process.stderr.write("usage: ay remote add <alias>
|
|
99
|
-
process.stderr.write(" example: ay remote add work-mac http://192.168.1.5:7432
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
4
|
-
import {
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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] [--
|
|
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 {
|
|
1081
|
-
//# sourceMappingURL=subcommands-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
224
|
+
//# sourceMappingURL=versionChecker-BjRotlWY.js.map
|
package/package.json
CHANGED
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
|
|
24
|
-
|
|
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>
|
|
112
|
-
" ay serve
|
|
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,
|
|
125
|
-
if (!alias || !
|
|
126
|
-
process.stderr.write("usage: ay remote add <alias>
|
|
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
|
-
|
|
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
|
|
157
|
-
" ay remote add <alias>
|
|
158
|
-
" ay remote rm <alias>
|
|
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
|
|
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` +
|
package/ts/subcommands.spec.ts
CHANGED
|
@@ -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)
|
|
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 };
|