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 +6 -6
- package/createIdleWatcher.spec.ts +2 -2
- package/dist/cli.js +188 -16
- package/dist/index.js +188 -16
- package/index.ts +154 -126
- package/package.json +2 -2
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
|
|
26
|
+
npm install claude-yes -g
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## Usage
|
|
30
30
|
|
|
31
|
-
|
|
32
31
|
```bash
|
|
33
|
-
yes
|
|
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
|
|
38
|
-
bunx yes
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
5050
|
-
import
|
|
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({
|
|
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 =
|
|
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 =
|
|
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({
|
|
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
|
|
5161
|
-
|
|
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.
|
|
4830
|
-
import
|
|
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({
|
|
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 =
|
|
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 =
|
|
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({
|
|
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
|
|
4941
|
-
|
|
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 "
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
.
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
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
|
-
"
|
|
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",
|