claude-yes 1.10.0 → 1.11.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/README.md CHANGED
@@ -23,26 +23,26 @@ Learn more about Claude Code: https://www.anthropic.com/claude-code
23
23
  Then install this project:
24
24
 
25
25
  ```bash
26
- npm install yes-claude -g
26
+ npm install claude-yes -g
27
27
  ```
28
28
 
29
29
  ## Usage
30
30
 
31
-
32
31
  ```bash
33
- yes-claude [command] [prompts] [--exit-on-idle]
32
+ claude-yes [command] [prompts] [--exit-on-idle]
34
33
  # works exactly same as `claude` command, and automatically says "Yes" to all yes/no prompts
35
34
 
36
35
  # e.g.
37
- yes-claude "run all tests and commit current changes"
38
- bunx yes-claude "Solve TODO.md"
36
+ claude-yes "run all tests and commit current changes"
37
+ bunx claude-yes "Solve TODO.md"
39
38
 
40
39
  # Auto-exit when Claude becomes idle (useful for automation)
41
- yes-claude "run all tests and commit current changes" --exit-on-idle
40
+ claude-yes "run all tests and commit current changes" --exit-on-idle
42
41
 
43
42
  ```
44
43
 
45
44
  The tool will:
45
+
46
46
  1. run Claude Code
47
47
  2. Whenever claude stucked on yes/no prompts, Automatically say YES, YES, YES, YES, YES to claude
48
48
  3. When using `--exit-on-idle` flag, automatically exit when Claude becomes idle for 3 seconds (useful for automation scripts)
@@ -13,7 +13,7 @@ it('createIdleWatcher should trigger onIdle after timeout', async () => {
13
13
  expect(idleTriggered).toBe(true);
14
14
  }, 1000);
15
15
 
16
- it.concurrent('createIdleWatcher should reset timeout on ping', async () => {
16
+ it('createIdleWatcher should reset timeout on ping', async () => {
17
17
  let idleTriggered = false;
18
18
  const watcher = createIdleWatcher(() => {
19
19
  idleTriggered = true;
@@ -28,7 +28,7 @@ it.concurrent('createIdleWatcher should reset timeout on ping', async () => {
28
28
  expect(idleTriggered).toBe(true);
29
29
  }, 1000);
30
30
 
31
- it.concurrent('createIdleWatcher should update lastActiveTime on ping', async () => {
31
+ it('createIdleWatcher should update lastActiveTime on ping', async () => {
32
32
  const watcher = createIdleWatcher(() => { }, 1000);
33
33
 
34
34
  const initialTime = watcher.getLastActiveTime();
package/dist/cli.js CHANGED
@@ -5046,8 +5046,172 @@ function fromWritable(i) {
5046
5046
  });
5047
5047
  }
5048
5048
 
5049
- // index.ts
5050
- import * as pty from "node-pty";
5049
+ // node_modules/bun-pty/dist/index.js
5050
+ import { dlopen, FFIType, ptr } from "bun:ffi";
5051
+ import { Buffer } from "buffer";
5052
+ import { join as join2 } from "path";
5053
+ import { existsSync } from "fs";
5054
+
5055
+ class EventEmitter {
5056
+ listeners = [];
5057
+ event = (listener) => {
5058
+ this.listeners.push(listener);
5059
+ return {
5060
+ dispose: () => {
5061
+ const i = this.listeners.indexOf(listener);
5062
+ if (i !== -1) {
5063
+ this.listeners.splice(i, 1);
5064
+ }
5065
+ }
5066
+ };
5067
+ };
5068
+ fire(data) {
5069
+ for (const listener of this.listeners) {
5070
+ listener(data);
5071
+ }
5072
+ }
5073
+ }
5074
+ var DEFAULT_COLS = 80;
5075
+ var DEFAULT_ROWS = 24;
5076
+ var DEFAULT_FILE = "sh";
5077
+ var DEFAULT_NAME = "xterm";
5078
+ function resolveLibPath() {
5079
+ const env = process.env.BUN_PTY_LIB;
5080
+ if (env && existsSync(env))
5081
+ return env;
5082
+ const platform = process.platform;
5083
+ const arch = process.arch;
5084
+ const filename = platform === "darwin" ? arch === "arm64" ? "librust_pty_arm64.dylib" : "librust_pty.dylib" : platform === "win32" ? "rust_pty.dll" : arch === "arm64" ? "librust_pty_arm64.so" : "librust_pty.so";
5085
+ const base = Bun.fileURLToPath(import.meta.url);
5086
+ const here = base.replace(/\/dist\/.*$/, "");
5087
+ const fallbackPaths = [
5088
+ join2(here, "rust-pty", "target", "release", filename),
5089
+ join2(here, "..", "bun-pty", "rust-pty", "target", "release", filename),
5090
+ join2(process.cwd(), "node_modules", "bun-pty", "rust-pty", "target", "release", filename)
5091
+ ];
5092
+ for (const path2 of fallbackPaths) {
5093
+ if (existsSync(path2))
5094
+ return path2;
5095
+ }
5096
+ throw new Error(`librust_pty shared library not found.
5097
+ Checked:
5098
+ - BUN_PTY_LIB=${env ?? "<unset>"}
5099
+ - ${fallbackPaths.join(`
5100
+ - `)}
5101
+
5102
+ Set BUN_PTY_LIB or ensure one of these paths contains the file.`);
5103
+ }
5104
+ var libPath = resolveLibPath();
5105
+ var lib;
5106
+ try {
5107
+ lib = dlopen(libPath, {
5108
+ bun_pty_spawn: {
5109
+ args: [FFIType.cstring, FFIType.cstring, FFIType.i32, FFIType.i32],
5110
+ returns: FFIType.i32
5111
+ },
5112
+ bun_pty_write: {
5113
+ args: [FFIType.i32, FFIType.pointer, FFIType.i32],
5114
+ returns: FFIType.i32
5115
+ },
5116
+ bun_pty_read: {
5117
+ args: [FFIType.i32, FFIType.pointer, FFIType.i32],
5118
+ returns: FFIType.i32
5119
+ },
5120
+ bun_pty_resize: {
5121
+ args: [FFIType.i32, FFIType.i32, FFIType.i32],
5122
+ returns: FFIType.i32
5123
+ },
5124
+ bun_pty_kill: { args: [FFIType.i32], returns: FFIType.i32 },
5125
+ bun_pty_get_pid: { args: [FFIType.i32], returns: FFIType.i32 },
5126
+ bun_pty_close: { args: [FFIType.i32], returns: FFIType.void }
5127
+ });
5128
+ } catch (error) {
5129
+ console.error("Failed to load lib", error);
5130
+ }
5131
+
5132
+ class Terminal {
5133
+ handle = -1;
5134
+ _pid = -1;
5135
+ _cols = DEFAULT_COLS;
5136
+ _rows = DEFAULT_ROWS;
5137
+ _name = DEFAULT_NAME;
5138
+ _readLoop = false;
5139
+ _closing = false;
5140
+ _onData = new EventEmitter;
5141
+ _onExit = new EventEmitter;
5142
+ constructor(file = DEFAULT_FILE, args = [], opts = { name: DEFAULT_NAME }) {
5143
+ this._cols = opts.cols ?? DEFAULT_COLS;
5144
+ this._rows = opts.rows ?? DEFAULT_ROWS;
5145
+ const cwd = opts.cwd ?? process.cwd();
5146
+ const cmdline = [file, ...args].join(" ");
5147
+ this.handle = lib.symbols.bun_pty_spawn(Buffer.from(`${cmdline}\x00`, "utf8"), Buffer.from(`${cwd}\x00`, "utf8"), this._cols, this._rows);
5148
+ if (this.handle < 0)
5149
+ throw new Error("PTY spawn failed");
5150
+ this._pid = lib.symbols.bun_pty_get_pid(this.handle);
5151
+ this._startReadLoop();
5152
+ }
5153
+ get pid() {
5154
+ return this._pid;
5155
+ }
5156
+ get cols() {
5157
+ return this._cols;
5158
+ }
5159
+ get rows() {
5160
+ return this._rows;
5161
+ }
5162
+ get process() {
5163
+ return "shell";
5164
+ }
5165
+ get onData() {
5166
+ return this._onData.event;
5167
+ }
5168
+ get onExit() {
5169
+ return this._onExit.event;
5170
+ }
5171
+ write(data) {
5172
+ if (this._closing)
5173
+ return;
5174
+ const buf = Buffer.from(data, "utf8");
5175
+ lib.symbols.bun_pty_write(this.handle, ptr(buf), buf.length);
5176
+ }
5177
+ resize(cols, rows) {
5178
+ if (this._closing)
5179
+ return;
5180
+ this._cols = cols;
5181
+ this._rows = rows;
5182
+ lib.symbols.bun_pty_resize(this.handle, cols, rows);
5183
+ }
5184
+ kill(signal = "SIGTERM") {
5185
+ if (this._closing)
5186
+ return;
5187
+ this._closing = true;
5188
+ lib.symbols.bun_pty_kill(this.handle);
5189
+ lib.symbols.bun_pty_close(this.handle);
5190
+ this._onExit.fire({ exitCode: 0, signal });
5191
+ }
5192
+ async _startReadLoop() {
5193
+ if (this._readLoop)
5194
+ return;
5195
+ this._readLoop = true;
5196
+ const buf = Buffer.allocUnsafe(4096);
5197
+ while (this._readLoop && !this._closing) {
5198
+ const n = lib.symbols.bun_pty_read(this.handle, ptr(buf), buf.length);
5199
+ if (n > 0) {
5200
+ this._onData.fire(buf.subarray(0, n).toString("utf8"));
5201
+ } else if (n === -2) {
5202
+ this._onExit.fire({ exitCode: 0 });
5203
+ break;
5204
+ } else if (n < 0) {
5205
+ break;
5206
+ } else {
5207
+ await new Promise((r) => setTimeout(r, 8));
5208
+ }
5209
+ }
5210
+ }
5211
+ }
5212
+ function spawn(file, args, options) {
5213
+ return new Terminal(file, args, options);
5214
+ }
5051
5215
 
5052
5216
  // createIdleWatcher.ts
5053
5217
  function createIdleWatcher(onIdle, idleTimeout) {
@@ -5082,7 +5246,11 @@ function sleepms(ms) {
5082
5246
  if (__require.main == __require.module)
5083
5247
  await main();
5084
5248
  async function main() {}
5085
- async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {}) {
5249
+ async function claudeYes({
5250
+ continueOnCrash,
5251
+ exitOnIdle,
5252
+ claudeArgs = []
5253
+ } = {}) {
5086
5254
  const defaultTimeout = 5000;
5087
5255
  const idleTimeout = typeof exitOnIdle === "number" ? exitOnIdle : defaultTimeout;
5088
5256
  console.log("⭐ Starting claude, automatically responding to yes/no prompts...");
@@ -5090,9 +5258,11 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5090
5258
  process.stdin.setRawMode?.(true);
5091
5259
  const prefix = "";
5092
5260
  const PREFIXLENGTH = prefix.length;
5261
+ let errorNoConversation = false;
5093
5262
  const shellOutputStream = new TransformStream;
5094
5263
  const outputWriter = shellOutputStream.writable.getWriter();
5095
- let shell = pty.spawn("claude", claudeArgs, {
5264
+ let shell = spawn("claude", claudeArgs, {
5265
+ name: "xterm-color",
5096
5266
  cols: process.stdout.columns - PREFIXLENGTH,
5097
5267
  rows: process.stdout.rows,
5098
5268
  cwd: process.cwd(),
@@ -5103,18 +5273,22 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5103
5273
  }
5104
5274
  shell.onData(onData);
5105
5275
  shell.onExit(function onExit({ exitCode }) {
5106
- if (continueOnCrash) {
5107
- if (exitCode !== 0) {
5108
- console.log("Claude crashed, restarting...");
5109
- shell = pty.spawn("claude", ["continue", "--continue"], {
5110
- cols: process.stdout.columns - PREFIXLENGTH,
5111
- rows: process.stdout.rows,
5112
- cwd: process.cwd(),
5113
- env: process.env
5114
- });
5115
- shell.onData(onData);
5116
- shell.onExit(onExit);
5276
+ if (continueOnCrash && exitCode !== 0) {
5277
+ if (errorNoConversation) {
5278
+ console.log('Claude crashed with "No conversation found to continue", exiting...');
5279
+ process.exit(exitCode);
5117
5280
  }
5281
+ console.log("Claude crashed, restarting...");
5282
+ shell = spawn("claude", ["continue", "--continue"], {
5283
+ name: "xterm-color",
5284
+ cols: process.stdout.columns - PREFIXLENGTH,
5285
+ rows: process.stdout.rows,
5286
+ cwd: process.cwd(),
5287
+ env: process.env
5288
+ });
5289
+ shell.onData(onData);
5290
+ shell.onExit(onExit);
5291
+ return;
5118
5292
  }
5119
5293
  process.exit(exitCode);
5120
5294
  });
@@ -5142,7 +5316,10 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5142
5316
  shell.resize(columns - PREFIXLENGTH, rows);
5143
5317
  });
5144
5318
  const shellStdio = {
5145
- writable: new WritableStream({ write: (data) => shell.write(data), close: () => {} }),
5319
+ writable: new WritableStream({
5320
+ write: (data) => shell.write(data),
5321
+ close: () => {}
5322
+ }),
5146
5323
  readable: shellOutputStream.readable
5147
5324
  };
5148
5325
  const idleWatcher = createIdleWatcher(async () => {
@@ -5151,15 +5328,18 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5151
5328
  await exitClaudeCode();
5152
5329
  }
5153
5330
  }, idleTimeout);
5331
+ const confirm = async () => {
5332
+ await sleepms(200);
5333
+ shell.write("\r");
5334
+ };
5154
5335
  await src_default(fromReadable(process.stdin)).map((buffer2) => buffer2.toString()).by(shellStdio).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
5155
- if (e2.match(/❯ 1. Yes/)) {
5156
- await sleepms(200);
5157
- shell.write("\r");
5158
- }
5159
- }).forEach(async (e2) => {
5160
- if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
5161
- await sleepms(200);
5162
- shell.write("\r");
5336
+ if (e2.match(/❯ 1. Yes/))
5337
+ return await confirm();
5338
+ if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
5339
+ return await confirm();
5340
+ if (e2.match(/No conversation found to continue/)) {
5341
+ errorNoConversation = true;
5342
+ return;
5163
5343
  }
5164
5344
  }).run()).replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line).forEach(() => idleWatcher.ping()).map((e) => !process.stdout.isTTY ? removeControlCharacters(e) : e).to(fromWritable(process.stdout));
5165
5345
  }
package/dist/index.js CHANGED
@@ -4826,8 +4826,172 @@ function fromWritable(i) {
4826
4826
  });
4827
4827
  }
4828
4828
 
4829
- // index.ts
4830
- import * as pty from "node-pty";
4829
+ // node_modules/bun-pty/dist/index.js
4830
+ import { dlopen, FFIType, ptr } from "bun:ffi";
4831
+ import { Buffer } from "buffer";
4832
+ import { join as join2 } from "path";
4833
+ import { existsSync } from "fs";
4834
+
4835
+ class EventEmitter {
4836
+ listeners = [];
4837
+ event = (listener) => {
4838
+ this.listeners.push(listener);
4839
+ return {
4840
+ dispose: () => {
4841
+ const i = this.listeners.indexOf(listener);
4842
+ if (i !== -1) {
4843
+ this.listeners.splice(i, 1);
4844
+ }
4845
+ }
4846
+ };
4847
+ };
4848
+ fire(data) {
4849
+ for (const listener of this.listeners) {
4850
+ listener(data);
4851
+ }
4852
+ }
4853
+ }
4854
+ var DEFAULT_COLS = 80;
4855
+ var DEFAULT_ROWS = 24;
4856
+ var DEFAULT_FILE = "sh";
4857
+ var DEFAULT_NAME = "xterm";
4858
+ function resolveLibPath() {
4859
+ const env = process.env.BUN_PTY_LIB;
4860
+ if (env && existsSync(env))
4861
+ return env;
4862
+ const platform = process.platform;
4863
+ const arch = process.arch;
4864
+ const filename = platform === "darwin" ? arch === "arm64" ? "librust_pty_arm64.dylib" : "librust_pty.dylib" : platform === "win32" ? "rust_pty.dll" : arch === "arm64" ? "librust_pty_arm64.so" : "librust_pty.so";
4865
+ const base = Bun.fileURLToPath(import.meta.url);
4866
+ const here = base.replace(/\/dist\/.*$/, "");
4867
+ const fallbackPaths = [
4868
+ join2(here, "rust-pty", "target", "release", filename),
4869
+ join2(here, "..", "bun-pty", "rust-pty", "target", "release", filename),
4870
+ join2(process.cwd(), "node_modules", "bun-pty", "rust-pty", "target", "release", filename)
4871
+ ];
4872
+ for (const path2 of fallbackPaths) {
4873
+ if (existsSync(path2))
4874
+ return path2;
4875
+ }
4876
+ throw new Error(`librust_pty shared library not found.
4877
+ Checked:
4878
+ - BUN_PTY_LIB=${env ?? "<unset>"}
4879
+ - ${fallbackPaths.join(`
4880
+ - `)}
4881
+
4882
+ Set BUN_PTY_LIB or ensure one of these paths contains the file.`);
4883
+ }
4884
+ var libPath = resolveLibPath();
4885
+ var lib;
4886
+ try {
4887
+ lib = dlopen(libPath, {
4888
+ bun_pty_spawn: {
4889
+ args: [FFIType.cstring, FFIType.cstring, FFIType.i32, FFIType.i32],
4890
+ returns: FFIType.i32
4891
+ },
4892
+ bun_pty_write: {
4893
+ args: [FFIType.i32, FFIType.pointer, FFIType.i32],
4894
+ returns: FFIType.i32
4895
+ },
4896
+ bun_pty_read: {
4897
+ args: [FFIType.i32, FFIType.pointer, FFIType.i32],
4898
+ returns: FFIType.i32
4899
+ },
4900
+ bun_pty_resize: {
4901
+ args: [FFIType.i32, FFIType.i32, FFIType.i32],
4902
+ returns: FFIType.i32
4903
+ },
4904
+ bun_pty_kill: { args: [FFIType.i32], returns: FFIType.i32 },
4905
+ bun_pty_get_pid: { args: [FFIType.i32], returns: FFIType.i32 },
4906
+ bun_pty_close: { args: [FFIType.i32], returns: FFIType.void }
4907
+ });
4908
+ } catch (error) {
4909
+ console.error("Failed to load lib", error);
4910
+ }
4911
+
4912
+ class Terminal {
4913
+ handle = -1;
4914
+ _pid = -1;
4915
+ _cols = DEFAULT_COLS;
4916
+ _rows = DEFAULT_ROWS;
4917
+ _name = DEFAULT_NAME;
4918
+ _readLoop = false;
4919
+ _closing = false;
4920
+ _onData = new EventEmitter;
4921
+ _onExit = new EventEmitter;
4922
+ constructor(file = DEFAULT_FILE, args = [], opts = { name: DEFAULT_NAME }) {
4923
+ this._cols = opts.cols ?? DEFAULT_COLS;
4924
+ this._rows = opts.rows ?? DEFAULT_ROWS;
4925
+ const cwd = opts.cwd ?? process.cwd();
4926
+ const cmdline = [file, ...args].join(" ");
4927
+ this.handle = lib.symbols.bun_pty_spawn(Buffer.from(`${cmdline}\x00`, "utf8"), Buffer.from(`${cwd}\x00`, "utf8"), this._cols, this._rows);
4928
+ if (this.handle < 0)
4929
+ throw new Error("PTY spawn failed");
4930
+ this._pid = lib.symbols.bun_pty_get_pid(this.handle);
4931
+ this._startReadLoop();
4932
+ }
4933
+ get pid() {
4934
+ return this._pid;
4935
+ }
4936
+ get cols() {
4937
+ return this._cols;
4938
+ }
4939
+ get rows() {
4940
+ return this._rows;
4941
+ }
4942
+ get process() {
4943
+ return "shell";
4944
+ }
4945
+ get onData() {
4946
+ return this._onData.event;
4947
+ }
4948
+ get onExit() {
4949
+ return this._onExit.event;
4950
+ }
4951
+ write(data) {
4952
+ if (this._closing)
4953
+ return;
4954
+ const buf = Buffer.from(data, "utf8");
4955
+ lib.symbols.bun_pty_write(this.handle, ptr(buf), buf.length);
4956
+ }
4957
+ resize(cols, rows) {
4958
+ if (this._closing)
4959
+ return;
4960
+ this._cols = cols;
4961
+ this._rows = rows;
4962
+ lib.symbols.bun_pty_resize(this.handle, cols, rows);
4963
+ }
4964
+ kill(signal = "SIGTERM") {
4965
+ if (this._closing)
4966
+ return;
4967
+ this._closing = true;
4968
+ lib.symbols.bun_pty_kill(this.handle);
4969
+ lib.symbols.bun_pty_close(this.handle);
4970
+ this._onExit.fire({ exitCode: 0, signal });
4971
+ }
4972
+ async _startReadLoop() {
4973
+ if (this._readLoop)
4974
+ return;
4975
+ this._readLoop = true;
4976
+ const buf = Buffer.allocUnsafe(4096);
4977
+ while (this._readLoop && !this._closing) {
4978
+ const n = lib.symbols.bun_pty_read(this.handle, ptr(buf), buf.length);
4979
+ if (n > 0) {
4980
+ this._onData.fire(buf.subarray(0, n).toString("utf8"));
4981
+ } else if (n === -2) {
4982
+ this._onExit.fire({ exitCode: 0 });
4983
+ break;
4984
+ } else if (n < 0) {
4985
+ break;
4986
+ } else {
4987
+ await new Promise((r) => setTimeout(r, 8));
4988
+ }
4989
+ }
4990
+ }
4991
+ }
4992
+ function spawn(file, args, options) {
4993
+ return new Terminal(file, args, options);
4994
+ }
4831
4995
 
4832
4996
  // createIdleWatcher.ts
4833
4997
  function createIdleWatcher(onIdle, idleTimeout) {
@@ -4862,7 +5026,11 @@ function sleepms(ms) {
4862
5026
  if (__require.main == __require.module)
4863
5027
  await main();
4864
5028
  async function main() {}
4865
- async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {}) {
5029
+ async function claudeYes({
5030
+ continueOnCrash,
5031
+ exitOnIdle,
5032
+ claudeArgs = []
5033
+ } = {}) {
4866
5034
  const defaultTimeout = 5000;
4867
5035
  const idleTimeout = typeof exitOnIdle === "number" ? exitOnIdle : defaultTimeout;
4868
5036
  console.log("⭐ Starting claude, automatically responding to yes/no prompts...");
@@ -4870,9 +5038,11 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4870
5038
  process.stdin.setRawMode?.(true);
4871
5039
  const prefix = "";
4872
5040
  const PREFIXLENGTH = prefix.length;
5041
+ let errorNoConversation = false;
4873
5042
  const shellOutputStream = new TransformStream;
4874
5043
  const outputWriter = shellOutputStream.writable.getWriter();
4875
- let shell = pty.spawn("claude", claudeArgs, {
5044
+ let shell = spawn("claude", claudeArgs, {
5045
+ name: "xterm-color",
4876
5046
  cols: process.stdout.columns - PREFIXLENGTH,
4877
5047
  rows: process.stdout.rows,
4878
5048
  cwd: process.cwd(),
@@ -4883,18 +5053,22 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4883
5053
  }
4884
5054
  shell.onData(onData);
4885
5055
  shell.onExit(function onExit({ exitCode }) {
4886
- if (continueOnCrash) {
4887
- if (exitCode !== 0) {
4888
- console.log("Claude crashed, restarting...");
4889
- shell = pty.spawn("claude", ["continue", "--continue"], {
4890
- cols: process.stdout.columns - PREFIXLENGTH,
4891
- rows: process.stdout.rows,
4892
- cwd: process.cwd(),
4893
- env: process.env
4894
- });
4895
- shell.onData(onData);
4896
- shell.onExit(onExit);
5056
+ if (continueOnCrash && exitCode !== 0) {
5057
+ if (errorNoConversation) {
5058
+ console.log('Claude crashed with "No conversation found to continue", exiting...');
5059
+ process.exit(exitCode);
4897
5060
  }
5061
+ console.log("Claude crashed, restarting...");
5062
+ shell = spawn("claude", ["continue", "--continue"], {
5063
+ name: "xterm-color",
5064
+ cols: process.stdout.columns - PREFIXLENGTH,
5065
+ rows: process.stdout.rows,
5066
+ cwd: process.cwd(),
5067
+ env: process.env
5068
+ });
5069
+ shell.onData(onData);
5070
+ shell.onExit(onExit);
5071
+ return;
4898
5072
  }
4899
5073
  process.exit(exitCode);
4900
5074
  });
@@ -4922,7 +5096,10 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4922
5096
  shell.resize(columns - PREFIXLENGTH, rows);
4923
5097
  });
4924
5098
  const shellStdio = {
4925
- writable: new WritableStream({ write: (data) => shell.write(data), close: () => {} }),
5099
+ writable: new WritableStream({
5100
+ write: (data) => shell.write(data),
5101
+ close: () => {}
5102
+ }),
4926
5103
  readable: shellOutputStream.readable
4927
5104
  };
4928
5105
  const idleWatcher = createIdleWatcher(async () => {
@@ -4931,15 +5108,18 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4931
5108
  await exitClaudeCode();
4932
5109
  }
4933
5110
  }, idleTimeout);
5111
+ const confirm = async () => {
5112
+ await sleepms(200);
5113
+ shell.write("\r");
5114
+ };
4934
5115
  await src_default(fromReadable(process.stdin)).map((buffer2) => buffer2.toString()).by(shellStdio).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach(async (e2) => {
4935
- if (e2.match(/❯ 1. Yes/)) {
4936
- await sleepms(200);
4937
- shell.write("\r");
4938
- }
4939
- }).forEach(async (e2) => {
4940
- if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
4941
- await sleepms(200);
4942
- shell.write("\r");
5116
+ if (e2.match(/❯ 1. Yes/))
5117
+ return await confirm();
5118
+ if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
5119
+ return await confirm();
5120
+ if (e2.match(/No conversation found to continue/)) {
5121
+ errorNoConversation = true;
5122
+ return;
4943
5123
  }
4944
5124
  }).run()).replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line).forEach(() => idleWatcher.ping()).map((e) => !process.stdout.isTTY ? removeControlCharacters(e) : e).to(fromWritable(process.stdout));
4945
5125
  }
package/index.ts CHANGED
@@ -1,136 +1,172 @@
1
-
2
1
  import { fromReadable, fromWritable } from "from-node-stream";
3
- import * as pty from "node-pty";
2
+ import * as pty from "bun-pty";
4
3
  import sflow from "sflow";
5
4
  import { createIdleWatcher } from "./createIdleWatcher";
6
5
  import { removeControlCharacters } from "./removeControlCharacters";
7
6
  import { sleepms } from "./utils";
8
7
 
9
-
10
8
  if (import.meta.main) await main();
11
9
  async function main() {
12
- // this script not support bun yet, so use node js to run.
13
-
14
- // node-pty is not supported in bun, so we use node.js to run this script
10
+ // this script not support bun yet, so use node js to run.
11
+ // Note: Attempted migration to bun-pty but reverted due to GLIBC compatibility issues (requires GLIBC 2.39)
12
+ // bun-pty is not compatible with this environment, using node-pty instead
15
13
  }
16
14
 
17
- export default async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] }: { continueOnCrash?: boolean, exitOnIdle?: boolean | number, claudeArgs?: string[] } = {}) {
18
- const defaultTimeout = 5e3; // 5 seconds idle timeout
19
- const idleTimeout = typeof exitOnIdle === 'number' ? exitOnIdle : defaultTimeout;
20
-
21
- console.log('⭐ Starting claude, automatically responding to yes/no prompts...');
22
- 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.');
23
-
24
- process.stdin.setRawMode?.(true) //must be called any stdout/stdin usage
25
- const prefix = '' // "YESC|"
26
- const PREFIXLENGTH = prefix.length;
27
-
28
-
29
- // TODO: implement this flag to continue on crash
30
- // 1. if it crashes, show message 'claude crashed, restarting..'
31
- // 2. spawn a 'claude --continue'
32
- // 3. when new process it's ready, re-attach the into new process (in shellStdio, pipe new process stdin/stdout to )
33
- // 4. if it crashes again, exit the process
34
-
35
- const shellOutputStream = new TransformStream<string, string>()
36
- const outputWriter = shellOutputStream.writable.getWriter()
37
-
38
- let shell = pty.spawn('claude', claudeArgs, {
15
+ /**
16
+ * Main function to run Claude with automatic yes/no responses
17
+ * @param options Configuration options
18
+ * @param options.continueOnCrash - If true, automatically restart Claude when it crashes:
19
+ * 1. Shows message 'Claude crashed, restarting..'
20
+ * 2. Spawns a new 'claude --continue' process
21
+ * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)
22
+ * 4. If it crashes with "No conversation found to continue", exits the process
23
+ * @param options.exitOnIdle - Exit when Claude is idle. Boolean or timeout in milliseconds
24
+ * @param options.claudeArgs - Additional arguments to pass to the Claude CLI
25
+ */
26
+ export default async function claudeYes({
27
+ continueOnCrash,
28
+ exitOnIdle,
29
+ claudeArgs = [],
30
+ }: {
31
+ continueOnCrash?: boolean;
32
+ exitOnIdle?: boolean | number;
33
+ claudeArgs?: string[];
34
+ } = {}) {
35
+ const defaultTimeout = 5e3; // 5 seconds idle timeout
36
+ const idleTimeout =
37
+ typeof exitOnIdle === "number" ? exitOnIdle : defaultTimeout;
38
+
39
+ console.log(
40
+ "⭐ Starting claude, automatically responding to yes/no prompts..."
41
+ );
42
+ console.log(
43
+ "⚠️ 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."
44
+ );
45
+
46
+ process.stdin.setRawMode?.(true); //must be called any stdout/stdin usage
47
+ const prefix = ""; // "YESC|"
48
+ const PREFIXLENGTH = prefix.length;
49
+ let errorNoConversation = false; // match 'No conversation found to continue'
50
+
51
+ const shellOutputStream = new TransformStream<string, string>();
52
+ const outputWriter = shellOutputStream.writable.getWriter();
53
+
54
+ let shell = pty.spawn("claude", claudeArgs, {
55
+ name: "xterm-color", // use xterm color mode
56
+ cols: process.stdout.columns - PREFIXLENGTH,
57
+ rows: process.stdout.rows,
58
+ cwd: process.cwd(),
59
+ env: process.env as Record<string, string>,
60
+ });
61
+ // TODO handle error if claude is not installed, show msg:
62
+ // npm install -g @anthropic-ai/claude-code
63
+
64
+ async function onData(data: string) {
65
+ // append data to the buffer, so we can process it later
66
+ await outputWriter.write(data);
67
+ }
68
+ shell.onData(onData);
69
+ // when claude process exits, exit the main process with the same exit code
70
+ shell.onExit(function onExit({ exitCode }) {
71
+ if (continueOnCrash && exitCode !== 0) {
72
+ if (errorNoConversation) {
73
+ console.log(
74
+ 'Claude crashed with "No conversation found to continue", exiting...'
75
+ );
76
+ void process.exit(exitCode);
77
+ }
78
+ console.log("Claude crashed, restarting...");
79
+ shell = pty.spawn("claude", ["continue", "--continue"], {
80
+ name: "xterm-color", // use xterm color mode
39
81
  cols: process.stdout.columns - PREFIXLENGTH,
40
82
  rows: process.stdout.rows,
41
83
  cwd: process.cwd(),
42
- env: process.env,
43
- });
44
- // TODO handle error if claude is not installed, show msg:
45
- // npm install -g @anthropic-ai/claude-code
46
-
47
- async function onData(data: string) {
48
- // append data to the buffer, so we can process it later
49
- await outputWriter.write(data);
84
+ env: process.env as Record<string, string>,
85
+ });
86
+ shell.onData(onData);
87
+ shell.onExit(onExit);
88
+ return;
50
89
  }
51
- shell.onData(onData)
52
- // when claude process exits, exit the main process with the same exit code
53
- shell.onExit(function onExit({ exitCode }) {
54
- if (continueOnCrash) {
55
- if (exitCode !== 0) {
56
- console.log('Claude crashed, restarting...');
57
- shell = pty.spawn('claude', ['continue', '--continue'], {
58
- cols: process.stdout.columns - PREFIXLENGTH,
59
- rows: process.stdout.rows,
60
- cwd: process.cwd(),
61
- env: process.env,
62
- });
63
- shell.onData(onData)
64
- shell.onExit(onExit);
65
- }
66
- }
67
- void process.exit(exitCode);
68
- });
69
-
70
- const exitClaudeCode = async () => {
71
- // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input
72
- await sflow(['\r', '/exit', '\r']).forEach(async (e) => {
73
- await sleepms(200)
74
- shell.write(e)
75
- }).run();
76
-
77
- // wait for shell to exit or kill it with a timeout
78
- let exited = false;
79
- await Promise.race([
80
- new Promise<void>((resolve) => shell.onExit(() => { resolve(); exited = true; })), // resolve when shell exits
81
- // if shell doesn't exit in 5 seconds, kill it
82
- new Promise<void>((resolve) => setTimeout(() => {
83
- if (exited) return; // if shell already exited, do nothing
84
- shell.kill(); // kill the shell process if it doesn't exit in time
85
- resolve();
86
- }, 5000)) // 5 seconds timeout
87
- ]);
90
+ void process.exit(exitCode);
91
+ });
92
+
93
+ const exitClaudeCode = async () => {
94
+ // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input
95
+ await sflow(["\r", "/exit", "\r"])
96
+ .forEach(async (e) => {
97
+ await sleepms(200);
98
+ shell.write(e);
99
+ })
100
+ .run();
101
+
102
+ // wait for shell to exit or kill it with a timeout
103
+ let exited = false;
104
+ await Promise.race([
105
+ new Promise<void>((resolve) =>
106
+ shell.onExit(() => {
107
+ resolve();
108
+ exited = true;
109
+ })
110
+ ), // resolve when shell exits
111
+ // if shell doesn't exit in 5 seconds, kill it
112
+ new Promise<void>((resolve) =>
113
+ setTimeout(() => {
114
+ if (exited) return; // if shell already exited, do nothing
115
+ shell.kill(); // kill the shell process if it doesn't exit in time
116
+ resolve();
117
+ }, 5000)
118
+ ), // 5 seconds timeout
119
+ ]);
120
+ };
121
+
122
+ // when current tty resized, resize the pty
123
+ process.stdout.on("resize", () => {
124
+ const { columns, rows } = process.stdout;
125
+ shell.resize(columns - PREFIXLENGTH, rows);
126
+ });
127
+
128
+ const shellStdio = {
129
+ writable: new WritableStream<string>({
130
+ write: (data) => shell.write(data),
131
+ close: () => {},
132
+ }),
133
+ readable: shellOutputStream.readable,
134
+ };
135
+
136
+ const idleWatcher = createIdleWatcher(async () => {
137
+ if (exitOnIdle) {
138
+ console.log("Claude is idle, exiting...");
139
+ await exitClaudeCode();
88
140
  }
89
-
90
- // when current tty resized, resize the pty
91
- process.stdout.on('resize', () => {
92
- const { columns, rows } = process.stdout;
93
- shell.resize(columns - PREFIXLENGTH, rows);
94
- });
95
-
96
- const shellStdio = {
97
- writable: new WritableStream<string>({ write: (data) => shell.write(data), close: () => { } }),
98
- readable: shellOutputStream.readable
99
- };
100
-
101
- const idleWatcher = createIdleWatcher(async () => {
102
- if (exitOnIdle) {
103
- console.log('Claude is idle, exiting...');
104
- await exitClaudeCode()
105
- }
106
- }, idleTimeout);
107
-
108
- await sflow(fromReadable<Buffer>(process.stdin))
109
- .map((buffer) => buffer.toString())
110
- // .forEach(e => appendFile('.cache/io.log', "input |" + JSON.stringify(e) + '\n')) // for debugging
111
- .by(shellStdio)
112
- .forkTo(e => e
113
- .map(e => removeControlCharacters(e as string))
114
- .map(e => e.replaceAll('\r', '')) // remove carriage return
115
- .forEach(async e => {
116
- if (e.match(/❯ 1. Yes/)) {
117
- await sleepms(200)
118
- shell.write("\r")
119
- }
120
- })
121
- .forEach(async e => {
122
- if (e.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
123
- await sleepms(200)
124
- shell.write("\r")
125
- }
126
- })
127
- // .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
128
- .run()
129
- )
130
- .replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line) // add prefix
131
- .forEach(() => idleWatcher.ping()) // ping the idle watcher on output for last active time to keep track of claude status
132
- .map(e => !process.stdout.isTTY ? removeControlCharacters(e) : (e)) // remove control characters if output is not a TTY
133
- .to(fromWritable(process.stdout));
141
+ }, idleTimeout);
142
+ const confirm = async () => {
143
+ await sleepms(200);
144
+ shell.write("\r");
145
+ };
146
+ await sflow(fromReadable<Buffer>(process.stdin))
147
+ .map((buffer) => buffer.toString())
148
+ // .forEach(e => appendFile('.cache/io.log', "input |" + JSON.stringify(e) + '\n')) // for debugging
149
+ .by(shellStdio)
150
+ .forkTo((e) =>
151
+ e
152
+ .map((e) => removeControlCharacters(e as string))
153
+ .map((e) => e.replaceAll("\r", "")) // remove carriage return
154
+ .forEach(async (e) => {
155
+ if (e.match(/❯ 1. Yes/)) return await confirm();
156
+ if (e.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
157
+ return await confirm();
158
+ if (e.match(/No conversation found to continue/)) {
159
+ errorNoConversation = true; // set flag to true if error message is found
160
+ return;
161
+ }
162
+ })
163
+ // .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
164
+ .run()
165
+ )
166
+ .replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line) // add prefix
167
+ .forEach(() => idleWatcher.ping()) // ping the idle watcher on output for last active time to keep track of claude status
168
+ .map((e) => (!process.stdout.isTTY ? removeControlCharacters(e) : e)) // remove control characters if output is not a TTY
169
+ .to(fromWritable(process.stdout));
134
170
  }
135
171
 
136
172
  export { removeControlCharacters };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "homepage": "https://github.com/snomiao/claude-yes#readme",
5
5
  "license": "MIT",
6
6
  "author": "snomiao <snomiao@gmail.com>",
@@ -60,7 +60,7 @@
60
60
  "module": "index.ts",
61
61
  "types": "./index.ts",
62
62
  "dependencies": {
63
- "node-pty": "^1.0.0"
63
+ "bun-pty": "^0.3.2"
64
64
  },
65
65
  "description": "A wrapper tool that automates interactions with the Claude CLI by automatically handling common prompts and responses.",
66
66
  "main": "index.js",