agent-shim 7.6.1 → 7.8.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/README.md +2 -1
- package/dist/cli.cjs +3714 -3687
- package/dist/index.cjs +1087 -183
- package/dist/index.mjs +1017 -168
- package/dist/types/cli/bool.d.ts +8 -0
- package/dist/types/cli/parsers.d.ts +40 -0
- package/dist/types/configProfilesStore.d.ts +61 -0
- package/dist/types/credential.d.ts +136 -0
- package/dist/types/credentialCache.d.ts +46 -0
- package/dist/types/directoryRulesStore.d.ts +43 -0
- package/dist/types/frontdoor/connect.d.ts +7 -6
- package/dist/types/frontdoor/route.d.ts +112 -0
- package/dist/types/frontdoor/tap.d.ts +2 -1
- package/dist/types/headroom/headers.d.ts +11 -0
- package/dist/types/identityStore.d.ts +85 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/launcher/flags.d.ts +96 -0
- package/dist/types/launcher/identity.d.ts +95 -0
- package/dist/types/poolStore.d.ts +18 -0
- package/dist/types/providersStore.d.ts +137 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -243,6 +243,11 @@ var SHIPPED_CATEGORY_DEFAULTS = Object.freeze({
|
|
|
243
243
|
settings: true
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
+
// src/config/store.ts
|
|
247
|
+
import fs from "node:fs";
|
|
248
|
+
import path2 from "node:path";
|
|
249
|
+
import { randomUUID } from "node:crypto";
|
|
250
|
+
|
|
246
251
|
// src/config/load.ts
|
|
247
252
|
import { cosmiconfigSync } from "cosmiconfig";
|
|
248
253
|
|
|
@@ -261,6 +266,98 @@ var UsageError = class extends CliError {
|
|
|
261
266
|
}
|
|
262
267
|
};
|
|
263
268
|
|
|
269
|
+
// src/config/load.ts
|
|
270
|
+
var ConfigValidationError = class extends CliError {
|
|
271
|
+
constructor(filepath, issues) {
|
|
272
|
+
const detail = issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
273
|
+
super(`Invalid configuration in ${filepath}:
|
|
274
|
+
${detail}`);
|
|
275
|
+
this.filepath = filepath;
|
|
276
|
+
this.issues = issues;
|
|
277
|
+
this.name = "ConfigValidationError";
|
|
278
|
+
}
|
|
279
|
+
filepath;
|
|
280
|
+
issues;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/config/store.ts
|
|
284
|
+
function isEnoent(error) {
|
|
285
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
286
|
+
}
|
|
287
|
+
var nodeStoreFs = {
|
|
288
|
+
readFileUtf8(filePath) {
|
|
289
|
+
try {
|
|
290
|
+
return fs.readFileSync(filePath, "utf8");
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (isEnoent(error)) {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
writeFileUtf8(filePath, contents) {
|
|
299
|
+
fs.writeFileSync(filePath, contents, "utf8");
|
|
300
|
+
},
|
|
301
|
+
renameSync(from, to) {
|
|
302
|
+
fs.renameSync(from, to);
|
|
303
|
+
},
|
|
304
|
+
mkdirSync(dirPath) {
|
|
305
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
306
|
+
},
|
|
307
|
+
unlinkSync(filePath) {
|
|
308
|
+
try {
|
|
309
|
+
fs.unlinkSync(filePath);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (!isEnoent(error)) {
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
function readJson(filePath, schema, storeFs = nodeStoreFs) {
|
|
318
|
+
const raw = storeFs.readFileUtf8(filePath);
|
|
319
|
+
if (raw === void 0) {
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
const parsed = JSON.parse(raw);
|
|
323
|
+
const result = schema.safeParse(parsed);
|
|
324
|
+
if (!result.success) {
|
|
325
|
+
throw new ConfigValidationError(filePath, result.error.issues);
|
|
326
|
+
}
|
|
327
|
+
return result.data;
|
|
328
|
+
}
|
|
329
|
+
function writeTextAtomic(filePath, contents, storeFs = nodeStoreFs) {
|
|
330
|
+
const dir = path2.dirname(filePath);
|
|
331
|
+
storeFs.mkdirSync(dir);
|
|
332
|
+
const tempPath = path2.join(dir, `.${path2.basename(filePath)}.${String(process.pid)}.${randomUUID()}.tmp`);
|
|
333
|
+
storeFs.writeFileUtf8(tempPath, contents);
|
|
334
|
+
try {
|
|
335
|
+
storeFs.renameSync(tempPath, filePath);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
storeFs.unlinkSync(tempPath);
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function writeJsonAtomic(filePath, data, storeFs = nodeStoreFs) {
|
|
342
|
+
writeTextAtomic(filePath, `${JSON.stringify(data, null, 2)}
|
|
343
|
+
`, storeFs);
|
|
344
|
+
}
|
|
345
|
+
function applyPatch(filePath, schema, patch, options = {}) {
|
|
346
|
+
const storeFs = options.storeFs ?? nodeStoreFs;
|
|
347
|
+
const existing = readJson(filePath, schema, storeFs) ?? options.defaults;
|
|
348
|
+
if (existing === void 0) {
|
|
349
|
+
throw new Error(`Cannot patch ${filePath}: it does not exist yet and no defaults were provided.`);
|
|
350
|
+
}
|
|
351
|
+
const merged = { ...existing, ...patch };
|
|
352
|
+
const result = schema.safeParse(merged);
|
|
353
|
+
if (!result.success) {
|
|
354
|
+
throw new ConfigValidationError(filePath, result.error.issues);
|
|
355
|
+
}
|
|
356
|
+
const validated = result.data;
|
|
357
|
+
writeJsonAtomic(filePath, validated, storeFs);
|
|
358
|
+
return validated;
|
|
359
|
+
}
|
|
360
|
+
|
|
264
361
|
// src/config/classify.ts
|
|
265
362
|
var GLOB_METACHARACTERS = /[*?[\]{}!()]/;
|
|
266
363
|
function isExactPattern(pattern) {
|
|
@@ -852,7 +949,7 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
852
949
|
}
|
|
853
950
|
|
|
854
951
|
// src/resolve/plan.ts
|
|
855
|
-
import
|
|
952
|
+
import path3 from "node:path";
|
|
856
953
|
function parentOf(rel) {
|
|
857
954
|
const index = rel.lastIndexOf("/");
|
|
858
955
|
return index === -1 ? "" : rel.slice(0, index);
|
|
@@ -902,7 +999,7 @@ function planFarm(params) {
|
|
|
902
999
|
}
|
|
903
1000
|
return value;
|
|
904
1001
|
};
|
|
905
|
-
const linkTarget = (rel) =>
|
|
1002
|
+
const linkTarget = (rel) => path3.join(params.facts.claudeHome, rel);
|
|
906
1003
|
const visit = (rel) => {
|
|
907
1004
|
const fact = params.facts.entries.get(rel);
|
|
908
1005
|
const decision = params.decisions.get(rel);
|
|
@@ -1126,7 +1223,7 @@ function resolveDecisions(input) {
|
|
|
1126
1223
|
}
|
|
1127
1224
|
|
|
1128
1225
|
// src/launcher/farm.ts
|
|
1129
|
-
import
|
|
1226
|
+
import path5 from "node:path";
|
|
1130
1227
|
import { z as z2 } from "zod";
|
|
1131
1228
|
|
|
1132
1229
|
// src/resolve/reconcile.ts
|
|
@@ -1225,8 +1322,8 @@ var LEGACY_HOME_DIRNAME = ".claude-use";
|
|
|
1225
1322
|
var LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
|
|
1226
1323
|
|
|
1227
1324
|
// src/launcher/lock.ts
|
|
1228
|
-
import { randomUUID } from "node:crypto";
|
|
1229
|
-
import
|
|
1325
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1326
|
+
import path4 from "node:path";
|
|
1230
1327
|
var DEFAULT_STALE_AFTER_MS = 12e4;
|
|
1231
1328
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
1232
1329
|
var DEFAULT_MAX_ATTEMPTS = 200;
|
|
@@ -1245,7 +1342,7 @@ var IdentityLockBusyError = class extends CliError {
|
|
|
1245
1342
|
holderPid;
|
|
1246
1343
|
};
|
|
1247
1344
|
function identityLockPath(dir, identity) {
|
|
1248
|
-
return
|
|
1345
|
+
return path4.join(dir, `.${identity}.lock`);
|
|
1249
1346
|
}
|
|
1250
1347
|
function parseLockRecord(raw) {
|
|
1251
1348
|
if (raw === void 0) {
|
|
@@ -1274,7 +1371,7 @@ function acquireIdentityLock(params) {
|
|
|
1274
1371
|
const staleAfterMs = params.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
1275
1372
|
const retryDelayMs = params.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
1276
1373
|
const maxAttempts = params.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
1277
|
-
const token =
|
|
1374
|
+
const token = randomUUID2();
|
|
1278
1375
|
if (params.fs.lstat(params.dir) === void 0) {
|
|
1279
1376
|
params.fs.mkdirp(params.dir);
|
|
1280
1377
|
}
|
|
@@ -1323,8 +1420,8 @@ var FarmManifestSchema = z2.strictObject({
|
|
|
1323
1420
|
materialised: z2.array(z2.string()),
|
|
1324
1421
|
links: z2.array(z2.strictObject({ rel: z2.string(), target: z2.string() }))
|
|
1325
1422
|
});
|
|
1326
|
-
function readFarmManifest(
|
|
1327
|
-
const raw =
|
|
1423
|
+
function readFarmManifest(fs7, farmRoot) {
|
|
1424
|
+
const raw = fs7.readFileUtf8(path5.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs7.readFileUtf8(path5.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
|
|
1328
1425
|
if (raw === void 0) {
|
|
1329
1426
|
return void 0;
|
|
1330
1427
|
}
|
|
@@ -1340,7 +1437,7 @@ function readFarmManifest(fs3, farmRoot) {
|
|
|
1340
1437
|
function buildEntryFacts(params) {
|
|
1341
1438
|
const entries = /* @__PURE__ */ new Map();
|
|
1342
1439
|
const visit = (rel) => {
|
|
1343
|
-
const absolute =
|
|
1440
|
+
const absolute = path5.join(params.claudeHome, rel);
|
|
1344
1441
|
const stat = params.fs.lstat(absolute);
|
|
1345
1442
|
if (stat === void 0) {
|
|
1346
1443
|
return void 0;
|
|
@@ -1382,9 +1479,9 @@ function buildEntryFacts(params) {
|
|
|
1382
1479
|
entries
|
|
1383
1480
|
};
|
|
1384
1481
|
}
|
|
1385
|
-
function listSubtree(
|
|
1386
|
-
const absolute =
|
|
1387
|
-
const stat =
|
|
1482
|
+
function listSubtree(fs7, root, rel) {
|
|
1483
|
+
const absolute = path5.join(root, rel);
|
|
1484
|
+
const stat = fs7.lstat(absolute);
|
|
1388
1485
|
if (stat === void 0) {
|
|
1389
1486
|
return [];
|
|
1390
1487
|
}
|
|
@@ -1392,10 +1489,10 @@ function listSubtree(fs3, root, rel) {
|
|
|
1392
1489
|
return [{ rel, kind: "symlink" }];
|
|
1393
1490
|
}
|
|
1394
1491
|
if (stat.kind === "dir") {
|
|
1395
|
-
const children = [...
|
|
1492
|
+
const children = [...fs7.readdir(absolute)].sort().flatMap((name) => listSubtree(fs7, root, `${rel}/${name}`));
|
|
1396
1493
|
return [{ rel, kind: "dir" }, ...children];
|
|
1397
1494
|
}
|
|
1398
|
-
const contentHash =
|
|
1495
|
+
const contentHash = fs7.hashFile(absolute);
|
|
1399
1496
|
return [{ rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } }];
|
|
1400
1497
|
}
|
|
1401
1498
|
function topLevelScopeRoots(roots) {
|
|
@@ -1409,25 +1506,25 @@ function topLevelScopeRoots(roots) {
|
|
|
1409
1506
|
}
|
|
1410
1507
|
return kept;
|
|
1411
1508
|
}
|
|
1412
|
-
function reconciliationScope(
|
|
1509
|
+
function reconciliationScope(fs7, farmRoot, manifest) {
|
|
1413
1510
|
if (manifest !== void 0) {
|
|
1414
1511
|
return topLevelScopeRoots(manifest.materialised);
|
|
1415
1512
|
}
|
|
1416
|
-
return [...
|
|
1513
|
+
return [...fs7.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs7.lstat(path5.join(farmRoot, name))?.kind === "dir").sort();
|
|
1417
1514
|
}
|
|
1418
|
-
function collectFarmListing(
|
|
1419
|
-
return scopeRoots.flatMap((root) => listSubtree(
|
|
1515
|
+
function collectFarmListing(fs7, farmRoot, scopeRoots) {
|
|
1516
|
+
return scopeRoots.flatMap((root) => listSubtree(fs7, farmRoot, root));
|
|
1420
1517
|
}
|
|
1421
|
-
function collectCanonicalCounterparts(
|
|
1518
|
+
function collectCanonicalCounterparts(fs7, claudeHome, farmListing) {
|
|
1422
1519
|
const out = [];
|
|
1423
1520
|
for (const entry of farmListing) {
|
|
1424
|
-
const absolute =
|
|
1425
|
-
const stat =
|
|
1521
|
+
const absolute = path5.join(claudeHome, entry.rel);
|
|
1522
|
+
const stat = fs7.lstat(absolute);
|
|
1426
1523
|
if (stat === void 0) {
|
|
1427
1524
|
continue;
|
|
1428
1525
|
}
|
|
1429
1526
|
if (stat.kind === "file") {
|
|
1430
|
-
const contentHash =
|
|
1527
|
+
const contentHash = fs7.hashFile(absolute);
|
|
1431
1528
|
out.push({ rel: entry.rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } });
|
|
1432
1529
|
continue;
|
|
1433
1530
|
}
|
|
@@ -1451,21 +1548,21 @@ function carryOver(params) {
|
|
|
1451
1548
|
if (accounted.has(name)) {
|
|
1452
1549
|
continue;
|
|
1453
1550
|
}
|
|
1454
|
-
const stat = params.fs.lstat(
|
|
1551
|
+
const stat = params.fs.lstat(path5.join(params.previousRoot, name));
|
|
1455
1552
|
if (stat === void 0 || stat.kind === "symlink") {
|
|
1456
1553
|
continue;
|
|
1457
1554
|
}
|
|
1458
|
-
if (params.fs.lstat(
|
|
1555
|
+
if (params.fs.lstat(path5.join(params.farmRoot, name)) !== void 0) {
|
|
1459
1556
|
const category = params.classification === void 0 ? void 0 : classifyEntries([name], params.classification).classification.get(name);
|
|
1460
1557
|
if (category === "runtime") {
|
|
1461
|
-
params.fs.removeRecursive(
|
|
1558
|
+
params.fs.removeRecursive(path5.join(params.previousRoot, name));
|
|
1462
1559
|
autoResolved.push(name);
|
|
1463
1560
|
continue;
|
|
1464
1561
|
}
|
|
1465
1562
|
collided.push(name);
|
|
1466
1563
|
continue;
|
|
1467
1564
|
}
|
|
1468
|
-
params.fs.rename(
|
|
1565
|
+
params.fs.rename(path5.join(params.previousRoot, name), path5.join(params.farmRoot, name));
|
|
1469
1566
|
carried.push(name);
|
|
1470
1567
|
}
|
|
1471
1568
|
return { carried, autoResolved, collided };
|
|
@@ -1473,9 +1570,9 @@ function carryOver(params) {
|
|
|
1473
1570
|
function buildScratchTree(params) {
|
|
1474
1571
|
params.fs.mkdirp(params.scratchRoot);
|
|
1475
1572
|
for (const entry of params.plan.entries) {
|
|
1476
|
-
const absolute =
|
|
1573
|
+
const absolute = path5.join(params.scratchRoot, entry.rel);
|
|
1477
1574
|
if (entry.kind === "link") {
|
|
1478
|
-
params.fs.mkdirp(
|
|
1575
|
+
params.fs.mkdirp(path5.dirname(absolute));
|
|
1479
1576
|
params.fs.symlink(entry.target, absolute);
|
|
1480
1577
|
continue;
|
|
1481
1578
|
}
|
|
@@ -1484,14 +1581,14 @@ function buildScratchTree(params) {
|
|
|
1484
1581
|
}
|
|
1485
1582
|
}
|
|
1486
1583
|
params.fs.writeFileUtf8(
|
|
1487
|
-
|
|
1584
|
+
path5.join(params.scratchRoot, FARM_MANIFEST_FILENAME),
|
|
1488
1585
|
`${JSON.stringify(params.manifest, null, 2)}
|
|
1489
1586
|
`
|
|
1490
1587
|
);
|
|
1491
1588
|
}
|
|
1492
1589
|
function swapIn(params) {
|
|
1493
1590
|
if (params.fs.lstat(params.farmRoot) === void 0) {
|
|
1494
|
-
params.fs.mkdirp(
|
|
1591
|
+
params.fs.mkdirp(path5.dirname(params.farmRoot));
|
|
1495
1592
|
params.fs.rename(params.scratchRoot, params.farmRoot);
|
|
1496
1593
|
return { carried: [], autoResolved: [], collided: [] };
|
|
1497
1594
|
}
|
|
@@ -1515,14 +1612,14 @@ function recoverInterruptedSwap(params) {
|
|
|
1515
1612
|
const names = [...params.fs.readdir(params.identitiesDir)].sort();
|
|
1516
1613
|
const removedScratch = [];
|
|
1517
1614
|
for (const name of names.filter((candidate) => candidate.startsWith(scratchPrefix))) {
|
|
1518
|
-
params.fs.removeRecursive(
|
|
1615
|
+
params.fs.removeRecursive(path5.join(params.identitiesDir, name));
|
|
1519
1616
|
removedScratch.push(name);
|
|
1520
1617
|
}
|
|
1521
1618
|
let previous = names.filter((candidate) => candidate.startsWith(previousPrefix));
|
|
1522
1619
|
let restoredFrom;
|
|
1523
1620
|
const first = previous[0];
|
|
1524
1621
|
if (first !== void 0 && params.fs.lstat(params.farmRoot) === void 0) {
|
|
1525
|
-
params.fs.rename(
|
|
1622
|
+
params.fs.rename(path5.join(params.identitiesDir, first), params.farmRoot);
|
|
1526
1623
|
restoredFrom = first;
|
|
1527
1624
|
previous = previous.slice(1);
|
|
1528
1625
|
}
|
|
@@ -1530,7 +1627,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1530
1627
|
const autoResolved = [];
|
|
1531
1628
|
const retained = [];
|
|
1532
1629
|
for (const name of previous) {
|
|
1533
|
-
const previousRoot =
|
|
1630
|
+
const previousRoot = path5.join(params.identitiesDir, name);
|
|
1534
1631
|
const result = carryOver({
|
|
1535
1632
|
fs: params.fs,
|
|
1536
1633
|
previousRoot,
|
|
@@ -1555,7 +1652,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1555
1652
|
};
|
|
1556
1653
|
}
|
|
1557
1654
|
function recoverFarm(params) {
|
|
1558
|
-
const farmRoot =
|
|
1655
|
+
const farmRoot = path5.join(params.identitiesDir, params.identity);
|
|
1559
1656
|
const lock = acquireIdentityLock({
|
|
1560
1657
|
identity: params.identity,
|
|
1561
1658
|
dir: params.identitiesDir,
|
|
@@ -1614,16 +1711,16 @@ function executeReconciliation(params) {
|
|
|
1614
1711
|
});
|
|
1615
1712
|
continue;
|
|
1616
1713
|
}
|
|
1617
|
-
const from =
|
|
1618
|
-
const canonical =
|
|
1714
|
+
const from = path5.join(params.farmRoot, action.rel);
|
|
1715
|
+
const canonical = path5.join(params.claudeHome, action.rel);
|
|
1619
1716
|
if (action.kind === "adopt") {
|
|
1620
|
-
params.fs.mkdirp(
|
|
1717
|
+
params.fs.mkdirp(path5.dirname(canonical));
|
|
1621
1718
|
params.fs.copyRecursive(from, canonical);
|
|
1622
1719
|
adopted.push(action.rel);
|
|
1623
1720
|
continue;
|
|
1624
1721
|
}
|
|
1625
1722
|
const preserved = `${canonical}.farm-conflict-${String(params.nowMs)}`;
|
|
1626
|
-
params.fs.mkdirp(
|
|
1723
|
+
params.fs.mkdirp(path5.dirname(canonical));
|
|
1627
1724
|
params.fs.copyRecursive(from, preserved);
|
|
1628
1725
|
conflicts.push(action.rel);
|
|
1629
1726
|
}
|
|
@@ -1648,7 +1745,7 @@ function farmAlreadyMatches(existing, next) {
|
|
|
1648
1745
|
return existing.identity === next.identity && existing.claudeHome === next.claudeHome && sameStrings(existing.materialised, next.materialised) && sameLinks(existing.links, next.links);
|
|
1649
1746
|
}
|
|
1650
1747
|
function resyncFarm(params) {
|
|
1651
|
-
const farmRoot =
|
|
1748
|
+
const farmRoot = path5.join(params.identitiesDir, params.identity);
|
|
1652
1749
|
const lock = acquireIdentityLock({
|
|
1653
1750
|
identity: params.identity,
|
|
1654
1751
|
dir: params.identitiesDir,
|
|
@@ -1727,8 +1824,8 @@ function resyncFarm(params) {
|
|
|
1727
1824
|
diagnostics
|
|
1728
1825
|
};
|
|
1729
1826
|
}
|
|
1730
|
-
const scratchRoot =
|
|
1731
|
-
const previousRoot =
|
|
1827
|
+
const scratchRoot = path5.join(params.identitiesDir, `.${params.identity}.scratch.${params.uniqueSuffix}`);
|
|
1828
|
+
const previousRoot = path5.join(params.identitiesDir, `.${params.identity}.previous.${params.uniqueSuffix}`);
|
|
1732
1829
|
buildScratchTree({ fs: params.fs, scratchRoot, plan: resolved.farm, manifest });
|
|
1733
1830
|
const swap = swapIn({ fs: params.fs, farmRoot, scratchRoot, previousRoot, classification: params.classification });
|
|
1734
1831
|
if (swap.retainedPrevious !== void 0) {
|
|
@@ -1786,7 +1883,7 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1786
1883
|
|
|
1787
1884
|
// src/frontdoor/connect.ts
|
|
1788
1885
|
import { X509Certificate as X509Certificate2, createPrivateKey as createPrivateKey2 } from "node:crypto";
|
|
1789
|
-
import * as
|
|
1886
|
+
import * as fs2 from "node:fs";
|
|
1790
1887
|
|
|
1791
1888
|
// src/frontdoor/x509.ts
|
|
1792
1889
|
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign, X509Certificate } from "node:crypto";
|
|
@@ -2060,7 +2157,7 @@ function issueLeafCertificate(ca, names, notBefore, notAfter) {
|
|
|
2060
2157
|
}
|
|
2061
2158
|
|
|
2062
2159
|
// src/frontdoor/tap.ts
|
|
2063
|
-
function pumpTapSession(secure, openUpstream, tap) {
|
|
2160
|
+
function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
2064
2161
|
let upstream;
|
|
2065
2162
|
let ended = false;
|
|
2066
2163
|
const end = () => {
|
|
@@ -2092,6 +2189,9 @@ function pumpTapSession(secure, openUpstream, tap) {
|
|
|
2092
2189
|
upstream.destroy();
|
|
2093
2190
|
return;
|
|
2094
2191
|
}
|
|
2192
|
+
if (prefix.length > 0) {
|
|
2193
|
+
upstream.write(prefix);
|
|
2194
|
+
}
|
|
2095
2195
|
if (tap !== void 0) {
|
|
2096
2196
|
secure.on("data", (chunk) => {
|
|
2097
2197
|
tap.onChunk("client-to-server", chunk);
|
|
@@ -2125,8 +2225,11 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2125
2225
|
return password === "" ? void 0 : password;
|
|
2126
2226
|
}
|
|
2127
2227
|
|
|
2228
|
+
// src/frontdoor/route.ts
|
|
2229
|
+
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2230
|
+
|
|
2128
2231
|
// src/frontdoor/state.ts
|
|
2129
|
-
import
|
|
2232
|
+
import path6 from "node:path";
|
|
2130
2233
|
import { z as z3 } from "zod";
|
|
2131
2234
|
var FRONT_DOOR_PROTOCOL = 1;
|
|
2132
2235
|
var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
|
|
@@ -2160,10 +2263,10 @@ var FrontDoorStateSchema = z3.strictObject({
|
|
|
2160
2263
|
/** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
|
|
2161
2264
|
lastError: z3.string().optional()
|
|
2162
2265
|
});
|
|
2163
|
-
function readFrontDoorState(
|
|
2266
|
+
function readFrontDoorState(fs7, stateFile) {
|
|
2164
2267
|
let raw;
|
|
2165
2268
|
try {
|
|
2166
|
-
raw =
|
|
2269
|
+
raw = fs7.readFileUtf8(stateFile);
|
|
2167
2270
|
} catch {
|
|
2168
2271
|
return void 0;
|
|
2169
2272
|
}
|
|
@@ -2177,28 +2280,28 @@ function readFrontDoorState(fs3, stateFile) {
|
|
|
2177
2280
|
return void 0;
|
|
2178
2281
|
}
|
|
2179
2282
|
}
|
|
2180
|
-
function writeFrontDoorState(
|
|
2181
|
-
|
|
2182
|
-
|
|
2283
|
+
function writeFrontDoorState(fs7, stateFile, state) {
|
|
2284
|
+
fs7.mkdirp(path6.dirname(stateFile));
|
|
2285
|
+
fs7.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2183
2286
|
`);
|
|
2184
2287
|
}
|
|
2185
2288
|
function frontDoorOrigin(state) {
|
|
2186
2289
|
const port = state?.directPort ?? state?.lastDirectPort;
|
|
2187
2290
|
return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
|
|
2188
2291
|
}
|
|
2189
|
-
function writeFrontDoorSession(
|
|
2190
|
-
|
|
2191
|
-
|
|
2292
|
+
function writeFrontDoorSession(fs7, sessionsDir, session) {
|
|
2293
|
+
fs7.mkdirPrivate(sessionsDir);
|
|
2294
|
+
fs7.writeFilePrivate(path6.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
2192
2295
|
`);
|
|
2193
2296
|
}
|
|
2194
|
-
function listFrontDoorSessions(
|
|
2297
|
+
function listFrontDoorSessions(fs7, sessionsDir) {
|
|
2195
2298
|
const sessions = [];
|
|
2196
|
-
for (const name of
|
|
2299
|
+
for (const name of fs7.readdir(sessionsDir)) {
|
|
2197
2300
|
if (!name.endsWith(".json")) {
|
|
2198
2301
|
continue;
|
|
2199
2302
|
}
|
|
2200
2303
|
try {
|
|
2201
|
-
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(
|
|
2304
|
+
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs7.readFileUtf8(path6.join(sessionsDir, name)) ?? ""));
|
|
2202
2305
|
if (parsed.success) {
|
|
2203
2306
|
sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
|
|
2204
2307
|
}
|
|
@@ -2208,14 +2311,14 @@ function listFrontDoorSessions(fs3, sessionsDir) {
|
|
|
2208
2311
|
}
|
|
2209
2312
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2210
2313
|
}
|
|
2211
|
-
function removeFrontDoorSession(
|
|
2212
|
-
|
|
2314
|
+
function removeFrontDoorSession(fs7, sessionsDir, pid) {
|
|
2315
|
+
fs7.removeRecursive(path6.join(sessionsDir, `${String(pid)}.json`));
|
|
2213
2316
|
}
|
|
2214
|
-
function pruneDeadFrontDoorSessions(
|
|
2317
|
+
function pruneDeadFrontDoorSessions(fs7, sessionsDir, isRunning) {
|
|
2215
2318
|
const removed = [];
|
|
2216
|
-
for (const session of listFrontDoorSessions(
|
|
2319
|
+
for (const session of listFrontDoorSessions(fs7, sessionsDir)) {
|
|
2217
2320
|
if (!isRunning(session.pid)) {
|
|
2218
|
-
removeFrontDoorSession(
|
|
2321
|
+
removeFrontDoorSession(fs7, sessionsDir, session.pid);
|
|
2219
2322
|
removed.push(session.pid);
|
|
2220
2323
|
}
|
|
2221
2324
|
}
|
|
@@ -2227,8 +2330,8 @@ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
|
2227
2330
|
var FRONTDOOR_POLL_MS = 1e3;
|
|
2228
2331
|
var MS_PER_MINUTE = 6e4;
|
|
2229
2332
|
async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
|
|
2230
|
-
const { fs:
|
|
2231
|
-
const previous = readFrontDoorState(
|
|
2333
|
+
const { fs: fs7, paths } = ports;
|
|
2334
|
+
const previous = readFrontDoorState(fs7, paths.frontdoorStateFile);
|
|
2232
2335
|
let lastPort = previous?.lastPort;
|
|
2233
2336
|
let lastConnectPort = previous?.lastConnectPort;
|
|
2234
2337
|
let lastDirectPort = previous?.lastDirectPort;
|
|
@@ -2238,12 +2341,12 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2238
2341
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2239
2342
|
});
|
|
2240
2343
|
const fail = (message) => {
|
|
2241
|
-
writeFrontDoorState(
|
|
2242
|
-
|
|
2344
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2345
|
+
fs7.removeRecursive(paths.frontdoorLockFile);
|
|
2243
2346
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
2244
2347
|
return 1;
|
|
2245
2348
|
};
|
|
2246
|
-
writeFrontDoorState(
|
|
2349
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
2247
2350
|
const previousSticky = lastPort;
|
|
2248
2351
|
let http2;
|
|
2249
2352
|
try {
|
|
@@ -2280,8 +2383,8 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2280
2383
|
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2281
2384
|
}
|
|
2282
2385
|
lastDirectPort = direct.port;
|
|
2283
|
-
writeFrontDoorState(
|
|
2284
|
-
|
|
2386
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2387
|
+
fs7.removeRecursive(paths.frontdoorLockFile);
|
|
2285
2388
|
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
2286
2389
|
let idleSince;
|
|
2287
2390
|
let ticks = 0;
|
|
@@ -2290,15 +2393,15 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2290
2393
|
return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
|
|
2291
2394
|
}
|
|
2292
2395
|
ticks += 1;
|
|
2293
|
-
pruneDeadFrontDoorSessions(
|
|
2294
|
-
if (listFrontDoorSessions(
|
|
2396
|
+
pruneDeadFrontDoorSessions(fs7, paths.frontdoorSessionsDir, ports.isRunning);
|
|
2397
|
+
if (listFrontDoorSessions(fs7, paths.frontdoorSessionsDir).length === 0) {
|
|
2295
2398
|
idleSince ??= ports.now();
|
|
2296
2399
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2297
2400
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2298
2401
|
await direct.close();
|
|
2299
2402
|
await connect3.close();
|
|
2300
2403
|
await http2.close();
|
|
2301
|
-
writeFrontDoorState(
|
|
2404
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, sticky());
|
|
2302
2405
|
return 0;
|
|
2303
2406
|
}
|
|
2304
2407
|
} else {
|
|
@@ -2353,8 +2456,8 @@ function parseConnectTarget(authority) {
|
|
|
2353
2456
|
function isInterceptedHost(host, interceptHosts) {
|
|
2354
2457
|
return interceptHosts.includes(host);
|
|
2355
2458
|
}
|
|
2356
|
-
function servedByPipeline(
|
|
2357
|
-
return
|
|
2459
|
+
function servedByPipeline(path13) {
|
|
2460
|
+
return path13?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
2358
2461
|
}
|
|
2359
2462
|
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
2360
2463
|
"connection",
|
|
@@ -2422,7 +2525,19 @@ function caParses(ca) {
|
|
|
2422
2525
|
}
|
|
2423
2526
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2424
2527
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2425
|
-
const
|
|
2528
|
+
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2529
|
+
const onUpgrade = (host) => (request3, socket, head) => {
|
|
2530
|
+
config.capture?.upgrade(request3);
|
|
2531
|
+
if (head.length > 0) {
|
|
2532
|
+
socket.pause();
|
|
2533
|
+
socket.unshift(head);
|
|
2534
|
+
}
|
|
2535
|
+
const headText = `${request3.method ?? "GET"} ${request3.url ?? "/"} HTTP/1.1\r
|
|
2536
|
+
${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2537
|
+
`, "")}\r
|
|
2538
|
+
`;
|
|
2539
|
+
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), void 0, Buffer.from(headText, "utf8"));
|
|
2540
|
+
};
|
|
2426
2541
|
const sessions = config.interceptHosts.map((host) => {
|
|
2427
2542
|
if (config.tapHosts.includes(host)) {
|
|
2428
2543
|
const tlsAcceptor2 = effects.createTlsAcceptor(
|
|
@@ -2436,13 +2551,18 @@ async function startConnectServer(config, effects, preferredPort) {
|
|
|
2436
2551
|
}
|
|
2437
2552
|
const handler = (request3, response) => {
|
|
2438
2553
|
if (host === config.routedHost && servedByPipeline(request3.url)) {
|
|
2554
|
+
const tunnelToken = tunnelTokens.get(request3.socket);
|
|
2555
|
+
if (tunnelToken !== void 0 && request3.headers[AUTH_HEADER] === void 0) {
|
|
2556
|
+
request3.headers[AUTH_HEADER] = tunnelToken;
|
|
2557
|
+
}
|
|
2439
2558
|
config.serveRouted(request3, response);
|
|
2440
2559
|
return;
|
|
2441
2560
|
}
|
|
2442
2561
|
effects.forwardHttp(config.upstreamFor(host), request3, response, config.capture?.observePassthrough(request3));
|
|
2443
2562
|
};
|
|
2444
|
-
const httpSession = effects.createHttpSession(handler, onUpgrade);
|
|
2445
|
-
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure) => {
|
|
2563
|
+
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2564
|
+
const tlsAcceptor = effects.createTlsAcceptor(config.leafFor(host), (secure, context) => {
|
|
2565
|
+
tunnelTokens.set(secure, context);
|
|
2446
2566
|
httpSession.serve(secure);
|
|
2447
2567
|
});
|
|
2448
2568
|
return { host, httpSession, tlsAcceptor };
|
|
@@ -2583,6 +2703,7 @@ function routeConnect(socket, head, context) {
|
|
|
2583
2703
|
const credential = proxyAuthorizationOf(head);
|
|
2584
2704
|
const capability = credential === void 0 ? void 0 : capabilityFromProxyAuthorization(credential);
|
|
2585
2705
|
if (capability === void 0 || !config.isLiveCapability(capability)) {
|
|
2706
|
+
console.error(`[upg-dbg] refused CONNECT to ${head.split("\r\n", 1)[0]?.split(" ")[1] ?? "?"} (credential ${credential === void 0 ? "absent" : "present but dead"})`);
|
|
2586
2707
|
refuse(socket, REFUSAL.proxyAuthenticationRequired, [`Proxy-Authenticate: Basic realm="${CONNECT_PROXY_REALM}"`]);
|
|
2587
2708
|
return;
|
|
2588
2709
|
}
|
|
@@ -2602,7 +2723,7 @@ function routeConnect(socket, head, context) {
|
|
|
2602
2723
|
config.capture?.connect(target, session !== void 0);
|
|
2603
2724
|
socket.write(CONNECT_ESTABLISHED);
|
|
2604
2725
|
if (session !== void 0) {
|
|
2605
|
-
session.tlsAcceptor.accept(socket);
|
|
2726
|
+
session.tlsAcceptor.accept(socket, capability);
|
|
2606
2727
|
return;
|
|
2607
2728
|
}
|
|
2608
2729
|
void blindTunnel(socket, target, effects);
|
|
@@ -2634,15 +2755,15 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2634
2755
|
socket.destroy();
|
|
2635
2756
|
});
|
|
2636
2757
|
}
|
|
2637
|
-
function
|
|
2758
|
+
function isEnoent2(error) {
|
|
2638
2759
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2639
2760
|
}
|
|
2640
2761
|
function realConnectCertStore(paths) {
|
|
2641
2762
|
const read = (file) => {
|
|
2642
2763
|
try {
|
|
2643
|
-
return
|
|
2764
|
+
return fs2.readFileSync(file, "utf8");
|
|
2644
2765
|
} catch (error) {
|
|
2645
|
-
if (
|
|
2766
|
+
if (isEnoent2(error)) {
|
|
2646
2767
|
return void 0;
|
|
2647
2768
|
}
|
|
2648
2769
|
throw error;
|
|
@@ -2655,10 +2776,10 @@ function realConnectCertStore(paths) {
|
|
|
2655
2776
|
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2656
2777
|
},
|
|
2657
2778
|
writeCa: (ca) => {
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2779
|
+
fs2.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2780
|
+
fs2.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2781
|
+
fs2.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2782
|
+
fs2.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2662
2783
|
}
|
|
2663
2784
|
};
|
|
2664
2785
|
}
|
|
@@ -2714,10 +2835,11 @@ function realConnectEffects() {
|
|
|
2714
2835
|
return await listenOnce(0, onSocket);
|
|
2715
2836
|
},
|
|
2716
2837
|
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
2838
|
+
let pendingContext = "";
|
|
2717
2839
|
const server = tls.createServer(
|
|
2718
2840
|
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
2719
2841
|
(secure) => {
|
|
2720
|
-
onSecure(secure);
|
|
2842
|
+
onSecure(secure, pendingContext);
|
|
2721
2843
|
}
|
|
2722
2844
|
);
|
|
2723
2845
|
server.on("clientError", (_error, socket) => {
|
|
@@ -2725,7 +2847,8 @@ function realConnectEffects() {
|
|
|
2725
2847
|
});
|
|
2726
2848
|
return {
|
|
2727
2849
|
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
2728
|
-
accept: (socket) => {
|
|
2850
|
+
accept: (socket, context) => {
|
|
2851
|
+
pendingContext = context;
|
|
2729
2852
|
server.emit("connection", socket);
|
|
2730
2853
|
},
|
|
2731
2854
|
close: () => {
|
|
@@ -2739,9 +2862,8 @@ function realConnectEffects() {
|
|
|
2739
2862
|
socket.destroy();
|
|
2740
2863
|
});
|
|
2741
2864
|
if (onUpgrade !== void 0) {
|
|
2742
|
-
server.on("upgrade", (request3, socket) => {
|
|
2743
|
-
onUpgrade(request3);
|
|
2744
|
-
socket.destroy();
|
|
2865
|
+
server.on("upgrade", (request3, socket, head) => {
|
|
2866
|
+
onUpgrade(request3, socket, head);
|
|
2745
2867
|
});
|
|
2746
2868
|
}
|
|
2747
2869
|
return {
|
|
@@ -2826,11 +2948,11 @@ function realConnectEffects() {
|
|
|
2826
2948
|
}
|
|
2827
2949
|
|
|
2828
2950
|
// src/frontdoor/ensure.ts
|
|
2829
|
-
import { randomUUID as
|
|
2951
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2830
2952
|
|
|
2831
2953
|
// src/headroom/state.ts
|
|
2832
2954
|
import { createHash as createHash2 } from "node:crypto";
|
|
2833
|
-
import
|
|
2955
|
+
import path7 from "node:path";
|
|
2834
2956
|
import { z as z4 } from "zod";
|
|
2835
2957
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
2836
2958
|
var HeadroomStateSchema = z4.strictObject({
|
|
@@ -2864,16 +2986,16 @@ function parseJson(raw, schema) {
|
|
|
2864
2986
|
const parsed = schema.safeParse(JSON.parse(raw));
|
|
2865
2987
|
return parsed.success ? parsed.data : void 0;
|
|
2866
2988
|
}
|
|
2867
|
-
function readHeadroomState(
|
|
2989
|
+
function readHeadroomState(fs7, stateFile) {
|
|
2868
2990
|
try {
|
|
2869
|
-
return parseJson(
|
|
2991
|
+
return parseJson(fs7.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
2870
2992
|
} catch {
|
|
2871
2993
|
return void 0;
|
|
2872
2994
|
}
|
|
2873
2995
|
}
|
|
2874
|
-
function writeHeadroomState(
|
|
2875
|
-
|
|
2876
|
-
|
|
2996
|
+
function writeHeadroomState(fs7, stateFile, state) {
|
|
2997
|
+
fs7.mkdirp(path7.dirname(stateFile));
|
|
2998
|
+
fs7.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2877
2999
|
`);
|
|
2878
3000
|
}
|
|
2879
3001
|
function headroomAllowlist(providers) {
|
|
@@ -2893,14 +3015,14 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
2893
3015
|
function hashAllowlist(allowlist) {
|
|
2894
3016
|
return createHash2("sha256").update(allowlist.join("\n")).digest("hex");
|
|
2895
3017
|
}
|
|
2896
|
-
function readAllProviders(
|
|
3018
|
+
function readAllProviders(fs7, providersDir) {
|
|
2897
3019
|
const result = [];
|
|
2898
|
-
for (const name of
|
|
3020
|
+
for (const name of fs7.readdir(providersDir)) {
|
|
2899
3021
|
if (!name.endsWith(".json")) {
|
|
2900
3022
|
continue;
|
|
2901
3023
|
}
|
|
2902
3024
|
try {
|
|
2903
|
-
const parsed = parseJson(
|
|
3025
|
+
const parsed = parseJson(fs7.readFileUtf8(path7.join(providersDir, name)), ProviderSchema);
|
|
2904
3026
|
if (parsed !== void 0) {
|
|
2905
3027
|
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
2906
3028
|
}
|
|
@@ -2910,22 +3032,22 @@ function readAllProviders(fs3, providersDir) {
|
|
|
2910
3032
|
}
|
|
2911
3033
|
return result;
|
|
2912
3034
|
}
|
|
2913
|
-
function writeSession(
|
|
2914
|
-
|
|
2915
|
-
|
|
3035
|
+
function writeSession(fs7, sessionsDir, session) {
|
|
3036
|
+
fs7.mkdirp(sessionsDir);
|
|
3037
|
+
fs7.writeFileUtf8(path7.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
2916
3038
|
`);
|
|
2917
3039
|
}
|
|
2918
|
-
function removeSession(
|
|
2919
|
-
|
|
3040
|
+
function removeSession(fs7, sessionsDir, pid) {
|
|
3041
|
+
fs7.removeRecursive(path7.join(sessionsDir, `${String(pid)}.json`));
|
|
2920
3042
|
}
|
|
2921
|
-
function listSessions(
|
|
3043
|
+
function listSessions(fs7, sessionsDir) {
|
|
2922
3044
|
const sessions = [];
|
|
2923
|
-
for (const name of
|
|
3045
|
+
for (const name of fs7.readdir(sessionsDir)) {
|
|
2924
3046
|
if (!name.endsWith(".json")) {
|
|
2925
3047
|
continue;
|
|
2926
3048
|
}
|
|
2927
3049
|
try {
|
|
2928
|
-
const parsed = parseJson(
|
|
3050
|
+
const parsed = parseJson(fs7.readFileUtf8(path7.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
2929
3051
|
if (parsed !== void 0) {
|
|
2930
3052
|
sessions.push(parsed);
|
|
2931
3053
|
}
|
|
@@ -2935,22 +3057,22 @@ function listSessions(fs3, sessionsDir) {
|
|
|
2935
3057
|
}
|
|
2936
3058
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2937
3059
|
}
|
|
2938
|
-
function sessionsForSupervisor(
|
|
2939
|
-
return listSessions(
|
|
3060
|
+
function sessionsForSupervisor(fs7, sessionsDir, supervisorPid) {
|
|
3061
|
+
return listSessions(fs7, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
2940
3062
|
}
|
|
2941
|
-
function pruneDeadSessions(
|
|
3063
|
+
function pruneDeadSessions(fs7, sessionsDir, isRunning) {
|
|
2942
3064
|
const removed = [];
|
|
2943
|
-
for (const session of listSessions(
|
|
3065
|
+
for (const session of listSessions(fs7, sessionsDir)) {
|
|
2944
3066
|
if (!isRunning(session.pid)) {
|
|
2945
|
-
removeSession(
|
|
3067
|
+
removeSession(fs7, sessionsDir, session.pid);
|
|
2946
3068
|
removed.push(session.pid);
|
|
2947
3069
|
}
|
|
2948
3070
|
}
|
|
2949
3071
|
return removed;
|
|
2950
3072
|
}
|
|
2951
|
-
function readStartLock(
|
|
3073
|
+
function readStartLock(fs7, lockFile) {
|
|
2952
3074
|
try {
|
|
2953
|
-
return parseJson(
|
|
3075
|
+
return parseJson(fs7.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
2954
3076
|
} catch {
|
|
2955
3077
|
return void 0;
|
|
2956
3078
|
}
|
|
@@ -2990,7 +3112,7 @@ function ensureFrontDoor(params) {
|
|
|
2990
3112
|
continue;
|
|
2991
3113
|
}
|
|
2992
3114
|
if (verdict.ok) {
|
|
2993
|
-
const token =
|
|
3115
|
+
const token = randomUUID3();
|
|
2994
3116
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
2995
3117
|
return { port: state.port, connectPort: state.connectPort, token };
|
|
2996
3118
|
}
|
|
@@ -3030,7 +3152,7 @@ function ensureFrontDoor(params) {
|
|
|
3030
3152
|
}
|
|
3031
3153
|
|
|
3032
3154
|
// src/usage/read.ts
|
|
3033
|
-
import
|
|
3155
|
+
import path8 from "node:path";
|
|
3034
3156
|
|
|
3035
3157
|
// src/usage/schema.ts
|
|
3036
3158
|
import { z as z5 } from "zod";
|
|
@@ -3197,11 +3319,11 @@ function snapshotPath(snapshotsDir, identity) {
|
|
|
3197
3319
|
if (!isIdentityName(identity)) {
|
|
3198
3320
|
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3199
3321
|
}
|
|
3200
|
-
return
|
|
3322
|
+
return path8.join(snapshotsDir, `${identity}.json`);
|
|
3201
3323
|
}
|
|
3202
|
-
function readUsageSnapshot(
|
|
3324
|
+
function readUsageSnapshot(fs7, snapshotsDir, identity) {
|
|
3203
3325
|
const file = snapshotPath(snapshotsDir, identity);
|
|
3204
|
-
const raw =
|
|
3326
|
+
const raw = fs7.readFileUtf8(file);
|
|
3205
3327
|
if (raw === void 0) {
|
|
3206
3328
|
return void 0;
|
|
3207
3329
|
}
|
|
@@ -3217,8 +3339,8 @@ function readUsageSnapshot(fs3, snapshotsDir, identity) {
|
|
|
3217
3339
|
}
|
|
3218
3340
|
return snapshot.data;
|
|
3219
3341
|
}
|
|
3220
|
-
function listUsageSnapshots(
|
|
3221
|
-
return
|
|
3342
|
+
function listUsageSnapshots(fs7, snapshotsDir) {
|
|
3343
|
+
return fs7.readdir(snapshotsDir).flatMap((name) => {
|
|
3222
3344
|
if (!name.endsWith(".json")) {
|
|
3223
3345
|
return [];
|
|
3224
3346
|
}
|
|
@@ -3226,7 +3348,7 @@ function listUsageSnapshots(fs3, snapshotsDir) {
|
|
|
3226
3348
|
if (!isIdentityName(identity)) {
|
|
3227
3349
|
return [];
|
|
3228
3350
|
}
|
|
3229
|
-
const snapshot = readUsageSnapshot(
|
|
3351
|
+
const snapshot = readUsageSnapshot(fs7, snapshotsDir, identity);
|
|
3230
3352
|
return snapshot === void 0 ? [] : [snapshot];
|
|
3231
3353
|
}).sort((left, right) => left.identity.localeCompare(right.identity));
|
|
3232
3354
|
}
|
|
@@ -3393,9 +3515,9 @@ function compareVersions(a, b) {
|
|
|
3393
3515
|
}
|
|
3394
3516
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
3395
3517
|
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
3396
|
-
const { fs:
|
|
3518
|
+
const { fs: fs7, paths } = ports;
|
|
3397
3519
|
let config = initialConfig;
|
|
3398
|
-
const previousState = readHeadroomState(
|
|
3520
|
+
const previousState = readHeadroomState(fs7, paths.headroomStateFile);
|
|
3399
3521
|
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3400
3522
|
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3401
3523
|
return 0;
|
|
@@ -3404,19 +3526,19 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3404
3526
|
let lastPort = previousState?.lastPort;
|
|
3405
3527
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3406
3528
|
const ownsState = () => {
|
|
3407
|
-
const owner = readHeadroomState(
|
|
3529
|
+
const owner = readHeadroomState(fs7, paths.headroomStateFile)?.supervisorPid;
|
|
3408
3530
|
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3409
3531
|
};
|
|
3410
3532
|
const writeOwnState = (state) => {
|
|
3411
3533
|
if (!ownsState()) {
|
|
3412
3534
|
return false;
|
|
3413
3535
|
}
|
|
3414
|
-
writeHeadroomState(
|
|
3536
|
+
writeHeadroomState(fs7, paths.headroomStateFile, state);
|
|
3415
3537
|
return true;
|
|
3416
3538
|
};
|
|
3417
3539
|
const fail = (message) => {
|
|
3418
3540
|
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3419
|
-
|
|
3541
|
+
fs7.removeRecursive(paths.headroomLockFile);
|
|
3420
3542
|
}
|
|
3421
3543
|
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
3422
3544
|
return 1;
|
|
@@ -3465,7 +3587,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3465
3587
|
const settingsHash = hashSettings(config.settings);
|
|
3466
3588
|
const allowlist = allowlistOf(ports);
|
|
3467
3589
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3468
|
-
pruneDeadSessions(
|
|
3590
|
+
pruneDeadSessions(fs7, paths.headroomSessionsDir, ports.isRunning);
|
|
3469
3591
|
const ownsNow = ownsState();
|
|
3470
3592
|
if (!ownsNow && !superseded) {
|
|
3471
3593
|
ports.log("agent-shim headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
@@ -3517,7 +3639,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3517
3639
|
...sticky()
|
|
3518
3640
|
});
|
|
3519
3641
|
if (recorded) {
|
|
3520
|
-
|
|
3642
|
+
fs7.removeRecursive(paths.headroomLockFile);
|
|
3521
3643
|
}
|
|
3522
3644
|
idleSince = void 0;
|
|
3523
3645
|
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
@@ -3532,7 +3654,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3532
3654
|
}
|
|
3533
3655
|
} else {
|
|
3534
3656
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3535
|
-
if (sessionsForSupervisor(
|
|
3657
|
+
if (sessionsForSupervisor(fs7, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3536
3658
|
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3537
3659
|
ports.stopProcess(headroomPid);
|
|
3538
3660
|
headroomPid = void 0;
|
|
@@ -3541,7 +3663,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3541
3663
|
continue;
|
|
3542
3664
|
}
|
|
3543
3665
|
}
|
|
3544
|
-
const sessions = sessionsForSupervisor(
|
|
3666
|
+
const sessions = sessionsForSupervisor(fs7, paths.headroomSessionsDir, ports.ownPid);
|
|
3545
3667
|
if (sessions.length === 0) {
|
|
3546
3668
|
idleSince ??= ports.now();
|
|
3547
3669
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3563,9 +3685,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3563
3685
|
await ports.sleep(HEADROOM_POLL_MS);
|
|
3564
3686
|
}
|
|
3565
3687
|
}
|
|
3566
|
-
function currentHeadroomAllowlist(
|
|
3567
|
-
const providers = readAllProviders(
|
|
3568
|
-
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(
|
|
3688
|
+
function currentHeadroomAllowlist(fs7, paths) {
|
|
3689
|
+
const providers = readAllProviders(fs7, paths.providersDir).map((entry) => entry.provider);
|
|
3690
|
+
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs7, paths.frontdoorStateFile))));
|
|
3569
3691
|
}
|
|
3570
3692
|
function allowlistOf(ports) {
|
|
3571
3693
|
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
@@ -3631,63 +3753,735 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3631
3753
|
}
|
|
3632
3754
|
|
|
3633
3755
|
// src/paths.ts
|
|
3634
|
-
import
|
|
3756
|
+
import fs3 from "node:fs";
|
|
3635
3757
|
import os from "node:os";
|
|
3636
|
-
import
|
|
3758
|
+
import path9 from "node:path";
|
|
3637
3759
|
function resolveAgentShimHome(env = process.env, home = os.homedir(), isDirectory = isExistingDirectory) {
|
|
3638
3760
|
const fromEnv = env.AGENT_SHIM_HOME;
|
|
3639
3761
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3640
3762
|
return fromEnv;
|
|
3641
3763
|
}
|
|
3642
|
-
const current =
|
|
3643
|
-
const legacy =
|
|
3764
|
+
const current = path9.join(home, HOME_DIRNAME);
|
|
3765
|
+
const legacy = path9.join(home, LEGACY_HOME_DIRNAME);
|
|
3644
3766
|
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
3645
3767
|
}
|
|
3646
3768
|
function isExistingDirectory(candidate) {
|
|
3647
|
-
return
|
|
3769
|
+
return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
3648
3770
|
}
|
|
3649
3771
|
function resolveClaudeHome() {
|
|
3650
3772
|
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
3651
3773
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3652
3774
|
return fromEnv;
|
|
3653
3775
|
}
|
|
3654
|
-
return
|
|
3776
|
+
return path9.join(os.homedir(), ".claude");
|
|
3655
3777
|
}
|
|
3656
3778
|
function buildLayoutPaths(root) {
|
|
3657
3779
|
return {
|
|
3658
3780
|
root,
|
|
3659
|
-
identitiesDir:
|
|
3660
|
-
configProfilesDir:
|
|
3661
|
-
providersDir:
|
|
3662
|
-
directoryRulesFile:
|
|
3663
|
-
activeIdentityFile:
|
|
3664
|
-
globalConfigFile:
|
|
3665
|
-
categoriesLocalFile:
|
|
3666
|
-
claudeShimFile:
|
|
3667
|
-
headroomDir:
|
|
3668
|
-
headroomStateFile:
|
|
3669
|
-
headroomLockFile:
|
|
3670
|
-
headroomSessionsDir:
|
|
3671
|
-
logsDir:
|
|
3672
|
-
headroomLogPath:
|
|
3673
|
-
frontdoorDir:
|
|
3674
|
-
frontdoorCaDir:
|
|
3675
|
-
frontdoorCaCertFile:
|
|
3676
|
-
frontdoorCaKeyFile:
|
|
3677
|
-
frontdoorCaBundlesDir:
|
|
3678
|
-
frontdoorStateFile:
|
|
3679
|
-
frontdoorLockFile:
|
|
3680
|
-
frontdoorSessionsDir:
|
|
3681
|
-
frontdoorLogPath:
|
|
3682
|
-
usageDir:
|
|
3683
|
-
usageLogDir:
|
|
3684
|
-
usageSnapshotsDir:
|
|
3685
|
-
usagePicksFile:
|
|
3781
|
+
identitiesDir: path9.join(root, "identities"),
|
|
3782
|
+
configProfilesDir: path9.join(root, "config-profiles"),
|
|
3783
|
+
providersDir: path9.join(root, "providers"),
|
|
3784
|
+
directoryRulesFile: path9.join(root, "directory-rules.json"),
|
|
3785
|
+
activeIdentityFile: path9.join(root, "active-identity"),
|
|
3786
|
+
globalConfigFile: path9.join(root, "config.json"),
|
|
3787
|
+
categoriesLocalFile: path9.join(root, "categories.local.json"),
|
|
3788
|
+
claudeShimFile: path9.join(root, "claude-shim.json"),
|
|
3789
|
+
headroomDir: path9.join(root, "headroom"),
|
|
3790
|
+
headroomStateFile: path9.join(root, "headroom", "state.json"),
|
|
3791
|
+
headroomLockFile: path9.join(root, "headroom", "start.lock"),
|
|
3792
|
+
headroomSessionsDir: path9.join(root, "headroom", "sessions"),
|
|
3793
|
+
logsDir: path9.join(root, "logs"),
|
|
3794
|
+
headroomLogPath: path9.join(root, "logs", "headroom.log"),
|
|
3795
|
+
frontdoorDir: path9.join(root, "frontdoor"),
|
|
3796
|
+
frontdoorCaDir: path9.join(root, "frontdoor", "ca"),
|
|
3797
|
+
frontdoorCaCertFile: path9.join(root, "frontdoor", "ca", "ca.pem"),
|
|
3798
|
+
frontdoorCaKeyFile: path9.join(root, "frontdoor", "ca", "ca.key"),
|
|
3799
|
+
frontdoorCaBundlesDir: path9.join(root, "frontdoor", "ca", "bundles"),
|
|
3800
|
+
frontdoorStateFile: path9.join(root, "frontdoor", "state.json"),
|
|
3801
|
+
frontdoorLockFile: path9.join(root, "frontdoor", "start.lock"),
|
|
3802
|
+
frontdoorSessionsDir: path9.join(root, "frontdoor", "sessions"),
|
|
3803
|
+
frontdoorLogPath: path9.join(root, "logs", "frontdoor.log"),
|
|
3804
|
+
usageDir: path9.join(root, "usage"),
|
|
3805
|
+
usageLogDir: path9.join(root, "usage", "log"),
|
|
3806
|
+
usageSnapshotsDir: path9.join(root, "usage", "snapshots"),
|
|
3807
|
+
usagePicksFile: path9.join(root, "usage", "picks.json")
|
|
3686
3808
|
};
|
|
3687
3809
|
}
|
|
3688
3810
|
function resolveLayoutPaths() {
|
|
3689
3811
|
return buildLayoutPaths(resolveAgentShimHome());
|
|
3690
3812
|
}
|
|
3813
|
+
|
|
3814
|
+
// src/identityStore.ts
|
|
3815
|
+
import fs5 from "node:fs";
|
|
3816
|
+
import path11 from "node:path";
|
|
3817
|
+
|
|
3818
|
+
// src/launcher/identity.ts
|
|
3819
|
+
function poolNameOf(selector) {
|
|
3820
|
+
return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
// src/configProfilesStore.ts
|
|
3824
|
+
import fs4 from "node:fs";
|
|
3825
|
+
import path10 from "node:path";
|
|
3826
|
+
var ProfileNotFoundError = class extends CliError {
|
|
3827
|
+
constructor(profileName) {
|
|
3828
|
+
super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
|
|
3829
|
+
this.profileName = profileName;
|
|
3830
|
+
this.name = "ProfileNotFoundError";
|
|
3831
|
+
}
|
|
3832
|
+
profileName;
|
|
3833
|
+
};
|
|
3834
|
+
var ProfileAlreadyExistsError = class extends CliError {
|
|
3835
|
+
constructor(profileName) {
|
|
3836
|
+
super(`A configuration profile named "${profileName}" already exists.`);
|
|
3837
|
+
this.profileName = profileName;
|
|
3838
|
+
this.name = "ProfileAlreadyExistsError";
|
|
3839
|
+
}
|
|
3840
|
+
profileName;
|
|
3841
|
+
};
|
|
3842
|
+
var InvalidCategoryNameError = class extends CliError {
|
|
3843
|
+
constructor(categoryName) {
|
|
3844
|
+
super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
|
|
3845
|
+
this.categoryName = categoryName;
|
|
3846
|
+
this.name = "InvalidCategoryNameError";
|
|
3847
|
+
}
|
|
3848
|
+
categoryName;
|
|
3849
|
+
};
|
|
3850
|
+
function profileJsonPath(paths, name) {
|
|
3851
|
+
return path10.join(paths.configProfilesDir, `${name}.json`);
|
|
3852
|
+
}
|
|
3853
|
+
function profileExists(paths, name) {
|
|
3854
|
+
return fs4.existsSync(profileJsonPath(paths, name));
|
|
3855
|
+
}
|
|
3856
|
+
function requireProfileExists(paths, name) {
|
|
3857
|
+
if (!profileExists(paths, name)) {
|
|
3858
|
+
throw new ProfileNotFoundError(name);
|
|
3859
|
+
}
|
|
3860
|
+
}
|
|
3861
|
+
function readProfile(paths, name) {
|
|
3862
|
+
return readJson(profileJsonPath(paths, name), ConfigProfileSchema);
|
|
3863
|
+
}
|
|
3864
|
+
function createProfile(paths, name, extendsList, description) {
|
|
3865
|
+
if (profileExists(paths, name)) {
|
|
3866
|
+
throw new ProfileAlreadyExistsError(name);
|
|
3867
|
+
}
|
|
3868
|
+
const filePath = profileJsonPath(paths, name);
|
|
3869
|
+
const parsed = ConfigProfileSchema.safeParse({
|
|
3870
|
+
...description === void 0 ? {} : { description },
|
|
3871
|
+
...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
|
|
3872
|
+
});
|
|
3873
|
+
if (!parsed.success) {
|
|
3874
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
3875
|
+
}
|
|
3876
|
+
writeJsonAtomic(filePath, parsed.data);
|
|
3877
|
+
return parsed.data;
|
|
3878
|
+
}
|
|
3879
|
+
function listProfiles(paths) {
|
|
3880
|
+
if (!fs4.existsSync(paths.configProfilesDir)) {
|
|
3881
|
+
return [];
|
|
3882
|
+
}
|
|
3883
|
+
const names = fs4.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
|
|
3884
|
+
const result = [];
|
|
3885
|
+
for (const name of names) {
|
|
3886
|
+
const profile = readProfile(paths, name);
|
|
3887
|
+
if (profile !== void 0) {
|
|
3888
|
+
result.push({ name, profile });
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
return result;
|
|
3892
|
+
}
|
|
3893
|
+
function globalConfigPath(paths) {
|
|
3894
|
+
return paths.globalConfigFile;
|
|
3895
|
+
}
|
|
3896
|
+
function readGlobalConfig(paths) {
|
|
3897
|
+
return readJson(globalConfigPath(paths), GlobalConfigSchema);
|
|
3898
|
+
}
|
|
3899
|
+
function setGlobalDefaultProfile(paths, name) {
|
|
3900
|
+
return applyPatch(
|
|
3901
|
+
globalConfigPath(paths),
|
|
3902
|
+
GlobalConfigSchema,
|
|
3903
|
+
{ defaultConfigProfile: name },
|
|
3904
|
+
{ defaults: {} }
|
|
3905
|
+
);
|
|
3906
|
+
}
|
|
3907
|
+
function validateCategoryNames(patch) {
|
|
3908
|
+
for (const key of Object.keys(patch)) {
|
|
3909
|
+
if (!isOverridableCategory(key)) {
|
|
3910
|
+
throw new InvalidCategoryNameError(key);
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
function setProfileCategories(paths, name, patch) {
|
|
3915
|
+
requireProfileExists(paths, name);
|
|
3916
|
+
const expandedPatch = expandAllCategoryKey(patch);
|
|
3917
|
+
validateCategoryNames(expandedPatch);
|
|
3918
|
+
const existing = readProfile(paths, name) ?? {};
|
|
3919
|
+
const mergedCategories = { ...existing.categories, ...expandedPatch };
|
|
3920
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { categories: mergedCategories });
|
|
3921
|
+
}
|
|
3922
|
+
function setProfileEntries(paths, name, patch) {
|
|
3923
|
+
requireProfileExists(paths, name);
|
|
3924
|
+
const existing = readProfile(paths, name) ?? {};
|
|
3925
|
+
const mergedEntries = { ...existing.entries, ...patch };
|
|
3926
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
|
|
3927
|
+
}
|
|
3928
|
+
function setProfileLaunchFlags(paths, name, patch) {
|
|
3929
|
+
requireProfileExists(paths, name);
|
|
3930
|
+
const existing = readProfile(paths, name) ?? {};
|
|
3931
|
+
const mergedLaunch = { ...existing.launch, ...patch };
|
|
3932
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
3933
|
+
// Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
|
|
3934
|
+
launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
function setProfileMetadata(paths, name, patch) {
|
|
3938
|
+
requireProfileExists(paths, name);
|
|
3939
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
3940
|
+
...patch.extends === void 0 ? {} : { extends: patch.extends.length === 0 ? void 0 : [...patch.extends] },
|
|
3941
|
+
...patch.description === void 0 ? {} : { description: patch.description === false ? void 0 : patch.description }
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
function removeProfile(paths, name) {
|
|
3945
|
+
requireProfileExists(paths, name);
|
|
3946
|
+
fs4.rmSync(profileJsonPath(paths, name));
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
// src/poolStore.ts
|
|
3950
|
+
var PoolNotFoundError = class extends CliError {
|
|
3951
|
+
constructor(poolName) {
|
|
3952
|
+
super(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
3953
|
+
this.poolName = poolName;
|
|
3954
|
+
this.name = "PoolNotFoundError";
|
|
3955
|
+
}
|
|
3956
|
+
poolName;
|
|
3957
|
+
};
|
|
3958
|
+
var PoolAlreadyExistsError = class extends CliError {
|
|
3959
|
+
constructor(poolName) {
|
|
3960
|
+
super(`A pool named "${poolName}" already exists. Use \`agent-shim pool set ${poolName}\` to change its members.`);
|
|
3961
|
+
this.poolName = poolName;
|
|
3962
|
+
this.name = "PoolAlreadyExistsError";
|
|
3963
|
+
}
|
|
3964
|
+
poolName;
|
|
3965
|
+
};
|
|
3966
|
+
var InvalidPoolNameError = class extends CliError {
|
|
3967
|
+
constructor(attemptedName) {
|
|
3968
|
+
super(`"${attemptedName}" is not a valid pool name: it must start with a letter or number and may then contain letters, numbers, dots, hyphens and underscores.`);
|
|
3969
|
+
this.attemptedName = attemptedName;
|
|
3970
|
+
this.name = "InvalidPoolNameError";
|
|
3971
|
+
}
|
|
3972
|
+
attemptedName;
|
|
3973
|
+
};
|
|
3974
|
+
function readPools(paths) {
|
|
3975
|
+
return readGlobalConfig(paths)?.pools ?? {};
|
|
3976
|
+
}
|
|
3977
|
+
function requirePool(paths, name) {
|
|
3978
|
+
const pool = readPools(paths)[name];
|
|
3979
|
+
if (pool === void 0) {
|
|
3980
|
+
throw new PoolNotFoundError(name);
|
|
3981
|
+
}
|
|
3982
|
+
return pool;
|
|
3983
|
+
}
|
|
3984
|
+
function writePools(paths, pools) {
|
|
3985
|
+
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
3986
|
+
}
|
|
3987
|
+
function addPool(paths, name, identities) {
|
|
3988
|
+
if (!PoolNameSchema.safeParse(name).success) {
|
|
3989
|
+
throw new InvalidPoolNameError(name);
|
|
3990
|
+
}
|
|
3991
|
+
const pools = readPools(paths);
|
|
3992
|
+
if (pools[name] !== void 0) {
|
|
3993
|
+
throw new PoolAlreadyExistsError(name);
|
|
3994
|
+
}
|
|
3995
|
+
const pool = { identities: [...identities] };
|
|
3996
|
+
writePools(paths, { ...pools, [name]: pool });
|
|
3997
|
+
return pool;
|
|
3998
|
+
}
|
|
3999
|
+
function setPool(paths, name, identities) {
|
|
4000
|
+
requirePool(paths, name);
|
|
4001
|
+
const pool = { identities: [...identities] };
|
|
4002
|
+
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
4003
|
+
return pool;
|
|
4004
|
+
}
|
|
4005
|
+
function removePool(paths, name) {
|
|
4006
|
+
requirePool(paths, name);
|
|
4007
|
+
const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
|
|
4008
|
+
writePools(paths, remaining);
|
|
4009
|
+
}
|
|
4010
|
+
|
|
4011
|
+
// src/identityStore.ts
|
|
4012
|
+
var IdentityNotFoundError = class extends CliError {
|
|
4013
|
+
constructor(name) {
|
|
4014
|
+
super(`No identity named "${name}". Run \`agent-shim identity add ${name}\` first.`);
|
|
4015
|
+
this.name = name;
|
|
4016
|
+
this.name = "IdentityNotFoundError";
|
|
4017
|
+
}
|
|
4018
|
+
name;
|
|
4019
|
+
};
|
|
4020
|
+
var IdentityAlreadyExistsError = class extends CliError {
|
|
4021
|
+
constructor(identityName) {
|
|
4022
|
+
super(`An identity named "${identityName}" already exists.`);
|
|
4023
|
+
this.identityName = identityName;
|
|
4024
|
+
this.name = "IdentityAlreadyExistsError";
|
|
4025
|
+
}
|
|
4026
|
+
identityName;
|
|
4027
|
+
};
|
|
4028
|
+
var InvalidIdentityNameError = class extends CliError {
|
|
4029
|
+
constructor(attemptedName) {
|
|
4030
|
+
super(
|
|
4031
|
+
`"${attemptedName}" is not a valid identity name \u2014 identity names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
|
|
4032
|
+
);
|
|
4033
|
+
this.attemptedName = attemptedName;
|
|
4034
|
+
this.name = "InvalidIdentityNameError";
|
|
4035
|
+
}
|
|
4036
|
+
attemptedName;
|
|
4037
|
+
};
|
|
4038
|
+
function identityJsonPath(paths, name) {
|
|
4039
|
+
return path11.join(paths.identitiesDir, name, "identity.json");
|
|
4040
|
+
}
|
|
4041
|
+
function identityExists(paths, name) {
|
|
4042
|
+
return fs5.existsSync(identityJsonPath(paths, name));
|
|
4043
|
+
}
|
|
4044
|
+
function readIdentity(paths, name) {
|
|
4045
|
+
return readJson(identityJsonPath(paths, name), IdentitySchema);
|
|
4046
|
+
}
|
|
4047
|
+
function addIdentity(paths, name) {
|
|
4048
|
+
if (identityExists(paths, name)) {
|
|
4049
|
+
throw new IdentityAlreadyExistsError(name);
|
|
4050
|
+
}
|
|
4051
|
+
const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
|
|
4052
|
+
if (!parsed.success) {
|
|
4053
|
+
throw new InvalidIdentityNameError(name);
|
|
4054
|
+
}
|
|
4055
|
+
writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
|
|
4056
|
+
return parsed.data;
|
|
4057
|
+
}
|
|
4058
|
+
function useIdentity(paths, name) {
|
|
4059
|
+
const pool = poolNameOf(name);
|
|
4060
|
+
if (pool !== void 0) {
|
|
4061
|
+
requirePool(paths, pool);
|
|
4062
|
+
} else if (!identityExists(paths, name)) {
|
|
4063
|
+
throw new IdentityNotFoundError(name);
|
|
4064
|
+
}
|
|
4065
|
+
writeTextAtomic(paths.activeIdentityFile, `${name}
|
|
4066
|
+
`);
|
|
4067
|
+
}
|
|
4068
|
+
function readActiveIdentity(paths) {
|
|
4069
|
+
if (!fs5.existsSync(paths.activeIdentityFile)) {
|
|
4070
|
+
return void 0;
|
|
4071
|
+
}
|
|
4072
|
+
const raw = fs5.readFileSync(paths.activeIdentityFile, "utf8").trim();
|
|
4073
|
+
return raw === "" ? void 0 : raw;
|
|
4074
|
+
}
|
|
4075
|
+
function isIdentityDirectoryName(name) {
|
|
4076
|
+
return !name.startsWith(".");
|
|
4077
|
+
}
|
|
4078
|
+
function readIdentityForListing(paths, name) {
|
|
4079
|
+
try {
|
|
4080
|
+
return readIdentity(paths, name);
|
|
4081
|
+
} catch (error) {
|
|
4082
|
+
if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
|
|
4083
|
+
return { problem: error.message.replace(/\s*\n\s*/g, " ") };
|
|
4084
|
+
}
|
|
4085
|
+
throw error;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
function listIdentities(paths) {
|
|
4089
|
+
if (!fs5.existsSync(paths.identitiesDir)) {
|
|
4090
|
+
return [];
|
|
4091
|
+
}
|
|
4092
|
+
const active = readActiveIdentity(paths);
|
|
4093
|
+
const names = fs5.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
|
|
4094
|
+
const result = [];
|
|
4095
|
+
for (const name of names) {
|
|
4096
|
+
const read = readIdentityForListing(paths, name);
|
|
4097
|
+
if (read === void 0) {
|
|
4098
|
+
continue;
|
|
4099
|
+
}
|
|
4100
|
+
const isActive = name === active;
|
|
4101
|
+
result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
|
|
4102
|
+
}
|
|
4103
|
+
return result;
|
|
4104
|
+
}
|
|
4105
|
+
function setDefaultConfigProfile(paths, identityName, profileName) {
|
|
4106
|
+
if (!identityExists(paths, identityName)) {
|
|
4107
|
+
throw new IdentityNotFoundError(identityName);
|
|
4108
|
+
}
|
|
4109
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4110
|
+
defaultConfigProfile: profileName
|
|
4111
|
+
});
|
|
4112
|
+
}
|
|
4113
|
+
function setAllowAmbientCredential(paths, identityName, allow) {
|
|
4114
|
+
if (!identityExists(paths, identityName)) {
|
|
4115
|
+
throw new IdentityNotFoundError(identityName);
|
|
4116
|
+
}
|
|
4117
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4118
|
+
allowAmbientCredential: allow
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
function setIdentityCredential(paths, identityName, change) {
|
|
4122
|
+
const existing = readIdentity(paths, identityName);
|
|
4123
|
+
if (existing === void 0) {
|
|
4124
|
+
throw new IdentityNotFoundError(identityName);
|
|
4125
|
+
}
|
|
4126
|
+
if (change === false) {
|
|
4127
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { credential: void 0 });
|
|
4128
|
+
}
|
|
4129
|
+
const sources = change.sources ?? existing.credential?.sources;
|
|
4130
|
+
if (sources === void 0) {
|
|
4131
|
+
throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
|
|
4132
|
+
}
|
|
4133
|
+
const target = change.target ?? existing.credential?.target;
|
|
4134
|
+
const cache = change.cache === void 0 ? existing.credential?.cache : change.cache === false ? void 0 : change.cache;
|
|
4135
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4136
|
+
credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
|
|
4137
|
+
});
|
|
4138
|
+
}
|
|
4139
|
+
function belongsToIdentity(entry, name) {
|
|
4140
|
+
return entry === name || entry === path11.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
|
|
4141
|
+
}
|
|
4142
|
+
function removeIdentity(paths, name) {
|
|
4143
|
+
if (!identityExists(paths, name)) {
|
|
4144
|
+
throw new IdentityNotFoundError(name);
|
|
4145
|
+
}
|
|
4146
|
+
for (const entry of fs5.readdirSync(paths.identitiesDir)) {
|
|
4147
|
+
if (belongsToIdentity(entry, name)) {
|
|
4148
|
+
fs5.rmSync(path11.join(paths.identitiesDir, entry), { recursive: true, force: true });
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
if (readActiveIdentity(paths) === name) {
|
|
4152
|
+
fs5.rmSync(paths.activeIdentityFile);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
// src/providersStore.ts
|
|
4157
|
+
import fs6 from "node:fs";
|
|
4158
|
+
import path12 from "node:path";
|
|
4159
|
+
|
|
4160
|
+
// src/credentialCache.ts
|
|
4161
|
+
import { z as z7 } from "zod";
|
|
4162
|
+
var CachedCredentialSchema = z7.strictObject({
|
|
4163
|
+
token: z7.string().min(1),
|
|
4164
|
+
fetchedAt: z7.number().int().nonnegative(),
|
|
4165
|
+
source: CredentialSourceSchema
|
|
4166
|
+
});
|
|
4167
|
+
|
|
4168
|
+
// src/providersStore.ts
|
|
4169
|
+
var ProviderNotFoundError = class extends CliError {
|
|
4170
|
+
constructor(name) {
|
|
4171
|
+
super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
|
|
4172
|
+
this.name = name;
|
|
4173
|
+
this.name = "ProviderNotFoundError";
|
|
4174
|
+
}
|
|
4175
|
+
name;
|
|
4176
|
+
};
|
|
4177
|
+
var ProviderAlreadyExistsError = class extends CliError {
|
|
4178
|
+
constructor(providerName) {
|
|
4179
|
+
super(`A provider named "${providerName}" already exists.`);
|
|
4180
|
+
this.providerName = providerName;
|
|
4181
|
+
this.name = "ProviderAlreadyExistsError";
|
|
4182
|
+
}
|
|
4183
|
+
providerName;
|
|
4184
|
+
};
|
|
4185
|
+
var InvalidProviderNameError = class extends CliError {
|
|
4186
|
+
constructor(attemptedName) {
|
|
4187
|
+
super(
|
|
4188
|
+
`"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
|
|
4189
|
+
);
|
|
4190
|
+
this.attemptedName = attemptedName;
|
|
4191
|
+
this.name = "InvalidProviderNameError";
|
|
4192
|
+
}
|
|
4193
|
+
attemptedName;
|
|
4194
|
+
};
|
|
4195
|
+
var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
|
|
4196
|
+
function providerJsonPath(paths, name) {
|
|
4197
|
+
return path12.join(paths.providersDir, `${name}.json`);
|
|
4198
|
+
}
|
|
4199
|
+
function providerExists(paths, name) {
|
|
4200
|
+
return fs6.existsSync(providerJsonPath(paths, name));
|
|
4201
|
+
}
|
|
4202
|
+
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
4203
|
+
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
4204
|
+
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
4205
|
+
function isRecord(value) {
|
|
4206
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4207
|
+
}
|
|
4208
|
+
function legacyProviderConversion(raw) {
|
|
4209
|
+
if (!isRecord(raw) || "credential" in raw) {
|
|
4210
|
+
return void 0;
|
|
4211
|
+
}
|
|
4212
|
+
const env = isRecord(raw.env) ? raw.env : void 0;
|
|
4213
|
+
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
4214
|
+
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
4215
|
+
if (fields.length === 0) {
|
|
4216
|
+
return void 0;
|
|
4217
|
+
}
|
|
4218
|
+
const sources = [];
|
|
4219
|
+
if (typeof raw.tokenEnv === "string") {
|
|
4220
|
+
sources.push({ env: raw.tokenEnv });
|
|
4221
|
+
}
|
|
4222
|
+
if (Array.isArray(raw.tokenCommand)) {
|
|
4223
|
+
sources.push({ command: raw.tokenCommand });
|
|
4224
|
+
}
|
|
4225
|
+
if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
|
|
4226
|
+
sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
|
|
4227
|
+
}
|
|
4228
|
+
const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
|
|
4229
|
+
const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
|
|
4230
|
+
const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
|
|
4231
|
+
return {
|
|
4232
|
+
fields,
|
|
4233
|
+
replacement: {
|
|
4234
|
+
...rest,
|
|
4235
|
+
credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
|
|
4236
|
+
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
var LegacyProviderFileError = class extends CliError {
|
|
4241
|
+
constructor(filePath, conversion) {
|
|
4242
|
+
super(
|
|
4243
|
+
`${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
|
|
4244
|
+
` + JSON.stringify(conversion.replacement, null, 2)
|
|
4245
|
+
);
|
|
4246
|
+
this.filePath = filePath;
|
|
4247
|
+
this.conversion = conversion;
|
|
4248
|
+
this.name = "LegacyProviderFileError";
|
|
4249
|
+
}
|
|
4250
|
+
filePath;
|
|
4251
|
+
conversion;
|
|
4252
|
+
};
|
|
4253
|
+
function parseProviderFile(filePath, raw) {
|
|
4254
|
+
const conversion = legacyProviderConversion(raw);
|
|
4255
|
+
if (conversion !== void 0) {
|
|
4256
|
+
throw new LegacyProviderFileError(filePath, conversion);
|
|
4257
|
+
}
|
|
4258
|
+
const parsed = ProviderSchema.safeParse(raw);
|
|
4259
|
+
if (!parsed.success) {
|
|
4260
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
4261
|
+
}
|
|
4262
|
+
return parsed.data;
|
|
4263
|
+
}
|
|
4264
|
+
function readProvider(paths, name) {
|
|
4265
|
+
const filePath = providerJsonPath(paths, name);
|
|
4266
|
+
if (!fs6.existsSync(filePath)) {
|
|
4267
|
+
return void 0;
|
|
4268
|
+
}
|
|
4269
|
+
const raw = JSON.parse(fs6.readFileSync(filePath, "utf8"));
|
|
4270
|
+
return parseProviderFile(filePath, raw);
|
|
4271
|
+
}
|
|
4272
|
+
function listProviders(paths) {
|
|
4273
|
+
const names = listProviderNames(
|
|
4274
|
+
(dir) => fs6.readdirSync(dir),
|
|
4275
|
+
paths.providersDir
|
|
4276
|
+
);
|
|
4277
|
+
const result = [];
|
|
4278
|
+
for (const name of names) {
|
|
4279
|
+
let provider;
|
|
4280
|
+
try {
|
|
4281
|
+
provider = readProvider(paths, name);
|
|
4282
|
+
} catch (error) {
|
|
4283
|
+
if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
|
|
4284
|
+
throw error;
|
|
4285
|
+
}
|
|
4286
|
+
console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
|
|
4287
|
+
continue;
|
|
4288
|
+
}
|
|
4289
|
+
if (provider !== void 0) {
|
|
4290
|
+
result.push({ name, provider });
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
return result;
|
|
4294
|
+
}
|
|
4295
|
+
function listProviderNames(readdir, providersDir) {
|
|
4296
|
+
let entries;
|
|
4297
|
+
try {
|
|
4298
|
+
entries = readdir(providersDir);
|
|
4299
|
+
} catch {
|
|
4300
|
+
return [];
|
|
4301
|
+
}
|
|
4302
|
+
return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
|
|
4303
|
+
}
|
|
4304
|
+
function describeProviderEndpoint(provider) {
|
|
4305
|
+
return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
|
|
4306
|
+
}
|
|
4307
|
+
var ProviderKindMismatchError = class extends UsageError {
|
|
4308
|
+
constructor(message) {
|
|
4309
|
+
super(message);
|
|
4310
|
+
this.name = "ProviderKindMismatchError";
|
|
4311
|
+
}
|
|
4312
|
+
};
|
|
4313
|
+
function isEmptyCodexConfig(config) {
|
|
4314
|
+
return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
|
|
4315
|
+
}
|
|
4316
|
+
function addProvider(paths, name, input) {
|
|
4317
|
+
if (providerExists(paths, name)) {
|
|
4318
|
+
throw new ProviderAlreadyExistsError(name);
|
|
4319
|
+
}
|
|
4320
|
+
if (!PROVIDER_NAME_RE.test(name)) {
|
|
4321
|
+
throw new InvalidProviderNameError(name);
|
|
4322
|
+
}
|
|
4323
|
+
const kind = input.kind ?? "http";
|
|
4324
|
+
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
4325
|
+
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
4326
|
+
}
|
|
4327
|
+
if (kind === "http" && input.baseUrl === void 0) {
|
|
4328
|
+
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
4329
|
+
}
|
|
4330
|
+
if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
|
|
4331
|
+
throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
|
|
4332
|
+
}
|
|
4333
|
+
const shared = {
|
|
4334
|
+
displayName: input.displayName,
|
|
4335
|
+
credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
|
|
4336
|
+
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
4337
|
+
};
|
|
4338
|
+
const parsed = ProviderSchema.safeParse(
|
|
4339
|
+
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
4340
|
+
);
|
|
4341
|
+
if (!parsed.success) {
|
|
4342
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
4343
|
+
}
|
|
4344
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
4345
|
+
return parsed.data;
|
|
4346
|
+
}
|
|
4347
|
+
function updateProvider(paths, name, input) {
|
|
4348
|
+
const existing = readProvider(paths, name);
|
|
4349
|
+
if (existing === void 0) {
|
|
4350
|
+
throw new ProviderNotFoundError(name);
|
|
4351
|
+
}
|
|
4352
|
+
const unset = new Set(input.unsetEnv);
|
|
4353
|
+
const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
|
|
4354
|
+
const target = input.target ?? existing.credential.target;
|
|
4355
|
+
const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
|
|
4356
|
+
if (isCodexProvider(existing) && input.baseUrl !== void 0) {
|
|
4357
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
|
|
4358
|
+
}
|
|
4359
|
+
if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
|
|
4360
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
|
|
4361
|
+
}
|
|
4362
|
+
const shared = {
|
|
4363
|
+
...input.displayName === void 0 ? {} : { displayName: input.displayName },
|
|
4364
|
+
credential: {
|
|
4365
|
+
sources: input.sources ?? existing.credential.sources,
|
|
4366
|
+
...target === void 0 ? {} : { target },
|
|
4367
|
+
...cache === void 0 ? {} : { cache }
|
|
4368
|
+
},
|
|
4369
|
+
env: Object.keys(env).length === 0 ? void 0 : env
|
|
4370
|
+
};
|
|
4371
|
+
let candidate;
|
|
4372
|
+
if (isCodexProvider(existing)) {
|
|
4373
|
+
const codex = {
|
|
4374
|
+
...existing.codex,
|
|
4375
|
+
...input.codex,
|
|
4376
|
+
...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
|
|
4377
|
+
};
|
|
4378
|
+
candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
|
|
4379
|
+
} else {
|
|
4380
|
+
candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
|
|
4381
|
+
}
|
|
4382
|
+
const parsed = ProviderSchema.safeParse(candidate);
|
|
4383
|
+
if (!parsed.success) {
|
|
4384
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
4385
|
+
}
|
|
4386
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
4387
|
+
return parsed.data;
|
|
4388
|
+
}
|
|
4389
|
+
function removeProvider(paths, name) {
|
|
4390
|
+
if (!providerExists(paths, name)) {
|
|
4391
|
+
throw new ProviderNotFoundError(name);
|
|
4392
|
+
}
|
|
4393
|
+
fs6.rmSync(providerJsonPath(paths, name));
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
// src/directoryRulesStore.ts
|
|
4397
|
+
var DirectoryRuleNotFoundError = class extends CliError {
|
|
4398
|
+
constructor(rulePath) {
|
|
4399
|
+
super(`No directory rule found for path "${rulePath}".`);
|
|
4400
|
+
this.rulePath = rulePath;
|
|
4401
|
+
this.name = "DirectoryRuleNotFoundError";
|
|
4402
|
+
}
|
|
4403
|
+
rulePath;
|
|
4404
|
+
};
|
|
4405
|
+
var DirectoryRuleMissingTargetError = class extends CliError {
|
|
4406
|
+
constructor() {
|
|
4407
|
+
super("A directory rule must set at least one of --config-profile or --identity.");
|
|
4408
|
+
this.name = "DirectoryRuleMissingTargetError";
|
|
4409
|
+
}
|
|
4410
|
+
};
|
|
4411
|
+
var DirectoryRuleAlreadyExistsError = class extends CliError {
|
|
4412
|
+
constructor(rulePath) {
|
|
4413
|
+
super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
|
|
4414
|
+
this.rulePath = rulePath;
|
|
4415
|
+
this.name = "DirectoryRuleAlreadyExistsError";
|
|
4416
|
+
}
|
|
4417
|
+
rulePath;
|
|
4418
|
+
};
|
|
4419
|
+
function readDirectoryRules(paths) {
|
|
4420
|
+
return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
|
|
4421
|
+
}
|
|
4422
|
+
function writeDirectoryRules(paths, rules) {
|
|
4423
|
+
const parsed = DirectoryRulesSchema.safeParse(rules);
|
|
4424
|
+
if (!parsed.success) {
|
|
4425
|
+
throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
|
|
4426
|
+
}
|
|
4427
|
+
writeJsonAtomic(paths.directoryRulesFile, parsed.data);
|
|
4428
|
+
}
|
|
4429
|
+
function listDirectoryRules(paths) {
|
|
4430
|
+
return readDirectoryRules(paths).rules;
|
|
4431
|
+
}
|
|
4432
|
+
function addDirectoryRule(paths, rulePath, options) {
|
|
4433
|
+
if (options.configProfile === void 0 && options.identity === void 0) {
|
|
4434
|
+
throw new DirectoryRuleMissingTargetError();
|
|
4435
|
+
}
|
|
4436
|
+
const current = readDirectoryRules(paths);
|
|
4437
|
+
if (current.rules.some((rule) => rule.path === rulePath)) {
|
|
4438
|
+
throw new DirectoryRuleAlreadyExistsError(rulePath);
|
|
4439
|
+
}
|
|
4440
|
+
const created = buildNewRule(rulePath, options);
|
|
4441
|
+
writeDirectoryRules(paths, { ...current, rules: [...current.rules, created] });
|
|
4442
|
+
return created;
|
|
4443
|
+
}
|
|
4444
|
+
function ruleHasEffect(rule) {
|
|
4445
|
+
return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
|
|
4446
|
+
}
|
|
4447
|
+
function updateDirectoryRule(paths, rulePath, options) {
|
|
4448
|
+
const current = readDirectoryRules(paths);
|
|
4449
|
+
const index = current.rules.findIndex((rule) => rule.path === rulePath);
|
|
4450
|
+
const existing = index === -1 ? void 0 : current.rules[index];
|
|
4451
|
+
if (existing === void 0) {
|
|
4452
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
4453
|
+
}
|
|
4454
|
+
const { configProfile, identity, ...rest } = existing;
|
|
4455
|
+
const nextConfigProfile = options.configProfile === void 0 ? configProfile : options.configProfile === false ? void 0 : options.configProfile;
|
|
4456
|
+
const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
|
|
4457
|
+
const updated = {
|
|
4458
|
+
...rest,
|
|
4459
|
+
...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
|
|
4460
|
+
...nextIdentity === void 0 ? {} : { identity: nextIdentity }
|
|
4461
|
+
};
|
|
4462
|
+
if (!ruleHasEffect(updated)) {
|
|
4463
|
+
throw new DirectoryRuleMissingTargetError();
|
|
4464
|
+
}
|
|
4465
|
+
const nextRules = [...current.rules];
|
|
4466
|
+
nextRules[index] = updated;
|
|
4467
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
4468
|
+
return updated;
|
|
4469
|
+
}
|
|
4470
|
+
function buildNewRule(rulePath, options) {
|
|
4471
|
+
return {
|
|
4472
|
+
path: rulePath,
|
|
4473
|
+
...options.configProfile !== void 0 ? { configProfile: options.configProfile } : {},
|
|
4474
|
+
...options.identity !== void 0 ? { identity: options.identity } : {}
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
function removeDirectoryRule(paths, rulePath) {
|
|
4478
|
+
const current = readDirectoryRules(paths);
|
|
4479
|
+
const nextRules = current.rules.filter((rule) => rule.path !== rulePath);
|
|
4480
|
+
if (nextRules.length === current.rules.length) {
|
|
4481
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
4482
|
+
}
|
|
4483
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
4484
|
+
}
|
|
3691
4485
|
export {
|
|
3692
4486
|
AMBIENT_CREDENTIAL_VARS,
|
|
3693
4487
|
CONNECT_INTERCEPT_HOST,
|
|
@@ -3701,6 +4495,9 @@ export {
|
|
|
3701
4495
|
CredentialCacheSchema,
|
|
3702
4496
|
CredentialSchema,
|
|
3703
4497
|
CredentialSourceSchema,
|
|
4498
|
+
DirectoryRuleAlreadyExistsError,
|
|
4499
|
+
DirectoryRuleMissingTargetError,
|
|
4500
|
+
DirectoryRuleNotFoundError,
|
|
3704
4501
|
DirectoryRuleSchema,
|
|
3705
4502
|
DirectoryRulesSchema,
|
|
3706
4503
|
EXIT_FAILURE,
|
|
@@ -3710,20 +4507,38 @@ export {
|
|
|
3710
4507
|
FrontDoorStartError,
|
|
3711
4508
|
GlobalConfigSchema,
|
|
3712
4509
|
HeadroomStartError,
|
|
4510
|
+
IdentityAlreadyExistsError,
|
|
4511
|
+
IdentityNotFoundError,
|
|
3713
4512
|
IdentitySchema,
|
|
4513
|
+
InvalidCategoryNameError,
|
|
4514
|
+
InvalidIdentityNameError,
|
|
4515
|
+
InvalidProviderNameError,
|
|
4516
|
+
LegacyProviderFileError,
|
|
3714
4517
|
PoolNameSchema,
|
|
4518
|
+
PoolNotFoundError,
|
|
3715
4519
|
PoolSchema,
|
|
3716
4520
|
PortableConfigSchema,
|
|
4521
|
+
ProfileAlreadyExistsError,
|
|
4522
|
+
ProfileNotFoundError,
|
|
4523
|
+
ProviderAlreadyExistsError,
|
|
4524
|
+
ProviderKindMismatchError,
|
|
4525
|
+
ProviderNotFoundError,
|
|
3717
4526
|
ProviderSchema,
|
|
3718
4527
|
ROUTED_PATH_PREFIX,
|
|
3719
4528
|
UsageError,
|
|
3720
4529
|
UsageSnapshotError,
|
|
3721
4530
|
UsageSnapshotSchema,
|
|
3722
4531
|
WhenSchema,
|
|
4532
|
+
addDirectoryRule,
|
|
4533
|
+
addIdentity,
|
|
4534
|
+
addPool,
|
|
4535
|
+
addProvider,
|
|
3723
4536
|
buildEntryFacts,
|
|
3724
4537
|
buildLayoutPaths,
|
|
3725
4538
|
carryOver,
|
|
3726
4539
|
createLeafCache,
|
|
4540
|
+
createProfile,
|
|
4541
|
+
describeProviderEndpoint,
|
|
3727
4542
|
detectAmbientCredential,
|
|
3728
4543
|
ensureCa,
|
|
3729
4544
|
ensureFrontDoor,
|
|
@@ -3732,16 +4547,37 @@ export {
|
|
|
3732
4547
|
formatAmbientCredentialGuardMessage,
|
|
3733
4548
|
forwardableHeaders,
|
|
3734
4549
|
generateCa,
|
|
4550
|
+
identityExists,
|
|
4551
|
+
isIdentityDirectoryName,
|
|
3735
4552
|
isInterceptedHost,
|
|
4553
|
+
listDirectoryRules,
|
|
4554
|
+
listIdentities,
|
|
4555
|
+
listProfiles,
|
|
4556
|
+
listProviders,
|
|
3736
4557
|
listUsageSnapshots,
|
|
3737
4558
|
mintLeaf,
|
|
3738
4559
|
parseConnectTarget,
|
|
4560
|
+
profileExists,
|
|
4561
|
+
providerExists,
|
|
4562
|
+
readActiveIdentity,
|
|
4563
|
+
readDirectoryRules,
|
|
3739
4564
|
readFarmManifest,
|
|
4565
|
+
readGlobalConfig,
|
|
4566
|
+
readIdentity,
|
|
4567
|
+
readPools,
|
|
4568
|
+
readProfile,
|
|
4569
|
+
readProvider,
|
|
3740
4570
|
readUsageSnapshot,
|
|
3741
4571
|
realConnectCertStore,
|
|
3742
4572
|
realConnectEffects,
|
|
3743
4573
|
recoverFarm,
|
|
3744
4574
|
recoveryDiagnostics,
|
|
4575
|
+
removeDirectoryRule,
|
|
4576
|
+
removeIdentity,
|
|
4577
|
+
removePool,
|
|
4578
|
+
removeProfile,
|
|
4579
|
+
removeProvider,
|
|
4580
|
+
requirePool,
|
|
3745
4581
|
resolveAgentShimHome,
|
|
3746
4582
|
resolveClaudeHome,
|
|
3747
4583
|
resolveDecisions,
|
|
@@ -3751,7 +4587,20 @@ export {
|
|
|
3751
4587
|
runFrontDoorSupervisor,
|
|
3752
4588
|
runSupervisor,
|
|
3753
4589
|
servedByPipeline,
|
|
4590
|
+
setAllowAmbientCredential,
|
|
4591
|
+
setDefaultConfigProfile,
|
|
4592
|
+
setGlobalDefaultProfile,
|
|
4593
|
+
setIdentityCredential,
|
|
4594
|
+
setPool,
|
|
4595
|
+
setProfileCategories,
|
|
4596
|
+
setProfileEntries,
|
|
4597
|
+
setProfileLaunchFlags,
|
|
4598
|
+
setProfileMetadata,
|
|
3754
4599
|
snapshotPath,
|
|
3755
4600
|
startConnectServer,
|
|
3756
|
-
topLevelNames
|
|
4601
|
+
topLevelNames,
|
|
4602
|
+
updateDirectoryRule,
|
|
4603
|
+
updateProvider,
|
|
4604
|
+
useIdentity,
|
|
4605
|
+
writeDirectoryRules
|
|
3757
4606
|
};
|