agent-shim 7.7.0 → 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 +3682 -3677
- package/dist/index.cjs +1052 -173
- package/dist/index.mjs +982 -158
- 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/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";
|
|
@@ -2132,7 +2229,7 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2132
2229
|
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2133
2230
|
|
|
2134
2231
|
// src/frontdoor/state.ts
|
|
2135
|
-
import
|
|
2232
|
+
import path6 from "node:path";
|
|
2136
2233
|
import { z as z3 } from "zod";
|
|
2137
2234
|
var FRONT_DOOR_PROTOCOL = 1;
|
|
2138
2235
|
var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
|
|
@@ -2166,10 +2263,10 @@ var FrontDoorStateSchema = z3.strictObject({
|
|
|
2166
2263
|
/** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
|
|
2167
2264
|
lastError: z3.string().optional()
|
|
2168
2265
|
});
|
|
2169
|
-
function readFrontDoorState(
|
|
2266
|
+
function readFrontDoorState(fs7, stateFile) {
|
|
2170
2267
|
let raw;
|
|
2171
2268
|
try {
|
|
2172
|
-
raw =
|
|
2269
|
+
raw = fs7.readFileUtf8(stateFile);
|
|
2173
2270
|
} catch {
|
|
2174
2271
|
return void 0;
|
|
2175
2272
|
}
|
|
@@ -2183,28 +2280,28 @@ function readFrontDoorState(fs3, stateFile) {
|
|
|
2183
2280
|
return void 0;
|
|
2184
2281
|
}
|
|
2185
2282
|
}
|
|
2186
|
-
function writeFrontDoorState(
|
|
2187
|
-
|
|
2188
|
-
|
|
2283
|
+
function writeFrontDoorState(fs7, stateFile, state) {
|
|
2284
|
+
fs7.mkdirp(path6.dirname(stateFile));
|
|
2285
|
+
fs7.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2189
2286
|
`);
|
|
2190
2287
|
}
|
|
2191
2288
|
function frontDoorOrigin(state) {
|
|
2192
2289
|
const port = state?.directPort ?? state?.lastDirectPort;
|
|
2193
2290
|
return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
|
|
2194
2291
|
}
|
|
2195
|
-
function writeFrontDoorSession(
|
|
2196
|
-
|
|
2197
|
-
|
|
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)}
|
|
2198
2295
|
`);
|
|
2199
2296
|
}
|
|
2200
|
-
function listFrontDoorSessions(
|
|
2297
|
+
function listFrontDoorSessions(fs7, sessionsDir) {
|
|
2201
2298
|
const sessions = [];
|
|
2202
|
-
for (const name of
|
|
2299
|
+
for (const name of fs7.readdir(sessionsDir)) {
|
|
2203
2300
|
if (!name.endsWith(".json")) {
|
|
2204
2301
|
continue;
|
|
2205
2302
|
}
|
|
2206
2303
|
try {
|
|
2207
|
-
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(
|
|
2304
|
+
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs7.readFileUtf8(path6.join(sessionsDir, name)) ?? ""));
|
|
2208
2305
|
if (parsed.success) {
|
|
2209
2306
|
sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
|
|
2210
2307
|
}
|
|
@@ -2214,14 +2311,14 @@ function listFrontDoorSessions(fs3, sessionsDir) {
|
|
|
2214
2311
|
}
|
|
2215
2312
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2216
2313
|
}
|
|
2217
|
-
function removeFrontDoorSession(
|
|
2218
|
-
|
|
2314
|
+
function removeFrontDoorSession(fs7, sessionsDir, pid) {
|
|
2315
|
+
fs7.removeRecursive(path6.join(sessionsDir, `${String(pid)}.json`));
|
|
2219
2316
|
}
|
|
2220
|
-
function pruneDeadFrontDoorSessions(
|
|
2317
|
+
function pruneDeadFrontDoorSessions(fs7, sessionsDir, isRunning) {
|
|
2221
2318
|
const removed = [];
|
|
2222
|
-
for (const session of listFrontDoorSessions(
|
|
2319
|
+
for (const session of listFrontDoorSessions(fs7, sessionsDir)) {
|
|
2223
2320
|
if (!isRunning(session.pid)) {
|
|
2224
|
-
removeFrontDoorSession(
|
|
2321
|
+
removeFrontDoorSession(fs7, sessionsDir, session.pid);
|
|
2225
2322
|
removed.push(session.pid);
|
|
2226
2323
|
}
|
|
2227
2324
|
}
|
|
@@ -2233,8 +2330,8 @@ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
|
2233
2330
|
var FRONTDOOR_POLL_MS = 1e3;
|
|
2234
2331
|
var MS_PER_MINUTE = 6e4;
|
|
2235
2332
|
async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
|
|
2236
|
-
const { fs:
|
|
2237
|
-
const previous = readFrontDoorState(
|
|
2333
|
+
const { fs: fs7, paths } = ports;
|
|
2334
|
+
const previous = readFrontDoorState(fs7, paths.frontdoorStateFile);
|
|
2238
2335
|
let lastPort = previous?.lastPort;
|
|
2239
2336
|
let lastConnectPort = previous?.lastConnectPort;
|
|
2240
2337
|
let lastDirectPort = previous?.lastDirectPort;
|
|
@@ -2244,12 +2341,12 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2244
2341
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2245
2342
|
});
|
|
2246
2343
|
const fail = (message) => {
|
|
2247
|
-
writeFrontDoorState(
|
|
2248
|
-
|
|
2344
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2345
|
+
fs7.removeRecursive(paths.frontdoorLockFile);
|
|
2249
2346
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
2250
2347
|
return 1;
|
|
2251
2348
|
};
|
|
2252
|
-
writeFrontDoorState(
|
|
2349
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
2253
2350
|
const previousSticky = lastPort;
|
|
2254
2351
|
let http2;
|
|
2255
2352
|
try {
|
|
@@ -2286,8 +2383,8 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2286
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)`);
|
|
2287
2384
|
}
|
|
2288
2385
|
lastDirectPort = direct.port;
|
|
2289
|
-
writeFrontDoorState(
|
|
2290
|
-
|
|
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);
|
|
2291
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)}`);
|
|
2292
2389
|
let idleSince;
|
|
2293
2390
|
let ticks = 0;
|
|
@@ -2296,15 +2393,15 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2296
2393
|
return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
|
|
2297
2394
|
}
|
|
2298
2395
|
ticks += 1;
|
|
2299
|
-
pruneDeadFrontDoorSessions(
|
|
2300
|
-
if (listFrontDoorSessions(
|
|
2396
|
+
pruneDeadFrontDoorSessions(fs7, paths.frontdoorSessionsDir, ports.isRunning);
|
|
2397
|
+
if (listFrontDoorSessions(fs7, paths.frontdoorSessionsDir).length === 0) {
|
|
2301
2398
|
idleSince ??= ports.now();
|
|
2302
2399
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2303
2400
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2304
2401
|
await direct.close();
|
|
2305
2402
|
await connect3.close();
|
|
2306
2403
|
await http2.close();
|
|
2307
|
-
writeFrontDoorState(
|
|
2404
|
+
writeFrontDoorState(fs7, paths.frontdoorStateFile, sticky());
|
|
2308
2405
|
return 0;
|
|
2309
2406
|
}
|
|
2310
2407
|
} else {
|
|
@@ -2359,8 +2456,8 @@ function parseConnectTarget(authority) {
|
|
|
2359
2456
|
function isInterceptedHost(host, interceptHosts) {
|
|
2360
2457
|
return interceptHosts.includes(host);
|
|
2361
2458
|
}
|
|
2362
|
-
function servedByPipeline(
|
|
2363
|
-
return
|
|
2459
|
+
function servedByPipeline(path13) {
|
|
2460
|
+
return path13?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
2364
2461
|
}
|
|
2365
2462
|
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
2366
2463
|
"connection",
|
|
@@ -2658,15 +2755,15 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2658
2755
|
socket.destroy();
|
|
2659
2756
|
});
|
|
2660
2757
|
}
|
|
2661
|
-
function
|
|
2758
|
+
function isEnoent2(error) {
|
|
2662
2759
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2663
2760
|
}
|
|
2664
2761
|
function realConnectCertStore(paths) {
|
|
2665
2762
|
const read = (file) => {
|
|
2666
2763
|
try {
|
|
2667
|
-
return
|
|
2764
|
+
return fs2.readFileSync(file, "utf8");
|
|
2668
2765
|
} catch (error) {
|
|
2669
|
-
if (
|
|
2766
|
+
if (isEnoent2(error)) {
|
|
2670
2767
|
return void 0;
|
|
2671
2768
|
}
|
|
2672
2769
|
throw error;
|
|
@@ -2679,10 +2776,10 @@ function realConnectCertStore(paths) {
|
|
|
2679
2776
|
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2680
2777
|
},
|
|
2681
2778
|
writeCa: (ca) => {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
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);
|
|
2686
2783
|
}
|
|
2687
2784
|
};
|
|
2688
2785
|
}
|
|
@@ -2851,11 +2948,11 @@ function realConnectEffects() {
|
|
|
2851
2948
|
}
|
|
2852
2949
|
|
|
2853
2950
|
// src/frontdoor/ensure.ts
|
|
2854
|
-
import { randomUUID as
|
|
2951
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2855
2952
|
|
|
2856
2953
|
// src/headroom/state.ts
|
|
2857
2954
|
import { createHash as createHash2 } from "node:crypto";
|
|
2858
|
-
import
|
|
2955
|
+
import path7 from "node:path";
|
|
2859
2956
|
import { z as z4 } from "zod";
|
|
2860
2957
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
2861
2958
|
var HeadroomStateSchema = z4.strictObject({
|
|
@@ -2889,16 +2986,16 @@ function parseJson(raw, schema) {
|
|
|
2889
2986
|
const parsed = schema.safeParse(JSON.parse(raw));
|
|
2890
2987
|
return parsed.success ? parsed.data : void 0;
|
|
2891
2988
|
}
|
|
2892
|
-
function readHeadroomState(
|
|
2989
|
+
function readHeadroomState(fs7, stateFile) {
|
|
2893
2990
|
try {
|
|
2894
|
-
return parseJson(
|
|
2991
|
+
return parseJson(fs7.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
2895
2992
|
} catch {
|
|
2896
2993
|
return void 0;
|
|
2897
2994
|
}
|
|
2898
2995
|
}
|
|
2899
|
-
function writeHeadroomState(
|
|
2900
|
-
|
|
2901
|
-
|
|
2996
|
+
function writeHeadroomState(fs7, stateFile, state) {
|
|
2997
|
+
fs7.mkdirp(path7.dirname(stateFile));
|
|
2998
|
+
fs7.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2902
2999
|
`);
|
|
2903
3000
|
}
|
|
2904
3001
|
function headroomAllowlist(providers) {
|
|
@@ -2918,14 +3015,14 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
2918
3015
|
function hashAllowlist(allowlist) {
|
|
2919
3016
|
return createHash2("sha256").update(allowlist.join("\n")).digest("hex");
|
|
2920
3017
|
}
|
|
2921
|
-
function readAllProviders(
|
|
3018
|
+
function readAllProviders(fs7, providersDir) {
|
|
2922
3019
|
const result = [];
|
|
2923
|
-
for (const name of
|
|
3020
|
+
for (const name of fs7.readdir(providersDir)) {
|
|
2924
3021
|
if (!name.endsWith(".json")) {
|
|
2925
3022
|
continue;
|
|
2926
3023
|
}
|
|
2927
3024
|
try {
|
|
2928
|
-
const parsed = parseJson(
|
|
3025
|
+
const parsed = parseJson(fs7.readFileUtf8(path7.join(providersDir, name)), ProviderSchema);
|
|
2929
3026
|
if (parsed !== void 0) {
|
|
2930
3027
|
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
2931
3028
|
}
|
|
@@ -2935,22 +3032,22 @@ function readAllProviders(fs3, providersDir) {
|
|
|
2935
3032
|
}
|
|
2936
3033
|
return result;
|
|
2937
3034
|
}
|
|
2938
|
-
function writeSession(
|
|
2939
|
-
|
|
2940
|
-
|
|
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)}
|
|
2941
3038
|
`);
|
|
2942
3039
|
}
|
|
2943
|
-
function removeSession(
|
|
2944
|
-
|
|
3040
|
+
function removeSession(fs7, sessionsDir, pid) {
|
|
3041
|
+
fs7.removeRecursive(path7.join(sessionsDir, `${String(pid)}.json`));
|
|
2945
3042
|
}
|
|
2946
|
-
function listSessions(
|
|
3043
|
+
function listSessions(fs7, sessionsDir) {
|
|
2947
3044
|
const sessions = [];
|
|
2948
|
-
for (const name of
|
|
3045
|
+
for (const name of fs7.readdir(sessionsDir)) {
|
|
2949
3046
|
if (!name.endsWith(".json")) {
|
|
2950
3047
|
continue;
|
|
2951
3048
|
}
|
|
2952
3049
|
try {
|
|
2953
|
-
const parsed = parseJson(
|
|
3050
|
+
const parsed = parseJson(fs7.readFileUtf8(path7.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
2954
3051
|
if (parsed !== void 0) {
|
|
2955
3052
|
sessions.push(parsed);
|
|
2956
3053
|
}
|
|
@@ -2960,22 +3057,22 @@ function listSessions(fs3, sessionsDir) {
|
|
|
2960
3057
|
}
|
|
2961
3058
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2962
3059
|
}
|
|
2963
|
-
function sessionsForSupervisor(
|
|
2964
|
-
return listSessions(
|
|
3060
|
+
function sessionsForSupervisor(fs7, sessionsDir, supervisorPid) {
|
|
3061
|
+
return listSessions(fs7, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
2965
3062
|
}
|
|
2966
|
-
function pruneDeadSessions(
|
|
3063
|
+
function pruneDeadSessions(fs7, sessionsDir, isRunning) {
|
|
2967
3064
|
const removed = [];
|
|
2968
|
-
for (const session of listSessions(
|
|
3065
|
+
for (const session of listSessions(fs7, sessionsDir)) {
|
|
2969
3066
|
if (!isRunning(session.pid)) {
|
|
2970
|
-
removeSession(
|
|
3067
|
+
removeSession(fs7, sessionsDir, session.pid);
|
|
2971
3068
|
removed.push(session.pid);
|
|
2972
3069
|
}
|
|
2973
3070
|
}
|
|
2974
3071
|
return removed;
|
|
2975
3072
|
}
|
|
2976
|
-
function readStartLock(
|
|
3073
|
+
function readStartLock(fs7, lockFile) {
|
|
2977
3074
|
try {
|
|
2978
|
-
return parseJson(
|
|
3075
|
+
return parseJson(fs7.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
2979
3076
|
} catch {
|
|
2980
3077
|
return void 0;
|
|
2981
3078
|
}
|
|
@@ -3015,7 +3112,7 @@ function ensureFrontDoor(params) {
|
|
|
3015
3112
|
continue;
|
|
3016
3113
|
}
|
|
3017
3114
|
if (verdict.ok) {
|
|
3018
|
-
const token =
|
|
3115
|
+
const token = randomUUID3();
|
|
3019
3116
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
3020
3117
|
return { port: state.port, connectPort: state.connectPort, token };
|
|
3021
3118
|
}
|
|
@@ -3055,7 +3152,7 @@ function ensureFrontDoor(params) {
|
|
|
3055
3152
|
}
|
|
3056
3153
|
|
|
3057
3154
|
// src/usage/read.ts
|
|
3058
|
-
import
|
|
3155
|
+
import path8 from "node:path";
|
|
3059
3156
|
|
|
3060
3157
|
// src/usage/schema.ts
|
|
3061
3158
|
import { z as z5 } from "zod";
|
|
@@ -3222,11 +3319,11 @@ function snapshotPath(snapshotsDir, identity) {
|
|
|
3222
3319
|
if (!isIdentityName(identity)) {
|
|
3223
3320
|
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3224
3321
|
}
|
|
3225
|
-
return
|
|
3322
|
+
return path8.join(snapshotsDir, `${identity}.json`);
|
|
3226
3323
|
}
|
|
3227
|
-
function readUsageSnapshot(
|
|
3324
|
+
function readUsageSnapshot(fs7, snapshotsDir, identity) {
|
|
3228
3325
|
const file = snapshotPath(snapshotsDir, identity);
|
|
3229
|
-
const raw =
|
|
3326
|
+
const raw = fs7.readFileUtf8(file);
|
|
3230
3327
|
if (raw === void 0) {
|
|
3231
3328
|
return void 0;
|
|
3232
3329
|
}
|
|
@@ -3242,8 +3339,8 @@ function readUsageSnapshot(fs3, snapshotsDir, identity) {
|
|
|
3242
3339
|
}
|
|
3243
3340
|
return snapshot.data;
|
|
3244
3341
|
}
|
|
3245
|
-
function listUsageSnapshots(
|
|
3246
|
-
return
|
|
3342
|
+
function listUsageSnapshots(fs7, snapshotsDir) {
|
|
3343
|
+
return fs7.readdir(snapshotsDir).flatMap((name) => {
|
|
3247
3344
|
if (!name.endsWith(".json")) {
|
|
3248
3345
|
return [];
|
|
3249
3346
|
}
|
|
@@ -3251,7 +3348,7 @@ function listUsageSnapshots(fs3, snapshotsDir) {
|
|
|
3251
3348
|
if (!isIdentityName(identity)) {
|
|
3252
3349
|
return [];
|
|
3253
3350
|
}
|
|
3254
|
-
const snapshot = readUsageSnapshot(
|
|
3351
|
+
const snapshot = readUsageSnapshot(fs7, snapshotsDir, identity);
|
|
3255
3352
|
return snapshot === void 0 ? [] : [snapshot];
|
|
3256
3353
|
}).sort((left, right) => left.identity.localeCompare(right.identity));
|
|
3257
3354
|
}
|
|
@@ -3418,9 +3515,9 @@ function compareVersions(a, b) {
|
|
|
3418
3515
|
}
|
|
3419
3516
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
3420
3517
|
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
3421
|
-
const { fs:
|
|
3518
|
+
const { fs: fs7, paths } = ports;
|
|
3422
3519
|
let config = initialConfig;
|
|
3423
|
-
const previousState = readHeadroomState(
|
|
3520
|
+
const previousState = readHeadroomState(fs7, paths.headroomStateFile);
|
|
3424
3521
|
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3425
3522
|
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3426
3523
|
return 0;
|
|
@@ -3429,19 +3526,19 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3429
3526
|
let lastPort = previousState?.lastPort;
|
|
3430
3527
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3431
3528
|
const ownsState = () => {
|
|
3432
|
-
const owner = readHeadroomState(
|
|
3529
|
+
const owner = readHeadroomState(fs7, paths.headroomStateFile)?.supervisorPid;
|
|
3433
3530
|
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3434
3531
|
};
|
|
3435
3532
|
const writeOwnState = (state) => {
|
|
3436
3533
|
if (!ownsState()) {
|
|
3437
3534
|
return false;
|
|
3438
3535
|
}
|
|
3439
|
-
writeHeadroomState(
|
|
3536
|
+
writeHeadroomState(fs7, paths.headroomStateFile, state);
|
|
3440
3537
|
return true;
|
|
3441
3538
|
};
|
|
3442
3539
|
const fail = (message) => {
|
|
3443
3540
|
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3444
|
-
|
|
3541
|
+
fs7.removeRecursive(paths.headroomLockFile);
|
|
3445
3542
|
}
|
|
3446
3543
|
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
3447
3544
|
return 1;
|
|
@@ -3490,7 +3587,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3490
3587
|
const settingsHash = hashSettings(config.settings);
|
|
3491
3588
|
const allowlist = allowlistOf(ports);
|
|
3492
3589
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3493
|
-
pruneDeadSessions(
|
|
3590
|
+
pruneDeadSessions(fs7, paths.headroomSessionsDir, ports.isRunning);
|
|
3494
3591
|
const ownsNow = ownsState();
|
|
3495
3592
|
if (!ownsNow && !superseded) {
|
|
3496
3593
|
ports.log("agent-shim headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
@@ -3542,7 +3639,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3542
3639
|
...sticky()
|
|
3543
3640
|
});
|
|
3544
3641
|
if (recorded) {
|
|
3545
|
-
|
|
3642
|
+
fs7.removeRecursive(paths.headroomLockFile);
|
|
3546
3643
|
}
|
|
3547
3644
|
idleSince = void 0;
|
|
3548
3645
|
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
@@ -3557,7 +3654,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3557
3654
|
}
|
|
3558
3655
|
} else {
|
|
3559
3656
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3560
|
-
if (sessionsForSupervisor(
|
|
3657
|
+
if (sessionsForSupervisor(fs7, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3561
3658
|
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3562
3659
|
ports.stopProcess(headroomPid);
|
|
3563
3660
|
headroomPid = void 0;
|
|
@@ -3566,7 +3663,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3566
3663
|
continue;
|
|
3567
3664
|
}
|
|
3568
3665
|
}
|
|
3569
|
-
const sessions = sessionsForSupervisor(
|
|
3666
|
+
const sessions = sessionsForSupervisor(fs7, paths.headroomSessionsDir, ports.ownPid);
|
|
3570
3667
|
if (sessions.length === 0) {
|
|
3571
3668
|
idleSince ??= ports.now();
|
|
3572
3669
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3588,9 +3685,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3588
3685
|
await ports.sleep(HEADROOM_POLL_MS);
|
|
3589
3686
|
}
|
|
3590
3687
|
}
|
|
3591
|
-
function currentHeadroomAllowlist(
|
|
3592
|
-
const providers = readAllProviders(
|
|
3593
|
-
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))));
|
|
3594
3691
|
}
|
|
3595
3692
|
function allowlistOf(ports) {
|
|
3596
3693
|
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
@@ -3656,63 +3753,735 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3656
3753
|
}
|
|
3657
3754
|
|
|
3658
3755
|
// src/paths.ts
|
|
3659
|
-
import
|
|
3756
|
+
import fs3 from "node:fs";
|
|
3660
3757
|
import os from "node:os";
|
|
3661
|
-
import
|
|
3758
|
+
import path9 from "node:path";
|
|
3662
3759
|
function resolveAgentShimHome(env = process.env, home = os.homedir(), isDirectory = isExistingDirectory) {
|
|
3663
3760
|
const fromEnv = env.AGENT_SHIM_HOME;
|
|
3664
3761
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3665
3762
|
return fromEnv;
|
|
3666
3763
|
}
|
|
3667
|
-
const current =
|
|
3668
|
-
const legacy =
|
|
3764
|
+
const current = path9.join(home, HOME_DIRNAME);
|
|
3765
|
+
const legacy = path9.join(home, LEGACY_HOME_DIRNAME);
|
|
3669
3766
|
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
3670
3767
|
}
|
|
3671
3768
|
function isExistingDirectory(candidate) {
|
|
3672
|
-
return
|
|
3769
|
+
return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
3673
3770
|
}
|
|
3674
3771
|
function resolveClaudeHome() {
|
|
3675
3772
|
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
3676
3773
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3677
3774
|
return fromEnv;
|
|
3678
3775
|
}
|
|
3679
|
-
return
|
|
3776
|
+
return path9.join(os.homedir(), ".claude");
|
|
3680
3777
|
}
|
|
3681
3778
|
function buildLayoutPaths(root) {
|
|
3682
3779
|
return {
|
|
3683
3780
|
root,
|
|
3684
|
-
identitiesDir:
|
|
3685
|
-
configProfilesDir:
|
|
3686
|
-
providersDir:
|
|
3687
|
-
directoryRulesFile:
|
|
3688
|
-
activeIdentityFile:
|
|
3689
|
-
globalConfigFile:
|
|
3690
|
-
categoriesLocalFile:
|
|
3691
|
-
claudeShimFile:
|
|
3692
|
-
headroomDir:
|
|
3693
|
-
headroomStateFile:
|
|
3694
|
-
headroomLockFile:
|
|
3695
|
-
headroomSessionsDir:
|
|
3696
|
-
logsDir:
|
|
3697
|
-
headroomLogPath:
|
|
3698
|
-
frontdoorDir:
|
|
3699
|
-
frontdoorCaDir:
|
|
3700
|
-
frontdoorCaCertFile:
|
|
3701
|
-
frontdoorCaKeyFile:
|
|
3702
|
-
frontdoorCaBundlesDir:
|
|
3703
|
-
frontdoorStateFile:
|
|
3704
|
-
frontdoorLockFile:
|
|
3705
|
-
frontdoorSessionsDir:
|
|
3706
|
-
frontdoorLogPath:
|
|
3707
|
-
usageDir:
|
|
3708
|
-
usageLogDir:
|
|
3709
|
-
usageSnapshotsDir:
|
|
3710
|
-
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")
|
|
3711
3808
|
};
|
|
3712
3809
|
}
|
|
3713
3810
|
function resolveLayoutPaths() {
|
|
3714
3811
|
return buildLayoutPaths(resolveAgentShimHome());
|
|
3715
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
|
+
}
|
|
3716
4485
|
export {
|
|
3717
4486
|
AMBIENT_CREDENTIAL_VARS,
|
|
3718
4487
|
CONNECT_INTERCEPT_HOST,
|
|
@@ -3726,6 +4495,9 @@ export {
|
|
|
3726
4495
|
CredentialCacheSchema,
|
|
3727
4496
|
CredentialSchema,
|
|
3728
4497
|
CredentialSourceSchema,
|
|
4498
|
+
DirectoryRuleAlreadyExistsError,
|
|
4499
|
+
DirectoryRuleMissingTargetError,
|
|
4500
|
+
DirectoryRuleNotFoundError,
|
|
3729
4501
|
DirectoryRuleSchema,
|
|
3730
4502
|
DirectoryRulesSchema,
|
|
3731
4503
|
EXIT_FAILURE,
|
|
@@ -3735,20 +4507,38 @@ export {
|
|
|
3735
4507
|
FrontDoorStartError,
|
|
3736
4508
|
GlobalConfigSchema,
|
|
3737
4509
|
HeadroomStartError,
|
|
4510
|
+
IdentityAlreadyExistsError,
|
|
4511
|
+
IdentityNotFoundError,
|
|
3738
4512
|
IdentitySchema,
|
|
4513
|
+
InvalidCategoryNameError,
|
|
4514
|
+
InvalidIdentityNameError,
|
|
4515
|
+
InvalidProviderNameError,
|
|
4516
|
+
LegacyProviderFileError,
|
|
3739
4517
|
PoolNameSchema,
|
|
4518
|
+
PoolNotFoundError,
|
|
3740
4519
|
PoolSchema,
|
|
3741
4520
|
PortableConfigSchema,
|
|
4521
|
+
ProfileAlreadyExistsError,
|
|
4522
|
+
ProfileNotFoundError,
|
|
4523
|
+
ProviderAlreadyExistsError,
|
|
4524
|
+
ProviderKindMismatchError,
|
|
4525
|
+
ProviderNotFoundError,
|
|
3742
4526
|
ProviderSchema,
|
|
3743
4527
|
ROUTED_PATH_PREFIX,
|
|
3744
4528
|
UsageError,
|
|
3745
4529
|
UsageSnapshotError,
|
|
3746
4530
|
UsageSnapshotSchema,
|
|
3747
4531
|
WhenSchema,
|
|
4532
|
+
addDirectoryRule,
|
|
4533
|
+
addIdentity,
|
|
4534
|
+
addPool,
|
|
4535
|
+
addProvider,
|
|
3748
4536
|
buildEntryFacts,
|
|
3749
4537
|
buildLayoutPaths,
|
|
3750
4538
|
carryOver,
|
|
3751
4539
|
createLeafCache,
|
|
4540
|
+
createProfile,
|
|
4541
|
+
describeProviderEndpoint,
|
|
3752
4542
|
detectAmbientCredential,
|
|
3753
4543
|
ensureCa,
|
|
3754
4544
|
ensureFrontDoor,
|
|
@@ -3757,16 +4547,37 @@ export {
|
|
|
3757
4547
|
formatAmbientCredentialGuardMessage,
|
|
3758
4548
|
forwardableHeaders,
|
|
3759
4549
|
generateCa,
|
|
4550
|
+
identityExists,
|
|
4551
|
+
isIdentityDirectoryName,
|
|
3760
4552
|
isInterceptedHost,
|
|
4553
|
+
listDirectoryRules,
|
|
4554
|
+
listIdentities,
|
|
4555
|
+
listProfiles,
|
|
4556
|
+
listProviders,
|
|
3761
4557
|
listUsageSnapshots,
|
|
3762
4558
|
mintLeaf,
|
|
3763
4559
|
parseConnectTarget,
|
|
4560
|
+
profileExists,
|
|
4561
|
+
providerExists,
|
|
4562
|
+
readActiveIdentity,
|
|
4563
|
+
readDirectoryRules,
|
|
3764
4564
|
readFarmManifest,
|
|
4565
|
+
readGlobalConfig,
|
|
4566
|
+
readIdentity,
|
|
4567
|
+
readPools,
|
|
4568
|
+
readProfile,
|
|
4569
|
+
readProvider,
|
|
3765
4570
|
readUsageSnapshot,
|
|
3766
4571
|
realConnectCertStore,
|
|
3767
4572
|
realConnectEffects,
|
|
3768
4573
|
recoverFarm,
|
|
3769
4574
|
recoveryDiagnostics,
|
|
4575
|
+
removeDirectoryRule,
|
|
4576
|
+
removeIdentity,
|
|
4577
|
+
removePool,
|
|
4578
|
+
removeProfile,
|
|
4579
|
+
removeProvider,
|
|
4580
|
+
requirePool,
|
|
3770
4581
|
resolveAgentShimHome,
|
|
3771
4582
|
resolveClaudeHome,
|
|
3772
4583
|
resolveDecisions,
|
|
@@ -3776,7 +4587,20 @@ export {
|
|
|
3776
4587
|
runFrontDoorSupervisor,
|
|
3777
4588
|
runSupervisor,
|
|
3778
4589
|
servedByPipeline,
|
|
4590
|
+
setAllowAmbientCredential,
|
|
4591
|
+
setDefaultConfigProfile,
|
|
4592
|
+
setGlobalDefaultProfile,
|
|
4593
|
+
setIdentityCredential,
|
|
4594
|
+
setPool,
|
|
4595
|
+
setProfileCategories,
|
|
4596
|
+
setProfileEntries,
|
|
4597
|
+
setProfileLaunchFlags,
|
|
4598
|
+
setProfileMetadata,
|
|
3779
4599
|
snapshotPath,
|
|
3780
4600
|
startConnectServer,
|
|
3781
|
-
topLevelNames
|
|
4601
|
+
topLevelNames,
|
|
4602
|
+
updateDirectoryRule,
|
|
4603
|
+
updateProvider,
|
|
4604
|
+
useIdentity,
|
|
4605
|
+
writeDirectoryRules
|
|
3782
4606
|
};
|