claude-yes 1.149.0 → 1.149.3

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,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-9In7C_H_.js";
1
+ import { t as CLIS_CONFIG } from "./ts-DIrs0jef.js";
2
2
 
3
3
  //#region ts/SUPPORTED_CLIS.ts
4
4
  const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
5
5
 
6
6
  //#endregion
7
7
  export { SUPPORTED_CLIS as t };
8
- //# sourceMappingURL=SUPPORTED_CLIS-DmEBbACr.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-B52NvCuZ.js.map
@@ -0,0 +1,8 @@
1
+ import "./ts-DIrs0jef.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-D0pC8VFv.js";
4
+ import "./pidStore-fqXqTKkh.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B52NvCuZ.js";
7
+
8
+ export { SUPPORTED_CLIS };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
  import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
3
3
  import { n as logger } from "./logger-CDIsZ-Pp.js";
4
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-xwsWu7p6.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-D0pC8VFv.js";
5
5
  import { argv } from "process";
6
6
  import { execFileSync, spawn } from "child_process";
7
7
  import ms from "ms";
@@ -480,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
480
480
  const rawArg = process.argv[2];
481
481
  const managerCommands = !invokedCliName(process.argv);
482
482
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
483
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DCsphJXW.js");
483
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-CU3G6_N5.js");
484
484
  if (isHelpFlag && process.argv.length === 3) {
485
485
  cmdHelp(managerCommands);
486
486
  process.exit(0);
@@ -513,7 +513,7 @@ if (config.useRust) {
513
513
  }
514
514
  }
515
515
  if (rustBinary) {
516
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CFaRUz70.js");
516
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Cx4SPJk7.js");
517
517
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
518
518
  if (config.verbose) {
519
519
  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-9In7C_H_.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-DIrs0jef.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-xwsWu7p6.js";
3
+ import "./versionChecker-D0pC8VFv.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -1,9 +1,9 @@
1
- import "./ts-9In7C_H_.js";
1
+ import "./ts-DIrs0jef.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-xwsWu7p6.js";
3
+ import "./versionChecker-D0pC8VFv.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B52NvCuZ.js";
7
7
  import { i as resolveSpawnCwd } from "./workspaceConfig-B9VX8tVO.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-CdJjOPt7.js.map
144
+ //# sourceMappingURL=schedule-BBxvNa3v.js.map
@@ -1,16 +1,16 @@
1
- import "./ts-9In7C_H_.js";
1
+ import "./ts-DIrs0jef.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-xwsWu7p6.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-D0pC8VFv.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import { a as updateGlobalPidStatus } from "./globalPidIndex-DlmmJlO8.js";
6
6
  import { t as pgidForWrapper } from "./reaper-C-eWAxIj.js";
7
7
  import "./configShared-C1C04bbq.js";
8
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-B52NvCuZ.js";
9
9
  import "./e2e-ClOI_aqV.js";
10
10
  import "./webrtcLink-BWhuA74k.js";
11
11
  import "./remotes-qK6uozO4.js";
12
12
  import { i as resolveSpawnCwd, r as isProvisionAllowed, t as getProvisionRoot } from "./workspaceConfig-B9VX8tVO.js";
13
- import { S as snapshotStatus, _ as renderRawLog, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, w as writeToIpc, y as resolveOne } from "./subcommands-PCUd7vJf.js";
13
+ import { C as snapshotStatus, T as writeToIpc, _ as readPtysize, b as resolveOne, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, v as renderRawLog } from "./subcommands-Dl8RuPbC.js";
14
14
  import yargs from "yargs";
15
15
  import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
16
16
  import { homedir, hostname, userInfo } from "os";
@@ -802,9 +802,13 @@ Options:
802
802
  try {
803
803
  const record = await resolveOne(keyword, defaultOpts());
804
804
  if (!record.log_file) return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
805
- const text = await renderRawLog(await readFile(record.log_file), {
805
+ const buf = await readFile(record.log_file);
806
+ const size = await readPtysize(record.pid);
807
+ const text = await renderRawLog(buf, {
806
808
  mode,
807
- n
809
+ n,
810
+ cols: size?.cols,
811
+ rows: size?.rows
808
812
  });
809
813
  return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
810
814
  } catch (e) {
@@ -816,20 +820,11 @@ Options:
816
820
  const keyword = decodeURIComponent(sizeM[1]);
817
821
  try {
818
822
  const record = await resolveOne(keyword, defaultOpts());
819
- const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
820
- let cols = null;
821
- let rows = null;
822
- try {
823
- const [c = 0, r = 0] = (await readFile(path.join(ayHome, "ptysize", String(record.pid)), "utf-8")).trim().split(/\s+/).map(Number);
824
- if (c > 0 && r > 0) {
825
- cols = c;
826
- rows = r;
827
- }
828
- } catch {}
823
+ const size = await readPtysize(record.pid);
829
824
  return Response.json({
830
825
  pid: record.pid,
831
- cols,
832
- rows
826
+ cols: size?.cols ?? null,
827
+ rows: size?.rows ?? null
833
828
  });
834
829
  } catch (e) {
835
830
  return new Response(e.message, { status: 404 });
@@ -1235,4 +1230,4 @@ Options:
1235
1230
 
1236
1231
  //#endregion
1237
1232
  export { cmdServe };
1238
- //# sourceMappingURL=serve-j3Jwmk5O.js.map
1233
+ //# sourceMappingURL=serve-CC4R9M3V.js.map
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
32
32
  if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
33
33
  if (noShare) return 0;
34
34
  process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
35
- const { cmdServe } = await import("./serve-j3Jwmk5O.js");
35
+ const { cmdServe } = await import("./serve-CC4R9M3V.js");
36
36
  return cmdServe([
37
37
  "install",
38
38
  "--share",
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
42
42
 
43
43
  //#endregion
44
44
  export { cmdSetup };
45
- //# sourceMappingURL=setup-BsVxgyr7.js.map
45
+ //# sourceMappingURL=setup-CKRssOuZ.js.map
@@ -0,0 +1,9 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-DlmmJlO8.js";
3
+ import "./configShared-C1C04bbq.js";
4
+ import "./e2e-ClOI_aqV.js";
5
+ import "./webrtcLink-BWhuA74k.js";
6
+ import "./remotes-qK6uozO4.js";
7
+ import { C as snapshotStatus, S as runSubcommand, T as writeToIpc, _ as readPtysize, a as cursorAbs, b as resolveOne, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLog, w as stopTipForCli, x as resolveReadWindow, y as renderRawLogLines } from "./subcommands-Dl8RuPbC.js";
8
+
9
+ export { cmdHelp, isSubcommand, runSubcommand };
@@ -554,15 +554,15 @@ async function runSubcommand(argv) {
554
554
  case "restart": return await cmdRestart(rest);
555
555
  case "note": return await cmdNote(rest);
556
556
  case "serve": {
557
- const { cmdServe } = await import("./serve-j3Jwmk5O.js");
557
+ const { cmdServe } = await import("./serve-CC4R9M3V.js");
558
558
  return cmdServe(rest);
559
559
  }
560
560
  case "setup": {
561
- const { cmdSetup } = await import("./setup-BsVxgyr7.js");
561
+ const { cmdSetup } = await import("./setup-CKRssOuZ.js");
562
562
  return cmdSetup(rest);
563
563
  }
564
564
  case "schedule": {
565
- const { cmdSchedule } = await import("./schedule-CdJjOPt7.js");
565
+ const { cmdSchedule } = await import("./schedule-BBxvNa3v.js");
566
566
  return cmdSchedule(rest);
567
567
  }
568
568
  case "remote": {
@@ -1298,12 +1298,15 @@ async function cmdRead(rest, { mode }) {
1298
1298
  }
1299
1299
  if (!stats.isFile()) throw new Error(`pid ${record.pid}: log path is not a file: ${logPath}`);
1300
1300
  const buf = await readFile(logPath);
1301
+ const size = await readPtysize(record.pid);
1301
1302
  const noteLabel = (await readNotes()).get(record.pid);
1302
1303
  const header = noteLabel ? `[pid ${record.pid} ${shortenPath(record.cwd)} * ${noteLabel}]` : `[pid ${record.pid} ${shortenPath(record.cwd)}]`;
1303
1304
  if (follow) {
1304
1305
  const rendered = await renderRawLog(buf, {
1305
1306
  mode,
1306
- n
1307
+ n,
1308
+ cols: size?.cols,
1309
+ rows: size?.rows
1307
1310
  });
1308
1311
  process.stderr.write(header + "\n");
1309
1312
  process.stdout.write(rendered);
@@ -1311,7 +1314,10 @@ async function cmdRead(rest, { mode }) {
1311
1314
  setInterval(() => void recordRead(reader.key, record.pid), 3e4).unref?.();
1312
1315
  return plain ? followPlainLocal(logPath, buf) : followRawLocal(logPath, buf);
1313
1316
  }
1314
- const allLines = await renderRawLogLines(buf);
1317
+ const allLines = await renderRawLogLines(buf, {
1318
+ cols: size?.cols,
1319
+ rows: size?.rows
1320
+ });
1315
1321
  const total = allLines.length;
1316
1322
  const win = resolveReadWindow({
1317
1323
  total,
@@ -1493,11 +1499,31 @@ async function followPlainLocal(logPath, buf) {
1493
1499
  return 0;
1494
1500
  }
1495
1501
  /**
1502
+ * Read an agent's last-known PTY geometry from `~/.agent-yes/ptysize/<pid>`
1503
+ * (written by both runtimes — ts/index.ts and rs/src/pty_spawner.rs — as
1504
+ * "<cols> <rows>\n"). Returns null when there's no sidecar (older agent, or not
1505
+ * yet written).
1506
+ */
1507
+ async function readPtysize(pid) {
1508
+ const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
1509
+ try {
1510
+ const [c = 0, r = 0] = (await readFile(path.join(dir, "ptysize", String(pid)), "utf-8")).trim().split(/\s+/).map(Number);
1511
+ if (c > 0 && r > 0) return {
1512
+ cols: c,
1513
+ rows: r
1514
+ };
1515
+ } catch {}
1516
+ return null;
1517
+ }
1518
+ /**
1496
1519
  * Feed the raw PTY bytes through @xterm/headless and emit plain text.
1497
1520
  * Same approach as koho's renderTerminalBuffer + agent-yes's XtermProxy.
1498
1521
  */
1499
- async function renderRawLog(buf, { mode, n }) {
1500
- const lines = await renderRawLogLines(buf);
1522
+ async function renderRawLog(buf, { mode, n, cols, rows }) {
1523
+ const lines = await renderRawLogLines(buf, {
1524
+ cols,
1525
+ rows
1526
+ });
1501
1527
  if (mode === "cat") return lines.join("\n");
1502
1528
  if (mode === "tail") return lines.slice(Math.max(0, lines.length - n)).join("\n");
1503
1529
  return lines.slice(0, n).join("\n");
@@ -1510,9 +1536,9 @@ async function renderRawLog(buf, { mode, n }) {
1510
1536
  * cursor moves / clears / wraps), so we always render the whole buffer once and
1511
1537
  * window the resulting lines.
1512
1538
  */
1513
- async function renderRawLogLines(buf) {
1514
- const cols = 200;
1515
- const rows = 50;
1539
+ async function renderRawLogLines(buf, geom) {
1540
+ const cols = geom?.cols && geom.cols > 0 ? geom.cols : 200;
1541
+ const rows = geom?.rows && geom.rows > 0 ? geom.rows : 50;
1516
1542
  const scrollback = 5e4;
1517
1543
  try {
1518
1544
  const { Terminal } = await import("@xterm/headless");
@@ -2381,7 +2407,7 @@ async function cmdStatus(rest) {
2381
2407
  cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
2382
2408
  };
2383
2409
  const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
2384
- if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait | --wait-idle] [--timeout=Ns]");
2410
+ if (!keyword) throw new Error("usage: ay status <keyword> [--watch | --wait | --wait-idle] [--timeout=Ns] [--cwd=DIR] [--latest]");
2385
2411
  {
2386
2412
  const remote = await resolveRemoteSpec(keyword);
2387
2413
  if (remote) return runRemoteStatus(remote);
@@ -2592,5 +2618,5 @@ async function cmdResultSet(rest) {
2592
2618
  }
2593
2619
 
2594
2620
  //#endregion
2595
- export { stopTipForCli as C, snapshotStatus as S, renderRawLog as _, cursorAbs as a, resolveReadWindow as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLogLines as v, writeToIpc as w, runSubcommand as x, resolveOne as y };
2596
- //# sourceMappingURL=subcommands-PCUd7vJf.js.map
2621
+ export { snapshotStatus as C, runSubcommand as S, writeToIpc as T, readPtysize as _, cursorAbs as a, resolveOne as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLog as v, stopTipForCli as w, resolveReadWindow as x, renderRawLogLines as y };
2622
+ //# sourceMappingURL=subcommands-Dl8RuPbC.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
2
- import { r as getInstalledPackage } from "./versionChecker-xwsWu7p6.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-D0pC8VFv.js";
3
3
  import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
5
5
  import { t as PidStore } from "./pidStore-fqXqTKkh.js";
@@ -1228,8 +1228,10 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1228
1228
  if (unresolved) continue;
1229
1229
  ptyEnv[key] = value;
1230
1230
  }
1231
+ if (!ptyEnv.TERM) ptyEnv.TERM = "xterm-256color";
1232
+ if (!ptyEnv.COLORTERM) ptyEnv.COLORTERM = "truecolor";
1231
1233
  const ptyOptions = {
1232
- name: "xterm-color",
1234
+ name: ptyEnv.TERM,
1233
1235
  ...getTerminalDimensions(),
1234
1236
  cwd: cwd ?? process.cwd(),
1235
1237
  env: ptyEnv
@@ -1330,7 +1332,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1330
1332
  let [bin, ...args] = [...parseCommandString(cliConf?.binary || cli), ...cliArgs.filter((arg) => !["--continue", "--resume"].includes(arg))];
1331
1333
  logger.info(`Restarting ${cli} ${JSON.stringify([bin, ...args])}`);
1332
1334
  const restartPtyOptions = {
1333
- name: "xterm-color",
1335
+ name: ptyEnv.TERM,
1334
1336
  ...getTerminalDimensions(),
1335
1337
  cwd: cwd ?? process.cwd(),
1336
1338
  env: ptyEnv
@@ -1414,7 +1416,7 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1414
1416
  } else logger.debug(`restore|no stored session, using default restore args`);
1415
1417
  }
1416
1418
  const restorePtyOptions = {
1417
- name: "xterm-color",
1419
+ name: ptyEnv.TERM,
1418
1420
  ...getTerminalDimensions(),
1419
1421
  cwd: cwd ?? process.cwd(),
1420
1422
  env: ptyEnv
@@ -1800,4 +1802,4 @@ function sleep(ms) {
1800
1802
 
1801
1803
  //#endregion
1802
1804
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1803
- //# sourceMappingURL=ts-9In7C_H_.js.map
1805
+ //# sourceMappingURL=ts-DIrs0jef.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.149.0";
10
+ var version = "1.149.3";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -215,4 +215,4 @@ async function displayVersion() {
215
215
 
216
216
  //#endregion
217
217
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
218
- //# sourceMappingURL=versionChecker-xwsWu7p6.js.map
218
+ //# sourceMappingURL=versionChecker-D0pC8VFv.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.149.0",
3
+ "version": "1.149.3",
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/index.ts CHANGED
@@ -395,8 +395,17 @@ export default async function agentYes({
395
395
  ptyEnv[key] = value;
396
396
  }
397
397
  }
398
+ // The agent runs in a PTY (a real terminal), so advertise terminal
399
+ // capabilities. A console/daemon-spawned agent inherits an env with no TERM/
400
+ // COLORTERM: neither the daemon (no controlling terminal) nor the recovered
401
+ // login-shell env (captured without a tty) carries them — those vars are set
402
+ // by the terminal emulator, not by the shell. Without them the wrapped CLI
403
+ // renders colorless in the web console. Fill only when absent so a
404
+ // terminal-launched agent keeps its real values (e.g. xterm-256color, tmux).
405
+ if (!ptyEnv.TERM) ptyEnv.TERM = "xterm-256color";
406
+ if (!ptyEnv.COLORTERM) ptyEnv.COLORTERM = "truecolor";
398
407
  const ptyOptions = {
399
- name: "xterm-color",
408
+ name: ptyEnv.TERM,
400
409
  ...getTerminalDimensions(),
401
410
  cwd: cwd ?? process.cwd(),
402
411
  env: ptyEnv,
@@ -561,7 +570,7 @@ export default async function agentYes({
561
570
  logger.info(`Restarting ${cli} ${JSON.stringify([bin, ...args])}`);
562
571
 
563
572
  const restartPtyOptions = {
564
- name: "xterm-color",
573
+ name: ptyEnv.TERM,
565
574
  ...getTerminalDimensions(),
566
575
  cwd: cwd ?? process.cwd(),
567
576
  env: ptyEnv,
@@ -667,7 +676,7 @@ export default async function agentYes({
667
676
  }
668
677
 
669
678
  const restorePtyOptions = {
670
- name: "xterm-color",
679
+ name: ptyEnv.TERM,
671
680
  ...getTerminalDimensions(),
672
681
  cwd: cwd ?? process.cwd(),
673
682
  env: ptyEnv,
package/ts/serve.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  extractTaskCounts,
12
12
  listRecords,
13
13
  readNotes,
14
+ readPtysize,
14
15
  renderRawLog,
15
16
  resolveOne,
16
17
  snapshotStatus,
@@ -1163,7 +1164,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
1163
1164
  if (!record.log_file)
1164
1165
  return new Response(`pid ${record.pid}: no log_file`, { status: 404 });
1165
1166
  const buf = await readFile(record.log_file);
1166
- const text = await renderRawLog(buf, { mode, n });
1167
+ const size = await readPtysize(record.pid);
1168
+ const text = await renderRawLog(buf, { mode, n, cols: size?.cols, rows: size?.rows });
1167
1169
  return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
1168
1170
  } catch (e) {
1169
1171
  return new Response((e as Error).message, { status: 404 });
@@ -1177,20 +1179,12 @@ export async function cmdServe(rest: string[]): Promise<number> {
1177
1179
  const keyword = decodeURIComponent(sizeM[1]!);
1178
1180
  try {
1179
1181
  const record = await resolveOne(keyword, defaultOpts());
1180
- const ayHome = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
1181
- let cols: number | null = null;
1182
- let rows: number | null = null;
1183
- try {
1184
- const txt = await readFile(path.join(ayHome, "ptysize", String(record.pid)), "utf-8");
1185
- const [c = 0, r = 0] = txt.trim().split(/\s+/).map(Number);
1186
- if (c > 0 && r > 0) {
1187
- cols = c;
1188
- rows = r;
1189
- }
1190
- } catch {
1191
- /* no ptysize sidecar (older agent or not yet written) */
1192
- }
1193
- return Response.json({ pid: record.pid, cols, rows });
1182
+ const size = await readPtysize(record.pid);
1183
+ return Response.json({
1184
+ pid: record.pid,
1185
+ cols: size?.cols ?? null,
1186
+ rows: size?.rows ?? null,
1187
+ });
1194
1188
  } catch (e) {
1195
1189
  return new Response((e as Error).message, { status: 404 });
1196
1190
  }
@@ -1718,3 +1718,80 @@ describe("subcommands.isAgentStuck / stuck state", () => {
1718
1718
  }
1719
1719
  });
1720
1720
  });
1721
+
1722
+ // A CLI like Claude Code repaints by moving the cursor UP over the previous
1723
+ // frame and rewriting it. The up-count is the frame's height AT THE AGENT'S REAL
1724
+ // WIDTH. Replayed narrower, the body line wraps to an extra row, the up-count
1725
+ // undershoots, and every old frame strands below as a duplicate — the `ay tail`
1726
+ // stutter. `bodyLen` chars stay one row at the real width but wrap below it.
1727
+ function buildRedrawLog(frames: number, bodyLen: number): Buffer {
1728
+ const body = "BODY-" + "z".repeat(Math.max(0, bodyLen - 5));
1729
+ const frame = (i: number) => `HEADER-${i}\r\n${body}`;
1730
+ let bytes = frame(0) + "\r\n";
1731
+ for (let i = 1; i < frames; i++) bytes += `\x1b[2A\r` + frame(i) + "\r\n"; // up 2 = header+body at real width
1732
+ return Buffer.from(bytes);
1733
+ }
1734
+ const countHeaders = (s: string) => (s.match(/^HEADER-\d+/gm) ?? []).length;
1735
+
1736
+ describe("renderRawLog honors the agent's recorded PTY geometry", () => {
1737
+ it("collapses redraw frames at the recorded width but duplicates at a mismatched width", async () => {
1738
+ const { renderRawLog } = await loadModule();
1739
+ const buf = buildRedrawLog(6, 220); // one row at >=220 cols, two rows below that
1740
+
1741
+ // Replayed at the real width, each repaint lands on the prior frame: one header.
1742
+ const correct = await renderRawLog(buf, { mode: "cat", n: 0, cols: 240, rows: 50 });
1743
+ expect(countHeaders(correct)).toBe(1);
1744
+
1745
+ // Replayed narrower (the body wraps), repaints undershoot and pile up.
1746
+ const wrong = await renderRawLog(buf, { mode: "cat", n: 0, cols: 120, rows: 50 });
1747
+ expect(countHeaders(wrong)).toBeGreaterThan(1);
1748
+ });
1749
+ });
1750
+
1751
+ describe("subcommands.cmdRead replays at the ptysize sidecar geometry", () => {
1752
+ it("renders at the recorded geometry, not the 200-col fallback", async () => {
1753
+ const { runSubcommand } = await loadModule();
1754
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
1755
+ const tmp = await mkdtemp(path.join(tmpdir(), "ay-raw-log-"));
1756
+ try {
1757
+ const logPath = path.join(tmp, "wide.raw.log");
1758
+ // Authored for a 240-col terminal; at the 200-col fallback the body wraps
1759
+ // and the redraw duplicates (see buildRedrawLog / renderRawLogLines).
1760
+ await writeFile(logPath, buildRedrawLog(6, 220));
1761
+
1762
+ // ptysize sidecar lives under the (mocked) home: ~/.agent-yes/ptysize/<pid>.
1763
+ const ptDir = path.join(testHome, ".agent-yes", "ptysize");
1764
+ await mkdir(ptDir, { recursive: true });
1765
+ await writeFile(path.join(ptDir, String(process.pid)), "240 50\n");
1766
+
1767
+ await appendGlobalPid({
1768
+ pid: process.pid,
1769
+ cli: "claude",
1770
+ prompt: null,
1771
+ cwd: process.cwd(),
1772
+ log_file: logPath,
1773
+ status: "active" as const,
1774
+ exit_code: null,
1775
+ exit_reason: null,
1776
+ started_at: Date.now(),
1777
+ });
1778
+
1779
+ const stdout: string[] = [];
1780
+ const orig = process.stdout.write.bind(process.stdout);
1781
+ (process.stdout as any).write = (s: any) => {
1782
+ stdout.push(String(s));
1783
+ return true;
1784
+ };
1785
+ try {
1786
+ const code = await runSubcommand(["bun", "cli.js", "cat", String(process.pid)]);
1787
+ expect(code).toBe(0);
1788
+ } finally {
1789
+ process.stdout.write = orig;
1790
+ }
1791
+ // With the sidecar honored, the six repaints collapse to a single frame.
1792
+ expect(countHeaders(stdout.join(""))).toBe(1);
1793
+ } finally {
1794
+ await rm(tmp, { recursive: true, force: true }).catch(() => null);
1795
+ }
1796
+ });
1797
+ });
package/ts/subcommands.ts CHANGED
@@ -1406,6 +1406,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
1406
1406
  }
1407
1407
 
1408
1408
  const buf = await readFile(logPath);
1409
+ const size = await readPtysize(record.pid);
1409
1410
  const notes = await readNotes();
1410
1411
  const noteLabel = notes.get(record.pid);
1411
1412
  const header = noteLabel
@@ -1414,7 +1415,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
1414
1415
 
1415
1416
  if (follow) {
1416
1417
  // Follow mode ignores pagination: print the initial context, then stream deltas.
1417
- const rendered = await renderRawLog(buf, { mode, n });
1418
+ const rendered = await renderRawLog(buf, { mode, n, cols: size?.cols, rows: size?.rows });
1418
1419
  process.stderr.write(header + "\n");
1419
1420
  process.stdout.write(rendered);
1420
1421
  if (!rendered.endsWith("\n")) process.stdout.write("\n");
@@ -1427,7 +1428,7 @@ async function cmdRead(rest: string[], { mode }: ReadOpts): Promise<number> {
1427
1428
 
1428
1429
  // Static read: render the full log once, then window into the rendered lines
1429
1430
  // so line numbers (and the pagination cursor in the footer) are exact.
1430
- const allLines = await renderRawLogLines(buf);
1431
+ const allLines = await renderRawLogLines(buf, { cols: size?.cols, rows: size?.rows });
1431
1432
  const total = allLines.length;
1432
1433
  const win = resolveReadWindow({
1433
1434
  total,
@@ -1663,15 +1664,42 @@ async function followPlainLocal(logPath: string, buf: Uint8Array): Promise<numbe
1663
1664
  return 0;
1664
1665
  }
1665
1666
 
1667
+ /**
1668
+ * The agent's real PTY geometry (from readPtysize), passed to the renderers so
1669
+ * the raw log replays at the size it was authored for. Omitted → a wide 200x50
1670
+ * default; if the agent ran wider/taller than that its cursor-addressed redraw
1671
+ * frames undershoot on replay and strand into scrollback as duplicates (the
1672
+ * `ay tail` stutter this guards).
1673
+ */
1674
+ type RenderGeom = { cols?: number; rows?: number };
1675
+
1676
+ /**
1677
+ * Read an agent's last-known PTY geometry from `~/.agent-yes/ptysize/<pid>`
1678
+ * (written by both runtimes — ts/index.ts and rs/src/pty_spawner.rs — as
1679
+ * "<cols> <rows>\n"). Returns null when there's no sidecar (older agent, or not
1680
+ * yet written).
1681
+ */
1682
+ export async function readPtysize(pid: number): Promise<{ cols: number; rows: number } | null> {
1683
+ const dir = process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
1684
+ try {
1685
+ const txt = await readFile(path.join(dir, "ptysize", String(pid)), "utf-8");
1686
+ const [c = 0, r = 0] = txt.trim().split(/\s+/).map(Number);
1687
+ if (c > 0 && r > 0) return { cols: c, rows: r };
1688
+ } catch {
1689
+ /* no ptysize sidecar */
1690
+ }
1691
+ return null;
1692
+ }
1693
+
1666
1694
  /**
1667
1695
  * Feed the raw PTY bytes through @xterm/headless and emit plain text.
1668
1696
  * Same approach as koho's renderTerminalBuffer + agent-yes's XtermProxy.
1669
1697
  */
1670
1698
  export async function renderRawLog(
1671
1699
  buf: Uint8Array,
1672
- { mode, n }: { mode: "cat" | "tail" | "head"; n: number },
1700
+ { mode, n, cols, rows }: { mode: "cat" | "tail" | "head"; n: number } & RenderGeom,
1673
1701
  ): Promise<string> {
1674
- const lines = await renderRawLogLines(buf);
1702
+ const lines = await renderRawLogLines(buf, { cols, rows });
1675
1703
  if (mode === "cat") return lines.join("\n");
1676
1704
  if (mode === "tail") return lines.slice(Math.max(0, lines.length - n)).join("\n");
1677
1705
  return lines.slice(0, n).join("\n");
@@ -1685,11 +1713,13 @@ export async function renderRawLog(
1685
1713
  * cursor moves / clears / wraps), so we always render the whole buffer once and
1686
1714
  * window the resulting lines.
1687
1715
  */
1688
- export async function renderRawLogLines(buf: Uint8Array): Promise<string[]> {
1689
- // Default screen geometry we don't know what the agent used, but
1690
- // 200x50 is a reasonable upper bound that won't truncate normal output.
1691
- const cols = 200;
1692
- const rows = 50;
1716
+ export async function renderRawLogLines(buf: Uint8Array, geom?: RenderGeom): Promise<string[]> {
1717
+ // Replay at the agent's real geometry when known (see RenderGeom / readPtysize);
1718
+ // otherwise fall back to a wide 200x50 a reasonable upper bound that won't
1719
+ // truncate normal output, though an agent wider/taller than it can still
1720
+ // duplicate on replay (which is why callers pass the recorded size).
1721
+ const cols = geom?.cols && geom.cols > 0 ? geom.cols : 200;
1722
+ const rows = geom?.rows && geom.rows > 0 ? geom.rows : 50;
1693
1723
  // Scrollback caps how far back pagination can reach; older lines are evicted.
1694
1724
  const scrollback = 50000;
1695
1725
 
@@ -2911,7 +2941,9 @@ async function cmdStatus(rest: string[]): Promise<number> {
2911
2941
  const keyword = argv._[0] !== undefined ? String(argv._[0]) : undefined;
2912
2942
 
2913
2943
  if (!keyword)
2914
- throw new Error("usage: ay status <keyword> [--watch | --wait | --wait-idle] [--timeout=Ns]");
2944
+ throw new Error(
2945
+ "usage: ay status <keyword> [--watch | --wait | --wait-idle] [--timeout=Ns] [--cwd=DIR] [--latest]",
2946
+ );
2915
2947
 
2916
2948
  {
2917
2949
  const remote = await resolveRemoteSpec(keyword);
@@ -1,8 +0,0 @@
1
- import "./ts-9In7C_H_.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-xwsWu7p6.js";
4
- import "./pidStore-fqXqTKkh.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
7
-
8
- export { SUPPORTED_CLIS };
@@ -1,9 +0,0 @@
1
- import "./logger-CDIsZ-Pp.js";
2
- import "./globalPidIndex-DlmmJlO8.js";
3
- import "./configShared-C1C04bbq.js";
4
- import "./e2e-ClOI_aqV.js";
5
- import "./webrtcLink-BWhuA74k.js";
6
- import "./remotes-qK6uozO4.js";
7
- import { C as stopTipForCli, S as snapshotStatus, _ as renderRawLog, a as cursorAbs, b as resolveReadWindow, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLogLines, w as writeToIpc, x as runSubcommand, y as resolveOne } from "./subcommands-PCUd7vJf.js";
8
-
9
- export { cmdHelp, isSubcommand, runSubcommand };