@bman654/clodex 2.1.0 → 2.1.2

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.
@@ -1,17 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/server-runtime.ts
4
- import {
5
- closeSync,
6
- mkdirSync,
7
- openSync,
8
- readFileSync,
9
- renameSync,
10
- rmSync,
11
- unlinkSync,
12
- writeFileSync
13
- } from "fs";
14
- import { dirname, join as join2 } from "path";
3
+ // src/config.ts
4
+ import { randomUUID as randomUUID3 } from "crypto";
5
+ import { readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync as unlinkSync3 } from "fs";
15
6
 
16
7
  // src/paths.ts
17
8
  import { homedir } from "os";
@@ -42,233 +33,22 @@ function getLogsPath(env = process.env) {
42
33
  return join(getAppHome(env), "logs");
43
34
  }
44
35
 
45
- // src/server-runtime.ts
46
- function getServerRuntimePath(env = process.env) {
47
- return join2(getAppHome(env), "server-runtime.json");
48
- }
49
- function getServerRuntimeLockPath(env = process.env) {
50
- return join2(getAppHome(env), "server-runtime.lock");
51
- }
52
- function isDiscoveryDisabled(flag, env = process.env) {
53
- if (flag !== void 0) return flag;
54
- const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();
55
- return raw === "1" || raw === "true";
56
- }
57
- function isPort(value) {
58
- return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
59
- }
60
- function parseServerRuntimeRecord(value) {
61
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
62
- const record = value;
63
- const mode = record["mode"];
64
- if (mode !== "endpoint" && mode !== "proxy") return null;
65
- if (!isPort(record["port"])) return null;
66
- const pid = record["pid"];
67
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
68
- const startedAt = typeof record["startedAt"] === "string" ? record["startedAt"] : "";
69
- const caPath = record["caPath"];
70
- if (mode === "proxy") {
71
- if (typeof caPath !== "string" || !caPath.trim()) return null;
72
- return { mode, port: record["port"], pid, caPath, startedAt };
73
- }
74
- return { mode, port: record["port"], pid, startedAt };
75
- }
76
- function parseServerRuntimeStates(raw) {
77
- let parsed;
78
- try {
79
- parsed = JSON.parse(raw);
80
- } catch {
81
- return [];
82
- }
83
- const items = Array.isArray(parsed) ? parsed : [parsed];
84
- const states = [];
85
- for (const item of items) {
86
- const state = parseServerRuntimeRecord(item);
87
- if (state) states.push(state);
88
- }
89
- return states;
90
- }
91
- function isPidAlive(pid, kill = process.kill.bind(process)) {
92
- try {
93
- kill(pid, 0);
94
- return true;
95
- } catch (err) {
96
- return err?.code === "EPERM";
97
- }
98
- }
99
- var RUNTIME_LOCK_STALE_MS = 1e4;
100
- var RUNTIME_LOCK_WAIT_MS = 500;
101
- var RUNTIME_LOCK_RETRY_MS = 25;
102
- function tryAcquireRuntimeLock(lockPath, opts = {}) {
103
- const now = opts.now ?? Date.now();
104
- const alive = opts.isAlive ?? isPidAlive;
105
- mkdirSync(dirname(lockPath), { recursive: true, mode: 448 });
106
- for (let attempt = 0; attempt < 2; attempt++) {
107
- try {
108
- const fd = openSync(lockPath, "wx");
109
- const content = { pid: process.pid, startedAt: now };
110
- writeFileSync(fd, JSON.stringify(content));
111
- closeSync(fd);
112
- return () => {
113
- try {
114
- unlinkSync(lockPath);
115
- } catch {
116
- }
117
- };
118
- } catch {
119
- let stale = false;
120
- try {
121
- const existing = JSON.parse(readFileSync(lockPath, "utf8"));
122
- stale = !existing.pid || !alive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > RUNTIME_LOCK_STALE_MS;
123
- } catch {
124
- stale = true;
125
- }
126
- if (!stale) return null;
127
- try {
128
- unlinkSync(lockPath);
129
- } catch {
130
- }
131
- }
132
- }
133
- return null;
134
- }
135
- function sleepSync(ms) {
136
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
137
- }
138
- function withRuntimeWriteLock(env, mutate) {
139
- const lockPath = getServerRuntimeLockPath(env);
140
- let release = null;
141
- const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;
142
- for (; ; ) {
143
- release = tryAcquireRuntimeLock(lockPath);
144
- if (release || Date.now() >= deadline) break;
145
- sleepSync(RUNTIME_LOCK_RETRY_MS);
146
- }
147
- try {
148
- mutate();
149
- } finally {
150
- release?.();
151
- }
152
- }
153
- function readAllRecords(env) {
154
- let raw;
155
- try {
156
- raw = readFileSync(getServerRuntimePath(env), "utf8");
157
- } catch {
158
- return [];
159
- }
160
- return parseServerRuntimeStates(raw);
161
- }
162
- function atomicWriteRecords(path, records) {
163
- mkdirSync(dirname(path), { recursive: true, mode: 448 });
164
- const tmpPath = `${path}.${process.pid}.tmp`;
165
- writeFileSync(tmpPath, `${JSON.stringify(records, null, 2)}
166
- `, { encoding: "utf8", mode: 384 });
167
- renameSync(tmpPath, path);
168
- }
169
- function registerServerRuntimeState(state, env = process.env, options = {}) {
170
- const alive = options.isAlive ?? isPidAlive;
171
- try {
172
- withRuntimeWriteLock(env, () => {
173
- const records = readAllRecords(env).filter(
174
- (record) => record.pid !== state.pid && alive(record.pid)
175
- );
176
- records.push(state);
177
- atomicWriteRecords(getServerRuntimePath(env), records);
178
- });
179
- } catch {
180
- }
181
- }
182
- function unregisterServerRuntimeState(pid = process.pid, env = process.env, options = {}) {
183
- const alive = options.isAlive ?? isPidAlive;
184
- try {
185
- withRuntimeWriteLock(env, () => {
186
- const records = readAllRecords(env).filter(
187
- (record) => record.pid !== pid && alive(record.pid)
188
- );
189
- if (records.length === 0) {
190
- rmSync(getServerRuntimePath(env), { force: true });
191
- } else {
192
- atomicWriteRecords(getServerRuntimePath(env), records);
193
- }
194
- });
195
- } catch {
196
- }
197
- }
198
- function readLiveServerRuntimeStates(env = process.env, options = {}) {
199
- const alive = options.isAlive ?? isPidAlive;
200
- return readAllRecords(env).filter((state) => alive(state.pid));
201
- }
202
- function orderWrapperServerCandidates(records) {
203
- return [...records].sort((a, b) => {
204
- if (a.mode !== b.mode) return a.mode === "proxy" ? -1 : 1;
205
- return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);
206
- });
207
- }
208
-
209
- // src/wrapper-env.ts
210
- var PROXY_ENV_VARS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"];
211
- var REQUIRE_SERVER_ENV = "CLODEX_REQUIRE_SERVER";
212
- function removeAnthropicProxyBypass(env) {
213
- const noProxyValues = [env["NO_PROXY"], env["no_proxy"]].filter((value) => value !== void 0);
214
- if (noProxyValues.length === 0) return;
215
- const filtered = [...new Set(noProxyValues.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).filter((value) => {
216
- const entry = value.toLowerCase().replace(/^https?:\/\//, "");
217
- const host = entry.replace(/:\d+$/, "");
218
- if (host === "*") return false;
219
- const suffix = host.startsWith("*.") ? host.slice(1) : host;
220
- const bypassesAnthropic = suffix.startsWith(".") ? "api.anthropic.com".endsWith(suffix) : "api.anthropic.com" === suffix || "api.anthropic.com".endsWith(`.${suffix}`);
221
- return !bypassesAnthropic;
222
- }))].join(",");
223
- if (filtered) {
224
- env["NO_PROXY"] = filtered;
225
- env["no_proxy"] = filtered;
226
- } else {
227
- delete env["NO_PROXY"];
228
- delete env["no_proxy"];
229
- }
230
- }
231
- var LOCAL_GATEWAY_API_KEY = "clodex-local";
232
- function wrapperRequiresServer(env) {
233
- return env[REQUIRE_SERVER_ENV] === "1";
234
- }
235
- function computeWrapperEnv(baseEnv, state) {
236
- const env = { ...baseEnv };
237
- if (!state) return env;
238
- if (state.mode === "proxy") {
239
- const proxyUrl = `http://127.0.0.1:${state.port}`;
240
- delete env["ANTHROPIC_BASE_URL"];
241
- for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;
242
- if (state.caPath) env["NODE_EXTRA_CA_CERTS"] = state.caPath;
243
- removeAnthropicProxyBypass(env);
244
- return env;
245
- }
246
- for (const name of PROXY_ENV_VARS) delete env[name];
247
- env["ANTHROPIC_BASE_URL"] = `http://127.0.0.1:${state.port}/anthropic`;
248
- env["ANTHROPIC_API_KEY"] = LOCAL_GATEWAY_API_KEY;
249
- return env;
250
- }
251
-
252
- // src/config.ts
253
- import { randomUUID as randomUUID3 } from "crypto";
254
- import { readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync as unlinkSync4 } from "fs";
255
-
256
36
  // src/registry/io.ts
257
37
  import { randomUUID as randomUUID2 } from "crypto";
258
38
  import {
259
39
  chmodSync,
260
- closeSync as closeSync3,
40
+ closeSync as closeSync2,
261
41
  copyFileSync,
262
42
  existsSync,
263
43
  fsyncSync as fsyncSync2,
264
- mkdirSync as mkdirSync3,
265
- openSync as openSync3,
266
- readFileSync as readFileSync3,
267
- renameSync as renameSync2,
268
- unlinkSync as unlinkSync3,
44
+ mkdirSync as mkdirSync2,
45
+ openSync as openSync2,
46
+ readFileSync as readFileSync2,
47
+ renameSync,
48
+ unlinkSync as unlinkSync2,
269
49
  writeSync
270
50
  } from "fs";
271
- import { dirname as dirname3 } from "path";
51
+ import { dirname as dirname2 } from "path";
272
52
 
273
53
  // src/registry/types.ts
274
54
  var REGISTRY_SCHEMA_VERSION = 1;
@@ -277,18 +57,19 @@ var REGISTRY_SCHEMA_VERSION = 1;
277
57
  import { AsyncLocalStorage } from "async_hooks";
278
58
  import { createHash, randomUUID } from "crypto";
279
59
  import {
280
- closeSync as closeSync2,
60
+ closeSync,
281
61
  fstatSync,
282
62
  fsyncSync,
283
63
  linkSync,
284
- mkdirSync as mkdirSync2,
285
- openSync as openSync2,
286
- readFileSync as readFileSync2,
64
+ mkdirSync,
65
+ openSync,
66
+ readFileSync,
287
67
  statSync,
288
- unlinkSync as unlinkSync2,
289
- writeFileSync as writeFileSync2
68
+ unlinkSync,
69
+ writeFileSync
290
70
  } from "fs";
291
- import { dirname as dirname2 } from "path";
71
+ import { userInfo } from "os";
72
+ import { dirname, isAbsolute, join as join2 } from "path";
292
73
  var DEFAULT_WAIT_MS = 3e4;
293
74
  var DEFAULT_CREDENTIAL_MUTATION_WAIT_MS = 15e4;
294
75
  var DEFAULT_RETRY_MS = 25;
@@ -302,7 +83,7 @@ var RegistryLockLostError = class extends Error {
302
83
  function getRegistryLockPath() {
303
84
  return `${getProvidersPath()}.lock`;
304
85
  }
305
- function isPidAlive2(pid) {
86
+ function isPidAlive(pid) {
306
87
  try {
307
88
  process.kill(pid, 0);
308
89
  return true;
@@ -328,8 +109,8 @@ function createLockRecord(lockPath, owner) {
328
109
  const tempPath = `${lockPath}.${process.pid}.${owner.token}.tmp`;
329
110
  let fd;
330
111
  try {
331
- fd = openSync2(tempPath, "wx", 384);
332
- writeFileSync2(fd, raw);
112
+ fd = openSync(tempPath, "wx", 384);
113
+ writeFileSync(fd, raw);
333
114
  fsyncSync(fd);
334
115
  const stats = fstatSync(fd);
335
116
  try {
@@ -345,9 +126,9 @@ function createLockRecord(lockPath, owner) {
345
126
  modifiedAt: stats.mtimeMs
346
127
  };
347
128
  } finally {
348
- if (fd !== void 0) closeSync2(fd);
129
+ if (fd !== void 0) closeSync(fd);
349
130
  try {
350
- unlinkSync2(tempPath);
131
+ unlinkSync(tempPath);
351
132
  } catch (err) {
352
133
  if (err.code !== "ENOENT") throw err;
353
134
  }
@@ -356,16 +137,16 @@ function createLockRecord(lockPath, owner) {
356
137
  function lockFileMatchesLease(lease) {
357
138
  let fd;
358
139
  try {
359
- fd = openSync2(lease.lockPath, "r");
140
+ fd = openSync(lease.lockPath, "r");
360
141
  const openedStats = fstatSync(fd);
361
- const owner = parseLockOwner(readFileSync2(fd, "utf8"));
142
+ const owner = parseLockOwner(readFileSync(fd, "utf8"));
362
143
  const pathStats = statSync(lease.lockPath);
363
144
  return owner?.token === lease.token && openedStats.dev === lease.device && openedStats.ino === lease.inode && pathStats.dev === lease.device && pathStats.ino === lease.inode;
364
145
  } catch (err) {
365
146
  if (err.code === "ENOENT") return false;
366
147
  throw err;
367
148
  } finally {
368
- if (fd !== void 0) closeSync2(fd);
149
+ if (fd !== void 0) closeSync(fd);
369
150
  }
370
151
  }
371
152
  function createLease(lockPath, owner, snapshot) {
@@ -384,7 +165,7 @@ function createLease(lockPath, owner, snapshot) {
384
165
  release: () => {
385
166
  if (!lease.active) return;
386
167
  lease.active = false;
387
- if (lockFileMatchesLease(lease)) unlinkSync2(lockPath);
168
+ if (lockFileMatchesLease(lease)) unlinkSync(lockPath);
388
169
  }
389
170
  };
390
171
  return lease;
@@ -396,7 +177,7 @@ function assertRegistryWriteOwnership(registryPath = getProvidersPath()) {
396
177
  lease.assertOwned();
397
178
  }
398
179
  function getStaleLockSnapshot(lockPath, alive) {
399
- const raw = readFileSync2(lockPath, "utf8");
180
+ const raw = readFileSync(lockPath, "utf8");
400
181
  const stats = statSync(lockPath);
401
182
  const snapshot = {
402
183
  raw,
@@ -411,12 +192,12 @@ function getStaleLockSnapshot(lockPath, alive) {
411
192
  function removeStaleLock(lockPath, expected) {
412
193
  try {
413
194
  if (expected) {
414
- const raw = readFileSync2(lockPath, "utf8");
195
+ const raw = readFileSync(lockPath, "utf8");
415
196
  const stats = statSync(lockPath);
416
197
  if (raw !== expected.raw || stats.dev !== expected.device || stats.ino !== expected.inode || stats.mtimeMs !== expected.modifiedAt)
417
198
  return false;
418
199
  }
419
- unlinkSync2(lockPath);
200
+ unlinkSync(lockPath);
420
201
  return true;
421
202
  } catch (err) {
422
203
  if (err.code !== "ENOENT") throw err;
@@ -452,8 +233,8 @@ function tryAcquireReaperGuard(lockPath, now, alive) {
452
233
  }
453
234
  function tryAcquireRegistryLock(lockPath = getRegistryLockPath(), options = {}) {
454
235
  const now = options.now?.() ?? Date.now();
455
- const alive = options.isAlive ?? isPidAlive2;
456
- mkdirSync2(dirname2(lockPath), { recursive: true, mode: 448 });
236
+ const alive = options.isAlive ?? isPidAlive;
237
+ mkdirSync(dirname(lockPath), { recursive: true, mode: 448 });
457
238
  for (let attempt = 0; attempt < 3; attempt += 1) {
458
239
  const owner = {
459
240
  pid: process.pid,
@@ -496,13 +277,13 @@ function tryAcquireRegistryLock(lockPath = getRegistryLockPath(), options = {})
496
277
  function sleep(ms) {
497
278
  return new Promise((resolve) => setTimeout(resolve, ms));
498
279
  }
499
- function sleepSync2(ms) {
280
+ function sleepSync(ms) {
500
281
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
501
282
  }
502
283
  function lockTimeoutError(lockPath, waitMs, alive) {
503
284
  let owner = null;
504
285
  try {
505
- owner = parseLockOwner(readFileSync2(lockPath, "utf8"));
286
+ owner = parseLockOwner(readFileSync(lockPath, "utf8"));
506
287
  } catch (err) {
507
288
  if (err.code !== "ENOENT") throw err;
508
289
  }
@@ -531,7 +312,7 @@ async function withRegistryWriteLock(operation, options = {}) {
531
312
  });
532
313
  if (lease) break;
533
314
  if (now() >= deadline)
534
- throw lockTimeoutError(lockPath, waitMs, options.isAlive ?? isPidAlive2);
315
+ throw lockTimeoutError(lockPath, waitMs, options.isAlive ?? isPidAlive);
535
316
  await sleep(retryMs);
536
317
  }
537
318
  const leases = new Map(inheritedLeases);
@@ -561,8 +342,8 @@ function withRegistryWriteLockSync(operation, options = {}) {
561
342
  });
562
343
  if (lease) break;
563
344
  if (now() >= deadline)
564
- throw lockTimeoutError(lockPath, waitMs, options.isAlive ?? isPidAlive2);
565
- sleepSync2(retryMs);
345
+ throw lockTimeoutError(lockPath, waitMs, options.isAlive ?? isPidAlive);
346
+ sleepSync(retryMs);
566
347
  }
567
348
  const leases = new Map(inheritedLeases);
568
349
  leases.set(lockPath, lease);
@@ -577,7 +358,20 @@ function withRegistryWriteLockSync(operation, options = {}) {
577
358
  }
578
359
  function getCredentialMutationLockPath(authRef) {
579
360
  const digest = createHash("sha256").update("clodex-credential-mutation\0").update(authRef).digest("hex");
580
- return `${getProvidersPath()}.credential-${digest}.lock`;
361
+ return join2(getCredentialLockRoot(), `${digest}.lock`);
362
+ }
363
+ function getNativeCredentialRoot() {
364
+ const nativeHome = userInfo().homedir;
365
+ if (!nativeHome || !isAbsolute(nativeHome)) {
366
+ throw new Error("Could not determine the native user home for credential coordination");
367
+ }
368
+ return join2(nativeHome, ".clodex");
369
+ }
370
+ function getCredentialLockRoot() {
371
+ return join2(getNativeCredentialRoot(), "credential-locks");
372
+ }
373
+ function getCredentialStateRoot() {
374
+ return join2(getNativeCredentialRoot(), "keyring-state");
581
375
  }
582
376
  function withCredentialMutationLock(authRef, operation, options = {}) {
583
377
  return withRegistryWriteLock(operation, {
@@ -586,6 +380,15 @@ function withCredentialMutationLock(authRef, operation, options = {}) {
586
380
  waitMs: options.waitMs ?? DEFAULT_CREDENTIAL_MUTATION_WAIT_MS
587
381
  });
588
382
  }
383
+ function getProviderMutationLockPath(providerSlot) {
384
+ const digest = createHash("sha256").update("clodex-provider-mutation\0").update(providerSlot).digest("hex");
385
+ return `${getProvidersPath()}.provider-${digest}.lock`;
386
+ }
387
+ function withProviderMutationLock(providerSlot, operation) {
388
+ return withRegistryWriteLock(operation, {
389
+ lockPath: getProviderMutationLockPath(providerSlot)
390
+ });
391
+ }
589
392
 
590
393
  // src/registry/migrate.ts
591
394
  function migrateOAuthOpenAiProvider(registry) {
@@ -615,7 +418,7 @@ var DIR_MODE = 448;
615
418
  var FILE_MODE = 384;
616
419
  function ensureSecureAppHome() {
617
420
  const home = getAppHome();
618
- mkdirSync3(home, { recursive: true, mode: DIR_MODE });
421
+ mkdirSync2(home, { recursive: true, mode: DIR_MODE });
619
422
  try {
620
423
  chmodSync(home, DIR_MODE);
621
424
  } catch {
@@ -623,8 +426,8 @@ function ensureSecureAppHome() {
623
426
  }
624
427
  function writeSecureFile(path, content) {
625
428
  ensureSecureAppHome();
626
- mkdirSync3(dirname3(path), { recursive: true, mode: DIR_MODE });
627
- const fd = openSync3(path, "wx", FILE_MODE);
429
+ mkdirSync2(dirname2(path), { recursive: true, mode: DIR_MODE });
430
+ const fd = openSync2(path, "wx", FILE_MODE);
628
431
  try {
629
432
  const payload = Buffer.from(content);
630
433
  let offset = 0;
@@ -637,7 +440,7 @@ function writeSecureFile(path, content) {
637
440
  }
638
441
  fsyncSync2(fd);
639
442
  } finally {
640
- closeSync3(fd);
443
+ closeSync2(fd);
641
444
  }
642
445
  try {
643
446
  chmodSync(path, FILE_MODE);
@@ -647,13 +450,13 @@ function writeSecureFile(path, content) {
647
450
  function syncParentDirectory(path) {
648
451
  let fd;
649
452
  try {
650
- fd = openSync3(dirname3(path), "r");
453
+ fd = openSync2(dirname2(path), "r");
651
454
  fsyncSync2(fd);
652
455
  } catch (error) {
653
456
  const code = error.code;
654
457
  if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EPERM") throw error;
655
458
  } finally {
656
- if (fd !== void 0) closeSync3(fd);
459
+ if (fd !== void 0) closeSync2(fd);
657
460
  }
658
461
  }
659
462
  function parseProvider(raw) {
@@ -760,14 +563,14 @@ function parseRegistryStrict(raw) {
760
563
  return parseRegistry(raw);
761
564
  }
762
565
  function readRegistryStrict(path) {
763
- return parseRegistryStrict(JSON.parse(readFileSync3(path, "utf8")));
566
+ return parseRegistryStrict(JSON.parse(readFileSync2(path, "utf8")));
764
567
  }
765
568
  function loadRegistry(path = getProvidersPath()) {
766
569
  if (!existsSync(path)) {
767
570
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
768
571
  }
769
572
  try {
770
- const raw = JSON.parse(readFileSync3(path, "utf8"));
573
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
771
574
  const registry = parseRegistry(raw);
772
575
  const migrated = migrateOAuthOpenAiProvider(registry);
773
576
  if (migrated) {
@@ -808,11 +611,11 @@ function saveRegistry(registry, path = getProvidersPath()) {
808
611
  try {
809
612
  writeSecureFile(tmp, payload);
810
613
  assertRegistryWriteOwnership(path);
811
- renameSync2(tmp, path);
614
+ renameSync(tmp, path);
812
615
  syncParentDirectory(path);
813
616
  } finally {
814
617
  try {
815
- unlinkSync3(tmp);
618
+ unlinkSync2(tmp);
816
619
  } catch (err) {
817
620
  if (err.code !== "ENOENT") throw err;
818
621
  }
@@ -822,7 +625,7 @@ function saveRegistry(registry, path = getProvidersPath()) {
822
625
  // src/config.ts
823
626
  function readJsonFile(path) {
824
627
  try {
825
- const parsed = JSON.parse(readFileSync4(path, "utf8"));
628
+ const parsed = JSON.parse(readFileSync3(path, "utf8"));
826
629
  return parsed && typeof parsed === "object" ? parsed : null;
827
630
  } catch {
828
631
  return null;
@@ -840,11 +643,11 @@ function writeConfig(config) {
840
643
  try {
841
644
  writeSecureFile(tmp, payload);
842
645
  assertRegistryWriteOwnership(configPath);
843
- renameSync3(tmp, configPath);
646
+ renameSync2(tmp, configPath);
844
647
  syncParentDirectory(configPath);
845
648
  } finally {
846
649
  try {
847
- unlinkSync4(tmp);
650
+ unlinkSync3(tmp);
848
651
  } catch (error) {
849
652
  if (error.code !== "ENOENT") throw error;
850
653
  }
@@ -1129,22 +932,337 @@ function launchClaude(env, model, extraArgs) {
1129
932
  });
1130
933
  }
1131
934
 
935
+ // src/listener-ready.ts
936
+ import { connect } from "net";
937
+ import { setTimeout as delay } from "timers/promises";
938
+ var LISTENER_READY_TIMEOUT_MS = 1e3;
939
+ var LISTENER_READY_RETRY_MS = 5;
940
+ var TCP_PROBE_TIMEOUT_MS = 50;
941
+ function connectHost(address) {
942
+ if (address === "0.0.0.0") return "127.0.0.1";
943
+ if (address === "::") return "::1";
944
+ return address;
945
+ }
946
+ function tcpListenerUrlHost(address) {
947
+ const host = connectHost(address);
948
+ return host.includes(":") ? `[${host}]` : host;
949
+ }
950
+ function probeTcpListener(host, port, timeoutMs) {
951
+ return new Promise((resolve) => {
952
+ const socket = connect({ host, port });
953
+ let settled = false;
954
+ const finish = (result) => {
955
+ if (settled) return;
956
+ settled = true;
957
+ socket.destroy();
958
+ resolve(result);
959
+ };
960
+ socket.once("connect", () => finish("ready"));
961
+ socket.once("error", (error) => {
962
+ finish(
963
+ error.code === "ETIMEDOUT" ? "timeout" : "unreachable"
964
+ );
965
+ });
966
+ socket.setTimeout(timeoutMs, () => finish("timeout"));
967
+ });
968
+ }
969
+ async function waitForTcpListenerCandidate(host, candidates, timeoutMs = LISTENER_READY_TIMEOUT_MS, options = {}) {
970
+ if (candidates.length === 0) return null;
971
+ const now = options.now ?? Date.now;
972
+ const probe = options.probe ?? probeTcpListener;
973
+ const retryFailure = options.retryFailure ?? (() => true);
974
+ const wait = options.delay ?? ((ms) => delay(ms));
975
+ const deadline = now() + timeoutMs;
976
+ let pendingCandidates = [...candidates];
977
+ do {
978
+ const remaining = Math.max(1, deadline - now());
979
+ const results = await Promise.all(
980
+ pendingCandidates.map((candidate) => probe(
981
+ host,
982
+ candidate.port,
983
+ Math.min(remaining, TCP_PROBE_TIMEOUT_MS)
984
+ ))
985
+ );
986
+ const readyIndex = results.findIndex((result) => result === "ready");
987
+ if (readyIndex >= 0) return pendingCandidates[readyIndex] ?? null;
988
+ pendingCandidates = pendingCandidates.filter((_candidate, index) => {
989
+ const result = results[index];
990
+ return result !== void 0 && result !== "ready" && retryFailure(result);
991
+ });
992
+ if (pendingCandidates.length === 0) return null;
993
+ const retryDelay = Math.min(LISTENER_READY_RETRY_MS, deadline - now());
994
+ if (retryDelay <= 0) return null;
995
+ await wait(retryDelay);
996
+ } while (now() < deadline);
997
+ return null;
998
+ }
999
+ async function waitForTcpListener(host, port, timeoutMs = LISTENER_READY_TIMEOUT_MS, options = {}) {
1000
+ return await waitForTcpListenerCandidate(host, [{ port }], timeoutMs, options) !== null;
1001
+ }
1002
+ async function closeAfterReadinessFailure(server) {
1003
+ if (!server.listening) return;
1004
+ await new Promise((resolve) => server.close(() => resolve()));
1005
+ }
1006
+ async function listenTcpServer(server, port, host) {
1007
+ await new Promise((resolve, reject) => {
1008
+ const cleanup = () => server.off("error", onError);
1009
+ const onError = (error) => {
1010
+ cleanup();
1011
+ reject(error);
1012
+ };
1013
+ server.once("error", onError);
1014
+ try {
1015
+ server.listen(port, host, () => {
1016
+ cleanup();
1017
+ resolve();
1018
+ });
1019
+ } catch (error) {
1020
+ cleanup();
1021
+ reject(error);
1022
+ }
1023
+ });
1024
+ const address = server.address();
1025
+ if (!address || typeof address === "string") {
1026
+ await closeAfterReadinessFailure(server);
1027
+ throw new Error("TCP server did not bind to a network address");
1028
+ }
1029
+ const probeHost = connectHost(address.address);
1030
+ if (await waitForTcpListener(probeHost, address.port)) return address;
1031
+ await closeAfterReadinessFailure(server);
1032
+ throw new Error(
1033
+ `TCP listener did not become reachable within ${LISTENER_READY_TIMEOUT_MS}ms: ${probeHost}:${address.port}`
1034
+ );
1035
+ }
1036
+
1037
+ // src/server-runtime.ts
1038
+ import {
1039
+ closeSync as closeSync3,
1040
+ mkdirSync as mkdirSync3,
1041
+ openSync as openSync3,
1042
+ readFileSync as readFileSync4,
1043
+ renameSync as renameSync3,
1044
+ rmSync,
1045
+ unlinkSync as unlinkSync4,
1046
+ writeFileSync as writeFileSync2
1047
+ } from "fs";
1048
+ import { dirname as dirname3, join as join4 } from "path";
1049
+ function getServerRuntimePath(env = process.env) {
1050
+ return join4(getAppHome(env), "server-runtime.json");
1051
+ }
1052
+ function getServerRuntimeLockPath(env = process.env) {
1053
+ return join4(getAppHome(env), "server-runtime.lock");
1054
+ }
1055
+ function isDiscoveryDisabled(flag, env = process.env) {
1056
+ if (flag !== void 0) return flag;
1057
+ const raw = env.CLODEX_NO_DISCOVERY?.trim().toLowerCase();
1058
+ return raw === "1" || raw === "true";
1059
+ }
1060
+ function isPort(value) {
1061
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
1062
+ }
1063
+ function parseServerRuntimeRecord(value) {
1064
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1065
+ const record = value;
1066
+ const mode = record["mode"];
1067
+ if (mode !== "endpoint" && mode !== "proxy") return null;
1068
+ if (!isPort(record["port"])) return null;
1069
+ const pid = record["pid"];
1070
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return null;
1071
+ const startedAt = typeof record["startedAt"] === "string" ? record["startedAt"] : "";
1072
+ const caPath = record["caPath"];
1073
+ if (mode === "proxy") {
1074
+ if (typeof caPath !== "string" || !caPath.trim()) return null;
1075
+ return { mode, port: record["port"], pid, caPath, startedAt };
1076
+ }
1077
+ return { mode, port: record["port"], pid, startedAt };
1078
+ }
1079
+ function parseServerRuntimeStates(raw) {
1080
+ let parsed;
1081
+ try {
1082
+ parsed = JSON.parse(raw);
1083
+ } catch {
1084
+ return [];
1085
+ }
1086
+ const items = Array.isArray(parsed) ? parsed : [parsed];
1087
+ const states = [];
1088
+ for (const item of items) {
1089
+ const state = parseServerRuntimeRecord(item);
1090
+ if (state) states.push(state);
1091
+ }
1092
+ return states;
1093
+ }
1094
+ function isPidAlive2(pid, kill = process.kill.bind(process)) {
1095
+ try {
1096
+ kill(pid, 0);
1097
+ return true;
1098
+ } catch (err) {
1099
+ return err?.code === "EPERM";
1100
+ }
1101
+ }
1102
+ var RUNTIME_LOCK_STALE_MS = 1e4;
1103
+ var RUNTIME_LOCK_WAIT_MS = 500;
1104
+ var RUNTIME_LOCK_RETRY_MS = 25;
1105
+ function tryAcquireRuntimeLock(lockPath, opts = {}) {
1106
+ const now = opts.now ?? Date.now();
1107
+ const alive = opts.isAlive ?? isPidAlive2;
1108
+ mkdirSync3(dirname3(lockPath), { recursive: true, mode: 448 });
1109
+ for (let attempt = 0; attempt < 2; attempt++) {
1110
+ try {
1111
+ const fd = openSync3(lockPath, "wx");
1112
+ const content = { pid: process.pid, startedAt: now };
1113
+ writeFileSync2(fd, JSON.stringify(content));
1114
+ closeSync3(fd);
1115
+ return () => {
1116
+ try {
1117
+ unlinkSync4(lockPath);
1118
+ } catch {
1119
+ }
1120
+ };
1121
+ } catch {
1122
+ let stale = false;
1123
+ try {
1124
+ const existing = JSON.parse(readFileSync4(lockPath, "utf8"));
1125
+ stale = !existing.pid || !alive(existing.pid) || typeof existing.startedAt === "number" && now - existing.startedAt > RUNTIME_LOCK_STALE_MS;
1126
+ } catch {
1127
+ stale = true;
1128
+ }
1129
+ if (!stale) return null;
1130
+ try {
1131
+ unlinkSync4(lockPath);
1132
+ } catch {
1133
+ }
1134
+ }
1135
+ }
1136
+ return null;
1137
+ }
1138
+ function sleepSync2(ms) {
1139
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1140
+ }
1141
+ function withRuntimeWriteLock(env, mutate) {
1142
+ const lockPath = getServerRuntimeLockPath(env);
1143
+ let release = null;
1144
+ const deadline = Date.now() + RUNTIME_LOCK_WAIT_MS;
1145
+ for (; ; ) {
1146
+ release = tryAcquireRuntimeLock(lockPath);
1147
+ if (release || Date.now() >= deadline) break;
1148
+ sleepSync2(RUNTIME_LOCK_RETRY_MS);
1149
+ }
1150
+ try {
1151
+ mutate();
1152
+ } finally {
1153
+ release?.();
1154
+ }
1155
+ }
1156
+ function readAllRecords(env) {
1157
+ let raw;
1158
+ try {
1159
+ raw = readFileSync4(getServerRuntimePath(env), "utf8");
1160
+ } catch {
1161
+ return [];
1162
+ }
1163
+ return parseServerRuntimeStates(raw);
1164
+ }
1165
+ function atomicWriteRecords(path, records) {
1166
+ mkdirSync3(dirname3(path), { recursive: true, mode: 448 });
1167
+ const tmpPath = `${path}.${process.pid}.tmp`;
1168
+ writeFileSync2(tmpPath, `${JSON.stringify(records, null, 2)}
1169
+ `, { encoding: "utf8", mode: 384 });
1170
+ renameSync3(tmpPath, path);
1171
+ }
1172
+ function registerServerRuntimeState(state, env = process.env, options = {}) {
1173
+ const alive = options.isAlive ?? isPidAlive2;
1174
+ try {
1175
+ withRuntimeWriteLock(env, () => {
1176
+ const records = readAllRecords(env).filter(
1177
+ (record) => record.pid !== state.pid && alive(record.pid)
1178
+ );
1179
+ records.push(state);
1180
+ atomicWriteRecords(getServerRuntimePath(env), records);
1181
+ });
1182
+ } catch {
1183
+ }
1184
+ }
1185
+ function unregisterServerRuntimeState(pid = process.pid, env = process.env, options = {}) {
1186
+ const alive = options.isAlive ?? isPidAlive2;
1187
+ try {
1188
+ withRuntimeWriteLock(env, () => {
1189
+ const records = readAllRecords(env).filter(
1190
+ (record) => record.pid !== pid && alive(record.pid)
1191
+ );
1192
+ if (records.length === 0) {
1193
+ rmSync(getServerRuntimePath(env), { force: true });
1194
+ } else {
1195
+ atomicWriteRecords(getServerRuntimePath(env), records);
1196
+ }
1197
+ });
1198
+ } catch {
1199
+ }
1200
+ }
1201
+ function readLiveServerRuntimeStates(env = process.env, options = {}) {
1202
+ const alive = options.isAlive ?? isPidAlive2;
1203
+ return readAllRecords(env).filter((state) => alive(state.pid));
1204
+ }
1205
+ function orderWrapperServerCandidates(records) {
1206
+ return [...records].sort((a, b) => {
1207
+ if (a.mode !== b.mode) return a.mode === "proxy" ? -1 : 1;
1208
+ return (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0);
1209
+ });
1210
+ }
1211
+
1212
+ // src/wrapper-env.ts
1213
+ var PROXY_ENV_VARS = ["HTTPS_PROXY", "HTTP_PROXY", "https_proxy", "http_proxy"];
1214
+ var REQUIRE_SERVER_ENV = "CLODEX_REQUIRE_SERVER";
1215
+ function removeAnthropicProxyBypass(env) {
1216
+ const noProxyValues = [env["NO_PROXY"], env["no_proxy"]].filter((value) => value !== void 0);
1217
+ if (noProxyValues.length === 0) return;
1218
+ const filtered = [...new Set(noProxyValues.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean).filter((value) => {
1219
+ const entry = value.toLowerCase().replace(/^https?:\/\//, "");
1220
+ const host = entry.replace(/:\d+$/, "");
1221
+ if (host === "*") return false;
1222
+ const suffix = host.startsWith("*.") ? host.slice(1) : host;
1223
+ const bypassesAnthropic = suffix.startsWith(".") ? "api.anthropic.com".endsWith(suffix) : "api.anthropic.com" === suffix || "api.anthropic.com".endsWith(`.${suffix}`);
1224
+ return !bypassesAnthropic;
1225
+ }))].join(",");
1226
+ if (filtered) {
1227
+ env["NO_PROXY"] = filtered;
1228
+ env["no_proxy"] = filtered;
1229
+ } else {
1230
+ delete env["NO_PROXY"];
1231
+ delete env["no_proxy"];
1232
+ }
1233
+ }
1234
+ var LOCAL_GATEWAY_API_KEY = "clodex-local";
1235
+ function wrapperRequiresServer(env) {
1236
+ return env[REQUIRE_SERVER_ENV] === "1";
1237
+ }
1238
+ function computeWrapperEnv(baseEnv, state) {
1239
+ const env = { ...baseEnv };
1240
+ if (!state) return env;
1241
+ if (state.mode === "proxy") {
1242
+ const proxyUrl = `http://127.0.0.1:${state.port}`;
1243
+ delete env["ANTHROPIC_BASE_URL"];
1244
+ for (const name of PROXY_ENV_VARS) env[name] = proxyUrl;
1245
+ if (state.caPath) env["NODE_EXTRA_CA_CERTS"] = state.caPath;
1246
+ removeAnthropicProxyBypass(env);
1247
+ return env;
1248
+ }
1249
+ for (const name of PROXY_ENV_VARS) delete env[name];
1250
+ env["ANTHROPIC_BASE_URL"] = `http://127.0.0.1:${state.port}/anthropic`;
1251
+ env["ANTHROPIC_API_KEY"] = LOCAL_GATEWAY_API_KEY;
1252
+ return env;
1253
+ }
1254
+
1132
1255
  export {
1133
1256
  getAppHome,
1134
1257
  getCredentialCleanupPath,
1135
1258
  getLogsPath,
1136
- isDiscoveryDisabled,
1137
- registerServerRuntimeState,
1138
- unregisterServerRuntimeState,
1139
- readLiveServerRuntimeStates,
1140
- orderWrapperServerCandidates,
1141
- removeAnthropicProxyBypass,
1142
- wrapperRequiresServer,
1143
- computeWrapperEnv,
1144
1259
  assertRegistryWriteOwnership,
1145
1260
  withRegistryWriteLock,
1146
1261
  withRegistryWriteLockSync,
1262
+ getCredentialMutationLockPath,
1263
+ getCredentialStateRoot,
1147
1264
  withCredentialMutationLock,
1265
+ withProviderMutationLock,
1148
1266
  isValidProviderId,
1149
1267
  ensureSecureAppHome,
1150
1268
  loadRegistry,
@@ -1166,6 +1284,17 @@ export {
1166
1284
  setServerListenMode,
1167
1285
  findClaudeBinary,
1168
1286
  getInstalledClaudeVersion,
1169
- launchClaude
1287
+ launchClaude,
1288
+ tcpListenerUrlHost,
1289
+ waitForTcpListenerCandidate,
1290
+ listenTcpServer,
1291
+ isDiscoveryDisabled,
1292
+ registerServerRuntimeState,
1293
+ unregisterServerRuntimeState,
1294
+ readLiveServerRuntimeStates,
1295
+ orderWrapperServerCandidates,
1296
+ removeAnthropicProxyBypass,
1297
+ wrapperRequiresServer,
1298
+ computeWrapperEnv
1170
1299
  };
1171
- //# sourceMappingURL=chunk-QLGIKUKC.js.map
1300
+ //# sourceMappingURL=chunk-OVO6OUZG.js.map