claude-yes 1.72.3 → 1.72.4
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/dist/{SUPPORTED_CLIS-C-KnmE0Y.js → SUPPORTED_CLIS-Bqw9gxey.js} +2 -2
- package/dist/cli.js +52 -25
- package/dist/index.js +1 -1
- package/dist/{tray-BQkynk6r.js → tray-CPpdxTV-.js} +1 -10
- package/package.json +1 -1
- package/ts/cli.ts +3 -5
- package/ts/tray.spec.ts +0 -56
- package/ts/tray.ts +0 -12
- package/ts/versionChecker.spec.ts +25 -2
- package/ts/versionChecker.ts +54 -20
|
@@ -815,7 +815,7 @@ function tryCatch(catchFn, fn) {
|
|
|
815
815
|
//#endregion
|
|
816
816
|
//#region package.json
|
|
817
817
|
var name = "agent-yes";
|
|
818
|
-
var version = "1.72.
|
|
818
|
+
var version = "1.72.4";
|
|
819
819
|
|
|
820
820
|
//#endregion
|
|
821
821
|
//#region ts/pty-fix.ts
|
|
@@ -1895,4 +1895,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
|
1895
1895
|
|
|
1896
1896
|
//#endregion
|
|
1897
1897
|
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 };
|
|
1898
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
1898
|
+
//# sourceMappingURL=SUPPORTED_CLIS-Bqw9gxey.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
2
|
+
import { c as PidStore, o as name, s as version, t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-Bqw9gxey.js";
|
|
3
3
|
import { t as logger } from "./logger-CX77vJDA.js";
|
|
4
4
|
import { argv } from "process";
|
|
5
|
-
import { spawn } from "child_process";
|
|
5
|
+
import { execFileSync, spawn } from "child_process";
|
|
6
6
|
import ms from "ms";
|
|
7
7
|
import yargs from "yargs";
|
|
8
8
|
import { hideBin } from "yargs/helpers";
|
|
@@ -225,44 +225,72 @@ async function writeUpdateCache(data) {
|
|
|
225
225
|
await writeFile(CACHE_FILE, JSON.stringify(data));
|
|
226
226
|
}
|
|
227
227
|
function detectPackageManager() {
|
|
228
|
-
if (process.env.BUN_INSTALL || process.env.npm_execpath?.includes("bun")) return "bun";
|
|
228
|
+
if (process.env.BUN_INSTALL || process.execPath?.includes("bun") || process.env.npm_execpath?.includes("bun")) return "bun";
|
|
229
229
|
return "npm";
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
232
|
-
* Check for updates
|
|
232
|
+
* Check for updates, auto-install if newer version is available, and re-exec
|
|
233
|
+
* so the current invocation always runs the latest code.
|
|
234
|
+
*
|
|
233
235
|
* Uses a 1-hour TTL cache to avoid hitting the registry on every run.
|
|
234
236
|
* All errors are swallowed — network issues must never break the tool.
|
|
235
237
|
* Set AGENT_YES_NO_UPDATE=1 to opt out.
|
|
238
|
+
*
|
|
239
|
+
* The AGENT_YES_UPDATED env var prevents infinite re-exec loops:
|
|
240
|
+
* after updating we re-exec with AGENT_YES_UPDATED=<version> so the
|
|
241
|
+
* new process skips the update check.
|
|
236
242
|
*/
|
|
237
243
|
async function checkAndAutoUpdate() {
|
|
238
244
|
if (process.env.AGENT_YES_NO_UPDATE) return;
|
|
245
|
+
if (process.env.AGENT_YES_UPDATED === version) return;
|
|
239
246
|
try {
|
|
247
|
+
let latestVersion;
|
|
240
248
|
const cache = await readUpdateCache();
|
|
241
|
-
if (cache && Date.now() - cache.checkedAt < TTL_MS)
|
|
242
|
-
|
|
243
|
-
|
|
249
|
+
if (cache && Date.now() - cache.checkedAt < TTL_MS) latestVersion = cache.latestVersion;
|
|
250
|
+
else {
|
|
251
|
+
const fetched = await fetchLatestVersion();
|
|
252
|
+
if (!fetched) return;
|
|
253
|
+
latestVersion = fetched;
|
|
254
|
+
await writeUpdateCache({
|
|
255
|
+
checkedAt: Date.now(),
|
|
256
|
+
latestVersion
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (compareVersions(version, latestVersion) < 0) {
|
|
260
|
+
if (await runInstall(latestVersion)) reExec(latestVersion);
|
|
244
261
|
}
|
|
245
|
-
const latestVersion = await fetchLatestVersion();
|
|
246
|
-
if (!latestVersion) return;
|
|
247
|
-
await writeUpdateCache({
|
|
248
|
-
checkedAt: Date.now(),
|
|
249
|
-
latestVersion
|
|
250
|
-
});
|
|
251
|
-
if (compareVersions(version, latestVersion) < 0) await runInstall(latestVersion);
|
|
252
262
|
} catch {}
|
|
253
263
|
}
|
|
254
264
|
async function runInstall(latestVersion) {
|
|
255
|
-
const
|
|
265
|
+
const installCmd = detectPackageManager() === "bun" ? `bun add -g agent-yes@${latestVersion}` : `npm install -g agent-yes@${latestVersion}`;
|
|
256
266
|
process.stderr.write(`\x1b[33m[agent-yes] Updating ${version} → ${latestVersion}…\x1b[0m\n`);
|
|
257
267
|
try {
|
|
258
|
-
await execaCommand(
|
|
259
|
-
await writeUpdateCache({
|
|
260
|
-
checkedAt: 0,
|
|
261
|
-
latestVersion
|
|
262
|
-
});
|
|
268
|
+
await execaCommand(installCmd, { stdio: "inherit" });
|
|
263
269
|
process.stderr.write(`\x1b[32m[agent-yes] Updated to ${latestVersion}\x1b[0m\n`);
|
|
270
|
+
return true;
|
|
264
271
|
} catch {
|
|
265
|
-
process.stderr.write(`\x1b[31m[agent-yes] Auto-update failed. Run: ${
|
|
272
|
+
process.stderr.write(`\x1b[31m[agent-yes] Auto-update failed. Run: ${installCmd}\x1b[0m\n`);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Re-exec the current process so the newly installed version runs.
|
|
278
|
+
* Sets AGENT_YES_UPDATED=<version> to prevent an infinite loop.
|
|
279
|
+
*/
|
|
280
|
+
function reExec(version) {
|
|
281
|
+
const [bin, ...args] = process.argv;
|
|
282
|
+
process.stderr.write(`\x1b[36m[agent-yes] Restarting with v${version}…\x1b[0m\n`);
|
|
283
|
+
try {
|
|
284
|
+
execFileSync(bin, args, {
|
|
285
|
+
stdio: "inherit",
|
|
286
|
+
env: {
|
|
287
|
+
...process.env,
|
|
288
|
+
AGENT_YES_UPDATED: version
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
process.exit(0);
|
|
292
|
+
} catch (err) {
|
|
293
|
+
process.exit(err.status ?? 1);
|
|
266
294
|
}
|
|
267
295
|
}
|
|
268
296
|
/**
|
|
@@ -478,15 +506,15 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
478
506
|
|
|
479
507
|
//#endregion
|
|
480
508
|
//#region ts/cli.ts
|
|
481
|
-
|
|
509
|
+
await checkAndAutoUpdate();
|
|
482
510
|
const config = parseCliArgs(process.argv);
|
|
483
511
|
if (config.tray) {
|
|
484
|
-
const { startTray } = await import("./tray-
|
|
512
|
+
const { startTray } = await import("./tray-CPpdxTV-.js");
|
|
485
513
|
await startTray();
|
|
486
514
|
await new Promise(() => {});
|
|
487
515
|
}
|
|
488
516
|
{
|
|
489
|
-
const { ensureTray } = await import("./tray-
|
|
517
|
+
const { ensureTray } = await import("./tray-CPpdxTV-.js");
|
|
490
518
|
ensureTray();
|
|
491
519
|
}
|
|
492
520
|
if (config.useRust) {
|
|
@@ -581,7 +609,6 @@ const { exitCode } = await cliYes({
|
|
|
581
609
|
...config,
|
|
582
610
|
autoYes: config.autoYes
|
|
583
611
|
});
|
|
584
|
-
await updateCheckPromise;
|
|
585
612
|
console.log("exiting process");
|
|
586
613
|
process.exit(exitCode ?? 1);
|
|
587
614
|
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-
|
|
1
|
+
import { a as AgentContext, i as config, l as removeControlCharacters, n as CLIS_CONFIG, r as agentYes } from "./SUPPORTED_CLIS-Bqw9gxey.js";
|
|
2
2
|
import "./logger-CX77vJDA.js";
|
|
3
3
|
|
|
4
4
|
export { AgentContext, CLIS_CONFIG, config, agentYes as default, removeControlCharacters };
|
|
@@ -6,7 +6,6 @@ import { existsSync } from "fs";
|
|
|
6
6
|
|
|
7
7
|
//#region ts/tray.ts
|
|
8
8
|
const POLL_INTERVAL = 2e3;
|
|
9
|
-
const IDLE_EXIT_POLLS = 15;
|
|
10
9
|
const getTrayDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
11
10
|
const getTrayPidFile = () => path.join(getTrayDir(), "tray.pid");
|
|
12
11
|
const ICON_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAjklEQVQ4T2NkoBAwUqifgWoGMDIyNjAyMv5nYGBYQMgVjMgCQM0LGBkZHYDYAY8BDUBxByB2wGcAyAUOQOwAxPYMDAyOeCzAbwBIMyMjowNQsz0ely8ACjng8wJeA0CaGRgY7IHYAZ8hQHEHfF7AawBYMwODPZABRHsBpwEgzUDN9kDsgM8lQHEHfC4gJhwAAM3hMBGq3cNNAAAAAElFTkSuQmCC";
|
|
@@ -153,17 +152,9 @@ async function startTray() {
|
|
|
153
152
|
if (action.item.title === "Quit Tray") cleanup();
|
|
154
153
|
});
|
|
155
154
|
let lastCount = count;
|
|
156
|
-
let idlePolls = count === 0 ? 1 : 0;
|
|
157
155
|
intervalId = setInterval(async () => {
|
|
158
156
|
try {
|
|
159
157
|
const { count: newCount, tasks: newTasks } = await getRunningAgentCount();
|
|
160
|
-
if (newCount === 0) {
|
|
161
|
-
idlePolls++;
|
|
162
|
-
if (idlePolls >= IDLE_EXIT_POLLS) {
|
|
163
|
-
cleanup();
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
} else idlePolls = 0;
|
|
167
158
|
if (newCount !== lastCount) {
|
|
168
159
|
lastCount = newCount;
|
|
169
160
|
systray.sendAction({
|
|
@@ -184,4 +175,4 @@ async function startTray() {
|
|
|
184
175
|
|
|
185
176
|
//#endregion
|
|
186
177
|
export { ensureTray, startTray };
|
|
187
|
-
//# sourceMappingURL=tray-
|
|
178
|
+
//# sourceMappingURL=tray-CPpdxTV-.js.map
|
package/package.json
CHANGED
package/ts/cli.ts
CHANGED
|
@@ -9,8 +9,9 @@ import { checkAndAutoUpdate, displayVersion } from "./versionChecker.ts";
|
|
|
9
9
|
import { getRustBinary } from "./rustBinary.ts";
|
|
10
10
|
import { buildRustArgs } from "./buildRustArgs.ts";
|
|
11
11
|
|
|
12
|
-
//
|
|
13
|
-
|
|
12
|
+
// Check for updates before starting — installs & re-execs if a newer version exists.
|
|
13
|
+
// Fast path: cached result (no network), so this adds near-zero latency most of the time.
|
|
14
|
+
await checkAndAutoUpdate();
|
|
14
15
|
|
|
15
16
|
// Parse CLI arguments
|
|
16
17
|
const config = parseCliArgs(process.argv);
|
|
@@ -152,8 +153,5 @@ if (config.verbose) {
|
|
|
152
153
|
const { default: cliYes } = await import("./index.ts");
|
|
153
154
|
const { exitCode } = await cliYes({ ...config, autoYes: config.autoYes });
|
|
154
155
|
|
|
155
|
-
// Apply update if one was found during the session
|
|
156
|
-
await updateCheckPromise;
|
|
157
|
-
|
|
158
156
|
console.log("exiting process");
|
|
159
157
|
process.exit(exitCode ?? 1);
|
package/ts/tray.spec.ts
CHANGED
|
@@ -215,62 +215,6 @@ describe("tray", () => {
|
|
|
215
215
|
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it("should auto-exit after ~30s with 0 agents", async () => {
|
|
219
|
-
const originalPlatform = process.platform;
|
|
220
|
-
Object.defineProperty(process, "platform", { value: "darwin" });
|
|
221
|
-
vi.useFakeTimers();
|
|
222
|
-
const mockExit = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
223
|
-
|
|
224
|
-
const { startTray } = await import("./tray.ts");
|
|
225
|
-
await startTray();
|
|
226
|
-
|
|
227
|
-
// Keep returning 0 agents for 15 polls (IDLE_EXIT_POLLS)
|
|
228
|
-
mockGetRunningAgentCount.mockResolvedValue({ count: 0, tasks: [] });
|
|
229
|
-
|
|
230
|
-
// Advance 15 * 2s = 30s
|
|
231
|
-
await vi.advanceTimersByTimeAsync(15 * 2100);
|
|
232
|
-
|
|
233
|
-
expect(mockSysTray.instance.kill).toHaveBeenCalledWith(false);
|
|
234
|
-
await vi.waitFor(() => expect(mockExit).toHaveBeenCalledWith(0));
|
|
235
|
-
|
|
236
|
-
mockExit.mockRestore();
|
|
237
|
-
vi.useRealTimers();
|
|
238
|
-
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("should reset idle counter when agents appear", async () => {
|
|
242
|
-
const originalPlatform = process.platform;
|
|
243
|
-
Object.defineProperty(process, "platform", { value: "darwin" });
|
|
244
|
-
vi.useFakeTimers();
|
|
245
|
-
const mockExit = vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
246
|
-
|
|
247
|
-
const { startTray } = await import("./tray.ts");
|
|
248
|
-
await startTray();
|
|
249
|
-
|
|
250
|
-
// 10 polls at 0 agents
|
|
251
|
-
mockGetRunningAgentCount.mockResolvedValue({ count: 0, tasks: [] });
|
|
252
|
-
await vi.advanceTimersByTimeAsync(10 * 2100);
|
|
253
|
-
|
|
254
|
-
// Then an agent appears — resets idle counter
|
|
255
|
-
mockGetRunningAgentCount.mockResolvedValue({
|
|
256
|
-
count: 1,
|
|
257
|
-
tasks: [
|
|
258
|
-
{ pid: 1, cwd: "/a", task: "t", status: "running" as const, startedAt: 0, lockedAt: 0 },
|
|
259
|
-
],
|
|
260
|
-
});
|
|
261
|
-
await vi.advanceTimersByTimeAsync(2100);
|
|
262
|
-
|
|
263
|
-
// Then 10 more polls at 0 — should NOT exit yet (need 15 consecutive)
|
|
264
|
-
mockGetRunningAgentCount.mockResolvedValue({ count: 0, tasks: [] });
|
|
265
|
-
await vi.advanceTimersByTimeAsync(10 * 2100);
|
|
266
|
-
|
|
267
|
-
expect(mockExit).not.toHaveBeenCalled();
|
|
268
|
-
|
|
269
|
-
mockExit.mockRestore();
|
|
270
|
-
vi.useRealTimers();
|
|
271
|
-
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
272
|
-
});
|
|
273
|
-
|
|
274
218
|
it("should work on Windows", async () => {
|
|
275
219
|
const originalPlatform = process.platform;
|
|
276
220
|
Object.defineProperty(process, "platform", { value: "win32" });
|
package/ts/tray.ts
CHANGED
|
@@ -5,7 +5,6 @@ import path from "path";
|
|
|
5
5
|
import { getRunningAgentCount, type Task } from "./runningLock.ts";
|
|
6
6
|
|
|
7
7
|
const POLL_INTERVAL = 2000;
|
|
8
|
-
const IDLE_EXIT_POLLS = 15; // Exit after 15 polls (~30s) with 0 agents
|
|
9
8
|
|
|
10
9
|
const getTrayDir = () => path.join(process.env.CLAUDE_YES_HOME || homedir(), ".claude-yes");
|
|
11
10
|
const getTrayPidFile = () => path.join(getTrayDir(), "tray.pid");
|
|
@@ -177,21 +176,10 @@ export async function startTray(): Promise<void> {
|
|
|
177
176
|
|
|
178
177
|
// Poll and update, auto-exit after ~30s idle (0 agents)
|
|
179
178
|
let lastCount = count;
|
|
180
|
-
let idlePolls = count === 0 ? 1 : 0;
|
|
181
179
|
intervalId = setInterval(async () => {
|
|
182
180
|
try {
|
|
183
181
|
const { count: newCount, tasks: newTasks } = await getRunningAgentCount();
|
|
184
182
|
|
|
185
|
-
if (newCount === 0) {
|
|
186
|
-
idlePolls++;
|
|
187
|
-
if (idlePolls >= IDLE_EXIT_POLLS) {
|
|
188
|
-
cleanup();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
idlePolls = 0;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
183
|
if (newCount !== lastCount) {
|
|
196
184
|
lastCount = newCount;
|
|
197
185
|
|
|
@@ -12,6 +12,14 @@ vi.mock("fs/promises", () => ({
|
|
|
12
12
|
readFile: vi.fn(),
|
|
13
13
|
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
14
14
|
}));
|
|
15
|
+
vi.mock("child_process", () => ({
|
|
16
|
+
execFileSync: vi.fn(() => {
|
|
17
|
+
// Simulate successful re-exec by throwing an exit-like error
|
|
18
|
+
const err = new Error("re-exec") as any;
|
|
19
|
+
err.status = 0;
|
|
20
|
+
throw err;
|
|
21
|
+
}),
|
|
22
|
+
}));
|
|
15
23
|
|
|
16
24
|
describe("versionChecker", () => {
|
|
17
25
|
describe("compareVersions", () => {
|
|
@@ -81,7 +89,10 @@ describe("versionChecker", () => {
|
|
|
81
89
|
vi.clearAllMocks();
|
|
82
90
|
vi.stubGlobal("fetch", vi.fn());
|
|
83
91
|
vi.spyOn(process.stderr, "write").mockImplementation(() => true);
|
|
92
|
+
// Use a mock for process.exit to prevent actual exit in tests
|
|
93
|
+
vi.spyOn(process, "exit").mockImplementation(() => undefined as never);
|
|
84
94
|
delete process.env.AGENT_YES_NO_UPDATE;
|
|
95
|
+
delete process.env.AGENT_YES_UPDATED;
|
|
85
96
|
delete process.env.BUN_INSTALL;
|
|
86
97
|
});
|
|
87
98
|
|
|
@@ -95,6 +106,13 @@ describe("versionChecker", () => {
|
|
|
95
106
|
expect(fetch).not.toHaveBeenCalled();
|
|
96
107
|
});
|
|
97
108
|
|
|
109
|
+
it("should skip when AGENT_YES_UPDATED matches current version", async () => {
|
|
110
|
+
const pkg = await import("../package.json");
|
|
111
|
+
process.env.AGENT_YES_UPDATED = pkg.default.version;
|
|
112
|
+
await checkAndAutoUpdate();
|
|
113
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
114
|
+
});
|
|
115
|
+
|
|
98
116
|
it("should use cached result within TTL and not install when up-to-date", async () => {
|
|
99
117
|
const { readFile } = await import("fs/promises");
|
|
100
118
|
vi.mocked(readFile).mockResolvedValueOnce(
|
|
@@ -105,19 +123,23 @@ describe("versionChecker", () => {
|
|
|
105
123
|
expect(process.stderr.write).not.toHaveBeenCalled();
|
|
106
124
|
});
|
|
107
125
|
|
|
108
|
-
it("should install from cache when cached version is newer and within TTL", async () => {
|
|
126
|
+
it("should install and re-exec from cache when cached version is newer and within TTL", async () => {
|
|
109
127
|
const { readFile } = await import("fs/promises");
|
|
110
128
|
const { execaCommand } = await import("execa");
|
|
129
|
+
const { execFileSync } = await import("child_process");
|
|
111
130
|
vi.mocked(readFile).mockResolvedValueOnce(
|
|
112
131
|
JSON.stringify({ checkedAt: Date.now(), latestVersion: "999.0.0" }) as any,
|
|
113
132
|
);
|
|
114
133
|
await checkAndAutoUpdate();
|
|
115
134
|
expect(execaCommand).toHaveBeenCalled();
|
|
135
|
+
expect(execFileSync).toHaveBeenCalled();
|
|
136
|
+
expect(process.exit).toHaveBeenCalled();
|
|
116
137
|
});
|
|
117
138
|
|
|
118
|
-
it("should fetch and write cache when stale, install if behind", async () => {
|
|
139
|
+
it("should fetch and write cache when stale, install and re-exec if behind", async () => {
|
|
119
140
|
const { readFile, writeFile } = await import("fs/promises");
|
|
120
141
|
const { execaCommand } = await import("execa");
|
|
142
|
+
const { execFileSync } = await import("child_process");
|
|
121
143
|
vi.mocked(readFile).mockRejectedValueOnce(new Error("no cache"));
|
|
122
144
|
vi.mocked(fetch).mockResolvedValue({
|
|
123
145
|
ok: true,
|
|
@@ -126,6 +148,7 @@ describe("versionChecker", () => {
|
|
|
126
148
|
await checkAndAutoUpdate();
|
|
127
149
|
expect(writeFile).toHaveBeenCalled();
|
|
128
150
|
expect(execaCommand).toHaveBeenCalled();
|
|
151
|
+
expect(execFileSync).toHaveBeenCalled();
|
|
129
152
|
});
|
|
130
153
|
|
|
131
154
|
it("should fetch and write cache but not install if up-to-date", async () => {
|
package/ts/versionChecker.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
1
2
|
import { execaCommand } from "execa";
|
|
2
3
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3
4
|
import { homedir } from "os";
|
|
@@ -25,59 +26,92 @@ async function writeUpdateCache(data: UpdateCache): Promise<void> {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
function detectPackageManager(): string {
|
|
28
|
-
if (
|
|
29
|
+
if (
|
|
30
|
+
process.env.BUN_INSTALL ||
|
|
31
|
+
process.execPath?.includes("bun") ||
|
|
32
|
+
process.env.npm_execpath?.includes("bun")
|
|
33
|
+
)
|
|
34
|
+
return "bun";
|
|
29
35
|
return "npm";
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
|
-
* Check for updates
|
|
39
|
+
* Check for updates, auto-install if newer version is available, and re-exec
|
|
40
|
+
* so the current invocation always runs the latest code.
|
|
41
|
+
*
|
|
34
42
|
* Uses a 1-hour TTL cache to avoid hitting the registry on every run.
|
|
35
43
|
* All errors are swallowed — network issues must never break the tool.
|
|
36
44
|
* Set AGENT_YES_NO_UPDATE=1 to opt out.
|
|
45
|
+
*
|
|
46
|
+
* The AGENT_YES_UPDATED env var prevents infinite re-exec loops:
|
|
47
|
+
* after updating we re-exec with AGENT_YES_UPDATED=<version> so the
|
|
48
|
+
* new process skips the update check.
|
|
37
49
|
*/
|
|
38
50
|
export async function checkAndAutoUpdate(): Promise<void> {
|
|
39
51
|
if (process.env.AGENT_YES_NO_UPDATE) return;
|
|
40
52
|
|
|
53
|
+
// Prevent infinite re-exec: if we just updated to this version, skip
|
|
54
|
+
if (process.env.AGENT_YES_UPDATED === pkg.version) return;
|
|
55
|
+
|
|
41
56
|
try {
|
|
57
|
+
let latestVersion: string | undefined;
|
|
58
|
+
|
|
42
59
|
// Check cache TTL
|
|
43
60
|
const cache = await readUpdateCache();
|
|
44
61
|
if (cache && Date.now() - cache.checkedAt < TTL_MS) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return;
|
|
62
|
+
latestVersion = cache.latestVersion;
|
|
63
|
+
} else {
|
|
64
|
+
// Fetch latest from registry
|
|
65
|
+
const fetched = await fetchLatestVersion();
|
|
66
|
+
if (!fetched) return;
|
|
67
|
+
latestVersion = fetched;
|
|
68
|
+
await writeUpdateCache({ checkedAt: Date.now(), latestVersion });
|
|
50
69
|
}
|
|
51
70
|
|
|
52
|
-
// Fetch latest from registry
|
|
53
|
-
const latestVersion = await fetchLatestVersion();
|
|
54
|
-
if (!latestVersion) return;
|
|
55
|
-
|
|
56
|
-
await writeUpdateCache({ checkedAt: Date.now(), latestVersion });
|
|
57
|
-
|
|
58
71
|
if (compareVersions(pkg.version, latestVersion) < 0) {
|
|
59
|
-
await runInstall(latestVersion);
|
|
72
|
+
const installed = await runInstall(latestVersion);
|
|
73
|
+
if (installed) {
|
|
74
|
+
reExec(latestVersion);
|
|
75
|
+
}
|
|
60
76
|
}
|
|
61
77
|
} catch {
|
|
62
78
|
// Silently ignore all errors
|
|
63
79
|
}
|
|
64
80
|
}
|
|
65
81
|
|
|
66
|
-
async function runInstall(latestVersion: string): Promise<
|
|
82
|
+
async function runInstall(latestVersion: string): Promise<boolean> {
|
|
67
83
|
const pm = detectPackageManager();
|
|
68
|
-
const
|
|
84
|
+
const installCmd =
|
|
69
85
|
pm === "bun"
|
|
70
86
|
? `bun add -g agent-yes@${latestVersion}`
|
|
71
87
|
: `npm install -g agent-yes@${latestVersion}`;
|
|
72
88
|
|
|
73
89
|
process.stderr.write(`\x1b[33m[agent-yes] Updating ${pkg.version} → ${latestVersion}…\x1b[0m\n`);
|
|
74
90
|
try {
|
|
75
|
-
await execaCommand(
|
|
76
|
-
// Clear cache so next run re-checks
|
|
77
|
-
await writeUpdateCache({ checkedAt: 0, latestVersion });
|
|
91
|
+
await execaCommand(installCmd, { stdio: "inherit" });
|
|
78
92
|
process.stderr.write(`\x1b[32m[agent-yes] Updated to ${latestVersion}\x1b[0m\n`);
|
|
93
|
+
return true;
|
|
79
94
|
} catch {
|
|
80
|
-
process.stderr.write(`\x1b[31m[agent-yes] Auto-update failed. Run: ${
|
|
95
|
+
process.stderr.write(`\x1b[31m[agent-yes] Auto-update failed. Run: ${installCmd}\x1b[0m\n`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Re-exec the current process so the newly installed version runs.
|
|
102
|
+
* Sets AGENT_YES_UPDATED=<version> to prevent an infinite loop.
|
|
103
|
+
*/
|
|
104
|
+
function reExec(version: string): never {
|
|
105
|
+
const [bin, ...args] = process.argv;
|
|
106
|
+
process.stderr.write(`\x1b[36m[agent-yes] Restarting with v${version}…\x1b[0m\n`);
|
|
107
|
+
try {
|
|
108
|
+
execFileSync(bin, args, {
|
|
109
|
+
stdio: "inherit",
|
|
110
|
+
env: { ...process.env, AGENT_YES_UPDATED: version },
|
|
111
|
+
});
|
|
112
|
+
process.exit(0);
|
|
113
|
+
} catch (err: any) {
|
|
114
|
+
process.exit(err.status ?? 1);
|
|
81
115
|
}
|
|
82
116
|
}
|
|
83
117
|
|