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