claude-yes 1.29.2 → 1.30.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.29.2",
3
+ "version": "1.30.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",
@@ -50,13 +50,15 @@
50
50
  },
51
51
  "files": [
52
52
  "ts/*.ts",
53
- "dist"
53
+ "dist",
54
+ "scripts"
54
55
  ],
55
56
  "scripts": {
56
57
  "build": "bun run build:index && bun run build:cli",
57
58
  "postbuild": "bun ./ts/postbuild.ts",
58
- "build:cli": "bun build ts/cli.ts --outdir=dist --target=node --sourcemap --external=node-pty --external=bun-pty --external=from-node-stream",
59
- "build:index": "bun build ts/index.ts --outdir=dist --target=node --sourcemap --external=node-pty --external=bun-pty --external=from-node-stream",
59
+ "build:cli": "bun build ts/cli.ts --outdir=dist --target=node --sourcemap --external=bun-pty --external=node-pty --external=from-node-stream --external=bun",
60
+ "build:index": "bun build ts/index.ts --outdir=dist --target=node --sourcemap --external=bun-pty --external=node-pty --external=from-node-stream --external=bun",
61
+ "demo": "bun run build && bun link && claude-yes -- demo",
60
62
  "dev": "bun ts/index.ts",
61
63
  "fmt": "bunx @biomejs/biome check --fix && bunx sort-package-json",
62
64
  "prepack": "bun run build",
@@ -89,8 +91,11 @@
89
91
  ]
90
92
  },
91
93
  "dependencies": {
94
+ "@types/ms": "^2.1.0",
95
+ "bun": "^1.3.1",
92
96
  "bun-pty": "^0.3.2",
93
- "from-node-stream": "^0.0.11"
97
+ "from-node-stream": "^0.0.11",
98
+ "ms": "^2.1.3"
94
99
  },
95
100
  "devDependencies": {
96
101
  "@biomejs/biome": "^2.2.5",
@@ -103,7 +108,6 @@
103
108
  "@types/node": "^24.0.10",
104
109
  "@types/yargs": "^17.0.33",
105
110
  "cpu-wait": "^0.0.10",
106
- "enhanced-ms": "^4.1.0",
107
111
  "execa": "^9.6.0",
108
112
  "husky": "^9.1.7",
109
113
  "lint-staged": "^16.1.4",
@@ -119,7 +123,7 @@
119
123
  "yargs": "^18.0.0"
120
124
  },
121
125
  "peerDependencies": {
122
- "node-pty": "^1.0.0",
126
+ "node-pty": "^1.1.0-beta38",
123
127
  "typescript": "^5.8.3"
124
128
  }
125
129
  }
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { catcher } from './tryCatch';
2
+ import { catcher } from './catcher';
3
3
 
4
- describe('tryCatch', () => {
4
+ describe('catcher', () => {
5
5
  describe('curried overload', () => {
6
6
  it('should return a function when called with only catchFn', () => {
7
7
  const catchFn = () => 'error';
@@ -9,10 +9,14 @@ describe('tryCatch', () => {
9
9
  expect(typeof result).toBe('function');
10
10
  });
11
11
 
12
- it('should catch errors and call catchFn', () => {
12
+ it('should catch errors and call catchFn with error, function, and args', () => {
13
13
  let catchedError: unknown;
14
- const catchFn = (error: unknown) => {
14
+ let catchedFn: unknown;
15
+ let catchedArgs: unknown[];
16
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
15
17
  catchedError = error;
18
+ catchedFn = fn;
19
+ catchedArgs = args;
16
20
  return 'caught';
17
21
  };
18
22
 
@@ -27,6 +31,8 @@ describe('tryCatch', () => {
27
31
 
28
32
  expect(result).toBe('caught');
29
33
  expect(catchedError).toBeInstanceOf(Error);
34
+ expect(catchedFn).toBe(errorFn);
35
+ expect(catchedArgs).toEqual(['arg1', 'arg2']);
30
36
  expect(calledArgs).toEqual(['arg1', 'arg2']);
31
37
  });
32
38
 
@@ -53,10 +59,14 @@ describe('tryCatch', () => {
53
59
  });
54
60
 
55
61
  describe('direct overload', () => {
56
- it('should catch errors and call catchFn directly', () => {
62
+ it('should catch errors and call catchFn with error, function, and args directly', () => {
57
63
  let catchedError: unknown;
58
- const catchFn = (error: unknown) => {
64
+ let catchedFn: unknown;
65
+ let catchedArgs: unknown[];
66
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
59
67
  catchedError = error;
68
+ catchedFn = fn;
69
+ catchedArgs = args;
60
70
  return 'caught';
61
71
  };
62
72
 
@@ -71,6 +81,8 @@ describe('tryCatch', () => {
71
81
 
72
82
  expect(result).toBe('caught');
73
83
  expect(catchedError).toBeInstanceOf(Error);
84
+ expect(catchedFn).toBe(errorFn);
85
+ expect(catchedArgs).toEqual(['arg1', 'arg2']);
74
86
  expect(calledArgs).toEqual(['arg1', 'arg2']);
75
87
  });
76
88
 
@@ -97,40 +109,52 @@ describe('tryCatch', () => {
97
109
  });
98
110
 
99
111
  describe('error handling', () => {
100
- it('should handle different error types', () => {
112
+ it('should handle different error types and pass function context', () => {
101
113
  const results: unknown[] = [];
102
- const catchFn = (error: unknown) => {
114
+ const functions: unknown[] = [];
115
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
103
116
  results.push(error);
117
+ functions.push(fn);
104
118
  return 'handled';
105
119
  };
106
120
 
107
121
  // String error
108
- const stringErrorFn = catcher(catchFn, () => {
122
+ const stringErrorFn = () => {
109
123
  throw 'string error';
110
- });
111
- expect(stringErrorFn()).toBe('handled');
124
+ };
125
+ const wrappedStringFn = catcher(catchFn, stringErrorFn);
126
+ expect(wrappedStringFn()).toBe('handled');
112
127
  expect(results[0]).toBe('string error');
128
+ expect(functions[0]).toBe(stringErrorFn);
113
129
 
114
130
  // Object error
115
131
  const objectError = { message: 'object error' };
116
- const objectErrorFn = catcher(catchFn, () => {
132
+ const objectErrorFn = () => {
117
133
  throw objectError;
118
- });
119
- expect(objectErrorFn()).toBe('handled');
134
+ };
135
+ const wrappedObjectFn = catcher(catchFn, objectErrorFn);
136
+ expect(wrappedObjectFn()).toBe('handled');
120
137
  expect(results[1]).toBe(objectError);
138
+ expect(functions[1]).toBe(objectErrorFn);
121
139
 
122
140
  // null error
123
- const nullErrorFn = catcher(catchFn, () => {
141
+ const nullErrorFn = () => {
124
142
  throw null;
125
- });
126
- expect(nullErrorFn()).toBe('handled');
143
+ };
144
+ const wrappedNullFn = catcher(catchFn, nullErrorFn);
145
+ expect(wrappedNullFn()).toBe('handled');
127
146
  expect(results[2]).toBe(null);
147
+ expect(functions[2]).toBe(nullErrorFn);
128
148
  });
129
149
 
130
- it('should preserve function parameters', () => {
150
+ it('should preserve function parameters and pass them to catchFn', () => {
131
151
  let caughtError: unknown;
132
- const catchFn = (error: unknown) => {
152
+ let caughtFn: unknown;
153
+ let caughtArgs: unknown[];
154
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
133
155
  caughtError = error;
156
+ caughtFn = fn;
157
+ caughtArgs = args;
134
158
  return 'caught';
135
159
  };
136
160
 
@@ -151,12 +175,18 @@ describe('tryCatch', () => {
151
175
  expect(wrappedFn(10, 'error', false)).toBe('caught');
152
176
  expect(testArgs).toEqual([10, 'error', false]);
153
177
  expect(caughtError).toBeInstanceOf(Error);
178
+ expect(caughtFn).toBe(testFn);
179
+ expect(caughtArgs).toEqual([10, 'error', false]);
154
180
  });
155
181
 
156
182
  it('should handle functions with no parameters', () => {
157
183
  let caughtError: unknown;
158
- const catchFn = (error: unknown) => {
184
+ let caughtFn: unknown;
185
+ let caughtArgs: unknown[];
186
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
159
187
  caughtError = error;
188
+ caughtFn = fn;
189
+ caughtArgs = args;
160
190
  return 'no params caught';
161
191
  };
162
192
 
@@ -172,6 +202,8 @@ describe('tryCatch', () => {
172
202
  expect(result).toBe('no params caught');
173
203
  expect(called).toBe(true);
174
204
  expect(caughtError).toBeInstanceOf(Error);
205
+ expect(caughtFn).toBe(noParamsFn);
206
+ expect(caughtArgs).toEqual([]);
175
207
  });
176
208
 
177
209
  it('should handle functions returning different types', () => {
@@ -194,7 +226,8 @@ describe('tryCatch', () => {
194
226
 
195
227
  describe('type safety', () => {
196
228
  it('should maintain function signature', () => {
197
- const catchFn = (error: unknown) => 'error';
229
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) =>
230
+ 'error';
198
231
  const originalFn = (a: number, b: string): string => `${a}-${b}`;
199
232
 
200
233
  const wrappedFn = catcher(catchFn, originalFn);
@@ -203,5 +236,25 @@ describe('tryCatch', () => {
203
236
  const result: string = wrappedFn(1, 'test');
204
237
  expect(result).toBe('1-test');
205
238
  });
239
+
240
+ it('should pass function reference and arguments to catchFn', () => {
241
+ let capturedFn: unknown;
242
+ let capturedArgs: unknown[];
243
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
244
+ capturedFn = fn;
245
+ capturedArgs = args;
246
+ return 'handled';
247
+ };
248
+
249
+ const testFn = (x: number, y: string) => {
250
+ throw new Error('test');
251
+ };
252
+
253
+ const wrappedFn = catcher(catchFn, testFn);
254
+ wrappedFn(42, 'hello');
255
+
256
+ expect(capturedFn).toBe(testFn);
257
+ expect(capturedArgs).toEqual([42, 'hello']);
258
+ });
206
259
  });
207
260
  });
package/ts/catcher.ts ADDED
@@ -0,0 +1,35 @@
1
+ // curried overload
2
+ export function catcher<F extends (...args: any[]) => any, R>(
3
+ catchFn: (error: unknown, fn: F, ...args: Parameters<F>) => 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, fn: F, ...args: Parameters<F>) => R,
9
+ fn: F,
10
+ ): (...args: Parameters<F>) => ReturnType<F> | R;
11
+
12
+ /**
13
+ * A utility function to wrap another function with a try-catch block.
14
+ * If an error occurs during the execution of the function, the provided
15
+ * catchFn is called with the error, the original function, and its arguments.
16
+ *
17
+ * This function supports both direct invocation and curried usage.
18
+ *
19
+ * @param catchFn - The function to call when an error occurs.
20
+ * @param fn - The function to wrap (optional for curried usage).
21
+ * @returns A new function that wraps the original function with error handling.
22
+ */
23
+ export function catcher<F extends (...args: any[]) => any, R>(
24
+ catchFn: (error: unknown, fn: F, ...args: Parameters<F>) => R,
25
+ fn?: F,
26
+ ) {
27
+ if (!fn) return (fn: F) => catcher(catchFn, fn) as any;
28
+ return (...args: Parameters<F>) => {
29
+ try {
30
+ return fn(...args);
31
+ } catch (error) {
32
+ return catchFn(error, fn, ...args);
33
+ }
34
+ };
35
+ }
package/ts/cli.ts CHANGED
@@ -1,14 +1,34 @@
1
1
  #!/usr/bin/env node
2
2
  import DIE from 'phpdie';
3
- import cliYes, { parseCliArgs } from './';
3
+ import cliYesConfig from '../cli-yes.config';
4
4
 
5
- // if (!globalThis.Bun) // run with same arguments in Bun
5
+ // if node-pty is not installed, re-run with bun
6
+ // const hasNodePty = !!await import('node-pty').catch(() => null);
7
+ if (!globalThis.Bun) {
8
+ // run with same arguments in Bun if not already
9
+ console.log('Re-running with Bun...', process.argv);
10
+ (await import('child_process')).spawnSync(
11
+ 'node_modules/.bin/bun',
12
+ [process.argv[1]!, '--', ...process.argv.slice(2)],
13
+ { stdio: 'inherit' },
14
+ );
15
+ process.exit(0);
16
+ }
17
+ // check and fix bun-pty on some systems
18
+ if (globalThis.Bun) console.log('Bun detected, using bun-pty');
19
+ // await import("./fix-pty.js")
20
+
21
+ // console.log('Running', process.argv);
22
+
23
+ // Import the CLI module
24
+ const { default: cliYes, parseCliArgs } = await import('./');
6
25
 
7
26
  // Parse CLI arguments
8
27
  const config = parseCliArgs(process.argv);
9
28
 
10
29
  // Validate CLI name
11
- if (!config.cli) DIE('missing cli def');
30
+ if (!config.cli)
31
+ DIE`missing cli def, available clis: ${Object.keys((await cliYesConfig).clis).join(', ')}`;
12
32
 
13
33
  if (config.verbose) {
14
34
  process.env.VERBOSE = 'true'; // enable verbose logging in yesLog.ts
package/ts/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { execaCommand, execaCommandSync, parseCommandString } from 'execa';
1
2
  import { fromReadable, fromWritable } from 'from-node-stream';
2
3
  import { mkdir, writeFile } from 'fs/promises';
3
4
  import path from 'path';
@@ -5,6 +6,7 @@ import DIE from 'phpdie';
5
6
  import sflow from 'sflow';
6
7
  import { TerminalTextRender } from 'terminal-render';
7
8
  import rawConfig from '../cli-yes.config.js';
9
+ import { catcher } from './catcher.js';
8
10
  import {
9
11
  extractSessionId,
10
12
  getSessionForCwd,
@@ -19,7 +21,6 @@ import {
19
21
  shouldUseLock,
20
22
  updateCurrentTaskStatus,
21
23
  } from './runningLock';
22
- import { catcher } from './tryCatch';
23
24
  import { yesLog } from './yesLog';
24
25
 
25
26
  export { parseCliArgs } from './parseCliArgs';
@@ -36,6 +37,7 @@ export type AgentCliConfig = {
36
37
  defaultArgs?: string[]; // function to ensure certain args are present
37
38
  noEOL?: boolean; // if true, do not split lines by \n, used for codex, which uses cursor-move csi code instead of \n to move lines
38
39
  promptArg?: (string & {}) | 'first-arg' | 'last-arg'; // argument name to pass the prompt, e.g. --prompt, or first-arg for positional arg
40
+ bunx?: boolean; // if true, use bunx to run the binary
39
41
  };
40
42
  export type CliYesConfig = {
41
43
  clis: { [key: string]: AgentCliConfig };
@@ -91,6 +93,7 @@ export default async function cliYes({
91
93
  removeControlCharactersFromStdout = false, // = !process.stdout.isTTY,
92
94
  verbose = false,
93
95
  queue = true,
96
+ install = false,
94
97
  }: {
95
98
  cli: SUPPORTED_CLIS;
96
99
  cliArgs?: string[];
@@ -103,6 +106,7 @@ export default async function cliYes({
103
106
  removeControlCharactersFromStdout?: boolean;
104
107
  verbose?: boolean;
105
108
  queue?: boolean;
109
+ install?: boolean; // if true, install the cli tool if not installed, e.g. will run `npm install -g cursor-agent`
106
110
  }) {
107
111
  // those overrides seems only works in bun
108
112
  // await Promise.allSettled([
@@ -118,7 +122,11 @@ export default async function cliYes({
118
122
  // });
119
123
 
120
124
  if (!cli) throw new Error(`cli is required`);
121
- const conf = CLIS_CONFIG[cli] || DIE(`Unsupported cli tool: ${cli}`);
125
+ const conf =
126
+ CLIS_CONFIG[cli] ||
127
+ DIE(
128
+ `Unsupported cli tool: ${cli}, current process.argv: ${process.argv.join(' ')}`,
129
+ );
122
130
 
123
131
  // Acquire lock before starting agent (if in git repo or same cwd and lock is not disabled)
124
132
  const workingDir = cwd ?? process.cwd();
@@ -157,12 +165,12 @@ export default async function cliYes({
157
165
  // const pty = await import('node-pty');
158
166
 
159
167
  // its recommened to use bun-pty in windows
160
- const pty = await (globalThis.Bun ? import('bun-pty') : import('node-pty'))
161
- // .catch(async () => await import('node-pty'))
162
- .catch(async () =>
163
- DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),
164
- );
165
- console.log(globalThis.Bun);
168
+ const pty = await (globalThis.Bun
169
+ ? import('bun-pty')
170
+ : import('node-pty')
171
+ ).catch(async () =>
172
+ DIE('Please install node-pty or bun-pty, run this: bun install bun-pty'),
173
+ );
166
174
 
167
175
  // Detect if running as sub-agent
168
176
  const isSubAgent = !!process.env.CLAUDE_PPID;
@@ -223,34 +231,60 @@ export default async function cliYes({
223
231
  }
224
232
  const cliCommand = cliConf?.binary || cli;
225
233
 
234
+ const spawn = () => {
235
+ // const [bin, ...args] = [...parseCommandString((cliConf.bunx ? 'bunx --bun ' : '') + cliCommand), ...(cliArgs)];
236
+ // console.log(`Spawning ${bin} with args: ${JSON.stringify(args)}`);
237
+ // return pty.spawn(bin!, args, getPtyOptions());
238
+ return pty.spawn(cliCommand, cliArgs, getPtyOptions());
239
+ };
226
240
  let shell = catcher(
227
- (error: unknown) => {
241
+ // error handler
242
+ (error: unknown, fn, ...args) => {
228
243
  console.error(`Fatal: Failed to start ${cliCommand}.`);
244
+
229
245
  if (cliConf?.install && isCommandNotFoundError(error))
246
+ if (install) {
247
+ console.log(`Attempting to install ${cli}...`);
248
+ execaCommandSync(cliConf.install, { stdio: 'inherit' });
249
+ console.log(
250
+ `${cli} installed successfully. Please rerun the command.`,
251
+ );
252
+ return spawn();
253
+ }
254
+ console.error(
255
+ `If you did not installed it yet, Please install it first: ${cliConf.install}`,
256
+ );
257
+
258
+ if (
259
+ globalThis.Bun &&
260
+ error instanceof Error &&
261
+ error.stack?.includes('bun-pty')
262
+ ) {
263
+ // try to fix bun-pty issues
230
264
  console.error(
231
- `If you did not installed it yet, Please install it first: ${cliConf.install}`,
265
+ `Detected bun-pty issue, attempted to fix it. Please try again.`,
232
266
  );
267
+ require('./fix-pty.js');
268
+ // unable to retry with same process, so exit here.
269
+ }
233
270
  throw error;
234
271
 
235
272
  function isCommandNotFoundError(e: unknown) {
236
273
  if (e instanceof Error) {
237
274
  return (
238
- e.message.includes('command not found') ||
239
- e.message.includes('ENOENT') ||
275
+ e.message.includes('command not found') || // unix
276
+ e.message.includes('ENOENT') || // unix
240
277
  e.message.includes('spawn') // windows
241
278
  );
242
279
  }
243
280
  return false;
244
281
  }
245
282
  },
246
- () => pty.spawn(cliCommand, cliArgs, getPtyOptions()),
283
+ spawn,
247
284
  )();
248
285
  const pendingExitCode = Promise.withResolvers<number | null>();
249
286
  let pendingExitCodeValue = null;
250
287
 
251
- // TODO handle error if claude is not installed, show msg:
252
- // npm install -g @anthropic-ai/claude-code
253
-
254
288
  async function onData(data: string) {
255
289
  // append data to the buffer, so we can process it later
256
290
  await outputWriter.write(data);
@@ -1,4 +1,4 @@
1
- import enhancedMs from 'enhanced-ms';
1
+ import ms from 'ms';
2
2
  import yargs from 'yargs';
3
3
  import { hideBin } from 'yargs/helpers';
4
4
  import { SUPPORTED_CLIS } from '.';
@@ -12,7 +12,7 @@ export function parseCliArgs(argv: string[]) {
12
12
  const scriptName = argv[1]?.split(/[\/\\]/).pop();
13
13
  const cliName = ((e?: string) => {
14
14
  if (e === 'cli' || e === 'cli.ts' || e === 'cli.js') return undefined;
15
- return e?.replace(/-yes$/, '');
15
+ return e?.replace(/-yes(\.[jt]s)?$/, '');
16
16
  })(scriptName);
17
17
 
18
18
  // Parse args with yargs (same logic as cli.ts:16-73)
@@ -105,7 +105,7 @@ export function parseCliArgs(argv: string[]) {
105
105
  [parsedArgv.prompt, dashPrompt].filter(Boolean).join(' ') || undefined,
106
106
  exitOnIdle: Number(
107
107
  (parsedArgv.idle || parsedArgv.exitOnIdle)?.replace(/.*/, (e) =>
108
- String(enhancedMs(e)),
108
+ String(ms(e as ms.StringValue)),
109
109
  ) || 0,
110
110
  ),
111
111
  queue: parsedArgv.queue,
package/ts/yesLog.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { appendFileSync, rmSync } from 'node:fs';
2
2
  import tsaComposer from 'tsa-composer';
3
- import { catcher } from './tryCatch';
3
+ import { catcher } from './catcher';
4
4
 
5
5
  let initial = true;
6
6
 
package/ts/tryCatch.ts DELETED
@@ -1,25 +0,0 @@
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
- }