claude-yes 1.24.1 → 1.25.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/ts/tryCatch.ts ADDED
@@ -0,0 +1,25 @@
1
+ // curried overload
2
+ export function catcher<F extends (...args: any[]) => any, R>(
3
+ catchFn: (error: unknown) => R,
4
+ ): (fn: F) => (...args: Parameters<F>) => ReturnType<F> | R;
5
+
6
+ // direct overload
7
+ export function catcher<F extends (...args: any[]) => any, R>(
8
+ catchFn: (error: unknown) => R,
9
+ fn: F,
10
+ ): (...args: Parameters<F>) => ReturnType<F> | R;
11
+
12
+ // implementation
13
+ export function catcher<F extends (...args: any[]) => any, R>(
14
+ catchFn: (error: unknown) => R,
15
+ fn?: F,
16
+ ) {
17
+ if (!fn) return (fn: F) => catcher(catchFn, fn) as any;
18
+ return (...args: Parameters<F>) => {
19
+ try {
20
+ return fn(...args);
21
+ } catch (error) {
22
+ return catchFn(error);
23
+ }
24
+ };
25
+ }
package/ts/utils.ts ADDED
@@ -0,0 +1,23 @@
1
+ export function sleepms(ms: number) {
2
+ return new Promise((resolve) => setTimeout(resolve, ms));
3
+ }
4
+ export function deepMixin<T>(target: T, source: DeepPartial<T>): T {
5
+ for (const key in source) {
6
+ if (
7
+ source[key] &&
8
+ typeof source[key] === 'object' &&
9
+ !Array.isArray(source[key])
10
+ ) {
11
+ if (!target[key] || typeof target[key] !== 'object') {
12
+ (target as any)[key] = {};
13
+ }
14
+ deepMixin(target[key], source[key] as any);
15
+ } else if (source[key] !== undefined) {
16
+ (target as any)[key] = source[key];
17
+ }
18
+ }
19
+ return target;
20
+ }
21
+ export type DeepPartial<T> = {
22
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
23
+ };
package/ts/yesLog.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { appendFileSync, rmSync } from 'node:fs';
2
+ import tsaComposer from 'tsa-composer';
3
+ import { catcher } from './tryCatch';
4
+
5
+ let initial = true;
6
+
7
+ /**
8
+ * Log messages to agent-yes.log file
9
+ * Each message is appended as a new line
10
+ * The log file is cleared on the first call
11
+ *
12
+ * use only for debug, enabled when process.env.VERBOSE is set
13
+ */
14
+ export const yesLog = tsaComposer()(
15
+ catcher(
16
+ (error) => {
17
+ console.error('yesLog error:', error);
18
+ },
19
+ function yesLog(msg: string) {
20
+ // process.stdout.write(`${msg}\r`); // touch process to avoid "The process is not running a TTY." error
21
+ if (!process.env.VERBOSE) return; // no-op if not verbose
22
+ if (initial) rmSync('./agent-yes.log'); // ignore error if file doesn't exist
23
+ initial = false;
24
+ appendFileSync('./agent-yes.log', `${msg}\n`);
25
+ },
26
+ ),
27
+ );
package/cli-idle.spec.ts DELETED
@@ -1,17 +0,0 @@
1
- import { exec } from 'child_process';
2
- import { fromStdio } from 'from-node-stream';
3
- import sflow from 'sflow';
4
- import { sleepms } from './utils';
5
-
6
- // 2025-08-11 ok
7
- it.skip('CLI --exit-on-idle flag with custom timeout', async () => {
8
- const p = exec(
9
- `bunx tsx ./cli.ts --verbose --logFile=./cli-idle.log --exit-on-idle=3s "say hello and wait"`,
10
- );
11
- const tr = new TransformStream<string, string>();
12
- const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
13
- console.log(output);
14
- expect(output).toContain('hello');
15
- await sleepms(1000); // wait for process exit
16
- expect(p.exitCode).toBe(0);
17
- }, 20e3);
package/cli.test.ts DELETED
@@ -1,63 +0,0 @@
1
- import { exec } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { readFile, unlink } from 'node:fs/promises';
4
- import { execaCommand } from 'execa';
5
- import { fromStdio } from 'from-node-stream';
6
- import sflow from 'sflow';
7
- import { beforeAll, describe, expect, it } from 'vitest';
8
- import { IdleWaiter } from './idleWaiter';
9
- import { sleepms } from './utils';
10
-
11
- it('Write file with auto bypass prompts', async () => {
12
- const flagFile = './.cache/flag.json';
13
- await cleanup();
14
- async function cleanup() {
15
- await unlink(flagFile).catch(() => {});
16
- await unlink('./cli-rendered.log').catch(() => {});
17
- }
18
-
19
- const p = exec(
20
- `bunx tsx ./cli.ts --logFile=./cli-rendered.log --exit-on-idle=3s "just write {on: 1} into ./.cache/flag.json and wait"`,
21
- );
22
- const pExitCode = new Promise<number | null>((r) => p.once('exit', r));
23
-
24
- const tr = new TransformStream<string, string>();
25
- const w = tr.writable.getWriter();
26
-
27
- const exit = async () =>
28
- await sflow(['\r', '/exit', '\r', '\r'])
29
- .forEach(async (e) => {
30
- await sleepms(200);
31
- await w.write(e);
32
- })
33
- .run();
34
-
35
- // ping function to exit claude when idle
36
-
37
- const idleWaiter = new IdleWaiter();
38
- idleWaiter.wait(3000).then(() => exit());
39
-
40
- const output = await sflow(tr.readable)
41
- .by(fromStdio(p))
42
- .log()
43
- .forEach(() => idleWaiter.ping())
44
- .text();
45
-
46
- // expect the file exists
47
- expect(existsSync(flagFile)).toBe(true);
48
- // expect the output contains the file path
49
- expect(output).toContain(flagFile);
50
-
51
- // expect the file content to be 'on'
52
- expect(await new Response(await readFile(flagFile)).json()).toEqual({
53
- on: 1,
54
- });
55
-
56
- expect(await pExitCode).toBe(0); // expect the process to exit successfully
57
- expect(await readFile('./cli-rendered.log', 'utf8')).toBeTruthy();
58
-
59
- // clean
60
- await cleanup();
61
-
62
- // it usually takes 13s to run (10s for claude to solve this problem, 3s for idle watcher to exit)
63
- }, 30e3);
package/cli.ts DELETED
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
- import enhancedMs from 'enhanced-ms';
3
- import yargs from 'yargs';
4
- import { hideBin } from 'yargs/helpers';
5
- import claudeYes from '.';
6
-
7
- // cli entry point
8
- const argv = yargs(hideBin(process.argv))
9
- .usage('Usage: $0 [options] [claude args] [--] [prompts...]')
10
- .example(
11
- '$0 --exit-on-idle=30s --continue-on-crash "help me solve all todos in my codebase"',
12
- 'Run Claude with a 30 seconds idle timeout and continue on crash',
13
- )
14
- .option('continue-on-crash', {
15
- type: 'boolean',
16
- default: true,
17
- description:
18
- 'spawn Claude with --continue if it crashes, only works for claude',
19
- alias: 'c',
20
- })
21
- .option('log-file', {
22
- type: 'string',
23
- description: 'Log file to write to',
24
- })
25
- .option('cli', {
26
- type: 'string',
27
- description:
28
- 'CLI command to run. Supports: claude, gemini, codex, copilot, cursor, grok. Defaults to the CLI inferred from the executable name or "claude".',
29
- })
30
- .option('prompt', {
31
- type: 'string',
32
- description: 'Prompt to send to Claude',
33
- alias: 'p',
34
- })
35
- .option('verbose', {
36
- type: 'boolean',
37
- description: 'Enable verbose logging',
38
- default: false,
39
- })
40
- .option('exit-on-idle', {
41
- type: 'string',
42
- description: 'Exit after a period of inactivity, e.g., "5s" or "1m"',
43
- alias: 'e',
44
- })
45
- .option('disable-lock', {
46
- type: 'boolean',
47
- description:
48
- 'Disable the running lock feature that prevents concurrent agents in the same directory/repo',
49
- default: false,
50
- })
51
- .help()
52
- .version()
53
- .parserConfiguration({
54
- 'unknown-options-as-args': true,
55
- 'halt-at-non-option': true,
56
- })
57
- .parseSync();
58
-
59
- // detect cli name for cli, while package.json have multiple bin link: {"claude-yes": "cli.js", "codex-yes": "cli.js", "gemini-yes": "cli.js"}
60
- if (!argv.cli) {
61
- const cliName = process.argv[1]?.split('/').pop()?.split('-')[0];
62
- argv.cli = cliName || 'claude';
63
- }
64
-
65
- // Support: everything after a literal `--` is a prompt string. Example:
66
- // claude-yes --exit-on-idle=30s -- "help me refactor this"
67
- // In that example the prompt will be `help me refactor this` and won't be
68
- // passed as args to the underlying CLI binary.
69
- const rawArgs = process.argv.slice(2);
70
- const dashIndex = rawArgs.indexOf('--');
71
- let promptFromDash: string | undefined = undefined;
72
- let cliArgsForSpawn: string[] = [];
73
- if (dashIndex !== -1) {
74
- // join everything after `--` into a single prompt string
75
- const after = rawArgs.slice(dashIndex + 1);
76
- promptFromDash = after.join(' ');
77
- // use everything before `--` as the cli args
78
- cliArgsForSpawn = rawArgs.slice(0, dashIndex).map(String);
79
- } else {
80
- // fallback to yargs parsed positional args when `--` is not used
81
- cliArgsForSpawn = argv._.map((e) => String(e));
82
- }
83
-
84
- console.clear();
85
- const { exitCode, logs } = await claudeYes({
86
- cli: argv.cli,
87
- // prefer explicit --prompt / -p; otherwise use the text after `--` if present
88
- prompt: argv.prompt || promptFromDash,
89
- exitOnIdle: argv.exitOnIdle ? enhancedMs(argv.exitOnIdle) : undefined,
90
- cliArgs: cliArgsForSpawn,
91
- continueOnCrash: argv.continueOnCrash,
92
- logFile: argv.logFile,
93
- verbose: argv.verbose,
94
- disableLock: argv.disableLock,
95
- });
96
-
97
- process.exit(exitCode ?? 1);
package/postbuild.ts DELETED
@@ -1,15 +0,0 @@
1
- #! /usr/bin/env bun
2
- import { execaCommand } from 'execa';
3
- import { copyFile } from 'fs/promises';
4
- import { CLI_CONFIGURES } from '.';
5
- import * as pkg from './package.json';
6
-
7
- const src = 'dist/cli.js';
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
- );
package/utils.ts DELETED
@@ -1,3 +0,0 @@
1
- export function sleepms(ms: number) {
2
- return new Promise((resolve) => setTimeout(resolve, ms));
3
- }
File without changes
File without changes
File without changes