claude-yes 1.21.1 → 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 { 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/,
@@ -43,7 +45,10 @@ export const CLI_CONFIGURES: Record<
43
45
  codex: {
44
46
  install: 'npm install -g @openai/codex-cli',
45
47
  ready: [/⏎ send/],
46
- enter: [/ > 1. Approve/, /> 1. Yes, allow Codex to work in this folder/],
48
+ enter: [
49
+ /> 1. Yes, allow Codex to work in this folder/,
50
+ /▌ > 1. Approve and run now/,
51
+ ],
47
52
  fatal: [/Error: The cursor position could not be read within/],
48
53
  // add to codex --search by default when not provided by the user
49
54
  ensureArgs: (args: string[]) => {
@@ -67,15 +72,15 @@ export const CLI_CONFIGURES: Record<
67
72
  },
68
73
  };
69
74
  /**
70
- * Main function to run Claude with automatic yes/no responses
75
+ * Main function to run agent-cli with automatic yes/no responses
71
76
  * @param options Configuration options
72
- * @param options.continueOnCrash - If true, automatically restart Claude when it crashes:
73
- * 1. Shows message 'Claude crashed, restarting..'
74
- * 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
75
80
  * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)
76
81
  * 4. If it crashes with "No conversation found to continue", exits the process
77
- * @param options.exitOnIdle - Exit when Claude is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false
78
- * @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
79
84
  * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY
80
85
  *
81
86
  * @example
@@ -116,31 +121,16 @@ export default async function claudeYes({
116
121
  removeControlCharactersFromStdout?: boolean;
117
122
  verbose?: boolean;
118
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
+ });
119
128
  const continueArgs = {
120
129
  codex: 'resume --last'.split(' '),
121
130
  claude: '--continue'.split(' '),
122
131
  gemini: [], // not possible yet
123
132
  };
124
133
 
125
- // if (verbose) {
126
- // console.log('calling claudeYes: ', {
127
- // cli,
128
- // continueOnCrash,
129
- // exitOnIdle,
130
- // cliArgs,
131
- // cwd,
132
- // removeControlCharactersFromStdout,
133
- // logFile,
134
- // verbose,
135
- // });
136
- // }
137
- // console.log(
138
- // `⭐ Starting ${cli}, automatically responding to yes/no prompts...`
139
- // );
140
- // console.log(
141
- // '⚠️ 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.'
142
- // );
143
-
144
134
  process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
145
135
  let isFatal = false; // when true, do not restart on crash, and exit agent
146
136
  const stdinReady = new ReadyManager();
@@ -243,7 +233,11 @@ export default async function claudeYes({
243
233
  await exitAgent();
244
234
  });
245
235
 
236
+ // console.log(
237
+ // `[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(" ")}`
238
+ // );
246
239
  // Message streaming
240
+
247
241
  sflow(fromReadable<Buffer>(process.stdin))
248
242
  .map((buffer) => buffer.toString())
249
243
  // .map((e) => e.replaceAll('\x1a', '')) // remove ctrl+z from user's input (seems bug)
@@ -264,16 +258,19 @@ export default async function claudeYes({
264
258
  terminalRender.write(text);
265
259
  // todo: .onStatus((msg)=> shell.write(msg))
266
260
  if (process.stdin.isTTY) return; // only handle it when stdin is not tty
267
- if (text.includes('\u001b[6n')) return; // only asked
268
-
261
+ if (!text.includes('\u001b[6n')) return; // only asked for cursor position
269
262
  // todo: use terminalRender API to get cursor position when new version is available
270
263
  // xterm replies CSI row; column R if asked cursor position
271
264
  // https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B
272
265
  // when agent asking position, respond with row; col
273
- const rendered = terminalRender.render();
274
- const row = rendered.split('\n').length + 1;
275
- const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
276
- shell.write(`\u001b[${row};${col}R`);
266
+ // const rendered = terminalRender.render();
267
+ const { col, row } = terminalRender.getCursorPosition();
268
+ console.log(
269
+ `[${cli}-yes] Responding cursor position: row=${row}, col=${col}`,
270
+ );
271
+ shell.write(`\u001b[${row};${col}R`); // reply cli when getting cursor position
272
+ // const row = rendered.split('\n').length + 1;
273
+ // const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
277
274
  })
278
275
 
279
276
  // auto-response
@@ -282,6 +279,7 @@ export default async function claudeYes({
282
279
  .map((e) => removeControlCharacters(e))
283
280
  .map((e) => e.replaceAll('\r', '')) // remove carriage return
284
281
  .lines({ EOL: 'NONE' })
282
+ .forEach((e) => yesLog`output|${e}`) // for debugging
285
283
  // Generic auto-response handler driven by CLI_CONFIGURES
286
284
  .forEach(async (e, i) => {
287
285
  const conf =
@@ -290,21 +288,25 @@ export default async function claudeYes({
290
288
 
291
289
  // ready matcher: if matched, mark stdin ready
292
290
  if (conf.ready?.some((rx: RegExp) => e.match(rx))) {
291
+ await yesLog`ready |${e}`;
293
292
  if (cli === 'gemini' && i <= 80) return; // gemini initial noise, only after many lines
294
293
  stdinReady.ready();
295
294
  }
296
295
 
297
296
  // enter matchers: send Enter when any enter regex matches
298
- if (conf.enter?.some((rx: RegExp) => e.match(rx)))
297
+ if (conf.enter?.some((rx: RegExp) => e.match(rx))) {
298
+ await yesLog`enter |${e}`;
299
299
  await sendEnter(300); // send Enter after 300ms idle wait
300
+ return;
301
+ }
300
302
 
301
303
  // fatal matchers: set isFatal flag when matched
302
304
  if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {
305
+ await yesLog`fatal |${e}`;
303
306
  isFatal = true;
304
307
  await exitAgent();
305
308
  }
306
309
  })
307
- // .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
308
310
  .run(),
309
311
  )
310
312
  .map((e) =>
@@ -314,6 +316,7 @@ export default async function claudeYes({
314
316
  .then(() => null); // run it immediately without await
315
317
 
316
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
317
320
  if (prompt) await sendMessage(prompt);
318
321
 
319
322
  const exitCode = await pendingExitCode.promise; // wait for the shell to exit
@@ -333,10 +336,10 @@ export default async function claudeYes({
333
336
  async function sendEnter(waitms = 1000) {
334
337
  // wait for idle for a bit to let agent cli finish rendering
335
338
  const st = Date.now();
336
-
337
339
  await idleWaiter.wait(waitms);
338
340
  const et = Date.now();
339
- 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`;
340
343
 
341
344
  shell.write('\r');
342
345
  }
@@ -344,6 +347,7 @@ export default async function claudeYes({
344
347
  async function sendMessage(message: string) {
345
348
  await stdinReady.wait();
346
349
  // show in-place message: write msg and move cursor back start
350
+ await yesLog`send |${message}`;
347
351
  shell.write(message);
348
352
  idleWaiter.ping(); // just sent a message, wait for echo
349
353
  await sendEnter();
@@ -371,10 +375,11 @@ export default async function claudeYes({
371
375
  }
372
376
 
373
377
  function getTerminalDimensions() {
378
+ if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty
374
379
  return {
375
380
  // TODO: enforce minimum cols/rows to avoid layout issues
376
381
  // cols: Math.max(process.stdout.columns, 80),
377
- cols: process.stdout.columns,
382
+ cols: Math.min(Math.max(20, process.stdout.columns), 80),
378
383
  rows: process.stdout.rows,
379
384
  };
380
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.21.1",
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",