@bman654/clodex 1.3.0 → 2.0.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": "@bman654/clodex",
3
- "version": "1.3.0",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,544 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/server-runtime.ts
4
- import {
5
- closeSync,
6
- mkdirSync as mkdirSync2,
7
- openSync,
8
- readFileSync,
9
- renameSync,
10
- rmSync,
11
- unlinkSync,
12
- writeFileSync
13
- } from "fs";
14
- import { dirname, join as join2 } from "path";
15
-
16
- // src/paths.ts
17
- import { homedir } from "os";
18
- import { join } from "path";
19
- import { cpSync, existsSync, mkdirSync, readdirSync } from "fs";
20
- var APP_DIR_NAME = "clodex";
21
- var LEGACY_APP_DIR_NAME = "relay-ai";
22
- function userHome(env = process.env) {
23
- return env.HOME ?? env.USERPROFILE ?? homedir();
24
- }
25
- function resolveAppHomeOverride(env = process.env) {
26
- const override = env.CLODEX_HOME;
27
- return override?.trim() || void 0;
28
- }
29
- function getAppHome(env = process.env) {
30
- const override = resolveAppHomeOverride(env);
31
- if (override) return override;
32
- return join(userHome(env), `.${APP_DIR_NAME}`);
33
- }
34
- function getLegacyAppHome(env = process.env) {
35
- return join(userHome(env), `.${LEGACY_APP_DIR_NAME}`);
36
- }
37
- var legacyMigrationDone = false;
38
- function ensureLegacyAppHomeMigrated(env = process.env) {
39
- if (legacyMigrationDone) return;
40
- legacyMigrationDone = true;
41
- try {
42
- const appHome = getAppHome(env);
43
- if (existsSync(appHome)) return;
44
- const legacyHome = getLegacyAppHome(env);
45
- if (!existsSync(legacyHome)) return;
46
- mkdirSync(appHome, { recursive: true, mode: 448 });
47
- for (const entry of readdirSync(legacyHome)) {
48
- if (entry === "logs") continue;
49
- cpSync(join(legacyHome, entry), join(appHome, entry), { recursive: true });
50
- }
51
- } catch {
52
- }
53
- }
54
- function getConfigPath(env = process.env) {
55
- return join(getAppHome(env), "config.json");
56
- }
57
- function getProvidersPath(env = process.env) {
58
- return join(getAppHome(env), "providers.json");
59
- }
60
- function getCredentialCleanupPath(env = process.env) {
61
- return join(getAppHome(env), "credential-cleanup.json");
62
- }
63
- function getLogsPath(env = process.env) {
64
- return join(getAppHome(env), "logs");
65
- }
66
-
67
- // src/server-runtime.ts
68
- function getServerRuntimePath(env = process.env) {
69
- return join2(getAppHome(env), "server-runtime.json");
70
- }
71
- function getServerRuntimeLockPath(env = process.env) {
72
- return join2(getAppHome(env), "server-runtime.lock");
73
- }
74
- function isDiscoveryDisabled(flag, env = process.env) {
75
- if (flag !== void 0) return flag;
76
- const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();
77
- return raw === "1" || raw === "true";
78
- }
79
- function isPort(value) {
80
- return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
81
- }
82
- function parseServerRuntimeRecord(value) {
83
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
84
- const record = value;
85
- const mode = record["mode"];
86
- if (mode !== "endpoint" && mode !== "proxy") return null;
87
- if (!isPort(record["port"])) return null;
88
- const pid = record["pid"];
89
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
90
- const startedAt = typeof record["startedAt"] === "string" ? record["startedAt"] : "";
91
- const caPath = record["caPath"];
92
- if (mode === "proxy") {
93
- if (typeof caPath !== "string" || !caPath.trim()) return null;
94
- return { mode, port: record["port"], pid, caPath, startedAt };
95
- }
96
- return { mode, port: record["port"], pid, startedAt };
97
- }
98
- function parseServerRuntimeStates(raw) {
99
- let parsed;
100
- try {
101
- parsed = JSON.parse(raw);
102
- } catch {
103
- return [];
104
- }
105
- const items = Array.isArray(parsed) ? parsed : [parsed];
106
- const states = [];
107
- for (const item of items) {
108
- const state = parseServerRuntimeRecord(item);
109
- if (state) states.push(state);
110
- }
111
- return states;
112
- }
113
- function isPidAlive(pid, kill = process.kill.bind(process)) {
114
- try {
115
- kill(pid, 0);
116
- return true;
117
- } catch (err) {
118
- return err?.code === "EPERM";
119
- }
120
- }
121
- var RUNTIME_LOCK_STALE_MS = 1e4;
122
- var RUNTIME_LOCK_WAIT_MS = 500;
123
- var RUNTIME_LOCK_RETRY_MS = 25;
124
- function tryAcquireRuntimeLock(lockPath, opts = {}) {
125
- const now = opts.now ?? Date.now();
126
- const alive = opts.isAlive ?? isPidAlive;
127
- mkdirSync2(dirname(lockPath), { recursive: true, mode: 448 });
128
- for (let attempt = 0; attempt < 2; attempt++) {
129
- try {
130
- const fd = openSync(lockPath, "wx");
131
- const content = { pid: process.pid, startedAt: now };
132
- writeFileSync(fd, JSON.stringify(content));
133
- closeSync(fd);
134
- return () => {
135
- try {
136
- unlinkSync(lockPath);
137
- } catch {
138
- }
139
- };
140
- } catch {
141
- let stale = false;
142
- try {
143
- const existing = JSON.parse(readFileSync(lockPath, "utf8"));
144
- stale = !existing.pid || !alive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > RUNTIME_LOCK_STALE_MS;
145
- } catch {
146
- stale = true;
147
- }
148
- if (!stale) return null;
149
- try {
150
- unlinkSync(lockPath);
151
- } catch {
152
- }
153
- }
154
- }
155
- return null;
156
- }
157
- function sleepSync(ms) {
158
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
159
- }
160
- function withRuntimeWriteLock(env, mutate) {
161
- const lockPath = getServerRuntimeLockPath(env);
162
- let release = null;
163
- const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;
164
- for (; ; ) {
165
- release = tryAcquireRuntimeLock(lockPath);
166
- if (release || Date.now() >= deadline) break;
167
- sleepSync(RUNTIME_LOCK_RETRY_MS);
168
- }
169
- try {
170
- mutate();
171
- } finally {
172
- release?.();
173
- }
174
- }
175
- function readAllRecords(env) {
176
- let raw;
177
- try {
178
- raw = readFileSync(getServerRuntimePath(env), "utf8");
179
- } catch {
180
- return [];
181
- }
182
- return parseServerRuntimeStates(raw);
183
- }
184
- function atomicWriteRecords(path, records) {
185
- mkdirSync2(dirname(path), { recursive: true, mode: 448 });
186
- const tmpPath = `${path}.${process.pid}.tmp`;
187
- writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}
188
- `, { encoding: "utf8", mode: 384 });
189
- renameSync(tmpPath, path);
190
- }
191
- function registerServerRuntimeState(state, env = process.env, options = {}) {
192
- const alive = options.isAlive ?? isPidAlive;
193
- try {
194
- withRuntimeWriteLock(env, () => {
195
- const records = readAllRecords(env).filter(
196
- (record) => record.pid !== state.pid && alive(record.pid)
197
- );
198
- records.push(state);
199
- atomicWriteRecords(getServerRuntimePath(env), records);
200
- });
201
- } catch {
202
- }
203
- }
204
- function unregisterServerRuntimeState(pid = process.pid, env = process.env, options = {}) {
205
- const alive = options.isAlive ?? isPidAlive;
206
- try {
207
- withRuntimeWriteLock(env, () => {
208
- const records = readAllRecords(env).filter(
209
- (record) => record.pid !== pid && alive(record.pid)
210
- );
211
- if (records.length === 0) {
212
- rmSync(getServerRuntimePath(env), { force: true });
213
- } else {
214
- atomicWriteRecords(getServerRuntimePath(env), records);
215
- }
216
- });
217
- } catch {
218
- }
219
- }
220
- function readLiveServerRuntimeStates(env = process.env, options = {}) {
221
- const alive = options.isAlive ?? isPidAlive;
222
- return readAllRecords(env).filter((state) => alive(state.pid));
223
- }
224
- function orderWrapperServerCandidates(records) {
225
- return [...records].sort((a, b) => {
226
- if (a.mode !== b.mode) return a.mode === "proxy" ? -1 : 1;
227
- return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);
228
- });
229
- }
230
-
231
- // src/config.ts
232
- import { dirname as dirname2 } from "path";
233
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
234
- function readJsonFile(path) {
235
- try {
236
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
237
- return parsed && typeof parsed === "object" ? parsed : null;
238
- } catch {
239
- return null;
240
- }
241
- }
242
- function readConfig() {
243
- ensureLegacyAppHomeMigrated();
244
- return readJsonFile(getConfigPath()) ?? {};
245
- }
246
- function writeConfig(config) {
247
- const configPath = getConfigPath();
248
- mkdirSync3(dirname2(configPath), { recursive: true, mode: 448 });
249
- writeFileSync2(configPath, `${JSON.stringify(config, null, 2)}
250
- `, { encoding: "utf8", mode: 384 });
251
- }
252
- function loadPreferences() {
253
- const config = readConfig();
254
- return {
255
- lastModel: config.lastModel,
256
- lastProvider: config.lastProvider,
257
- recentModelsByProvider: config.recentModelsByProvider,
258
- favoriteModels: config.favoriteModels,
259
- modelAliases: config.modelAliases,
260
- claudeBridgeMode: config.claudeBridgeMode,
261
- serverBridgeMode: config.serverBridgeMode,
262
- appPathOverrides: config.appPathOverrides,
263
- recentLaunchFolders: config.recentLaunchFolders,
264
- server: config.server
265
- };
266
- }
267
- function savePreferences(prefs) {
268
- const config = readConfig();
269
- if (prefs.lastModel !== void 0) config.lastModel = prefs.lastModel;
270
- if (prefs.lastProvider !== void 0) config.lastProvider = prefs.lastProvider;
271
- if (prefs.recentModelsByProvider !== void 0) config.recentModelsByProvider = prefs.recentModelsByProvider;
272
- if (prefs.favoriteModels !== void 0) config.favoriteModels = prefs.favoriteModels;
273
- if (prefs.modelAliases !== void 0) config.modelAliases = prefs.modelAliases;
274
- if (prefs.claudeBridgeMode !== void 0) config.claudeBridgeMode = prefs.claudeBridgeMode;
275
- if (prefs.serverBridgeMode !== void 0) config.serverBridgeMode = prefs.serverBridgeMode;
276
- if (prefs.appPathOverrides !== void 0) config.appPathOverrides = prefs.appPathOverrides;
277
- if (prefs.recentLaunchFolders !== void 0) config.recentLaunchFolders = prefs.recentLaunchFolders;
278
- writeConfig(config);
279
- }
280
- function getAppPathOverride(appId) {
281
- const value = loadPreferences().appPathOverrides?.[appId];
282
- return typeof value === "string" && value.trim() ? value : void 0;
283
- }
284
- function resolveBridgeMode(command, explicit, opts = {}) {
285
- const key = command === "claude" ? "claudeBridgeMode" : "serverBridgeMode";
286
- if (explicit) {
287
- if (opts.persist === true) savePreferences({ [key]: explicit });
288
- return explicit;
289
- }
290
- return loadPreferences()[key] ?? "proxy";
291
- }
292
- var MAX_RECENT_MODELS = 3;
293
- function recordLaunchSelection(_agent, providerId, modelId, prefs) {
294
- const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];
295
- const updatedRecent = [modelId, ...prevRecent.filter((id) => id !== modelId)].slice(0, MAX_RECENT_MODELS);
296
- savePreferences({
297
- lastProvider: providerId,
298
- lastModel: modelId,
299
- recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent }
300
- });
301
- }
302
- var SERVER_PASSWORD_SERVICE = "clodex-server-password";
303
- var SERVER_PASSWORD_ACCOUNT = "server-password";
304
- async function getServerPasswordKeyring() {
305
- try {
306
- const { Entry } = await import("@napi-rs/keyring");
307
- return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);
308
- } catch {
309
- return null;
310
- }
311
- }
312
- async function getSavedServerPassword() {
313
- const config = readConfig();
314
- if (config.server?.savedPassword) {
315
- const pwd = config.server.savedPassword;
316
- const keyring2 = await getServerPasswordKeyring();
317
- if (keyring2) {
318
- try {
319
- await keyring2.setPassword(pwd);
320
- delete config.server.savedPassword;
321
- if (Object.keys(config.server).length === 0) delete config.server;
322
- writeConfig(config);
323
- } catch {
324
- }
325
- }
326
- return pwd;
327
- }
328
- const keyring = await getServerPasswordKeyring();
329
- if (keyring) {
330
- try {
331
- return await keyring.getPassword();
332
- } catch {
333
- return null;
334
- }
335
- }
336
- return null;
337
- }
338
- async function setSavedServerPassword(password) {
339
- const keyring = await getServerPasswordKeyring();
340
- if (keyring) {
341
- try {
342
- await keyring.setPassword(password);
343
- return;
344
- } catch {
345
- }
346
- }
347
- const config = readConfig();
348
- config.server = {
349
- ...config.server ?? {},
350
- savedPassword: password
351
- };
352
- writeConfig(config);
353
- }
354
- function getServerExposedProviders() {
355
- const list = readConfig().server?.exposedProviders;
356
- return list && list.length > 0 ? list : null;
357
- }
358
- function setServerExposedProviders(providerIds) {
359
- const config = readConfig();
360
- config.server = {
361
- ...config.server ?? {},
362
- exposedProviders: providerIds
363
- };
364
- writeConfig(config);
365
- }
366
- function getServerMaskGatewayIds() {
367
- return readConfig().server?.maskGatewayIds ?? true;
368
- }
369
- function setServerMaskGatewayIds(mask) {
370
- const config = readConfig();
371
- config.server = {
372
- ...config.server ?? {},
373
- maskGatewayIds: mask
374
- };
375
- writeConfig(config);
376
- }
377
- function getServerFavoritesOnly() {
378
- return readConfig().server?.favoritesOnly ?? false;
379
- }
380
- function setServerFavoritesOnly(favoritesOnly) {
381
- const config = readConfig();
382
- config.server = {
383
- ...config.server ?? {},
384
- favoritesOnly
385
- };
386
- writeConfig(config);
387
- }
388
- function getServerListenMode() {
389
- return readConfig().server?.listenMode === "network" ? "network" : "local";
390
- }
391
- function setServerListenMode(listenMode) {
392
- const config = readConfig();
393
- config.server = {
394
- ...config.server ?? {},
395
- listenMode
396
- };
397
- writeConfig(config);
398
- }
399
-
400
- // src/launch.ts
401
- import { execSync, spawn } from "child_process";
402
- import { existsSync as existsSync3, appendFileSync } from "fs";
403
- import { homedir as homedir2 } from "os";
404
- import { join as join3 } from "path";
405
-
406
- // src/binary-lookup.ts
407
- import { execFileSync } from "child_process";
408
- import { existsSync as existsSync2 } from "fs";
409
- function findBinaryOnPath(name, fallbackPaths, options = {}) {
410
- const isWindows2 = options.isWindows ?? process.platform === "win32";
411
- const exists = options.exists ?? existsSync2;
412
- const runWhich = options.runWhich ?? ((binary, win) => execFileSync(win ? "where.exe" : "which", [binary], {
413
- encoding: "utf8",
414
- stdio: ["pipe", "pipe", "pipe"]
415
- }));
416
- try {
417
- const lines = runWhich(name, isWindows2).trim().split("\n").map((line) => line.trim()).filter(Boolean);
418
- const path = (isWindows2 ? lines.find((line) => line.toLowerCase().endsWith(".cmd")) : null) ?? lines[0];
419
- if (path && (!options.verifyWhichResult || exists(path))) return path;
420
- } catch {
421
- }
422
- for (const path of fallbackPaths) {
423
- if (exists(path)) return path;
424
- }
425
- return null;
426
- }
427
-
428
- // src/launch.ts
429
- var isWindows = process.platform === "win32";
430
- var FALLBACK_PATHS = isWindows ? [
431
- join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude.cmd"),
432
- join3(process.env["APPDATA"] ?? homedir2(), "npm", "claude"),
433
- join3(homedir2(), "AppData", "Roaming", "npm", "claude.cmd")
434
- ] : [
435
- join3(homedir2(), ".local", "bin", "claude"),
436
- join3(homedir2(), ".npm", "bin", "claude"),
437
- "/usr/local/bin/claude",
438
- "/opt/homebrew/bin/claude"
439
- ];
440
- function findClaudeBinary() {
441
- const environmentOverride = process.env["CLODEX_CLAUDE_PATH"];
442
- if (environmentOverride?.trim()) {
443
- return existsSync3(environmentOverride) ? environmentOverride : null;
444
- }
445
- const override = getAppPathOverride("claude");
446
- if (override) return existsSync3(override) ? override : null;
447
- return findBinaryOnPath("claude", FALLBACK_PATHS);
448
- }
449
- function getInstalledClaudeVersion() {
450
- try {
451
- const claudePath = findClaudeBinary();
452
- if (!claudePath) return "2.1.183";
453
- const result = execSync(`${isWindows ? `"${claudePath}"` : claudePath} --version`, {
454
- encoding: "utf8",
455
- stdio: ["pipe", "pipe", "pipe"]
456
- });
457
- const match = result.match(/(\d+\.\d+\.\d+)/);
458
- if (match) return match[1];
459
- } catch {
460
- }
461
- return "2.1.183";
462
- }
463
- function buildClaudeArgs(model, extraArgs) {
464
- return model ? ["--model", model, ...extraArgs] : [...extraArgs];
465
- }
466
- function launchClaude(env, model, extraArgs) {
467
- return new Promise((resolve) => {
468
- const claudePath = findClaudeBinary();
469
- const args = buildClaudeArgs(model, extraArgs);
470
- const debugFileIdx = extraArgs.indexOf("--debug-file");
471
- const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : void 0;
472
- const originalStdoutWrite = process.stdout.write;
473
- const originalStderrWrite = process.stderr.write;
474
- const muteWrite = (chunk, encoding, callback) => {
475
- if (typeof encoding === "function") {
476
- callback = encoding;
477
- }
478
- if (debugLogPath) {
479
- try {
480
- const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
481
- appendFileSync(debugLogPath, `[parent] ${str}`);
482
- } catch {
483
- }
484
- }
485
- if (callback) callback();
486
- return true;
487
- };
488
- process.stdout.write = muteWrite;
489
- process.stderr.write = muteWrite;
490
- const restore = () => {
491
- process.stdout.write = originalStdoutWrite;
492
- process.stderr.write = originalStderrWrite;
493
- };
494
- const child = spawn(claudePath, args, {
495
- stdio: "inherit",
496
- env,
497
- shell: isWindows
498
- });
499
- const forward = (signal) => {
500
- child.kill(signal);
501
- };
502
- process.once("SIGINT", () => forward("SIGINT"));
503
- process.once("SIGTERM", () => forward("SIGTERM"));
504
- child.on("exit", (code) => {
505
- restore();
506
- resolve(code ?? 0);
507
- });
508
- child.on("error", (err) => {
509
- restore();
510
- resolve(1);
511
- });
512
- });
513
- }
514
-
515
- export {
516
- getAppHome,
517
- ensureLegacyAppHomeMigrated,
518
- getProvidersPath,
519
- getCredentialCleanupPath,
520
- getLogsPath,
521
- isDiscoveryDisabled,
522
- registerServerRuntimeState,
523
- unregisterServerRuntimeState,
524
- readLiveServerRuntimeStates,
525
- orderWrapperServerCandidates,
526
- loadPreferences,
527
- savePreferences,
528
- resolveBridgeMode,
529
- recordLaunchSelection,
530
- getSavedServerPassword,
531
- setSavedServerPassword,
532
- getServerExposedProviders,
533
- setServerExposedProviders,
534
- getServerMaskGatewayIds,
535
- setServerMaskGatewayIds,
536
- getServerFavoritesOnly,
537
- setServerFavoritesOnly,
538
- getServerListenMode,
539
- setServerListenMode,
540
- findClaudeBinary,
541
- getInstalledClaudeVersion,
542
- launchClaude
543
- };
544
- //# sourceMappingURL=chunk-ZN5X7YFE.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server-runtime.ts","../src/paths.ts","../src/config.ts","../src/launch.ts","../src/binary-lookup.ts"],"sourcesContent":["// src/server-runtime.ts\n//\n// Runtime-state advertisement for the standalone `clodex server` command.\n// Each registering server ADDS its own record (keyed by pid) to\n// ~/.clodex/server-runtime.json on startup and removes ONLY its own record on\n// graceful shutdown, so other processes (notably the `clodex-claude` wrapper\n// bin) can discover every running server's mode, port, and CA path without any\n// hardcoding. The file holds an ARRAY of records; the legacy single-object\n// shape (pre multi-server) is tolerated on read as a one-element list. Stale\n// detection is the READER's job: a crashed server leaves its record behind, so\n// readers must validate pid liveness before trusting it. Writers additionally\n// prune dead-pid records while they hold the write lock.\n//\n// Concurrency: read-modify-write cycles are serialized by a short-lived pid\n// lock (~/.clodex/server-runtime.lock — same pattern as the patcher's\n// patch.lock: O_EXCL create, pid + staleness, ESRCH liveness) and the file is\n// replaced via write-temp-then-rename so a reader never sees a torn write. A\n// crashed lock holder cannot deadlock registration: the lock goes stale after\n// 10 seconds or when its pid dies, and after a brief bounded wait a writer\n// proceeds lockless (best-effort — same exposure as the old single-slot write).\n//\n// NOTE: only the standalone `clodex server` command writes this file. The\n// per-session MITM proxy spawned by `clodex claude --proxy` is private to that\n// session and must NOT advertise itself here. `clodex server --no-discovery`\n// (or CLODEX_NO_DISCOVERY=1) also opts a server out of registration entirely.\n\nimport {\n closeSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getAppHome } from './paths.js';\n\nexport interface ServerRuntimeState {\n mode: 'endpoint' | 'proxy';\n port: number;\n pid: number;\n /** Proxy mode only: absolute path to the CA bundle a client must trust. */\n caPath?: string;\n startedAt: string;\n}\n\ninterface HomeEnv {\n HOME?: string;\n CLODEX_HOME?: string;\n USERPROFILE?: string;\n}\n\nexport function getServerRuntimePath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'server-runtime.json');\n}\n\nexport function getServerRuntimeLockPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'server-runtime.lock');\n}\n\n/** `--no-discovery` flag, with CLODEX_NO_DISCOVERY=1 as the env fallback. */\nexport function isDiscoveryDisabled(\n flag: boolean | undefined,\n env: { CLODEX_NO_DISCOVERY?: string } = process.env,\n): boolean {\n if (flag !== undefined) return flag;\n const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();\n return raw === '1' || raw === 'true';\n}\n\nfunction isPort(value: unknown): value is number {\n return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 65535;\n}\n\n/** Validate one runtime record. Returns null for anything malformed. */\nexport function parseServerRuntimeRecord(value: unknown): ServerRuntimeState | null {\n if (!value || typeof value !== 'object' || Array.isArray(value)) return null;\n const record = value as Record<string, unknown>;\n\n const mode = record['mode'];\n if (mode !== 'endpoint' && mode !== 'proxy') return null;\n if (!isPort(record['port'])) return null;\n const pid = record['pid'];\n if (typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0) return null;\n const startedAt = typeof record['startedAt'] === 'string' ? record['startedAt'] : '';\n\n const caPath = record['caPath'];\n if (mode === 'proxy') {\n // A proxy-mode server without a CA path is unusable to clients — treat as invalid.\n if (typeof caPath !== 'string' || !caPath.trim()) return null;\n return { mode, port: record['port'], pid, caPath, startedAt };\n }\n return { mode, port: record['port'], pid, startedAt };\n}\n\n/**\n * Parse a raw server-runtime.json payload into a list of records. Tolerates\n * BOTH shapes: the current array of records and the legacy single object\n * (wrapped as a one-element list). Malformed input or records are skipped —\n * never throws.\n */\nexport function parseServerRuntimeStates(raw: string): ServerRuntimeState[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return [];\n }\n const items = Array.isArray(parsed) ? parsed : [parsed];\n const states: ServerRuntimeState[] = [];\n for (const item of items) {\n const state = parseServerRuntimeRecord(item);\n if (state) states.push(state);\n }\n return states;\n}\n\n/** kill(pid, 0) liveness probe: EPERM still means the process exists. */\nexport function isPidAlive(\n pid: number,\n kill: (pid: number, signal: number) => unknown = process.kill.bind(process),\n): boolean {\n try {\n kill(pid, 0);\n return true;\n } catch (err) {\n return (err as NodeJS.ErrnoException)?.code === 'EPERM';\n }\n}\n\n// ── Write lock (pid + staleness, patcher pattern) ───────────────────────────\n\nconst RUNTIME_LOCK_STALE_MS = 10_000;\nconst RUNTIME_LOCK_WAIT_MS = 500;\nconst RUNTIME_LOCK_RETRY_MS = 25;\n\ninterface RuntimeLockContent {\n pid: number;\n startedAt: number;\n}\n\nfunction tryAcquireRuntimeLock(\n lockPath: string,\n opts: { now?: number; isAlive?: (pid: number) => boolean } = {},\n): (() => void) | null {\n const now = opts.now ?? Date.now();\n const alive = opts.isAlive ?? isPidAlive;\n mkdirSync(dirname(lockPath), { recursive: true, mode: 0o700 });\n\n for (let attempt = 0; attempt < 2; attempt++) {\n try {\n const fd = openSync(lockPath, 'wx');\n const content: RuntimeLockContent = { pid: process.pid, startedAt: now };\n writeFileSync(fd, JSON.stringify(content));\n closeSync(fd);\n return () => {\n try {\n unlinkSync(lockPath);\n } catch {\n // already gone\n }\n };\n } catch {\n // Lock exists — check staleness.\n let stale = false;\n try {\n const existing = JSON.parse(readFileSync(lockPath, 'utf8')) as RuntimeLockContent;\n stale = !existing.pid\n || !alive(existing.pid)\n || (typeof existing.startedAt === 'number' && now - existing.startedAt > RUNTIME_LOCK_STALE_MS);\n } catch {\n stale = true; // unreadable lock file → stale\n }\n if (!stale) return null;\n try {\n unlinkSync(lockPath);\n } catch {\n // raced with the owner's cleanup — retry loop handles it\n }\n }\n }\n return null;\n}\n\nfunction sleepSync(ms: number): void {\n Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);\n}\n\n/**\n * Run a read-modify-write mutation under the runtime lock. The lock is only\n * ever held for a few milliseconds, so after a short bounded wait the mutation\n * proceeds WITHOUT the lock rather than dropping a registration — the atomic\n * rename still prevents torn files; the worst case is a lost concurrent\n * update, which is no worse than the old single-slot behavior.\n */\nfunction withRuntimeWriteLock(env: HomeEnv, mutate: () => void): void {\n const lockPath = getServerRuntimeLockPath(env);\n let release: (() => void) | null = null;\n const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;\n for (;;) {\n release = tryAcquireRuntimeLock(lockPath);\n if (release || Date.now() >= deadline) break;\n sleepSync(RUNTIME_LOCK_RETRY_MS);\n }\n try {\n mutate();\n } finally {\n release?.();\n }\n}\n\nfunction readAllRecords(env: HomeEnv): ServerRuntimeState[] {\n let raw: string;\n try {\n raw = readFileSync(getServerRuntimePath(env), 'utf8');\n } catch {\n return [];\n }\n return parseServerRuntimeStates(raw);\n}\n\n/** Atomic replace: write a temp file in the same directory, then rename over. */\nfunction atomicWriteRecords(path: string, records: ServerRuntimeState[]): void {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n const tmpPath = `${path}.${process.pid}.tmp`;\n writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n renameSync(tmpPath, path);\n}\n\nexport interface RuntimeMutateOptions {\n isAlive?: (pid: number) => boolean;\n}\n\n/**\n * Add or update this server's own record (keyed by pid), pruning records whose\n * pids are dead. Best-effort — a state-file failure must never take the server\n * down.\n */\nexport function registerServerRuntimeState(\n state: ServerRuntimeState,\n env: HomeEnv = process.env,\n options: RuntimeMutateOptions = {},\n): void {\n const alive = options.isAlive ?? isPidAlive;\n try {\n withRuntimeWriteLock(env, () => {\n const records = readAllRecords(env).filter(\n record => record.pid !== state.pid && alive(record.pid),\n );\n records.push(state);\n atomicWriteRecords(getServerRuntimePath(env), records);\n });\n } catch {\n // Discovery is optional; the server itself keeps running.\n }\n}\n\n/**\n * Remove ONLY this server's own record (by pid) on graceful shutdown, pruning\n * dead-pid records along the way. Missing file/record is fine. When no live\n * records remain the file is removed entirely.\n */\nexport function unregisterServerRuntimeState(\n pid: number = process.pid,\n env: HomeEnv = process.env,\n options: RuntimeMutateOptions = {},\n): void {\n const alive = options.isAlive ?? isPidAlive;\n try {\n withRuntimeWriteLock(env, () => {\n const records = readAllRecords(env).filter(\n record => record.pid !== pid && alive(record.pid),\n );\n if (records.length === 0) {\n rmSync(getServerRuntimePath(env), { force: true });\n } else {\n atomicWriteRecords(getServerRuntimePath(env), records);\n }\n });\n } catch {\n // Stale records are handled by readers via pid liveness.\n }\n}\n\nexport interface ReadServerRuntimeOptions {\n isAlive?: (pid: number) => boolean;\n}\n\n/**\n * Read every advertised server record whose process is still alive. Missing or\n * malformed files yield an empty list. Read-only: stale records are ignored\n * here and physically pruned on the next registration/unregistration.\n */\nexport function readLiveServerRuntimeStates(\n env: HomeEnv = process.env,\n options: ReadServerRuntimeOptions = {},\n): ServerRuntimeState[] {\n const alive = options.isAlive ?? isPidAlive;\n return readAllRecords(env).filter(state => alive(state.pid));\n}\n\n/**\n * Wrapper selection policy: order candidate servers by preference —\n * 1. proxy mode before endpoint mode (bridging through the MITM proxy keeps\n * Claude Code's own Anthropic auth, the recommended setup);\n * 2. within a mode, newest startedAt first.\n * If only an endpoint server is live it is used; with no live server the\n * wrapper launches claude untouched (both handled by the caller).\n */\nexport function orderWrapperServerCandidates(records: ServerRuntimeState[]): ServerRuntimeState[] {\n return [...records].sort((a, b) => {\n if (a.mode !== b.mode) return a.mode === 'proxy' ? -1 : 1;\n return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);\n });\n}\n\n/**\n * Read the single preferred live server (selection policy above), or null when\n * none is advertised/alive.\n */\nexport function readLiveServerRuntimeState(\n env: HomeEnv = process.env,\n options: ReadServerRuntimeOptions = {},\n): ServerRuntimeState | null {\n return orderWrapperServerCandidates(readLiveServerRuntimeStates(env, options))[0] ?? null;\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { cpSync, existsSync, mkdirSync, readdirSync } from 'node:fs';\n\nexport const APP_DIR_NAME = 'clodex';\n/** One-time silent migration source: the relay-ai config home. */\nexport const LEGACY_APP_DIR_NAME = 'relay-ai';\n\ninterface HomeEnv {\n HOME?: string;\n CLODEX_HOME?: string;\n USERPROFILE?: string;\n}\n\nfunction userHome(env: HomeEnv = process.env): string {\n return env.HOME ?? env.USERPROFILE ?? homedir();\n}\n\nexport function resolveAppHomeOverride(env: HomeEnv = process.env): string | undefined {\n const override = env.CLODEX_HOME;\n return override?.trim() || undefined;\n}\n\nexport function getAppHome(env: HomeEnv = process.env): string {\n const override = resolveAppHomeOverride(env);\n if (override) return override;\n return join(userHome(env), `.${APP_DIR_NAME}`);\n}\n\nexport function getLegacyAppHome(env: HomeEnv = process.env): string {\n return join(userHome(env), `.${LEGACY_APP_DIR_NAME}`);\n}\n\nlet legacyMigrationDone = false;\n\n/**\n * One-time silent migration: when the clodex home does not exist yet but a\n * legacy ~/.relay-ai does, copy its config + auth state over so existing\n * providers and OAuth credentials keep working. The legacy directory itself is\n * never modified or deleted.\n */\nexport function ensureLegacyAppHomeMigrated(env: HomeEnv = process.env): void {\n if (legacyMigrationDone) return;\n legacyMigrationDone = true;\n try {\n const appHome = getAppHome(env);\n if (existsSync(appHome)) return;\n const legacyHome = getLegacyAppHome(env);\n if (!existsSync(legacyHome)) return;\n\n mkdirSync(appHome, { recursive: true, mode: 0o700 });\n for (const entry of readdirSync(legacyHome)) {\n if (entry === 'logs') continue; // session logs are not config/auth state\n cpSync(join(legacyHome, entry), join(appHome, entry), { recursive: true });\n }\n } catch {\n // Migration is best-effort; a fresh home still works.\n }\n}\n\n/** Test hook: allow the migration to run again against a new CLODEX_HOME. */\nexport function resetLegacyMigrationForTests(): void {\n legacyMigrationDone = false;\n}\n\nexport function getConfigPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'config.json');\n}\n\nexport function getProvidersPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'providers.json');\n}\n\nexport function getCredentialCleanupPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'credential-cleanup.json');\n}\n\nexport function getLogsPath(env: HomeEnv = process.env): string {\n return join(getAppHome(env), 'logs');\n}\n","import type { UserPreferences } from './types.js';\nimport { dirname } from 'node:path';\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { ensureLegacyAppHomeMigrated, getConfigPath } from './paths.js';\n\nfunction readJsonFile(path: string): UserPreferences | null {\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf8'));\n return parsed && typeof parsed === 'object' ? parsed as UserPreferences : null;\n } catch {\n return null;\n }\n}\n\nfunction readConfig(): UserPreferences {\n ensureLegacyAppHomeMigrated();\n return readJsonFile(getConfigPath()) ?? {};\n}\n\nfunction writeConfig(config: UserPreferences): void {\n const configPath = getConfigPath();\n mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, { encoding: 'utf8', mode: 0o600 });\n}\n\nexport function loadPreferences(): UserPreferences {\n const config = readConfig();\n return {\n lastModel: config.lastModel,\n lastProvider: config.lastProvider,\n recentModelsByProvider: config.recentModelsByProvider,\n favoriteModels: config.favoriteModels,\n modelAliases: config.modelAliases,\n claudeBridgeMode: config.claudeBridgeMode,\n serverBridgeMode: config.serverBridgeMode,\n appPathOverrides: config.appPathOverrides,\n recentLaunchFolders: config.recentLaunchFolders,\n server: config.server,\n };\n}\n\nexport function savePreferences(prefs: Partial<Pick<UserPreferences, 'lastModel' | 'lastProvider' | 'recentModelsByProvider' | 'favoriteModels' | 'modelAliases' | 'claudeBridgeMode' | 'serverBridgeMode' | 'appPathOverrides' | 'recentLaunchFolders'>>): void {\n const config = readConfig();\n if (prefs.lastModel !== undefined) config.lastModel = prefs.lastModel;\n if (prefs.lastProvider !== undefined) config.lastProvider = prefs.lastProvider;\n if (prefs.recentModelsByProvider !== undefined) config.recentModelsByProvider = prefs.recentModelsByProvider;\n if (prefs.favoriteModels !== undefined) config.favoriteModels = prefs.favoriteModels;\n if (prefs.modelAliases !== undefined) config.modelAliases = prefs.modelAliases;\n if (prefs.claudeBridgeMode !== undefined) config.claudeBridgeMode = prefs.claudeBridgeMode;\n if (prefs.serverBridgeMode !== undefined) config.serverBridgeMode = prefs.serverBridgeMode;\n if (prefs.appPathOverrides !== undefined) config.appPathOverrides = prefs.appPathOverrides;\n if (prefs.recentLaunchFolders !== undefined) config.recentLaunchFolders = prefs.recentLaunchFolders;\n writeConfig(config);\n}\n\nexport function getAppPathOverride(appId: string): string | undefined {\n const value = loadPreferences().appPathOverrides?.[appId];\n return typeof value === 'string' && value.trim() ? value : undefined;\n}\n\nexport function setAppPathOverride(appId: string, path: string | null): Record<string, string> {\n const config = readConfig();\n const next = { ...(config.appPathOverrides ?? {}) };\n const trimmed = path?.trim() ?? '';\n if (trimmed) next[appId] = trimmed;\n else delete next[appId];\n config.appPathOverrides = next;\n if (Object.keys(next).length === 0) delete config.appPathOverrides;\n writeConfig(config);\n return next;\n}\n\n/**\n * Resolve the bridge mode for a command. An explicit flag applies to that run only —\n * it is persisted as the command's default ONLY when the caller opts in (--save-mode).\n * With no flag, the saved per-command default applies; with no saved default, proxy.\n */\nexport function resolveBridgeMode(\n command: 'claude' | 'server',\n explicit: import('./types.js').BridgeMode | undefined,\n opts: { persist?: boolean } = {},\n): import('./types.js').BridgeMode {\n const key = command === 'claude' ? 'claudeBridgeMode' : 'serverBridgeMode';\n if (explicit) {\n if (opts.persist === true) savePreferences({ [key]: explicit });\n return explicit;\n }\n return loadPreferences()[key] ?? 'proxy';\n}\n\nconst MAX_RECENT_MODELS = 3;\nconst MAX_RECENT_LAUNCH_FOLDERS = 6;\n\nexport function recordLaunchFolder(folder: string): string[] {\n const trimmed = folder.trim();\n if (!trimmed) return loadPreferences().recentLaunchFolders ?? [];\n const config = readConfig();\n const prev = config.recentLaunchFolders ?? [];\n const next = [trimmed, ...prev.filter(path => path !== trimmed)].slice(0, MAX_RECENT_LAUNCH_FOLDERS);\n config.recentLaunchFolders = next;\n writeConfig(config);\n return next;\n}\n\nexport function recordLaunchSelection(\n _agent: 'claude',\n providerId: string,\n modelId: string,\n prefs: UserPreferences,\n): void {\n const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];\n const updatedRecent = [modelId, ...prevRecent.filter(id => id !== modelId)].slice(0, MAX_RECENT_MODELS);\n savePreferences({\n lastProvider: providerId,\n lastModel: modelId,\n recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent },\n });\n}\n\nconst SERVER_PASSWORD_SERVICE = 'clodex-server-password';\nconst SERVER_PASSWORD_ACCOUNT = 'server-password';\n\nasync function getServerPasswordKeyring(): Promise<any | null> {\n try {\n const { Entry } = await import('@napi-rs/keyring');\n return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);\n } catch {\n return null;\n }\n}\n\nexport async function getSavedServerPassword(): Promise<string | null> {\n const config = readConfig();\n if (config.server?.savedPassword) {\n const pwd = config.server.savedPassword;\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(pwd);\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n } catch {\n // Fallback: keep in config.json if keyring fails\n }\n }\n return pwd;\n }\n\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n return await keyring.getPassword();\n } catch {\n return null;\n }\n }\n return null;\n}\n\nexport async function setSavedServerPassword(password: string): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.setPassword(password);\n return;\n } catch {\n // Fallback\n }\n }\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n savedPassword: password,\n };\n writeConfig(config);\n}\n\nexport async function clearSavedServerPassword(): Promise<void> {\n const keyring = await getServerPasswordKeyring();\n if (keyring) {\n try {\n await keyring.deletePassword();\n } catch {\n // Ignore\n }\n }\n const config = readConfig();\n if (!config.server) return;\n delete config.server.savedPassword;\n if (Object.keys(config.server).length === 0) delete config.server;\n writeConfig(config);\n}\n\nexport function getServerExposedProviders(): string[] | null {\n const list = readConfig().server?.exposedProviders;\n return list && list.length > 0 ? list : null;\n}\n\nexport function setServerExposedProviders(providerIds: string[]): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n exposedProviders: providerIds,\n };\n writeConfig(config);\n}\n\nexport function getServerMaskGatewayIds(): boolean {\n return readConfig().server?.maskGatewayIds ?? true;\n}\n\nexport function setServerMaskGatewayIds(mask: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n maskGatewayIds: mask,\n };\n writeConfig(config);\n}\n\nexport function getServerFavoritesOnly(): boolean {\n return readConfig().server?.favoritesOnly ?? false;\n}\n\nexport function setServerFavoritesOnly(favoritesOnly: boolean): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n favoritesOnly,\n };\n writeConfig(config);\n}\n\nexport function getServerListenMode(): 'local' | 'network' {\n return readConfig().server?.listenMode === 'network' ? 'network' : 'local';\n}\n\nexport function setServerListenMode(listenMode: 'local' | 'network'): void {\n const config = readConfig();\n config.server = {\n ...(config.server ?? {}),\n listenMode,\n };\n writeConfig(config);\n}\n","// src/launch.ts\nimport { execSync, spawn } from 'node:child_process';\nimport { existsSync, appendFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { getAppPathOverride } from './config.js';\nimport { findBinaryOnPath } from './binary-lookup.js';\n\nconst isWindows = process.platform === 'win32';\n\nconst FALLBACK_PATHS = isWindows\n ? [\n join(process.env['APPDATA'] ?? homedir(), 'npm', 'claude.cmd'),\n join(process.env['APPDATA'] ?? homedir(), 'npm', 'claude'),\n join(homedir(), 'AppData', 'Roaming', 'npm', 'claude.cmd'),\n ]\n : [\n join(homedir(), '.local', 'bin', 'claude'),\n join(homedir(), '.npm', 'bin', 'claude'),\n '/usr/local/bin/claude',\n '/opt/homebrew/bin/claude',\n ];\n\nexport function findClaudeBinary(): string | null {\n const environmentOverride = process.env['CLODEX_CLAUDE_PATH'];\n if (environmentOverride?.trim()) {\n return existsSync(environmentOverride) ? environmentOverride : null;\n }\n\n const override = getAppPathOverride('claude');\n if (override) return existsSync(override) ? override : null;\n\n return findBinaryOnPath('claude', FALLBACK_PATHS);\n}\n\nexport function getInstalledClaudeVersion(): string {\n try {\n const claudePath = findClaudeBinary();\n if (!claudePath) return '2.1.183';\n const result = execSync(`${isWindows ? `\"${claudePath}\"` : claudePath} --version`, {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n const match = result.match(/(\\d+\\.\\d+\\.\\d+)/);\n if (match) return match[1];\n } catch {\n // fallback\n }\n return '2.1.183'; // default fallback version known to work\n}\n\nexport function buildClaudeArgs(model: string | undefined, extraArgs: string[]): string[] {\n return model ? ['--model', model, ...extraArgs] : [...extraArgs];\n}\n\nexport function launchClaude(\n env: NodeJS.ProcessEnv,\n model: string | undefined,\n extraArgs: string[],\n): Promise<number> {\n return new Promise((resolve) => {\n const claudePath = findClaudeBinary()!;\n const args = buildClaudeArgs(model, extraArgs);\n\n const debugFileIdx = extraArgs.indexOf('--debug-file');\n const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : undefined;\n\n const originalStdoutWrite = process.stdout.write;\n const originalStderrWrite = process.stderr.write;\n\n const muteWrite = (chunk: string | Uint8Array, encoding?: any, callback?: any) => {\n if (typeof encoding === 'function') {\n callback = encoding;\n }\n if (debugLogPath) {\n try {\n const str = typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);\n appendFileSync(debugLogPath, `[parent] ${str}`);\n } catch {\n // ignore\n }\n }\n if (callback) callback();\n return true;\n };\n\n process.stdout.write = muteWrite as any;\n process.stderr.write = muteWrite as any;\n\n const restore = () => {\n process.stdout.write = originalStdoutWrite;\n process.stderr.write = originalStderrWrite;\n };\n\n const child = spawn(claudePath, args, {\n stdio: 'inherit',\n env,\n shell: isWindows,\n });\n\n const forward = (signal: NodeJS.Signals): void => {\n child.kill(signal);\n };\n\n process.once('SIGINT', () => forward('SIGINT'));\n process.once('SIGTERM', () => forward('SIGTERM'));\n\n child.on('exit', (code) => {\n restore();\n resolve(code ?? 0);\n });\n\n child.on('error', (err) => {\n restore();\n resolve(1);\n });\n });\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\n\nexport interface FindBinaryOnPathOptions {\n verifyWhichResult?: boolean;\n isWindows?: boolean;\n exists?: (path: string) => boolean;\n runWhich?: (name: string, isWindows: boolean) => string;\n}\n\nexport function findBinaryOnPath(\n name: string,\n fallbackPaths: string[],\n options: FindBinaryOnPathOptions = {},\n): string | null {\n const isWindows = options.isWindows ?? process.platform === 'win32';\n const exists = options.exists ?? existsSync;\n // argv form, never a shell string — the binary name must not be shell-interpretable\n // (defense-in-depth originally added in d887984, must survive refactors).\n const runWhich = options.runWhich ?? ((binary, win) =>\n execFileSync(win ? 'where.exe' : 'which', [binary], {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }));\n\n try {\n const lines = runWhich(name, isWindows)\n .trim()\n .split('\\n')\n .map(line => line.trim())\n .filter(Boolean);\n const path = (isWindows ? lines.find(line => line.toLowerCase().endsWith('.cmd')) : null)\n ?? lines[0];\n if (path && (!options.verifyWhichResult || exists(path))) return path;\n } catch {\n // Fall through to fallback paths.\n }\n\n for (const path of fallbackPaths) {\n if (exists(path)) return path;\n }\n return null;\n}\n"],"mappings":"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","names":["mkdirSync","join","join","mkdirSync","dirname","mkdirSync","readFileSync","writeFileSync","readFileSync","mkdirSync","dirname","writeFileSync","keyring","existsSync","homedir","join","existsSync","isWindows","join","homedir","existsSync"]}