claude-yes 1.22.0 → 1.23.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/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { fromReadable, fromWritable } from 'from-node-stream';
2
- import { appendFile, mkdir, writeFile } from 'fs/promises';
2
+ import { appendFile, mkdir, rm, writeFile } from 'fs/promises';
3
3
  import path from 'path';
4
4
  import DIE from 'phpdie';
5
5
  import sflow from 'sflow';
6
6
  import { TerminalTextRender } from 'terminal-render';
7
+ import tsaComposer from 'tsa-composer';
7
8
  import { IdleWaiter } from './idleWaiter';
8
9
  import { ReadyManager } from './ReadyManager';
9
10
  import { removeControlCharacters } from './removeControlCharacters';
@@ -26,7 +27,8 @@ export const CLI_CONFIGURES: Record<
26
27
  },
27
28
  claude: {
28
29
  install: 'npm install -g @anthropic-ai/claude-code',
29
- ready: [/^> /], // regex matcher for stdin ready,
30
+ // ready: [/^> /], // regex matcher for stdin ready
31
+ ready: [/\? for shortcuts/], // regex matcher for stdin ready
30
32
  enter: [/❯ 1. Yes/, /❯ 1. Dark mode✔/, /Press Enter to continue…/],
31
33
  fatal: [
32
34
  /No conversation found to continue/,
@@ -44,8 +46,8 @@ export const CLI_CONFIGURES: Record<
44
46
  install: 'npm install -g @openai/codex-cli',
45
47
  ready: [/⏎ send/],
46
48
  enter: [
47
- /\b▌ \> 1\. Approve and run now/,
48
- /\b\> 1\. Yes, allow Codex to work in this folder/,
49
+ /> 1. Yes, allow Codex to work in this folder/,
50
+ /▌ > 1. Approve and run now/,
49
51
  ],
50
52
  fatal: [/Error: The cursor position could not be read within/],
51
53
  // add to codex --search by default when not provided by the user
@@ -70,15 +72,15 @@ export const CLI_CONFIGURES: Record<
70
72
  },
71
73
  };
72
74
  /**
73
- * Main function to run Claude with automatic yes/no responses
75
+ * Main function to run agent-cli with automatic yes/no responses
74
76
  * @param options Configuration options
75
- * @param options.continueOnCrash - If true, automatically restart Claude when it crashes:
76
- * 1. Shows message 'Claude crashed, restarting..'
77
- * 2. Spawns a new 'claude --continue' process
77
+ * @param options.continueOnCrash - If true, automatically restart agent-cli when it crashes:
78
+ * 1. Shows message 'agent-cli crashed, restarting..'
79
+ * 2. Spawns a new 'agent-cli --continue' process
78
80
  * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)
79
81
  * 4. If it crashes with "No conversation found to continue", exits the process
80
- * @param options.exitOnIdle - Exit when Claude is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false
81
- * @param options.claudeArgs - Additional arguments to pass to the Claude CLI
82
+ * @param options.exitOnIdle - Exit when agent-cli is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false
83
+ * @param options.cliArgs - Additional arguments to pass to the agent-cli CLI
82
84
  * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY
83
85
  *
84
86
  * @example
@@ -119,31 +121,16 @@ export default async function claudeYes({
119
121
  removeControlCharactersFromStdout?: boolean;
120
122
  verbose?: boolean;
121
123
  } = {}) {
124
+ await rm('agent-yes.log').catch(() => null); // ignore error if file doesn't exist
125
+ const yesLog = tsaComposer()(async function yesLog(msg: string) {
126
+ await appendFile('agent-yes.log', `${msg}\n`).catch(() => null);
127
+ });
122
128
  const continueArgs = {
123
129
  codex: 'resume --last'.split(' '),
124
130
  claude: '--continue'.split(' '),
125
131
  gemini: [], // not possible yet
126
132
  };
127
133
 
128
- // if (verbose) {
129
- // console.log('calling claudeYes: ', {
130
- // cli,
131
- // continueOnCrash,
132
- // exitOnIdle,
133
- // cliArgs,
134
- // cwd,
135
- // removeControlCharactersFromStdout,
136
- // logFile,
137
- // verbose,
138
- // });
139
- // }
140
- // console.log(
141
- // `⭐ Starting ${cli}, automatically responding to yes/no prompts...`
142
- // );
143
- // console.log(
144
- // '⚠️ Important Security Warning: Only run this on trusted repositories. This tool automatically responds to prompts and can execute commands without user confirmation. Be aware of potential prompt injection attacks where malicious code or instructions could be embedded in files or user inputs to manipulate the automated responses.'
145
- // );
146
-
147
134
  process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
148
135
  let isFatal = false; // when true, do not restart on crash, and exit agent
149
136
  const stdinReady = new ReadyManager();
@@ -246,11 +233,10 @@ export default async function claudeYes({
246
233
  await exitAgent();
247
234
  });
248
235
 
249
- console.log(
250
- `[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(' ')}`,
251
- );
236
+ // console.log(
237
+ // `[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(" ")}`
238
+ // );
252
239
  // Message streaming
253
- shell.write(`\u001b[${1};${1}R`); // reply cli when getting cursor position
254
240
 
255
241
  sflow(fromReadable<Buffer>(process.stdin))
256
242
  .map((buffer) => buffer.toString())
@@ -272,7 +258,7 @@ export default async function claudeYes({
272
258
  terminalRender.write(text);
273
259
  // todo: .onStatus((msg)=> shell.write(msg))
274
260
  if (process.stdin.isTTY) return; // only handle it when stdin is not tty
275
- if (text.includes('\u001b[6n')) return; // only asked for cursor position
261
+ if (!text.includes('\u001b[6n')) return; // only asked for cursor position
276
262
  // todo: use terminalRender API to get cursor position when new version is available
277
263
  // xterm replies CSI row; column R if asked cursor position
278
264
  // https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B
@@ -292,10 +278,8 @@ export default async function claudeYes({
292
278
  e
293
279
  .map((e) => removeControlCharacters(e))
294
280
  .map((e) => e.replaceAll('\r', '')) // remove carriage return
295
- // .lines({ EOL: 'NONE' })
296
- .forEach((e) =>
297
- appendFile('io.log', 'output|' + JSON.stringify(e) + '\n'),
298
- ) // for debugging
281
+ .lines({ EOL: 'NONE' })
282
+ .forEach((e) => yesLog`output|${e}`) // for debugging
299
283
  // Generic auto-response handler driven by CLI_CONFIGURES
300
284
  .forEach(async (e, i) => {
301
285
  const conf =
@@ -304,17 +288,21 @@ export default async function claudeYes({
304
288
 
305
289
  // ready matcher: if matched, mark stdin ready
306
290
  if (conf.ready?.some((rx: RegExp) => e.match(rx))) {
291
+ await yesLog`ready |${e}`;
307
292
  if (cli === 'gemini' && i <= 80) return; // gemini initial noise, only after many lines
308
293
  stdinReady.ready();
309
294
  }
310
295
 
311
296
  // enter matchers: send Enter when any enter regex matches
312
297
  if (conf.enter?.some((rx: RegExp) => e.match(rx))) {
298
+ await yesLog`enter |${e}`;
313
299
  await sendEnter(300); // send Enter after 300ms idle wait
300
+ return;
314
301
  }
315
302
 
316
303
  // fatal matchers: set isFatal flag when matched
317
304
  if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {
305
+ await yesLog`fatal |${e}`;
318
306
  isFatal = true;
319
307
  await exitAgent();
320
308
  }
@@ -328,6 +316,7 @@ export default async function claudeYes({
328
316
  .then(() => null); // run it immediately without await
329
317
 
330
318
  // wait for cli ready and send prompt if provided
319
+ if (cli === 'codex') shell.write(`\u001b[1;1R`); // send cursor position response when stdin is not tty
331
320
  if (prompt) await sendMessage(prompt);
332
321
 
333
322
  const exitCode = await pendingExitCode.promise; // wait for the shell to exit
@@ -347,10 +336,10 @@ export default async function claudeYes({
347
336
  async function sendEnter(waitms = 1000) {
348
337
  // wait for idle for a bit to let agent cli finish rendering
349
338
  const st = Date.now();
350
-
351
339
  await idleWaiter.wait(waitms);
352
340
  const et = Date.now();
353
- process.stdout.write(`\ridleWaiter.wait(${waitms}) took ${et - st}ms\r`);
341
+ // process.stdout.write(`\ridleWaiter.wait(${waitms}) took ${et - st}ms\r`);
342
+ await yesLog`sendEn| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`;
354
343
 
355
344
  shell.write('\r');
356
345
  }
@@ -358,6 +347,7 @@ export default async function claudeYes({
358
347
  async function sendMessage(message: string) {
359
348
  await stdinReady.wait();
360
349
  // show in-place message: write msg and move cursor back start
350
+ await yesLog`send |${message}`;
361
351
  shell.write(message);
362
352
  idleWaiter.ping(); // just sent a message, wait for echo
363
353
  await sendEnter();
@@ -385,10 +375,11 @@ export default async function claudeYes({
385
375
  }
386
376
 
387
377
  function getTerminalDimensions() {
378
+ if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty
388
379
  return {
389
380
  // TODO: enforce minimum cols/rows to avoid layout issues
390
381
  // cols: Math.max(process.stdout.columns, 80),
391
- cols: process.stdout.columns,
382
+ cols: Math.min(Math.max(20, process.stdout.columns), 80),
392
383
  rows: process.stdout.rows,
393
384
  };
394
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "claude",
@@ -62,7 +62,8 @@
62
62
  "dependencies": {
63
63
  "bun-pty": "^0.3.2",
64
64
  "p-map": "^7.0.3",
65
- "phpdie": "^1.7.0"
65
+ "phpdie": "^1.7.0",
66
+ "tsa-composer": "^3.0.0"
66
67
  },
67
68
  "devDependencies": {
68
69
  "@biomejs/biome": "^2.2.5",