claude-yes 1.28.0 → 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.28.0",
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",
@@ -33,7 +33,6 @@
33
33
  "import": "./dist/index.js",
34
34
  "types": "./ts/index.ts"
35
35
  },
36
- "main": "dist/index.js",
37
36
  "module": "ts/index.ts",
38
37
  "types": "./ts/index.ts",
39
38
  "bin": {
@@ -51,15 +50,19 @@
51
50
  },
52
51
  "files": [
53
52
  "ts/*.ts",
54
- "dist"
53
+ "dist",
54
+ "scripts"
55
55
  ],
56
56
  "scripts": {
57
- "build": "bun build ts/index.ts ts/cli.ts --outdir=dist --target=node --sourcemap --packages=external --external=node-pty",
57
+ "build": "bun run build:index && bun run build:cli",
58
58
  "postbuild": "bun ./ts/postbuild.ts",
59
- "dev": "tsx ts/index.ts",
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",
62
+ "dev": "bun ts/index.ts",
60
63
  "fmt": "bunx @biomejs/biome check --fix && bunx sort-package-json",
61
64
  "prepack": "bun run build",
62
- "prepare": "bunx husky",
65
+ "prepare": "bunx husky && bun run build",
63
66
  "test": "bun test --coverage"
64
67
  },
65
68
  "lint-staged": {
@@ -88,7 +91,11 @@
88
91
  ]
89
92
  },
90
93
  "dependencies": {
91
- "bun-pty": "^0.3.2"
94
+ "@types/ms": "^2.1.0",
95
+ "bun": "^1.3.1",
96
+ "bun-pty": "^0.3.2",
97
+ "from-node-stream": "^0.0.11",
98
+ "ms": "^2.1.3"
92
99
  },
93
100
  "devDependencies": {
94
101
  "@biomejs/biome": "^2.2.5",
@@ -101,9 +108,7 @@
101
108
  "@types/node": "^24.0.10",
102
109
  "@types/yargs": "^17.0.33",
103
110
  "cpu-wait": "^0.0.10",
104
- "enhanced-ms": "^4.1.0",
105
111
  "execa": "^9.6.0",
106
- "from-node-stream": "^0.0.11",
107
112
  "husky": "^9.1.7",
108
113
  "lint-staged": "^16.1.4",
109
114
  "p-map": "^7.0.3",
@@ -114,12 +119,11 @@
114
119
  "strip-ansi-control-characters": "^2.0.0",
115
120
  "terminal-render": "^1.2.0",
116
121
  "tsa-composer": "^3.0.2",
117
- "tsx": "^4.20.3",
118
122
  "vitest": "^3.2.4",
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
  }
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { ReadyManager } from './ReadyManager';
3
+
4
+ describe('ReadyManager', () => {
5
+ it('should start in not ready state', () => {
6
+ const manager = new ReadyManager();
7
+ expect(manager.wait()).toBeInstanceOf(Promise);
8
+ });
9
+
10
+ it('should resolve wait when ready is called', async () => {
11
+ const manager = new ReadyManager();
12
+ const waitPromise = manager.wait();
13
+
14
+ manager.ready();
15
+
16
+ await expect(waitPromise).resolves.toBeUndefined();
17
+ });
18
+
19
+ it('should resolve immediately if already ready', async () => {
20
+ const manager = new ReadyManager();
21
+ manager.ready();
22
+
23
+ const result = manager.wait();
24
+ expect(result).toBeUndefined();
25
+ });
26
+
27
+ it('should handle multiple waiters', async () => {
28
+ const manager = new ReadyManager();
29
+ const wait1 = manager.wait();
30
+ const wait2 = manager.wait();
31
+ const wait3 = manager.wait();
32
+
33
+ manager.ready();
34
+
35
+ await Promise.all([
36
+ expect(wait1).resolves.toBeUndefined(),
37
+ expect(wait2).resolves.toBeUndefined(),
38
+ expect(wait3).resolves.toBeUndefined(),
39
+ ]);
40
+ });
41
+
42
+ it('should reset to not ready when unready is called', async () => {
43
+ const manager = new ReadyManager();
44
+ manager.ready();
45
+ manager.unready();
46
+
47
+ expect(manager.wait()).toBeInstanceOf(Promise);
48
+ });
49
+
50
+ it('should handle ready with no waiting queue', () => {
51
+ const manager = new ReadyManager();
52
+ manager.ready(); // Should not throw even if no one is waiting
53
+ expect(manager.wait()).toBeUndefined(); // Should be ready now
54
+ });
55
+
56
+ it('should handle multiple ready/unready cycles', async () => {
57
+ const manager = new ReadyManager();
58
+
59
+ // First cycle
60
+ const wait1 = manager.wait();
61
+ manager.ready();
62
+ await wait1;
63
+
64
+ // Reset
65
+ manager.unready();
66
+
67
+ // Second cycle
68
+ const wait2 = manager.wait();
69
+ manager.ready();
70
+ await expect(wait2).resolves.toBeUndefined();
71
+ });
72
+ });
@@ -0,0 +1,260 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { catcher } from './catcher';
3
+
4
+ describe('catcher', () => {
5
+ describe('curried overload', () => {
6
+ it('should return a function when called with only catchFn', () => {
7
+ const catchFn = () => 'error';
8
+ const result = catcher(catchFn);
9
+ expect(typeof result).toBe('function');
10
+ });
11
+
12
+ it('should catch errors and call catchFn with error, function, and args', () => {
13
+ let catchedError: unknown;
14
+ let catchedFn: unknown;
15
+ let catchedArgs: unknown[];
16
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
17
+ catchedError = error;
18
+ catchedFn = fn;
19
+ catchedArgs = args;
20
+ return 'caught';
21
+ };
22
+
23
+ let calledArgs: unknown[] = [];
24
+ const errorFn = (...args: unknown[]) => {
25
+ calledArgs = args;
26
+ throw new Error('test error');
27
+ };
28
+
29
+ const wrappedFn = catcher(catchFn)(errorFn);
30
+ const result = wrappedFn('arg1', 'arg2');
31
+
32
+ expect(result).toBe('caught');
33
+ expect(catchedError).toBeInstanceOf(Error);
34
+ expect(catchedFn).toBe(errorFn);
35
+ expect(catchedArgs).toEqual(['arg1', 'arg2']);
36
+ expect(calledArgs).toEqual(['arg1', 'arg2']);
37
+ });
38
+
39
+ it('should return normal result when no error occurs', () => {
40
+ let catchCalled = false;
41
+ const catchFn = () => {
42
+ catchCalled = true;
43
+ return 'error';
44
+ };
45
+
46
+ let calledArgs: unknown[] = [];
47
+ const normalFn = (...args: unknown[]) => {
48
+ calledArgs = args;
49
+ return 'success';
50
+ };
51
+
52
+ const wrappedFn = catcher(catchFn)(normalFn);
53
+ const result = wrappedFn('arg1', 'arg2');
54
+
55
+ expect(result).toBe('success');
56
+ expect(catchCalled).toBe(false);
57
+ expect(calledArgs).toEqual(['arg1', 'arg2']);
58
+ });
59
+ });
60
+
61
+ describe('direct overload', () => {
62
+ it('should catch errors and call catchFn with error, function, and args directly', () => {
63
+ let catchedError: unknown;
64
+ let catchedFn: unknown;
65
+ let catchedArgs: unknown[];
66
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
67
+ catchedError = error;
68
+ catchedFn = fn;
69
+ catchedArgs = args;
70
+ return 'caught';
71
+ };
72
+
73
+ let calledArgs: unknown[] = [];
74
+ const errorFn = (...args: unknown[]) => {
75
+ calledArgs = args;
76
+ throw new Error('test error');
77
+ };
78
+
79
+ const wrappedFn = catcher(catchFn, errorFn);
80
+ const result = wrappedFn('arg1', 'arg2');
81
+
82
+ expect(result).toBe('caught');
83
+ expect(catchedError).toBeInstanceOf(Error);
84
+ expect(catchedFn).toBe(errorFn);
85
+ expect(catchedArgs).toEqual(['arg1', 'arg2']);
86
+ expect(calledArgs).toEqual(['arg1', 'arg2']);
87
+ });
88
+
89
+ it('should return normal result when no error occurs directly', () => {
90
+ let catchCalled = false;
91
+ const catchFn = () => {
92
+ catchCalled = true;
93
+ return 'error';
94
+ };
95
+
96
+ let calledArgs: unknown[] = [];
97
+ const normalFn = (...args: unknown[]) => {
98
+ calledArgs = args;
99
+ return 'success';
100
+ };
101
+
102
+ const wrappedFn = catcher(catchFn, normalFn);
103
+ const result = wrappedFn('arg1', 'arg2');
104
+
105
+ expect(result).toBe('success');
106
+ expect(catchCalled).toBe(false);
107
+ expect(calledArgs).toEqual(['arg1', 'arg2']);
108
+ });
109
+ });
110
+
111
+ describe('error handling', () => {
112
+ it('should handle different error types and pass function context', () => {
113
+ const results: unknown[] = [];
114
+ const functions: unknown[] = [];
115
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
116
+ results.push(error);
117
+ functions.push(fn);
118
+ return 'handled';
119
+ };
120
+
121
+ // String error
122
+ const stringErrorFn = () => {
123
+ throw 'string error';
124
+ };
125
+ const wrappedStringFn = catcher(catchFn, stringErrorFn);
126
+ expect(wrappedStringFn()).toBe('handled');
127
+ expect(results[0]).toBe('string error');
128
+ expect(functions[0]).toBe(stringErrorFn);
129
+
130
+ // Object error
131
+ const objectError = { message: 'object error' };
132
+ const objectErrorFn = () => {
133
+ throw objectError;
134
+ };
135
+ const wrappedObjectFn = catcher(catchFn, objectErrorFn);
136
+ expect(wrappedObjectFn()).toBe('handled');
137
+ expect(results[1]).toBe(objectError);
138
+ expect(functions[1]).toBe(objectErrorFn);
139
+
140
+ // null error
141
+ const nullErrorFn = () => {
142
+ throw null;
143
+ };
144
+ const wrappedNullFn = catcher(catchFn, nullErrorFn);
145
+ expect(wrappedNullFn()).toBe('handled');
146
+ expect(results[2]).toBe(null);
147
+ expect(functions[2]).toBe(nullErrorFn);
148
+ });
149
+
150
+ it('should preserve function parameters and pass them to catchFn', () => {
151
+ let caughtError: unknown;
152
+ let caughtFn: unknown;
153
+ let caughtArgs: unknown[];
154
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
155
+ caughtError = error;
156
+ caughtFn = fn;
157
+ caughtArgs = args;
158
+ return 'caught';
159
+ };
160
+
161
+ let testArgs: [number, string, boolean] | undefined;
162
+ const testFn = (a: number, b: string, c: boolean) => {
163
+ testArgs = [a, b, c];
164
+ if (a > 5) throw new Error('too big');
165
+ return `${a}-${b}-${c}`;
166
+ };
167
+
168
+ const wrappedFn = catcher(catchFn, testFn);
169
+
170
+ // Normal execution
171
+ expect(wrappedFn(3, 'test', true)).toBe('3-test-true');
172
+ expect(testArgs).toEqual([3, 'test', true]);
173
+
174
+ // Error execution
175
+ expect(wrappedFn(10, 'error', false)).toBe('caught');
176
+ expect(testArgs).toEqual([10, 'error', false]);
177
+ expect(caughtError).toBeInstanceOf(Error);
178
+ expect(caughtFn).toBe(testFn);
179
+ expect(caughtArgs).toEqual([10, 'error', false]);
180
+ });
181
+
182
+ it('should handle functions with no parameters', () => {
183
+ let caughtError: unknown;
184
+ let caughtFn: unknown;
185
+ let caughtArgs: unknown[];
186
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
187
+ caughtError = error;
188
+ caughtFn = fn;
189
+ caughtArgs = args;
190
+ return 'no params caught';
191
+ };
192
+
193
+ let called = false;
194
+ const noParamsFn = () => {
195
+ called = true;
196
+ throw new Error('no params error');
197
+ };
198
+
199
+ const wrappedFn = catcher(catchFn, noParamsFn);
200
+ const result = wrappedFn();
201
+
202
+ expect(result).toBe('no params caught');
203
+ expect(called).toBe(true);
204
+ expect(caughtError).toBeInstanceOf(Error);
205
+ expect(caughtFn).toBe(noParamsFn);
206
+ expect(caughtArgs).toEqual([]);
207
+ });
208
+
209
+ it('should handle functions returning different types', () => {
210
+ const catchFn = () => null;
211
+
212
+ // Function returning number
213
+ const numberFn = catcher(catchFn, () => 42);
214
+ expect(numberFn()).toBe(42);
215
+
216
+ // Function returning object
217
+ const obj = { key: 'value' };
218
+ const objectFn = catcher(catchFn, () => obj);
219
+ expect(objectFn()).toBe(obj);
220
+
221
+ // Function returning undefined
222
+ const undefinedFn = catcher(catchFn, () => undefined);
223
+ expect(undefinedFn()).toBeUndefined();
224
+ });
225
+ });
226
+
227
+ describe('type safety', () => {
228
+ it('should maintain function signature', () => {
229
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) =>
230
+ 'error';
231
+ const originalFn = (a: number, b: string): string => `${a}-${b}`;
232
+
233
+ const wrappedFn = catcher(catchFn, originalFn);
234
+
235
+ // This should be type-safe
236
+ const result: string = wrappedFn(1, 'test');
237
+ expect(result).toBe('1-test');
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
+ });
259
+ });
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,17 +1,35 @@
1
1
  #!/usr/bin/env node
2
2
  import DIE from 'phpdie';
3
- import cliYes, { SUPPORTED_CLIS } from '.';
4
- import { parseCliArgs } from './parseCliArgs';
3
+ import cliYesConfig from '../cli-yes.config';
4
+
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('./');
5
25
 
6
26
  // Parse CLI arguments
7
27
  const config = parseCliArgs(process.argv);
8
28
 
9
29
  // Validate CLI name
10
- if (!config.cli) {
11
- DIE('missing cli def');
12
- }
30
+ if (!config.cli)
31
+ DIE`missing cli def, available clis: ${Object.keys((await cliYesConfig).clis).join(', ')}`;
13
32
 
14
- // console.clear();
15
33
  if (config.verbose) {
16
34
  process.env.VERBOSE = 'true'; // enable verbose logging in yesLog.ts
17
35
  console.log(config);
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { IdleWaiter } from './idleWaiter';
3
+
4
+ describe('IdleWaiter', () => {
5
+ it('should initialize with current time', () => {
6
+ const waiter = new IdleWaiter();
7
+ expect(waiter.lastActivityTime).toBeCloseTo(Date.now(), -2);
8
+ });
9
+
10
+ it('should update lastActivityTime when ping is called', () => {
11
+ const waiter = new IdleWaiter();
12
+ const initialTime = waiter.lastActivityTime;
13
+
14
+ // Wait a small amount
15
+ const start = Date.now();
16
+ while (Date.now() - start < 10) {
17
+ // busy wait
18
+ }
19
+
20
+ waiter.ping();
21
+ expect(waiter.lastActivityTime).toBeGreaterThan(initialTime);
22
+ });
23
+
24
+ it('should return this when ping is called for chaining', () => {
25
+ const waiter = new IdleWaiter();
26
+ expect(waiter.ping()).toBe(waiter);
27
+ });
28
+
29
+ it('should resolve wait immediately when already idle', async () => {
30
+ const waiter = new IdleWaiter();
31
+
32
+ // Wait enough time to be considered idle
33
+ const start = Date.now();
34
+ while (Date.now() - start < 50) {
35
+ // busy wait
36
+ }
37
+
38
+ // This should resolve quickly since enough time has passed
39
+ const waitPromise = waiter.wait(10);
40
+ await expect(waitPromise).resolves.toBeUndefined();
41
+ });
42
+
43
+ it('should respect custom check interval', () => {
44
+ const waiter = new IdleWaiter();
45
+ waiter.checkInterval = 200;
46
+
47
+ expect(waiter.checkInterval).toBe(200);
48
+ });
49
+
50
+ it('should have ping method that chains', () => {
51
+ const waiter = new IdleWaiter();
52
+ const result = waiter.ping().ping().ping();
53
+ expect(result).toBe(waiter);
54
+ });
55
+ });