agent-yes 1.57.0 → 1.58.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/agent-yes.config.schema.json +4 -1
- package/dist/{SUPPORTED_CLIS-DNBWqwwx.js → SUPPORTED_CLIS-Kash6b_w.js} +5 -5
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/package.json +8 -8
- package/scripts/verify-deps.js +27 -27
- package/ts/JsonlStore.ts +3 -1
- package/ts/agentRegistry.ts +1 -1
- package/ts/buildRustArgs.spec.ts +42 -7
- package/ts/buildRustArgs.ts +4 -9
- package/ts/configLoader.spec.ts +17 -19
- package/ts/configLoader.ts +5 -6
- package/ts/index.ts +79 -60
- package/ts/parseCliArgs.spec.ts +20 -4
- package/ts/parseCliArgs.ts +4 -2
- package/ts/rustBinary.ts +15 -9
- package/ts/tryCatch.spec.ts +6 -6
- package/ts/versionChecker.ts +4 -4
|
@@ -49,7 +49,10 @@
|
|
|
49
49
|
{
|
|
50
50
|
"type": "object",
|
|
51
51
|
"properties": {
|
|
52
|
-
"powershell": {
|
|
52
|
+
"powershell": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "PowerShell install command (Windows)"
|
|
55
|
+
},
|
|
53
56
|
"bash": { "type": "string", "description": "Bash install command (Unix/macOS)" },
|
|
54
57
|
"npm": { "type": "string", "description": "npm install command (fallback)" },
|
|
55
58
|
"unix": { "type": "string", "description": "Unix-specific install command" },
|
|
@@ -9797,7 +9797,7 @@ function tryCatch(catchFn, fn) {
|
|
|
9797
9797
|
//#endregion
|
|
9798
9798
|
//#region package.json
|
|
9799
9799
|
var name = "agent-yes";
|
|
9800
|
-
var version = "1.
|
|
9800
|
+
var version = "1.58.0";
|
|
9801
9801
|
|
|
9802
9802
|
//#endregion
|
|
9803
9803
|
//#region ts/pty-fix.ts
|
|
@@ -10646,21 +10646,21 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
10646
10646
|
const dataHandler = (chunk) => {
|
|
10647
10647
|
try {
|
|
10648
10648
|
controller.enqueue(chunk);
|
|
10649
|
-
} catch
|
|
10649
|
+
} catch {}
|
|
10650
10650
|
};
|
|
10651
10651
|
const endHandler = () => {
|
|
10652
10652
|
if (closed) return;
|
|
10653
10653
|
closed = true;
|
|
10654
10654
|
try {
|
|
10655
10655
|
controller.close();
|
|
10656
|
-
} catch
|
|
10656
|
+
} catch {}
|
|
10657
10657
|
};
|
|
10658
10658
|
const errorHandler = (err) => {
|
|
10659
10659
|
if (closed) return;
|
|
10660
10660
|
closed = true;
|
|
10661
10661
|
try {
|
|
10662
10662
|
controller.error(err);
|
|
10663
|
-
} catch
|
|
10663
|
+
} catch {}
|
|
10664
10664
|
};
|
|
10665
10665
|
process.stdin.on("data", dataHandler);
|
|
10666
10666
|
process.stdin.on("end", endHandler);
|
|
@@ -10844,4 +10844,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
10844
10844
|
|
|
10845
10845
|
//#endregion
|
|
10846
10846
|
export { AgentContext as a, PidStore as c, config as i, removeControlCharacters as l, CLIS_CONFIG as n, name as o, agentYes as r, version as s, SUPPORTED_CLIS as t };
|
|
10847
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
10847
|
+
//# sourceMappingURL=SUPPORTED_CLIS-Kash6b_w.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { c as __toESM, i as __commonJSMin, r as require_ms, t as logger } from "./logger-DH1Rx9HI.js";
|
|
3
3
|
import "./agent-yes.config-Dr2p5kBW.js";
|
|
4
|
-
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
4
|
+
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Kash6b_w.js";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { argv } from "process";
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -4690,7 +4690,7 @@ async function fetchLatestVersion() {
|
|
|
4690
4690
|
const response = await fetch(`https://registry.npmjs.org/${name}/latest`, { signal: AbortSignal.timeout(3e3) });
|
|
4691
4691
|
if (!response.ok) return null;
|
|
4692
4692
|
return (await response.json()).version;
|
|
4693
|
-
} catch
|
|
4693
|
+
} catch {
|
|
4694
4694
|
return null;
|
|
4695
4695
|
}
|
|
4696
4696
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "./logger-DH1Rx9HI.js";
|
|
2
|
-
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-
|
|
2
|
+
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-Kash6b_w.js";
|
|
3
3
|
|
|
4
4
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-yes",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.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
|
"ai",
|
|
@@ -30,29 +30,29 @@
|
|
|
30
30
|
},
|
|
31
31
|
"bin": {
|
|
32
32
|
"agent-yes": "./dist/agent-yes.js",
|
|
33
|
-
"ay": "./dist/agent-yes.js",
|
|
34
33
|
"amp-yes": "./dist/amp-yes.js",
|
|
35
34
|
"auggie-yes": "./dist/auggie-yes.js",
|
|
35
|
+
"ay": "./dist/agent-yes.js",
|
|
36
36
|
"claude-yes": "./dist/claude-yes.js",
|
|
37
37
|
"codex-yes": "./dist/codex-yes.js",
|
|
38
38
|
"copilot-yes": "./dist/copilot-yes.js",
|
|
39
39
|
"cursor-yes": "./dist/cursor-yes.js",
|
|
40
40
|
"gemini-yes": "./dist/gemini-yes.js",
|
|
41
41
|
"grok-yes": "./dist/grok-yes.js",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"opencode-yes": "./dist/opencode-yes.js",
|
|
43
|
+
"qwen-yes": "./dist/qwen-yes.js"
|
|
44
44
|
},
|
|
45
45
|
"directories": {
|
|
46
46
|
"doc": "docs"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
|
+
"agent-yes.config.schema.json",
|
|
50
|
+
"bin",
|
|
51
|
+
"examples",
|
|
49
52
|
"scripts",
|
|
50
53
|
"ts/*.ts",
|
|
51
54
|
"!dist/**/*.map",
|
|
52
|
-
"dist/**/*.js"
|
|
53
|
-
"bin",
|
|
54
|
-
"agent-yes.config.schema.json",
|
|
55
|
-
"examples"
|
|
55
|
+
"dist/**/*.js"
|
|
56
56
|
],
|
|
57
57
|
"type": "module",
|
|
58
58
|
"module": "ts/index.ts",
|
package/scripts/verify-deps.js
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Pre-publish verification script
|
|
4
4
|
* Checks that critical runtime dependencies are properly configured
|
|
5
5
|
*/
|
|
6
|
-
import { readFileSync, existsSync } from
|
|
7
|
-
import { join } from
|
|
8
|
-
import { fileURLToPath } from
|
|
9
|
-
import { dirname } from
|
|
6
|
+
import { readFileSync, existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
import { dirname } from "path";
|
|
10
10
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
@@ -15,10 +15,10 @@ const errors = [];
|
|
|
15
15
|
const warnings = [];
|
|
16
16
|
|
|
17
17
|
// Load package.json
|
|
18
|
-
const pkgPath = join(__dirname,
|
|
19
|
-
const pkg = JSON.parse(readFileSync(pkgPath,
|
|
18
|
+
const pkgPath = join(__dirname, "..", "package.json");
|
|
19
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
20
20
|
|
|
21
|
-
console.log(
|
|
21
|
+
console.log("🔍 Verifying agent-yes package configuration...\n");
|
|
22
22
|
|
|
23
23
|
// Check 1: Verify critical runtime dependencies are in dependencies, not devDependencies
|
|
24
24
|
const runtimeImports = [
|
|
@@ -41,16 +41,16 @@ for (const { module, reason } of runtimeImports) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Check 2: Verify build script externalizes the right dependencies
|
|
44
|
-
const buildScript = pkg.scripts?.build ||
|
|
44
|
+
const buildScript = pkg.scripts?.build || "";
|
|
45
45
|
const externals = buildScript.match(/--external=[^\s]+/g) || [];
|
|
46
46
|
|
|
47
|
-
console.log(
|
|
47
|
+
console.log("\n📦 Build externals:");
|
|
48
48
|
for (const ext of externals) {
|
|
49
49
|
console.log(` ${ext}`);
|
|
50
|
-
const modName = ext.replace(
|
|
50
|
+
const modName = ext.replace("--external=", "");
|
|
51
51
|
|
|
52
52
|
// Check if externalized module is in dependencies
|
|
53
|
-
if (modName.startsWith(
|
|
53
|
+
if (modName.startsWith("@") || modName.includes("/")) {
|
|
54
54
|
const inDeps = pkg.dependencies?.[modName];
|
|
55
55
|
const inOptionalDeps = pkg.optionalDependencies?.[modName];
|
|
56
56
|
|
|
@@ -62,22 +62,22 @@ for (const ext of externals) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Check 3: Verify files field includes necessary runtime files
|
|
65
|
-
console.log(
|
|
65
|
+
console.log("\n📁 Package files configuration:");
|
|
66
66
|
const files = pkg.files || [];
|
|
67
|
-
console.log(` Files patterns: ${files.join(
|
|
67
|
+
console.log(` Files patterns: ${files.join(", ")}`);
|
|
68
68
|
|
|
69
|
-
if (!files.includes(
|
|
70
|
-
errors.push(
|
|
69
|
+
if (!files.includes("dist/**/*.js") && !files.includes("dist")) {
|
|
70
|
+
errors.push("❌ dist directory not included in package files");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
if (!files.includes(
|
|
74
|
-
warnings.push(
|
|
73
|
+
if (!files.includes("scripts") && pkg.scripts?.postinstall?.includes("scripts/")) {
|
|
74
|
+
warnings.push("⚠️ postinstall references scripts/ but it may not be included");
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// Check 4: Verify bin entries point to existing files (after build)
|
|
78
|
-
console.log(
|
|
78
|
+
console.log("\n🔗 Binary entries:");
|
|
79
79
|
for (const [name, path] of Object.entries(pkg.bin || {})) {
|
|
80
|
-
const fullPath = join(__dirname,
|
|
80
|
+
const fullPath = join(__dirname, "..", path);
|
|
81
81
|
if (existsSync(fullPath)) {
|
|
82
82
|
console.log(` ✓ ${name} → ${path}`);
|
|
83
83
|
} else {
|
|
@@ -87,26 +87,26 @@ for (const [name, path] of Object.entries(pkg.bin || {})) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Summary
|
|
90
|
-
console.log(
|
|
90
|
+
console.log("\n" + "=".repeat(60));
|
|
91
91
|
if (errors.length === 0 && warnings.length === 0) {
|
|
92
|
-
console.log(
|
|
92
|
+
console.log("✅ All checks passed! Package is ready for publish.\n");
|
|
93
93
|
process.exit(0);
|
|
94
94
|
} else {
|
|
95
95
|
if (errors.length > 0) {
|
|
96
|
-
console.error(
|
|
97
|
-
errors.forEach(err => console.error(` ${err}`));
|
|
96
|
+
console.error("\n❌ ERRORS FOUND:");
|
|
97
|
+
errors.forEach((err) => console.error(` ${err}`));
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
if (warnings.length > 0) {
|
|
101
|
-
console.warn(
|
|
102
|
-
warnings.forEach(warn => console.warn(` ${warn}`));
|
|
101
|
+
console.warn("\n⚠️ WARNINGS:");
|
|
102
|
+
warnings.forEach((warn) => console.warn(` ${warn}`));
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (errors.length > 0) {
|
|
106
|
-
console.error(
|
|
106
|
+
console.error("\n❌ Fix errors before publishing!\n");
|
|
107
107
|
process.exit(1);
|
|
108
108
|
} else {
|
|
109
|
-
console.warn(
|
|
109
|
+
console.warn("\n⚠️ Review warnings before publishing.\n");
|
|
110
110
|
process.exit(0);
|
|
111
111
|
}
|
|
112
112
|
}
|
package/ts/JsonlStore.ts
CHANGED
|
@@ -204,5 +204,7 @@ export class JsonlStore<T extends Record<string, any> = Record<string, any>> {
|
|
|
204
204
|
|
|
205
205
|
let idCounter = 0;
|
|
206
206
|
function generateId(): string {
|
|
207
|
-
return
|
|
207
|
+
return (
|
|
208
|
+
Date.now().toString(36) + (idCounter++).toString(36) + Math.random().toString(36).slice(2, 6)
|
|
209
|
+
);
|
|
208
210
|
}
|
package/ts/agentRegistry.ts
CHANGED
package/ts/buildRustArgs.spec.ts
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { buildRustArgs } from "./buildRustArgs";
|
|
4
4
|
|
|
5
|
-
const SUPPORTED_CLIS = [
|
|
5
|
+
const SUPPORTED_CLIS = [
|
|
6
|
+
"claude",
|
|
7
|
+
"gemini",
|
|
8
|
+
"codex",
|
|
9
|
+
"copilot",
|
|
10
|
+
"cursor",
|
|
11
|
+
"grok",
|
|
12
|
+
"qwen",
|
|
13
|
+
"auggie",
|
|
14
|
+
"amp",
|
|
15
|
+
"opencode",
|
|
16
|
+
];
|
|
6
17
|
|
|
7
18
|
// Helper: simulate argv as [node, script, ...userArgs]
|
|
8
19
|
function argv(...userArgs: string[]): string[] {
|
|
@@ -21,7 +32,11 @@ describe("buildRustArgs", () => {
|
|
|
21
32
|
});
|
|
22
33
|
|
|
23
34
|
it("appends CLI name after multiple flags", () => {
|
|
24
|
-
const result = buildRustArgs(
|
|
35
|
+
const result = buildRustArgs(
|
|
36
|
+
argv("--timeout", "30s", "--verbose", "--robust", "true"),
|
|
37
|
+
"claude",
|
|
38
|
+
SUPPORTED_CLIS,
|
|
39
|
+
);
|
|
25
40
|
expect(result).toEqual(["--timeout", "30s", "--verbose", "--robust", "true", "claude"]);
|
|
26
41
|
});
|
|
27
42
|
|
|
@@ -89,7 +104,11 @@ describe("buildRustArgs", () => {
|
|
|
89
104
|
});
|
|
90
105
|
|
|
91
106
|
it("removes --rust regardless of position", () => {
|
|
92
|
-
const result = buildRustArgs(
|
|
107
|
+
const result = buildRustArgs(
|
|
108
|
+
argv("--verbose", "--rust", "--timeout", "5m"),
|
|
109
|
+
"claude",
|
|
110
|
+
SUPPORTED_CLIS,
|
|
111
|
+
);
|
|
93
112
|
expect(result).not.toContain("--rust");
|
|
94
113
|
expect(result).toEqual(["--verbose", "--timeout", "5m", "claude"]);
|
|
95
114
|
});
|
|
@@ -105,13 +124,21 @@ describe("buildRustArgs", () => {
|
|
|
105
124
|
});
|
|
106
125
|
|
|
107
126
|
it("skips appending when --cli= flag is used", () => {
|
|
108
|
-
const result = buildRustArgs(
|
|
127
|
+
const result = buildRustArgs(
|
|
128
|
+
argv("--cli=gemini", "--timeout", "30s"),
|
|
129
|
+
"claude",
|
|
130
|
+
SUPPORTED_CLIS,
|
|
131
|
+
);
|
|
109
132
|
expect(result).toEqual(["--cli=gemini", "--timeout", "30s"]);
|
|
110
133
|
expect(result).not.toContain("claude");
|
|
111
134
|
});
|
|
112
135
|
|
|
113
136
|
it("skips appending when --cli flag is used (separate value)", () => {
|
|
114
|
-
const result = buildRustArgs(
|
|
137
|
+
const result = buildRustArgs(
|
|
138
|
+
argv("--cli", "gemini", "--timeout", "30s"),
|
|
139
|
+
"claude",
|
|
140
|
+
SUPPORTED_CLIS,
|
|
141
|
+
);
|
|
115
142
|
expect(result).toEqual(["--cli", "gemini", "--timeout", "30s"]);
|
|
116
143
|
expect(result).not.toContain("claude");
|
|
117
144
|
});
|
|
@@ -218,7 +245,11 @@ describe("buildRustArgs", () => {
|
|
|
218
245
|
});
|
|
219
246
|
|
|
220
247
|
it("claude-yes --rust --timeout 30s --verbose", () => {
|
|
221
|
-
const result = buildRustArgs(
|
|
248
|
+
const result = buildRustArgs(
|
|
249
|
+
argv("--rust", "--timeout", "30s", "--verbose"),
|
|
250
|
+
"claude",
|
|
251
|
+
SUPPORTED_CLIS,
|
|
252
|
+
);
|
|
222
253
|
expect(result).toEqual(["--timeout", "30s", "--verbose", "claude"]);
|
|
223
254
|
});
|
|
224
255
|
|
|
@@ -237,7 +268,11 @@ describe("buildRustArgs", () => {
|
|
|
237
268
|
});
|
|
238
269
|
|
|
239
270
|
it("agent-yes --rust claude --timeout 1h (explicit CLI in args)", () => {
|
|
240
|
-
const result = buildRustArgs(
|
|
271
|
+
const result = buildRustArgs(
|
|
272
|
+
argv("--rust", "claude", "--timeout", "1h"),
|
|
273
|
+
undefined,
|
|
274
|
+
SUPPORTED_CLIS,
|
|
275
|
+
);
|
|
241
276
|
// cliFromScript is undefined (agent-yes), CLI already in args
|
|
242
277
|
expect(result).toEqual(["claude", "--timeout", "1h"]);
|
|
243
278
|
});
|
package/ts/buildRustArgs.ts
CHANGED
|
@@ -13,20 +13,15 @@ export function buildRustArgs(
|
|
|
13
13
|
supportedClis: readonly string[],
|
|
14
14
|
): string[] {
|
|
15
15
|
// Filter out --rust flag (already handled by TS layer)
|
|
16
|
-
const rawRustArgs = argv
|
|
17
|
-
.slice(2)
|
|
18
|
-
.filter((arg) => arg !== "--rust" && !arg.startsWith("--rust="));
|
|
16
|
+
const rawRustArgs = argv.slice(2).filter((arg) => arg !== "--rust" && !arg.startsWith("--rust="));
|
|
19
17
|
|
|
20
18
|
// Check if swarm mode is requested (don't append CLI name for swarm mode)
|
|
21
|
-
const hasSwarmArg = rawRustArgs.some(
|
|
22
|
-
(arg) => arg === "--swarm" || arg.startsWith("--swarm="),
|
|
23
|
-
);
|
|
19
|
+
const hasSwarmArg = rawRustArgs.some((arg) => arg === "--swarm" || arg.startsWith("--swarm="));
|
|
24
20
|
|
|
25
21
|
// Check if CLI is already specified in args
|
|
26
22
|
const hasCliArg =
|
|
27
|
-
rawRustArgs.some(
|
|
28
|
-
|
|
29
|
-
) || rawRustArgs.some((arg) => supportedClis.includes(arg));
|
|
23
|
+
rawRustArgs.some((arg) => arg.startsWith("--cli=") || arg === "--cli") ||
|
|
24
|
+
rawRustArgs.some((arg) => supportedClis.includes(arg));
|
|
30
25
|
|
|
31
26
|
// Append CLI name at the end so it doesn't trigger trailing_var_arg in clap,
|
|
32
27
|
// which would cause all subsequent args (like --timeout) to be treated as positional
|
package/ts/configLoader.spec.ts
CHANGED
|
@@ -26,7 +26,7 @@ describe("configLoader", () => {
|
|
|
26
26
|
defaultArgs: ["--verbose"],
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
-
})
|
|
29
|
+
}),
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
@@ -44,7 +44,7 @@ clis:
|
|
|
44
44
|
gemini:
|
|
45
45
|
defaultArgs:
|
|
46
46
|
- --resume
|
|
47
|
-
|
|
47
|
+
`,
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
@@ -58,7 +58,7 @@ clis:
|
|
|
58
58
|
configPath,
|
|
59
59
|
`
|
|
60
60
|
logsDir: /custom/logs
|
|
61
|
-
|
|
61
|
+
`,
|
|
62
62
|
);
|
|
63
63
|
|
|
64
64
|
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
@@ -68,12 +68,9 @@ logsDir: /custom/logs
|
|
|
68
68
|
it("should prefer JSON over YAML when both exist", async () => {
|
|
69
69
|
await writeFile(
|
|
70
70
|
path.join(testDir, ".agent-yes.config.json"),
|
|
71
|
-
JSON.stringify({ configDir: "/json/config" })
|
|
72
|
-
);
|
|
73
|
-
await writeFile(
|
|
74
|
-
path.join(testDir, ".agent-yes.config.yaml"),
|
|
75
|
-
`configDir: /yaml/config`
|
|
71
|
+
JSON.stringify({ configDir: "/json/config" }),
|
|
76
72
|
);
|
|
73
|
+
await writeFile(path.join(testDir, ".agent-yes.config.yaml"), `configDir: /yaml/config`);
|
|
77
74
|
|
|
78
75
|
const config = await loadCascadingConfig({ projectDir: testDir });
|
|
79
76
|
expect(config.configDir).toBe("/json/config");
|
|
@@ -110,14 +107,14 @@ logsDir: /custom/logs
|
|
|
110
107
|
JSON.stringify({
|
|
111
108
|
configDir: "/home/config",
|
|
112
109
|
logsDir: "/home/logs",
|
|
113
|
-
})
|
|
110
|
+
}),
|
|
114
111
|
);
|
|
115
112
|
|
|
116
113
|
await writeFile(
|
|
117
114
|
path.join(projectDir, ".agent-yes.config.json"),
|
|
118
115
|
JSON.stringify({
|
|
119
116
|
configDir: "/project/config",
|
|
120
|
-
})
|
|
117
|
+
}),
|
|
121
118
|
);
|
|
122
119
|
|
|
123
120
|
const config = await loadCascadingConfig({ projectDir, homeDir });
|
|
@@ -127,10 +124,7 @@ logsDir: /custom/logs
|
|
|
127
124
|
|
|
128
125
|
it("should add schema reference to JSON config without one", async () => {
|
|
129
126
|
const configPath = path.join(testDir, ".agent-yes.config.json");
|
|
130
|
-
await writeFile(
|
|
131
|
-
configPath,
|
|
132
|
-
JSON.stringify({ configDir: "/test" })
|
|
133
|
-
);
|
|
127
|
+
await writeFile(configPath, JSON.stringify({ configDir: "/test" }));
|
|
134
128
|
|
|
135
129
|
const result = await ensureSchemaInConfigFiles({ projectDir: testDir, homeDir: testDir });
|
|
136
130
|
expect(result.modified).toContain(configPath);
|
|
@@ -150,7 +144,7 @@ clis:
|
|
|
150
144
|
claude:
|
|
151
145
|
defaultArgs:
|
|
152
146
|
- --verbose
|
|
153
|
-
|
|
147
|
+
`,
|
|
154
148
|
);
|
|
155
149
|
|
|
156
150
|
const result = await ensureSchemaInConfigFiles({ projectDir: testDir, homeDir: testDir });
|
|
@@ -164,10 +158,14 @@ clis:
|
|
|
164
158
|
|
|
165
159
|
it("should skip JSON config that already has schema", async () => {
|
|
166
160
|
const configPath = path.join(testDir, ".agent-yes.config.json");
|
|
167
|
-
const originalContent = JSON.stringify(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
const originalContent = JSON.stringify(
|
|
162
|
+
{
|
|
163
|
+
$schema: "https://example.com/schema.json",
|
|
164
|
+
configDir: "/test",
|
|
165
|
+
},
|
|
166
|
+
null,
|
|
167
|
+
2,
|
|
168
|
+
);
|
|
171
169
|
await writeFile(configPath, originalContent);
|
|
172
170
|
|
|
173
171
|
const result = await ensureSchemaInConfigFiles({ projectDir: testDir, homeDir: testDir });
|
package/ts/configLoader.ts
CHANGED
|
@@ -12,7 +12,8 @@ import { deepMixin } from "./utils.ts";
|
|
|
12
12
|
|
|
13
13
|
const CONFIG_FILENAME = ".agent-yes.config";
|
|
14
14
|
const CONFIG_EXTENSIONS = [".json", ".yml", ".yaml"] as const;
|
|
15
|
-
const SCHEMA_URL =
|
|
15
|
+
const SCHEMA_URL =
|
|
16
|
+
"https://raw.githubusercontent.com/snomiao/agent-yes/main/agent-yes.config.schema.json";
|
|
16
17
|
const YAML_SCHEMA_COMMENT = `# yaml-language-server: $schema=${SCHEMA_URL}`;
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -97,7 +98,7 @@ export interface ConfigLoadOptions {
|
|
|
97
98
|
* Higher priority configs override lower priority ones
|
|
98
99
|
*/
|
|
99
100
|
export async function loadCascadingConfig(
|
|
100
|
-
options: ConfigLoadOptions = {}
|
|
101
|
+
options: ConfigLoadOptions = {},
|
|
101
102
|
): Promise<Partial<AgentYesConfig>> {
|
|
102
103
|
const projectDir = options.projectDir ?? process.cwd();
|
|
103
104
|
const homeDir = options.homeDir ?? os.homedir();
|
|
@@ -114,9 +115,7 @@ export async function loadCascadingConfig(
|
|
|
114
115
|
]);
|
|
115
116
|
|
|
116
117
|
// Filter out empty configs and merge
|
|
117
|
-
const nonEmptyConfigs = configs.filter(
|
|
118
|
-
(c) => c && Object.keys(c).length > 0
|
|
119
|
-
);
|
|
118
|
+
const nonEmptyConfigs = configs.filter((c) => c && Object.keys(c).length > 0);
|
|
120
119
|
|
|
121
120
|
if (nonEmptyConfigs.length === 0) {
|
|
122
121
|
logger.debug("[config] No config files found in any location");
|
|
@@ -212,7 +211,7 @@ function addSchemaReference(content: string, ext: string): string {
|
|
|
212
211
|
* Modifies files in-place if they don't have a schema reference
|
|
213
212
|
*/
|
|
214
213
|
export async function ensureSchemaInConfigFiles(
|
|
215
|
-
options: ConfigLoadOptions = {}
|
|
214
|
+
options: ConfigLoadOptions = {},
|
|
216
215
|
): Promise<{ modified: string[]; skipped: string[] }> {
|
|
217
216
|
const projectDir = options.projectDir ?? process.cwd();
|
|
218
217
|
const homeDir = options.homeDir ?? os.homedir();
|
package/ts/index.ts
CHANGED
|
@@ -178,7 +178,10 @@ export default async function agentYes({
|
|
|
178
178
|
// Track when user sends Ctrl+C to avoid treating intentional exit as crash
|
|
179
179
|
let userSentCtrlC = false;
|
|
180
180
|
|
|
181
|
-
if (verbose)
|
|
181
|
+
if (verbose)
|
|
182
|
+
logger.debug(
|
|
183
|
+
`[stdin] isTTY: ${process.stdin.isTTY}, setRawMode available: ${!!process.stdin.setRawMode}`,
|
|
184
|
+
);
|
|
182
185
|
process.stdin.setRawMode?.(true); // must be called any stdout/stdin usage
|
|
183
186
|
if (verbose) logger.debug(`[stdin] Raw mode set, isRaw: ${(process.stdin as any).isRaw}`);
|
|
184
187
|
|
|
@@ -358,7 +361,7 @@ export default async function agentYes({
|
|
|
358
361
|
cli,
|
|
359
362
|
prompt,
|
|
360
363
|
startTime: Date.now(),
|
|
361
|
-
stdoutBuffer: []
|
|
364
|
+
stdoutBuffer: [],
|
|
362
365
|
});
|
|
363
366
|
} catch (error) {
|
|
364
367
|
logger.warn(`[agentRegistry] Failed to register agent ${shell.pid}:`, error);
|
|
@@ -450,7 +453,7 @@ export default async function agentYes({
|
|
|
450
453
|
cli,
|
|
451
454
|
prompt,
|
|
452
455
|
startTime: Date.now(),
|
|
453
|
-
stdoutBuffer: []
|
|
456
|
+
stdoutBuffer: [],
|
|
454
457
|
});
|
|
455
458
|
} catch (error) {
|
|
456
459
|
logger.warn(`[agentRegistry] Failed to register restarted agent ${shell.pid}:`, error);
|
|
@@ -522,7 +525,13 @@ export default async function agentYes({
|
|
|
522
525
|
shell = pty.spawn(cli, restoreArgs, restorePtyOptions);
|
|
523
526
|
// Register process in pidStore (non-blocking)
|
|
524
527
|
try {
|
|
525
|
-
await pidStore.registerProcess({
|
|
528
|
+
await pidStore.registerProcess({
|
|
529
|
+
pid: shell.pid,
|
|
530
|
+
cli,
|
|
531
|
+
args: restoreArgs,
|
|
532
|
+
prompt,
|
|
533
|
+
cwd: workingDir,
|
|
534
|
+
});
|
|
526
535
|
} catch (error) {
|
|
527
536
|
logger.warn(`[pidStore] Failed to register restored process ${shell.pid}:`, error);
|
|
528
537
|
}
|
|
@@ -537,7 +546,7 @@ export default async function agentYes({
|
|
|
537
546
|
cli,
|
|
538
547
|
prompt,
|
|
539
548
|
startTime: Date.now(),
|
|
540
|
-
stdoutBuffer: []
|
|
549
|
+
stdoutBuffer: [],
|
|
541
550
|
});
|
|
542
551
|
} catch (error) {
|
|
543
552
|
logger.warn(`[agentRegistry] Failed to register restored agent ${shell.pid}:`, error);
|
|
@@ -561,7 +570,9 @@ export default async function agentYes({
|
|
|
561
570
|
} catch (error) {
|
|
562
571
|
logger.warn(`[pidStore] Failed to update status for PID ${exitedPid}:`, error);
|
|
563
572
|
}
|
|
564
|
-
notifyWebhook("EXIT", `${exitReason} exitCode=${exitCode ?? "?"}`, workingDir).catch(
|
|
573
|
+
notifyWebhook("EXIT", `${exitReason} exitCode=${exitCode ?? "?"}`, workingDir).catch(
|
|
574
|
+
() => null,
|
|
575
|
+
);
|
|
565
576
|
return pendingExitCode.resolve(exitCode);
|
|
566
577
|
});
|
|
567
578
|
|
|
@@ -687,7 +698,7 @@ export default async function agentYes({
|
|
|
687
698
|
const dataHandler = (chunk: Buffer) => {
|
|
688
699
|
try {
|
|
689
700
|
controller.enqueue(chunk);
|
|
690
|
-
} catch
|
|
701
|
+
} catch {
|
|
691
702
|
// Ignore enqueue errors (stream may be closed)
|
|
692
703
|
}
|
|
693
704
|
};
|
|
@@ -698,7 +709,7 @@ export default async function agentYes({
|
|
|
698
709
|
closed = true;
|
|
699
710
|
try {
|
|
700
711
|
controller.close();
|
|
701
|
-
} catch
|
|
712
|
+
} catch {
|
|
702
713
|
// Ignore close errors (already closed)
|
|
703
714
|
}
|
|
704
715
|
};
|
|
@@ -708,19 +719,19 @@ export default async function agentYes({
|
|
|
708
719
|
closed = true;
|
|
709
720
|
try {
|
|
710
721
|
controller.error(err);
|
|
711
|
-
} catch
|
|
722
|
+
} catch {
|
|
712
723
|
// Ignore error after close
|
|
713
724
|
}
|
|
714
725
|
};
|
|
715
726
|
|
|
716
|
-
process.stdin.on(
|
|
717
|
-
process.stdin.on(
|
|
718
|
-
process.stdin.on(
|
|
719
|
-
process.stdin.on(
|
|
727
|
+
process.stdin.on("data", dataHandler);
|
|
728
|
+
process.stdin.on("end", endHandler);
|
|
729
|
+
process.stdin.on("close", endHandler);
|
|
730
|
+
process.stdin.on("error", errorHandler);
|
|
720
731
|
},
|
|
721
732
|
cancel(reason) {
|
|
722
733
|
process.stdin.pause();
|
|
723
|
-
}
|
|
734
|
+
},
|
|
724
735
|
});
|
|
725
736
|
|
|
726
737
|
let aborted = false;
|
|
@@ -752,63 +763,71 @@ export default async function agentYes({
|
|
|
752
763
|
if (str === CTRL_C) {
|
|
753
764
|
userSentCtrlC = true;
|
|
754
765
|
// Reset flag after 2 seconds in case CLI doesn't exit immediately
|
|
755
|
-
setTimeout(() => {
|
|
766
|
+
setTimeout(() => {
|
|
767
|
+
userSentCtrlC = false;
|
|
768
|
+
}, 2000);
|
|
756
769
|
}
|
|
757
770
|
|
|
758
771
|
return str;
|
|
759
772
|
})
|
|
760
773
|
|
|
761
774
|
// Detect Ctrl+Y or /auto command to toggle auto-yes mode
|
|
762
|
-
.map(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
ctx.
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const status = ctx.autoYesEnabled ? "\x1b[32m[auto-yes: ON]\x1b[0m" : "\x1b[33m[auto-yes: OFF]\x1b[0m";
|
|
772
|
-
process.stderr.write(`\r${status} (Ctrl+Y to toggle)\n`);
|
|
773
|
-
};
|
|
774
|
-
return (data: string) => {
|
|
775
|
-
let out = "";
|
|
776
|
-
for (const ch of data) {
|
|
777
|
-
// Ctrl+Y (\x19) toggles auto-yes immediately
|
|
778
|
-
if (ch === "\x19") {
|
|
779
|
-
toggleAutoYes();
|
|
780
|
-
// Do not forward Ctrl+Y to the PTY
|
|
781
|
-
continue;
|
|
775
|
+
.map(
|
|
776
|
+
(() => {
|
|
777
|
+
let line = "";
|
|
778
|
+
const toggleAutoYes = () => {
|
|
779
|
+
ctx.autoYesEnabled = !ctx.autoYesEnabled;
|
|
780
|
+
// When switching to manual mode, mark stdin ready so user keystrokes are not blocked
|
|
781
|
+
if (!ctx.autoYesEnabled) {
|
|
782
|
+
ctx.stdinReady.ready();
|
|
783
|
+
ctx.stdinFirstReady.ready();
|
|
782
784
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
785
|
+
const status = ctx.autoYesEnabled
|
|
786
|
+
? "\x1b[32m[auto-yes: ON]\x1b[0m"
|
|
787
|
+
: "\x1b[33m[auto-yes: OFF]\x1b[0m";
|
|
788
|
+
process.stderr.write(`\r${status} (Ctrl+Y to toggle)\n`);
|
|
789
|
+
};
|
|
790
|
+
return (data: string) => {
|
|
791
|
+
let out = "";
|
|
792
|
+
for (const ch of data) {
|
|
793
|
+
// Ctrl+Y (\x19) toggles auto-yes immediately
|
|
794
|
+
if (ch === "\x19") {
|
|
795
|
+
toggleAutoYes();
|
|
796
|
+
// Do not forward Ctrl+Y to the PTY
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
// Handle Enter
|
|
800
|
+
if (ch === "\r" || ch === "\n") {
|
|
801
|
+
// Only check for /auto if line is short enough
|
|
802
|
+
if (line.length <= 20) {
|
|
803
|
+
const cleanLine = line
|
|
804
|
+
.replace(/[\x00-\x1f]|\x1b\[[0-9;]*[A-Za-z]|\[[A-Z]/g, "")
|
|
805
|
+
.trim();
|
|
806
|
+
if (cleanLine === "/auto") {
|
|
807
|
+
out += "\x15"; // Ctrl+U to clear the /auto text from shell input
|
|
808
|
+
toggleAutoYes();
|
|
809
|
+
line = "";
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
793
812
|
}
|
|
813
|
+
line = "";
|
|
814
|
+
out += ch;
|
|
815
|
+
continue;
|
|
794
816
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
817
|
+
// Handle backspace
|
|
818
|
+
if (ch === "\x7f" || ch === "\b") {
|
|
819
|
+
if (line.length > 0) line = line.slice(0, -1);
|
|
820
|
+
out += ch;
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
// Track only printable ASCII for line, with size limit
|
|
824
|
+
if (ch >= " " && ch <= "~" && line.length < 50) line += ch;
|
|
802
825
|
out += ch;
|
|
803
|
-
continue;
|
|
804
826
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return out;
|
|
810
|
-
};
|
|
811
|
-
})())
|
|
827
|
+
return out;
|
|
828
|
+
};
|
|
829
|
+
})(),
|
|
830
|
+
)
|
|
812
831
|
|
|
813
832
|
// TODO(sno): Read from IPC stream if available (FIFO on Linux, Named Pipes on Windows)
|
|
814
833
|
// .by(async (s) => {
|
package/ts/parseCliArgs.spec.ts
CHANGED
|
@@ -248,13 +248,29 @@ describe("CLI argument parsing", () => {
|
|
|
248
248
|
});
|
|
249
249
|
|
|
250
250
|
it("should prioritize --timeout over --exit-on-idle", () => {
|
|
251
|
-
const result = parseCliArgs([
|
|
251
|
+
const result = parseCliArgs([
|
|
252
|
+
"node",
|
|
253
|
+
"/path/to/cli",
|
|
254
|
+
"--timeout",
|
|
255
|
+
"30s",
|
|
256
|
+
"--exit-on-idle",
|
|
257
|
+
"1m",
|
|
258
|
+
"claude",
|
|
259
|
+
]);
|
|
252
260
|
|
|
253
261
|
expect(result.exitOnIdle).toBe(30000);
|
|
254
262
|
});
|
|
255
263
|
|
|
256
264
|
it("should prioritize --timeout over --idle", () => {
|
|
257
|
-
const result = parseCliArgs([
|
|
265
|
+
const result = parseCliArgs([
|
|
266
|
+
"node",
|
|
267
|
+
"/path/to/cli",
|
|
268
|
+
"--timeout",
|
|
269
|
+
"15s",
|
|
270
|
+
"--idle",
|
|
271
|
+
"1m",
|
|
272
|
+
"claude",
|
|
273
|
+
]);
|
|
258
274
|
|
|
259
275
|
expect(result.exitOnIdle).toBe(15000);
|
|
260
276
|
});
|
|
@@ -265,7 +281,7 @@ describe("CLI argument parsing", () => {
|
|
|
265
281
|
parseCliArgs(["node", "/path/to/cli", "--exit-on-idle", "1m", "claude"]);
|
|
266
282
|
|
|
267
283
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
268
|
-
expect.stringContaining("--exit-on-idle and -e are deprecated")
|
|
284
|
+
expect.stringContaining("--exit-on-idle and -e are deprecated"),
|
|
269
285
|
);
|
|
270
286
|
|
|
271
287
|
warnSpy.mockRestore();
|
|
@@ -277,7 +293,7 @@ describe("CLI argument parsing", () => {
|
|
|
277
293
|
parseCliArgs(["node", "/path/to/cli", "-e", "1m", "claude"]);
|
|
278
294
|
|
|
279
295
|
expect(warnSpy).toHaveBeenCalledWith(
|
|
280
|
-
expect.stringContaining("--exit-on-idle and -e are deprecated")
|
|
296
|
+
expect.stringContaining("--exit-on-idle and -e are deprecated"),
|
|
281
297
|
);
|
|
282
298
|
|
|
283
299
|
warnSpy.mockRestore();
|
package/ts/parseCliArgs.ts
CHANGED
|
@@ -79,7 +79,8 @@ export function parseCliArgs(argv: string[]) {
|
|
|
79
79
|
})
|
|
80
80
|
.option("idle-action", {
|
|
81
81
|
type: "string",
|
|
82
|
-
description:
|
|
82
|
+
description:
|
|
83
|
+
'Idle action to perform when idle time is reached, e.g., "/exit" or "check TODO.md"',
|
|
83
84
|
})
|
|
84
85
|
.option("queue", {
|
|
85
86
|
type: "boolean",
|
|
@@ -111,7 +112,8 @@ export function parseCliArgs(argv: string[]) {
|
|
|
111
112
|
})
|
|
112
113
|
.option("auto", {
|
|
113
114
|
type: "string",
|
|
114
|
-
description:
|
|
115
|
+
description:
|
|
116
|
+
"Control auto-yes mode: 'yes' to auto-approve prompts (default), 'no' to start in manual mode. Press Ctrl+Y during the session to toggle at any time.",
|
|
115
117
|
choices: ["yes", "no"] as const,
|
|
116
118
|
default: "yes",
|
|
117
119
|
})
|
package/ts/rustBinary.ts
CHANGED
|
@@ -27,7 +27,7 @@ export function getBinaryName(): string {
|
|
|
27
27
|
if (!binaryName) {
|
|
28
28
|
throw new Error(
|
|
29
29
|
`Unsupported platform: ${platform}-${arch}. ` +
|
|
30
|
-
`Supported: ${Object.keys(PLATFORM_MAP).join(", ")}
|
|
30
|
+
`Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`,
|
|
31
31
|
);
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -49,7 +49,7 @@ export function getBinDir(): string {
|
|
|
49
49
|
process.env.AGENT_YES_CACHE_DIR ||
|
|
50
50
|
path.join(
|
|
51
51
|
process.env.XDG_CACHE_HOME || path.join(process.env.HOME || "/tmp", ".cache"),
|
|
52
|
-
"agent-yes"
|
|
52
|
+
"agent-yes",
|
|
53
53
|
);
|
|
54
54
|
|
|
55
55
|
return path.join(cacheDir, "bin");
|
|
@@ -136,8 +136,12 @@ export async function downloadBinary(verbose = false): Promise<string> {
|
|
|
136
136
|
|
|
137
137
|
// Use PowerShell to extract zip
|
|
138
138
|
const proc = Bun.spawn(
|
|
139
|
-
[
|
|
140
|
-
|
|
139
|
+
[
|
|
140
|
+
"powershell",
|
|
141
|
+
"-Command",
|
|
142
|
+
`Expand-Archive -Path '${tempZipPath}' -DestinationPath '${binDir}' -Force`,
|
|
143
|
+
],
|
|
144
|
+
{ cwd: binDir, stdio: ["ignore", "pipe", "pipe"] },
|
|
141
145
|
);
|
|
142
146
|
await proc.exited;
|
|
143
147
|
|
|
@@ -191,10 +195,12 @@ export async function downloadBinary(verbose = false): Promise<string> {
|
|
|
191
195
|
/**
|
|
192
196
|
* Get or download the Rust binary
|
|
193
197
|
*/
|
|
194
|
-
export async function getRustBinary(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
export async function getRustBinary(
|
|
199
|
+
options: {
|
|
200
|
+
verbose?: boolean;
|
|
201
|
+
forceDownload?: boolean;
|
|
202
|
+
} = {},
|
|
203
|
+
): Promise<string> {
|
|
198
204
|
const { verbose = false, forceDownload = false } = options;
|
|
199
205
|
|
|
200
206
|
// First try to find existing binary
|
|
@@ -218,7 +224,7 @@ export async function getRustBinary(options: {
|
|
|
218
224
|
} catch (err) {
|
|
219
225
|
throw new Error(
|
|
220
226
|
`Failed to get Rust binary: ${err instanceof Error ? err.message : err}\n` +
|
|
221
|
-
`You can build manually with: cd rs && cargo build --release
|
|
227
|
+
`You can build manually with: cd rs && cargo build --release`,
|
|
222
228
|
);
|
|
223
229
|
}
|
|
224
230
|
}
|
package/ts/tryCatch.spec.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import { tryCatch } from "./tryCatch";
|
|
3
3
|
|
|
4
4
|
describe("tryCatch", () => {
|
|
5
|
-
|
|
6
5
|
describe("direct overload", () => {
|
|
7
6
|
it("should catch errors and call catchFn with error, attempts, robustFn, and args", () => {
|
|
8
7
|
let catchedError: unknown;
|
|
@@ -213,11 +212,11 @@ describe("tryCatch", () => {
|
|
|
213
212
|
};
|
|
214
213
|
|
|
215
214
|
const wrappedFn = tryCatch(catchFn, sometimesFails);
|
|
216
|
-
expect(wrappedFn()).toBe(1);
|
|
217
|
-
expect(wrappedFn()).toBe(-1);
|
|
215
|
+
expect(wrappedFn()).toBe(1); // attempt 1, success
|
|
216
|
+
expect(wrappedFn()).toBe(-1); // attempt 2, fail
|
|
218
217
|
expect(lastAttempts).toBe(2);
|
|
219
|
-
expect(wrappedFn()).toBe(3);
|
|
220
|
-
expect(wrappedFn()).toBe(-1);
|
|
218
|
+
expect(wrappedFn()).toBe(3); // attempt 3, success
|
|
219
|
+
expect(wrappedFn()).toBe(-1); // attempt 4, fail
|
|
221
220
|
expect(lastAttempts).toBe(4);
|
|
222
221
|
});
|
|
223
222
|
|
|
@@ -242,7 +241,8 @@ describe("tryCatch", () => {
|
|
|
242
241
|
|
|
243
242
|
describe("type safety", () => {
|
|
244
243
|
it("should maintain function signature", () => {
|
|
245
|
-
const catchFn = (_error: unknown, _attempts: number, _fn: unknown, ..._args: unknown[]) =>
|
|
244
|
+
const catchFn = (_error: unknown, _attempts: number, _fn: unknown, ..._args: unknown[]) =>
|
|
245
|
+
"error";
|
|
246
246
|
const originalFn = (a: number, b: string): string => `${a}-${b}`;
|
|
247
247
|
|
|
248
248
|
const wrappedFn = tryCatch(catchFn, originalFn);
|
package/ts/versionChecker.ts
CHANGED
|
@@ -13,9 +13,9 @@ export async function fetchLatestVersion(): Promise<string | null> {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const data = await response.json() as { version: string };
|
|
16
|
+
const data = (await response.json()) as { version: string };
|
|
17
17
|
return data.version;
|
|
18
|
-
} catch
|
|
18
|
+
} catch {
|
|
19
19
|
// Silently fail if network is unavailable or request times out
|
|
20
20
|
return null;
|
|
21
21
|
}
|
|
@@ -26,8 +26,8 @@ export async function fetchLatestVersion(): Promise<string | null> {
|
|
|
26
26
|
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
27
27
|
*/
|
|
28
28
|
export function compareVersions(v1: string, v2: string): number {
|
|
29
|
-
const parts1 = v1.split(
|
|
30
|
-
const parts2 = v2.split(
|
|
29
|
+
const parts1 = v1.split(".").map(Number);
|
|
30
|
+
const parts2 = v2.split(".").map(Number);
|
|
31
31
|
|
|
32
32
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
33
33
|
const part1 = parts1[i] || 0;
|