claude-yes 1.78.0 → 1.80.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-eyTIFAJl.js → SUPPORTED_CLIS-ujFlzMra.js} +3 -3
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{subcommands-Ctgm4cEn.js → subcommands-BVcxPfly.js} +90 -16
- package/dist/{ts-B2M_r506.js → ts-DJTdHpX6.js} +2 -2
- package/dist/{versionChecker-CoV20GQ7.js → versionChecker-iWnXmXoS.js} +2 -2
- package/package.json +1 -1
- package/ts/subcommands.spec.ts +1 -1
- package/ts/subcommands.ts +111 -18
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-DJTdHpX6.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-iWnXmXoS.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-ujFlzMra.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-iWnXmXoS.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
5
|
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
@@ -475,7 +475,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
475
475
|
}
|
|
476
476
|
}
|
|
477
477
|
{
|
|
478
|
-
const { isSubcommand, runSubcommand } = await import("./subcommands-
|
|
478
|
+
const { isSubcommand, runSubcommand } = await import("./subcommands-BVcxPfly.js");
|
|
479
479
|
if (isSubcommand(process.argv[2])) {
|
|
480
480
|
const code = await runSubcommand(process.argv);
|
|
481
481
|
process.exit(code ?? 0);
|
|
@@ -504,7 +504,7 @@ if (config.useRust) {
|
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
506
|
if (rustBinary) {
|
|
507
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
507
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-ujFlzMra.js");
|
|
508
508
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
509
509
|
if (config.verbose) {
|
|
510
510
|
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-DJTdHpX6.js";
|
|
2
2
|
import "./logger-B9h0djqx.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-iWnXmXoS.js";
|
|
4
4
|
import "./pidStore-C1JXxoPi.js";
|
|
5
5
|
import "./globalPidIndex-Cr-g75QF.js";
|
|
6
6
|
|
|
@@ -81,7 +81,8 @@ const SUBCOMMANDS = new Set([
|
|
|
81
81
|
"cat",
|
|
82
82
|
"tail",
|
|
83
83
|
"head",
|
|
84
|
-
"send"
|
|
84
|
+
"send",
|
|
85
|
+
"restart"
|
|
85
86
|
]);
|
|
86
87
|
function isSubcommand(name) {
|
|
87
88
|
return !!name && SUBCOMMANDS.has(name);
|
|
@@ -104,6 +105,7 @@ async function runSubcommand(argv) {
|
|
|
104
105
|
case "tail": return await cmdRead(rest, { mode: "tail" });
|
|
105
106
|
case "head": return await cmdRead(rest, { mode: "head" });
|
|
106
107
|
case "send": return await cmdSend(rest);
|
|
108
|
+
case "restart": return await cmdRestart(rest);
|
|
107
109
|
default: return null;
|
|
108
110
|
}
|
|
109
111
|
} catch (err) {
|
|
@@ -125,6 +127,8 @@ function parseArgs(rest) {
|
|
|
125
127
|
const next = rest[i + 1];
|
|
126
128
|
if ([
|
|
127
129
|
"all",
|
|
130
|
+
"active",
|
|
131
|
+
"follow",
|
|
128
132
|
"json",
|
|
129
133
|
"latest"
|
|
130
134
|
].includes(key) || !next || next.startsWith("-")) flags[key] = true;
|
|
@@ -147,6 +151,7 @@ function parseArgs(rest) {
|
|
|
147
151
|
function commonOpts(flags) {
|
|
148
152
|
return {
|
|
149
153
|
all: !!flags.all,
|
|
154
|
+
active: !!flags.active,
|
|
150
155
|
cwdScope: typeof flags.cwd === "string" ? path.resolve(flags.cwd) : flags.cwd === true ? process.cwd() : null,
|
|
151
156
|
latest: !!flags.latest,
|
|
152
157
|
json: !!flags.json
|
|
@@ -163,7 +168,8 @@ function matchKeyword(record, keyword) {
|
|
|
163
168
|
}
|
|
164
169
|
async function listRecords(keyword, opts) {
|
|
165
170
|
let records = mergeRecords(await readLocalTsPids(process.cwd()), opts.cwdScope ? await readLocalTsPids(opts.cwdScope) : [], await readGlobalPids());
|
|
166
|
-
if (!opts.all) records = records.filter((r) => r.status !== "exited"
|
|
171
|
+
if (!opts.all) records = records.filter((r) => r.status !== "exited");
|
|
172
|
+
if (opts.active) records = records.filter((r) => isPidAlive(r.pid));
|
|
167
173
|
if (opts.cwdScope) {
|
|
168
174
|
const scope = opts.cwdScope;
|
|
169
175
|
records = records.filter((r) => r.cwd === scope || r.cwd.startsWith(scope + path.sep));
|
|
@@ -183,7 +189,7 @@ function isPidAlive(pid) {
|
|
|
183
189
|
async function resolveOne(keyword, opts) {
|
|
184
190
|
if (!keyword) throw new Error("keyword required (pid, cwd substring, cli name, or prompt substring)");
|
|
185
191
|
const matches = await listRecords(keyword, opts);
|
|
186
|
-
if (matches.length === 0) throw new Error(`no
|
|
192
|
+
if (matches.length === 0) throw new Error(`no agent matched "${keyword}"`);
|
|
187
193
|
if (matches.length === 1) return matches[0];
|
|
188
194
|
if (opts.latest) return matches[0];
|
|
189
195
|
const lines = matches.slice(0, 10).map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`).join("\n");
|
|
@@ -215,18 +221,20 @@ async function cmdLs(rest) {
|
|
|
215
221
|
const promptBudget = Math.max(20, termWidth - fixedWidth - 1);
|
|
216
222
|
const IDLE_THRESHOLD_MS = 60 * 1e3;
|
|
217
223
|
const rows = await Promise.all(records.map(async (r) => {
|
|
218
|
-
let displayStatus
|
|
219
|
-
if (r.
|
|
224
|
+
let displayStatus;
|
|
225
|
+
if (!isPidAlive(r.pid)) displayStatus = "stopped";
|
|
226
|
+
else if (r.log_file) {
|
|
220
227
|
const mtime = await stat(r.log_file).then((s) => s.mtimeMs).catch(() => null);
|
|
221
|
-
|
|
222
|
-
}
|
|
228
|
+
displayStatus = mtime !== null && Date.now() - mtime > IDLE_THRESHOLD_MS ? "idle" : "active";
|
|
229
|
+
} else displayStatus = "active";
|
|
223
230
|
return {
|
|
224
231
|
pid: String(r.pid),
|
|
225
232
|
cli: r.cli,
|
|
226
233
|
status: displayStatus,
|
|
227
234
|
age: humanizeAge(Date.now() - r.started_at),
|
|
228
235
|
cwd: shortenPath(r.cwd),
|
|
229
|
-
prompt: truncate(r.prompt ?? "", promptBudget)
|
|
236
|
+
prompt: truncate(r.prompt ?? "", promptBudget),
|
|
237
|
+
_alive: displayStatus !== "stopped"
|
|
230
238
|
};
|
|
231
239
|
}));
|
|
232
240
|
const header = [
|
|
@@ -246,9 +254,19 @@ async function cmdLs(rest) {
|
|
|
246
254
|
r.cwd.padEnd(widths.cwd),
|
|
247
255
|
r.prompt
|
|
248
256
|
].join(" ") + "\n");
|
|
249
|
-
if (!opts.json &&
|
|
250
|
-
const
|
|
251
|
-
|
|
257
|
+
if (!opts.json && rows.length > 0) {
|
|
258
|
+
const alive = rows.find((r) => r._alive);
|
|
259
|
+
const stopped = rows.find((r) => !r._alive);
|
|
260
|
+
const hints = ["\n"];
|
|
261
|
+
if (alive) {
|
|
262
|
+
hints.push(` cy tail ${alive.pid} # view latest output\n`);
|
|
263
|
+
hints.push(` cy tail -f ${alive.pid} # follow live output\n`);
|
|
264
|
+
hints.push(` cy send ${alive.pid} "next: ..." # send a prompt\n`);
|
|
265
|
+
hints.push(` cy send ${alive.pid} "" --code=ctrl-c # interrupt\n`);
|
|
266
|
+
}
|
|
267
|
+
if (stopped) hints.push(` cy restart ${stopped.pid} # restart stopped agent\n`);
|
|
268
|
+
if (!alive && !stopped) hints.push(` cy ls --all # show exited agents\n`);
|
|
269
|
+
process.stderr.write(hints.join(""));
|
|
252
270
|
}
|
|
253
271
|
return 0;
|
|
254
272
|
}
|
|
@@ -274,6 +292,7 @@ async function cmdRead(rest, { mode }) {
|
|
|
274
292
|
const { flags, positional } = parseArgs(rest);
|
|
275
293
|
const opts = commonOpts(flags);
|
|
276
294
|
const keyword = positional[0];
|
|
295
|
+
const follow = !!(flags.f || flags.follow);
|
|
277
296
|
const nFlag = typeof flags.n === "string" ? Number(flags.n) : void 0;
|
|
278
297
|
const n = nFlag !== void 0 && Number.isFinite(nFlag) && nFlag > 0 ? Math.floor(nFlag) : mode === "cat" ? 0 : 96;
|
|
279
298
|
const record = await resolveOne(keyword, opts);
|
|
@@ -286,16 +305,39 @@ async function cmdRead(rest, { mode }) {
|
|
|
286
305
|
throw new Error(`pid ${record.pid}: log file not found at ${logPath}`);
|
|
287
306
|
}
|
|
288
307
|
if (!stats.isFile()) throw new Error(`pid ${record.pid}: log path is not a file: ${logPath}`);
|
|
289
|
-
const
|
|
308
|
+
const buf = await readFile(logPath);
|
|
309
|
+
const rendered = await renderRawLog(buf, {
|
|
290
310
|
mode,
|
|
291
311
|
n
|
|
292
312
|
});
|
|
293
313
|
process.stderr.write(`[pid ${record.pid} ${shortenPath(record.cwd)}]\n`);
|
|
294
314
|
process.stdout.write(rendered);
|
|
295
315
|
if (!rendered.endsWith("\n")) process.stdout.write("\n");
|
|
316
|
+
if (follow) {
|
|
317
|
+
process.stderr.write(`following... (Ctrl-C to stop)\n`);
|
|
318
|
+
let offset = buf.length;
|
|
319
|
+
const { watch } = await import("fs");
|
|
320
|
+
const ansiRe = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
321
|
+
const ctrlRe = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
322
|
+
await new Promise((resolve) => {
|
|
323
|
+
const watcher = watch(logPath, async () => {
|
|
324
|
+
const full = await readFile(logPath);
|
|
325
|
+
if (full.length <= offset) return;
|
|
326
|
+
const chunk = full.slice(offset);
|
|
327
|
+
offset = full.length;
|
|
328
|
+
const text = new TextDecoder().decode(chunk).replace(ansiRe, "").replace(ctrlRe, "");
|
|
329
|
+
if (text.trim()) process.stdout.write(text.trimStart());
|
|
330
|
+
});
|
|
331
|
+
process.on("SIGINT", () => {
|
|
332
|
+
watcher.close();
|
|
333
|
+
resolve();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
296
338
|
process.stderr.write(`
|
|
297
339
|
cy ls # list all agents
|
|
298
|
-
cy send ${record.pid} "next: ..." # send a prompt\n cy send ${record.pid} "" --code=ctrl-c # interrupt\n`);
|
|
340
|
+
cy tail -f ${record.pid} # follow live output\n cy send ${record.pid} "next: ..." # send a prompt\n cy send ${record.pid} "" --code=ctrl-c # interrupt\n`);
|
|
299
341
|
return 0;
|
|
300
342
|
}
|
|
301
343
|
/**
|
|
@@ -344,8 +386,13 @@ async function cmdSend(rest) {
|
|
|
344
386
|
const record = await resolveOne(keyword, opts);
|
|
345
387
|
const fifoPath = record.fifo_file;
|
|
346
388
|
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)`);
|
|
347
|
-
const
|
|
348
|
-
|
|
389
|
+
const body = message ?? "";
|
|
390
|
+
if (body && trailing) {
|
|
391
|
+
await writeToIpc(fifoPath, body);
|
|
392
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
393
|
+
await writeToIpc(fifoPath, trailing);
|
|
394
|
+
} else await writeToIpc(fifoPath, body + trailing);
|
|
395
|
+
const payload = body + trailing;
|
|
349
396
|
process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
|
|
350
397
|
process.stderr.write(`\n cy tail ${record.pid} # watch output\n cy ls # list all agents\n`);
|
|
351
398
|
return 0;
|
|
@@ -402,7 +449,34 @@ async function writeToIpc(ipcPath, payload) {
|
|
|
402
449
|
}
|
|
403
450
|
}
|
|
404
451
|
}
|
|
452
|
+
async function cmdRestart(rest) {
|
|
453
|
+
const { flags, positional } = parseArgs(rest);
|
|
454
|
+
const opts = {
|
|
455
|
+
...commonOpts(flags),
|
|
456
|
+
all: true
|
|
457
|
+
};
|
|
458
|
+
const keyword = positional[0];
|
|
459
|
+
const record = await resolveOne(keyword, opts);
|
|
460
|
+
if (isPidAlive(record.pid)) {
|
|
461
|
+
process.stderr.write(`pid ${record.pid} is still running — stop it first or use cy send\n`);
|
|
462
|
+
return 1;
|
|
463
|
+
}
|
|
464
|
+
const args = ["--cli=" + record.cli];
|
|
465
|
+
if (record.prompt) args.push(record.prompt);
|
|
466
|
+
const proc = Bun.spawn(["agent-yes", ...args], {
|
|
467
|
+
cwd: record.cwd,
|
|
468
|
+
detached: true,
|
|
469
|
+
stdio: [
|
|
470
|
+
"ignore",
|
|
471
|
+
"ignore",
|
|
472
|
+
"ignore"
|
|
473
|
+
]
|
|
474
|
+
});
|
|
475
|
+
process.stdout.write(`restarted ${record.cli} in ${shortenPath(record.cwd)} (new pid: ${proc.pid})\n`);
|
|
476
|
+
process.stderr.write(`\n cy tail ${proc.pid} # watch output\n cy ls # list all agents\n`);
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
405
479
|
|
|
406
480
|
//#endregion
|
|
407
481
|
export { isSubcommand, runSubcommand };
|
|
408
|
-
//# sourceMappingURL=subcommands-
|
|
482
|
+
//# sourceMappingURL=subcommands-BVcxPfly.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-iWnXmXoS.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 { arch, platform } from "process";
|
|
@@ -1679,4 +1679,4 @@ function sleep(ms) {
|
|
|
1679
1679
|
|
|
1680
1680
|
//#endregion
|
|
1681
1681
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1682
|
-
//# sourceMappingURL=ts-
|
|
1682
|
+
//# sourceMappingURL=ts-DJTdHpX6.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.80.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-iWnXmXoS.js.map
|
package/package.json
CHANGED
package/ts/subcommands.spec.ts
CHANGED
|
@@ -206,7 +206,7 @@ describe("subcommands.runSubcommand routing", () => {
|
|
|
206
206
|
try {
|
|
207
207
|
const code = await runSubcommand(["bun", "cli.js", "read", "no-such-agent-keyword"]);
|
|
208
208
|
expect(code).toBe(1);
|
|
209
|
-
expect(stderr.join("")).toMatch(/no
|
|
209
|
+
expect(stderr.join("")).toMatch(/no agent matched/);
|
|
210
210
|
} finally {
|
|
211
211
|
process.stderr.write = orig;
|
|
212
212
|
}
|
package/ts/subcommands.ts
CHANGED
|
@@ -76,7 +76,7 @@ function mergeRecords(...buckets: GlobalPidRecord[][]): GlobalPidRecord[] {
|
|
|
76
76
|
return Array.from(out.values());
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
const SUBCOMMANDS = new Set(["ls", "list", "ps", "read", "cat", "tail", "head", "send"]);
|
|
79
|
+
const SUBCOMMANDS = new Set(["ls", "list", "ps", "read", "cat", "tail", "head", "send", "restart"]);
|
|
80
80
|
|
|
81
81
|
export function isSubcommand(name: string | undefined): boolean {
|
|
82
82
|
return !!name && SUBCOMMANDS.has(name);
|
|
@@ -107,6 +107,8 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
107
107
|
return await cmdRead(rest, { mode: "head" });
|
|
108
108
|
case "send":
|
|
109
109
|
return await cmdSend(rest);
|
|
110
|
+
case "restart":
|
|
111
|
+
return await cmdRestart(rest);
|
|
110
112
|
default:
|
|
111
113
|
return null;
|
|
112
114
|
}
|
|
@@ -123,6 +125,7 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
|
|
|
123
125
|
|
|
124
126
|
interface CommonOpts {
|
|
125
127
|
all: boolean;
|
|
128
|
+
active: boolean;
|
|
126
129
|
cwdScope: string | null;
|
|
127
130
|
latest: boolean;
|
|
128
131
|
json: boolean;
|
|
@@ -146,7 +149,11 @@ export function parseArgs(rest: string[]): ParsedArgs {
|
|
|
146
149
|
const key = arg.slice(2);
|
|
147
150
|
const next = rest[i + 1];
|
|
148
151
|
// Boolean flags: --all, --json, --latest
|
|
149
|
-
if (
|
|
152
|
+
if (
|
|
153
|
+
["all", "active", "follow", "json", "latest"].includes(key) ||
|
|
154
|
+
!next ||
|
|
155
|
+
next.startsWith("-")
|
|
156
|
+
) {
|
|
150
157
|
flags[key] = true;
|
|
151
158
|
} else {
|
|
152
159
|
flags[key] = next;
|
|
@@ -171,6 +178,7 @@ export function parseArgs(rest: string[]): ParsedArgs {
|
|
|
171
178
|
function commonOpts(flags: Record<string, string | boolean>): CommonOpts {
|
|
172
179
|
return {
|
|
173
180
|
all: !!flags.all,
|
|
181
|
+
active: !!flags.active,
|
|
174
182
|
cwdScope:
|
|
175
183
|
typeof flags.cwd === "string"
|
|
176
184
|
? path.resolve(flags.cwd)
|
|
@@ -210,7 +218,10 @@ async function listRecords(
|
|
|
210
218
|
let records = mergeRecords(local, scopeLocal, global);
|
|
211
219
|
|
|
212
220
|
if (!opts.all) {
|
|
213
|
-
records = records.filter((r) => r.status !== "exited"
|
|
221
|
+
records = records.filter((r) => r.status !== "exited");
|
|
222
|
+
}
|
|
223
|
+
if (opts.active) {
|
|
224
|
+
records = records.filter((r) => isPidAlive(r.pid));
|
|
214
225
|
}
|
|
215
226
|
if (opts.cwdScope) {
|
|
216
227
|
const scope = opts.cwdScope;
|
|
@@ -237,7 +248,7 @@ async function resolveOne(keyword: string | undefined, opts: CommonOpts): Promis
|
|
|
237
248
|
}
|
|
238
249
|
const matches = await listRecords(keyword, opts);
|
|
239
250
|
if (matches.length === 0) {
|
|
240
|
-
throw new Error(`no
|
|
251
|
+
throw new Error(`no agent matched "${keyword}"`);
|
|
241
252
|
}
|
|
242
253
|
if (matches.length === 1) return matches[0]!;
|
|
243
254
|
if (opts.latest) return matches[0]!; // already sorted newest-first
|
|
@@ -291,12 +302,17 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
291
302
|
const IDLE_THRESHOLD_MS = 60 * 1000;
|
|
292
303
|
const rows = await Promise.all(
|
|
293
304
|
records.map(async (r) => {
|
|
294
|
-
let displayStatus
|
|
295
|
-
if (r.
|
|
305
|
+
let displayStatus: string;
|
|
306
|
+
if (!isPidAlive(r.pid)) {
|
|
307
|
+
displayStatus = "stopped";
|
|
308
|
+
} else if (r.log_file) {
|
|
296
309
|
const mtime = await stat(r.log_file)
|
|
297
310
|
.then((s) => s.mtimeMs)
|
|
298
311
|
.catch(() => null);
|
|
299
|
-
|
|
312
|
+
displayStatus =
|
|
313
|
+
mtime !== null && Date.now() - mtime > IDLE_THRESHOLD_MS ? "idle" : "active";
|
|
314
|
+
} else {
|
|
315
|
+
displayStatus = "active";
|
|
300
316
|
}
|
|
301
317
|
return {
|
|
302
318
|
pid: String(r.pid),
|
|
@@ -305,6 +321,7 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
305
321
|
age: humanizeAge(Date.now() - r.started_at),
|
|
306
322
|
cwd: shortenPath(r.cwd),
|
|
307
323
|
prompt: truncate(r.prompt ?? "", promptBudget),
|
|
324
|
+
_alive: displayStatus !== "stopped",
|
|
308
325
|
};
|
|
309
326
|
}),
|
|
310
327
|
);
|
|
@@ -333,15 +350,22 @@ async function cmdLs(rest: string[]): Promise<number> {
|
|
|
333
350
|
);
|
|
334
351
|
}
|
|
335
352
|
|
|
336
|
-
if (!opts.json &&
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
353
|
+
if (!opts.json && rows.length > 0) {
|
|
354
|
+
const alive = rows.find((r) => r._alive);
|
|
355
|
+
const stopped = rows.find((r) => !r._alive);
|
|
356
|
+
const hints: string[] = ["\n"];
|
|
357
|
+
if (alive) {
|
|
358
|
+
hints.push(` cy tail ${alive.pid} # view latest output\n`);
|
|
359
|
+
hints.push(` cy tail -f ${alive.pid} # follow live output\n`);
|
|
360
|
+
hints.push(` cy send ${alive.pid} "next: ..." # send a prompt\n`);
|
|
361
|
+
hints.push(` cy send ${alive.pid} "" --code=ctrl-c # interrupt\n`);
|
|
362
|
+
}
|
|
363
|
+
if (stopped) {
|
|
364
|
+
hints.push(` cy restart ${stopped.pid} # restart stopped agent\n`);
|
|
365
|
+
}
|
|
366
|
+
if (!alive && !stopped)
|
|
367
|
+
hints.push(` cy ls --all # show exited agents\n`);
|
|
368
|
+
process.stderr.write(hints.join(""));
|
|
345
369
|
}
|
|
346
370
|
|
|
347
371
|
return 0;
|
|
@@ -381,6 +405,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
381
405
|
const { flags, positional } = parseArgs(rest);
|
|
382
406
|
const opts = commonOpts(flags);
|
|
383
407
|
const keyword = positional[0];
|
|
408
|
+
const follow = !!(flags.f || flags.follow);
|
|
384
409
|
|
|
385
410
|
const nFlag = typeof flags.n === "string" ? Number(flags.n) : undefined;
|
|
386
411
|
const n =
|
|
@@ -412,9 +437,35 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
|
|
|
412
437
|
process.stdout.write(rendered);
|
|
413
438
|
if (!rendered.endsWith("\n")) process.stdout.write("\n");
|
|
414
439
|
|
|
440
|
+
if (follow) {
|
|
441
|
+
process.stderr.write(`following... (Ctrl-C to stop)\n`);
|
|
442
|
+
let offset = buf.length;
|
|
443
|
+
const { watch } = await import("fs");
|
|
444
|
+
// oxlint-disable-next-line no-control-regex -- intentional: strip ANSI/control
|
|
445
|
+
const ansiRe = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-Z\\-_]/g;
|
|
446
|
+
// oxlint-disable-next-line no-control-regex -- intentional: strip control chars
|
|
447
|
+
const ctrlRe = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
448
|
+
await new Promise<void>((resolve) => {
|
|
449
|
+
const watcher = watch(logPath, async () => {
|
|
450
|
+
const full = await readFile(logPath);
|
|
451
|
+
if (full.length <= offset) return;
|
|
452
|
+
const chunk = full.slice(offset);
|
|
453
|
+
offset = full.length;
|
|
454
|
+
const text = new TextDecoder().decode(chunk).replace(ansiRe, "").replace(ctrlRe, "");
|
|
455
|
+
if (text.trim()) process.stdout.write(text.trimStart());
|
|
456
|
+
});
|
|
457
|
+
process.on("SIGINT", () => {
|
|
458
|
+
watcher.close();
|
|
459
|
+
resolve();
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
return 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
415
465
|
process.stderr.write(
|
|
416
466
|
`\n` +
|
|
417
467
|
` cy ls # list all agents\n` +
|
|
468
|
+
` cy tail -f ${record.pid} # follow live output\n` +
|
|
418
469
|
` cy send ${record.pid} "next: ..." # send a prompt\n` +
|
|
419
470
|
` cy send ${record.pid} "" --code=ctrl-c # interrupt\n`,
|
|
420
471
|
);
|
|
@@ -491,8 +542,15 @@ async function cmdSend(rest: string[]): Promise<number> {
|
|
|
491
542
|
);
|
|
492
543
|
}
|
|
493
544
|
|
|
494
|
-
const
|
|
495
|
-
|
|
545
|
+
const body = message ?? "";
|
|
546
|
+
if (body && trailing) {
|
|
547
|
+
await writeToIpc(fifoPath, body);
|
|
548
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
549
|
+
await writeToIpc(fifoPath, trailing);
|
|
550
|
+
} else {
|
|
551
|
+
await writeToIpc(fifoPath, body + trailing);
|
|
552
|
+
}
|
|
553
|
+
const payload = body + trailing;
|
|
496
554
|
process.stdout.write(`sent to pid ${record.pid} (${record.cli}): ${truncate(payload, 80)}\n`);
|
|
497
555
|
|
|
498
556
|
process.stderr.write(
|
|
@@ -564,3 +622,38 @@ async function writeToIpc(ipcPath: string, payload: string): Promise<void> {
|
|
|
564
622
|
}
|
|
565
623
|
}
|
|
566
624
|
}
|
|
625
|
+
|
|
626
|
+
// ---------------------------------------------------------------------------
|
|
627
|
+
// cy restart
|
|
628
|
+
// ---------------------------------------------------------------------------
|
|
629
|
+
|
|
630
|
+
async function cmdRestart(rest: string[]): Promise<number> {
|
|
631
|
+
const { flags, positional } = parseArgs(rest);
|
|
632
|
+
const opts = { ...commonOpts(flags), all: true }; // search stopped agents too
|
|
633
|
+
const keyword = positional[0];
|
|
634
|
+
const record = await resolveOne(keyword, opts);
|
|
635
|
+
|
|
636
|
+
if (isPidAlive(record.pid)) {
|
|
637
|
+
process.stderr.write(`pid ${record.pid} is still running — stop it first or use cy send\n`);
|
|
638
|
+
return 1;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const args = ["--cli=" + record.cli];
|
|
642
|
+
if (record.prompt) args.push(record.prompt);
|
|
643
|
+
|
|
644
|
+
const proc = Bun.spawn(["agent-yes", ...args], {
|
|
645
|
+
cwd: record.cwd,
|
|
646
|
+
detached: true,
|
|
647
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
process.stdout.write(
|
|
651
|
+
`restarted ${record.cli} in ${shortenPath(record.cwd)} (new pid: ${proc.pid})\n`,
|
|
652
|
+
);
|
|
653
|
+
process.stderr.write(
|
|
654
|
+
`\n` +
|
|
655
|
+
` cy tail ${proc.pid} # watch output\n` +
|
|
656
|
+
` cy ls # list all agents\n`,
|
|
657
|
+
);
|
|
658
|
+
return 0;
|
|
659
|
+
}
|