claude-yes 1.21.0 → 1.22.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 +26 -0
- package/dist/claude-yes.js +23 -19
- package/dist/cli.js +23 -19
- package/dist/cli.js.map +3 -3
- package/dist/codex-yes.js +23 -19
- package/dist/copilot-yes.js +23 -19
- package/dist/cursor-yes.js +23 -19
- package/dist/gemini-yes.js +23 -19
- package/dist/grok-yes.js +342 -0
- package/dist/index.js +23 -19
- package/dist/index.js.map +3 -3
- package/index.ts +39 -36
- package/package.json +14 -13
- package/postbuild.ts +10 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// index.ts
|
|
21
21
|
import { fromReadable, fromWritable } from "from-node-stream";
|
|
22
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
22
|
+
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
23
23
|
import path from "path";
|
|
24
24
|
import DIE from "phpdie";
|
|
25
25
|
import sflow from "sflow";
|
|
@@ -71,7 +71,8 @@ function removeControlCharacters(str) {
|
|
|
71
71
|
var CLI_CONFIGURES = {
|
|
72
72
|
grok: {
|
|
73
73
|
install: "npm install -g @vibe-kit/grok-cli",
|
|
74
|
-
ready: [/^
|
|
74
|
+
ready: [/^ │ ❯ /],
|
|
75
|
+
enter: [/^ 1. Yes/]
|
|
75
76
|
},
|
|
76
77
|
claude: {
|
|
77
78
|
install: "npm install -g @anthropic-ai/claude-code",
|
|
@@ -79,7 +80,7 @@ var CLI_CONFIGURES = {
|
|
|
79
80
|
enter: [/❯ 1. Yes/, /❯ 1. Dark mode✔/, /Press Enter to continue…/],
|
|
80
81
|
fatal: [
|
|
81
82
|
/No conversation found to continue/,
|
|
82
|
-
/⎿
|
|
83
|
+
/⎿ Claude usage limit reached\./
|
|
83
84
|
]
|
|
84
85
|
},
|
|
85
86
|
gemini: {
|
|
@@ -91,7 +92,10 @@ var CLI_CONFIGURES = {
|
|
|
91
92
|
codex: {
|
|
92
93
|
install: "npm install -g @openai/codex-cli",
|
|
93
94
|
ready: [/⏎ send/],
|
|
94
|
-
enter: [
|
|
95
|
+
enter: [
|
|
96
|
+
/\b▌ \> 1\. Approve and run now/,
|
|
97
|
+
/\b\> 1\. Yes, allow Codex to work in this folder/
|
|
98
|
+
],
|
|
95
99
|
fatal: [/Error: The cursor position could not be read within/],
|
|
96
100
|
ensureArgs: (args) => {
|
|
97
101
|
if (!args.includes("--search"))
|
|
@@ -110,7 +114,7 @@ var CLI_CONFIGURES = {
|
|
|
110
114
|
binary: "cursor-agent",
|
|
111
115
|
ready: [/\/ commands/],
|
|
112
116
|
enter: [/→ Run \(once\) \(y\) \(enter\)/, /▶ \[a\] Trust this workspace/],
|
|
113
|
-
fatal: []
|
|
117
|
+
fatal: [/^ Error: You've hit your usage limit/]
|
|
114
118
|
}
|
|
115
119
|
};
|
|
116
120
|
async function claudeYes({
|
|
@@ -166,7 +170,6 @@ async function claudeYes({
|
|
|
166
170
|
return console.warn(`continueOnCrash is only supported for ${Object.keys(continueArgs).join(", ")} currently, not ${cli}`);
|
|
167
171
|
}
|
|
168
172
|
if (isFatal) {
|
|
169
|
-
console.log(`${cli} crashed with "No conversation found to continue", exiting...`);
|
|
170
173
|
return pendingExitCode.resolve(pendingExitCodeValue = exitCode2);
|
|
171
174
|
}
|
|
172
175
|
console.log(`${cli} crashed, restarting...`);
|
|
@@ -193,6 +196,8 @@ async function claudeYes({
|
|
|
193
196
|
console.log("[${cli}-yes] ${cli} is idle, exiting...");
|
|
194
197
|
await exitAgent();
|
|
195
198
|
});
|
|
199
|
+
console.log(`[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(" ")}`);
|
|
200
|
+
shell.write("\x1B[1;1R");
|
|
196
201
|
sflow(fromReadable(process.stdin)).map((buffer) => buffer.toString()).by({
|
|
197
202
|
writable: new WritableStream({
|
|
198
203
|
write: async (data) => {
|
|
@@ -207,13 +212,11 @@ async function claudeYes({
|
|
|
207
212
|
return;
|
|
208
213
|
if (text.includes("\x1B[6n"))
|
|
209
214
|
return;
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
`).length + 1;
|
|
213
|
-
const col = (rendered.split(`
|
|
214
|
-
`).slice(-1)[0]?.length || 0) + 1;
|
|
215
|
+
const { col, row } = terminalRender.getCursorPosition();
|
|
216
|
+
console.log(`[${cli}-yes] Responding cursor position: row=${row}, col=${col}`);
|
|
215
217
|
shell.write(`\x1B[${row};${col}R`);
|
|
216
|
-
}).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).
|
|
218
|
+
}).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).forEach((e2) => appendFile("io.log", "output|" + JSON.stringify(e2) + `
|
|
219
|
+
`)).forEach(async (e2, i) => {
|
|
217
220
|
const conf = CLI_CONFIGURES[cli] || null;
|
|
218
221
|
if (!conf)
|
|
219
222
|
return;
|
|
@@ -222,15 +225,16 @@ async function claudeYes({
|
|
|
222
225
|
return;
|
|
223
226
|
stdinReady.ready();
|
|
224
227
|
}
|
|
225
|
-
if (conf.enter?.some((rx) => e2.match(rx)))
|
|
228
|
+
if (conf.enter?.some((rx) => e2.match(rx))) {
|
|
226
229
|
await sendEnter(300);
|
|
227
|
-
|
|
230
|
+
}
|
|
231
|
+
if (conf.fatal?.some((rx) => e2.match(rx))) {
|
|
228
232
|
isFatal = true;
|
|
233
|
+
await exitAgent();
|
|
234
|
+
}
|
|
229
235
|
}).run()).map((e) => removeControlCharactersFromStdout ? removeControlCharacters(e) : e).to(fromWritable(process.stdout)).then(() => null);
|
|
230
236
|
if (prompt)
|
|
231
|
-
|
|
232
|
-
await sendMessage(prompt);
|
|
233
|
-
})();
|
|
237
|
+
await sendMessage(prompt);
|
|
234
238
|
const exitCode = await pendingExitCode.promise;
|
|
235
239
|
console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
|
|
236
240
|
if (logFile) {
|
|
@@ -269,7 +273,7 @@ async function claudeYes({
|
|
|
269
273
|
}
|
|
270
274
|
function getTerminalDimensions() {
|
|
271
275
|
return {
|
|
272
|
-
cols:
|
|
276
|
+
cols: process.stdout.columns,
|
|
273
277
|
rows: process.stdout.rows
|
|
274
278
|
};
|
|
275
279
|
}
|
|
@@ -287,5 +291,5 @@ export {
|
|
|
287
291
|
CLI_CONFIGURES
|
|
288
292
|
};
|
|
289
293
|
|
|
290
|
-
//# debugId=
|
|
294
|
+
//# debugId=725F10399412016964756E2164756E21
|
|
291
295
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../index.ts", "../idleWaiter.ts", "../ReadyManager.ts", "../removeControlCharacters.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { fromReadable, fromWritable } from 'from-node-stream';\nimport { mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport DIE from 'phpdie';\nimport sflow from 'sflow';\nimport { TerminalTextRender } from 'terminal-render';\nimport { IdleWaiter } from './idleWaiter';\nimport { ReadyManager } from './ReadyManager';\nimport { removeControlCharacters } from './removeControlCharacters';\n\nexport const CLI_CONFIGURES: Record<\n string,\n {\n install?: string; // hint user for install command if not installed\n binary?: string; // actual binary name if different from cli\n ready?: RegExp[]; // regex matcher for stdin ready, or line index for gemini\n enter?: RegExp[]; // array of regex to match for sending Enter\n fatal?: RegExp[]; // array of regex to match for fatal errors\n ensureArgs?: (args: string[]) => string[]; // function to ensure certain args are present\n }\n> = {\n grok: {\n install: 'npm install -g @vibe-kit/grok-cli',\n ready: [/^ +│ ❯ /],\n },\n claude: {\n install: 'npm install -g @anthropic-ai/claude-code',\n ready: [/^> /], // regex matcher for stdin ready,\n enter: [/❯ 1. Yes/, /❯ 1. Dark mode✔/, /Press Enter to continue…/],\n fatal: [\n /No conversation found to continue/,\n /⎿ {2}Claude usage limit reached\\./,\n ],\n },\n gemini: {\n install: 'npm install -g @google/gemini-cli',\n // match the agent prompt after initial lines; handled by index logic using line index\n ready: [/Type your message/], // used with line index check\n enter: [/│ ● 1. Yes, allow once/],\n fatal: [],\n },\n codex: {\n install: 'npm install -g @openai/codex-cli',\n ready: [/⏎ send/],\n enter: [/ > 1. Approve/, /> 1. Yes, allow Codex to work in this folder/],\n fatal: [/Error: The cursor position could not be read within/],\n // add to codex --search by default when not provided by the user\n ensureArgs: (args: string[]) => {\n if (!args.includes('--search')) return ['--search', ...args];\n return args;\n },\n },\n copilot: {\n install: 'npm install -g @github/copilot',\n ready: [/^ > /],\n enter: [/ │ ❯ 1. Yes, proceed/, /❯ 1. Yes/],\n fatal: [],\n },\n cursor: {\n install: 'open https://cursor.com/ja/docs/cli/installation',\n // map logical \"cursor\" cli name to actual binary name\n binary: 'cursor-agent',\n ready: [/\\/ commands/],\n enter: [/→ Run \\(once\\) \\(y\\) \\(enter\\)/, /▶ \\[a\\] Trust this workspace/],\n fatal: [],\n },\n};\n/**\n * Main function to run Claude with automatic yes/no responses\n * @param options Configuration options\n * @param options.continueOnCrash - If true, automatically restart Claude when it crashes:\n * 1. Shows message 'Claude crashed, restarting..'\n * 2. Spawns a new 'claude --continue' process\n * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)\n * 4. If it crashes with \"No conversation found to continue\", exits the process\n * @param options.exitOnIdle - Exit when Claude is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false\n * @param options.claudeArgs - Additional arguments to pass to the Claude CLI\n * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY\n *\n * @example\n * ```typescript\n * import claudeYes from 'claude-yes';\n * await claudeYes({\n * prompt: 'help me solve all todos in my codebase',\n *\n * // optional\n * cli: 'claude',\n * cliArgs: ['--verbose'], // additional args to pass to claude\n * exitOnIdle: 30000, // exit after 30 seconds of idle\n * continueOnCrash: true, // restart if claude crashes, default is true\n * logFile: 'claude.log', // save logs to file\n * });\n * ```\n */\nexport default async function claudeYes({\n cli = 'claude',\n cliArgs = [],\n prompt,\n continueOnCrash,\n cwd,\n env,\n exitOnIdle,\n logFile,\n removeControlCharactersFromStdout = false, // = !process.stdout.isTTY,\n verbose = false,\n}: {\n cli?: (string & {}) | keyof typeof CLI_CONFIGURES;\n cliArgs?: string[];\n prompt?: string;\n continueOnCrash?: boolean;\n cwd?: string;\n env?: Record<string, string>;\n exitOnIdle?: number;\n logFile?: string;\n removeControlCharactersFromStdout?: boolean;\n verbose?: boolean;\n} = {}) {\n const continueArgs = {\n codex: 'resume --last'.split(' '),\n claude: '--continue'.split(' '),\n gemini: [], // not possible yet\n };\n\n // if (verbose) {\n // console.log('calling claudeYes: ', {\n // cli,\n // continueOnCrash,\n // exitOnIdle,\n // cliArgs,\n // cwd,\n // removeControlCharactersFromStdout,\n // logFile,\n // verbose,\n // });\n // }\n // console.log(\n // `⭐ Starting ${cli}, automatically responding to yes/no prompts...`\n // );\n // console.log(\n // '⚠️ 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.'\n // );\n\n process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage\n let isFatal = false; // match 'No conversation found to continue'\n const stdinReady = new ReadyManager();\n\n const shellOutputStream = new TransformStream<string, string>();\n const outputWriter = shellOutputStream.writable.getWriter();\n // const pty = await import('node-pty');\n\n // its recommened to use bun-pty in windows\n const pty = await import('node-pty')\n .catch(async () => await import('bun-pty'))\n .catch(async () =>\n DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),\n );\n\n const getPtyOptions = () => ({\n name: 'xterm-color',\n ...getTerminalDimensions(),\n cwd: cwd ?? process.cwd(),\n env: env ?? (process.env as Record<string, string>),\n });\n\n // Apply CLI specific configurations (moved to CLI_CONFIGURES)\n const cliConf = (CLI_CONFIGURES as Record<string, any>)[cli] || {};\n cliArgs = cliConf.ensureArgs?.(cliArgs) ?? cliArgs;\n const cliCommand = cliConf?.binary || cli;\n\n let shell = tryCatch(\n () => pty.spawn(cliCommand, cliArgs, getPtyOptions()),\n (error: unknown) => {\n console.error(`Fatal: Failed to start ${cliCommand}.`);\n if (cliConf?.install)\n console.error(\n `If you did not installed it yet, Please install it first: ${cliConf.install}`,\n );\n throw error;\n },\n );\n const pendingExitCode = Promise.withResolvers<number | null>();\n let pendingExitCodeValue = null;\n\n // TODO handle error if claude is not installed, show msg:\n // npm install -g @anthropic-ai/claude-code\n\n async function onData(data: string) {\n // append data to the buffer, so we can process it later\n await outputWriter.write(data);\n }\n\n shell.onData(onData);\n shell.onExit(function onExit({ exitCode }) {\n stdinReady.unready(); // start buffer stdin\n const agentCrashed = exitCode !== 0;\n const continueArg = (continueArgs as Record<string, string[]>)[cli];\n\n if (agentCrashed && continueOnCrash && continueArg) {\n if (!continueArg) {\n return console.warn(\n `continueOnCrash is only supported for ${Object.keys(continueArgs).join(', ')} currently, not ${cli}`,\n );\n }\n if (isFatal) {\n console.log(\n `${cli} crashed with \"No conversation found to continue\", exiting...`,\n );\n return pendingExitCode.resolve((pendingExitCodeValue = exitCode));\n }\n console.log(`${cli} crashed, restarting...`);\n\n shell = pty.spawn(cli, continueArg, getPtyOptions());\n shell.onData(onData);\n shell.onExit(onExit);\n return;\n }\n return pendingExitCode.resolve((pendingExitCodeValue = exitCode));\n });\n\n // when current tty resized, resize the pty\n process.stdout.on('resize', () => {\n const { cols, rows } = getTerminalDimensions(); // minimum 80 columns to avoid layout issues\n shell.resize(cols, rows); // minimum 80 columns to avoid layout issues\n });\n\n const terminalRender = new TerminalTextRender();\n const isStillWorkingQ = () =>\n terminalRender\n .render()\n .replace(/\\s+/g, ' ')\n .match(/esc to interrupt|to run in background/);\n\n const idleWaiter = new IdleWaiter();\n if (exitOnIdle)\n idleWaiter.wait(exitOnIdle).then(async () => {\n if (isStillWorkingQ()) {\n console.log(\n '[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet',\n );\n return;\n }\n\n console.log('[${cli}-yes] ${cli} is idle, exiting...');\n await exitAgent();\n });\n\n // Message streaming\n sflow(fromReadable<Buffer>(process.stdin))\n .map((buffer) => buffer.toString())\n // .map((e) => e.replaceAll('\\x1a', '')) // remove ctrl+z from user's input (seems bug)\n // .forEach(e => appendFile('.cache/io.log', \"input |\" + JSON.stringify(e) + '\\n')) // for debugging\n // pipe\n .by({\n writable: new WritableStream<string>({\n write: async (data) => {\n await stdinReady.wait();\n // await idleWaiter.wait(20); // wait for idle for 200ms to avoid messing up claude's input\n shell.write(data);\n },\n }),\n readable: shellOutputStream.readable,\n })\n .forEach(() => idleWaiter.ping())\n .forEach((text) => {\n terminalRender.write(text);\n // todo: .onStatus((msg)=> shell.write(msg))\n if (process.stdin.isTTY) return; // only handle it when stdin is not tty\n if (text.includes('\\u001b[6n')) return; // only asked\n\n // todo: use terminalRender API to get cursor position when new version is available\n // xterm replies CSI row; column R if asked cursor position\n // https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B\n // when agent asking position, respond with row; col\n const rendered = terminalRender.render();\n const row = rendered.split('\\n').length + 1;\n const col = (rendered.split('\\n').slice(-1)[0]?.length || 0) + 1;\n shell.write(`\\u001b[${row};${col}R`);\n })\n\n // auto-response\n .forkTo((e) =>\n e\n .map((e) => removeControlCharacters(e))\n .map((e) => e.replaceAll('\\r', '')) // remove carriage return\n .lines({ EOL: 'NONE' })\n // Generic auto-response handler driven by CLI_CONFIGURES\n .forEach(async (e, i) => {\n const conf =\n CLI_CONFIGURES[cli as keyof typeof CLI_CONFIGURES] || null;\n if (!conf) return;\n\n // ready matcher: if matched, mark stdin ready\n if (conf.ready?.some((rx: RegExp) => e.match(rx))) {\n if (cli === 'gemini' && i <= 80) return; // gemini initial noise, only after many lines\n stdinReady.ready();\n }\n\n // enter matchers: send Enter when any enter regex matches\n if (conf.enter?.some((rx: RegExp) => e.match(rx)))\n await sendEnter(300); // send Enter after 300ms idle wait\n\n // fatal matchers: set isFatal flag when matched\n if (conf.fatal?.some((rx: RegExp) => e.match(rx))) isFatal = true;\n })\n // .forEach(e => appendFile('.cache/io.log', \"output|\" + JSON.stringify(e) + '\\n')) // for debugging\n .run(),\n )\n .map((e) =>\n removeControlCharactersFromStdout ? removeControlCharacters(e) : e,\n )\n .to(fromWritable(process.stdout))\n .then(() => null); // run it immediately without await\n\n // wait for cli ready and send prompt if provided\n if (prompt)\n (async () => {\n // console.log(`[${cli}-yes] Ready to send prompt to ${cli}: ${prompt}`);\n // idleWaiter.ping();\n // console.log(\n // 'await idleWaiter.wait(1000); // wait a bit for claude to start'\n // );\n // await idleWaiter.wait(1000); // wait a bit for claude to start\n // console.log('await stdinReady.wait();');\n // await stdinReady.wait();\n // console.log(`[${cli}-yes] Waiting for ${cli} to be ready...`);\n // console.log('await idleWaiter.wait(200);');\n // await idleWaiter.wait(200);\n // console.log(`[${cli}-yes] Sending prompt to ${cli}: ${prompt}`);\n await sendMessage(prompt);\n })();\n\n const exitCode = await pendingExitCode.promise; // wait for the shell to exit\n console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);\n\n if (logFile) {\n verbose && console.log(`[${cli}-yes] Writing rendered logs to ${logFile}`);\n const logFilePath = path.resolve(logFile);\n await mkdir(path.dirname(logFilePath), { recursive: true }).catch(\n () => null,\n );\n await writeFile(logFilePath, terminalRender.render());\n }\n\n return { exitCode, logs: terminalRender.render() };\n\n async function sendEnter(waitms = 1000) {\n // wait for idle for a bit to let agent cli finish rendering\n const st = Date.now();\n\n await idleWaiter.wait(waitms);\n const et = Date.now();\n process.stdout.write(`\\ridleWaiter.wait(${waitms}) took ${et - st}ms\\r`);\n\n shell.write('\\r');\n }\n\n async function sendMessage(message: string) {\n await stdinReady.wait();\n // show in-place message: write msg and move cursor back start\n shell.write(message);\n idleWaiter.ping(); // just sent a message, wait for echo\n await sendEnter();\n }\n\n async function exitAgent() {\n continueOnCrash = false;\n // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input\n await sendMessage('/exit');\n\n // wait for shell to exit or kill it with a timeout\n let exited = false;\n await Promise.race([\n pendingExitCode.promise.then(() => (exited = true)), // resolve when shell exits\n\n // if shell doesn't exit in 5 seconds, kill it\n new Promise<void>((resolve) =>\n setTimeout(() => {\n if (exited) return; // if shell already exited, do nothing\n shell.kill(); // kill the shell process if it doesn't exit in time\n resolve();\n }, 5000),\n ), // 5 seconds timeout\n ]);\n }\n\n function getTerminalDimensions() {\n return {\n cols: Math.max(process.stdout.columns, 80),\n rows: process.stdout.rows,\n };\n }\n}\n\nexport { removeControlCharacters };\n\nfunction tryCatch<T, R>(fn: () => T, catchFn: (error: unknown) => R): T | R {\n try {\n return fn();\n } catch (error) {\n return catchFn(error);\n }\n}\n",
|
|
5
|
+
"import { fromReadable, fromWritable } from 'from-node-stream';\nimport { appendFile, mkdir, writeFile } from 'fs/promises';\nimport path from 'path';\nimport DIE from 'phpdie';\nimport sflow from 'sflow';\nimport { TerminalTextRender } from 'terminal-render';\nimport { IdleWaiter } from './idleWaiter';\nimport { ReadyManager } from './ReadyManager';\nimport { removeControlCharacters } from './removeControlCharacters';\n\nexport const CLI_CONFIGURES: Record<\n string,\n {\n install?: string; // hint user for install command if not installed\n binary?: string; // actual binary name if different from cli\n ready?: RegExp[]; // regex matcher for stdin ready, or line index for gemini\n enter?: RegExp[]; // array of regex to match for sending Enter\n fatal?: RegExp[]; // array of regex to match for fatal errors\n ensureArgs?: (args: string[]) => string[]; // function to ensure certain args are present\n }\n> = {\n grok: {\n install: 'npm install -g @vibe-kit/grok-cli',\n ready: [/^ │ ❯ /],\n enter: [/^ 1. Yes/],\n },\n claude: {\n install: 'npm install -g @anthropic-ai/claude-code',\n ready: [/^> /], // regex matcher for stdin ready,\n enter: [/❯ 1. Yes/, /❯ 1. Dark mode✔/, /Press Enter to continue…/],\n fatal: [\n /No conversation found to continue/,\n /⎿ Claude usage limit reached\\./,\n ],\n },\n gemini: {\n install: 'npm install -g @google/gemini-cli',\n // match the agent prompt after initial lines; handled by index logic using line index\n ready: [/Type your message/], // used with line index check\n enter: [/│ ● 1. Yes, allow once/],\n fatal: [],\n },\n codex: {\n install: 'npm install -g @openai/codex-cli',\n ready: [/⏎ send/],\n enter: [\n /\\b▌ \\> 1\\. Approve and run now/,\n /\\b\\> 1\\. Yes, allow Codex to work in this folder/,\n ],\n fatal: [/Error: The cursor position could not be read within/],\n // add to codex --search by default when not provided by the user\n ensureArgs: (args: string[]) => {\n if (!args.includes('--search')) return ['--search', ...args];\n return args;\n },\n },\n copilot: {\n install: 'npm install -g @github/copilot',\n ready: [/^ > /],\n enter: [/ │ ❯ 1. Yes, proceed/, /❯ 1. Yes/],\n fatal: [],\n },\n cursor: {\n install: 'open https://cursor.com/ja/docs/cli/installation',\n // map logical \"cursor\" cli name to actual binary name\n binary: 'cursor-agent',\n ready: [/\\/ commands/],\n enter: [/→ Run \\(once\\) \\(y\\) \\(enter\\)/, /▶ \\[a\\] Trust this workspace/],\n fatal: [/^ Error: You've hit your usage limit/],\n },\n};\n/**\n * Main function to run Claude with automatic yes/no responses\n * @param options Configuration options\n * @param options.continueOnCrash - If true, automatically restart Claude when it crashes:\n * 1. Shows message 'Claude crashed, restarting..'\n * 2. Spawns a new 'claude --continue' process\n * 3. Re-attaches the new process to the shell stdio (pipes new process stdin/stdout)\n * 4. If it crashes with \"No conversation found to continue\", exits the process\n * @param options.exitOnIdle - Exit when Claude is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false\n * @param options.claudeArgs - Additional arguments to pass to the Claude CLI\n * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY\n *\n * @example\n * ```typescript\n * import claudeYes from 'claude-yes';\n * await claudeYes({\n * prompt: 'help me solve all todos in my codebase',\n *\n * // optional\n * cli: 'claude',\n * cliArgs: ['--verbose'], // additional args to pass to claude\n * exitOnIdle: 30000, // exit after 30 seconds of idle\n * continueOnCrash: true, // restart if claude crashes, default is true\n * logFile: 'claude.log', // save logs to file\n * });\n * ```\n */\nexport default async function claudeYes({\n cli = 'claude',\n cliArgs = [],\n prompt,\n continueOnCrash,\n cwd,\n env,\n exitOnIdle,\n logFile,\n removeControlCharactersFromStdout = false, // = !process.stdout.isTTY,\n verbose = false,\n}: {\n cli?: (string & {}) | keyof typeof CLI_CONFIGURES;\n cliArgs?: string[];\n prompt?: string;\n continueOnCrash?: boolean;\n cwd?: string;\n env?: Record<string, string>;\n exitOnIdle?: number;\n logFile?: string;\n removeControlCharactersFromStdout?: boolean;\n verbose?: boolean;\n} = {}) {\n const continueArgs = {\n codex: 'resume --last'.split(' '),\n claude: '--continue'.split(' '),\n gemini: [], // not possible yet\n };\n\n // if (verbose) {\n // console.log('calling claudeYes: ', {\n // cli,\n // continueOnCrash,\n // exitOnIdle,\n // cliArgs,\n // cwd,\n // removeControlCharactersFromStdout,\n // logFile,\n // verbose,\n // });\n // }\n // console.log(\n // `⭐ Starting ${cli}, automatically responding to yes/no prompts...`\n // );\n // console.log(\n // '⚠️ 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.'\n // );\n\n process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage\n let isFatal = false; // when true, do not restart on crash, and exit agent\n const stdinReady = new ReadyManager();\n\n const shellOutputStream = new TransformStream<string, string>();\n const outputWriter = shellOutputStream.writable.getWriter();\n // const pty = await import('node-pty');\n\n // its recommened to use bun-pty in windows\n const pty = await import('node-pty')\n .catch(async () => await import('bun-pty'))\n .catch(async () =>\n DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),\n );\n\n const getPtyOptions = () => ({\n name: 'xterm-color',\n ...getTerminalDimensions(),\n cwd: cwd ?? process.cwd(),\n env: env ?? (process.env as Record<string, string>),\n });\n\n // Apply CLI specific configurations (moved to CLI_CONFIGURES)\n const cliConf = (CLI_CONFIGURES as Record<string, any>)[cli] || {};\n cliArgs = cliConf.ensureArgs?.(cliArgs) ?? cliArgs;\n const cliCommand = cliConf?.binary || cli;\n\n let shell = tryCatch(\n () => pty.spawn(cliCommand, cliArgs, getPtyOptions()),\n (error: unknown) => {\n console.error(`Fatal: Failed to start ${cliCommand}.`);\n if (cliConf?.install)\n console.error(\n `If you did not installed it yet, Please install it first: ${cliConf.install}`,\n );\n throw error;\n },\n );\n const pendingExitCode = Promise.withResolvers<number | null>();\n let pendingExitCodeValue = null;\n\n // TODO handle error if claude is not installed, show msg:\n // npm install -g @anthropic-ai/claude-code\n\n async function onData(data: string) {\n // append data to the buffer, so we can process it later\n await outputWriter.write(data);\n }\n\n shell.onData(onData);\n shell.onExit(function onExit({ exitCode }) {\n stdinReady.unready(); // start buffer stdin\n const agentCrashed = exitCode !== 0;\n const continueArg = (continueArgs as Record<string, string[]>)[cli];\n\n if (agentCrashed && continueOnCrash && continueArg) {\n if (!continueArg) {\n return console.warn(\n `continueOnCrash is only supported for ${Object.keys(continueArgs).join(', ')} currently, not ${cli}`,\n );\n }\n if (isFatal) {\n return pendingExitCode.resolve((pendingExitCodeValue = exitCode));\n }\n\n console.log(`${cli} crashed, restarting...`);\n\n shell = pty.spawn(cli, continueArg, getPtyOptions());\n shell.onData(onData);\n shell.onExit(onExit);\n return;\n }\n return pendingExitCode.resolve((pendingExitCodeValue = exitCode));\n });\n\n // when current tty resized, resize the pty\n process.stdout.on('resize', () => {\n const { cols, rows } = getTerminalDimensions(); // minimum 80 columns to avoid layout issues\n shell.resize(cols, rows); // minimum 80 columns to avoid layout issues\n });\n\n const terminalRender = new TerminalTextRender();\n const isStillWorkingQ = () =>\n terminalRender\n .render()\n .replace(/\\s+/g, ' ')\n .match(/esc to interrupt|to run in background/);\n\n const idleWaiter = new IdleWaiter();\n if (exitOnIdle)\n idleWaiter.wait(exitOnIdle).then(async () => {\n if (isStillWorkingQ()) {\n console.log(\n '[${cli}-yes] ${cli} is idle, but seems still working, not exiting yet',\n );\n return;\n }\n\n console.log('[${cli}-yes] ${cli} is idle, exiting...');\n await exitAgent();\n });\n\n console.log(\n `[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(' ')}`,\n );\n // Message streaming\n shell.write(`\\u001b[${1};${1}R`); // reply cli when getting cursor position\n\n sflow(fromReadable<Buffer>(process.stdin))\n .map((buffer) => buffer.toString())\n // .map((e) => e.replaceAll('\\x1a', '')) // remove ctrl+z from user's input (seems bug)\n // .forEach(e => appendFile('.cache/io.log', \"input |\" + JSON.stringify(e) + '\\n')) // for debugging\n // pipe\n .by({\n writable: new WritableStream<string>({\n write: async (data) => {\n await stdinReady.wait();\n // await idleWaiter.wait(20); // wait for idle for 200ms to avoid messing up claude's input\n shell.write(data);\n },\n }),\n readable: shellOutputStream.readable,\n })\n .forEach(() => idleWaiter.ping())\n .forEach((text) => {\n terminalRender.write(text);\n // todo: .onStatus((msg)=> shell.write(msg))\n if (process.stdin.isTTY) return; // only handle it when stdin is not tty\n if (text.includes('\\u001b[6n')) return; // only asked for cursor position\n // todo: use terminalRender API to get cursor position when new version is available\n // xterm replies CSI row; column R if asked cursor position\n // https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B\n // when agent asking position, respond with row; col\n // const rendered = terminalRender.render();\n const { col, row } = terminalRender.getCursorPosition();\n console.log(\n `[${cli}-yes] Responding cursor position: row=${row}, col=${col}`,\n );\n shell.write(`\\u001b[${row};${col}R`); // reply cli when getting cursor position\n // const row = rendered.split('\\n').length + 1;\n // const col = (rendered.split('\\n').slice(-1)[0]?.length || 0) + 1;\n })\n\n // auto-response\n .forkTo((e) =>\n e\n .map((e) => removeControlCharacters(e))\n .map((e) => e.replaceAll('\\r', '')) // remove carriage return\n // .lines({ EOL: 'NONE' })\n .forEach((e) =>\n appendFile('io.log', 'output|' + JSON.stringify(e) + '\\n'),\n ) // for debugging\n // Generic auto-response handler driven by CLI_CONFIGURES\n .forEach(async (e, i) => {\n const conf =\n CLI_CONFIGURES[cli as keyof typeof CLI_CONFIGURES] || null;\n if (!conf) return;\n\n // ready matcher: if matched, mark stdin ready\n if (conf.ready?.some((rx: RegExp) => e.match(rx))) {\n if (cli === 'gemini' && i <= 80) return; // gemini initial noise, only after many lines\n stdinReady.ready();\n }\n\n // enter matchers: send Enter when any enter regex matches\n if (conf.enter?.some((rx: RegExp) => e.match(rx))) {\n await sendEnter(300); // send Enter after 300ms idle wait\n }\n\n // fatal matchers: set isFatal flag when matched\n if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {\n isFatal = true;\n await exitAgent();\n }\n })\n .run(),\n )\n .map((e) =>\n removeControlCharactersFromStdout ? removeControlCharacters(e) : e,\n )\n .to(fromWritable(process.stdout))\n .then(() => null); // run it immediately without await\n\n // wait for cli ready and send prompt if provided\n if (prompt) await sendMessage(prompt);\n\n const exitCode = await pendingExitCode.promise; // wait for the shell to exit\n console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);\n\n if (logFile) {\n verbose && console.log(`[${cli}-yes] Writing rendered logs to ${logFile}`);\n const logFilePath = path.resolve(logFile);\n await mkdir(path.dirname(logFilePath), { recursive: true }).catch(\n () => null,\n );\n await writeFile(logFilePath, terminalRender.render());\n }\n\n return { exitCode, logs: terminalRender.render() };\n\n async function sendEnter(waitms = 1000) {\n // wait for idle for a bit to let agent cli finish rendering\n const st = Date.now();\n\n await idleWaiter.wait(waitms);\n const et = Date.now();\n process.stdout.write(`\\ridleWaiter.wait(${waitms}) took ${et - st}ms\\r`);\n\n shell.write('\\r');\n }\n\n async function sendMessage(message: string) {\n await stdinReady.wait();\n // show in-place message: write msg and move cursor back start\n shell.write(message);\n idleWaiter.ping(); // just sent a message, wait for echo\n await sendEnter();\n }\n\n async function exitAgent() {\n continueOnCrash = false;\n // send exit command to the shell, must sleep a bit to avoid claude treat it as pasted input\n await sendMessage('/exit');\n\n // wait for shell to exit or kill it with a timeout\n let exited = false;\n await Promise.race([\n pendingExitCode.promise.then(() => (exited = true)), // resolve when shell exits\n\n // if shell doesn't exit in 5 seconds, kill it\n new Promise<void>((resolve) =>\n setTimeout(() => {\n if (exited) return; // if shell already exited, do nothing\n shell.kill(); // kill the shell process if it doesn't exit in time\n resolve();\n }, 5000),\n ), // 5 seconds timeout\n ]);\n }\n\n function getTerminalDimensions() {\n return {\n // TODO: enforce minimum cols/rows to avoid layout issues\n // cols: Math.max(process.stdout.columns, 80),\n cols: process.stdout.columns,\n rows: process.stdout.rows,\n };\n }\n}\n\nexport { removeControlCharacters };\n\nfunction tryCatch<T, R>(fn: () => T, catchFn: (error: unknown) => R): T | R {\n try {\n return fn();\n } catch (error) {\n return catchFn(error);\n }\n}\n",
|
|
6
6
|
"/**\n * A utility class to wait for idle periods based on activity pings.\n *\n * @example\n * const idleWaiter = new IdleWaiter();\n *\n * // Somewhere in your code, when activity occurs:\n * idleWaiter.ping();\n *\n * // To wait for an idle period of 5 seconds:\n * await idleWaiter.wait(5000);\n * console.log('System has been idle for 5 seconds');\n */\nexport class IdleWaiter {\n lastActivityTime = Date.now();\n checkInterval = 100; // Default check interval in milliseconds\n\n constructor() {\n this.ping();\n }\n\n ping() {\n this.lastActivityTime = Date.now();\n return this;\n }\n\n async wait(ms: number) {\n while (this.lastActivityTime >= Date.now() - ms)\n await new Promise((resolve) => setTimeout(resolve, this.checkInterval));\n }\n}\n",
|
|
7
7
|
"export class ReadyManager {\n private isReady = false;\n private readyQueue: (() => void)[] = [];\n wait() {\n if (this.isReady) return;\n return new Promise<void>((resolve) => this.readyQueue.push(resolve));\n }\n unready() {\n this.isReady = false;\n }\n ready() {\n this.isReady = true;\n if (!this.readyQueue.length) return; // check len for performance\n this.readyQueue.splice(0).map((resolve) => resolve());\n }\n}\n",
|
|
8
8
|
"export function removeControlCharacters(str: string): string {\n // Matches control characters in the C0 and C1 ranges, including Delete (U+007F)\n return str.replace(\n /[\\u001b\\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,\n '',\n );\n}\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;;;ACQO,MAAM,WAAW;AAAA,EACtB,mBAAmB,KAAK,IAAI;AAAA,EAC5B,gBAAgB;AAAA,EAEhB,WAAW,GAAG;AAAA,IACZ,KAAK,KAAK;AAAA;AAAA,EAGZ,IAAI,GAAG;AAAA,IACL,KAAK,mBAAmB,KAAK,IAAI;AAAA,IACjC,OAAO;AAAA;AAAA,OAGH,KAAI,CAAC,IAAY;AAAA,IACrB,OAAO,KAAK,oBAAoB,KAAK,IAAI,IAAI;AAAA,MAC3C,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,aAAa,CAAC;AAAA;AAE5E;;;AC9BO,MAAM,aAAa;AAAA,EAChB,UAAU;AAAA,EACV,aAA6B,CAAC;AAAA,EACtC,IAAI,GAAG;AAAA,IACL,IAAI,KAAK;AAAA,MAAS;AAAA,IAClB,OAAO,IAAI,QAAc,CAAC,YAAY,KAAK,WAAW,KAAK,OAAO,CAAC;AAAA;AAAA,EAErE,OAAO,GAAG;AAAA,IACR,KAAK,UAAU;AAAA;AAAA,EAEjB,KAAK,GAAG;AAAA,IACN,KAAK,UAAU;AAAA,IACf,IAAI,CAAC,KAAK,WAAW;AAAA,MAAQ;AAAA,IAC7B,KAAK,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,QAAQ,CAAC;AAAA;AAExD;;;ACfO,SAAS,uBAAuB,CAAC,KAAqB;AAAA,EAE3D,OAAO,IAAI,QACT,+EACA,EACF;AAAA;;;AHKK,IAAM,iBAUT;AAAA,EACF,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,OAAO,CAAC,SAAQ;AAAA,
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;;;ACQO,MAAM,WAAW;AAAA,EACtB,mBAAmB,KAAK,IAAI;AAAA,EAC5B,gBAAgB;AAAA,EAEhB,WAAW,GAAG;AAAA,IACZ,KAAK,KAAK;AAAA;AAAA,EAGZ,IAAI,GAAG;AAAA,IACL,KAAK,mBAAmB,KAAK,IAAI;AAAA,IACjC,OAAO;AAAA;AAAA,OAGH,KAAI,CAAC,IAAY;AAAA,IACrB,OAAO,KAAK,oBAAoB,KAAK,IAAI,IAAI;AAAA,MAC3C,MAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,aAAa,CAAC;AAAA;AAE5E;;;AC9BO,MAAM,aAAa;AAAA,EAChB,UAAU;AAAA,EACV,aAA6B,CAAC;AAAA,EACtC,IAAI,GAAG;AAAA,IACL,IAAI,KAAK;AAAA,MAAS;AAAA,IAClB,OAAO,IAAI,QAAc,CAAC,YAAY,KAAK,WAAW,KAAK,OAAO,CAAC;AAAA;AAAA,EAErE,OAAO,GAAG;AAAA,IACR,KAAK,UAAU;AAAA;AAAA,EAEjB,KAAK,GAAG;AAAA,IACN,KAAK,UAAU;AAAA,IACf,IAAI,CAAC,KAAK,WAAW;AAAA,MAAQ;AAAA,IAC7B,KAAK,WAAW,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,QAAQ,CAAC;AAAA;AAExD;;;ACfO,SAAS,uBAAuB,CAAC,KAAqB;AAAA,EAE3D,OAAO,IAAI,QACT,+EACA,EACF;AAAA;;;AHKK,IAAM,iBAUT;AAAA,EACF,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,OAAO,CAAC,SAAQ;AAAA,IAChB,OAAO,CAAC,YAAY;AAAA,EACtB;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IACT,OAAO,CAAC,KAAK;AAAA,IACb,OAAO,CAAC,YAAW,mBAAmB,0BAA0B;AAAA,IAChE,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IAET,OAAO,CAAC,mBAAmB;AAAA,IAC3B,OAAO,CAAC,wBAAuB;AAAA,IAC/B,OAAO,CAAC;AAAA,EACV;AAAA,EACA,OAAO;AAAA,IACL,SAAS;AAAA,IACT,OAAO,CAAC,QAAO;AAAA,IACf,OAAO;AAAA,MACL;AAAA,MACA;AAAA,IACF;AAAA,IACA,OAAO,CAAC,qDAAqD;AAAA,IAE7D,YAAY,CAAC,SAAmB;AAAA,MAC9B,IAAI,CAAC,KAAK,SAAS,UAAU;AAAA,QAAG,OAAO,CAAC,YAAY,GAAG,IAAI;AAAA,MAC3D,OAAO;AAAA;AAAA,EAEX;AAAA,EACA,SAAS;AAAA,IACP,SAAS;AAAA,IACT,OAAO,CAAC,OAAO;AAAA,IACf,OAAO,CAAC,wBAAuB,UAAU;AAAA,IACzC,OAAO,CAAC;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,SAAS;AAAA,IAET,QAAQ;AAAA,IACR,OAAO,CAAC,aAAa;AAAA,IACrB,OAAO,CAAC,kCAAiC,8BAA8B;AAAA,IACvE,OAAO,CAAC,uCAAuC;AAAA,EACjD;AACF;AA4BA,eAA8B,SAAS;AAAA,EACrC,MAAM;AAAA,EACN,UAAU,CAAC;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oCAAoC;AAAA,EACpC,UAAU;AAAA,IAYR,CAAC,GAAG;AAAA,EACN,MAAM,eAAe;AAAA,IACnB,OAAO,gBAAgB,MAAM,GAAG;AAAA,IAChC,QAAQ,aAAa,MAAM,GAAG;AAAA,IAC9B,QAAQ,CAAC;AAAA,EACX;AAAA,EAqBA,QAAQ,MAAM,aAAa,IAAI;AAAA,EAC/B,IAAI,UAAU;AAAA,EACd,MAAM,aAAa,IAAI;AAAA,EAEvB,MAAM,oBAAoB,IAAI;AAAA,EAC9B,MAAM,eAAe,kBAAkB,SAAS,UAAU;AAAA,EAI1D,MAAM,MAAM,MAAa,mBACtB,MAAM,YAAY,MAAa,iBAAU,EACzC,MAAM,YACL,IAAI,mEAAmE,CACzE;AAAA,EAEF,MAAM,gBAAgB,OAAO;AAAA,IAC3B,MAAM;AAAA,OACH,sBAAsB;AAAA,IACzB,KAAK,OAAO,QAAQ,IAAI;AAAA,IACxB,KAAK,OAAQ,QAAQ;AAAA,EACvB;AAAA,EAGA,MAAM,UAAW,eAAuC,QAAQ,CAAC;AAAA,EACjE,UAAU,QAAQ,aAAa,OAAO,KAAK;AAAA,EAC3C,MAAM,aAAa,SAAS,UAAU;AAAA,EAEtC,IAAI,QAAQ,SACV,MAAM,IAAI,MAAM,YAAY,SAAS,cAAc,CAAC,GACpD,CAAC,UAAmB;AAAA,IAClB,QAAQ,MAAM,0BAA0B,aAAa;AAAA,IACrD,IAAI,SAAS;AAAA,MACX,QAAQ,MACN,6DAA6D,QAAQ,SACvE;AAAA,IACF,MAAM;AAAA,GAEV;AAAA,EACA,MAAM,kBAAkB,QAAQ,cAA6B;AAAA,EAC7D,IAAI,uBAAuB;AAAA,EAK3B,eAAe,MAAM,CAAC,MAAc;AAAA,IAElC,MAAM,aAAa,MAAM,IAAI;AAAA;AAAA,EAG/B,MAAM,OAAO,MAAM;AAAA,EACnB,MAAM,OAAO,SAAS,MAAM,GAAG,uBAAY;AAAA,IACzC,WAAW,QAAQ;AAAA,IACnB,MAAM,eAAe,cAAa;AAAA,IAClC,MAAM,cAAe,aAA0C;AAAA,IAE/D,IAAI,gBAAgB,mBAAmB,aAAa;AAAA,MAClD,IAAI,CAAC,aAAa;AAAA,QAChB,OAAO,QAAQ,KACb,yCAAyC,OAAO,KAAK,YAAY,EAAE,KAAK,IAAI,oBAAoB,KAClG;AAAA,MACF;AAAA,MACA,IAAI,SAAS;AAAA,QACX,OAAO,gBAAgB,QAAS,uBAAuB,SAAS;AAAA,MAClE;AAAA,MAEA,QAAQ,IAAI,GAAG,4BAA4B;AAAA,MAE3C,QAAQ,IAAI,MAAM,KAAK,aAAa,cAAc,CAAC;AAAA,MACnD,MAAM,OAAO,MAAM;AAAA,MACnB,MAAM,OAAO,MAAM;AAAA,MACnB;AAAA,IACF;AAAA,IACA,OAAO,gBAAgB,QAAS,uBAAuB,SAAS;AAAA,GACjE;AAAA,EAGD,QAAQ,OAAO,GAAG,UAAU,MAAM;AAAA,IAChC,QAAQ,MAAM,SAAS,sBAAsB;AAAA,IAC7C,MAAM,OAAO,MAAM,IAAI;AAAA,GACxB;AAAA,EAED,MAAM,iBAAiB,IAAI;AAAA,EAC3B,MAAM,kBAAkB,MACtB,eACG,OAAO,EACP,QAAQ,QAAQ,GAAG,EACnB,MAAM,uCAAuC;AAAA,EAElD,MAAM,aAAa,IAAI;AAAA,EACvB,IAAI;AAAA,IACF,WAAW,KAAK,UAAU,EAAE,KAAK,YAAY;AAAA,MAC3C,IAAI,gBAAgB,GAAG;AAAA,QACrB,QAAQ,IACN,uEACF;AAAA,QACA;AAAA,MACF;AAAA,MAEA,QAAQ,IAAI,yCAAyC;AAAA,MACrD,MAAM,UAAU;AAAA,KACjB;AAAA,EAEH,QAAQ,IACN,IAAI,oBAAoB,kBAAkB,CAAC,YAAY,GAAG,OAAO,EAAE,KAAK,GAAG,GAC7E;AAAA,EAEA,MAAM,MAAM,WAAmB;AAAA,EAE/B,MAAM,aAAqB,QAAQ,KAAK,CAAC,EACtC,IAAI,CAAC,WAAW,OAAO,SAAS,CAAC,EAIjC,GAAG;AAAA,IACF,UAAU,IAAI,eAAuB;AAAA,MACnC,OAAO,OAAO,SAAS;AAAA,QACrB,MAAM,WAAW,KAAK;AAAA,QAEtB,MAAM,MAAM,IAAI;AAAA;AAAA,IAEpB,CAAC;AAAA,IACD,UAAU,kBAAkB;AAAA,EAC9B,CAAC,EACA,QAAQ,MAAM,WAAW,KAAK,CAAC,EAC/B,QAAQ,CAAC,SAAS;AAAA,IACjB,eAAe,MAAM,IAAI;AAAA,IAEzB,IAAI,QAAQ,MAAM;AAAA,MAAO;AAAA,IACzB,IAAI,KAAK,SAAS,SAAW;AAAA,MAAG;AAAA,IAMhC,QAAQ,KAAK,QAAQ,eAAe,kBAAkB;AAAA,IACtD,QAAQ,IACN,IAAI,4CAA4C,YAAY,KAC9D;AAAA,IACA,MAAM,MAAM,QAAU,OAAO,MAAM;AAAA,GAGpC,EAGA,OAAO,CAAC,MACP,EACG,IAAI,CAAC,OAAM,wBAAwB,EAAC,CAAC,EACrC,IAAI,CAAC,OAAM,GAAE,WAAW,MAAM,EAAE,CAAC,EAEjC,QAAQ,CAAC,OACR,WAAW,UAAU,YAAY,KAAK,UAAU,EAAC,IAAI;AAAA,CAAI,CAC3D,EAEC,QAAQ,OAAO,IAAG,MAAM;AAAA,IACvB,MAAM,OACJ,eAAe,QAAuC;AAAA,IACxD,IAAI,CAAC;AAAA,MAAM;AAAA,IAGX,IAAI,KAAK,OAAO,KAAK,CAAC,OAAe,GAAE,MAAM,EAAE,CAAC,GAAG;AAAA,MACjD,IAAI,QAAQ,YAAY,KAAK;AAAA,QAAI;AAAA,MACjC,WAAW,MAAM;AAAA,IACnB;AAAA,IAGA,IAAI,KAAK,OAAO,KAAK,CAAC,OAAe,GAAE,MAAM,EAAE,CAAC,GAAG;AAAA,MACjD,MAAM,UAAU,GAAG;AAAA,IACrB;AAAA,IAGA,IAAI,KAAK,OAAO,KAAK,CAAC,OAAe,GAAE,MAAM,EAAE,CAAC,GAAG;AAAA,MACjD,UAAU;AAAA,MACV,MAAM,UAAU;AAAA,IAClB;AAAA,GACD,EACA,IAAI,CACT,EACC,IAAI,CAAC,MACJ,oCAAoC,wBAAwB,CAAC,IAAI,CACnE,EACC,GAAG,aAAa,QAAQ,MAAM,CAAC,EAC/B,KAAK,MAAM,IAAI;AAAA,EAGlB,IAAI;AAAA,IAAQ,MAAM,YAAY,MAAM;AAAA,EAEpC,MAAM,WAAW,MAAM,gBAAgB;AAAA,EACvC,QAAQ,IAAI,IAAI,YAAY,wBAAwB,UAAU;AAAA,EAE9D,IAAI,SAAS;AAAA,IACX,WAAW,QAAQ,IAAI,IAAI,qCAAqC,SAAS;AAAA,IACzE,MAAM,cAAc,KAAK,QAAQ,OAAO;AAAA,IACxC,MAAM,MAAM,KAAK,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,MAC1D,MAAM,IACR;AAAA,IACA,MAAM,UAAU,aAAa,eAAe,OAAO,CAAC;AAAA,EACtD;AAAA,EAEA,OAAO,EAAE,UAAU,MAAM,eAAe,OAAO,EAAE;AAAA,EAEjD,eAAe,SAAS,CAAC,SAAS,MAAM;AAAA,IAEtC,MAAM,KAAK,KAAK,IAAI;AAAA,IAEpB,MAAM,WAAW,KAAK,MAAM;AAAA,IAC5B,MAAM,KAAK,KAAK,IAAI;AAAA,IACpB,QAAQ,OAAO,MAAM,qBAAqB,gBAAgB,KAAK,QAAQ;AAAA,IAEvE,MAAM,MAAM,IAAI;AAAA;AAAA,EAGlB,eAAe,WAAW,CAAC,SAAiB;AAAA,IAC1C,MAAM,WAAW,KAAK;AAAA,IAEtB,MAAM,MAAM,OAAO;AAAA,IACnB,WAAW,KAAK;AAAA,IAChB,MAAM,UAAU;AAAA;AAAA,EAGlB,eAAe,SAAS,GAAG;AAAA,IACzB,kBAAkB;AAAA,IAElB,MAAM,YAAY,OAAO;AAAA,IAGzB,IAAI,SAAS;AAAA,IACb,MAAM,QAAQ,KAAK;AAAA,MACjB,gBAAgB,QAAQ,KAAK,MAAO,SAAS,IAAK;AAAA,MAGlD,IAAI,QAAc,CAAC,YACjB,WAAW,MAAM;AAAA,QACf,IAAI;AAAA,UAAQ;AAAA,QACZ,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,SACP,IAAI,CACT;AAAA,IACF,CAAC;AAAA;AAAA,EAGH,SAAS,qBAAqB,GAAG;AAAA,IAC/B,OAAO;AAAA,MAGL,MAAM,QAAQ,OAAO;AAAA,MACrB,MAAM,QAAQ,OAAO;AAAA,IACvB;AAAA;AAAA;AAMJ,SAAS,QAAc,CAAC,IAAa,SAAuC;AAAA,EAC1E,IAAI;AAAA,IACF,OAAO,GAAG;AAAA,IACV,OAAO,OAAO;AAAA,IACd,OAAO,QAAQ,KAAK;AAAA;AAAA;",
|
|
11
|
+
"debugId": "725F10399412016964756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { fromReadable, fromWritable } from 'from-node-stream';
|
|
2
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { appendFile, mkdir, writeFile } from 'fs/promises';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import DIE from 'phpdie';
|
|
5
5
|
import sflow from 'sflow';
|
|
@@ -21,7 +21,8 @@ export const CLI_CONFIGURES: Record<
|
|
|
21
21
|
> = {
|
|
22
22
|
grok: {
|
|
23
23
|
install: 'npm install -g @vibe-kit/grok-cli',
|
|
24
|
-
ready: [/^
|
|
24
|
+
ready: [/^ │ ❯ /],
|
|
25
|
+
enter: [/^ 1. Yes/],
|
|
25
26
|
},
|
|
26
27
|
claude: {
|
|
27
28
|
install: 'npm install -g @anthropic-ai/claude-code',
|
|
@@ -29,7 +30,7 @@ export const CLI_CONFIGURES: Record<
|
|
|
29
30
|
enter: [/❯ 1. Yes/, /❯ 1. Dark mode✔/, /Press Enter to continue…/],
|
|
30
31
|
fatal: [
|
|
31
32
|
/No conversation found to continue/,
|
|
32
|
-
/⎿
|
|
33
|
+
/⎿ Claude usage limit reached\./,
|
|
33
34
|
],
|
|
34
35
|
},
|
|
35
36
|
gemini: {
|
|
@@ -42,7 +43,10 @@ export const CLI_CONFIGURES: Record<
|
|
|
42
43
|
codex: {
|
|
43
44
|
install: 'npm install -g @openai/codex-cli',
|
|
44
45
|
ready: [/⏎ send/],
|
|
45
|
-
enter: [
|
|
46
|
+
enter: [
|
|
47
|
+
/\b▌ \> 1\. Approve and run now/,
|
|
48
|
+
/\b\> 1\. Yes, allow Codex to work in this folder/,
|
|
49
|
+
],
|
|
46
50
|
fatal: [/Error: The cursor position could not be read within/],
|
|
47
51
|
// add to codex --search by default when not provided by the user
|
|
48
52
|
ensureArgs: (args: string[]) => {
|
|
@@ -62,7 +66,7 @@ export const CLI_CONFIGURES: Record<
|
|
|
62
66
|
binary: 'cursor-agent',
|
|
63
67
|
ready: [/\/ commands/],
|
|
64
68
|
enter: [/→ Run \(once\) \(y\) \(enter\)/, /▶ \[a\] Trust this workspace/],
|
|
65
|
-
fatal: [],
|
|
69
|
+
fatal: [/^ Error: You've hit your usage limit/],
|
|
66
70
|
},
|
|
67
71
|
};
|
|
68
72
|
/**
|
|
@@ -141,7 +145,7 @@ export default async function claudeYes({
|
|
|
141
145
|
// );
|
|
142
146
|
|
|
143
147
|
process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
|
|
144
|
-
let isFatal = false; //
|
|
148
|
+
let isFatal = false; // when true, do not restart on crash, and exit agent
|
|
145
149
|
const stdinReady = new ReadyManager();
|
|
146
150
|
|
|
147
151
|
const shellOutputStream = new TransformStream<string, string>();
|
|
@@ -202,11 +206,9 @@ export default async function claudeYes({
|
|
|
202
206
|
);
|
|
203
207
|
}
|
|
204
208
|
if (isFatal) {
|
|
205
|
-
console.log(
|
|
206
|
-
`${cli} crashed with "No conversation found to continue", exiting...`,
|
|
207
|
-
);
|
|
208
209
|
return pendingExitCode.resolve((pendingExitCodeValue = exitCode));
|
|
209
210
|
}
|
|
211
|
+
|
|
210
212
|
console.log(`${cli} crashed, restarting...`);
|
|
211
213
|
|
|
212
214
|
shell = pty.spawn(cli, continueArg, getPtyOptions());
|
|
@@ -244,7 +246,12 @@ export default async function claudeYes({
|
|
|
244
246
|
await exitAgent();
|
|
245
247
|
});
|
|
246
248
|
|
|
249
|
+
console.log(
|
|
250
|
+
`[${cli}-yes] Started ${cli} with args: ${[cliCommand, ...cliArgs].join(' ')}`,
|
|
251
|
+
);
|
|
247
252
|
// Message streaming
|
|
253
|
+
shell.write(`\u001b[${1};${1}R`); // reply cli when getting cursor position
|
|
254
|
+
|
|
248
255
|
sflow(fromReadable<Buffer>(process.stdin))
|
|
249
256
|
.map((buffer) => buffer.toString())
|
|
250
257
|
// .map((e) => e.replaceAll('\x1a', '')) // remove ctrl+z from user's input (seems bug)
|
|
@@ -265,16 +272,19 @@ export default async function claudeYes({
|
|
|
265
272
|
terminalRender.write(text);
|
|
266
273
|
// todo: .onStatus((msg)=> shell.write(msg))
|
|
267
274
|
if (process.stdin.isTTY) return; // only handle it when stdin is not tty
|
|
268
|
-
if (text.includes('\u001b[6n')) return; // only asked
|
|
269
|
-
|
|
275
|
+
if (text.includes('\u001b[6n')) return; // only asked for cursor position
|
|
270
276
|
// todo: use terminalRender API to get cursor position when new version is available
|
|
271
277
|
// xterm replies CSI row; column R if asked cursor position
|
|
272
278
|
// https://en.wikipedia.org/wiki/ANSI_escape_code#:~:text=citation%20needed%5D-,xterm%20replies,-CSI%20row%C2%A0%3B
|
|
273
279
|
// when agent asking position, respond with row; col
|
|
274
|
-
const rendered = terminalRender.render();
|
|
275
|
-
const row =
|
|
276
|
-
|
|
277
|
-
|
|
280
|
+
// const rendered = terminalRender.render();
|
|
281
|
+
const { col, row } = terminalRender.getCursorPosition();
|
|
282
|
+
console.log(
|
|
283
|
+
`[${cli}-yes] Responding cursor position: row=${row}, col=${col}`,
|
|
284
|
+
);
|
|
285
|
+
shell.write(`\u001b[${row};${col}R`); // reply cli when getting cursor position
|
|
286
|
+
// const row = rendered.split('\n').length + 1;
|
|
287
|
+
// const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
|
|
278
288
|
})
|
|
279
289
|
|
|
280
290
|
// auto-response
|
|
@@ -282,7 +292,10 @@ export default async function claudeYes({
|
|
|
282
292
|
e
|
|
283
293
|
.map((e) => removeControlCharacters(e))
|
|
284
294
|
.map((e) => e.replaceAll('\r', '')) // remove carriage return
|
|
285
|
-
.lines({ EOL: 'NONE' })
|
|
295
|
+
// .lines({ EOL: 'NONE' })
|
|
296
|
+
.forEach((e) =>
|
|
297
|
+
appendFile('io.log', 'output|' + JSON.stringify(e) + '\n'),
|
|
298
|
+
) // for debugging
|
|
286
299
|
// Generic auto-response handler driven by CLI_CONFIGURES
|
|
287
300
|
.forEach(async (e, i) => {
|
|
288
301
|
const conf =
|
|
@@ -296,13 +309,16 @@ export default async function claudeYes({
|
|
|
296
309
|
}
|
|
297
310
|
|
|
298
311
|
// enter matchers: send Enter when any enter regex matches
|
|
299
|
-
if (conf.enter?.some((rx: RegExp) => e.match(rx)))
|
|
312
|
+
if (conf.enter?.some((rx: RegExp) => e.match(rx))) {
|
|
300
313
|
await sendEnter(300); // send Enter after 300ms idle wait
|
|
314
|
+
}
|
|
301
315
|
|
|
302
316
|
// fatal matchers: set isFatal flag when matched
|
|
303
|
-
if (conf.fatal?.some((rx: RegExp) => e.match(rx)))
|
|
317
|
+
if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {
|
|
318
|
+
isFatal = true;
|
|
319
|
+
await exitAgent();
|
|
320
|
+
}
|
|
304
321
|
})
|
|
305
|
-
// .forEach(e => appendFile('.cache/io.log', "output|" + JSON.stringify(e) + '\n')) // for debugging
|
|
306
322
|
.run(),
|
|
307
323
|
)
|
|
308
324
|
.map((e) =>
|
|
@@ -312,22 +328,7 @@ export default async function claudeYes({
|
|
|
312
328
|
.then(() => null); // run it immediately without await
|
|
313
329
|
|
|
314
330
|
// wait for cli ready and send prompt if provided
|
|
315
|
-
if (prompt)
|
|
316
|
-
(async () => {
|
|
317
|
-
// console.log(`[${cli}-yes] Ready to send prompt to ${cli}: ${prompt}`);
|
|
318
|
-
// idleWaiter.ping();
|
|
319
|
-
// console.log(
|
|
320
|
-
// 'await idleWaiter.wait(1000); // wait a bit for claude to start'
|
|
321
|
-
// );
|
|
322
|
-
// await idleWaiter.wait(1000); // wait a bit for claude to start
|
|
323
|
-
// console.log('await stdinReady.wait();');
|
|
324
|
-
// await stdinReady.wait();
|
|
325
|
-
// console.log(`[${cli}-yes] Waiting for ${cli} to be ready...`);
|
|
326
|
-
// console.log('await idleWaiter.wait(200);');
|
|
327
|
-
// await idleWaiter.wait(200);
|
|
328
|
-
// console.log(`[${cli}-yes] Sending prompt to ${cli}: ${prompt}`);
|
|
329
|
-
await sendMessage(prompt);
|
|
330
|
-
})();
|
|
331
|
+
if (prompt) await sendMessage(prompt);
|
|
331
332
|
|
|
332
333
|
const exitCode = await pendingExitCode.promise; // wait for the shell to exit
|
|
333
334
|
console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
|
|
@@ -385,7 +386,9 @@ export default async function claudeYes({
|
|
|
385
386
|
|
|
386
387
|
function getTerminalDimensions() {
|
|
387
388
|
return {
|
|
388
|
-
|
|
389
|
+
// TODO: enforce minimum cols/rows to avoid layout issues
|
|
390
|
+
// cols: Math.max(process.stdout.columns, 80),
|
|
391
|
+
cols: process.stdout.columns,
|
|
389
392
|
rows: process.stdout.rows,
|
|
390
393
|
};
|
|
391
394
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-yes",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A wrapper tool that automates interactions with
|
|
3
|
+
"version": "1.22.0",
|
|
4
|
+
"description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"ai",
|
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
"bin": {
|
|
34
34
|
"claude-yes": "dist/claude-yes.js",
|
|
35
35
|
"codex-yes": "dist/codex-yes.js",
|
|
36
|
-
"
|
|
36
|
+
"copilot-yes": "dist/copilot-yes.js",
|
|
37
37
|
"cursor-yes": "dist/cursor-yes.js",
|
|
38
|
-
"
|
|
38
|
+
"gemini-yes": "dist/gemini-yes.js",
|
|
39
|
+
"grok-yes": "dist/grok-yes.js"
|
|
39
40
|
},
|
|
40
41
|
"directories": {
|
|
41
42
|
"doc": "docs"
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"build": "bun build index.ts cli.ts --packages=external --outdir=dist --target=node --sourcemap",
|
|
49
50
|
"postbuild": "bun ./postbuild.ts",
|
|
50
51
|
"dev": "tsx index.ts",
|
|
51
|
-
"fmt": "bunx @biomejs/biome check --fix",
|
|
52
|
+
"fmt": "bunx @biomejs/biome check --fix && bunx sort-package-json",
|
|
52
53
|
"prepack": "bun run build",
|
|
53
54
|
"prepare": "bunx husky",
|
|
54
55
|
"test": "vitest"
|
|
@@ -58,6 +59,11 @@
|
|
|
58
59
|
"bunx @biomejs/biome check --fix"
|
|
59
60
|
]
|
|
60
61
|
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"bun-pty": "^0.3.2",
|
|
64
|
+
"p-map": "^7.0.3",
|
|
65
|
+
"phpdie": "^1.7.0"
|
|
66
|
+
},
|
|
61
67
|
"devDependencies": {
|
|
62
68
|
"@biomejs/biome": "^2.2.5",
|
|
63
69
|
"@types/bun": "^1.2.18",
|
|
@@ -73,18 +79,13 @@
|
|
|
73
79
|
"semantic-release": "^24.2.6",
|
|
74
80
|
"sflow": "^1.20.2",
|
|
75
81
|
"strip-ansi-control-characters": "^2.0.0",
|
|
76
|
-
"terminal-render": "^1.
|
|
82
|
+
"terminal-render": "^1.2.0",
|
|
77
83
|
"tsx": "^4.20.3",
|
|
78
84
|
"vitest": "^3.2.4",
|
|
79
85
|
"yargs": "^18.0.0"
|
|
80
86
|
},
|
|
81
87
|
"peerDependencies": {
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
},
|
|
85
|
-
"dependencies": {
|
|
86
|
-
"bun-pty": "^0.3.2",
|
|
87
|
-
"p-map": "^7.0.3",
|
|
88
|
-
"phpdie": "^1.7.0"
|
|
88
|
+
"node-pty": "^1.0.0",
|
|
89
|
+
"typescript": "^5.8.3"
|
|
89
90
|
}
|
|
90
91
|
}
|
package/postbuild.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
#! /usr/bin/env bun
|
|
2
|
+
import { execaCommand } from 'execa';
|
|
2
3
|
import { copyFile } from 'fs/promises';
|
|
4
|
+
import { CLI_CONFIGURES } from '.';
|
|
3
5
|
import * as pkg from './package.json';
|
|
4
6
|
|
|
5
7
|
const src = 'dist/cli.js';
|
|
6
|
-
await Promise.all(
|
|
8
|
+
await Promise.all(
|
|
9
|
+
Object.keys(CLI_CONFIGURES).map(async (cli) => {
|
|
10
|
+
const dst = `dist/${cli}-yes.js`;
|
|
11
|
+
if (!pkg.bin?.[cli as keyof typeof pkg.bin])
|
|
12
|
+
await execaCommand(`npm pkg set bin.${cli}-yes=${dst}`);
|
|
13
|
+
await copyFile(src, dst);
|
|
14
|
+
}),
|
|
15
|
+
);
|