claude-yes 1.23.2 → 1.24.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/dist/index.js CHANGED
@@ -19,8 +19,8 @@ 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";
23
- import path from "path";
22
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
23
+ import path2 from "path";
24
24
  import DIE from "phpdie";
25
25
  import sflow from "sflow";
26
26
  import { TerminalTextRender } from "terminal-render";
@@ -67,6 +67,176 @@ function removeControlCharacters(str) {
67
67
  return str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
68
68
  }
69
69
 
70
+ // runningLock.ts
71
+ import { execSync } from "child_process";
72
+ import { existsSync } from "fs";
73
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
74
+ import { homedir } from "os";
75
+ import path from "path";
76
+ var LOCK_DIR = path.join(homedir(), ".claude-yes");
77
+ var LOCK_FILE = path.join(LOCK_DIR, "running.lock.json");
78
+ var MAX_RETRIES = 5;
79
+ var RETRY_DELAYS = [50, 100, 200, 400, 800];
80
+ var POLL_INTERVAL = 2000;
81
+ function isProcessRunning(pid) {
82
+ try {
83
+ process.kill(pid, 0);
84
+ return true;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ function getGitRoot(cwd) {
90
+ try {
91
+ const result = execSync("git rev-parse --show-toplevel", {
92
+ cwd,
93
+ encoding: "utf8",
94
+ stdio: ["pipe", "pipe", "ignore"]
95
+ });
96
+ return result.trim();
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ function isGitRepo(cwd) {
102
+ try {
103
+ const gitRoot = getGitRoot(cwd);
104
+ return gitRoot !== null;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ function resolveRealPath(p) {
110
+ try {
111
+ return path.resolve(p);
112
+ } catch {
113
+ return p;
114
+ }
115
+ }
116
+ function sleep(ms) {
117
+ return new Promise((resolve) => setTimeout(resolve, ms));
118
+ }
119
+ async function readLockFile() {
120
+ try {
121
+ await mkdir(LOCK_DIR, { recursive: true });
122
+ if (!existsSync(LOCK_FILE)) {
123
+ return { tasks: [] };
124
+ }
125
+ const content = await readFile(LOCK_FILE, "utf8");
126
+ const lockFile = JSON.parse(content);
127
+ lockFile.tasks = lockFile.tasks.filter((task) => {
128
+ if (isProcessRunning(task.pid))
129
+ return true;
130
+ return false;
131
+ });
132
+ return lockFile;
133
+ } catch (error) {
134
+ return { tasks: [] };
135
+ }
136
+ }
137
+ async function writeLockFile(lockFile, retryCount = 0) {
138
+ try {
139
+ await mkdir(LOCK_DIR, { recursive: true });
140
+ const tempFile = `${LOCK_FILE}.tmp.${process.pid}`;
141
+ await writeFile(tempFile, JSON.stringify(lockFile, null, 2), "utf8");
142
+ await rename(tempFile, LOCK_FILE);
143
+ } catch (error) {
144
+ if (retryCount < MAX_RETRIES) {
145
+ await sleep(RETRY_DELAYS[retryCount] || 800);
146
+ return writeLockFile(lockFile, retryCount + 1);
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+ async function checkLock(cwd, prompt) {
152
+ const resolvedCwd = resolveRealPath(cwd);
153
+ const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;
154
+ const lockKey = gitRoot || resolvedCwd;
155
+ const lockFile = await readLockFile();
156
+ const blockingTasks = lockFile.tasks.filter((task) => {
157
+ if (!isProcessRunning(task.pid))
158
+ return false;
159
+ if (task.status !== "running")
160
+ return false;
161
+ if (gitRoot && task.gitRoot) {
162
+ return task.gitRoot === gitRoot;
163
+ } else {
164
+ return task.cwd === lockKey;
165
+ }
166
+ });
167
+ return {
168
+ isLocked: blockingTasks.length > 0,
169
+ blockingTasks,
170
+ lockKey
171
+ };
172
+ }
173
+ async function addTask(task) {
174
+ const lockFile = await readLockFile();
175
+ lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== task.pid);
176
+ lockFile.tasks.push(task);
177
+ await writeLockFile(lockFile);
178
+ }
179
+ async function updateTaskStatus(pid, status) {
180
+ const lockFile = await readLockFile();
181
+ const task = lockFile.tasks.find((t) => t.pid === pid);
182
+ if (task) {
183
+ task.status = status;
184
+ await writeLockFile(lockFile);
185
+ }
186
+ }
187
+ async function removeTask(pid) {
188
+ const lockFile = await readLockFile();
189
+ lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== pid);
190
+ await writeLockFile(lockFile);
191
+ }
192
+ async function waitForUnlock(blockingTasks, currentTask) {
193
+ const blockingTask = blockingTasks[0];
194
+ console.log(`⏳ Queueing for unlock of: ${blockingTask.task}`);
195
+ await addTask({ ...currentTask, status: "queued" });
196
+ let dots = 0;
197
+ while (true) {
198
+ await sleep(POLL_INTERVAL);
199
+ const lockCheck = await checkLock(currentTask.cwd, currentTask.task);
200
+ if (!lockCheck.isLocked) {
201
+ await updateTaskStatus(currentTask.pid, "running");
202
+ console.log(`
203
+ ✓ Lock released, starting task...`);
204
+ break;
205
+ }
206
+ dots = (dots + 1) % 4;
207
+ process.stdout.write(`\r⏳ Queueing${".".repeat(dots)}${" ".repeat(3 - dots)}`);
208
+ }
209
+ }
210
+ async function acquireLock(cwd, prompt = "no prompt provided") {
211
+ const resolvedCwd = resolveRealPath(cwd);
212
+ const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;
213
+ const task = {
214
+ cwd: resolvedCwd,
215
+ gitRoot: gitRoot || undefined,
216
+ task: prompt.substring(0, 100),
217
+ pid: process.pid,
218
+ status: "running",
219
+ startedAt: Date.now(),
220
+ lockedAt: Date.now()
221
+ };
222
+ const lockCheck = await checkLock(resolvedCwd, prompt);
223
+ if (lockCheck.isLocked) {
224
+ await waitForUnlock(lockCheck.blockingTasks, task);
225
+ } else {
226
+ await addTask(task);
227
+ }
228
+ }
229
+ async function releaseLock(pid = process.pid) {
230
+ await removeTask(pid);
231
+ }
232
+ async function updateCurrentTaskStatus(status, pid = process.pid) {
233
+ await updateTaskStatus(pid, status);
234
+ }
235
+ function shouldUseLock(cwd) {
236
+ const resolvedCwd = resolveRealPath(cwd);
237
+ return true;
238
+ }
239
+
70
240
  // index.ts
71
241
  var CLI_CONFIGURES = {
72
242
  grok: {
@@ -105,7 +275,7 @@ var CLI_CONFIGURES = {
105
275
  },
106
276
  copilot: {
107
277
  install: "npm install -g @github/copilot",
108
- ready: [/^ > /],
278
+ ready: [/^ +> /, /Ctrl\+c Exit/],
109
279
  enter: [/ │ ❯ 1. Yes, proceed/, /❯ 1. Yes/],
110
280
  fatal: []
111
281
  },
@@ -127,13 +297,36 @@ async function claudeYes({
127
297
  exitOnIdle,
128
298
  logFile,
129
299
  removeControlCharactersFromStdout = false,
130
- verbose = false
300
+ verbose = false,
301
+ disableLock = false
131
302
  } = {}) {
132
303
  const continueArgs = {
133
304
  codex: "resume --last".split(" "),
134
305
  claude: "--continue".split(" "),
135
306
  gemini: []
136
307
  };
308
+ const workingDir = cwd ?? process.cwd();
309
+ if (!disableLock && shouldUseLock(workingDir)) {
310
+ await acquireLock(workingDir, prompt ?? "Interactive session");
311
+ }
312
+ const cleanupLock = async () => {
313
+ if (!disableLock && shouldUseLock(workingDir)) {
314
+ await releaseLock().catch(() => null);
315
+ }
316
+ };
317
+ process.on("exit", () => {
318
+ if (!disableLock) {
319
+ releaseLock().catch(() => null);
320
+ }
321
+ });
322
+ process.on("SIGINT", async () => {
323
+ await cleanupLock();
324
+ process.exit(130);
325
+ });
326
+ process.on("SIGTERM", async () => {
327
+ await cleanupLock();
328
+ process.exit(143);
329
+ });
137
330
  process.stdin.setRawMode?.(true);
138
331
  let isFatal = false;
139
332
  const stdinReady = new ReadyManager;
@@ -214,7 +407,6 @@ async function claudeYes({
214
407
  if (!text.includes("\x1B[6n"))
215
408
  return;
216
409
  const { col, row } = terminalRender.getCursorPosition();
217
- console.log(`[${cli}-yes] Responding cursor position: row=${row}, col=${col}`);
218
410
  shell.write(`\x1B[${row};${col}R`);
219
411
  }).forkTo((e) => e.map((e2) => removeControlCharacters(e2)).map((e2) => e2.replaceAll("\r", "")).by((s) => {
220
412
  if (cli === "codex")
@@ -244,11 +436,15 @@ async function claudeYes({
244
436
  await sendMessage(prompt);
245
437
  const exitCode = await pendingExitCode.promise;
246
438
  console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
439
+ if (!disableLock && shouldUseLock(workingDir)) {
440
+ await updateCurrentTaskStatus(exitCode === 0 ? "completed" : "failed").catch(() => null);
441
+ await releaseLock().catch(() => null);
442
+ }
247
443
  if (logFile) {
248
444
  verbose && console.log(`[${cli}-yes] Writing rendered logs to ${logFile}`);
249
- const logFilePath = path.resolve(logFile);
250
- await mkdir(path.dirname(logFilePath), { recursive: true }).catch(() => null);
251
- await writeFile(logFilePath, terminalRender.render());
445
+ const logFilePath = path2.resolve(logFile);
446
+ await mkdir2(path2.dirname(logFilePath), { recursive: true }).catch(() => null);
447
+ await writeFile2(logFilePath, terminalRender.render());
252
448
  }
253
449
  return { exitCode, logs: terminalRender.render() };
254
450
  async function sendEnter(waitms = 1000) {
@@ -301,5 +497,5 @@ export {
301
497
  CLI_CONFIGURES
302
498
  };
303
499
 
304
- //# debugId=D17BB3E3D1DC936764756E2164756E21
500
+ //# debugId=6EB7DAB2DD725AD464756E2164756E21
305
501
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../index.ts", "../idleWaiter.ts", "../ReadyManager.ts", "../removeControlCharacters.ts"],
3
+ "sources": ["../index.ts", "../idleWaiter.ts", "../ReadyManager.ts", "../removeControlCharacters.ts", "../runningLock.ts"],
4
4
  "sourcesContent": [
5
- "import { fromReadable, fromWritable } from 'from-node-stream';\nimport { appendFile, mkdir, rm, writeFile } from 'fs/promises';\nimport path from 'path';\nimport DIE from 'phpdie';\nimport sflow from 'sflow';\nimport { TerminalTextRender } from 'terminal-render';\nimport tsaComposer from 'tsa-composer';\nimport { IdleWaiter } from './idleWaiter';\nimport { ReadyManager } from './ReadyManager';\nimport { removeControlCharacters } from './removeControlCharacters';\n\n// const yesLog = tsaComposer()(async function yesLog(msg: string) {\n// // await rm('agent-yes.log').catch(() => null); // ignore error if file doesn't exist\n// await appendFile(\"agent-yes.log\", `${msg}\\n`).catch(() => null);\n// });\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 ready: [/\\? for shortcuts/], // 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 /> 1. Yes, allow Codex to work in this folder/,\n /> 1. Approve and run now/,\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 agent-cli with automatic yes/no responses\n * @param options Configuration options\n * @param options.continueOnCrash - If true, automatically restart agent-cli when it crashes:\n * 1. Shows message 'agent-cli crashed, restarting..'\n * 2. Spawns a new 'agent-cli --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 agent-cli is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false\n * @param options.cliArgs - Additional arguments to pass to the agent-cli 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 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 const nextStdout = 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 nextStdout.ready();\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 nextStdout.ready();\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\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 .by((s) => {\n if (cli === 'codex') return s; // codex use cursor-move csi code insteadof \\n to move lines, so the output have no \\n at all, this hack prevents stuck on unended line\n return s.lines({ EOL: 'NONE' }); // other clis use ink, which is rerendering the block based on \\n lines\n })\n // .forEach((e) => yesLog`output|${e}`) // 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 // await yesLog`ready |${e}`;\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 yesLog`enter |${e}`;\n await sendEnter(300); // send Enter after 300ms idle wait\n return;\n }\n\n // fatal matchers: set isFatal flag when matched\n if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {\n // await yesLog`fatal |${e}`;\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 (cli === 'codex') shell.write(`\\u001b[1;1R`); // send cursor position response when stdin is not tty\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 await idleWaiter.wait(waitms);\n const et = Date.now();\n // process.stdout.write(`\\ridleWaiter.wait(${waitms}) took ${et - st}ms\\r`);\n // await yesLog`sendEn| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`;\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 // await yesLog`send |${message}`;\n shell.write(message);\n nextStdout.unready();\n idleWaiter.ping(); // just sent a message, wait for echo\n await nextStdout.wait();\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 if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty\n return {\n // TODO: enforce minimum cols/rows to avoid layout issues\n // cols: Math.max(process.stdout.columns, 80),\n cols: Math.min(Math.max(20, 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, rm, writeFile } from 'fs/promises';\nimport path from 'path';\nimport DIE from 'phpdie';\nimport sflow from 'sflow';\nimport { TerminalTextRender } from 'terminal-render';\nimport tsaComposer from 'tsa-composer';\nimport { IdleWaiter } from './idleWaiter';\nimport { ReadyManager } from './ReadyManager';\nimport { removeControlCharacters } from './removeControlCharacters';\nimport {\n acquireLock,\n releaseLock,\n shouldUseLock,\n updateCurrentTaskStatus,\n} from './runningLock';\n\n// const yesLog = tsaComposer()(async function yesLog(msg: string) {\n// // await rm('agent-yes.log').catch(() => null); // ignore error if file doesn't exist\n// await appendFile(\"agent-yes.log\", `${msg}\\n`).catch(() => null);\n// });\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 ready: [/\\? for shortcuts/], // 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 /> 1. Yes, allow Codex to work in this folder/,\n /> 1. Approve and run now/,\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: [/^ +> /, /Ctrl\\+c Exit/],\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 agent-cli with automatic yes/no responses\n * @param options Configuration options\n * @param options.continueOnCrash - If true, automatically restart agent-cli when it crashes:\n * 1. Shows message 'agent-cli crashed, restarting..'\n * 2. Spawns a new 'agent-cli --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 agent-cli is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false\n * @param options.cliArgs - Additional arguments to pass to the agent-cli CLI\n * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY\n * @param options.disableLock - Disable the running lock feature that prevents concurrent agents in the same directory/repo\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 * disableLock: false, // disable running lock (default is false)\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 disableLock = 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 disableLock?: boolean;\n} = {}) {\n const continueArgs = {\n codex: 'resume --last'.split(' '),\n claude: '--continue'.split(' '),\n gemini: [], // not possible yet\n };\n\n // Acquire lock before starting agent (if in git repo or same cwd and lock is not disabled)\n const workingDir = cwd ?? process.cwd();\n if (!disableLock && shouldUseLock(workingDir)) {\n await acquireLock(workingDir, prompt ?? 'Interactive session');\n }\n\n // Register cleanup handlers for lock release\n const cleanupLock = async () => {\n if (!disableLock && shouldUseLock(workingDir)) {\n await releaseLock().catch(() => null); // Ignore errors during cleanup\n }\n };\n\n process.on('exit', () => {\n if (!disableLock) {\n releaseLock().catch(() => null);\n }\n });\n process.on('SIGINT', async () => {\n await cleanupLock();\n process.exit(130);\n });\n process.on('SIGTERM', async () => {\n await cleanupLock();\n process.exit(143);\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 const nextStdout = 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 nextStdout.ready();\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 nextStdout.ready();\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\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 shell.write(`\\u001b[${row};${col}R`); // reply cli when getting cursor position\n // await yesLog(`cursor|respond position: row=${row}, col=${col}`);\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 .by((s) => {\n if (cli === 'codex') return s; // codex use cursor-move csi code insteadof \\n to move lines, so the output have no \\n at all, this hack prevents stuck on unended line\n return s.lines({ EOL: 'NONE' }); // other clis use ink, which is rerendering the block based on \\n lines\n })\n // .forEach((e) => yesLog`output|${e}`) // 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 // await yesLog`ready |${e}`;\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 yesLog`enter |${e}`;\n await sendEnter(300); // send Enter after 300ms idle wait\n return;\n }\n\n // fatal matchers: set isFatal flag when matched\n if (conf.fatal?.some((rx: RegExp) => e.match(rx))) {\n // await yesLog`fatal |${e}`;\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 (cli === 'codex') shell.write(`\\u001b[1;1R`); // send cursor position response when stdin is not tty\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 // Update task status and release lock\n if (!disableLock && shouldUseLock(workingDir)) {\n await updateCurrentTaskStatus(\n exitCode === 0 ? 'completed' : 'failed',\n ).catch(() => null);\n await releaseLock().catch(() => null);\n }\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 await idleWaiter.wait(waitms);\n const et = Date.now();\n // process.stdout.write(`\\ridleWaiter.wait(${waitms}) took ${et - st}ms\\r`);\n // await yesLog`sendEn| idleWaiter.wait(${String(waitms)}) took ${String(et - st)}ms`;\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 // await yesLog`send |${message}`;\n shell.write(message);\n nextStdout.unready();\n idleWaiter.ping(); // just sent a message, wait for echo\n await nextStdout.wait();\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 if (!process.stdout.isTTY) return { cols: 80, rows: 30 }; // default size when not tty\n return {\n // TODO: enforce minimum cols/rows to avoid layout issues\n // cols: Math.max(process.stdout.columns, 80),\n cols: Math.min(Math.max(20, 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",
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
- "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"
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
+ "import { execSync } from 'child_process';\nimport { existsSync } from 'fs';\nimport { mkdir, readFile, rename, unlink, writeFile } from 'fs/promises';\nimport { homedir } from 'os';\nimport path from 'path';\n\nexport interface Task {\n cwd: string;\n gitRoot?: string;\n task: string;\n pid: number;\n status: 'running' | 'queued' | 'completed' | 'failed';\n startedAt: number;\n lockedAt: number;\n}\n\nexport interface LockFile {\n tasks: Task[];\n}\n\ninterface LockCheckResult {\n isLocked: boolean;\n blockingTasks: Task[];\n lockKey: string;\n}\n\nconst LOCK_DIR = path.join(homedir(), '.claude-yes');\nconst LOCK_FILE = path.join(LOCK_DIR, 'running.lock.json');\nconst MAX_RETRIES = 5;\nconst RETRY_DELAYS = [50, 100, 200, 400, 800]; // exponential backoff in ms\nconst POLL_INTERVAL = 2000; // 2 seconds\n\n/**\n * Check if a process is running\n */\nfunction isProcessRunning(pid: number): boolean {\n try {\n // Sending signal 0 checks if process exists without killing it\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get git repository root for a directory\n */\nfunction getGitRoot(cwd: string): string | null {\n try {\n const result = execSync('git rev-parse --show-toplevel', {\n cwd,\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'ignore'],\n });\n return result.trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Check if directory is in a git repository\n */\nfunction isGitRepo(cwd: string): boolean {\n try {\n const gitRoot = getGitRoot(cwd);\n return gitRoot !== null;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve path to real path (handling symlinks)\n */\nfunction resolveRealPath(p: string): string {\n try {\n return path.resolve(p);\n } catch {\n return p;\n }\n}\n\n/**\n * Sleep for a given number of milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Read lock file with retry logic and stale lock cleanup\n */\nasync function readLockFile(): Promise<LockFile> {\n try {\n await mkdir(LOCK_DIR, { recursive: true });\n\n if (!existsSync(LOCK_FILE)) {\n return { tasks: [] };\n }\n\n const content = await readFile(LOCK_FILE, 'utf8');\n const lockFile = JSON.parse(content) as LockFile;\n\n // Clean stale locks while reading\n lockFile.tasks = lockFile.tasks.filter((task) => {\n if (isProcessRunning(task.pid)) return true;\n return false;\n });\n\n return lockFile;\n } catch (error) {\n // If file is corrupted or doesn't exist, return empty lock file\n return { tasks: [] };\n }\n}\n\n/**\n * Write lock file atomically with retry logic\n */\nasync function writeLockFile(\n lockFile: LockFile,\n retryCount = 0,\n): Promise<void> {\n try {\n await mkdir(LOCK_DIR, { recursive: true });\n\n const tempFile = `${LOCK_FILE}.tmp.${process.pid}`;\n await writeFile(tempFile, JSON.stringify(lockFile, null, 2), 'utf8');\n\n // Atomic rename\n await rename(tempFile, LOCK_FILE);\n } catch (error) {\n if (retryCount < MAX_RETRIES) {\n // Exponential backoff retry\n await sleep(RETRY_DELAYS[retryCount] || 800);\n return writeLockFile(lockFile, retryCount + 1);\n }\n throw error;\n }\n}\n\n/**\n * Check if lock exists for the current working directory\n */\nasync function checkLock(\n cwd: string,\n prompt: string,\n): Promise<LockCheckResult> {\n const resolvedCwd = resolveRealPath(cwd);\n const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;\n const lockKey = gitRoot || resolvedCwd;\n\n const lockFile = await readLockFile();\n\n // Find running tasks for this location\n const blockingTasks = lockFile.tasks.filter((task) => {\n if (!isProcessRunning(task.pid)) return false; // Skip stale locks\n if (task.status !== 'running') return false; // Only check running tasks\n\n if (gitRoot && task.gitRoot) {\n // In git repo: check by git root\n return task.gitRoot === gitRoot;\n } else {\n // Not in git repo: exact cwd match\n return task.cwd === lockKey;\n }\n });\n\n return {\n isLocked: blockingTasks.length > 0,\n blockingTasks,\n lockKey,\n };\n}\n\n/**\n * Add a task to the lock file\n */\nasync function addTask(task: Task): Promise<void> {\n const lockFile = await readLockFile();\n\n // Remove any existing task with same PID (shouldn't happen, but be safe)\n lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== task.pid);\n\n lockFile.tasks.push(task);\n await writeLockFile(lockFile);\n}\n\n/**\n * Update task status\n */\nasync function updateTaskStatus(\n pid: number,\n status: Task['status'],\n): Promise<void> {\n const lockFile = await readLockFile();\n const task = lockFile.tasks.find((t) => t.pid === pid);\n\n if (task) {\n task.status = status;\n await writeLockFile(lockFile);\n }\n}\n\n/**\n * Remove a task from the lock file\n */\nasync function removeTask(pid: number): Promise<void> {\n const lockFile = await readLockFile();\n lockFile.tasks = lockFile.tasks.filter((t) => t.pid !== pid);\n await writeLockFile(lockFile);\n}\n\n/**\n * Wait for lock to be released\n */\nasync function waitForUnlock(\n blockingTasks: Task[],\n currentTask: Task,\n): Promise<void> {\n const blockingTask = blockingTasks[0];\n console.log(`⏳ Queueing for unlock of: ${blockingTask.task}`);\n\n // Add current task as 'queued'\n await addTask({ ...currentTask, status: 'queued' });\n\n let dots = 0;\n while (true) {\n await sleep(POLL_INTERVAL);\n\n const lockCheck = await checkLock(currentTask.cwd, currentTask.task);\n\n if (!lockCheck.isLocked) {\n // Lock released, update status to running\n await updateTaskStatus(currentTask.pid, 'running');\n console.log(`\\n✓ Lock released, starting task...`);\n break;\n }\n\n // Show progress indicator\n dots = (dots + 1) % 4;\n process.stdout.write(\n `\\r⏳ Queueing${'.'.repeat(dots)}${' '.repeat(3 - dots)}`,\n );\n }\n}\n\n/**\n * Clean stale locks from the lock file\n */\nexport async function cleanStaleLocks(): Promise<void> {\n const lockFile = await readLockFile();\n\n const before = lockFile.tasks.length;\n lockFile.tasks = lockFile.tasks.filter((task) => {\n if (isProcessRunning(task.pid)) return true;\n\n console.log(`🧹 Cleaned stale lock for PID ${task.pid}`);\n return false;\n });\n\n if (lockFile.tasks.length !== before) {\n await writeLockFile(lockFile);\n }\n}\n\n/**\n * Acquire lock or wait if locked\n */\nexport async function acquireLock(\n cwd: string,\n prompt: string = 'no prompt provided',\n): Promise<void> {\n const resolvedCwd = resolveRealPath(cwd);\n const gitRoot = isGitRepo(resolvedCwd) ? getGitRoot(resolvedCwd) : null;\n\n const task: Task = {\n cwd: resolvedCwd,\n gitRoot: gitRoot || undefined,\n task: prompt.substring(0, 100), // Limit task description length\n pid: process.pid,\n status: 'running',\n startedAt: Date.now(),\n lockedAt: Date.now(),\n };\n\n const lockCheck = await checkLock(resolvedCwd, prompt);\n\n if (lockCheck.isLocked) {\n await waitForUnlock(lockCheck.blockingTasks, task);\n } else {\n await addTask(task);\n }\n}\n\n/**\n * Release lock for current process\n */\nexport async function releaseLock(pid: number = process.pid): Promise<void> {\n await removeTask(pid);\n}\n\n/**\n * Update status of current task\n */\nexport async function updateCurrentTaskStatus(\n status: Task['status'],\n pid: number = process.pid,\n): Promise<void> {\n await updateTaskStatus(pid, status);\n}\n\n/**\n * Check if we should use locking for this directory\n * Only use locking if we're in a git repository\n */\nexport function shouldUseLock(cwd: string): boolean {\n const resolvedCwd = resolveRealPath(cwd);\n // Only use lock if in git repo OR if explicitly requested\n // For now, use lock for all cases to handle same-dir conflicts\n return true;\n}\n"
9
10
  ],
10
- "mappings": "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",
11
- "debugId": "D17BB3E3D1DC936764756E2164756E21",
11
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AACA,kBAAqB,qBAAW;AAChC;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;;;ACLF;AACA;AACA;AACA;AACA;AAsBA,IAAM,WAAW,KAAK,KAAK,QAAQ,GAAG,aAAa;AACnD,IAAM,YAAY,KAAK,KAAK,UAAU,mBAAmB;AACzD,IAAM,cAAc;AACpB,IAAM,eAAe,CAAC,IAAI,KAAK,KAAK,KAAK,GAAG;AAC5C,IAAM,gBAAgB;AAKtB,SAAS,gBAAgB,CAAC,KAAsB;AAAA,EAC9C,IAAI;AAAA,IAEF,QAAQ,KAAK,KAAK,CAAC;AAAA,IACnB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAOX,SAAS,UAAU,CAAC,KAA4B;AAAA,EAC9C,IAAI;AAAA,IACF,MAAM,SAAS,SAAS,iCAAiC;AAAA,MACvD;AAAA,MACA,UAAU;AAAA,MACV,OAAO,CAAC,QAAQ,QAAQ,QAAQ;AAAA,IAClC,CAAC;AAAA,IACD,OAAO,OAAO,KAAK;AAAA,IACnB,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAOX,SAAS,SAAS,CAAC,KAAsB;AAAA,EACvC,IAAI;AAAA,IACF,MAAM,UAAU,WAAW,GAAG;AAAA,IAC9B,OAAO,YAAY;AAAA,IACnB,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAOX,SAAS,eAAe,CAAC,GAAmB;AAAA,EAC1C,IAAI;AAAA,IACF,OAAO,KAAK,QAAQ,CAAC;AAAA,IACrB,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;AAOX,SAAS,KAAK,CAAC,IAA2B;AAAA,EACxC,OAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AAAA;AAMzD,eAAe,YAAY,GAAsB;AAAA,EAC/C,IAAI;AAAA,IACF,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IAEzC,IAAI,CAAC,WAAW,SAAS,GAAG;AAAA,MAC1B,OAAO,EAAE,OAAO,CAAC,EAAE;AAAA,IACrB;AAAA,IAEA,MAAM,UAAU,MAAM,SAAS,WAAW,MAAM;AAAA,IAChD,MAAM,WAAW,KAAK,MAAM,OAAO;AAAA,IAGnC,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC,SAAS;AAAA,MAC/C,IAAI,iBAAiB,KAAK,GAAG;AAAA,QAAG,OAAO;AAAA,MACvC,OAAO;AAAA,KACR;AAAA,IAED,OAAO;AAAA,IACP,OAAO,OAAO;AAAA,IAEd,OAAO,EAAE,OAAO,CAAC,EAAE;AAAA;AAAA;AAOvB,eAAe,aAAa,CAC1B,UACA,aAAa,GACE;AAAA,EACf,IAAI;AAAA,IACF,MAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,IAEzC,MAAM,WAAW,GAAG,iBAAiB,QAAQ;AAAA,IAC7C,MAAM,UAAU,UAAU,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,MAAM;AAAA,IAGnE,MAAM,OAAO,UAAU,SAAS;AAAA,IAChC,OAAO,OAAO;AAAA,IACd,IAAI,aAAa,aAAa;AAAA,MAE5B,MAAM,MAAM,aAAa,eAAe,GAAG;AAAA,MAC3C,OAAO,cAAc,UAAU,aAAa,CAAC;AAAA,IAC/C;AAAA,IACA,MAAM;AAAA;AAAA;AAOV,eAAe,SAAS,CACtB,KACA,QAC0B;AAAA,EAC1B,MAAM,cAAc,gBAAgB,GAAG;AAAA,EACvC,MAAM,UAAU,UAAU,WAAW,IAAI,WAAW,WAAW,IAAI;AAAA,EACnE,MAAM,UAAU,WAAW;AAAA,EAE3B,MAAM,WAAW,MAAM,aAAa;AAAA,EAGpC,MAAM,gBAAgB,SAAS,MAAM,OAAO,CAAC,SAAS;AAAA,IACpD,IAAI,CAAC,iBAAiB,KAAK,GAAG;AAAA,MAAG,OAAO;AAAA,IACxC,IAAI,KAAK,WAAW;AAAA,MAAW,OAAO;AAAA,IAEtC,IAAI,WAAW,KAAK,SAAS;AAAA,MAE3B,OAAO,KAAK,YAAY;AAAA,IAC1B,EAAO;AAAA,MAEL,OAAO,KAAK,QAAQ;AAAA;AAAA,GAEvB;AAAA,EAED,OAAO;AAAA,IACL,UAAU,cAAc,SAAS;AAAA,IACjC;AAAA,IACA;AAAA,EACF;AAAA;AAMF,eAAe,OAAO,CAAC,MAA2B;AAAA,EAChD,MAAM,WAAW,MAAM,aAAa;AAAA,EAGpC,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ,KAAK,GAAG;AAAA,EAEhE,SAAS,MAAM,KAAK,IAAI;AAAA,EACxB,MAAM,cAAc,QAAQ;AAAA;AAM9B,eAAe,gBAAgB,CAC7B,KACA,QACe;AAAA,EACf,MAAM,WAAW,MAAM,aAAa;AAAA,EACpC,MAAM,OAAO,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,EAErD,IAAI,MAAM;AAAA,IACR,KAAK,SAAS;AAAA,IACd,MAAM,cAAc,QAAQ;AAAA,EAC9B;AAAA;AAMF,eAAe,UAAU,CAAC,KAA4B;AAAA,EACpD,MAAM,WAAW,MAAM,aAAa;AAAA,EACpC,SAAS,QAAQ,SAAS,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,EAC3D,MAAM,cAAc,QAAQ;AAAA;AAM9B,eAAe,aAAa,CAC1B,eACA,aACe;AAAA,EACf,MAAM,eAAe,cAAc;AAAA,EACnC,QAAQ,IAAI,6BAA4B,aAAa,MAAM;AAAA,EAG3D,MAAM,QAAQ,KAAK,aAAa,QAAQ,SAAS,CAAC;AAAA,EAElD,IAAI,OAAO;AAAA,EACX,OAAO,MAAM;AAAA,IACX,MAAM,MAAM,aAAa;AAAA,IAEzB,MAAM,YAAY,MAAM,UAAU,YAAY,KAAK,YAAY,IAAI;AAAA,IAEnE,IAAI,CAAC,UAAU,UAAU;AAAA,MAEvB,MAAM,iBAAiB,YAAY,KAAK,SAAS;AAAA,MACjD,QAAQ,IAAI;AAAA,kCAAoC;AAAA,MAChD;AAAA,IACF;AAAA,IAGA,QAAQ,OAAO,KAAK;AAAA,IACpB,QAAQ,OAAO,MACb,eAAc,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,GACtD;AAAA,EACF;AAAA;AAyBF,eAAsB,WAAW,CAC/B,KACA,SAAiB,sBACF;AAAA,EACf,MAAM,cAAc,gBAAgB,GAAG;AAAA,EACvC,MAAM,UAAU,UAAU,WAAW,IAAI,WAAW,WAAW,IAAI;AAAA,EAEnE,MAAM,OAAa;AAAA,IACjB,KAAK;AAAA,IACL,SAAS,WAAW;AAAA,IACpB,MAAM,OAAO,UAAU,GAAG,GAAG;AAAA,IAC7B,KAAK,QAAQ;AAAA,IACb,QAAQ;AAAA,IACR,WAAW,KAAK,IAAI;AAAA,IACpB,UAAU,KAAK,IAAI;AAAA,EACrB;AAAA,EAEA,MAAM,YAAY,MAAM,UAAU,aAAa,MAAM;AAAA,EAErD,IAAI,UAAU,UAAU;AAAA,IACtB,MAAM,cAAc,UAAU,eAAe,IAAI;AAAA,EACnD,EAAO;AAAA,IACL,MAAM,QAAQ,IAAI;AAAA;AAAA;AAOtB,eAAsB,WAAW,CAAC,MAAc,QAAQ,KAAoB;AAAA,EAC1E,MAAM,WAAW,GAAG;AAAA;AAMtB,eAAsB,uBAAuB,CAC3C,QACA,MAAc,QAAQ,KACP;AAAA,EACf,MAAM,iBAAiB,KAAK,MAAM;AAAA;AAO7B,SAAS,aAAa,CAAC,KAAsB;AAAA,EAClD,MAAM,cAAc,gBAAgB,GAAG;AAAA,EAGvC,OAAO;AAAA;;;AJ5SF,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,IAET,OAAO,CAAC,kBAAkB;AAAA,IAC1B,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,SAAS,cAAc;AAAA,IAC/B,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;AA8BA,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,EACV,cAAc;AAAA,IAaZ,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,EAGA,MAAM,aAAa,OAAO,QAAQ,IAAI;AAAA,EACtC,IAAI,CAAC,eAAe,cAAc,UAAU,GAAG;AAAA,IAC7C,MAAM,YAAY,YAAY,UAAU,qBAAqB;AAAA,EAC/D;AAAA,EAGA,MAAM,cAAc,YAAY;AAAA,IAC9B,IAAI,CAAC,eAAe,cAAc,UAAU,GAAG;AAAA,MAC7C,MAAM,YAAY,EAAE,MAAM,MAAM,IAAI;AAAA,IACtC;AAAA;AAAA,EAGF,QAAQ,GAAG,QAAQ,MAAM;AAAA,IACvB,IAAI,CAAC,aAAa;AAAA,MAChB,YAAY,EAAE,MAAM,MAAM,IAAI;AAAA,IAChC;AAAA,GACD;AAAA,EACD,QAAQ,GAAG,UAAU,YAAY;AAAA,IAC/B,MAAM,YAAY;AAAA,IAClB,QAAQ,KAAK,GAAG;AAAA,GACjB;AAAA,EACD,QAAQ,GAAG,WAAW,YAAY;AAAA,IAChC,MAAM,YAAY;AAAA,IAClB,QAAQ,KAAK,GAAG;AAAA,GACjB;AAAA,EAED,QAAQ,MAAM,aAAa,IAAI;AAAA,EAC/B,IAAI,UAAU;AAAA,EACd,MAAM,aAAa,IAAI;AAAA,EACvB,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,IAClC,WAAW,MAAM;AAAA,IAEjB,MAAM,aAAa,MAAM,IAAI;AAAA;AAAA,EAG/B,MAAM,OAAO,MAAM;AAAA,EACnB,MAAM,OAAO,SAAS,MAAM,GAAG,uBAAY;AAAA,IACzC,WAAW,MAAM;AAAA,IACjB,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,EAOH,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,CAAC,KAAK,SAAS,SAAW;AAAA,MAAG;AAAA,IAMjC,QAAQ,KAAK,QAAQ,eAAe,kBAAkB;AAAA,IACtD,MAAM,MAAM,QAAU,OAAO,MAAM;AAAA,GAIpC,EAGA,OAAO,CAAC,MACP,EACG,IAAI,CAAC,OAAM,wBAAwB,EAAC,CAAC,EACrC,IAAI,CAAC,OAAM,GAAE,WAAW,MAAM,EAAE,CAAC,EACjC,GAAG,CAAC,MAAM;AAAA,IACT,IAAI,QAAQ;AAAA,MAAS,OAAO;AAAA,IAC5B,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;AAAA,GAC/B,EAGA,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,MAEjD,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,MAEjD,MAAM,UAAU,GAAG;AAAA,MACnB;AAAA,IACF;AAAA,IAGA,IAAI,KAAK,OAAO,KAAK,CAAC,OAAe,GAAE,MAAM,EAAE,CAAC,GAAG;AAAA,MAEjD,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,QAAQ;AAAA,IAAS,MAAM,MAAM,WAAa;AAAA,EAC9C,IAAI;AAAA,IAAQ,MAAM,YAAY,MAAM;AAAA,EAEpC,MAAM,WAAW,MAAM,gBAAgB;AAAA,EACvC,QAAQ,IAAI,IAAI,YAAY,wBAAwB,UAAU;AAAA,EAG9D,IAAI,CAAC,eAAe,cAAc,UAAU,GAAG;AAAA,IAC7C,MAAM,wBACJ,aAAa,IAAI,cAAc,QACjC,EAAE,MAAM,MAAM,IAAI;AAAA,IAClB,MAAM,YAAY,EAAE,MAAM,MAAM,IAAI;AAAA,EACtC;AAAA,EAEA,IAAI,SAAS;AAAA,IACX,WAAW,QAAQ,IAAI,IAAI,qCAAqC,SAAS;AAAA,IACzE,MAAM,cAAc,MAAK,QAAQ,OAAO;AAAA,IACxC,MAAM,OAAM,MAAK,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC,EAAE,MAC1D,MAAM,IACR;AAAA,IACA,MAAM,WAAU,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,IACpB,MAAM,WAAW,KAAK,MAAM;AAAA,IAC5B,MAAM,KAAK,KAAK,IAAI;AAAA,IAIpB,MAAM,MAAM,IAAI;AAAA;AAAA,EAGlB,eAAe,WAAW,CAAC,SAAiB;AAAA,IAC1C,MAAM,WAAW,KAAK;AAAA,IAGtB,MAAM,MAAM,OAAO;AAAA,IACnB,WAAW,QAAQ;AAAA,IACnB,WAAW,KAAK;AAAA,IAChB,MAAM,WAAW,KAAK;AAAA,IACtB,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,IAAI,CAAC,QAAQ,OAAO;AAAA,MAAO,OAAO,EAAE,MAAM,IAAI,MAAM,GAAG;AAAA,IACvD,OAAO;AAAA,MAGL,MAAM,KAAK,IAAI,KAAK,IAAI,IAAI,QAAQ,OAAO,OAAO,GAAG,EAAE;AAAA,MACvD,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;",
12
+ "debugId": "6EB7DAB2DD725AD464756E2164756E21",
12
13
  "names": []
13
14
  }
package/index.ts CHANGED
@@ -8,6 +8,12 @@ import tsaComposer from 'tsa-composer';
8
8
  import { IdleWaiter } from './idleWaiter';
9
9
  import { ReadyManager } from './ReadyManager';
10
10
  import { removeControlCharacters } from './removeControlCharacters';
11
+ import {
12
+ acquireLock,
13
+ releaseLock,
14
+ shouldUseLock,
15
+ updateCurrentTaskStatus,
16
+ } from './runningLock';
11
17
 
12
18
  // const yesLog = tsaComposer()(async function yesLog(msg: string) {
13
19
  // // await rm('agent-yes.log').catch(() => null); // ignore error if file doesn't exist
@@ -63,7 +69,7 @@ export const CLI_CONFIGURES: Record<
63
69
  },
64
70
  copilot: {
65
71
  install: 'npm install -g @github/copilot',
66
- ready: [/^ > /],
72
+ ready: [/^ +> /, /Ctrl\+c Exit/],
67
73
  enter: [/ │ ❯ 1. Yes, proceed/, /❯ 1. Yes/],
68
74
  fatal: [],
69
75
  },
@@ -87,6 +93,7 @@ export const CLI_CONFIGURES: Record<
87
93
  * @param options.exitOnIdle - Exit when agent-cli is idle. Boolean or timeout in milliseconds, recommended 5000 - 60000, default is false
88
94
  * @param options.cliArgs - Additional arguments to pass to the agent-cli CLI
89
95
  * @param options.removeControlCharactersFromStdout - Remove ANSI control characters from stdout. Defaults to !process.stdout.isTTY
96
+ * @param options.disableLock - Disable the running lock feature that prevents concurrent agents in the same directory/repo
90
97
  *
91
98
  * @example
92
99
  * ```typescript
@@ -100,6 +107,7 @@ export const CLI_CONFIGURES: Record<
100
107
  * exitOnIdle: 30000, // exit after 30 seconds of idle
101
108
  * continueOnCrash: true, // restart if claude crashes, default is true
102
109
  * logFile: 'claude.log', // save logs to file
110
+ * disableLock: false, // disable running lock (default is false)
103
111
  * });
104
112
  * ```
105
113
  */
@@ -114,6 +122,7 @@ export default async function claudeYes({
114
122
  logFile,
115
123
  removeControlCharactersFromStdout = false, // = !process.stdout.isTTY,
116
124
  verbose = false,
125
+ disableLock = false,
117
126
  }: {
118
127
  cli?: (string & {}) | keyof typeof CLI_CONFIGURES;
119
128
  cliArgs?: string[];
@@ -125,6 +134,7 @@ export default async function claudeYes({
125
134
  logFile?: string;
126
135
  removeControlCharactersFromStdout?: boolean;
127
136
  verbose?: boolean;
137
+ disableLock?: boolean;
128
138
  } = {}) {
129
139
  const continueArgs = {
130
140
  codex: 'resume --last'.split(' '),
@@ -132,6 +142,33 @@ export default async function claudeYes({
132
142
  gemini: [], // not possible yet
133
143
  };
134
144
 
145
+ // Acquire lock before starting agent (if in git repo or same cwd and lock is not disabled)
146
+ const workingDir = cwd ?? process.cwd();
147
+ if (!disableLock && shouldUseLock(workingDir)) {
148
+ await acquireLock(workingDir, prompt ?? 'Interactive session');
149
+ }
150
+
151
+ // Register cleanup handlers for lock release
152
+ const cleanupLock = async () => {
153
+ if (!disableLock && shouldUseLock(workingDir)) {
154
+ await releaseLock().catch(() => null); // Ignore errors during cleanup
155
+ }
156
+ };
157
+
158
+ process.on('exit', () => {
159
+ if (!disableLock) {
160
+ releaseLock().catch(() => null);
161
+ }
162
+ });
163
+ process.on('SIGINT', async () => {
164
+ await cleanupLock();
165
+ process.exit(130);
166
+ });
167
+ process.on('SIGTERM', async () => {
168
+ await cleanupLock();
169
+ process.exit(143);
170
+ });
171
+
135
172
  process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
136
173
  let isFatal = false; // when true, do not restart on crash, and exit agent
137
174
  const stdinReady = new ReadyManager();
@@ -269,10 +306,8 @@ export default async function claudeYes({
269
306
  // when agent asking position, respond with row; col
270
307
  // const rendered = terminalRender.render();
271
308
  const { col, row } = terminalRender.getCursorPosition();
272
- console.log(
273
- `[${cli}-yes] Responding cursor position: row=${row}, col=${col}`,
274
- );
275
309
  shell.write(`\u001b[${row};${col}R`); // reply cli when getting cursor position
310
+ // await yesLog(`cursor|respond position: row=${row}, col=${col}`);
276
311
  // const row = rendered.split('\n').length + 1;
277
312
  // const col = (rendered.split('\n').slice(-1)[0]?.length || 0) + 1;
278
313
  })
@@ -329,6 +364,14 @@ export default async function claudeYes({
329
364
  const exitCode = await pendingExitCode.promise; // wait for the shell to exit
330
365
  console.log(`[${cli}-yes] ${cli} exited with code ${exitCode}`);
331
366
 
367
+ // Update task status and release lock
368
+ if (!disableLock && shouldUseLock(workingDir)) {
369
+ await updateCurrentTaskStatus(
370
+ exitCode === 0 ? 'completed' : 'failed',
371
+ ).catch(() => null);
372
+ await releaseLock().catch(() => null);
373
+ }
374
+
332
375
  if (logFile) {
333
376
  verbose && console.log(`[${cli}-yes] Writing rendered logs to ${logFile}`);
334
377
  const logFilePath = path.resolve(logFile);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.23.2",
3
+ "version": "1.24.0",
4
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",