agent-yes 1.31.41

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ await import('./cli.js')
package/package.json ADDED
@@ -0,0 +1,145 @@
1
+ {
2
+ "name": "agent-yes",
3
+ "version": "1.31.41",
4
+ "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
+ "keywords": [
6
+ "ai",
7
+ "anthropic",
8
+ "assistant",
9
+ "auto-response",
10
+ "automation",
11
+ "claude",
12
+ "cli",
13
+ "codex",
14
+ "copilot",
15
+ "cursor",
16
+ "gemini",
17
+ "grok",
18
+ "qwen",
19
+ "wrapper"
20
+ ],
21
+ "homepage": "https://github.com/snomiao/agent-yes#readme",
22
+ "bugs": {
23
+ "url": "https://github.com/snomiao/agent-yes/issues"
24
+ },
25
+ "license": "MIT",
26
+ "author": "snomiao <snomiao@gmail.com>",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/snomiao/agent-yes.git"
30
+ },
31
+ "directories": {
32
+ "doc": "docs"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "scripts",
37
+ "ts/*.ts"
38
+ ],
39
+ "type": "module",
40
+ "module": "ts/index.ts",
41
+ "types": "./ts/index.ts",
42
+ "exports": {
43
+ "types": "./ts/index.ts",
44
+ "import": "./dist/index.js"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "scripts": {
51
+ "build": "bun build ./ts/cli.ts ./ts/index.ts --outdir=dist --target=node --sourcemap --external=@snomiao/bun-pty --external=bun-pty --external=node-pty --external=from-node-stream --external=bun",
52
+ "postbuild": "bun ./ts/postbuild.ts",
53
+ "demo": "bun run build && bun link && claude-yes -- demo",
54
+ "dev": "bun ts/index.ts",
55
+ "fmt": "oxlint --fix --fix-suggestions && oxfmt",
56
+ "_prepack": "bun run build",
57
+ "prepare": "husky",
58
+ "release": "standard-version && npm publish",
59
+ "release:beta": "standard-version && npm publish --tag beta",
60
+ "test": "bun test --coverage"
61
+ },
62
+ "dependencies": {
63
+ "@anthropic-ai/sdk": "^0.71.2",
64
+ "@snomiao/bun-pty": "^0.3.4",
65
+ "bun-pty": "^0.4.8",
66
+ "cpu-wait": "^0.0.10",
67
+ "execa": "^9.6.0",
68
+ "from-node-stream": "^0.1.2",
69
+ "ink": "^6.6.0",
70
+ "ms": "^2.1.3",
71
+ "openai": "^6.16.0",
72
+ "p-map": "^7.0.3",
73
+ "phpdie": "^1.7.0",
74
+ "rambda": "^10.3.2",
75
+ "sflow": "^1.20.2",
76
+ "strip-ansi-control-characters": "^2.0.0",
77
+ "terminal-render": "^1.2.2",
78
+ "tsa-composer": "^3.0.3",
79
+ "winston": "^3.19.0",
80
+ "yargs": "^18.0.0"
81
+ },
82
+ "devDependencies": {
83
+ "@semantic-release/changelog": "^6.0.3",
84
+ "@semantic-release/exec": "^7.1.0",
85
+ "@semantic-release/git": "^10.0.1",
86
+ "@semantic-release/release-notes-generator": "^14.1.0",
87
+ "@types/bun": "^1.2.18",
88
+ "@types/jest": "^30.0.0",
89
+ "@types/ms": "^2.1.0",
90
+ "@types/node": "^25.0.3",
91
+ "@types/yargs": "^17.0.33",
92
+ "husky": "^9.1.7",
93
+ "lint-staged": "^16.1.4",
94
+ "node-pty": "^1.1.0",
95
+ "oxfmt": "^0.23.0",
96
+ "oxlint": "^1.38.0",
97
+ "semantic-release": "^24.2.9",
98
+ "standard-version": "^9.5.0",
99
+ "vitest": "^3.2.4"
100
+ },
101
+ "peerDependencies": {
102
+ "node-pty": "latest",
103
+ "typescript": "^5.8.3"
104
+ },
105
+ "peerDependenciesMeta": {
106
+ "node-pty": {
107
+ "optional": true
108
+ }
109
+ },
110
+ "engines": {
111
+ "node": ">=22.0.0"
112
+ },
113
+ "_release": {
114
+ "branches": [
115
+ "main"
116
+ ],
117
+ "plugins": [
118
+ "@semantic-release/commit-analyzer",
119
+ "@semantic-release/release-notes-generator",
120
+ "@semantic-release/changelog",
121
+ "@semantic-release/npm",
122
+ [
123
+ "@semantic-release/exec",
124
+ {
125
+ "publishCmd": "npm pkg set name=claude-yes"
126
+ }
127
+ ],
128
+ "@semantic-release/npm",
129
+ "@semantic-release/git",
130
+ "@semantic-release/github"
131
+ ]
132
+ },
133
+ "bin": {
134
+ "qwen-yes": "./dist/qwen-yes.js",
135
+ "grok-yes": "./dist/grok-yes.js",
136
+ "claude-yes": "./dist/claude-yes.js",
137
+ "gemini-yes": "./dist/gemini-yes.js",
138
+ "codex-yes": "./dist/codex-yes.js",
139
+ "copilot-yes": "./dist/copilot-yes.js",
140
+ "cursor-yes": "./dist/cursor-yes.js",
141
+ "auggie-yes": "./dist/auggie-yes.js",
142
+ "agent-yes": "./dist/agent-yes.js",
143
+ "amp-yes": "./dist/amp-yes.js"
144
+ }
145
+ }
@@ -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,16 @@
1
+ export class ReadyManager {
2
+ isReady = false;
3
+ private readyQueue: (() => void)[] = [];
4
+ wait() {
5
+ if (this.isReady) return;
6
+ return new Promise<void>((resolve) => this.readyQueue.push(resolve));
7
+ }
8
+ unready() {
9
+ this.isReady = false;
10
+ }
11
+ ready() {
12
+ this.isReady = true;
13
+ if (!this.readyQueue.length) return; // check len for performance
14
+ this.readyQueue.splice(0).map((resolve) => resolve());
15
+ }
16
+ }
@@ -0,0 +1,5 @@
1
+ import { CLIS_CONFIG } from "./index.ts";
2
+
3
+ export const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG) as SUPPORTED_CLIS[];
4
+ export type SUPPORTED_CLIS = keyof typeof CLIS_CONFIG;
5
+
@@ -0,0 +1,259 @@
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[]) => "error";
230
+ const originalFn = (a: number, b: string): string => `${a}-${b}`;
231
+
232
+ const wrappedFn = catcher(catchFn, originalFn);
233
+
234
+ // This should be type-safe
235
+ const result: string = wrappedFn(1, "test");
236
+ expect(result).toBe("1-test");
237
+ });
238
+
239
+ it("should pass function reference and arguments to catchFn", () => {
240
+ let capturedFn: unknown;
241
+ let capturedArgs: unknown[];
242
+ const catchFn = (error: unknown, fn: unknown, ...args: unknown[]) => {
243
+ capturedFn = fn;
244
+ capturedArgs = args;
245
+ return "handled";
246
+ };
247
+
248
+ const testFn = (_x: number, _y: string) => {
249
+ throw new Error("test");
250
+ };
251
+
252
+ const wrappedFn = catcher(catchFn, testFn);
253
+ wrappedFn(42, "hello");
254
+
255
+ expect(capturedFn).toBe(testFn);
256
+ expect(capturedArgs).toEqual([42, "hello"]);
257
+ });
258
+ });
259
+ });
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
+ }
@@ -0,0 +1,20 @@
1
+ import { exec } from "child_process";
2
+ import { fromStdio } from "from-node-stream";
3
+ import sflow from "sflow";
4
+ import { describe, it } from "vitest";
5
+ import { sleepms } from "./utils";
6
+
7
+ describe("CLI idle tests", () => {
8
+ // 2025-08-11 ok
9
+ it.skip("CLI --exit-on-idle flag with custom timeout", async () => {
10
+ const p = exec(
11
+ `bunx tsx ./ts/cli.ts claude --verbose --logFile=./cli-idle.log --exit-on-idle=3s "say hello and wait"`,
12
+ );
13
+ const tr = new TransformStream<string, string>();
14
+ const output = await sflow(tr.readable).by(fromStdio(p)).log().text();
15
+ console.log(output);
16
+ expect(output).toContain("hello");
17
+ await sleepms(1000); // wait for process exit
18
+ expect(p.exitCode).toBe(0);
19
+ }, 20e3);
20
+ });
package/ts/cli.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bun
2
+ import { argv } from "process";
3
+ import cliYesConfig from "../agent-yes.config.ts";
4
+ import { parseCliArgs } from "./parseCliArgs.ts";
5
+ import { logger } from "./logger.ts";
6
+
7
+ // Import the CLI module
8
+
9
+ // Parse CLI arguments
10
+ const config = parseCliArgs(process.argv);
11
+
12
+ // Validate CLI name
13
+ if (!config.cli) {
14
+ logger.error(process.argv)
15
+ logger.error("Error: No CLI name provided.");
16
+ throw new Error(`missing cli def, available clis: ${Object.keys((await cliYesConfig).clis).join(", ")}`);
17
+ }
18
+
19
+ // console.log(`Using CLI: ${config.cli}`);
20
+
21
+ if (config.verbose) {
22
+ process.env.VERBOSE = "true"; // enable verbose logging in yesLog.ts
23
+ console.log(config);
24
+ console.log(argv);
25
+ }
26
+
27
+ const { default: cliYes } = await import("./index.ts");
28
+ const { exitCode } = await cliYes(config);
29
+ console.log("exiting process");
30
+ process.exit(exitCode ?? 1);
@@ -0,0 +1,12 @@
1
+ import type { AgentCliConfig, AgentYesConfig } from "./index.ts";
2
+
3
+ type Awaitable<T> = T | Promise<T>;
4
+ export async function defineCliYesConfig<T extends AgentYesConfig>(
5
+ cfg: Awaitable<T> | ((original: T) => Awaitable<T>),
6
+ ) {
7
+ if (typeof cfg === "function") cfg = await cfg({ clis: {} } as T);
8
+
9
+ return cfg as unknown as Omit<AgentYesConfig, "clis"> & {
10
+ clis: Record<string, AgentCliConfig>;
11
+ };
12
+ }
@@ -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
+ });
@@ -0,0 +1,31 @@
1
+ /**
2
+ * A utility class to wait for idle periods based on activity pings.
3
+ *
4
+ * @example
5
+ * const idleWaiter = new IdleWaiter();
6
+ *
7
+ * // Somewhere in your code, when activity occurs:
8
+ * idleWaiter.ping();
9
+ *
10
+ * // To wait for an idle period of 5 seconds:
11
+ * await idleWaiter.wait(5000);
12
+ * console.log('System has been idle for 5 seconds');
13
+ */
14
+ export class IdleWaiter {
15
+ lastActivityTime = Date.now();
16
+ checkInterval = 100; // Default check interval in milliseconds
17
+
18
+ constructor() {
19
+ this.ping();
20
+ }
21
+
22
+ ping() {
23
+ this.lastActivityTime = Date.now();
24
+ return this;
25
+ }
26
+
27
+ async wait(ms: number) {
28
+ while (this.lastActivityTime >= Date.now() - ms)
29
+ await new Promise((resolve) => setTimeout(resolve, this.checkInterval));
30
+ }
31
+ }