claude-yes 0.0.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 +82 -0
- package/ReadyManager.ts +18 -0
- package/cli-idle.spec.ts +17 -0
- package/cli.test.ts +63 -0
- package/cli.ts +86 -0
- package/dist/claude-yes.js +332 -0
- package/dist/cli-claude.js +11339 -0
- package/dist/cli-codex.js +11339 -0
- package/dist/cli-cursor.js +11339 -0
- package/dist/cli-gemini.js +11339 -0
- package/dist/cli.js +332 -0
- package/dist/cli.js.map +11 -0
- package/dist/codex-yes.js +332 -0
- package/dist/copilot-yes.js +332 -0
- package/dist/cursor-yes.js +332 -0
- package/dist/gemini-yes.js +332 -0
- package/dist/index.js +285 -0
- package/dist/index.js.map +13 -0
- package/idleWaiter.ts +31 -0
- package/index.ts +376 -0
- package/package.json +90 -0
- package/postbuild.ts +6 -0
- package/removeControlCharacters.ts +7 -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,82 @@
|
|
|
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 keep run until your task done, and wait for your next prompt.
|
|
12
|
+
- You can still Queue More Prompts or Cancel executing task by `ESC` or `Ctrl+C`
|
|
13
|
+
|
|
14
|
+
## Prerequirements
|
|
15
|
+
|
|
16
|
+
First, install Claude Code globally:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @anthropic-ai/claude-code
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Learn more about Claude Code: https://www.anthropic.com/claude-code
|
|
23
|
+
|
|
24
|
+
Then install this project:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install claude-yes -g
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### claude-yes cli
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
claude-yes [--exit-on-idle=60s] [claude-command] [claude-prompts]
|
|
36
|
+
# works exactly same as `claude` command, and automatically says "Yes" to all yes/no prompts
|
|
37
|
+
|
|
38
|
+
# e.g.
|
|
39
|
+
claude-yes "run all tests and commit current changes"
|
|
40
|
+
bunx claude-yes "Solve TODO.md"
|
|
41
|
+
|
|
42
|
+
# Auto-exit when Claude becomes idle (useful for automation scripts)
|
|
43
|
+
claude-yes --exit-on-idle=60s "run all tests and commit current changes"
|
|
44
|
+
|
|
45
|
+
# Alternative: use with claude-code-execute
|
|
46
|
+
claude-code-execute claude-yes "your task here"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The tool will:
|
|
50
|
+
|
|
51
|
+
1. run Claude Code
|
|
52
|
+
2. Whenever claude stucked on yes/no prompts, Automatically say YES, YES, YES, YES, YES to claude
|
|
53
|
+
3. When using `--exit-on-idle` flag, automatically exit when Claude becomes idle for 3 seconds (useful for automation scripts)
|
|
54
|
+
|
|
55
|
+
<!-- TODO: add usage As lib: call await claudeYes() and it returns render result -->
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
- `--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.
|
|
60
|
+
|
|
61
|
+
## Implementation
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
❯ 1. Yes
|
|
67
|
+
2. No
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The tool will automatically send "\r" when it detects this pattern.
|
|
71
|
+
|
|
72
|
+
## Dependencies
|
|
73
|
+
|
|
74
|
+
- `node-pty` - For spawning and managing the Claude CLI process
|
|
75
|
+
|
|
76
|
+
## Inspiration
|
|
77
|
+
|
|
78
|
+
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/)
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/ReadyManager.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export class ReadyManager {
|
|
2
|
+
private isReady = false;
|
|
3
|
+
private readyQueue: (() => void)[] = [];
|
|
4
|
+
wait() {
|
|
5
|
+
return new Promise<void>((resolve) => {
|
|
6
|
+
if (this.isReady) return resolve();
|
|
7
|
+
this.readyQueue.push(resolve);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
unready() {
|
|
11
|
+
this.isReady = false;
|
|
12
|
+
}
|
|
13
|
+
ready() {
|
|
14
|
+
this.isReady = true;
|
|
15
|
+
if (!this.readyQueue.length) return; // check len for performance
|
|
16
|
+
this.readyQueue.splice(0).map((resolve) => resolve());
|
|
17
|
+
}
|
|
18
|
+
}
|
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
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readFile, unlink } from "node:fs/promises";
|
|
4
|
+
import { execaCommand } from "execa";
|
|
5
|
+
import { fromStdio } from "from-node-stream";
|
|
6
|
+
import sflow from "sflow";
|
|
7
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
8
|
+
import { IdleWaiter } from "./idleWaiter";
|
|
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() {
|
|
15
|
+
await unlink(flagFile).catch(() => {});
|
|
16
|
+
await unlink("./cli-rendered.log").catch(() => {});
|
|
17
|
+
}
|
|
18
|
+
|
|
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));
|
|
23
|
+
|
|
24
|
+
const tr = new TransformStream<string, string>();
|
|
25
|
+
const w = tr.writable.getWriter();
|
|
26
|
+
|
|
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();
|
|
34
|
+
|
|
35
|
+
// ping function to exit claude when idle
|
|
36
|
+
|
|
37
|
+
const idleWaiter = new IdleWaiter()
|
|
38
|
+
idleWaiter.wait(3000).then(() => exit());
|
|
39
|
+
|
|
40
|
+
const output = await sflow(tr.readable)
|
|
41
|
+
.by(fromStdio(p))
|
|
42
|
+
.log()
|
|
43
|
+
.forEach(() => idleWaiter.ping())
|
|
44
|
+
.text();
|
|
45
|
+
|
|
46
|
+
// expect the file exists
|
|
47
|
+
expect(existsSync(flagFile)).toBe(true);
|
|
48
|
+
// expect the output contains the file path
|
|
49
|
+
expect(output).toContain(flagFile);
|
|
50
|
+
|
|
51
|
+
// expect the file content to be 'on'
|
|
52
|
+
expect(await new Response(await readFile(flagFile)).json()).toEqual({
|
|
53
|
+
on: 1,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(await pExitCode).toBe(0); // expect the process to exit successfully
|
|
57
|
+
expect(await readFile("./cli-rendered.log", "utf8")).toBeTruthy();
|
|
58
|
+
|
|
59
|
+
// clean
|
|
60
|
+
await cleanup();
|
|
61
|
+
|
|
62
|
+
// it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
|
|
63
|
+
}, 30e3);
|
package/cli.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import enhancedMs from 'enhanced-ms';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import claudeYes from '.';
|
|
6
|
+
|
|
7
|
+
// cli entry point
|
|
8
|
+
const argv = yargs(hideBin(process.argv))
|
|
9
|
+
.usage('Usage: $0 [options] [claude args] [--] [prompts...]')
|
|
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
|
+
)
|
|
14
|
+
.option('continue-on-crash', {
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
default: true,
|
|
17
|
+
description:
|
|
18
|
+
'spawn Claude with --continue if it crashes, only works for claude',
|
|
19
|
+
})
|
|
20
|
+
.option('log-file', {
|
|
21
|
+
type: 'string',
|
|
22
|
+
description: 'Log file to write to',
|
|
23
|
+
})
|
|
24
|
+
.option('cli', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description:
|
|
27
|
+
'Claude CLI command, e.g. "claude,gemini,codex,cursor,copilot", default is "claude"',
|
|
28
|
+
})
|
|
29
|
+
.option('prompt', {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Prompt to send to Claude',
|
|
32
|
+
alias: 'p',
|
|
33
|
+
})
|
|
34
|
+
.option('verbose', {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
description: 'Enable verbose logging',
|
|
37
|
+
default: false,
|
|
38
|
+
})
|
|
39
|
+
.option('exit-on-idle', {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Exit after a period of inactivity, e.g., "5s" or "1m"',
|
|
42
|
+
})
|
|
43
|
+
.parserConfiguration({
|
|
44
|
+
'unknown-options-as-args': true,
|
|
45
|
+
'halt-at-non-option': true,
|
|
46
|
+
})
|
|
47
|
+
.parseSync();
|
|
48
|
+
|
|
49
|
+
// detect cli name for cli, while package.json have multiple bin link: {"claude-yes": "cli.js", "codex-yes": "cli.js", "gemini-yes": "cli.js"}
|
|
50
|
+
if (!argv.cli) {
|
|
51
|
+
const cliName = process.argv[1]?.split('/').pop()?.split('-')[0];
|
|
52
|
+
argv.cli = cliName || 'claude';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Support: everything after a literal `--` is a prompt string. Example:
|
|
56
|
+
// claude-yes --exit-on-idle=30s -- "help me refactor this"
|
|
57
|
+
// In that example the prompt will be `help me refactor this` and won't be
|
|
58
|
+
// passed as args to the underlying CLI binary.
|
|
59
|
+
const rawArgs = process.argv.slice(2);
|
|
60
|
+
const dashIndex = rawArgs.indexOf('--');
|
|
61
|
+
let promptFromDash: string | undefined = undefined;
|
|
62
|
+
let cliArgsForSpawn: string[] = [];
|
|
63
|
+
if (dashIndex !== -1) {
|
|
64
|
+
// join everything after `--` into a single prompt string
|
|
65
|
+
const after = rawArgs.slice(dashIndex + 1);
|
|
66
|
+
promptFromDash = after.join(' ');
|
|
67
|
+
// use everything before `--` as the cli args
|
|
68
|
+
cliArgsForSpawn = rawArgs.slice(0, dashIndex).map(String);
|
|
69
|
+
} else {
|
|
70
|
+
// fallback to yargs parsed positional args when `--` is not used
|
|
71
|
+
cliArgsForSpawn = argv._.map((e) => String(e));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.clear();
|
|
75
|
+
const { exitCode, logs } = await claudeYes({
|
|
76
|
+
cli: argv.cli,
|
|
77
|
+
// prefer explicit --prompt / -p; otherwise use the text after `--` if present
|
|
78
|
+
prompt: argv.prompt || promptFromDash,
|
|
79
|
+
exitOnIdle: argv.exitOnIdle ? enhancedMs(argv.exitOnIdle) : undefined,
|
|
80
|
+
cliArgs: cliArgsForSpawn,
|
|
81
|
+
continueOnCrash: argv.continueOnCrash,
|
|
82
|
+
logFile: argv.logFile,
|
|
83
|
+
verbose: argv.verbose,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
process.exit(exitCode ?? 1);
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
// cli.ts
|
|
22
|
+
import enhancedMs from "enhanced-ms";
|
|
23
|
+
import yargs from "yargs";
|
|
24
|
+
import { hideBin } from "yargs/helpers";
|
|
25
|
+
|
|
26
|
+
// dist/index.js
|
|
27
|
+
import { fromReadable, fromWritable } from "from-node-stream";
|
|
28
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
29
|
+
import path from "path";
|
|
30
|
+
import DIE from "phpdie";
|
|
31
|
+
import sflow from "sflow";
|
|
32
|
+
import { TerminalTextRender } from "terminal-render";
|
|
33
|
+
class IdleWaiter {
|
|
34
|
+
lastActivityTime = Date.now();
|
|
35
|
+
checkInterval = 100;
|
|
36
|
+
constructor() {
|
|
37
|
+
this.ping();
|
|
38
|
+
}
|
|
39
|
+
ping() {
|
|
40
|
+
this.lastActivityTime = Date.now();
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
async wait(ms) {
|
|
44
|
+
while (this.lastActivityTime >= Date.now() - ms)
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class ReadyManager {
|
|
50
|
+
isReady = false;
|
|
51
|
+
readyQueue = [];
|
|
52
|
+
wait() {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
if (this.isReady)
|
|
55
|
+
return resolve();
|
|
56
|
+
this.readyQueue.push(resolve);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
unready() {
|
|
60
|
+
this.isReady = false;
|
|
61
|
+
}
|
|
62
|
+
ready() {
|
|
63
|
+
this.isReady = true;
|
|
64
|
+
if (!this.readyQueue.length)
|
|
65
|
+
return;
|
|
66
|
+
this.readyQueue.splice(0).map((resolve) => resolve());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function removeControlCharacters(str) {
|
|
70
|
+
return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
71
|
+
}
|
|
72
|
+
var CLI_CONFIGURES = {
|
|
73
|
+
claude: {
|
|
74
|
+
ready: "",
|
|
75
|
+
enter: []
|
|
76
|
+
},
|
|
77
|
+
gemini: {
|
|
78
|
+
ready: "",
|
|
79
|
+
enter: []
|
|
80
|
+
},
|
|
81
|
+
codex: {
|
|
82
|
+
ready: "",
|
|
83
|
+
enter: [],
|
|
84
|
+
ensureArgs: (args) => {
|
|
85
|
+
if (!args.includes("--search"))
|
|
86
|
+
return ["--search", ...args];
|
|
87
|
+
return args;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
copilot: {},
|
|
91
|
+
cursor: {
|
|
92
|
+
binary: "cursor-agent"
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
async function claudeYes({
|
|
96
|
+
cli = "claude",
|
|
97
|
+
cliArgs = [],
|
|
98
|
+
prompt,
|
|
99
|
+
continueOnCrash,
|
|
100
|
+
cwd,
|
|
101
|
+
env,
|
|
102
|
+
exitOnIdle,
|
|
103
|
+
logFile,
|
|
104
|
+
removeControlCharactersFromStdout = false,
|
|
105
|
+
verbose = false
|
|
106
|
+
} = {}) {
|
|
107
|
+
const continueArgs = {
|
|
108
|
+
codex: "resume --last".split(" "),
|
|
109
|
+
claude: "--continue".split(" "),
|
|
110
|
+
gemini: []
|
|
111
|
+
};
|
|
112
|
+
process.stdin.setRawMode?.(true);
|
|
113
|
+
let isFatal = false;
|
|
114
|
+
const stdinReady = new ReadyManager;
|
|
115
|
+
const shellOutputStream = new TransformStream;
|
|
116
|
+
const outputWriter = shellOutputStream.writable.getWriter();
|
|
117
|
+
const pty = await import("node-pty").catch(async () => await import("bun-pty")).catch(async () => DIE("Please install node-pty or bun-pty, run this: bun install bun-pty"));
|
|
118
|
+
const getPtyOptions = () => ({
|
|
119
|
+
name: "xterm-color",
|
|
120
|
+
...getTerminalDimensions(),
|
|
121
|
+
cwd: cwd ?? process.cwd(),
|
|
122
|
+
env: env ?? process.env
|
|
123
|
+
});
|
|
124
|
+
const cliConf = CLI_CONFIGURES[cli] || {};
|
|
125
|
+
cliArgs = cliConf.ensureArgs?.(cliArgs) ?? cliArgs;
|
|
126
|
+
const cliCommand = cliConf?.binary || cli;
|
|
127
|
+
let shell = pty.spawn(cliCommand, cliArgs, getPtyOptions());
|
|
128
|
+
const pendingExitCode = Promise.withResolvers();
|
|
129
|
+
let pendingExitCodeValue = null;
|
|
130
|
+
async function onData(data) {
|
|
131
|
+
await outputWriter.write(data);
|
|
132
|
+
}
|
|
133
|
+
shell.onData(onData);
|
|
134
|
+
shell.onExit(function onExit({ exitCode: exitCode2 }) {
|
|
135
|
+
stdinReady.unready();
|
|
136
|
+
const agentCrashed = exitCode2 !== 0;
|
|
137
|
+
const continueArg = continueArgs[cli];
|
|
138
|
+
if (agentCrashed && continueOnCrash && continueArg) {
|
|
139
|
+
if (!continueArg) {
|
|
140
|
+
return console.warn(`continueOnCrash is only supported for ${Object.keys(continueArgs).join(", ")} currently, not ${cli}`);
|
|
141
|
+
}
|
|
142
|
+
if (isFatal) {
|
|
143
|
+
console.log(`${cli} crashed with "No conversation found to continue", exiting...`);
|
|
144
|
+
return pendingExitCode.resolve(pendingExitCodeValue = exitCode2);
|
|
145
|
+
}
|
|
146
|
+
console.log(`${cli} crashed, restarting...`);
|
|
147
|
+
shell = pty.spawn(cli, continueArg, getPtyOptions());
|
|
148
|
+
shell.onData(onData);
|
|
149
|
+
shell.onExit(onExit);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return pendingExitCode.resolve(pendingExitCodeValue = exitCode2);
|
|
153
|
+
});
|
|
154
|
+
process.stdout.on("resize", () => {
|
|
155
|
+
const { cols, rows } = getTerminalDimensions();
|
|
156
|
+
shell.resize(cols, rows);
|
|
157
|
+
});
|
|
158
|
+
const terminalRender = new TerminalTextRender;
|
|
159
|
+
const isStillWorkingQ = () => terminalRender.render().replace(/\s+/g, " ").match(/esc to interrupt|to run in background/);
|
|
160
|
+
const idleWaiter = new IdleWaiter;
|
|
161
|
+
if (exitOnIdle)
|
|
162
|
+
idleWaiter.wait(exitOnIdle).then(async () => {
|
|
163
|
+
if (isStillWorkingQ()) {
|
|
164
|
+
console.log("[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet");
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
console.log("[${cli}-yes] ${cli} is idle, exiting...");
|
|
168
|
+
await exitAgent();
|
|
169
|
+
});
|
|
170
|
+
sflow(fromReadable(process.stdin)).map((buffer) => buffer.toString()).map((e) => e.replaceAll("\x1A", "")).by({
|
|
171
|
+
writable: new WritableStream({
|
|
172
|
+
write: async (data) => {
|
|
173
|
+
await idleWaiter.wait(200);
|
|
174
|
+
shell.write(data);
|
|
175
|
+
}
|
|
176
|
+
}),
|
|
177
|
+
readable: shellOutputStream.readable
|
|
178
|
+
}).forEach(() => idleWaiter.ping()).forEach((text) => terminalRender.write(text)).forEach((txt) => {
|
|
179
|
+
if (process.stdin.isTTY)
|
|
180
|
+
return;
|
|
181
|
+
if (txt.includes("\x1B[6n"))
|
|
182
|
+
return;
|
|
183
|
+
const rendered = terminalRender.render();
|
|
184
|
+
const row = rendered.split(`
|
|
185
|
+
`).length + 1;
|
|
186
|
+
const col = (rendered.split(`
|
|
187
|
+
`).slice(-1)[0]?.length || 0) + 1;
|
|
188
|
+
}).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).lines({ EOL: "NONE" }).forEach(async (e2) => {
|
|
189
|
+
if (cli !== "claude")
|
|
190
|
+
return;
|
|
191
|
+
if (e2.match(/^> /))
|
|
192
|
+
return stdinReady.ready();
|
|
193
|
+
if (e2.match(/❯ 1. Yes/))
|
|
194
|
+
return await sendEnter();
|
|
195
|
+
if (e2.match(/❯ 1. Dark mode✔|Press Enter to continue…/))
|
|
196
|
+
return await sendEnter();
|
|
197
|
+
if (e2.match(/No conversation found to continue/))
|
|
198
|
+
return isFatal = true;
|
|
199
|
+
if (e2.match(/⎿ {2}Claude usage limit reached./))
|
|
200
|
+
return isFatal = true;
|
|
201
|
+
}).forEach(async (e2, i) => {
|
|
202
|
+
if (cli !== "gemini")
|
|
203
|
+
return;
|
|
204
|
+
if (e2.match(/ > {3}Type your message/) && i > 80) {
|
|
205
|
+
return stdinReady.ready();
|
|
206
|
+
}
|
|
207
|
+
if (e2.match(/│ ● 1. Yes, allow once/))
|
|
208
|
+
return await sendEnter();
|
|
209
|
+
}).forEach(async (e2) => {
|
|
210
|
+
if (cli === "codex") {
|
|
211
|
+
if (e2.match(/ > 1. Approve/))
|
|
212
|
+
return await sendEnter();
|
|
213
|
+
if (e2.match(/Error: The cursor position could not be read within/))
|
|
214
|
+
return isFatal = true;
|
|
215
|
+
if (e2.match(/> 1. Yes, allow Codex to work in this folder/))
|
|
216
|
+
return await sendEnter();
|
|
217
|
+
if (e2.match(/⏎ send/))
|
|
218
|
+
return stdinReady.ready();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (cli === "cursor") {
|
|
222
|
+
if (e2.match(/\/ commands/))
|
|
223
|
+
return stdinReady.ready();
|
|
224
|
+
if (e2.match(/→ Run \(once\) \(y\) \(enter\)/))
|
|
225
|
+
return await sendEnter();
|
|
226
|
+
if (e2.match(/▶ \[a\] Trust this workspace/))
|
|
227
|
+
return await sendEnter();
|
|
228
|
+
}
|
|
229
|
+
}).run()).map((e) => removeControlCharactersFromStdout ? removeControlCharacters(e) : e).to(fromWritable(process.stdout)).then(() => null);
|
|
230
|
+
if (prompt)
|
|
231
|
+
(async () => {
|
|
232
|
+
await sendMessage(prompt);
|
|
233
|
+
})();
|
|
234
|
+
const exitCode = await pendingExitCode.promise;
|
|
235
|
+
console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
|
|
236
|
+
if (logFile) {
|
|
237
|
+
verbose && console.log(`[${cli}-yes] Writing rendered logs to ${logFile}`);
|
|
238
|
+
const logFilePath = path.resolve(logFile);
|
|
239
|
+
await mkdir(path.dirname(logFilePath), { recursive: true }).catch(() => null);
|
|
240
|
+
await writeFile(logFilePath, terminalRender.render());
|
|
241
|
+
}
|
|
242
|
+
return { exitCode, logs: terminalRender.render() };
|
|
243
|
+
async function sendEnter(waitms = 1000) {
|
|
244
|
+
const st = Date.now();
|
|
245
|
+
await idleWaiter.wait(waitms);
|
|
246
|
+
const et = Date.now();
|
|
247
|
+
process.stdout.write(`\ridleWaiter.wait(${waitms}) took ${et - st}ms\r`);
|
|
248
|
+
shell.write("\r");
|
|
249
|
+
}
|
|
250
|
+
async function sendMessage(message) {
|
|
251
|
+
await stdinReady.wait();
|
|
252
|
+
shell.write(message);
|
|
253
|
+
idleWaiter.ping();
|
|
254
|
+
await sendEnter();
|
|
255
|
+
}
|
|
256
|
+
async function exitAgent() {
|
|
257
|
+
continueOnCrash = false;
|
|
258
|
+
await sendMessage("/exit");
|
|
259
|
+
let exited = false;
|
|
260
|
+
await Promise.race([
|
|
261
|
+
pendingExitCode.promise.then(() => exited = true),
|
|
262
|
+
new Promise((resolve) => setTimeout(() => {
|
|
263
|
+
if (exited)
|
|
264
|
+
return;
|
|
265
|
+
shell.kill();
|
|
266
|
+
resolve();
|
|
267
|
+
}, 5000))
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
function getTerminalDimensions() {
|
|
271
|
+
return {
|
|
272
|
+
cols: Math.max(process.stdout.columns, 80),
|
|
273
|
+
rows: process.stdout.rows
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// cli.ts
|
|
279
|
+
var argv = yargs(hideBin(process.argv)).usage("Usage: $0 [options] [claude args] [--] [prompts...]").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("continue-on-crash", {
|
|
280
|
+
type: "boolean",
|
|
281
|
+
default: true,
|
|
282
|
+
description: "spawn Claude with --continue if it crashes, only works for claude"
|
|
283
|
+
}).option("log-file", {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "Log file to write to"
|
|
286
|
+
}).option("cli", {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: 'Claude CLI command, e.g. "claude,gemini,codex,cursor,copilot", default is "claude"'
|
|
289
|
+
}).option("prompt", {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "Prompt to send to Claude",
|
|
292
|
+
alias: "p"
|
|
293
|
+
}).option("verbose", {
|
|
294
|
+
type: "boolean",
|
|
295
|
+
description: "Enable verbose logging",
|
|
296
|
+
default: false
|
|
297
|
+
}).option("exit-on-idle", {
|
|
298
|
+
type: "string",
|
|
299
|
+
description: 'Exit after a period of inactivity, e.g., "5s" or "1m"'
|
|
300
|
+
}).parserConfiguration({
|
|
301
|
+
"unknown-options-as-args": true,
|
|
302
|
+
"halt-at-non-option": true
|
|
303
|
+
}).parseSync();
|
|
304
|
+
if (!argv.cli) {
|
|
305
|
+
const cliName = process.argv[1]?.split("/").pop()?.split("-")[0];
|
|
306
|
+
argv.cli = cliName || "claude";
|
|
307
|
+
}
|
|
308
|
+
var rawArgs = process.argv.slice(2);
|
|
309
|
+
var dashIndex = rawArgs.indexOf("--");
|
|
310
|
+
var promptFromDash = undefined;
|
|
311
|
+
var cliArgsForSpawn = [];
|
|
312
|
+
if (dashIndex !== -1) {
|
|
313
|
+
const after = rawArgs.slice(dashIndex + 1);
|
|
314
|
+
promptFromDash = after.join(" ");
|
|
315
|
+
cliArgsForSpawn = rawArgs.slice(0, dashIndex).map(String);
|
|
316
|
+
} else {
|
|
317
|
+
cliArgsForSpawn = argv._.map((e) => String(e));
|
|
318
|
+
}
|
|
319
|
+
console.clear();
|
|
320
|
+
var { exitCode, logs } = await claudeYes({
|
|
321
|
+
cli: argv.cli,
|
|
322
|
+
prompt: argv.prompt || promptFromDash,
|
|
323
|
+
exitOnIdle: argv.exitOnIdle ? enhancedMs(argv.exitOnIdle) : undefined,
|
|
324
|
+
cliArgs: cliArgsForSpawn,
|
|
325
|
+
continueOnCrash: argv.continueOnCrash,
|
|
326
|
+
logFile: argv.logFile,
|
|
327
|
+
verbose: argv.verbose
|
|
328
|
+
});
|
|
329
|
+
process.exit(exitCode ?? 1);
|
|
330
|
+
|
|
331
|
+
//# debugId=81134D71621755A164756E2164756E21
|
|
332
|
+
//# sourceMappingURL=cli.js.map
|