claude-yes 1.10.1 → 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...");
@@ -5093,7 +5261,8 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5093
5261
  let errorNoConversation = false;
5094
5262
  const shellOutputStream = new TransformStream;
5095
5263
  const outputWriter = shellOutputStream.writable.getWriter();
5096
- let shell = pty.spawn("claude", claudeArgs, {
5264
+ let shell = spawn("claude", claudeArgs, {
5265
+ name: "xterm-color",
5097
5266
  cols: process.stdout.columns - PREFIXLENGTH,
5098
5267
  rows: process.stdout.rows,
5099
5268
  cwd: process.cwd(),
@@ -5110,7 +5279,8 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5110
5279
  process.exit(exitCode);
5111
5280
  }
5112
5281
  console.log("Claude crashed, restarting...");
5113
- shell = pty.spawn("claude", ["continue", "--continue"], {
5282
+ shell = spawn("claude", ["continue", "--continue"], {
5283
+ name: "xterm-color",
5114
5284
  cols: process.stdout.columns - PREFIXLENGTH,
5115
5285
  rows: process.stdout.rows,
5116
5286
  cwd: process.cwd(),
@@ -5146,7 +5316,10 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5146
5316
  shell.resize(columns - PREFIXLENGTH, rows);
5147
5317
  });
5148
5318
  const shellStdio = {
5149
- writable: new WritableStream({ write: (data) => shell.write(data), close: () => {} }),
5319
+ writable: new WritableStream({
5320
+ write: (data) => shell.write(data),
5321
+ close: () => {}
5322
+ }),
5150
5323
  readable: shellOutputStream.readable
5151
5324
  };
5152
5325
  const idleWatcher = createIdleWatcher(async () => {
@@ -5155,19 +5328,18 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
5155
5328
  await exitClaudeCode();
5156
5329
  }
5157
5330
  }, idleTimeout);
5331
+ const confirm = async () => {
5332
+ await sleepms(200);
5333
+ shell.write("\r");
5334
+ };
5158
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) => {
5159
- if (e2.match(/❯ 1. Yes/)) {
5160
- await sleepms(200);
5161
- shell.write("\r");
5162
- }
5163
- }).forEach(async (e2) => {
5164
- if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
5165
- await sleepms(200);
5166
- shell.write("\r");
5167
- }
5168
- }).forEach((e2) => {
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();
5169
5340
  if (e2.match(/No conversation found to continue/)) {
5170
5341
  errorNoConversation = true;
5342
+ return;
5171
5343
  }
5172
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));
5173
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...");
@@ -4873,7 +5041,8 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4873
5041
  let errorNoConversation = false;
4874
5042
  const shellOutputStream = new TransformStream;
4875
5043
  const outputWriter = shellOutputStream.writable.getWriter();
4876
- let shell = pty.spawn("claude", claudeArgs, {
5044
+ let shell = spawn("claude", claudeArgs, {
5045
+ name: "xterm-color",
4877
5046
  cols: process.stdout.columns - PREFIXLENGTH,
4878
5047
  rows: process.stdout.rows,
4879
5048
  cwd: process.cwd(),
@@ -4890,7 +5059,8 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4890
5059
  process.exit(exitCode);
4891
5060
  }
4892
5061
  console.log("Claude crashed, restarting...");
4893
- shell = pty.spawn("claude", ["continue", "--continue"], {
5062
+ shell = spawn("claude", ["continue", "--continue"], {
5063
+ name: "xterm-color",
4894
5064
  cols: process.stdout.columns - PREFIXLENGTH,
4895
5065
  rows: process.stdout.rows,
4896
5066
  cwd: process.cwd(),
@@ -4926,7 +5096,10 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4926
5096
  shell.resize(columns - PREFIXLENGTH, rows);
4927
5097
  });
4928
5098
  const shellStdio = {
4929
- writable: new WritableStream({ write: (data) => shell.write(data), close: () => {} }),
5099
+ writable: new WritableStream({
5100
+ write: (data) => shell.write(data),
5101
+ close: () => {}
5102
+ }),
4930
5103
  readable: shellOutputStream.readable
4931
5104
  };
4932
5105
  const idleWatcher = createIdleWatcher(async () => {
@@ -4935,19 +5108,18 @@ async function claudeYes({ continueOnCrash, exitOnIdle, claudeArgs = [] } = {})
4935
5108
  await exitClaudeCode();
4936
5109
  }
4937
5110
  }, idleTimeout);
5111
+ const confirm = async () => {
5112
+ await sleepms(200);
5113
+ shell.write("\r");
5114
+ };
4938
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) => {
4939
- if (e2.match(/❯ 1. Yes/)) {
4940
- await sleepms(200);
4941
- shell.write("\r");
4942
- }
4943
- }).forEach(async (e2) => {
4944
- if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
4945
- await sleepms(200);
4946
- shell.write("\r");
4947
- }
4948
- }).forEach((e2) => {
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();
4949
5120
  if (e2.match(/No conversation found to continue/)) {
4950
5121
  errorNoConversation = true;
5122
+ return;
4951
5123
  }
4952
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));
4953
5125
  }
package/index.ts CHANGED
@@ -1,144 +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
- let errorNoConversation = false; // match 'No conversation found to continue'
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 && exitCode !== 0) {
55
- if (errorNoConversation) {
56
- console.log('Claude crashed with "No conversation found to continue", exiting...');
57
- void process.exit(exitCode);
58
- }
59
- console.log('Claude crashed, restarting...');
60
- shell = pty.spawn('claude', ['continue', '--continue'], {
61
- cols: process.stdout.columns - PREFIXLENGTH,
62
- rows: process.stdout.rows,
63
- cwd: process.cwd(),
64
- env: process.env,
65
- });
66
- shell.onData(onData)
67
- shell.onExit(onExit);
68
- return
69
- }
70
- void process.exit(exitCode);
71
- });
72
-
73
- const exitClaudeCode = async () => {
74
- // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input
75
- await sflow(['\r', '/exit', '\r']).forEach(async (e) => {
76
- await sleepms(200)
77
- shell.write(e)
78
- }).run();
79
-
80
- // wait for shell to exit or kill it with a timeout
81
- let exited = false;
82
- await Promise.race([
83
- new Promise<void>((resolve) => shell.onExit(() => { resolve(); exited = true; })), // resolve when shell exits
84
- // if shell doesn't exit in 5 seconds, kill it
85
- new Promise<void>((resolve) => setTimeout(() => {
86
- if (exited) return; // if shell already exited, do nothing
87
- shell.kill(); // kill the shell process if it doesn't exit in time
88
- resolve();
89
- }, 5000)) // 5 seconds timeout
90
- ]);
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();
91
140
  }
92
-
93
- // when current tty resized, resize the pty
94
- process.stdout.on('resize', () => {
95
- const { columns, rows } = process.stdout;
96
- shell.resize(columns - PREFIXLENGTH, rows);
97
- });
98
-
99
- const shellStdio = {
100
- writable: new WritableStream<string>({ write: (data) => shell.write(data), close: () => { } }),
101
- readable: shellOutputStream.readable
102
- };
103
-
104
- const idleWatcher = createIdleWatcher(async () => {
105
- if (exitOnIdle) {
106
- console.log('Claude is idle, exiting...');
107
- await exitClaudeCode()
108
- }
109
- }, idleTimeout);
110
-
111
- await sflow(fromReadable<Buffer>(process.stdin))
112
- .map((buffer) => buffer.toString())
113
- // .forEach(e => appendFile('.cache/io.log', "input |" + JSON.stringify(e) + '\n')) // for debugging
114
- .by(shellStdio)
115
- .forkTo(e => e
116
- .map(e => removeControlCharacters(e as string))
117
- .map(e => e.replaceAll('\r', '')) // remove carriage return
118
- .forEach(async e => {
119
- if (e.match(/❯ 1. Yes/)) {
120
- await sleepms(200)
121
- shell.write("\r")
122
- }
123
- })
124
- .forEach(async e => {
125
- if (e.match(/❯ 1. Dark mode✔|Press Enter to continue…/)) {
126
- await sleepms(200)
127
- shell.write("\r")
128
- }
129
- })
130
- .forEach(e => {
131
- if (e.match(/No conversation found to continue/)) {
132
- errorNoConversation = true; // set flag to true if error message is found
133
- }
134
- })
135
- // .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
136
- .run()
137
- )
138
- .replaceAll(/.*(?:\r\n?|\r?\n)/g, (line) => prefix + line) // add prefix
139
- .forEach(() => idleWatcher.ping()) // ping the idle watcher on output for last active time to keep track of claude status
140
- .map(e => !process.stdout.isTTY ? removeControlCharacters(e) : (e)) // remove control characters if output is not a TTY
141
- .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));
142
170
  }
143
171
 
144
172
  export { removeControlCharacters };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.10.1",
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",