claude-yes 1.15.0 → 1.15.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.
@@ -0,0 +1,17 @@
1
+ import { exec } from 'child_process';
2
+ import { fromStdio } from 'from-node-stream';
3
+ import sflow from 'sflow';
4
+ import { sleepms } from './utils';
5
+
6
+ // 2025-08-11 ok
7
+ it.skip('CLI --exit-on-idle flag with custom timeout', async () => {
8
+ const p = exec(
9
+ `bunx tsx ./cli.ts --verbose --logFile=./cli-idle.log --exit-on-idle=3s "say hello and wait"`
10
+ );
11
+ const tr = new TransformStream<string, string>();
12
+ const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
13
+ console.log(output);
14
+ expect(output).toContain('hello');
15
+ await sleepms(1000); // wait for process exit
16
+ expect(p.exitCode).toBe(0);
17
+ }, 20e3);
package/cli.test.ts CHANGED
@@ -1,79 +1,62 @@
1
- import { execaCommand } from "execa";
2
- import { fromStdio } from "from-node-stream";
3
- import { exec } from "node:child_process";
4
- import { existsSync } from "node:fs";
5
- import { readFile, unlink } from "node:fs/promises";
6
- import sflow from "sflow";
7
- import { beforeAll, describe, expect, it } from "vitest";
8
- import { createIdleWatcher } from "./createIdleWatcher";
9
- import { sleepms } from "./utils";
10
-
11
- beforeAll(async () => {
12
- await execaCommand(`bun run build`).then(() =>
13
- console.log("Build successful"),
14
- );
15
- });
16
-
17
- describe("CLI Tests", () => {
18
- it("Write file with auto bypass permission prompt", async () => {
19
- const flagFile = "./.cache/flag.json";
20
- // clean
1
+ import { execaCommand } from 'execa';
2
+ import { fromStdio } from 'from-node-stream';
3
+ import { exec } from 'node:child_process';
4
+ import { existsSync } from 'node:fs';
5
+ import { readFile, unlink } from 'node:fs/promises';
6
+ import sflow from 'sflow';
7
+ import { beforeAll, describe, expect, it } from 'vitest';
8
+ import { createIdleWatcher } from './createIdleWatcher';
9
+ import { sleepms } from './utils';
10
+
11
+ it('Write file with auto bypass prompts', async () => {
12
+ const flagFile = './.cache/flag.json';
13
+ await cleanup();
14
+ async function cleanup() {
21
15
  await unlink(flagFile).catch(() => {});
16
+ await unlink('./cli-rendered.log').catch(() => {});
17
+ }
22
18
 
23
- const p = exec(
24
- `node dist/cli.js --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json"`,
25
- );
26
- const tr = new TransformStream<string, string>();
27
- const w = tr.writable.getWriter();
19
+ const p = exec(
20
+ `bunx tsx ./cli.ts --logFile=./cli-rendered.log --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json and wait"`
21
+ );
22
+ const pExitCode = new Promise<number | null>((r) => p.once('exit', r));
28
23
 
29
- const exit = async () =>
30
- await sflow(["\r", "/exit", "\r", "\r"])
31
- .forEach(async (e) => {
32
- await sleepms(200);
33
- await w.write(e);
34
- })
35
- .run();
24
+ const tr = new TransformStream<string, string>();
25
+ const w = tr.writable.getWriter();
36
26
 
37
- // ping function to exit claude when idle
27
+ const exit = async () =>
28
+ await sflow(['\r', '/exit', '\r', '\r'])
29
+ .forEach(async (e) => {
30
+ await sleepms(200);
31
+ await w.write(e);
32
+ })
33
+ .run();
38
34
 
39
- const { ping } = createIdleWatcher(() => exit(), 3000);
35
+ // ping function to exit claude when idle
40
36
 
41
- const output = await sflow(tr.readable)
42
- .by(fromStdio(p))
43
- .log()
44
- .forEach(() => ping())
45
- .text();
37
+ const { ping } = createIdleWatcher(() => exit(), 3000);
46
38
 
47
- // expect the file exists
48
- expect(existsSync(flagFile)).toBe(true);
49
- // expect the output contains the file path
50
- expect(output).toContain(flagFile);
39
+ const output = await sflow(tr.readable)
40
+ .by(fromStdio(p))
41
+ .log()
42
+ .forEach(() => ping())
43
+ .text();
51
44
 
52
- // expect the file content to be 'on'
53
- expect(await new Response(await readFile(flagFile)).json()).toEqual({
54
- on: 1,
55
- });
45
+ // expect the file exists
46
+ expect(existsSync(flagFile)).toBe(true);
47
+ // expect the output contains the file path
48
+ expect(output).toContain(flagFile);
56
49
 
57
- expect(p.exitCode).toBe(0); // expect the process to exit successfully
50
+ // expect the file content to be 'on'
51
+ expect(await new Response(await readFile(flagFile)).json()).toEqual({
52
+ on: 1,
53
+ });
58
54
 
59
- // 30 seconds timeout for this test, it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
60
- }, 30e3);
55
+ expect(await pExitCode).toBe(0); // expect the process to exit successfully
56
+ expect(await readFile('./cli-rendered.log', 'utf8')).toBeTruthy();
61
57
 
62
- it.skip("CLI --exit-on-idle flag with default timeout", async () => {
63
- const p = exec(`node dist/cli.js "echo hello" --exit-on-idle`);
64
- const tr = new TransformStream<string, string>();
65
- const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
66
- expect(output).toContain("hello");
67
- await sleepms(1000); // wait for process exit
68
- expect(p.exitCode).toBe(0);
69
- }, 30e3);
58
+ // clean
59
+ await cleanup();
70
60
 
71
- it("CLI --exit-on-idle flag with custom timeout", async () => {
72
- const p = exec(`node dist/cli.js --exit-on-idle=1s "echo hello"`);
73
- const tr = new TransformStream<string, string>();
74
- const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
75
- expect(output).toContain("hello");
76
- await sleepms(1000); // wait for process exit
77
- expect(p.exitCode).toBe(0);
78
- }, 30e3);
79
- });
61
+ // it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
62
+ }, 30e3);
package/cli.ts CHANGED
@@ -6,7 +6,11 @@ import claudeYes from '.';
6
6
 
7
7
  // cli entry point
8
8
  const argv = yargs(hideBin(process.argv))
9
- .usage('Usage: $0 [options] [claude args]')
9
+ .usage('Usage: $0 [options] [--] [claude args]')
10
+ .example(
11
+ '$0 --exit-on-idle=30s --continue-on-crash "help me solve all todos in my codebase"',
12
+ 'Run Claude with a 30 seconds idle timeout and continue on crash'
13
+ )
10
14
  .option('exit-on-idle', {
11
15
  type: 'string',
12
16
  default: '60s',
@@ -32,26 +36,12 @@ const argv = yargs(hideBin(process.argv))
32
36
  })
33
37
  .parseSync();
34
38
 
35
- const {
36
- exitOnIdle: exitOnIdleArg,
37
- continueOnCrash: continueOnCrashArg,
38
- logFile,
39
- ...rest
40
- } = argv;
41
-
42
- const claudeArgs = argv._.map((e) => String(e));
43
- const exitOnIdle = argv.exitOnIdle != null ? ms(argv.exitOnIdle) : undefined;
44
-
45
- argv.verbose &&
46
- console.debug('[claude-yes] Parsed args:', {
47
- exitOnIdle,
48
- continueOnCrash: continueOnCrashArg,
49
- claudeArgs,
50
- });
51
-
52
- await claudeYes({
53
- exitOnIdle,
54
- claudeArgs,
55
- continueOnCrash: continueOnCrashArg,
56
- logFile,
39
+ const { exitCode, logs } = await claudeYes({
40
+ exitOnIdle: argv.exitOnIdle != null ? ms(argv.exitOnIdle) : undefined,
41
+ claudeArgs: argv._.map((e) => String(e)),
42
+ continueOnCrash: argv.continueOnCrash,
43
+ logFile: argv.logFile,
44
+ verbose: argv.verbose,
57
45
  });
46
+
47
+ process.exit(exitCode ?? 1);
@@ -1,18 +1,20 @@
1
+ export function createIdleWatcher(
2
+ onIdle: () => void,
3
+ idleTimeout: number
4
+ ): { ping: () => void; getLastActiveTime: () => Date } {
5
+ let lastActiveTime = new Date();
6
+ let idleTimeoutId: NodeJS.Timeout | null = null;
1
7
 
2
- export function createIdleWatcher(onIdle: () => void, idleTimeout: number): { ping: () => void; getLastActiveTime: () => Date; } {
3
- let lastActiveTime = new Date();
4
- let idleTimeoutId: NodeJS.Timeout | null = null;
5
- return {
6
- ping: () => {
7
- if (idleTimeoutId) {
8
- clearTimeout(idleTimeoutId);
9
- }
10
- idleTimeoutId = setTimeout(() => {
11
- clearTimeout(idleTimeoutId!);
12
- onIdle();
13
- }, idleTimeout);
14
- lastActiveTime = new Date();
15
- },
16
- getLastActiveTime: () => lastActiveTime
17
- };
8
+ return {
9
+ ping: () => {
10
+ if (idleTimeoutId) clearTimeout(idleTimeoutId);
11
+
12
+ lastActiveTime = new Date();
13
+ idleTimeoutId = setTimeout(() => {
14
+ clearTimeout(idleTimeoutId!);
15
+ onIdle();
16
+ }, idleTimeout);
17
+ },
18
+ getLastActiveTime: () => lastActiveTime,
19
+ };
18
20
  }
package/dist/cli.js CHANGED
@@ -4861,14 +4861,13 @@ function createIdleWatcher(onIdle, idleTimeout) {
4861
4861
  let idleTimeoutId = null;
4862
4862
  return {
4863
4863
  ping: () => {
4864
- if (idleTimeoutId) {
4864
+ if (idleTimeoutId)
4865
4865
  clearTimeout(idleTimeoutId);
4866
- }
4866
+ lastActiveTime = new Date;
4867
4867
  idleTimeoutId = setTimeout(() => {
4868
4868
  clearTimeout(idleTimeoutId);
4869
4869
  onIdle();
4870
4870
  }, idleTimeout);
4871
- lastActiveTime = new Date;
4872
4871
  },
4873
4872
  getLastActiveTime: () => lastActiveTime
4874
4873
  };
@@ -5118,16 +5117,28 @@ class TerminalTextRender {
5118
5117
 
5119
5118
  // index.ts
5120
5119
  import { writeFile } from "fs/promises";
5120
+ import path2 from "path";
5121
+ import { mkdir } from "fs/promises";
5121
5122
  async function claudeYes({
5122
5123
  continueOnCrash,
5123
5124
  exitOnIdle,
5124
5125
  claudeArgs = [],
5125
5126
  cwd = process.cwd(),
5126
5127
  removeControlCharactersFromStdout = false,
5127
- logFile
5128
+ logFile,
5129
+ verbose = false
5128
5130
  } = {}) {
5129
- const defaultIdleTimeout = 60000;
5130
- const idleTimeout = typeof exitOnIdle === "number" ? exitOnIdle : defaultIdleTimeout;
5131
+ if (verbose) {
5132
+ console.log("calling claudeYes: ", {
5133
+ continueOnCrash,
5134
+ exitOnIdle,
5135
+ claudeArgs,
5136
+ cwd,
5137
+ removeControlCharactersFromStdout,
5138
+ logFile,
5139
+ verbose
5140
+ });
5141
+ }
5131
5142
  console.log("⭐ Starting claude, automatically responding to yes/no prompts...");
5132
5143
  console.log("⚠️ 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.");
5133
5144
  process.stdin.setRawMode?.(true);
@@ -5144,15 +5155,16 @@ async function claudeYes({
5144
5155
  cwd,
5145
5156
  env: process.env
5146
5157
  });
5158
+ let pendingExitCode = Promise.withResolvers();
5147
5159
  async function onData(data) {
5148
5160
  await outputWriter.write(data);
5149
5161
  }
5150
5162
  shell.onData(onData);
5151
- shell.onExit(function onExit({ exitCode }) {
5152
- if (continueOnCrash && exitCode !== 0) {
5163
+ shell.onExit(function onExit({ exitCode: exitCode2 }) {
5164
+ if (continueOnCrash && exitCode2 !== 0) {
5153
5165
  if (errorNoConversation) {
5154
5166
  console.log('Claude crashed with "No conversation found to continue", exiting...');
5155
- process.exit(exitCode);
5167
+ return pendingExitCode.resolve(exitCode2);
5156
5168
  }
5157
5169
  console.log("Claude crashed, restarting...");
5158
5170
  shell = pty.spawn("claude", ["continue", "--continue"], {
@@ -5166,7 +5178,7 @@ async function claudeYes({
5166
5178
  shell.onExit(onExit);
5167
5179
  return;
5168
5180
  }
5169
- process.exit(exitCode);
5181
+ return pendingExitCode.resolve(exitCode2);
5170
5182
  });
5171
5183
  const exitClaudeCode = async () => {
5172
5184
  await src_default(["\r", "/exit", "\r"]).forEach(async (e) => {
@@ -5199,21 +5211,19 @@ async function claudeYes({
5199
5211
  readable: shellOutputStream.readable
5200
5212
  };
5201
5213
  const ttr = new TerminalTextRender;
5202
- const idleWatcher = createIdleWatcher(async () => {
5203
- if (exitOnIdle) {
5204
- if (ttr.render().replace(/\s+/g, " ").match(/esc to interrupt|to run in background/)) {
5205
- console.warn("[CLAUDE-YES] Claude is idle, but seems still working, not exiting yet");
5206
- } else {
5207
- console.warn("[CLAUDE-YES] Claude is idle, exiting...");
5208
- await exitClaudeCode();
5209
- }
5214
+ const idleWatcher = !exitOnIdle ? null : createIdleWatcher(async () => {
5215
+ if (ttr.render().replace(/\s+/g, " ").match(/esc to interrupt|to run in background/)) {
5216
+ console.log("[claude-yes] Claude is idle, but seems still working, not exiting yet");
5217
+ } else {
5218
+ console.log("[claude-yes] Claude is idle, exiting...");
5219
+ await exitClaudeCode();
5210
5220
  }
5211
- }, idleTimeout);
5221
+ }, exitOnIdle);
5212
5222
  const confirm = async () => {
5213
5223
  await sleepms(200);
5214
5224
  shell.write("\r");
5215
5225
  };
5216
- await src_default(fromReadable(process.stdin)).forEach(() => idleWatcher.ping()).map((buffer2) => buffer2.toString()).forEach((text) => ttr.write(text)).by(shellStdio).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
5226
+ src_default(fromReadable(process.stdin)).map((buffer2) => buffer2.toString()).by(shellStdio).forEach((text) => ttr.write(text)).forEach(() => idleWatcher?.ping()).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
5217
5227
  if (e2.match(/❯ 1. Yes/))
5218
5228
  return await confirm();
5219
5229
  if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
@@ -5223,8 +5233,15 @@ async function claudeYes({
5223
5233
  return;
5224
5234
  }
5225
5235
  }).run()).replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line).map((e) => removeControlCharactersFromStdout ? removeControlCharacters(e) : e).to(fromWritable(process.stdout));
5226
- logFile && await writeFile(logFile, ttr.render());
5227
- return ttr.render();
5236
+ const exitCode = await pendingExitCode.promise;
5237
+ verbose && console.log(`[claude-yes] claude exited with code ${exitCode}`);
5238
+ if (logFile) {
5239
+ verbose && console.log(`[claude-yes] Writing rendered logs to ${logFile}`);
5240
+ const logFilePath = path2.resolve(logFile);
5241
+ await mkdir(path2.dirname(logFilePath), { recursive: true }).catch(() => null);
5242
+ await writeFile(logFilePath, ttr.render());
5243
+ }
5244
+ return { exitCode, logs: ttr.render() };
5228
5245
  }
5229
5246
  // node_modules/enhanced-ms/dist/index.js
5230
5247
  var units = {
@@ -7768,11 +7785,11 @@ var parser = new YargsParser({
7768
7785
  format,
7769
7786
  normalize,
7770
7787
  resolve: resolve2,
7771
- require: (path2) => {
7788
+ require: (path3) => {
7772
7789
  if (typeof require2 !== "undefined") {
7773
- return require2(path2);
7774
- } else if (path2.match(/\.json$/)) {
7775
- return JSON.parse(readFileSync(path2, "utf8"));
7790
+ return require2(path3);
7791
+ } else if (path3.match(/\.json$/)) {
7792
+ return JSON.parse(readFileSync(path3, "utf8"));
7776
7793
  } else {
7777
7794
  throw Error("only .json config files are supported in ESM");
7778
7795
  }
@@ -11374,7 +11391,7 @@ var Yargs = YargsFactory(esm_default);
11374
11391
  var yargs_default = Yargs;
11375
11392
 
11376
11393
  // cli.ts
11377
- var argv = yargs_default(hideBin(process.argv)).usage("Usage: $0 [options] [claude args]").option("exit-on-idle", {
11394
+ var argv = yargs_default(hideBin(process.argv)).usage("Usage: $0 [options] [--] [claude args]").example('$0 --exit-on-idle=30s --continue-on-crash "help me solve all todos in my codebase"', "Run Claude with a 30 seconds idle timeout and continue on crash").option("exit-on-idle", {
11378
11395
  type: "string",
11379
11396
  default: "60s",
11380
11397
  description: "Exit after being idle for specified duration"
@@ -11393,22 +11410,11 @@ var argv = yargs_default(hideBin(process.argv)).usage("Usage: $0 [options] [clau
11393
11410
  "unknown-options-as-args": true,
11394
11411
  "halt-at-non-option": true
11395
11412
  }).parseSync();
11396
- var {
11397
- exitOnIdle: exitOnIdleArg,
11398
- continueOnCrash: continueOnCrashArg,
11399
- logFile,
11400
- ...rest
11401
- } = argv;
11402
- var claudeArgs = argv._.map((e) => String(e));
11403
- var exitOnIdle = argv.exitOnIdle != null ? index_default(argv.exitOnIdle) : undefined;
11404
- argv.verbose && console.debug("[claude-yes] Parsed args:", {
11405
- exitOnIdle,
11406
- continueOnCrash: continueOnCrashArg,
11407
- claudeArgs
11408
- });
11409
- await claudeYes({
11410
- exitOnIdle,
11411
- claudeArgs,
11412
- continueOnCrash: continueOnCrashArg,
11413
- logFile
11413
+ var { exitCode, logs: logs2 } = await claudeYes({
11414
+ exitOnIdle: argv.exitOnIdle != null ? index_default(argv.exitOnIdle) : undefined,
11415
+ claudeArgs: argv._.map((e) => String(e)),
11416
+ continueOnCrash: argv.continueOnCrash,
11417
+ logFile: argv.logFile,
11418
+ verbose: argv.verbose
11414
11419
  });
11420
+ process.exit(exitCode ?? 1);
package/dist/index.js CHANGED
@@ -4832,14 +4832,13 @@ function createIdleWatcher(onIdle, idleTimeout) {
4832
4832
  let idleTimeoutId = null;
4833
4833
  return {
4834
4834
  ping: () => {
4835
- if (idleTimeoutId) {
4835
+ if (idleTimeoutId)
4836
4836
  clearTimeout(idleTimeoutId);
4837
- }
4837
+ lastActiveTime = new Date;
4838
4838
  idleTimeoutId = setTimeout(() => {
4839
4839
  clearTimeout(idleTimeoutId);
4840
4840
  onIdle();
4841
4841
  }, idleTimeout);
4842
- lastActiveTime = new Date;
4843
4842
  },
4844
4843
  getLastActiveTime: () => lastActiveTime
4845
4844
  };
@@ -5089,16 +5088,28 @@ class TerminalTextRender {
5089
5088
 
5090
5089
  // index.ts
5091
5090
  import { writeFile } from "fs/promises";
5091
+ import path2 from "path";
5092
+ import { mkdir } from "fs/promises";
5092
5093
  async function claudeYes({
5093
5094
  continueOnCrash,
5094
5095
  exitOnIdle,
5095
5096
  claudeArgs = [],
5096
5097
  cwd = process.cwd(),
5097
5098
  removeControlCharactersFromStdout = false,
5098
- logFile
5099
+ logFile,
5100
+ verbose = false
5099
5101
  } = {}) {
5100
- const defaultIdleTimeout = 60000;
5101
- const idleTimeout = typeof exitOnIdle === "number" ? exitOnIdle : defaultIdleTimeout;
5102
+ if (verbose) {
5103
+ console.log("calling claudeYes: ", {
5104
+ continueOnCrash,
5105
+ exitOnIdle,
5106
+ claudeArgs,
5107
+ cwd,
5108
+ removeControlCharactersFromStdout,
5109
+ logFile,
5110
+ verbose
5111
+ });
5112
+ }
5102
5113
  console.log("⭐ Starting claude, automatically responding to yes/no prompts...");
5103
5114
  console.log("⚠️ 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.");
5104
5115
  process.stdin.setRawMode?.(true);
@@ -5115,15 +5126,16 @@ async function claudeYes({
5115
5126
  cwd,
5116
5127
  env: process.env
5117
5128
  });
5129
+ let pendingExitCode = Promise.withResolvers();
5118
5130
  async function onData(data) {
5119
5131
  await outputWriter.write(data);
5120
5132
  }
5121
5133
  shell.onData(onData);
5122
- shell.onExit(function onExit({ exitCode }) {
5123
- if (continueOnCrash && exitCode !== 0) {
5134
+ shell.onExit(function onExit({ exitCode: exitCode2 }) {
5135
+ if (continueOnCrash && exitCode2 !== 0) {
5124
5136
  if (errorNoConversation) {
5125
5137
  console.log('Claude crashed with "No conversation found to continue", exiting...');
5126
- process.exit(exitCode);
5138
+ return pendingExitCode.resolve(exitCode2);
5127
5139
  }
5128
5140
  console.log("Claude crashed, restarting...");
5129
5141
  shell = pty.spawn("claude", ["continue", "--continue"], {
@@ -5137,7 +5149,7 @@ async function claudeYes({
5137
5149
  shell.onExit(onExit);
5138
5150
  return;
5139
5151
  }
5140
- process.exit(exitCode);
5152
+ return pendingExitCode.resolve(exitCode2);
5141
5153
  });
5142
5154
  const exitClaudeCode = async () => {
5143
5155
  await src_default(["\r", "/exit", "\r"]).forEach(async (e) => {
@@ -5170,21 +5182,19 @@ async function claudeYes({
5170
5182
  readable: shellOutputStream.readable
5171
5183
  };
5172
5184
  const ttr = new TerminalTextRender;
5173
- const idleWatcher = createIdleWatcher(async () => {
5174
- if (exitOnIdle) {
5175
- if (ttr.render().replace(/\s+/g, " ").match(/esc to interrupt|to run in background/)) {
5176
- console.warn("[CLAUDE-YES] Claude is idle, but seems still working, not exiting yet");
5177
- } else {
5178
- console.warn("[CLAUDE-YES] Claude is idle, exiting...");
5179
- await exitClaudeCode();
5180
- }
5185
+ const idleWatcher = !exitOnIdle ? null : createIdleWatcher(async () => {
5186
+ if (ttr.render().replace(/\s+/g, " ").match(/esc to interrupt|to run in background/)) {
5187
+ console.log("[claude-yes] Claude is idle, but seems still working, not exiting yet");
5188
+ } else {
5189
+ console.log("[claude-yes] Claude is idle, exiting...");
5190
+ await exitClaudeCode();
5181
5191
  }
5182
- }, idleTimeout);
5192
+ }, exitOnIdle);
5183
5193
  const confirm = async () => {
5184
5194
  await sleepms(200);
5185
5195
  shell.write("\r");
5186
5196
  };
5187
- await src_default(fromReadable(process.stdin)).forEach(() => idleWatcher.ping()).map((buffer2) => buffer2.toString()).forEach((text) => ttr.write(text)).by(shellStdio).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
5197
+ src_default(fromReadable(process.stdin)).map((buffer2) => buffer2.toString()).by(shellStdio).forEach((text) => ttr.write(text)).forEach(() => idleWatcher?.ping()).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
5188
5198
  if (e2.match(/❯ 1. Yes/))
5189
5199
  return await confirm();
5190
5200
  if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
@@ -5194,8 +5204,15 @@ async function claudeYes({
5194
5204
  return;
5195
5205
  }
5196
5206
  }).run()).replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line).map((e) => removeControlCharactersFromStdout ? removeControlCharacters(e) : e).to(fromWritable(process.stdout));
5197
- logFile && await writeFile(logFile, ttr.render());
5198
- return ttr.render();
5207
+ const exitCode = await pendingExitCode.promise;
5208
+ verbose && console.log(`[claude-yes] claude exited with code ${exitCode}`);
5209
+ if (logFile) {
5210
+ verbose && console.log(`[claude-yes] Writing rendered logs to ${logFile}`);
5211
+ const logFilePath = path2.resolve(logFile);
5212
+ await mkdir(path2.dirname(logFilePath), { recursive: true }).catch(() => null);
5213
+ await writeFile(logFilePath, ttr.render());
5214
+ }
5215
+ return { exitCode, logs: ttr.render() };
5199
5216
  }
5200
5217
  export {
5201
5218
  removeControlCharacters,
package/index.ts CHANGED
@@ -5,15 +5,8 @@ import { removeControlCharacters } from './removeControlCharacters';
5
5
  import { sleepms } from './utils';
6
6
  import { TerminalTextRender } from 'terminal-render';
7
7
  import { writeFile } from 'fs/promises';
8
- // for debug only
9
- // if (import.meta.main) await main();
10
- // async function main() {
11
- // await claudeYes({
12
- // continueOnCrash: true,
13
- // exitOnIdle: 10000,
14
- // claudeArgs: ["say hello and exit"]
15
- // })
16
- // }
8
+ import path from 'path';
9
+ import { mkdir } from 'fs/promises';
17
10
 
18
11
  /**
19
12
  * Main function to run Claude with automatic yes/no respojnses
@@ -35,18 +28,27 @@ export default async function claudeYes({
35
28
  // removeControlCharactersFromStdout = !process.stdout.isTTY,
36
29
  removeControlCharactersFromStdout = false,
37
30
  logFile,
31
+ verbose = false,
38
32
  }: {
39
33
  continueOnCrash?: boolean;
40
- exitOnIdle?: boolean | number;
34
+ exitOnIdle?: number;
41
35
  claudeArgs?: string[];
42
36
  cwd?: string;
43
37
  removeControlCharactersFromStdout?: boolean;
44
38
  logFile?: string;
39
+ verbose?: boolean;
45
40
  } = {}) {
46
- const defaultIdleTimeout = 60e3;
47
- const idleTimeout =
48
- typeof exitOnIdle === 'number' ? exitOnIdle : defaultIdleTimeout;
49
-
41
+ if (verbose) {
42
+ console.log('calling claudeYes: ', {
43
+ continueOnCrash,
44
+ exitOnIdle,
45
+ claudeArgs,
46
+ cwd,
47
+ removeControlCharactersFromStdout,
48
+ logFile,
49
+ verbose,
50
+ });
51
+ }
50
52
  console.log(
51
53
  '⭐ Starting claude, automatically responding to yes/no prompts...'
52
54
  );
@@ -71,6 +73,7 @@ export default async function claudeYes({
71
73
  cwd,
72
74
  env: process.env as Record<string, string>,
73
75
  });
76
+ let pendingExitCode = Promise.withResolvers<number | null>();
74
77
  // TODO handle error if claude is not installed, show msg:
75
78
  // npm install -g @anthropic-ai/claude-code
76
79
 
@@ -86,7 +89,7 @@ export default async function claudeYes({
86
89
  console.log(
87
90
  'Claude crashed with "No conversation found to continue", exiting...'
88
91
  );
89
- void process.exit(exitCode);
92
+ return pendingExitCode.resolve(exitCode);
90
93
  }
91
94
  console.log('Claude crashed, restarting...');
92
95
  shell = pty.spawn('claude', ['continue', '--continue'], {
@@ -100,7 +103,7 @@ export default async function claudeYes({
100
103
  shell.onExit(onExit);
101
104
  return;
102
105
  }
103
- void process.exit(exitCode);
106
+ return pendingExitCode.resolve(exitCode);
104
107
  });
105
108
 
106
109
  const exitClaudeCode = async () => {
@@ -147,33 +150,38 @@ export default async function claudeYes({
147
150
  };
148
151
 
149
152
  const ttr = new TerminalTextRender();
150
- const idleWatcher = createIdleWatcher(async () => {
151
- if (exitOnIdle) {
152
- if (
153
- ttr
154
- .render()
155
- .replace(/\s+/g, ' ')
156
- .match(/esc to interrupt|to run in background/)
157
- ) {
158
- console.warn(
159
- '[CLAUDE-YES] Claude is idle, but seems still working, not exiting yet'
160
- );
161
- } else {
162
- console.warn('[CLAUDE-YES] Claude is idle, exiting...');
163
- await exitClaudeCode();
164
- }
165
- }
166
- }, idleTimeout);
153
+ const idleWatcher = !exitOnIdle
154
+ ? null
155
+ : createIdleWatcher(async () => {
156
+ if (
157
+ ttr
158
+ .render()
159
+ .replace(/\s+/g, ' ')
160
+ .match(/esc to interrupt|to run in background/)
161
+ ) {
162
+ console.log(
163
+ '[claude-yes] Claude is idle, but seems still working, not exiting yet'
164
+ );
165
+ } else {
166
+ console.log('[claude-yes] Claude is idle, exiting...');
167
+ await exitClaudeCode();
168
+ }
169
+ }, exitOnIdle);
167
170
  const confirm = async () => {
168
171
  await sleepms(200);
169
172
  shell.write('\r');
170
173
  };
171
- await sflow(fromReadable<Buffer>(process.stdin))
172
- .forEach(() => idleWatcher.ping()) // ping the idle watcher on output for last active time to keep track of claude status
174
+
175
+ sflow(fromReadable<Buffer>(process.stdin))
173
176
  .map((buffer) => buffer.toString())
174
- .forEach((text) => ttr.write(text))
175
177
  // .forEach(e => appendFile('.cache/io.log', "input |" + JSON.stringify(e) + '\n')) // for debugging
176
178
  .by(shellStdio)
179
+ // handle ttr render
180
+ .forEach((text) => ttr.write(text))
181
+
182
+ // handle idle
183
+ .forEach(() => idleWatcher?.ping()) // ping the idle watcher on output for last active time to keep track of claude status
184
+ // auto-response
177
185
  .forkTo((e) =>
178
186
  e
179
187
  .map((e) => removeControlCharacters(e as string))
@@ -187,7 +195,6 @@ export default async function claudeYes({
187
195
  return;
188
196
  }
189
197
  })
190
-
191
198
  // .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
192
199
  .run()
193
200
  )
@@ -197,8 +204,19 @@ export default async function claudeYes({
197
204
  )
198
205
  .to(fromWritable(process.stdout));
199
206
 
200
- logFile && (await writeFile(logFile, ttr.render()));
201
- return ttr.render(); // return full rendered logs
207
+ const exitCode = await pendingExitCode.promise; // wait for the shell to exit
208
+ verbose && console.log(`[claude-yes] claude exited with code ${exitCode}`);
209
+
210
+ if (logFile) {
211
+ verbose && console.log(`[claude-yes] Writing rendered logs to ${logFile}`);
212
+ const logFilePath = path.resolve(logFile);
213
+ await mkdir(path.dirname(logFilePath), { recursive: true }).catch(
214
+ () => null
215
+ );
216
+ await writeFile(logFilePath, ttr.render());
217
+ }
218
+
219
+ return { exitCode, logs: ttr.render() };
202
220
  }
203
221
 
204
222
  export { removeControlCharacters };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.15.0",
3
+ "version": "1.15.1",
4
4
  "homepage": "https://github.com/snomiao/claude-yes#readme",
5
5
  "license": "MIT",
6
6
  "author": "snomiao <snomiao@gmail.com>",