claude-yes 1.10.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/LICENSE +21 -0
- package/README.md +77 -0
- package/cli.test.ts +71 -0
- package/cli.ts +47 -0
- package/createIdleWatcher.spec.ts +41 -0
- package/createIdleWatcher.ts +18 -0
- package/dist/cli.js +6053 -0
- package/dist/index.js +4949 -0
- package/index.ts +136 -0
- package/package.json +73 -0
- package/removeControlCharacters.ts +4 -0
- package/sleep.ts +3 -0
- package/utils.ts +3 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 snomiao
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Yes! Claude
|
|
2
|
+
|
|
3
|
+
A wrapper tool that automates interactions with the Claude CLI by automatically handling common prompts and responses.
|
|
4
|
+
|
|
5
|
+
⚠️ **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.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Same as `claude` command
|
|
10
|
+
- Automatically responds to common prompts like "Yes, proceed" and "Yes"
|
|
11
|
+
- So, this will Let claude run until your task done, and wait for your next prompt.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
First, install Claude Code globally:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @anthropic-ai/claude-code
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Learn more about Claude Code: https://www.anthropic.com/claude-code
|
|
22
|
+
|
|
23
|
+
Then install this project:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install yes-claude -g
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yes-claude [command] [prompts] [--exit-on-idle]
|
|
34
|
+
# works exactly same as `claude` command, and automatically says "Yes" to all yes/no prompts
|
|
35
|
+
|
|
36
|
+
# e.g.
|
|
37
|
+
yes-claude "run all tests and commit current changes"
|
|
38
|
+
bunx yes-claude "Solve TODO.md"
|
|
39
|
+
|
|
40
|
+
# Auto-exit when Claude becomes idle (useful for automation)
|
|
41
|
+
yes-claude "run all tests and commit current changes" --exit-on-idle
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The tool will:
|
|
46
|
+
1. run Claude Code
|
|
47
|
+
2. Whenever claude stucked on yes/no prompts, Automatically say YES, YES, YES, YES, YES to claude
|
|
48
|
+
3. When using `--exit-on-idle` flag, automatically exit when Claude becomes idle for 3 seconds (useful for automation scripts)
|
|
49
|
+
|
|
50
|
+
## Options
|
|
51
|
+
|
|
52
|
+
- `--exit-on-idle`: Automatically exit when Claude becomes idle for 3 seconds. Useful for automation scripts where you want the process to terminate when Claude finishes its work.
|
|
53
|
+
|
|
54
|
+
## Implementation
|
|
55
|
+
|
|
56
|
+
The tool simply mirrors the terminal use node-pty and looks for "❯ 1. Yes" patterns to automatically respond with "\r" to proceed with Claude's prompts.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
❯ 1. Yes
|
|
60
|
+
2. No
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The tool will automatically send "\r" when it detects this pattern.
|
|
64
|
+
|
|
65
|
+
## Dependencies
|
|
66
|
+
|
|
67
|
+
- `node-pty` - For spawning and managing the Claude CLI process
|
|
68
|
+
- `sflow` - For stream processing and data flow management
|
|
69
|
+
- `from-node-stream` - For converting Node.js streams to web streams
|
|
70
|
+
|
|
71
|
+
## Inspiration
|
|
72
|
+
|
|
73
|
+
This project was inspired by: [Claude Code full auto while I sleep : r/ClaudeAI](https://www.reddit.com/r/ClaudeAI/comments/1klk6aw/claude_code_full_auto_while_i_sleep/)
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/cli.test.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execaCommand } from 'execa';
|
|
2
|
+
import { fromStdio } from "from-node-stream";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { readFile, unlink } from 'node:fs/promises';
|
|
6
|
+
import sflow from "sflow";
|
|
7
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
8
|
+
import { createIdleWatcher } from "./createIdleWatcher";
|
|
9
|
+
import { sleepms } from './utils';
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
await execaCommand(`bun run build`)
|
|
13
|
+
.then(() => console.log('Build successful')
|
|
14
|
+
)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe('CLI Tests', () => {
|
|
18
|
+
it('Write file with auto bypass permission prompt', async () => {
|
|
19
|
+
const flagFile = './.cache/flag.json';
|
|
20
|
+
// clean
|
|
21
|
+
await unlink(flagFile).catch(() => { });
|
|
22
|
+
|
|
23
|
+
const p = exec(`node dist/cli.js --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json"`);
|
|
24
|
+
const tr = new TransformStream<string, string>()
|
|
25
|
+
const w = tr.writable.getWriter();
|
|
26
|
+
|
|
27
|
+
const exit = async () => await sflow(['\r', '/exit', '\r', '\r']).forEach(async (e) => {
|
|
28
|
+
await sleepms(200)
|
|
29
|
+
await w.write(e)
|
|
30
|
+
}).run();
|
|
31
|
+
|
|
32
|
+
// ping function to exit claude when idle
|
|
33
|
+
|
|
34
|
+
const { ping } = createIdleWatcher(() => exit(), 3000);
|
|
35
|
+
|
|
36
|
+
const output = (await sflow(tr.readable).by(fromStdio(p)).log()
|
|
37
|
+
.forEach(() => ping())
|
|
38
|
+
.text())
|
|
39
|
+
|
|
40
|
+
// expect the file exists
|
|
41
|
+
expect(existsSync(flagFile)).toBe(true);
|
|
42
|
+
// expect the output contains the file path
|
|
43
|
+
expect(output).toContain(flagFile);
|
|
44
|
+
|
|
45
|
+
// expect the file content to be 'on'
|
|
46
|
+
expect(await new Response(await readFile(flagFile)).json()).toEqual({ on: 1 });
|
|
47
|
+
|
|
48
|
+
expect(p.exitCode).toBe(0); // expect the process to exit successfully
|
|
49
|
+
|
|
50
|
+
// 30 seconds timeout for this test, it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
|
|
51
|
+
}, 30e3);
|
|
52
|
+
|
|
53
|
+
it.skip('CLI --exit-on-idle flag with default timeout', async () => {
|
|
54
|
+
const p = exec(`node dist/cli.js "echo hello" --exit-on-idle`);
|
|
55
|
+
const tr = new TransformStream<string, string>()
|
|
56
|
+
const output = (await sflow(tr.readable).by(fromStdio(p)).log().text())
|
|
57
|
+
expect(output).toContain('hello');
|
|
58
|
+
await sleepms(1000); // wait for process exit
|
|
59
|
+
expect(p.exitCode).toBe(0);
|
|
60
|
+
}, 30e3);
|
|
61
|
+
|
|
62
|
+
it('CLI --exit-on-idle flag with custom timeout', async () => {
|
|
63
|
+
const p = exec(`node dist/cli.js --exit-on-idle=1s "echo hello"`);
|
|
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);
|
|
70
|
+
})
|
|
71
|
+
|
package/cli.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import ms from "enhanced-ms";
|
|
3
|
+
import minimist from "minimist";
|
|
4
|
+
import claudeYes from ".";
|
|
5
|
+
|
|
6
|
+
// cli entry point
|
|
7
|
+
const args = minimist(process.argv.slice(2), {
|
|
8
|
+
string: ['exit-on-idle'],
|
|
9
|
+
boolean: ['continue-on-crash'],
|
|
10
|
+
// boolean: ['exit-on-idle'],
|
|
11
|
+
default: {
|
|
12
|
+
'exit-on-idle': '60s',
|
|
13
|
+
'continue-on-crash': true,
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const { 'exit-on-idle': exitOnIdleArg, 'continue-on-crash': continueOnCrashArg, ...rest } = args;
|
|
18
|
+
const claudeArgs = Object.entries(rest).flatMap(([key, value]) => {
|
|
19
|
+
if (key === '_') return value as string[];
|
|
20
|
+
if (typeof value === 'boolean') return value ? [`--${key}`] : [];
|
|
21
|
+
return [`--${key}`, String(value)];
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
let exitOnIdle: boolean | number | undefined;
|
|
26
|
+
if (typeof exitOnIdleArg === 'string') {
|
|
27
|
+
if (exitOnIdleArg === '') {
|
|
28
|
+
exitOnIdle = true; // default timeout will be used
|
|
29
|
+
} else {
|
|
30
|
+
exitOnIdle = ms(exitOnIdleArg); // parse duration string like "5s", "30s", "1m"
|
|
31
|
+
}
|
|
32
|
+
} else {
|
|
33
|
+
exitOnIdle = undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
// console.debug('Parsed args:', {
|
|
38
|
+
// exitOnIdle,
|
|
39
|
+
// continueOnCrash: continueOnCrashArg,
|
|
40
|
+
// claudeArgs,
|
|
41
|
+
// });
|
|
42
|
+
|
|
43
|
+
await claudeYes({
|
|
44
|
+
exitOnIdle,
|
|
45
|
+
claudeArgs,
|
|
46
|
+
continueOnCrash: continueOnCrashArg,
|
|
47
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, it } from 'vitest';
|
|
2
|
+
import { createIdleWatcher } from "./createIdleWatcher";
|
|
3
|
+
import { sleepms } from "./utils";
|
|
4
|
+
|
|
5
|
+
it('createIdleWatcher should trigger onIdle after timeout', async () => {
|
|
6
|
+
let idleTriggered = false;
|
|
7
|
+
const watcher = createIdleWatcher(() => {
|
|
8
|
+
idleTriggered = true;
|
|
9
|
+
}, 100);
|
|
10
|
+
|
|
11
|
+
watcher.ping();
|
|
12
|
+
await sleepms(150);
|
|
13
|
+
expect(idleTriggered).toBe(true);
|
|
14
|
+
}, 1000);
|
|
15
|
+
|
|
16
|
+
it.concurrent('createIdleWatcher should reset timeout on ping', async () => {
|
|
17
|
+
let idleTriggered = false;
|
|
18
|
+
const watcher = createIdleWatcher(() => {
|
|
19
|
+
idleTriggered = true;
|
|
20
|
+
}, 100);
|
|
21
|
+
|
|
22
|
+
watcher.ping();
|
|
23
|
+
await sleepms(50);
|
|
24
|
+
watcher.ping();
|
|
25
|
+
await sleepms(50);
|
|
26
|
+
expect(idleTriggered).toBe(false);
|
|
27
|
+
await sleepms(100);
|
|
28
|
+
expect(idleTriggered).toBe(true);
|
|
29
|
+
}, 1000);
|
|
30
|
+
|
|
31
|
+
it.concurrent('createIdleWatcher should update lastActiveTime on ping', async () => {
|
|
32
|
+
const watcher = createIdleWatcher(() => { }, 1000);
|
|
33
|
+
|
|
34
|
+
const initialTime = watcher.getLastActiveTime();
|
|
35
|
+
await sleepms(50);
|
|
36
|
+
watcher.ping();
|
|
37
|
+
const updatedTime = watcher.getLastActiveTime();
|
|
38
|
+
|
|
39
|
+
expect(updatedTime.getTime()).toBeGreaterThan(initialTime.getTime());
|
|
40
|
+
}, 1000);
|
|
41
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
export function createIdleWatcher(onIdle: () => void, idleTimeout: number): { ping: () => void; getLastActiveTime: () => Date; } {
|
|
3
|
+
let lastActiveTime = new Date();
|
|
4
|
+
let idleTimeoutId: NodeJS.Timeout | null = null;
|
|
5
|
+
return {
|
|
6
|
+
ping: () => {
|
|
7
|
+
if (idleTimeoutId) {
|
|
8
|
+
clearTimeout(idleTimeoutId);
|
|
9
|
+
}
|
|
10
|
+
idleTimeoutId = setTimeout(() => {
|
|
11
|
+
clearTimeout(idleTimeoutId!);
|
|
12
|
+
onIdle();
|
|
13
|
+
}, idleTimeout);
|
|
14
|
+
lastActiveTime = new Date();
|
|
15
|
+
},
|
|
16
|
+
getLastActiveTime: () => lastActiveTime
|
|
17
|
+
};
|
|
18
|
+
}
|