claude-yes 1.36.4 → 1.37.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.36.4",
3
+ "version": "1.37.1",
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",
@@ -66,6 +66,8 @@
66
66
  "postbuild": "bun ./ts/postbuild.ts",
67
67
  "demo": "bun run build && bun link && claude-yes -- demo",
68
68
  "dev": "bun ts/index.ts",
69
+ "dev:tsgo": "tsgo ts/cli.ts",
70
+ "test:tsgo": "tsgo --test",
69
71
  "fmt": "oxlint --fix --fix-suggestions && oxfmt",
70
72
  "_prepack": "bun run build",
71
73
  "prepare": "husky",
@@ -91,6 +93,7 @@
91
93
  "@types/ms": "^2.1.0",
92
94
  "@types/node": "^25.0.10",
93
95
  "@types/yargs": "^17.0.35",
96
+ "@typescript/native-preview": "^7.0.0-dev.20260124.1",
94
97
  "cpu-wait": "^0.0.10",
95
98
  "execa": "^9.6.1",
96
99
  "husky": "^9.1.7",
@@ -125,25 +128,5 @@
125
128
  },
126
129
  "engines": {
127
130
  "node": ">=22.0.0"
128
- },
129
- "_release": {
130
- "branches": [
131
- "main"
132
- ],
133
- "plugins": [
134
- "@semantic-release/commit-analyzer",
135
- "@semantic-release/release-notes-generator",
136
- "@semantic-release/changelog",
137
- "@semantic-release/npm",
138
- [
139
- "@semantic-release/exec",
140
- {
141
- "publishCmd": "npm pkg set name=claude-yes"
142
- }
143
- ],
144
- "@semantic-release/npm",
145
- "@semantic-release/git",
146
- "@semantic-release/github"
147
- ]
148
131
  }
149
132
  }
package/ts/index.ts CHANGED
@@ -21,6 +21,22 @@ import { createFifoStream } from "./beta/fifo.ts";
21
21
  import { PidStore } from "./pidStore.ts";
22
22
  import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
23
23
  import winston from "winston";
24
+ import { sendEnter, sendMessage, type MessageContext } from "./core/messaging.ts";
25
+ import {
26
+ initializeLogPaths,
27
+ setupDebugLogging,
28
+ saveLogFile,
29
+ saveDeprecatedLogFile,
30
+ type LogPaths,
31
+ } from "./core/logging.ts";
32
+ import { spawnAgent, getTerminalDimensions } from "./core/spawner.ts";
33
+ import { AgentContext } from "./core/context.ts";
34
+ import { createAutoResponseHandler } from "./core/responders.ts";
35
+ import {
36
+ handleConsoleControlCodes,
37
+ createTerminateSignalHandler,
38
+ createTerminatorStream,
39
+ } from "./core/streamHelpers.ts";
24
40
 
25
41
  export { removeControlCharacters };
26
42
 
@@ -175,44 +191,16 @@ export default async function agentYes({
175
191
 
176
192
  process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
177
193
 
178
- let isFatal = false; // when true, do not restart on crash, and exit agent
179
- let shouldRestartWithoutContinue = false; // when true, restart without continue args
180
-
181
- const stdinReady = new ReadyManager();
182
- const stdinFirstReady = new ReadyManager(); // if user send ctrl+c before
183
-
184
- // force ready after 10s to avoid stuck forever if the ready-word mismatched
185
- sleep(10e3).then(() => {
186
- if (!stdinReady.isReady) stdinReady.ready();
187
- if (!stdinFirstReady.isReady) stdinFirstReady.ready();
188
- });
189
- const nextStdout = new ReadyManager();
190
-
191
194
  const shellOutputStream = new TransformStream<string, string>();
192
195
  const outputWriter = shellOutputStream.writable.getWriter();
193
196
 
194
197
  logger.debug(`Using ${ptyPackage} for pseudo terminal management.`);
195
198
 
196
- let logPath: string | false = false;
197
- let rawLogPath: string | false = false;
198
- let rawLinesLogPath: string | false = false;
199
- let debuggingLogsPath: string | false = false;
200
-
201
199
  // Detect if running as sub-agent
202
200
  const isSubAgent = !!process.env.CLAUDE_PPID;
203
201
  if (isSubAgent)
204
202
  logger.info(`[${cli}-yes] Running as sub-agent (CLAUDE_PPID=${process.env.CLAUDE_PPID})`);
205
203
 
206
- const getPtyOptions = () => {
207
- const ptyEnv = { ...(env ?? (process.env as Record<string, string>)) };
208
- return {
209
- name: "xterm-color",
210
- ...getTerminalDimensions(),
211
- cwd: cwd ?? process.cwd(),
212
- env: ptyEnv,
213
- };
214
- };
215
-
216
204
  // Apply CLI specific configurations (moved to CLI_CONFIGURES)
217
205
  const cliConf = (CLIS_CONFIG as Record<string, AgentCliConfig>)[cli] || {};
218
206
  cliArgs = cliConf.defaultArgs ? [...cliConf.defaultArgs, ...cliArgs] : cliArgs;
@@ -327,118 +315,46 @@ export default async function agentYes({
327
315
  logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
328
316
  }
329
317
  }
330
- // Determine the actual cli command to run
331
-
332
- // Helper function to get install command based on platform/availability
333
- const getInstallCommand = (
334
- installConfig:
335
- | string
336
- | { powershell?: string; bash?: string; npm?: string; unix?: string; windows?: string },
337
- ): string | null => {
338
- if (typeof installConfig === "string") {
339
- return installConfig;
340
- }
341
318
 
342
- const isWindows = process.platform === "win32";
343
- const platform = isWindows ? "windows" : "unix";
344
-
345
- // Try platform-specific commands first
346
- if (installConfig[platform]) {
347
- return installConfig[platform];
348
- }
349
-
350
- // Try shell-specific commands
351
- if (isWindows && installConfig.powershell) {
352
- return installConfig.powershell;
353
- }
354
-
355
- if (!isWindows && installConfig.bash) {
356
- return installConfig.bash;
357
- }
358
-
359
- // Fallback to npm if available
360
- if (installConfig.npm) {
361
- return installConfig.npm;
362
- }
363
-
364
- return null;
365
- };
366
-
367
- const spawn = () => {
368
- const cliCommand = cliConf?.binary || cli;
369
- let [bin, ...args] = [...parseCommandString(cliCommand), ...cliArgs];
370
- if (verbose) logger.info(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
371
- logger.info(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
372
- // throw new Error(JSON.stringify([bin!, args, getPtyOptions()]))
373
- const spawned = pty.spawn(bin!, args, getPtyOptions());
374
- logger.info(`[${cli}-yes] Spawned ${bin} with PID ${spawned.pid}`);
375
- // if (globalThis.Bun)
376
- // args = args.map((arg) => `'${arg.replace(/'/g, "\\'")}'`);
377
- return spawned;
319
+ // Spawn the agent CLI process
320
+ const ptyEnv = { ...(env ?? (process.env as Record<string, string>)) };
321
+ const ptyOptions = {
322
+ name: "xterm-color",
323
+ ...getTerminalDimensions(),
324
+ cwd: cwd ?? process.cwd(),
325
+ env: ptyEnv,
378
326
  };
379
327
 
380
- let shell = catcher(
381
- // error handler
382
- (error: unknown, _fn, ..._args) => {
383
- logger.error(`Fatal: Failed to start ${cli}.`);
384
-
385
- const isNotFound = isCommandNotFoundError(error);
386
- if (cliConf?.install && isNotFound) {
387
- const installCmd = getInstallCommand(cliConf.install);
388
- if (!installCmd) {
389
- logger.error(`No suitable install command found for ${cli} on this platform`);
390
- throw error;
391
- }
392
-
393
- logger.info(`Please install the cli by run ${installCmd}`);
394
-
395
- if (install) {
396
- logger.info(`Attempting to install ${cli}...`);
397
- execaCommandSync(installCmd, { stdio: "inherit" });
398
- logger.info(`${cli} installed successfully. Please rerun the command.`);
399
- return spawn();
400
- } else {
401
- logger.error(`If you did not installed it yet, Please install it first: ${installCmd}`);
402
- throw error;
403
- }
404
- }
405
-
406
- if (globalThis.Bun && error instanceof Error && error.stack?.includes("bun-pty")) {
407
- // try to fix bun-pty issues
408
- logger.error(`Detected bun-pty issue, attempted to fix it. Please try again.`);
409
- require("./pty-fix");
410
- // unable to retry with same process, so exit here.
411
- }
412
- throw error;
413
-
414
- function isCommandNotFoundError(e: unknown) {
415
- if (e instanceof Error) {
416
- return (
417
- e.message.includes("command not found") || // unix
418
- e.message.includes("ENOENT") || // unix
419
- e.message.includes("spawn") // windows
420
- );
421
- }
422
- return false;
423
- }
424
- },
425
- spawn,
426
- )();
328
+ let shell = spawnAgent({
329
+ cli,
330
+ cliConf,
331
+ cliArgs,
332
+ verbose,
333
+ install,
334
+ ptyOptions,
335
+ });
427
336
 
428
337
  // Register process in pidStore and compute log paths
429
338
  await pidStore.registerProcess({ pid: shell.pid, cli, args: cliArgs, prompt });
430
- logPath = pidStore.getLogPath(shell.pid);
431
- rawLogPath = path.resolve(path.dirname(logPath), `${shell.pid}.raw.log`);
432
- rawLinesLogPath = path.resolve(path.dirname(logPath), `${shell.pid}.lines.log`);
433
- debuggingLogsPath = path.resolve(path.dirname(logPath), `${shell.pid}.debug.log`);
434
-
435
- if (debuggingLogsPath)
436
- logger.add(
437
- new winston.transports.File({
438
- filename: debuggingLogsPath,
439
- level: "debug",
440
- }),
441
- );
339
+ const logPaths = initializeLogPaths(pidStore, shell.pid);
340
+ setupDebugLogging(logPaths.debuggingLogsPath);
341
+
342
+ // Create agent context
343
+ const ctx = new AgentContext({
344
+ shell,
345
+ pidStore,
346
+ logPaths,
347
+ cli,
348
+ cliConf,
349
+ verbose,
350
+ robust,
351
+ });
352
+
353
+ // force ready after 10s to avoid stuck forever if the ready-word mismatched
354
+ sleep(10e3).then(() => {
355
+ if (!ctx.stdinReady.isReady) ctx.stdinReady.ready();
356
+ if (!ctx.stdinFirstReady.isReady) ctx.stdinFirstReady.ready();
357
+ });
442
358
 
443
359
  const pendingExitCode = Promise.withResolvers<number | null>();
444
360
 
@@ -449,18 +365,18 @@ export default async function agentYes({
449
365
 
450
366
  shell.onData(onData);
451
367
  shell.onExit(async function onExit({ exitCode }) {
452
- stdinReady.unready(); // start buffer stdin
368
+ ctx.stdinReady.unready(); // start buffer stdin
453
369
  const agentCrashed = exitCode !== 0;
454
370
 
455
371
  // Handle restart without continue args (e.g., "No conversation found to continue")
456
372
  // logger.debug(``, { shouldRestartWithoutContinue, robust })
457
- if (shouldRestartWithoutContinue) {
373
+ if (ctx.shouldRestartWithoutContinue) {
458
374
  await pidStore.updateStatus(shell.pid, "exited", {
459
375
  exitReason: "restarted",
460
376
  exitCode: exitCode ?? undefined,
461
377
  });
462
- shouldRestartWithoutContinue = false; // reset flag
463
- isFatal = false; // reset fatal flag to allow restart
378
+ ctx.shouldRestartWithoutContinue = false; // reset flag
379
+ ctx.isFatal = false; // reset fatal flag to allow restart
464
380
 
465
381
  // Restart without continue args - use original cliArgs without restoreArgs
466
382
  const cliCommand = cliConf?.binary || cli;
@@ -487,7 +403,7 @@ export default async function agentYes({
487
403
  );
488
404
  return;
489
405
  }
490
- if (isFatal) {
406
+ if (ctx.isFatal) {
491
407
  await pidStore.updateStatus(shell.pid, "exited", {
492
408
  exitReason: "fatal",
493
409
  exitCode: exitCode ?? undefined,
@@ -541,9 +457,8 @@ export default async function agentYes({
541
457
  .replace(/\s+/g, " ")
542
458
  .match(/esc to interrupt|to run in background/);
543
459
 
544
- const idleWaiter = new IdleWaiter();
545
460
  if (exitOnIdle)
546
- idleWaiter.wait(exitOnIdle).then(async () => {
461
+ ctx.idleWaiter.wait(exitOnIdle).then(async () => {
547
462
  await pidStore.updateStatus(shell.pid, "idle").catch(() => null);
548
463
  if (isStillWorkingQ()) {
549
464
  logger.warn("[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet");
@@ -562,22 +477,11 @@ export default async function agentYes({
562
477
  .map((buffer) => buffer.toString())
563
478
 
564
479
  .by(function handleTerminateSignals(s) {
565
- let aborted = false;
566
- return s.map((chunk) => {
567
- // handle CTRL+Z and filter it out, as I dont know how to support it yet
568
- if (!aborted && chunk === "\u001A") {
569
- return "";
570
- }
571
- // handle CTRL+C, when stdin is not ready (no response from agent yet, usually this is when agent loading)
572
- if (!aborted && !stdinReady.isReady && chunk === "\u0003") {
573
- logger.error("User aborted: SIGINT");
574
- shell.kill("SIGINT");
575
- pendingExitCode.resolve(130); // SIGINT
576
- aborted = true;
577
- return chunk; // still pass into agent, but they prob be killed XD
578
- }
579
- return chunk; // normal inputs
480
+ const handler = createTerminateSignalHandler(ctx.stdinReady, (exitCode) => {
481
+ shell.kill("SIGINT");
482
+ pendingExitCode.resolve(exitCode);
580
483
  });
484
+ return s.map(handler);
581
485
  })
582
486
 
583
487
  // read from IPC stream if available (FIFO on Linux, Named Pipes on Windows)
@@ -596,14 +500,14 @@ export default async function agentYes({
596
500
  .onStart(async function promptOnStart() {
597
501
  // send prompt when start
598
502
  logger.debug("Sending prompt message: " + JSON.stringify(prompt));
599
- if (prompt) await sendMessage(prompt);
503
+ if (prompt) await sendMessage(ctx.messageContext, prompt);
600
504
  })
601
505
 
602
506
  // pipe content by shell
603
507
  .by({
604
508
  writable: new WritableStream<string>({
605
509
  write: async (data) => {
606
- await stdinReady.wait();
510
+ await ctx.stdinReady.wait();
607
511
  shell.write(data);
608
512
  },
609
513
  }),
@@ -611,21 +515,21 @@ export default async function agentYes({
611
515
  })
612
516
 
613
517
  .forEach(() => {
614
- idleWaiter.ping();
518
+ ctx.idleWaiter.ping();
615
519
  pidStore.updateStatus(shell.pid, "active").catch(() => null);
616
520
  })
617
- .forEach(() => nextStdout.ready())
521
+ .forEach(() => ctx.nextStdout.ready())
618
522
 
619
523
  .forkTo(async function rawLogger(f) {
620
- if (!rawLogPath) return f.run(); // no stream
524
+ if (!ctx.logPaths.rawLogPath) return f.run(); // no stream
621
525
 
622
526
  // try stream the raw log for realtime debugging, including control chars, note: it will be a huge file
623
- return await mkdir(path.dirname(rawLogPath), { recursive: true })
527
+ return await mkdir(path.dirname(ctx.logPaths.rawLogPath), { recursive: true })
624
528
  .then(() => {
625
- logger.debug(`[${cli}-yes] raw logs streaming to ${rawLogPath}`);
529
+ logger.debug(`[${cli}-yes] raw logs streaming to ${ctx.logPaths.rawLogPath}`);
626
530
  return f
627
531
  .forEach(async (chars) => {
628
- await writeFile(rawLogPath, chars, { flag: "a" }).catch(() => null);
532
+ await writeFile(ctx.logPaths.rawLogPath!, chars, { flag: "a" }).catch(() => null);
629
533
  })
630
534
  .run();
631
535
  })
@@ -636,36 +540,7 @@ export default async function agentYes({
636
540
  .by(function consoleResponder(e) {
637
541
  // wait for cli ready and send prompt if provided
638
542
  if (cli === "codex") shell.write(`\u001b[1;1R`); // send cursor position response when stdin is not tty
639
- return e.forEach((text) => {
640
- // render terminal output for log file
641
- terminalRender.write(text);
642
-
643
- // Handle Device Attributes query (DA) - ESC[c or ESC[0c
644
- // This must be handled regardless of TTY status
645
- if (text.includes("\u001b[c") || text.includes("\u001b[0c")) {
646
- // Respond shell with VT100 with Advanced Video Option
647
- shell.write("\u001b[?1;2c");
648
- if (verbose) {
649
- logger.debug("device|respond DA: VT100 with Advanced Video Option");
650
- }
651
- return;
652
- }
653
-
654
- // todo: .onStatus((msg)=> shell.write(msg))
655
- if (process.stdin.isTTY) return; // only handle it when stdin is not tty, because tty already handled this
656
-
657
- if (!text.includes("\u001b[6n")) return; // only asked for cursor position
658
- // todo: use terminalRender API to get cursor position when new version is available
659
- // xterm replies CSI row; column R if asked cursor position
660
- // https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B
661
- // when agent asking position, respond with row; col
662
- // const rendered = terminalRender.render();
663
- const { col, row } = terminalRender.getCursorPosition();
664
- shell.write(`\u001b[${row};${col}R`); // reply cli when getting cursor position
665
- logger.debug(`cursor|respond position: row=${String(row)}, col=${String(col)}`);
666
- // const row = rendered.split('\n').length + 1;
667
- // const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
668
- });
543
+ return e.forEach((text) => handleConsoleControlCodes(text, shell, terminalRender, cli, verbose));
669
544
  })
670
545
 
671
546
  // auto-response
@@ -695,75 +570,19 @@ export default async function agentYes({
695
570
  // })
696
571
 
697
572
  // Generic auto-response handler driven by CLI_CONFIGURES
698
- .forEach(async function autoResponseOnChunk(e, i) {
699
- logger.debug(`stdout|${e}`);
700
- // ready matcher: if matched, mark stdin ready
701
- if (conf.ready?.some((rx: RegExp) => e.match(rx))) {
702
- logger.debug(`ready |${e}`);
703
- if (cli === "gemini" && i <= 80) return; // gemini initial noise, only after many lines
704
- stdinReady.ready();
705
- stdinFirstReady.ready();
706
- }
707
- // enter matchers: send Enter when any enter regex matches
708
-
709
- if (conf.enter?.some((rx: RegExp) => e.match(rx))) {
710
- logger.debug(`enter |${e}`);
711
- return await sendEnter(400); // wait for idle for a short while and then send Enter
712
- }
713
-
714
- // typingRespond matcher: if matched, send the specified message
715
- const typingResponded = await sflow(Object.entries(conf.typingRespond ?? {}))
716
- .filter(([_sendString, onThePatterns]) => onThePatterns.some((rx) => e.match(rx)))
717
- .map(async ([sendString]) => await sendMessage(sendString, { waitForReady: false }))
718
- .toCount();
719
- if (typingResponded) return;
720
-
721
- // fatal matchers: set isFatal flag when matched
722
- if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {
723
- logger.debug(`fatal |${e}`);
724
- isFatal = true;
725
- await exitAgent();
726
- }
727
-
728
- // restartWithoutContinueArg matchers: set flag to restart without continue args
729
- if (conf.restartWithoutContinueArg?.some((rx: RegExp) => e.match(rx))) {
730
- await logger.debug(`restart-without-continue|${e}`);
731
- shouldRestartWithoutContinue = true;
732
- isFatal = true; // also set fatal to trigger exit
733
- await exitAgent();
734
- }
735
-
736
- // session ID capture for codex
737
- if (cli === "codex") {
738
- const sessionId = extractSessionId(e);
739
- if (sessionId) {
740
- await logger.debug(`session|captured session ID: ${sessionId}`);
741
- await storeSessionForCwd(workingDir, sessionId);
742
- }
743
- }
744
- })
573
+ .forEach(async (line, lineIndex) =>
574
+ createAutoResponseHandler(line, lineIndex, { ctx, conf, cli, workingDir, exitAgent }),
575
+ )
745
576
  .run()
746
577
  );
747
578
  })
748
579
  .by((s) => (removeControlCharactersFromStdout ? s.map((e) => removeControlCharacters(e)) : s))
749
580
 
750
581
  // terminate whole stream when shell did exited (already crash-handled)
751
- .by(
752
- new TransformStream({
753
- start: function terminator(ctrl) {
754
- pendingExitCode.promise.then(() => ctrl.terminate());
755
- },
756
- transform: (e, ctrl) => ctrl.enqueue(e),
757
- flush: (ctrl) => ctrl.terminate(),
758
- }),
759
- )
582
+ .by(createTerminatorStream(pendingExitCode.promise))
760
583
  .to(fromWritable(process.stdout));
761
584
 
762
- if (logPath) {
763
- await mkdir(path.dirname(logPath), { recursive: true }).catch(() => null);
764
- await writeFile(logPath, terminalRender.render()).catch(() => null);
765
- logger.info(`[${cli}-yes] Full logs saved to ${logPath}`);
766
- }
585
+ await saveLogFile(ctx.logPaths.logPath, terminalRender.render());
767
586
 
768
587
  // and then get its exitcode
769
588
  const exitCode = await pendingExitCode.promise;
@@ -776,72 +595,15 @@ export default async function agentYes({
776
595
  await outputWriter.close();
777
596
 
778
597
  // deprecated logFile option, we have logPath now, but keep for backward compatibility
779
- if (logFile) {
780
- if (verbose) logger.info(`[${cli}-yes] Writing rendered logs to ${logFile}`);
781
- const logFilePath = path.resolve(logFile);
782
- await mkdir(path.dirname(logFilePath), { recursive: true }).catch(() => null);
783
- await writeFile(logFilePath, terminalRender.render());
784
- }
598
+ await saveDeprecatedLogFile(logFile, terminalRender.render(), verbose);
785
599
 
786
600
  return { exitCode, logs: terminalRender.render() };
787
601
 
788
- async function sendEnter(waitms = 1000) {
789
- // wait for idle for a bit to let agent cli finish rendering
790
- const st = Date.now();
791
- await idleWaiter.wait(waitms); // wait for idle a while
792
- const et = Date.now();
793
- // process.stdout.write(`\ridleWaiter.wait(${waitms}) took ${et - st}ms\r`);
794
- logger.debug(`sendEn| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`);
795
- nextStdout.unready();
796
- // send the enter key
797
- shell.write("\r");
798
-
799
- // retry once if not received any output in 1 second after sending Enter
800
- await Promise.race([
801
- nextStdout.wait(),
802
- new Promise<void>((resolve) =>
803
- setTimeout(() => {
804
- if (!nextStdout.ready) {
805
- shell.write("\r");
806
- }
807
- resolve();
808
- }, 1000),
809
- ),
810
- ]);
811
-
812
- // retry the second time if not received any output in 3 second after sending Enter
813
- await Promise.race([
814
- nextStdout.wait(),
815
- new Promise<void>((resolve) =>
816
- setTimeout(() => {
817
- if (!nextStdout.ready) {
818
- shell.write("\r");
819
- }
820
- resolve();
821
- }, 3000),
822
- ),
823
- ]);
824
- }
825
-
826
- async function sendMessage(message: string, { waitForReady = true } = {}) {
827
- if (waitForReady) await stdinReady.wait();
828
- // show in-place message: write msg and move cursor back start
829
- logger.debug(`send |${message}`);
830
- nextStdout.unready();
831
- shell.write(message);
832
- idleWaiter.ping(); // just sent a message, wait for echo
833
- logger.debug(`waiting next stdout|${message}`);
834
- await nextStdout.wait();
835
- logger.debug(`sending enter`);
836
- await sendEnter(1000);
837
- logger.debug(`sent enter`);
838
- }
839
-
840
602
  async function exitAgent() {
841
- robust = false; // disable robust to avoid auto restart
603
+ ctx.robust = false; // disable robust to avoid auto restart
842
604
 
843
605
  // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input
844
- for (const cmd of cliConf.exitCommands ?? ["/exit"]) await sendMessage(cmd);
606
+ for (const cmd of cliConf.exitCommands ?? ["/exit"]) await sendMessage(ctx.messageContext, cmd);
845
607
 
846
608
  // wait for shell to exit or kill it with a timeout
847
609
  let exited = false;
@@ -858,16 +620,6 @@ export default async function agentYes({
858
620
  ), // 5 seconds timeout
859
621
  ]);
860
622
  }
861
-
862
- function getTerminalDimensions() {
863
- if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty
864
- return {
865
- // TODO: enforce minimum cols/rows to avoid layout issues
866
- // cols: Math.max(process.stdout.columns, 80),
867
- cols: Math.min(Math.max(20, process.stdout.columns), 80),
868
- rows: process.stdout.rows,
869
- };
870
- }
871
623
  }
872
624
 
873
625
  function sleep(ms: number) {
@@ -196,6 +196,6 @@ export function parseCliArgs(argv: string[]) {
196
196
  resume: parsedArgv.continue, // Note: intentional use resume here to avoid preserved keyword (continue)
197
197
  useSkills: parsedArgv.useSkills,
198
198
  appendPrompt: parsedArgv.appendPrompt,
199
- useFifo: parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo, // Support --stdpush, --ipc, and --fifo (backward compatibility)
199
+ useFifo: Boolean(parsedArgv.stdpush || parsedArgv.ipc || parsedArgv.fifo), // Support --stdpush, --ipc, and --fifo (backward compatibility)
200
200
  };
201
201
  }
package/ts/pidStore.ts CHANGED
@@ -247,6 +247,10 @@ fifo/
247
247
  *.raw.log
248
248
  *.lines.log
249
249
  *.debug.log
250
+
251
+ # Ignore .gitignore itself
252
+ .gitignore
253
+
250
254
  `;
251
255
 
252
256
  try {