agent-shim 7.8.0 → 7.9.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 +533 -510
- package/dist/index.cjs +2314 -152
- package/dist/index.mjs +2306 -152
- package/dist/types/checkReport.d.ts +154 -0
- package/dist/types/claudeShim.d.ts +114 -0
- package/dist/types/cli/commandDeps.d.ts +28 -0
- package/dist/types/cli/envBool.d.ts +11 -0
- package/dist/types/codex/http.d.ts +15 -0
- package/dist/types/configure.d.ts +170 -0
- package/dist/types/doctorReport.d.ts +117 -0
- package/dist/types/frontdoor/custody.d.ts +32 -0
- package/dist/types/frontdoor/headroomHop.d.ts +22 -0
- package/dist/types/frontdoor/pipeline.d.ts +109 -0
- package/dist/types/frontdoor/server.d.ts +44 -0
- package/dist/types/index.d.ts +5 -1
- package/dist/types/launcher/cascade.d.ts +42 -0
- package/dist/types/launcher/pool.d.ts +44 -0
- package/dist/types/pathNorm.d.ts +14 -0
- package/dist/types/poolPickReport.d.ts +44 -0
- package/dist/types/realPorts.d.ts +102 -0
- package/dist/types/usage/middleware.d.ts +17 -0
- package/dist/types/usage/pick.d.ts +62 -0
- package/dist/types/usage/plan.d.ts +18 -0
- package/dist/types/usage/poolPick.d.ts +31 -0
- package/dist/types/usage/preflight.d.ts +20 -0
- package/dist/types/usage/rateLimit.d.ts +25 -0
- package/dist/types/usage/scan.d.ts +24 -0
- package/dist/types/usage/store.d.ts +43 -0
- package/dist/types/versionDiscovery.d.ts +47 -0
- package/package.json +1 -1
- package/dist/types/cli/parsers.d.ts +0 -40
package/dist/index.cjs
CHANGED
|
@@ -83,6 +83,10 @@ __export(index_exports, {
|
|
|
83
83
|
buildEntryFacts: () => buildEntryFacts,
|
|
84
84
|
buildLayoutPaths: () => buildLayoutPaths,
|
|
85
85
|
carryOver: () => carryOver,
|
|
86
|
+
checkReportHasWarnings: () => checkReportHasWarnings,
|
|
87
|
+
checkReportToJson: () => checkReportToJson,
|
|
88
|
+
collectCheckReport: () => collectCheckReport,
|
|
89
|
+
collectDoctorReport: () => collectDoctorReport,
|
|
86
90
|
createLeafCache: () => createLeafCache,
|
|
87
91
|
createProfile: () => createProfile,
|
|
88
92
|
describeProviderEndpoint: () => describeProviderEndpoint,
|
|
@@ -92,6 +96,8 @@ __export(index_exports, {
|
|
|
92
96
|
ensureHeadroom: () => ensureHeadroom,
|
|
93
97
|
evaluateAmbientCredentialGuard: () => evaluateAmbientCredentialGuard,
|
|
94
98
|
formatAmbientCredentialGuardMessage: () => formatAmbientCredentialGuardMessage,
|
|
99
|
+
formatCheckReport: () => formatCheckReport,
|
|
100
|
+
formatDoctorReport: () => formatDoctorReport,
|
|
95
101
|
forwardableHeaders: () => forwardableHeaders,
|
|
96
102
|
generateCa: () => generateCa,
|
|
97
103
|
identityExists: () => identityExists,
|
|
@@ -131,6 +137,8 @@ __export(index_exports, {
|
|
|
131
137
|
resolveLayoutPaths: () => resolveLayoutPaths,
|
|
132
138
|
resolveSupervisorConfig: () => resolveSupervisorConfig,
|
|
133
139
|
resyncFarm: () => resyncFarm,
|
|
140
|
+
runCheck: () => runCheck,
|
|
141
|
+
runDoctor: () => runDoctor,
|
|
134
142
|
runFrontDoorSupervisor: () => runFrontDoorSupervisor,
|
|
135
143
|
runSupervisor: () => runSupervisor,
|
|
136
144
|
servedByPipeline: () => servedByPipeline,
|
|
@@ -156,6 +164,58 @@ module.exports = __toCommonJS(index_exports);
|
|
|
156
164
|
// src/config/classify.ts
|
|
157
165
|
var import_picomatch = __toESM(require("picomatch"), 1);
|
|
158
166
|
|
|
167
|
+
// src/config/categories.default.json
|
|
168
|
+
var categories_default_default = {
|
|
169
|
+
secret: [".credentials.json", "backups", "settings.json.bak*"],
|
|
170
|
+
runtime: [
|
|
171
|
+
"daemon*",
|
|
172
|
+
".git*",
|
|
173
|
+
".DS_Store",
|
|
174
|
+
"mcp-needs-auth-cache.json",
|
|
175
|
+
"codex-quota.json",
|
|
176
|
+
"file-transfers",
|
|
177
|
+
"shell-snapshots",
|
|
178
|
+
"statsig",
|
|
179
|
+
"telemetry",
|
|
180
|
+
"stats-cache.json",
|
|
181
|
+
"usage-data",
|
|
182
|
+
"ide",
|
|
183
|
+
"cache",
|
|
184
|
+
"scheduled_tasks.lock"
|
|
185
|
+
],
|
|
186
|
+
history: [
|
|
187
|
+
"projects",
|
|
188
|
+
"sessions",
|
|
189
|
+
"session-env",
|
|
190
|
+
"teams",
|
|
191
|
+
"tasks",
|
|
192
|
+
"todos",
|
|
193
|
+
"history.jsonl",
|
|
194
|
+
"transcripts",
|
|
195
|
+
"paste-cache",
|
|
196
|
+
"file-history",
|
|
197
|
+
"plans",
|
|
198
|
+
"workflows",
|
|
199
|
+
"jobs",
|
|
200
|
+
"debug",
|
|
201
|
+
"downloads",
|
|
202
|
+
"chrome"
|
|
203
|
+
],
|
|
204
|
+
knowledge: [
|
|
205
|
+
"skills",
|
|
206
|
+
"agents",
|
|
207
|
+
"rules",
|
|
208
|
+
"memory",
|
|
209
|
+
"commands",
|
|
210
|
+
"plugins",
|
|
211
|
+
"hooks",
|
|
212
|
+
"AGENTS.md",
|
|
213
|
+
"CLAUDE.md",
|
|
214
|
+
"README.md"
|
|
215
|
+
],
|
|
216
|
+
settings: ["settings.json", "settings.local.json"]
|
|
217
|
+
};
|
|
218
|
+
|
|
159
219
|
// src/config/schema.ts
|
|
160
220
|
var import_node_path = __toESM(require("node:path"), 1);
|
|
161
221
|
var import_zod = require("zod");
|
|
@@ -422,6 +482,32 @@ var UsageError = class extends CliError {
|
|
|
422
482
|
};
|
|
423
483
|
|
|
424
484
|
// src/config/load.ts
|
|
485
|
+
function createExplorer(moduleName = "agent-shim") {
|
|
486
|
+
return (0, import_cosmiconfig.cosmiconfigSync)(moduleName, { searchPlaces: [] });
|
|
487
|
+
}
|
|
488
|
+
function isRecord(value) {
|
|
489
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
490
|
+
}
|
|
491
|
+
function captureEntryOrder(value) {
|
|
492
|
+
if (!isRecord(value)) {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
const entries = value.entries;
|
|
496
|
+
if (!isRecord(entries)) {
|
|
497
|
+
return [];
|
|
498
|
+
}
|
|
499
|
+
return Object.keys(entries);
|
|
500
|
+
}
|
|
501
|
+
function captureRuleEntryOrders(value) {
|
|
502
|
+
if (!isRecord(value)) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const rules = value.rules;
|
|
506
|
+
if (!Array.isArray(rules)) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
return rules.map((rule) => captureEntryOrder(rule));
|
|
510
|
+
}
|
|
425
511
|
var ConfigValidationError = class extends CliError {
|
|
426
512
|
constructor(filepath, issues) {
|
|
427
513
|
const detail = issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
@@ -434,6 +520,40 @@ ${detail}`);
|
|
|
434
520
|
filepath;
|
|
435
521
|
issues;
|
|
436
522
|
};
|
|
523
|
+
function cosmiconfigReader(explorer = createExplorer()) {
|
|
524
|
+
return (filepath) => {
|
|
525
|
+
let result;
|
|
526
|
+
try {
|
|
527
|
+
result = explorer.load(filepath);
|
|
528
|
+
} catch (error) {
|
|
529
|
+
const code = isRecord(error) ? error.code : void 0;
|
|
530
|
+
if (code === "ENOENT") {
|
|
531
|
+
return void 0;
|
|
532
|
+
}
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
if (result === null || result.isEmpty === true) {
|
|
536
|
+
return void 0;
|
|
537
|
+
}
|
|
538
|
+
return result.config;
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function loadConfigFile(filepath, schema, read = cosmiconfigReader()) {
|
|
542
|
+
const raw = read(filepath);
|
|
543
|
+
if (raw === void 0) {
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
const parsed = schema.safeParse(raw);
|
|
547
|
+
if (!parsed.success) {
|
|
548
|
+
throw new ConfigValidationError(filepath, parsed.error.issues);
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
filepath,
|
|
552
|
+
config: parsed.data,
|
|
553
|
+
entryOrder: captureEntryOrder(raw),
|
|
554
|
+
ruleEntryOrders: captureRuleEntryOrders(raw)
|
|
555
|
+
};
|
|
556
|
+
}
|
|
437
557
|
|
|
438
558
|
// src/config/store.ts
|
|
439
559
|
function isEnoent(error) {
|
|
@@ -561,6 +681,13 @@ function compareClassificationPatterns(a, b) {
|
|
|
561
681
|
}
|
|
562
682
|
return a.ordinal - b.ordinal;
|
|
563
683
|
}
|
|
684
|
+
function loadClassification(paths) {
|
|
685
|
+
const overlay = readJson(paths.categoriesLocalFile, CategoryClassificationOverlaySchema);
|
|
686
|
+
return {
|
|
687
|
+
defaults: CategoryClassificationSchema.parse(categories_default_default),
|
|
688
|
+
...overlay === void 0 ? {} : { overlay }
|
|
689
|
+
};
|
|
690
|
+
}
|
|
564
691
|
function classifyEntries(names, options) {
|
|
565
692
|
const patterns = compileClassificationPatterns(options.defaults, options.overlay);
|
|
566
693
|
const classification = /* @__PURE__ */ new Map();
|
|
@@ -672,6 +799,7 @@ var import_picomatch3 = __toESM(require("picomatch"), 1);
|
|
|
672
799
|
|
|
673
800
|
// src/resolve/projects.ts
|
|
674
801
|
var NON_ALPHANUMERIC = /[^A-Za-z0-9]/g;
|
|
802
|
+
var NON_ALPHANUMERIC_CHARACTER = /[^A-Za-z0-9]/;
|
|
675
803
|
var WILDCARD_TOKENS = ["**", "*", "?"];
|
|
676
804
|
function encodeProjectPath(realPath) {
|
|
677
805
|
return realPath.replace(NON_ALPHANUMERIC, "-");
|
|
@@ -725,6 +853,64 @@ function encodeProjectPattern(fragment, options) {
|
|
|
725
853
|
}
|
|
726
854
|
return splitOnWildcards(expanded).map((piece) => piece.kind === "literal" ? encodeProjectPath(piece.text) : piece.text).join("");
|
|
727
855
|
}
|
|
856
|
+
function lossyCharacters(literal) {
|
|
857
|
+
const found = /* @__PURE__ */ new Set();
|
|
858
|
+
for (const character of literal) {
|
|
859
|
+
if (character !== "/" && NON_ALPHANUMERIC_CHARACTER.test(character)) {
|
|
860
|
+
found.add(character);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return [...found];
|
|
864
|
+
}
|
|
865
|
+
function detectEncodingAmbiguity(fragments, options) {
|
|
866
|
+
const ambiguities = [];
|
|
867
|
+
const encodedByFragment = /* @__PURE__ */ new Map();
|
|
868
|
+
for (const fragment of fragments) {
|
|
869
|
+
let encoded;
|
|
870
|
+
try {
|
|
871
|
+
encoded = encodeProjectPattern(fragment, { home: options.home });
|
|
872
|
+
} catch {
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
encodedByFragment.set(fragment, encoded);
|
|
876
|
+
const expanded = expandHome(fragment, options.home);
|
|
877
|
+
const literalText = splitOnWildcards(expanded).filter((piece) => piece.kind === "literal").map((piece) => piece.text).join("");
|
|
878
|
+
const lossy = lossyCharacters(literalText);
|
|
879
|
+
if (lossy.length > 0) {
|
|
880
|
+
const matched = options.existingNames?.filter((name) => name === encoded).length;
|
|
881
|
+
const suffix = matched === void 0 ? "" : ` It currently matches ${String(matched)} existing project directory name(s).`;
|
|
882
|
+
ambiguities.push({
|
|
883
|
+
fragment,
|
|
884
|
+
encoded,
|
|
885
|
+
reason: "lossy-characters",
|
|
886
|
+
detail: `The literal portion contains ${lossy.map((character) => JSON.stringify(character)).join(", ")}, which the encoding collapses to "-" exactly as it collapses "/". A different real path differing only in those characters would encode to the same name.${suffix}`
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const byEncoded = /* @__PURE__ */ new Map();
|
|
891
|
+
for (const [fragment, encoded] of encodedByFragment) {
|
|
892
|
+
const group = byEncoded.get(encoded);
|
|
893
|
+
if (group === void 0) {
|
|
894
|
+
byEncoded.set(encoded, [fragment]);
|
|
895
|
+
} else {
|
|
896
|
+
group.push(fragment);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
for (const [encoded, group] of byEncoded) {
|
|
900
|
+
if (group.length < 2) {
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
for (const fragment of group) {
|
|
904
|
+
ambiguities.push({
|
|
905
|
+
fragment,
|
|
906
|
+
encoded,
|
|
907
|
+
reason: "collides-with-sibling-pattern",
|
|
908
|
+
detail: `Encodes identically to ${group.filter((other) => other !== fragment).map((other) => JSON.stringify(other)).join(", ")}.`
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return ambiguities;
|
|
913
|
+
}
|
|
728
914
|
|
|
729
915
|
// src/resolve/match.ts
|
|
730
916
|
var GLOB_METACHARACTERS2 = /[*?[\]{}!()]/;
|
|
@@ -1200,6 +1386,9 @@ function planFarm(params) {
|
|
|
1200
1386
|
return { entries, materialised, links };
|
|
1201
1387
|
}
|
|
1202
1388
|
|
|
1389
|
+
// src/resolve/walk.ts
|
|
1390
|
+
var import_node_path4 = __toESM(require("node:path"), 1);
|
|
1391
|
+
|
|
1203
1392
|
// src/resolve/extends.ts
|
|
1204
1393
|
function lineariseProfile(rootName, load) {
|
|
1205
1394
|
const stack = [];
|
|
@@ -1265,6 +1454,27 @@ function profileLayers(rootName, load, startId) {
|
|
|
1265
1454
|
}
|
|
1266
1455
|
|
|
1267
1456
|
// src/resolve/walk.ts
|
|
1457
|
+
function walkDirectoryAncestors(cwd, options) {
|
|
1458
|
+
const limit = import_node_path4.default.resolve(options.limit ?? options.home);
|
|
1459
|
+
const isReadable = options.isReadable ?? (() => true);
|
|
1460
|
+
const collected = [];
|
|
1461
|
+
let current = import_node_path4.default.resolve(cwd);
|
|
1462
|
+
for (; ; ) {
|
|
1463
|
+
if (!isReadable(current)) {
|
|
1464
|
+
break;
|
|
1465
|
+
}
|
|
1466
|
+
collected.push(current);
|
|
1467
|
+
if (current === limit) {
|
|
1468
|
+
break;
|
|
1469
|
+
}
|
|
1470
|
+
const parent = import_node_path4.default.dirname(current);
|
|
1471
|
+
if (parent === current) {
|
|
1472
|
+
break;
|
|
1473
|
+
}
|
|
1474
|
+
current = parent;
|
|
1475
|
+
}
|
|
1476
|
+
return collected.reverse();
|
|
1477
|
+
}
|
|
1268
1478
|
function assembleCascade(input) {
|
|
1269
1479
|
const layers = [];
|
|
1270
1480
|
const diagnostics = [];
|
|
@@ -1378,7 +1588,7 @@ function resolveDecisions(input) {
|
|
|
1378
1588
|
}
|
|
1379
1589
|
|
|
1380
1590
|
// src/launcher/farm.ts
|
|
1381
|
-
var
|
|
1591
|
+
var import_node_path6 = __toESM(require("node:path"), 1);
|
|
1382
1592
|
var import_zod2 = require("zod");
|
|
1383
1593
|
|
|
1384
1594
|
// src/resolve/reconcile.ts
|
|
@@ -1472,13 +1682,17 @@ function planReconciliation(params) {
|
|
|
1472
1682
|
}
|
|
1473
1683
|
|
|
1474
1684
|
// src/legacy.ts
|
|
1685
|
+
var ENV_PREFIX = "AGENT_SHIM_";
|
|
1686
|
+
var LEGACY_ENV_PREFIX = "CLAUDE_USE_";
|
|
1475
1687
|
var HOME_DIRNAME = ".agent-shim";
|
|
1476
1688
|
var LEGACY_HOME_DIRNAME = ".claude-use";
|
|
1689
|
+
var LEGACY_PORTABLE_CONFIG_FILENAME = ".claude-use.json";
|
|
1690
|
+
var LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME = ".claude-use.local.json";
|
|
1477
1691
|
var LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
|
|
1478
1692
|
|
|
1479
1693
|
// src/launcher/lock.ts
|
|
1480
1694
|
var import_node_crypto2 = require("node:crypto");
|
|
1481
|
-
var
|
|
1695
|
+
var import_node_path5 = __toESM(require("node:path"), 1);
|
|
1482
1696
|
var DEFAULT_STALE_AFTER_MS = 12e4;
|
|
1483
1697
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
1484
1698
|
var DEFAULT_MAX_ATTEMPTS = 200;
|
|
@@ -1497,7 +1711,7 @@ var IdentityLockBusyError = class extends CliError {
|
|
|
1497
1711
|
holderPid;
|
|
1498
1712
|
};
|
|
1499
1713
|
function identityLockPath(dir, identity) {
|
|
1500
|
-
return
|
|
1714
|
+
return import_node_path5.default.join(dir, `.${identity}.lock`);
|
|
1501
1715
|
}
|
|
1502
1716
|
function parseLockRecord(raw) {
|
|
1503
1717
|
if (raw === void 0) {
|
|
@@ -1575,8 +1789,8 @@ var FarmManifestSchema = import_zod2.z.strictObject({
|
|
|
1575
1789
|
materialised: import_zod2.z.array(import_zod2.z.string()),
|
|
1576
1790
|
links: import_zod2.z.array(import_zod2.z.strictObject({ rel: import_zod2.z.string(), target: import_zod2.z.string() }))
|
|
1577
1791
|
});
|
|
1578
|
-
function readFarmManifest(
|
|
1579
|
-
const raw =
|
|
1792
|
+
function readFarmManifest(fs10, farmRoot) {
|
|
1793
|
+
const raw = fs10.readFileUtf8(import_node_path6.default.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs10.readFileUtf8(import_node_path6.default.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
|
|
1580
1794
|
if (raw === void 0) {
|
|
1581
1795
|
return void 0;
|
|
1582
1796
|
}
|
|
@@ -1592,7 +1806,7 @@ function readFarmManifest(fs7, farmRoot) {
|
|
|
1592
1806
|
function buildEntryFacts(params) {
|
|
1593
1807
|
const entries = /* @__PURE__ */ new Map();
|
|
1594
1808
|
const visit = (rel) => {
|
|
1595
|
-
const absolute =
|
|
1809
|
+
const absolute = import_node_path6.default.join(params.claudeHome, rel);
|
|
1596
1810
|
const stat = params.fs.lstat(absolute);
|
|
1597
1811
|
if (stat === void 0) {
|
|
1598
1812
|
return void 0;
|
|
@@ -1634,9 +1848,9 @@ function buildEntryFacts(params) {
|
|
|
1634
1848
|
entries
|
|
1635
1849
|
};
|
|
1636
1850
|
}
|
|
1637
|
-
function listSubtree(
|
|
1638
|
-
const absolute =
|
|
1639
|
-
const stat =
|
|
1851
|
+
function listSubtree(fs10, root, rel) {
|
|
1852
|
+
const absolute = import_node_path6.default.join(root, rel);
|
|
1853
|
+
const stat = fs10.lstat(absolute);
|
|
1640
1854
|
if (stat === void 0) {
|
|
1641
1855
|
return [];
|
|
1642
1856
|
}
|
|
@@ -1644,10 +1858,10 @@ function listSubtree(fs7, root, rel) {
|
|
|
1644
1858
|
return [{ rel, kind: "symlink" }];
|
|
1645
1859
|
}
|
|
1646
1860
|
if (stat.kind === "dir") {
|
|
1647
|
-
const children = [...
|
|
1861
|
+
const children = [...fs10.readdir(absolute)].sort().flatMap((name) => listSubtree(fs10, root, `${rel}/${name}`));
|
|
1648
1862
|
return [{ rel, kind: "dir" }, ...children];
|
|
1649
1863
|
}
|
|
1650
|
-
const contentHash =
|
|
1864
|
+
const contentHash = fs10.hashFile(absolute);
|
|
1651
1865
|
return [{ rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } }];
|
|
1652
1866
|
}
|
|
1653
1867
|
function topLevelScopeRoots(roots) {
|
|
@@ -1661,25 +1875,25 @@ function topLevelScopeRoots(roots) {
|
|
|
1661
1875
|
}
|
|
1662
1876
|
return kept;
|
|
1663
1877
|
}
|
|
1664
|
-
function reconciliationScope(
|
|
1878
|
+
function reconciliationScope(fs10, farmRoot, manifest) {
|
|
1665
1879
|
if (manifest !== void 0) {
|
|
1666
1880
|
return topLevelScopeRoots(manifest.materialised);
|
|
1667
1881
|
}
|
|
1668
|
-
return [...
|
|
1882
|
+
return [...fs10.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs10.lstat(import_node_path6.default.join(farmRoot, name))?.kind === "dir").sort();
|
|
1669
1883
|
}
|
|
1670
|
-
function collectFarmListing(
|
|
1671
|
-
return scopeRoots.flatMap((root) => listSubtree(
|
|
1884
|
+
function collectFarmListing(fs10, farmRoot, scopeRoots) {
|
|
1885
|
+
return scopeRoots.flatMap((root) => listSubtree(fs10, farmRoot, root));
|
|
1672
1886
|
}
|
|
1673
|
-
function collectCanonicalCounterparts(
|
|
1887
|
+
function collectCanonicalCounterparts(fs10, claudeHome, farmListing) {
|
|
1674
1888
|
const out = [];
|
|
1675
1889
|
for (const entry of farmListing) {
|
|
1676
|
-
const absolute =
|
|
1677
|
-
const stat =
|
|
1890
|
+
const absolute = import_node_path6.default.join(claudeHome, entry.rel);
|
|
1891
|
+
const stat = fs10.lstat(absolute);
|
|
1678
1892
|
if (stat === void 0) {
|
|
1679
1893
|
continue;
|
|
1680
1894
|
}
|
|
1681
1895
|
if (stat.kind === "file") {
|
|
1682
|
-
const contentHash =
|
|
1896
|
+
const contentHash = fs10.hashFile(absolute);
|
|
1683
1897
|
out.push({ rel: entry.rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } });
|
|
1684
1898
|
continue;
|
|
1685
1899
|
}
|
|
@@ -1703,21 +1917,21 @@ function carryOver(params) {
|
|
|
1703
1917
|
if (accounted.has(name)) {
|
|
1704
1918
|
continue;
|
|
1705
1919
|
}
|
|
1706
|
-
const stat = params.fs.lstat(
|
|
1920
|
+
const stat = params.fs.lstat(import_node_path6.default.join(params.previousRoot, name));
|
|
1707
1921
|
if (stat === void 0 || stat.kind === "symlink") {
|
|
1708
1922
|
continue;
|
|
1709
1923
|
}
|
|
1710
|
-
if (params.fs.lstat(
|
|
1924
|
+
if (params.fs.lstat(import_node_path6.default.join(params.farmRoot, name)) !== void 0) {
|
|
1711
1925
|
const category = params.classification === void 0 ? void 0 : classifyEntries([name], params.classification).classification.get(name);
|
|
1712
1926
|
if (category === "runtime") {
|
|
1713
|
-
params.fs.removeRecursive(
|
|
1927
|
+
params.fs.removeRecursive(import_node_path6.default.join(params.previousRoot, name));
|
|
1714
1928
|
autoResolved.push(name);
|
|
1715
1929
|
continue;
|
|
1716
1930
|
}
|
|
1717
1931
|
collided.push(name);
|
|
1718
1932
|
continue;
|
|
1719
1933
|
}
|
|
1720
|
-
params.fs.rename(
|
|
1934
|
+
params.fs.rename(import_node_path6.default.join(params.previousRoot, name), import_node_path6.default.join(params.farmRoot, name));
|
|
1721
1935
|
carried.push(name);
|
|
1722
1936
|
}
|
|
1723
1937
|
return { carried, autoResolved, collided };
|
|
@@ -1725,9 +1939,9 @@ function carryOver(params) {
|
|
|
1725
1939
|
function buildScratchTree(params) {
|
|
1726
1940
|
params.fs.mkdirp(params.scratchRoot);
|
|
1727
1941
|
for (const entry of params.plan.entries) {
|
|
1728
|
-
const absolute =
|
|
1942
|
+
const absolute = import_node_path6.default.join(params.scratchRoot, entry.rel);
|
|
1729
1943
|
if (entry.kind === "link") {
|
|
1730
|
-
params.fs.mkdirp(
|
|
1944
|
+
params.fs.mkdirp(import_node_path6.default.dirname(absolute));
|
|
1731
1945
|
params.fs.symlink(entry.target, absolute);
|
|
1732
1946
|
continue;
|
|
1733
1947
|
}
|
|
@@ -1736,14 +1950,14 @@ function buildScratchTree(params) {
|
|
|
1736
1950
|
}
|
|
1737
1951
|
}
|
|
1738
1952
|
params.fs.writeFileUtf8(
|
|
1739
|
-
|
|
1953
|
+
import_node_path6.default.join(params.scratchRoot, FARM_MANIFEST_FILENAME),
|
|
1740
1954
|
`${JSON.stringify(params.manifest, null, 2)}
|
|
1741
1955
|
`
|
|
1742
1956
|
);
|
|
1743
1957
|
}
|
|
1744
1958
|
function swapIn(params) {
|
|
1745
1959
|
if (params.fs.lstat(params.farmRoot) === void 0) {
|
|
1746
|
-
params.fs.mkdirp(
|
|
1960
|
+
params.fs.mkdirp(import_node_path6.default.dirname(params.farmRoot));
|
|
1747
1961
|
params.fs.rename(params.scratchRoot, params.farmRoot);
|
|
1748
1962
|
return { carried: [], autoResolved: [], collided: [] };
|
|
1749
1963
|
}
|
|
@@ -1767,14 +1981,14 @@ function recoverInterruptedSwap(params) {
|
|
|
1767
1981
|
const names = [...params.fs.readdir(params.identitiesDir)].sort();
|
|
1768
1982
|
const removedScratch = [];
|
|
1769
1983
|
for (const name of names.filter((candidate) => candidate.startsWith(scratchPrefix))) {
|
|
1770
|
-
params.fs.removeRecursive(
|
|
1984
|
+
params.fs.removeRecursive(import_node_path6.default.join(params.identitiesDir, name));
|
|
1771
1985
|
removedScratch.push(name);
|
|
1772
1986
|
}
|
|
1773
1987
|
let previous = names.filter((candidate) => candidate.startsWith(previousPrefix));
|
|
1774
1988
|
let restoredFrom;
|
|
1775
1989
|
const first = previous[0];
|
|
1776
1990
|
if (first !== void 0 && params.fs.lstat(params.farmRoot) === void 0) {
|
|
1777
|
-
params.fs.rename(
|
|
1991
|
+
params.fs.rename(import_node_path6.default.join(params.identitiesDir, first), params.farmRoot);
|
|
1778
1992
|
restoredFrom = first;
|
|
1779
1993
|
previous = previous.slice(1);
|
|
1780
1994
|
}
|
|
@@ -1782,7 +1996,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1782
1996
|
const autoResolved = [];
|
|
1783
1997
|
const retained = [];
|
|
1784
1998
|
for (const name of previous) {
|
|
1785
|
-
const previousRoot =
|
|
1999
|
+
const previousRoot = import_node_path6.default.join(params.identitiesDir, name);
|
|
1786
2000
|
const result = carryOver({
|
|
1787
2001
|
fs: params.fs,
|
|
1788
2002
|
previousRoot,
|
|
@@ -1807,7 +2021,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1807
2021
|
};
|
|
1808
2022
|
}
|
|
1809
2023
|
function recoverFarm(params) {
|
|
1810
|
-
const farmRoot =
|
|
2024
|
+
const farmRoot = import_node_path6.default.join(params.identitiesDir, params.identity);
|
|
1811
2025
|
const lock = acquireIdentityLock({
|
|
1812
2026
|
identity: params.identity,
|
|
1813
2027
|
dir: params.identitiesDir,
|
|
@@ -1866,16 +2080,16 @@ function executeReconciliation(params) {
|
|
|
1866
2080
|
});
|
|
1867
2081
|
continue;
|
|
1868
2082
|
}
|
|
1869
|
-
const from =
|
|
1870
|
-
const canonical =
|
|
2083
|
+
const from = import_node_path6.default.join(params.farmRoot, action.rel);
|
|
2084
|
+
const canonical = import_node_path6.default.join(params.claudeHome, action.rel);
|
|
1871
2085
|
if (action.kind === "adopt") {
|
|
1872
|
-
params.fs.mkdirp(
|
|
2086
|
+
params.fs.mkdirp(import_node_path6.default.dirname(canonical));
|
|
1873
2087
|
params.fs.copyRecursive(from, canonical);
|
|
1874
2088
|
adopted.push(action.rel);
|
|
1875
2089
|
continue;
|
|
1876
2090
|
}
|
|
1877
2091
|
const preserved = `${canonical}.farm-conflict-${String(params.nowMs)}`;
|
|
1878
|
-
params.fs.mkdirp(
|
|
2092
|
+
params.fs.mkdirp(import_node_path6.default.dirname(canonical));
|
|
1879
2093
|
params.fs.copyRecursive(from, preserved);
|
|
1880
2094
|
conflicts.push(action.rel);
|
|
1881
2095
|
}
|
|
@@ -1900,7 +2114,7 @@ function farmAlreadyMatches(existing, next) {
|
|
|
1900
2114
|
return existing.identity === next.identity && existing.claudeHome === next.claudeHome && sameStrings(existing.materialised, next.materialised) && sameLinks(existing.links, next.links);
|
|
1901
2115
|
}
|
|
1902
2116
|
function resyncFarm(params) {
|
|
1903
|
-
const farmRoot =
|
|
2117
|
+
const farmRoot = import_node_path6.default.join(params.identitiesDir, params.identity);
|
|
1904
2118
|
const lock = acquireIdentityLock({
|
|
1905
2119
|
identity: params.identity,
|
|
1906
2120
|
dir: params.identitiesDir,
|
|
@@ -1979,8 +2193,8 @@ function resyncFarm(params) {
|
|
|
1979
2193
|
diagnostics
|
|
1980
2194
|
};
|
|
1981
2195
|
}
|
|
1982
|
-
const scratchRoot =
|
|
1983
|
-
const previousRoot =
|
|
2196
|
+
const scratchRoot = import_node_path6.default.join(params.identitiesDir, `.${params.identity}.scratch.${params.uniqueSuffix}`);
|
|
2197
|
+
const previousRoot = import_node_path6.default.join(params.identitiesDir, `.${params.identity}.previous.${params.uniqueSuffix}`);
|
|
1984
2198
|
buildScratchTree({ fs: params.fs, scratchRoot, plan: resolved.farm, manifest });
|
|
1985
2199
|
const swap = swapIn({ fs: params.fs, farmRoot, scratchRoot, previousRoot, classification: params.classification });
|
|
1986
2200
|
if (swap.retainedPrevious !== void 0) {
|
|
@@ -2384,7 +2598,7 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2384
2598
|
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2385
2599
|
|
|
2386
2600
|
// src/frontdoor/state.ts
|
|
2387
|
-
var
|
|
2601
|
+
var import_node_path7 = __toESM(require("node:path"), 1);
|
|
2388
2602
|
var import_zod3 = require("zod");
|
|
2389
2603
|
var FRONT_DOOR_PROTOCOL = 1;
|
|
2390
2604
|
var FrontDoorSessionSchema = import_zod3.z.strictObject({ pid: import_zod3.z.number().int().positive(), startedAt: import_zod3.z.number(), token: import_zod3.z.string().min(1) });
|
|
@@ -2418,10 +2632,10 @@ var FrontDoorStateSchema = import_zod3.z.strictObject({
|
|
|
2418
2632
|
/** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
|
|
2419
2633
|
lastError: import_zod3.z.string().optional()
|
|
2420
2634
|
});
|
|
2421
|
-
function readFrontDoorState(
|
|
2635
|
+
function readFrontDoorState(fs10, stateFile) {
|
|
2422
2636
|
let raw;
|
|
2423
2637
|
try {
|
|
2424
|
-
raw =
|
|
2638
|
+
raw = fs10.readFileUtf8(stateFile);
|
|
2425
2639
|
} catch {
|
|
2426
2640
|
return void 0;
|
|
2427
2641
|
}
|
|
@@ -2435,28 +2649,28 @@ function readFrontDoorState(fs7, stateFile) {
|
|
|
2435
2649
|
return void 0;
|
|
2436
2650
|
}
|
|
2437
2651
|
}
|
|
2438
|
-
function writeFrontDoorState(
|
|
2439
|
-
|
|
2440
|
-
|
|
2652
|
+
function writeFrontDoorState(fs10, stateFile, state) {
|
|
2653
|
+
fs10.mkdirp(import_node_path7.default.dirname(stateFile));
|
|
2654
|
+
fs10.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2441
2655
|
`);
|
|
2442
2656
|
}
|
|
2443
2657
|
function frontDoorOrigin(state) {
|
|
2444
2658
|
const port = state?.directPort ?? state?.lastDirectPort;
|
|
2445
2659
|
return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
|
|
2446
2660
|
}
|
|
2447
|
-
function writeFrontDoorSession(
|
|
2448
|
-
|
|
2449
|
-
|
|
2661
|
+
function writeFrontDoorSession(fs10, sessionsDir, session) {
|
|
2662
|
+
fs10.mkdirPrivate(sessionsDir);
|
|
2663
|
+
fs10.writeFilePrivate(import_node_path7.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
2450
2664
|
`);
|
|
2451
2665
|
}
|
|
2452
|
-
function listFrontDoorSessions(
|
|
2666
|
+
function listFrontDoorSessions(fs10, sessionsDir) {
|
|
2453
2667
|
const sessions = [];
|
|
2454
|
-
for (const name of
|
|
2668
|
+
for (const name of fs10.readdir(sessionsDir)) {
|
|
2455
2669
|
if (!name.endsWith(".json")) {
|
|
2456
2670
|
continue;
|
|
2457
2671
|
}
|
|
2458
2672
|
try {
|
|
2459
|
-
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(
|
|
2673
|
+
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs10.readFileUtf8(import_node_path7.default.join(sessionsDir, name)) ?? ""));
|
|
2460
2674
|
if (parsed.success) {
|
|
2461
2675
|
sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
|
|
2462
2676
|
}
|
|
@@ -2466,14 +2680,14 @@ function listFrontDoorSessions(fs7, sessionsDir) {
|
|
|
2466
2680
|
}
|
|
2467
2681
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2468
2682
|
}
|
|
2469
|
-
function removeFrontDoorSession(
|
|
2470
|
-
|
|
2683
|
+
function removeFrontDoorSession(fs10, sessionsDir, pid) {
|
|
2684
|
+
fs10.removeRecursive(import_node_path7.default.join(sessionsDir, `${String(pid)}.json`));
|
|
2471
2685
|
}
|
|
2472
|
-
function pruneDeadFrontDoorSessions(
|
|
2686
|
+
function pruneDeadFrontDoorSessions(fs10, sessionsDir, isRunning) {
|
|
2473
2687
|
const removed = [];
|
|
2474
|
-
for (const session of listFrontDoorSessions(
|
|
2688
|
+
for (const session of listFrontDoorSessions(fs10, sessionsDir)) {
|
|
2475
2689
|
if (!isRunning(session.pid)) {
|
|
2476
|
-
removeFrontDoorSession(
|
|
2690
|
+
removeFrontDoorSession(fs10, sessionsDir, session.pid);
|
|
2477
2691
|
removed.push(session.pid);
|
|
2478
2692
|
}
|
|
2479
2693
|
}
|
|
@@ -2485,8 +2699,8 @@ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
|
2485
2699
|
var FRONTDOOR_POLL_MS = 1e3;
|
|
2486
2700
|
var MS_PER_MINUTE = 6e4;
|
|
2487
2701
|
async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
|
|
2488
|
-
const { fs:
|
|
2489
|
-
const previous = readFrontDoorState(
|
|
2702
|
+
const { fs: fs10, paths } = ports;
|
|
2703
|
+
const previous = readFrontDoorState(fs10, paths.frontdoorStateFile);
|
|
2490
2704
|
let lastPort = previous?.lastPort;
|
|
2491
2705
|
let lastConnectPort = previous?.lastConnectPort;
|
|
2492
2706
|
let lastDirectPort = previous?.lastDirectPort;
|
|
@@ -2496,12 +2710,12 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2496
2710
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2497
2711
|
});
|
|
2498
2712
|
const fail = (message) => {
|
|
2499
|
-
writeFrontDoorState(
|
|
2500
|
-
|
|
2713
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2714
|
+
fs10.removeRecursive(paths.frontdoorLockFile);
|
|
2501
2715
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
2502
2716
|
return 1;
|
|
2503
2717
|
};
|
|
2504
|
-
writeFrontDoorState(
|
|
2718
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
2505
2719
|
const previousSticky = lastPort;
|
|
2506
2720
|
let http2;
|
|
2507
2721
|
try {
|
|
@@ -2538,8 +2752,8 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2538
2752
|
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)`);
|
|
2539
2753
|
}
|
|
2540
2754
|
lastDirectPort = direct.port;
|
|
2541
|
-
writeFrontDoorState(
|
|
2542
|
-
|
|
2755
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2756
|
+
fs10.removeRecursive(paths.frontdoorLockFile);
|
|
2543
2757
|
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)}`);
|
|
2544
2758
|
let idleSince;
|
|
2545
2759
|
let ticks = 0;
|
|
@@ -2548,15 +2762,15 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2548
2762
|
return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
|
|
2549
2763
|
}
|
|
2550
2764
|
ticks += 1;
|
|
2551
|
-
pruneDeadFrontDoorSessions(
|
|
2552
|
-
if (listFrontDoorSessions(
|
|
2765
|
+
pruneDeadFrontDoorSessions(fs10, paths.frontdoorSessionsDir, ports.isRunning);
|
|
2766
|
+
if (listFrontDoorSessions(fs10, paths.frontdoorSessionsDir).length === 0) {
|
|
2553
2767
|
idleSince ??= ports.now();
|
|
2554
2768
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2555
2769
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2556
2770
|
await direct.close();
|
|
2557
2771
|
await connect3.close();
|
|
2558
2772
|
await http2.close();
|
|
2559
|
-
writeFrontDoorState(
|
|
2773
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, sticky());
|
|
2560
2774
|
return 0;
|
|
2561
2775
|
}
|
|
2562
2776
|
} else {
|
|
@@ -2611,8 +2825,8 @@ function parseConnectTarget(authority) {
|
|
|
2611
2825
|
function isInterceptedHost(host, interceptHosts) {
|
|
2612
2826
|
return interceptHosts.includes(host);
|
|
2613
2827
|
}
|
|
2614
|
-
function servedByPipeline(
|
|
2615
|
-
return
|
|
2828
|
+
function servedByPipeline(path23) {
|
|
2829
|
+
return path23?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
2616
2830
|
}
|
|
2617
2831
|
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
2618
2832
|
"connection",
|
|
@@ -3107,7 +3321,7 @@ var import_node_crypto6 = require("node:crypto");
|
|
|
3107
3321
|
|
|
3108
3322
|
// src/headroom/state.ts
|
|
3109
3323
|
var import_node_crypto5 = require("node:crypto");
|
|
3110
|
-
var
|
|
3324
|
+
var import_node_path8 = __toESM(require("node:path"), 1);
|
|
3111
3325
|
var import_zod4 = require("zod");
|
|
3112
3326
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
3113
3327
|
var HeadroomStateSchema = import_zod4.z.strictObject({
|
|
@@ -3141,16 +3355,16 @@ function parseJson(raw, schema) {
|
|
|
3141
3355
|
const parsed = schema.safeParse(JSON.parse(raw));
|
|
3142
3356
|
return parsed.success ? parsed.data : void 0;
|
|
3143
3357
|
}
|
|
3144
|
-
function readHeadroomState(
|
|
3358
|
+
function readHeadroomState(fs10, stateFile) {
|
|
3145
3359
|
try {
|
|
3146
|
-
return parseJson(
|
|
3360
|
+
return parseJson(fs10.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
3147
3361
|
} catch {
|
|
3148
3362
|
return void 0;
|
|
3149
3363
|
}
|
|
3150
3364
|
}
|
|
3151
|
-
function writeHeadroomState(
|
|
3152
|
-
|
|
3153
|
-
|
|
3365
|
+
function writeHeadroomState(fs10, stateFile, state) {
|
|
3366
|
+
fs10.mkdirp(import_node_path8.default.dirname(stateFile));
|
|
3367
|
+
fs10.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
3154
3368
|
`);
|
|
3155
3369
|
}
|
|
3156
3370
|
function headroomAllowlist(providers) {
|
|
@@ -3170,14 +3384,14 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
3170
3384
|
function hashAllowlist(allowlist) {
|
|
3171
3385
|
return (0, import_node_crypto5.createHash)("sha256").update(allowlist.join("\n")).digest("hex");
|
|
3172
3386
|
}
|
|
3173
|
-
function readAllProviders(
|
|
3387
|
+
function readAllProviders(fs10, providersDir) {
|
|
3174
3388
|
const result = [];
|
|
3175
|
-
for (const name of
|
|
3389
|
+
for (const name of fs10.readdir(providersDir)) {
|
|
3176
3390
|
if (!name.endsWith(".json")) {
|
|
3177
3391
|
continue;
|
|
3178
3392
|
}
|
|
3179
3393
|
try {
|
|
3180
|
-
const parsed = parseJson(
|
|
3394
|
+
const parsed = parseJson(fs10.readFileUtf8(import_node_path8.default.join(providersDir, name)), ProviderSchema);
|
|
3181
3395
|
if (parsed !== void 0) {
|
|
3182
3396
|
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
3183
3397
|
}
|
|
@@ -3187,22 +3401,22 @@ function readAllProviders(fs7, providersDir) {
|
|
|
3187
3401
|
}
|
|
3188
3402
|
return result;
|
|
3189
3403
|
}
|
|
3190
|
-
function writeSession(
|
|
3191
|
-
|
|
3192
|
-
|
|
3404
|
+
function writeSession(fs10, sessionsDir, session) {
|
|
3405
|
+
fs10.mkdirp(sessionsDir);
|
|
3406
|
+
fs10.writeFileUtf8(import_node_path8.default.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
3193
3407
|
`);
|
|
3194
3408
|
}
|
|
3195
|
-
function removeSession(
|
|
3196
|
-
|
|
3409
|
+
function removeSession(fs10, sessionsDir, pid) {
|
|
3410
|
+
fs10.removeRecursive(import_node_path8.default.join(sessionsDir, `${String(pid)}.json`));
|
|
3197
3411
|
}
|
|
3198
|
-
function listSessions(
|
|
3412
|
+
function listSessions(fs10, sessionsDir) {
|
|
3199
3413
|
const sessions = [];
|
|
3200
|
-
for (const name of
|
|
3414
|
+
for (const name of fs10.readdir(sessionsDir)) {
|
|
3201
3415
|
if (!name.endsWith(".json")) {
|
|
3202
3416
|
continue;
|
|
3203
3417
|
}
|
|
3204
3418
|
try {
|
|
3205
|
-
const parsed = parseJson(
|
|
3419
|
+
const parsed = parseJson(fs10.readFileUtf8(import_node_path8.default.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
3206
3420
|
if (parsed !== void 0) {
|
|
3207
3421
|
sessions.push(parsed);
|
|
3208
3422
|
}
|
|
@@ -3212,22 +3426,22 @@ function listSessions(fs7, sessionsDir) {
|
|
|
3212
3426
|
}
|
|
3213
3427
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
3214
3428
|
}
|
|
3215
|
-
function sessionsForSupervisor(
|
|
3216
|
-
return listSessions(
|
|
3429
|
+
function sessionsForSupervisor(fs10, sessionsDir, supervisorPid) {
|
|
3430
|
+
return listSessions(fs10, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
3217
3431
|
}
|
|
3218
|
-
function pruneDeadSessions(
|
|
3432
|
+
function pruneDeadSessions(fs10, sessionsDir, isRunning) {
|
|
3219
3433
|
const removed = [];
|
|
3220
|
-
for (const session of listSessions(
|
|
3434
|
+
for (const session of listSessions(fs10, sessionsDir)) {
|
|
3221
3435
|
if (!isRunning(session.pid)) {
|
|
3222
|
-
removeSession(
|
|
3436
|
+
removeSession(fs10, sessionsDir, session.pid);
|
|
3223
3437
|
removed.push(session.pid);
|
|
3224
3438
|
}
|
|
3225
3439
|
}
|
|
3226
3440
|
return removed;
|
|
3227
3441
|
}
|
|
3228
|
-
function readStartLock(
|
|
3442
|
+
function readStartLock(fs10, lockFile) {
|
|
3229
3443
|
try {
|
|
3230
|
-
return parseJson(
|
|
3444
|
+
return parseJson(fs10.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
3231
3445
|
} catch {
|
|
3232
3446
|
return void 0;
|
|
3233
3447
|
}
|
|
@@ -3307,7 +3521,10 @@ function ensureFrontDoor(params) {
|
|
|
3307
3521
|
}
|
|
3308
3522
|
|
|
3309
3523
|
// src/usage/read.ts
|
|
3310
|
-
var
|
|
3524
|
+
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
3525
|
+
|
|
3526
|
+
// src/usage/account.ts
|
|
3527
|
+
var import_node_path9 = __toESM(require("node:path"), 1);
|
|
3311
3528
|
|
|
3312
3529
|
// src/usage/schema.ts
|
|
3313
3530
|
var import_zod5 = require("zod");
|
|
@@ -3457,11 +3674,111 @@ var UsageSnapshotSchema = import_zod5.z.strictObject({
|
|
|
3457
3674
|
});
|
|
3458
3675
|
|
|
3459
3676
|
// src/usage/account.ts
|
|
3677
|
+
var CLAUDE_JSON = ".claude.json";
|
|
3678
|
+
var AccountMetadataError = class extends CliError {
|
|
3679
|
+
constructor(file, detail) {
|
|
3680
|
+
super(`${file}: ${detail}, so its account metadata cannot be read.`);
|
|
3681
|
+
this.file = file;
|
|
3682
|
+
this.name = "AccountMetadataError";
|
|
3683
|
+
}
|
|
3684
|
+
file;
|
|
3685
|
+
};
|
|
3686
|
+
function isObject(value) {
|
|
3687
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3688
|
+
}
|
|
3460
3689
|
function isIdentityName(name) {
|
|
3461
3690
|
return IdentitySchema.shape.name.safeParse(name).success;
|
|
3462
3691
|
}
|
|
3692
|
+
function claudeJsonPath(identitiesDir, identity) {
|
|
3693
|
+
return import_node_path9.default.join(identitiesDir, identity, CLAUDE_JSON);
|
|
3694
|
+
}
|
|
3695
|
+
function readAccountMetadata(fs10, identitiesDir, identity) {
|
|
3696
|
+
if (!isIdentityName(identity)) {
|
|
3697
|
+
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3698
|
+
}
|
|
3699
|
+
const file = claudeJsonPath(identitiesDir, identity);
|
|
3700
|
+
const raw = fs10.readFileUtf8(file);
|
|
3701
|
+
if (raw === void 0) {
|
|
3702
|
+
return void 0;
|
|
3703
|
+
}
|
|
3704
|
+
let parsed;
|
|
3705
|
+
try {
|
|
3706
|
+
parsed = JSON.parse(raw);
|
|
3707
|
+
} catch {
|
|
3708
|
+
throw new AccountMetadataError(file, "not valid JSON");
|
|
3709
|
+
}
|
|
3710
|
+
if (!isObject(parsed)) {
|
|
3711
|
+
throw new AccountMetadataError(file, "not a JSON object");
|
|
3712
|
+
}
|
|
3713
|
+
const oauthAccount = parsed.oauthAccount;
|
|
3714
|
+
if (!isObject(oauthAccount)) {
|
|
3715
|
+
return void 0;
|
|
3716
|
+
}
|
|
3717
|
+
const picked = {};
|
|
3718
|
+
for (const key of Object.keys(AccountMetadataSchema.shape)) {
|
|
3719
|
+
const value = oauthAccount[key];
|
|
3720
|
+
if (value !== null && value !== void 0) {
|
|
3721
|
+
picked[key] = value;
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
const account = AccountMetadataSchema.safeParse(picked);
|
|
3725
|
+
if (!account.success) {
|
|
3726
|
+
throw new AccountMetadataError(file, account.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
|
|
3727
|
+
}
|
|
3728
|
+
return account.data;
|
|
3729
|
+
}
|
|
3463
3730
|
|
|
3464
3731
|
// src/usage/read.ts
|
|
3732
|
+
var SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
|
|
3733
|
+
var DAY_MS = 864e5;
|
|
3734
|
+
function listLogSegments(fs10, logDir) {
|
|
3735
|
+
return fs10.readdir(logDir).flatMap((name) => {
|
|
3736
|
+
const match = SEGMENT_RE.exec(name);
|
|
3737
|
+
const day = match?.[1];
|
|
3738
|
+
if (day === void 0) {
|
|
3739
|
+
return [];
|
|
3740
|
+
}
|
|
3741
|
+
return [{ name, day, endsAt: Date.parse(`${day}T00:00:00.000Z`) + DAY_MS }];
|
|
3742
|
+
}).sort((left, right) => left.day.localeCompare(right.day) || left.name.localeCompare(right.name));
|
|
3743
|
+
}
|
|
3744
|
+
function readUsageLog(fs10, logDir, options = {}) {
|
|
3745
|
+
const { sinceMs } = options;
|
|
3746
|
+
const records = [];
|
|
3747
|
+
let invalidLines = 0;
|
|
3748
|
+
for (const segment of listLogSegments(fs10, logDir)) {
|
|
3749
|
+
if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
|
|
3750
|
+
continue;
|
|
3751
|
+
}
|
|
3752
|
+
const contents = fs10.readFileUtf8(import_node_path10.default.join(logDir, segment.name));
|
|
3753
|
+
if (contents === void 0) {
|
|
3754
|
+
continue;
|
|
3755
|
+
}
|
|
3756
|
+
const lines = contents.split("\n");
|
|
3757
|
+
lines.pop();
|
|
3758
|
+
for (const line of lines) {
|
|
3759
|
+
if (line === "") {
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
let raw;
|
|
3763
|
+
try {
|
|
3764
|
+
raw = JSON.parse(line);
|
|
3765
|
+
} catch {
|
|
3766
|
+
invalidLines += 1;
|
|
3767
|
+
continue;
|
|
3768
|
+
}
|
|
3769
|
+
const parsed = UsageRecordSchema.safeParse(raw);
|
|
3770
|
+
if (!parsed.success) {
|
|
3771
|
+
invalidLines += 1;
|
|
3772
|
+
continue;
|
|
3773
|
+
}
|
|
3774
|
+
if (sinceMs === void 0 || Date.parse(parsed.data.at) >= sinceMs) {
|
|
3775
|
+
records.push(parsed.data);
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
records.sort((left, right) => left.at.localeCompare(right.at));
|
|
3780
|
+
return { records, invalidLines };
|
|
3781
|
+
}
|
|
3465
3782
|
var UsageSnapshotError = class extends Error {
|
|
3466
3783
|
constructor(file, detail) {
|
|
3467
3784
|
super(`${file} is not a usage snapshot this agent-shim can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
|
|
@@ -3474,11 +3791,11 @@ function snapshotPath(snapshotsDir, identity) {
|
|
|
3474
3791
|
if (!isIdentityName(identity)) {
|
|
3475
3792
|
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3476
3793
|
}
|
|
3477
|
-
return
|
|
3794
|
+
return import_node_path10.default.join(snapshotsDir, `${identity}.json`);
|
|
3478
3795
|
}
|
|
3479
|
-
function readUsageSnapshot(
|
|
3796
|
+
function readUsageSnapshot(fs10, snapshotsDir, identity) {
|
|
3480
3797
|
const file = snapshotPath(snapshotsDir, identity);
|
|
3481
|
-
const raw =
|
|
3798
|
+
const raw = fs10.readFileUtf8(file);
|
|
3482
3799
|
if (raw === void 0) {
|
|
3483
3800
|
return void 0;
|
|
3484
3801
|
}
|
|
@@ -3494,8 +3811,8 @@ function readUsageSnapshot(fs7, snapshotsDir, identity) {
|
|
|
3494
3811
|
}
|
|
3495
3812
|
return snapshot.data;
|
|
3496
3813
|
}
|
|
3497
|
-
function listUsageSnapshots(
|
|
3498
|
-
return
|
|
3814
|
+
function listUsageSnapshots(fs10, snapshotsDir) {
|
|
3815
|
+
return fs10.readdir(snapshotsDir).flatMap((name) => {
|
|
3499
3816
|
if (!name.endsWith(".json")) {
|
|
3500
3817
|
return [];
|
|
3501
3818
|
}
|
|
@@ -3503,7 +3820,7 @@ function listUsageSnapshots(fs7, snapshotsDir) {
|
|
|
3503
3820
|
if (!isIdentityName(identity)) {
|
|
3504
3821
|
return [];
|
|
3505
3822
|
}
|
|
3506
|
-
const snapshot = readUsageSnapshot(
|
|
3823
|
+
const snapshot = readUsageSnapshot(fs10, snapshotsDir, identity);
|
|
3507
3824
|
return snapshot === void 0 ? [] : [snapshot];
|
|
3508
3825
|
}).sort((left, right) => left.identity.localeCompare(right.identity));
|
|
3509
3826
|
}
|
|
@@ -3596,6 +3913,14 @@ var import_zod6 = require("zod");
|
|
|
3596
3913
|
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
3597
3914
|
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
3598
3915
|
var DirectUrlSchema = import_zod6.z.object({ vcs_info: import_zod6.z.object({ commit_id: import_zod6.z.string() }) });
|
|
3916
|
+
function isMovingGitSource(spec) {
|
|
3917
|
+
const match = GIT_SPEC_RE.exec(spec);
|
|
3918
|
+
if (match === null) {
|
|
3919
|
+
return false;
|
|
3920
|
+
}
|
|
3921
|
+
const ref = match[1];
|
|
3922
|
+
return ref === void 0 || !COMMIT_SHA_RE.test(ref);
|
|
3923
|
+
}
|
|
3599
3924
|
function pinnedGitCommit(spec) {
|
|
3600
3925
|
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
3601
3926
|
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
@@ -3670,9 +3995,9 @@ function compareVersions(a, b) {
|
|
|
3670
3995
|
}
|
|
3671
3996
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
3672
3997
|
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
3673
|
-
const { fs:
|
|
3998
|
+
const { fs: fs10, paths } = ports;
|
|
3674
3999
|
let config = initialConfig;
|
|
3675
|
-
const previousState = readHeadroomState(
|
|
4000
|
+
const previousState = readHeadroomState(fs10, paths.headroomStateFile);
|
|
3676
4001
|
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3677
4002
|
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3678
4003
|
return 0;
|
|
@@ -3681,19 +4006,19 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3681
4006
|
let lastPort = previousState?.lastPort;
|
|
3682
4007
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3683
4008
|
const ownsState = () => {
|
|
3684
|
-
const owner = readHeadroomState(
|
|
4009
|
+
const owner = readHeadroomState(fs10, paths.headroomStateFile)?.supervisorPid;
|
|
3685
4010
|
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3686
4011
|
};
|
|
3687
4012
|
const writeOwnState = (state) => {
|
|
3688
4013
|
if (!ownsState()) {
|
|
3689
4014
|
return false;
|
|
3690
4015
|
}
|
|
3691
|
-
writeHeadroomState(
|
|
4016
|
+
writeHeadroomState(fs10, paths.headroomStateFile, state);
|
|
3692
4017
|
return true;
|
|
3693
4018
|
};
|
|
3694
4019
|
const fail = (message) => {
|
|
3695
4020
|
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3696
|
-
|
|
4021
|
+
fs10.removeRecursive(paths.headroomLockFile);
|
|
3697
4022
|
}
|
|
3698
4023
|
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
3699
4024
|
return 1;
|
|
@@ -3742,7 +4067,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3742
4067
|
const settingsHash = hashSettings(config.settings);
|
|
3743
4068
|
const allowlist = allowlistOf(ports);
|
|
3744
4069
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3745
|
-
pruneDeadSessions(
|
|
4070
|
+
pruneDeadSessions(fs10, paths.headroomSessionsDir, ports.isRunning);
|
|
3746
4071
|
const ownsNow = ownsState();
|
|
3747
4072
|
if (!ownsNow && !superseded) {
|
|
3748
4073
|
ports.log("agent-shim headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
@@ -3794,7 +4119,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3794
4119
|
...sticky()
|
|
3795
4120
|
});
|
|
3796
4121
|
if (recorded) {
|
|
3797
|
-
|
|
4122
|
+
fs10.removeRecursive(paths.headroomLockFile);
|
|
3798
4123
|
}
|
|
3799
4124
|
idleSince = void 0;
|
|
3800
4125
|
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
@@ -3809,7 +4134,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3809
4134
|
}
|
|
3810
4135
|
} else {
|
|
3811
4136
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3812
|
-
if (sessionsForSupervisor(
|
|
4137
|
+
if (sessionsForSupervisor(fs10, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3813
4138
|
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3814
4139
|
ports.stopProcess(headroomPid);
|
|
3815
4140
|
headroomPid = void 0;
|
|
@@ -3818,7 +4143,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3818
4143
|
continue;
|
|
3819
4144
|
}
|
|
3820
4145
|
}
|
|
3821
|
-
const sessions = sessionsForSupervisor(
|
|
4146
|
+
const sessions = sessionsForSupervisor(fs10, paths.headroomSessionsDir, ports.ownPid);
|
|
3822
4147
|
if (sessions.length === 0) {
|
|
3823
4148
|
idleSince ??= ports.now();
|
|
3824
4149
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3840,9 +4165,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3840
4165
|
await ports.sleep(HEADROOM_POLL_MS);
|
|
3841
4166
|
}
|
|
3842
4167
|
}
|
|
3843
|
-
function currentHeadroomAllowlist(
|
|
3844
|
-
const providers = readAllProviders(
|
|
3845
|
-
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(
|
|
4168
|
+
function currentHeadroomAllowlist(fs10, paths) {
|
|
4169
|
+
const providers = readAllProviders(fs10, paths.providersDir).map((entry) => entry.provider);
|
|
4170
|
+
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs10, paths.frontdoorStateFile))));
|
|
3846
4171
|
}
|
|
3847
4172
|
function allowlistOf(ports) {
|
|
3848
4173
|
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
@@ -3910,14 +4235,14 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3910
4235
|
// src/paths.ts
|
|
3911
4236
|
var import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
3912
4237
|
var import_node_os = __toESM(require("node:os"), 1);
|
|
3913
|
-
var
|
|
4238
|
+
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
3914
4239
|
function resolveAgentShimHome(env = process.env, home = import_node_os.default.homedir(), isDirectory = isExistingDirectory) {
|
|
3915
4240
|
const fromEnv = env.AGENT_SHIM_HOME;
|
|
3916
4241
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3917
4242
|
return fromEnv;
|
|
3918
4243
|
}
|
|
3919
|
-
const current =
|
|
3920
|
-
const legacy =
|
|
4244
|
+
const current = import_node_path11.default.join(home, HOME_DIRNAME);
|
|
4245
|
+
const legacy = import_node_path11.default.join(home, LEGACY_HOME_DIRNAME);
|
|
3921
4246
|
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
3922
4247
|
}
|
|
3923
4248
|
function isExistingDirectory(candidate) {
|
|
@@ -3928,38 +4253,38 @@ function resolveClaudeHome() {
|
|
|
3928
4253
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3929
4254
|
return fromEnv;
|
|
3930
4255
|
}
|
|
3931
|
-
return
|
|
4256
|
+
return import_node_path11.default.join(import_node_os.default.homedir(), ".claude");
|
|
3932
4257
|
}
|
|
3933
4258
|
function buildLayoutPaths(root) {
|
|
3934
4259
|
return {
|
|
3935
4260
|
root,
|
|
3936
|
-
identitiesDir:
|
|
3937
|
-
configProfilesDir:
|
|
3938
|
-
providersDir:
|
|
3939
|
-
directoryRulesFile:
|
|
3940
|
-
activeIdentityFile:
|
|
3941
|
-
globalConfigFile:
|
|
3942
|
-
categoriesLocalFile:
|
|
3943
|
-
claudeShimFile:
|
|
3944
|
-
headroomDir:
|
|
3945
|
-
headroomStateFile:
|
|
3946
|
-
headroomLockFile:
|
|
3947
|
-
headroomSessionsDir:
|
|
3948
|
-
logsDir:
|
|
3949
|
-
headroomLogPath:
|
|
3950
|
-
frontdoorDir:
|
|
3951
|
-
frontdoorCaDir:
|
|
3952
|
-
frontdoorCaCertFile:
|
|
3953
|
-
frontdoorCaKeyFile:
|
|
3954
|
-
frontdoorCaBundlesDir:
|
|
3955
|
-
frontdoorStateFile:
|
|
3956
|
-
frontdoorLockFile:
|
|
3957
|
-
frontdoorSessionsDir:
|
|
3958
|
-
frontdoorLogPath:
|
|
3959
|
-
usageDir:
|
|
3960
|
-
usageLogDir:
|
|
3961
|
-
usageSnapshotsDir:
|
|
3962
|
-
usagePicksFile:
|
|
4261
|
+
identitiesDir: import_node_path11.default.join(root, "identities"),
|
|
4262
|
+
configProfilesDir: import_node_path11.default.join(root, "config-profiles"),
|
|
4263
|
+
providersDir: import_node_path11.default.join(root, "providers"),
|
|
4264
|
+
directoryRulesFile: import_node_path11.default.join(root, "directory-rules.json"),
|
|
4265
|
+
activeIdentityFile: import_node_path11.default.join(root, "active-identity"),
|
|
4266
|
+
globalConfigFile: import_node_path11.default.join(root, "config.json"),
|
|
4267
|
+
categoriesLocalFile: import_node_path11.default.join(root, "categories.local.json"),
|
|
4268
|
+
claudeShimFile: import_node_path11.default.join(root, "claude-shim.json"),
|
|
4269
|
+
headroomDir: import_node_path11.default.join(root, "headroom"),
|
|
4270
|
+
headroomStateFile: import_node_path11.default.join(root, "headroom", "state.json"),
|
|
4271
|
+
headroomLockFile: import_node_path11.default.join(root, "headroom", "start.lock"),
|
|
4272
|
+
headroomSessionsDir: import_node_path11.default.join(root, "headroom", "sessions"),
|
|
4273
|
+
logsDir: import_node_path11.default.join(root, "logs"),
|
|
4274
|
+
headroomLogPath: import_node_path11.default.join(root, "logs", "headroom.log"),
|
|
4275
|
+
frontdoorDir: import_node_path11.default.join(root, "frontdoor"),
|
|
4276
|
+
frontdoorCaDir: import_node_path11.default.join(root, "frontdoor", "ca"),
|
|
4277
|
+
frontdoorCaCertFile: import_node_path11.default.join(root, "frontdoor", "ca", "ca.pem"),
|
|
4278
|
+
frontdoorCaKeyFile: import_node_path11.default.join(root, "frontdoor", "ca", "ca.key"),
|
|
4279
|
+
frontdoorCaBundlesDir: import_node_path11.default.join(root, "frontdoor", "ca", "bundles"),
|
|
4280
|
+
frontdoorStateFile: import_node_path11.default.join(root, "frontdoor", "state.json"),
|
|
4281
|
+
frontdoorLockFile: import_node_path11.default.join(root, "frontdoor", "start.lock"),
|
|
4282
|
+
frontdoorSessionsDir: import_node_path11.default.join(root, "frontdoor", "sessions"),
|
|
4283
|
+
frontdoorLogPath: import_node_path11.default.join(root, "logs", "frontdoor.log"),
|
|
4284
|
+
usageDir: import_node_path11.default.join(root, "usage"),
|
|
4285
|
+
usageLogDir: import_node_path11.default.join(root, "usage", "log"),
|
|
4286
|
+
usageSnapshotsDir: import_node_path11.default.join(root, "usage", "snapshots"),
|
|
4287
|
+
usagePicksFile: import_node_path11.default.join(root, "usage", "picks.json")
|
|
3963
4288
|
};
|
|
3964
4289
|
}
|
|
3965
4290
|
function resolveLayoutPaths() {
|
|
@@ -3968,16 +4293,64 @@ function resolveLayoutPaths() {
|
|
|
3968
4293
|
|
|
3969
4294
|
// src/identityStore.ts
|
|
3970
4295
|
var import_node_fs4 = __toESM(require("node:fs"), 1);
|
|
3971
|
-
var
|
|
4296
|
+
var import_node_path14 = __toESM(require("node:path"), 1);
|
|
3972
4297
|
|
|
3973
4298
|
// src/launcher/identity.ts
|
|
4299
|
+
var import_node_path12 = __toESM(require("node:path"), 1);
|
|
3974
4300
|
function poolNameOf(selector) {
|
|
3975
4301
|
return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
|
|
3976
4302
|
}
|
|
4303
|
+
function isNonEmpty(value) {
|
|
4304
|
+
return value !== void 0 && value !== "";
|
|
4305
|
+
}
|
|
4306
|
+
function selected(selector, source) {
|
|
4307
|
+
const pool = poolNameOf(selector);
|
|
4308
|
+
return pool === void 0 ? { name: selector, source, configDirEscapeHatch: false } : { pool, source, configDirEscapeHatch: false };
|
|
4309
|
+
}
|
|
4310
|
+
function decideIdentity(params) {
|
|
4311
|
+
if (isNonEmpty(params.env.CLAUDE_CONFIG_DIR)) {
|
|
4312
|
+
return { source: "config-dir-escape-hatch", configDirEscapeHatch: true };
|
|
4313
|
+
}
|
|
4314
|
+
if (isNonEmpty(params.argv0Identity)) {
|
|
4315
|
+
return selected(params.argv0Identity, "argv");
|
|
4316
|
+
}
|
|
4317
|
+
if (isNonEmpty(params.env.AGENT_SHIM_IDENTITY)) {
|
|
4318
|
+
return selected(params.env.AGENT_SHIM_IDENTITY, "env");
|
|
4319
|
+
}
|
|
4320
|
+
if (isNonEmpty(params.directoryPinnedIdentity)) {
|
|
4321
|
+
return selected(params.directoryPinnedIdentity, "directory-pin");
|
|
4322
|
+
}
|
|
4323
|
+
const persisted = params.readActiveIdentityFile();
|
|
4324
|
+
if (isNonEmpty(persisted)) {
|
|
4325
|
+
return selected(persisted, "active-identity-file");
|
|
4326
|
+
}
|
|
4327
|
+
return { source: "none", configDirEscapeHatch: false };
|
|
4328
|
+
}
|
|
4329
|
+
function decideConfigProfile(params) {
|
|
4330
|
+
if (isNonEmpty(params.cliFlagConfigProfile)) {
|
|
4331
|
+
return { name: params.cliFlagConfigProfile, source: "cli-flag" };
|
|
4332
|
+
}
|
|
4333
|
+
if (isNonEmpty(params.env.AGENT_SHIM_CONFIG_PROFILE)) {
|
|
4334
|
+
return { name: params.env.AGENT_SHIM_CONFIG_PROFILE, source: "env" };
|
|
4335
|
+
}
|
|
4336
|
+
if (isNonEmpty(params.directoryRuleConfigProfile)) {
|
|
4337
|
+
return { name: params.directoryRuleConfigProfile, source: "directory-rule" };
|
|
4338
|
+
}
|
|
4339
|
+
if (isNonEmpty(params.identityDefaultConfigProfile)) {
|
|
4340
|
+
return { name: params.identityDefaultConfigProfile, source: "identity-default" };
|
|
4341
|
+
}
|
|
4342
|
+
if (isNonEmpty(params.globalDefaultConfigProfile)) {
|
|
4343
|
+
return { name: params.globalDefaultConfigProfile, source: "global-default" };
|
|
4344
|
+
}
|
|
4345
|
+
return { source: "none" };
|
|
4346
|
+
}
|
|
4347
|
+
function loadIdentity(identitiesDir, name, fs10) {
|
|
4348
|
+
return loadConfigFile(import_node_path12.default.join(identitiesDir, name, "identity.json"), IdentitySchema, fs10.readConfigFile);
|
|
4349
|
+
}
|
|
3977
4350
|
|
|
3978
4351
|
// src/configProfilesStore.ts
|
|
3979
4352
|
var import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
3980
|
-
var
|
|
4353
|
+
var import_node_path13 = __toESM(require("node:path"), 1);
|
|
3981
4354
|
var ProfileNotFoundError = class extends CliError {
|
|
3982
4355
|
constructor(profileName) {
|
|
3983
4356
|
super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
|
|
@@ -4003,7 +4376,7 @@ var InvalidCategoryNameError = class extends CliError {
|
|
|
4003
4376
|
categoryName;
|
|
4004
4377
|
};
|
|
4005
4378
|
function profileJsonPath(paths, name) {
|
|
4006
|
-
return
|
|
4379
|
+
return import_node_path13.default.join(paths.configProfilesDir, `${name}.json`);
|
|
4007
4380
|
}
|
|
4008
4381
|
function profileExists(paths, name) {
|
|
4009
4382
|
return import_node_fs3.default.existsSync(profileJsonPath(paths, name));
|
|
@@ -4191,7 +4564,7 @@ var InvalidIdentityNameError = class extends CliError {
|
|
|
4191
4564
|
attemptedName;
|
|
4192
4565
|
};
|
|
4193
4566
|
function identityJsonPath(paths, name) {
|
|
4194
|
-
return
|
|
4567
|
+
return import_node_path14.default.join(paths.identitiesDir, name, "identity.json");
|
|
4195
4568
|
}
|
|
4196
4569
|
function identityExists(paths, name) {
|
|
4197
4570
|
return import_node_fs4.default.existsSync(identityJsonPath(paths, name));
|
|
@@ -4292,7 +4665,7 @@ function setIdentityCredential(paths, identityName, change) {
|
|
|
4292
4665
|
});
|
|
4293
4666
|
}
|
|
4294
4667
|
function belongsToIdentity(entry, name) {
|
|
4295
|
-
return entry === name || entry ===
|
|
4668
|
+
return entry === name || entry === import_node_path14.default.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
|
|
4296
4669
|
}
|
|
4297
4670
|
function removeIdentity(paths, name) {
|
|
4298
4671
|
if (!identityExists(paths, name)) {
|
|
@@ -4300,7 +4673,7 @@ function removeIdentity(paths, name) {
|
|
|
4300
4673
|
}
|
|
4301
4674
|
for (const entry of import_node_fs4.default.readdirSync(paths.identitiesDir)) {
|
|
4302
4675
|
if (belongsToIdentity(entry, name)) {
|
|
4303
|
-
import_node_fs4.default.rmSync(
|
|
4676
|
+
import_node_fs4.default.rmSync(import_node_path14.default.join(paths.identitiesDir, entry), { recursive: true, force: true });
|
|
4304
4677
|
}
|
|
4305
4678
|
}
|
|
4306
4679
|
if (readActiveIdentity(paths) === name) {
|
|
@@ -4310,7 +4683,7 @@ function removeIdentity(paths, name) {
|
|
|
4310
4683
|
|
|
4311
4684
|
// src/providersStore.ts
|
|
4312
4685
|
var import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
4313
|
-
var
|
|
4686
|
+
var import_node_path15 = __toESM(require("node:path"), 1);
|
|
4314
4687
|
|
|
4315
4688
|
// src/credentialCache.ts
|
|
4316
4689
|
var import_zod7 = require("zod");
|
|
@@ -4320,6 +4693,62 @@ var CachedCredentialSchema = import_zod7.z.strictObject({
|
|
|
4320
4693
|
source: CredentialSourceSchema
|
|
4321
4694
|
});
|
|
4322
4695
|
|
|
4696
|
+
// src/credential.ts
|
|
4697
|
+
function summariseSource(source) {
|
|
4698
|
+
if ("env" in source) {
|
|
4699
|
+
return { kind: "env", variable: source.env };
|
|
4700
|
+
}
|
|
4701
|
+
if ("file" in source) {
|
|
4702
|
+
return { kind: "file", path: source.file };
|
|
4703
|
+
}
|
|
4704
|
+
if ("command" in source) {
|
|
4705
|
+
return { kind: "command", program: source.command[0] };
|
|
4706
|
+
}
|
|
4707
|
+
if ("op" in source) {
|
|
4708
|
+
return { kind: "op", reference: source.op };
|
|
4709
|
+
}
|
|
4710
|
+
if ("keychain" in source) {
|
|
4711
|
+
return {
|
|
4712
|
+
kind: "keychain",
|
|
4713
|
+
service: source.keychain.service,
|
|
4714
|
+
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
4715
|
+
};
|
|
4716
|
+
}
|
|
4717
|
+
return { kind: "literal" };
|
|
4718
|
+
}
|
|
4719
|
+
function summariseCredential(credential) {
|
|
4720
|
+
return {
|
|
4721
|
+
target: credential.target ?? "bearer",
|
|
4722
|
+
sources: credential.sources.map(summariseSource),
|
|
4723
|
+
...credential.cache === void 0 ? {} : { cache: credential.cache }
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
function formatSourceSummary(summary) {
|
|
4727
|
+
switch (summary.kind) {
|
|
4728
|
+
case "env":
|
|
4729
|
+
return `env ${summary.variable}`;
|
|
4730
|
+
case "file":
|
|
4731
|
+
return `file ${summary.path}`;
|
|
4732
|
+
case "command":
|
|
4733
|
+
return `command ${summary.program}`;
|
|
4734
|
+
case "op":
|
|
4735
|
+
return `op ${summary.reference}`;
|
|
4736
|
+
case "keychain":
|
|
4737
|
+
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
4738
|
+
case "literal":
|
|
4739
|
+
return "literal (non-secret placeholder)";
|
|
4740
|
+
default:
|
|
4741
|
+
return summary;
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
function formatCredentialSummary(summary) {
|
|
4745
|
+
const cache = summary.cache === void 0 ? "" : `, cached ${summary.cache.ttl === void 0 ? "with no expiry" : `for ${summary.cache.ttl}`}${summary.cache.store === void 0 ? "" : ` in the ${summary.cache.store} store`}`;
|
|
4746
|
+
return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
|
|
4747
|
+
}
|
|
4748
|
+
function describeCredential(credential) {
|
|
4749
|
+
return formatCredentialSummary(summariseCredential(credential));
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4323
4752
|
// src/providersStore.ts
|
|
4324
4753
|
var ProviderNotFoundError = class extends CliError {
|
|
4325
4754
|
constructor(name) {
|
|
@@ -4349,7 +4778,7 @@ var InvalidProviderNameError = class extends CliError {
|
|
|
4349
4778
|
};
|
|
4350
4779
|
var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
|
|
4351
4780
|
function providerJsonPath(paths, name) {
|
|
4352
|
-
return
|
|
4781
|
+
return import_node_path15.default.join(paths.providersDir, `${name}.json`);
|
|
4353
4782
|
}
|
|
4354
4783
|
function providerExists(paths, name) {
|
|
4355
4784
|
return import_node_fs5.default.existsSync(providerJsonPath(paths, name));
|
|
@@ -4357,14 +4786,14 @@ function providerExists(paths, name) {
|
|
|
4357
4786
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
4358
4787
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
4359
4788
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
4360
|
-
function
|
|
4789
|
+
function isRecord2(value) {
|
|
4361
4790
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4362
4791
|
}
|
|
4363
4792
|
function legacyProviderConversion(raw) {
|
|
4364
|
-
if (!
|
|
4793
|
+
if (!isRecord2(raw) || "credential" in raw) {
|
|
4365
4794
|
return void 0;
|
|
4366
4795
|
}
|
|
4367
|
-
const env =
|
|
4796
|
+
const env = isRecord2(raw.env) ? raw.env : void 0;
|
|
4368
4797
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
4369
4798
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
4370
4799
|
if (fields.length === 0) {
|
|
@@ -4547,6 +4976,15 @@ function removeProvider(paths, name) {
|
|
|
4547
4976
|
}
|
|
4548
4977
|
import_node_fs5.default.rmSync(providerJsonPath(paths, name));
|
|
4549
4978
|
}
|
|
4979
|
+
function cascadeProviderName(cascade) {
|
|
4980
|
+
let name;
|
|
4981
|
+
for (const layer of assembleCascade(cascade).layers) {
|
|
4982
|
+
if (layer.launch?.provider !== void 0) {
|
|
4983
|
+
name = layer.launch.provider;
|
|
4984
|
+
}
|
|
4985
|
+
}
|
|
4986
|
+
return name;
|
|
4987
|
+
}
|
|
4550
4988
|
|
|
4551
4989
|
// src/directoryRulesStore.ts
|
|
4552
4990
|
var DirectoryRuleNotFoundError = class extends CliError {
|
|
@@ -4637,6 +5075,1722 @@ function removeDirectoryRule(paths, rulePath) {
|
|
|
4637
5075
|
}
|
|
4638
5076
|
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
4639
5077
|
}
|
|
5078
|
+
|
|
5079
|
+
// src/checkReport.ts
|
|
5080
|
+
var import_node_os3 = __toESM(require("node:os"), 1);
|
|
5081
|
+
|
|
5082
|
+
// src/launcher/cascade.ts
|
|
5083
|
+
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
5084
|
+
|
|
5085
|
+
// src/pathNorm.ts
|
|
5086
|
+
var import_node_path16 = __toESM(require("node:path"), 1);
|
|
5087
|
+
function expandTilde(value, home) {
|
|
5088
|
+
if (value === "~") {
|
|
5089
|
+
return home;
|
|
5090
|
+
}
|
|
5091
|
+
if (value.startsWith("~/")) {
|
|
5092
|
+
return import_node_path16.default.join(home, value.slice(2));
|
|
5093
|
+
}
|
|
5094
|
+
return value;
|
|
5095
|
+
}
|
|
5096
|
+
function normaliseRulePath(rulePath, home) {
|
|
5097
|
+
return import_node_path16.default.resolve(expandTilde(rulePath, home));
|
|
5098
|
+
}
|
|
5099
|
+
|
|
5100
|
+
// src/launcher/cascade.ts
|
|
5101
|
+
var PORTABLE_CONFIG_FILENAME = ".agent-shim.json";
|
|
5102
|
+
var PORTABLE_LOCAL_CONFIG_FILENAME = ".agent-shim.local.json";
|
|
5103
|
+
function loadCascadeInput(params) {
|
|
5104
|
+
const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
|
|
5105
|
+
const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
|
|
5106
|
+
const loadProfile = (name) => {
|
|
5107
|
+
const filepath = import_node_path17.default.join(params.paths.configProfilesDir, `${name}.json`);
|
|
5108
|
+
const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
|
|
5109
|
+
if (loaded === void 0) {
|
|
5110
|
+
return void 0;
|
|
5111
|
+
}
|
|
5112
|
+
return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
|
|
5113
|
+
};
|
|
5114
|
+
const limit = globalConfig?.config.walkUpLimit;
|
|
5115
|
+
const dirs = walkDirectoryAncestors(params.cwd, {
|
|
5116
|
+
home: params.home,
|
|
5117
|
+
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
5118
|
+
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
5119
|
+
});
|
|
5120
|
+
const levels = [];
|
|
5121
|
+
for (const dir of dirs) {
|
|
5122
|
+
const portable = loadConfigFile(import_node_path17.default.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path17.default.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
5123
|
+
const portableLocal = loadConfigFile(import_node_path17.default.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path17.default.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
5124
|
+
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
5125
|
+
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
5126
|
+
return {
|
|
5127
|
+
rule,
|
|
5128
|
+
...entryOrder === void 0 ? {} : { entryOrder },
|
|
5129
|
+
filepath: params.paths.directoryRulesFile
|
|
5130
|
+
};
|
|
5131
|
+
});
|
|
5132
|
+
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
5133
|
+
continue;
|
|
5134
|
+
}
|
|
5135
|
+
levels.push({
|
|
5136
|
+
dir,
|
|
5137
|
+
...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
|
|
5138
|
+
...rules.length === 0 ? {} : { rules },
|
|
5139
|
+
...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
5142
|
+
const input = {
|
|
5143
|
+
home: params.home,
|
|
5144
|
+
...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
|
|
5145
|
+
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
5146
|
+
loadProfile,
|
|
5147
|
+
levels,
|
|
5148
|
+
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
5149
|
+
};
|
|
5150
|
+
return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
|
|
5151
|
+
}
|
|
5152
|
+
function readDirectorySelections(loaded) {
|
|
5153
|
+
let identity;
|
|
5154
|
+
let configProfile;
|
|
5155
|
+
for (const level of loaded.input.levels ?? []) {
|
|
5156
|
+
for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
|
|
5157
|
+
if (source === void 0) {
|
|
5158
|
+
continue;
|
|
5159
|
+
}
|
|
5160
|
+
if (source.identity !== void 0) {
|
|
5161
|
+
identity = source.identity;
|
|
5162
|
+
}
|
|
5163
|
+
if (source.configProfile !== void 0) {
|
|
5164
|
+
configProfile = source.configProfile;
|
|
5165
|
+
}
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
|
|
5169
|
+
}
|
|
5170
|
+
|
|
5171
|
+
// src/usage/preflight.ts
|
|
5172
|
+
var MS_PER_SECOND2 = 1e3;
|
|
5173
|
+
var SECONDS_PER_MINUTE = 60;
|
|
5174
|
+
var MINUTES_PER_HOUR = 60;
|
|
5175
|
+
var HOURS_PER_DAY = 24;
|
|
5176
|
+
function formatAge(ms) {
|
|
5177
|
+
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
5178
|
+
if (minutes < MINUTES_PER_HOUR) {
|
|
5179
|
+
return `${String(Math.max(minutes, 0))}m`;
|
|
5180
|
+
}
|
|
5181
|
+
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5182
|
+
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5183
|
+
}
|
|
5184
|
+
function effectiveWindow(window, nowMs) {
|
|
5185
|
+
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5186
|
+
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5187
|
+
return { reset: true, utilization: 0 };
|
|
5188
|
+
}
|
|
5189
|
+
return {
|
|
5190
|
+
reset: false,
|
|
5191
|
+
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5192
|
+
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5193
|
+
...window.status === void 0 ? {} : { status: window.status }
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
// src/usage/poolPick.ts
|
|
5198
|
+
var import_zod9 = require("zod");
|
|
5199
|
+
|
|
5200
|
+
// src/usage/plan.ts
|
|
5201
|
+
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
5202
|
+
var ZERO_TIER = /(?:^|_)zero$/;
|
|
5203
|
+
var BASE_TIER = /(?:^|_)(?:pro|free)$/;
|
|
5204
|
+
function planOf(account) {
|
|
5205
|
+
const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
|
|
5206
|
+
const tierField = tier === void 0 ? {} : { tier };
|
|
5207
|
+
if (tier === void 0) {
|
|
5208
|
+
return { kind: "subscription", capacity: 1, recognised: false };
|
|
5209
|
+
}
|
|
5210
|
+
if (ZERO_TIER.test(tier)) {
|
|
5211
|
+
return { kind: "pay-per-use", ...tierField };
|
|
5212
|
+
}
|
|
5213
|
+
const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
|
|
5214
|
+
if (multiplier !== void 0) {
|
|
5215
|
+
return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
|
|
5216
|
+
}
|
|
5217
|
+
return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5220
|
+
// src/usage/rateLimit.ts
|
|
5221
|
+
var MS_PER_SECOND3 = 1e3;
|
|
5222
|
+
function epochSecondsToIso(value) {
|
|
5223
|
+
if (value === void 0) {
|
|
5224
|
+
return void 0;
|
|
5225
|
+
}
|
|
5226
|
+
const seconds = Number(value);
|
|
5227
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
|
|
5228
|
+
}
|
|
5229
|
+
function nonNegativeNumber(value) {
|
|
5230
|
+
if (value === void 0 || value.trim() === "") {
|
|
5231
|
+
return void 0;
|
|
5232
|
+
}
|
|
5233
|
+
const parsed = Number(value);
|
|
5234
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
5235
|
+
}
|
|
5236
|
+
var UNIFIED_PREFIX = "anthropic-ratelimit-unified-";
|
|
5237
|
+
function unifiedWindow(headers, window) {
|
|
5238
|
+
const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
|
|
5239
|
+
const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
|
|
5240
|
+
const status = headers[`${UNIFIED_PREFIX}${window}-status`];
|
|
5241
|
+
if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
|
|
5242
|
+
return void 0;
|
|
5243
|
+
}
|
|
5244
|
+
return { ...utilization === void 0 ? {} : { utilization }, ...resetsAt === void 0 ? {} : { resetsAt }, ...status === void 0 ? {} : { status } };
|
|
5245
|
+
}
|
|
5246
|
+
function parseUnifiedRateLimit(headers) {
|
|
5247
|
+
if (!Object.keys(headers).some((name) => name.startsWith(UNIFIED_PREFIX))) {
|
|
5248
|
+
return void 0;
|
|
5249
|
+
}
|
|
5250
|
+
const status = headers[`${UNIFIED_PREFIX}status`];
|
|
5251
|
+
const fiveHour = unifiedWindow(headers, "5h");
|
|
5252
|
+
const sevenDay = unifiedWindow(headers, "7d");
|
|
5253
|
+
const representativeClaim = headers[`${UNIFIED_PREFIX}representative-claim`];
|
|
5254
|
+
const resetAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}reset`]);
|
|
5255
|
+
const overageStatus = headers[`${UNIFIED_PREFIX}overage-status`];
|
|
5256
|
+
return {
|
|
5257
|
+
...status === void 0 ? {} : { status },
|
|
5258
|
+
...fiveHour === void 0 ? {} : { fiveHour },
|
|
5259
|
+
...sevenDay === void 0 ? {} : { sevenDay },
|
|
5260
|
+
...representativeClaim === void 0 ? {} : { representativeClaim },
|
|
5261
|
+
...resetAt === void 0 ? {} : { resetAt },
|
|
5262
|
+
...overageStatus === void 0 ? {} : { overageStatus }
|
|
5263
|
+
};
|
|
5264
|
+
}
|
|
5265
|
+
|
|
5266
|
+
// src/usage/scan.ts
|
|
5267
|
+
var import_zod8 = require("zod");
|
|
5268
|
+
var AnthropicUsageSchema = import_zod8.z.looseObject({
|
|
5269
|
+
input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5270
|
+
output_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5271
|
+
cache_creation_input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5272
|
+
cache_read_input_tokens: import_zod8.z.number().int().nonnegative().nullish()
|
|
5273
|
+
});
|
|
5274
|
+
var ErrorObjectSchema = import_zod8.z.looseObject({
|
|
5275
|
+
type: import_zod8.z.string().optional(),
|
|
5276
|
+
code: import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number()]).optional(),
|
|
5277
|
+
message: import_zod8.z.string().optional()
|
|
5278
|
+
});
|
|
5279
|
+
|
|
5280
|
+
// src/usage/middleware.ts
|
|
5281
|
+
var ANTHROPIC_PROVIDER = "anthropic";
|
|
5282
|
+
|
|
5283
|
+
// src/usage/pick.ts
|
|
5284
|
+
var MS_PER_SECOND4 = 1e3;
|
|
5285
|
+
var MS_PER_HOUR = 36e5;
|
|
5286
|
+
var HOURS_PER_DAY2 = 24;
|
|
5287
|
+
var FIVE_HOURS = 5;
|
|
5288
|
+
var PROMPT_CACHE_TTL_HOURS = 1;
|
|
5289
|
+
var PERCENT = 100;
|
|
5290
|
+
var SEVEN_DAYS = 7;
|
|
5291
|
+
var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
|
|
5292
|
+
var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
|
|
5293
|
+
var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
|
|
5294
|
+
var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
|
|
5295
|
+
function unifiedOf(state) {
|
|
5296
|
+
return state?.rateLimit?.unified;
|
|
5297
|
+
}
|
|
5298
|
+
function limitBlockedUntil(state, nowMs) {
|
|
5299
|
+
const limit = state?.lastLimit;
|
|
5300
|
+
if (state === void 0 || limit === void 0) {
|
|
5301
|
+
return void 0;
|
|
5302
|
+
}
|
|
5303
|
+
if (Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
|
|
5304
|
+
return void 0;
|
|
5305
|
+
}
|
|
5306
|
+
const until = limit.resetAt === void 0 ? limit.retryAfterSeconds === void 0 ? void 0 : Date.parse(limit.observedAt) + limit.retryAfterSeconds * MS_PER_SECOND4 : Date.parse(limit.resetAt);
|
|
5307
|
+
return until !== void 0 && until > nowMs ? until : void 0;
|
|
5308
|
+
}
|
|
5309
|
+
function windowBlockedUntil(windows) {
|
|
5310
|
+
const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
|
|
5311
|
+
return resets.length === 0 ? void 0 : Math.max(...resets);
|
|
5312
|
+
}
|
|
5313
|
+
function fiveHourBurn(records) {
|
|
5314
|
+
const points = [];
|
|
5315
|
+
for (const record of records) {
|
|
5316
|
+
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
|
|
5317
|
+
if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
|
|
5318
|
+
points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
const latest = points.at(-1)?.resetsAt;
|
|
5322
|
+
const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
|
|
5323
|
+
const first = current.at(0);
|
|
5324
|
+
const last = current.at(-1);
|
|
5325
|
+
if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
|
|
5326
|
+
return void 0;
|
|
5327
|
+
}
|
|
5328
|
+
return (last.utilization - first.utilization) / (last.atMs - first.atMs);
|
|
5329
|
+
}
|
|
5330
|
+
function percent(fraction) {
|
|
5331
|
+
return `${String(Math.round(fraction * PERCENT))}%`;
|
|
5332
|
+
}
|
|
5333
|
+
function describeWindow(label, window, effective, nowMs) {
|
|
5334
|
+
if (effective.reset) {
|
|
5335
|
+
return `${label} window has reset`;
|
|
5336
|
+
}
|
|
5337
|
+
const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
|
|
5338
|
+
const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
|
|
5339
|
+
return `${label} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
|
|
5340
|
+
}
|
|
5341
|
+
function capacityOf(plan) {
|
|
5342
|
+
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
5343
|
+
}
|
|
5344
|
+
function assess(member, nowMs) {
|
|
5345
|
+
const plan = planOf(member.account);
|
|
5346
|
+
const planReasons = plan.kind === "pay-per-use" ? ["billed by use (no plan allowance)"] : plan.recognised ? [`${String(plan.capacity)}x plan`] : [`plan tier ${plan.tier === void 0 ? "not known" : `"${plan.tier}" not recognised`}, counted as 1x`];
|
|
5347
|
+
const base = { identity: member.identity, plan };
|
|
5348
|
+
if (member.readError !== void 0) {
|
|
5349
|
+
return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
|
|
5350
|
+
}
|
|
5351
|
+
const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
|
|
5352
|
+
const unified = unifiedOf(state);
|
|
5353
|
+
if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
|
|
5354
|
+
return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
|
|
5355
|
+
}
|
|
5356
|
+
const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
|
|
5357
|
+
const fiveHourWindow = unified.fiveHour;
|
|
5358
|
+
const sevenDayWindow = unified.sevenDay;
|
|
5359
|
+
const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
|
|
5360
|
+
const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
|
|
5361
|
+
const windowReasons = [
|
|
5362
|
+
...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
|
|
5363
|
+
...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
|
|
5364
|
+
];
|
|
5365
|
+
const blockedUntil = Math.max(
|
|
5366
|
+
windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
|
|
5367
|
+
limitBlockedUntil(state, nowMs) ?? 0
|
|
5368
|
+
);
|
|
5369
|
+
if (blockedUntil > nowMs) {
|
|
5370
|
+
if (unified.overageStatus === "allowed") {
|
|
5371
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
|
|
5372
|
+
}
|
|
5373
|
+
return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
|
|
5374
|
+
}
|
|
5375
|
+
if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
|
|
5376
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"]] } };
|
|
5377
|
+
}
|
|
5378
|
+
const basis = seven?.utilization !== void 0 ? { window: seven, windowMs: SEVEN_DAY_WINDOW_MS } : five?.utilization !== void 0 ? { window: five, windowMs: FIVE_HOUR_WINDOW_MS } : void 0;
|
|
5379
|
+
if (basis?.window.utilization === void 0) {
|
|
5380
|
+
return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
|
|
5381
|
+
}
|
|
5382
|
+
const remaining = Math.max(0, 1 - basis.window.utilization);
|
|
5383
|
+
const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5384
|
+
const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
|
|
5385
|
+
const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
|
|
5386
|
+
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5387
|
+
const burn = fiveHourBurn(member.records);
|
|
5388
|
+
return {
|
|
5389
|
+
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
5390
|
+
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
5391
|
+
...burn === void 0 ? {} : { burn }
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
5394
|
+
function rankPool(input) {
|
|
5395
|
+
const { members, nowMs } = input;
|
|
5396
|
+
const assessments = members.map((member) => assess(member, nowMs));
|
|
5397
|
+
const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
|
|
5398
|
+
const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
|
|
5399
|
+
const candidates = assessments.map((assessment) => {
|
|
5400
|
+
const { candidate, fiveHour, burn } = assessment;
|
|
5401
|
+
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
5402
|
+
if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
|
|
5403
|
+
return { ...candidate, feasible: true };
|
|
5404
|
+
}
|
|
5405
|
+
const runsDryInMs = fiveHour.remaining / pace;
|
|
5406
|
+
if (runsDryInMs >= fiveHour.untilResetMs) {
|
|
5407
|
+
return { ...candidate, feasible: true };
|
|
5408
|
+
}
|
|
5409
|
+
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
5410
|
+
});
|
|
5411
|
+
const ordered = [...candidates].sort(
|
|
5412
|
+
(left, right) => CLASS_ORDER[left.class] - CLASS_ORDER[right.class] || Number(right.feasible) - Number(left.feasible) || (right.score ?? 0) - (left.score ?? 0) || left.identity.localeCompare(right.identity)
|
|
5413
|
+
);
|
|
5414
|
+
const sticky = input.sticky;
|
|
5415
|
+
const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
|
|
5416
|
+
const stickyCandidate = ordered[stickyIndex];
|
|
5417
|
+
if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
|
|
5418
|
+
const ageMs = nowMs - Date.parse(sticky.at);
|
|
5419
|
+
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
5420
|
+
if (input.resuming || warm) {
|
|
5421
|
+
const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
|
|
5422
|
+
ordered.splice(stickyIndex, 1);
|
|
5423
|
+
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
const pick = ordered.find((candidate) => candidate.class !== "ineligible");
|
|
5427
|
+
const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
|
|
5428
|
+
const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
|
|
5429
|
+
return {
|
|
5430
|
+
candidates: ordered,
|
|
5431
|
+
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
|
|
5432
|
+
};
|
|
5433
|
+
}
|
|
5434
|
+
|
|
5435
|
+
// src/usage/store.ts
|
|
5436
|
+
var DAY_MS2 = 864e5;
|
|
5437
|
+
var DAYS_IN_WEEK = 7;
|
|
5438
|
+
var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
5439
|
+
|
|
5440
|
+
// src/usage/poolPick.ts
|
|
5441
|
+
var SCHEMA_VERSION = 1;
|
|
5442
|
+
var PickEntrySchema = import_zod9.z.strictObject({ identity: import_zod9.z.string().min(1), at: import_zod9.z.iso.datetime() });
|
|
5443
|
+
var PicksFileSchema = import_zod9.z.strictObject({
|
|
5444
|
+
schemaVersion: import_zod9.z.literal(SCHEMA_VERSION),
|
|
5445
|
+
picks: import_zod9.z.record(import_zod9.z.string().min(1), PickEntrySchema)
|
|
5446
|
+
});
|
|
5447
|
+
function errorMessage(error) {
|
|
5448
|
+
return error instanceof Error ? error.message : String(error);
|
|
5449
|
+
}
|
|
5450
|
+
function loadPoolMembers(fs10, paths, identities, nowMs) {
|
|
5451
|
+
const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
|
|
5452
|
+
const log = readUsageLog(fs10, paths.usageLogDir, { sinceMs: windowStartMs });
|
|
5453
|
+
return identities.map((identity) => {
|
|
5454
|
+
const records = log.records.filter((record) => record.identity === identity && record.provider === ANTHROPIC_PROVIDER && Date.parse(record.at) >= windowStartMs);
|
|
5455
|
+
try {
|
|
5456
|
+
const snapshot = readUsageSnapshot(fs10, paths.usageSnapshotsDir, identity);
|
|
5457
|
+
const account = readAccountMetadata(fs10, paths.identitiesDir, identity) ?? snapshot?.account;
|
|
5458
|
+
return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
|
|
5459
|
+
} catch (error) {
|
|
5460
|
+
if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
|
|
5461
|
+
return { identity, records, readError: errorMessage(error) };
|
|
5462
|
+
}
|
|
5463
|
+
throw error;
|
|
5464
|
+
}
|
|
5465
|
+
});
|
|
5466
|
+
}
|
|
5467
|
+
function rankPoolFromStore(params) {
|
|
5468
|
+
return rankPool({
|
|
5469
|
+
members: loadPoolMembers(params.fs, params.paths, params.identities, params.nowMs),
|
|
5470
|
+
nowMs: params.nowMs,
|
|
5471
|
+
resuming: params.resuming,
|
|
5472
|
+
...params.sticky === void 0 ? {} : { sticky: params.sticky }
|
|
5473
|
+
});
|
|
5474
|
+
}
|
|
5475
|
+
function readPicksFile(fs10, file) {
|
|
5476
|
+
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
5477
|
+
const raw = fs10.readFileUtf8(file);
|
|
5478
|
+
if (raw === void 0) {
|
|
5479
|
+
return { file: empty };
|
|
5480
|
+
}
|
|
5481
|
+
let parsed;
|
|
5482
|
+
try {
|
|
5483
|
+
parsed = JSON.parse(raw);
|
|
5484
|
+
} catch {
|
|
5485
|
+
return { file: empty, problem: `${file} is not valid JSON` };
|
|
5486
|
+
}
|
|
5487
|
+
const result = PicksFileSchema.safeParse(parsed);
|
|
5488
|
+
return result.success ? { file: result.data } : { file: empty, problem: `${file} is not a picks file this agent-shim can read` };
|
|
5489
|
+
}
|
|
5490
|
+
function readStickyPick(fs10, picksFile, directory) {
|
|
5491
|
+
const read = readPicksFile(fs10, picksFile);
|
|
5492
|
+
const entry = read.file.picks[directory];
|
|
5493
|
+
return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
|
|
5494
|
+
}
|
|
5495
|
+
|
|
5496
|
+
// src/launcher/pool.ts
|
|
5497
|
+
function splitMembers(pool, paths, fs10) {
|
|
5498
|
+
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs10) !== void 0;
|
|
5499
|
+
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
5500
|
+
}
|
|
5501
|
+
|
|
5502
|
+
// src/poolPickReport.ts
|
|
5503
|
+
function candidateView(candidate) {
|
|
5504
|
+
return {
|
|
5505
|
+
identity: candidate.identity,
|
|
5506
|
+
class: candidate.class,
|
|
5507
|
+
...candidate.score === void 0 ? {} : { score: candidate.score },
|
|
5508
|
+
feasible: candidate.feasible,
|
|
5509
|
+
...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
|
|
5510
|
+
plan: candidate.plan,
|
|
5511
|
+
reasons: candidate.reasons
|
|
5512
|
+
};
|
|
5513
|
+
}
|
|
5514
|
+
function collectPoolPick(params) {
|
|
5515
|
+
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
5516
|
+
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
5517
|
+
const ranking = rankPoolFromStore({
|
|
5518
|
+
fs: params.usageFs,
|
|
5519
|
+
paths: params.paths,
|
|
5520
|
+
identities: present,
|
|
5521
|
+
nowMs: params.nowMs,
|
|
5522
|
+
resuming: false,
|
|
5523
|
+
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
5524
|
+
});
|
|
5525
|
+
return {
|
|
5526
|
+
pool: params.poolName,
|
|
5527
|
+
directory: params.directory,
|
|
5528
|
+
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
5529
|
+
candidates: ranking.candidates.map(candidateView),
|
|
5530
|
+
missing,
|
|
5531
|
+
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
5532
|
+
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
// src/realPorts.ts
|
|
5537
|
+
var import_node_child_process = require("node:child_process");
|
|
5538
|
+
var import_node_crypto8 = require("node:crypto");
|
|
5539
|
+
var import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
5540
|
+
var import_node_path19 = __toESM(require("node:path"), 1);
|
|
5541
|
+
var import_node_sea = require("node:sea");
|
|
5542
|
+
|
|
5543
|
+
// src/versionDiscovery.ts
|
|
5544
|
+
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
5545
|
+
var import_node_path18 = __toESM(require("node:path"), 1);
|
|
5546
|
+
function defaultVersionsDir() {
|
|
5547
|
+
return import_node_path18.default.join(import_node_os2.default.homedir(), ".local", "share", "claude", "versions");
|
|
5548
|
+
}
|
|
5549
|
+
var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
|
|
5550
|
+
function isNumericDottedVersion(name) {
|
|
5551
|
+
return NUMERIC_DOTTED_VERSION_RE.test(name);
|
|
5552
|
+
}
|
|
5553
|
+
function compareVersions2(a, b) {
|
|
5554
|
+
if (!isNumericDottedVersion(a)) {
|
|
5555
|
+
throw new Error(`compareVersions: "${a}" is not a valid plain dotted-numeric version`);
|
|
5556
|
+
}
|
|
5557
|
+
if (!isNumericDottedVersion(b)) {
|
|
5558
|
+
throw new Error(`compareVersions: "${b}" is not a valid plain dotted-numeric version`);
|
|
5559
|
+
}
|
|
5560
|
+
const segmentsA = a.split(".").map(Number);
|
|
5561
|
+
const segmentsB = b.split(".").map(Number);
|
|
5562
|
+
const length = Math.max(segmentsA.length, segmentsB.length);
|
|
5563
|
+
for (let i = 0; i < length; i += 1) {
|
|
5564
|
+
const segmentA = segmentsA[i] ?? 0;
|
|
5565
|
+
const segmentB = segmentsB[i] ?? 0;
|
|
5566
|
+
if (segmentA !== segmentB) {
|
|
5567
|
+
return segmentA - segmentB;
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
return 0;
|
|
5571
|
+
}
|
|
5572
|
+
function pickHighestVersion(entries) {
|
|
5573
|
+
const candidates = entries.filter(
|
|
5574
|
+
(entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)
|
|
5575
|
+
);
|
|
5576
|
+
if (candidates.length === 0) {
|
|
5577
|
+
return void 0;
|
|
5578
|
+
}
|
|
5579
|
+
return candidates.reduce(
|
|
5580
|
+
(highest, candidate) => compareVersions2(candidate.name, highest.name) > 0 ? candidate : highest
|
|
5581
|
+
).name;
|
|
5582
|
+
}
|
|
5583
|
+
function discoverClaudeBinary(options) {
|
|
5584
|
+
const versionsDir = options.versionsDir ?? defaultVersionsDir();
|
|
5585
|
+
const entries = options.listVersionsDir(versionsDir);
|
|
5586
|
+
const highestVersion = pickHighestVersion(entries);
|
|
5587
|
+
if (highestVersion !== void 0) {
|
|
5588
|
+
return {
|
|
5589
|
+
path: import_node_path18.default.join(versionsDir, highestVersion),
|
|
5590
|
+
source: "versions-dir",
|
|
5591
|
+
version: highestVersion
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => import_node_path18.default.resolve(dir)));
|
|
5595
|
+
for (const dir of options.pathDirs) {
|
|
5596
|
+
if (ownInstallDirs.has(import_node_path18.default.resolve(dir))) {
|
|
5597
|
+
continue;
|
|
5598
|
+
}
|
|
5599
|
+
const found = options.findExecutableInDir(dir, "claude");
|
|
5600
|
+
if (found !== void 0) {
|
|
5601
|
+
return { path: found, source: "path-fallback" };
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
throw new Error(
|
|
5605
|
+
`Could not find a claude binary. Looked for genuinely-executable versioned binaries under "${versionsDir}" (missing, empty, or containing nothing executable) and for a "claude" executable on PATH (excluding this tool's own install directory). Install Claude Code, or set the versions directory / PATH correctly, then try again.`
|
|
5606
|
+
);
|
|
5607
|
+
}
|
|
5608
|
+
|
|
5609
|
+
// src/realPorts.ts
|
|
5610
|
+
function isEnoent3(error) {
|
|
5611
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5612
|
+
}
|
|
5613
|
+
var realFsPort = {
|
|
5614
|
+
readFileUtf8(filePath) {
|
|
5615
|
+
try {
|
|
5616
|
+
return import_node_fs6.default.readFileSync(filePath, "utf8");
|
|
5617
|
+
} catch (error) {
|
|
5618
|
+
if (isEnoent3(error)) {
|
|
5619
|
+
return void 0;
|
|
5620
|
+
}
|
|
5621
|
+
throw error;
|
|
5622
|
+
}
|
|
5623
|
+
},
|
|
5624
|
+
readConfigFile: cosmiconfigReader(),
|
|
5625
|
+
readdir(dirPath) {
|
|
5626
|
+
try {
|
|
5627
|
+
return import_node_fs6.default.readdirSync(dirPath);
|
|
5628
|
+
} catch (error) {
|
|
5629
|
+
if (isEnoent3(error)) {
|
|
5630
|
+
return [];
|
|
5631
|
+
}
|
|
5632
|
+
throw error;
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
};
|
|
5636
|
+
function isErrorWithCode(error, code) {
|
|
5637
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
5638
|
+
}
|
|
5639
|
+
var OWNER_ONLY_DIR_MODE = 448;
|
|
5640
|
+
var OWNER_ONLY_FILE_MODE = 384;
|
|
5641
|
+
var PERMISSION_BITS = 511;
|
|
5642
|
+
var realFarmFs = {
|
|
5643
|
+
lstat(filePath) {
|
|
5644
|
+
let stat;
|
|
5645
|
+
try {
|
|
5646
|
+
stat = import_node_fs6.default.lstatSync(filePath);
|
|
5647
|
+
} catch (error) {
|
|
5648
|
+
if (isEnoent3(error)) {
|
|
5649
|
+
return void 0;
|
|
5650
|
+
}
|
|
5651
|
+
throw error;
|
|
5652
|
+
}
|
|
5653
|
+
const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
|
|
5654
|
+
return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
|
|
5655
|
+
},
|
|
5656
|
+
readdir(dirPath) {
|
|
5657
|
+
try {
|
|
5658
|
+
return import_node_fs6.default.readdirSync(dirPath);
|
|
5659
|
+
} catch (error) {
|
|
5660
|
+
if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
|
|
5661
|
+
return [];
|
|
5662
|
+
}
|
|
5663
|
+
throw error;
|
|
5664
|
+
}
|
|
5665
|
+
},
|
|
5666
|
+
mkdirp(dirPath) {
|
|
5667
|
+
import_node_fs6.default.mkdirSync(dirPath, { recursive: true });
|
|
5668
|
+
},
|
|
5669
|
+
symlink(target, linkPath) {
|
|
5670
|
+
import_node_fs6.default.symlinkSync(target, linkPath);
|
|
5671
|
+
},
|
|
5672
|
+
rename(from, to) {
|
|
5673
|
+
import_node_fs6.default.renameSync(from, to);
|
|
5674
|
+
},
|
|
5675
|
+
removeRecursive(targetPath) {
|
|
5676
|
+
import_node_fs6.default.rmSync(targetPath, { recursive: true, force: true });
|
|
5677
|
+
},
|
|
5678
|
+
copyRecursive(from, to) {
|
|
5679
|
+
import_node_fs6.default.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5680
|
+
},
|
|
5681
|
+
readFileUtf8(filePath) {
|
|
5682
|
+
try {
|
|
5683
|
+
return import_node_fs6.default.readFileSync(filePath, "utf8");
|
|
5684
|
+
} catch (error) {
|
|
5685
|
+
if (isEnoent3(error)) {
|
|
5686
|
+
return void 0;
|
|
5687
|
+
}
|
|
5688
|
+
throw error;
|
|
5689
|
+
}
|
|
5690
|
+
},
|
|
5691
|
+
writeFileUtf8(filePath, contents) {
|
|
5692
|
+
import_node_fs6.default.writeFileSync(filePath, contents, "utf8");
|
|
5693
|
+
},
|
|
5694
|
+
mkdirPrivate(dirPath) {
|
|
5695
|
+
import_node_fs6.default.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
|
|
5696
|
+
import_node_fs6.default.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
|
|
5697
|
+
},
|
|
5698
|
+
writeFilePrivate(filePath, contents) {
|
|
5699
|
+
const temp = `${filePath}.${String(process.pid)}.tmp`;
|
|
5700
|
+
import_node_fs6.default.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
|
|
5701
|
+
import_node_fs6.default.chmodSync(temp, OWNER_ONLY_FILE_MODE);
|
|
5702
|
+
import_node_fs6.default.renameSync(temp, filePath);
|
|
5703
|
+
},
|
|
5704
|
+
appendFilePrivate(filePath, contents) {
|
|
5705
|
+
const descriptor = import_node_fs6.default.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
|
|
5706
|
+
try {
|
|
5707
|
+
if ((import_node_fs6.default.fstatSync(descriptor).mode & PERMISSION_BITS) !== OWNER_ONLY_FILE_MODE) {
|
|
5708
|
+
import_node_fs6.default.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
|
|
5709
|
+
}
|
|
5710
|
+
const bytes = Buffer.from(contents, "utf8");
|
|
5711
|
+
let written = 0;
|
|
5712
|
+
while (written < bytes.length) {
|
|
5713
|
+
written += import_node_fs6.default.writeSync(descriptor, bytes, written, bytes.length - written);
|
|
5714
|
+
}
|
|
5715
|
+
} finally {
|
|
5716
|
+
import_node_fs6.default.closeSync(descriptor);
|
|
5717
|
+
}
|
|
5718
|
+
},
|
|
5719
|
+
writeFileExclusive(filePath, contents) {
|
|
5720
|
+
try {
|
|
5721
|
+
import_node_fs6.default.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
|
|
5722
|
+
return true;
|
|
5723
|
+
} catch (error) {
|
|
5724
|
+
if (isErrorWithCode(error, "EEXIST")) {
|
|
5725
|
+
return false;
|
|
5726
|
+
}
|
|
5727
|
+
throw error;
|
|
5728
|
+
}
|
|
5729
|
+
},
|
|
5730
|
+
hashFile(filePath) {
|
|
5731
|
+
try {
|
|
5732
|
+
return (0, import_node_crypto8.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
|
|
5733
|
+
} catch (error) {
|
|
5734
|
+
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
5735
|
+
return void 0;
|
|
5736
|
+
}
|
|
5737
|
+
throw error;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
};
|
|
5741
|
+
function realIsProcessAlive(pid) {
|
|
5742
|
+
try {
|
|
5743
|
+
process.kill(pid, 0);
|
|
5744
|
+
return true;
|
|
5745
|
+
} catch (error) {
|
|
5746
|
+
return isErrorWithCode(error, "EPERM");
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5749
|
+
function realIsProcessZombie(pid) {
|
|
5750
|
+
const result = (0, import_node_child_process.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
|
|
5751
|
+
return result.status === 0 && result.stdout.trim().startsWith("Z");
|
|
5752
|
+
}
|
|
5753
|
+
function realIsProcessRunning(pid) {
|
|
5754
|
+
return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
|
|
5755
|
+
}
|
|
5756
|
+
var realRunPort = {
|
|
5757
|
+
run(command, args) {
|
|
5758
|
+
const result = (0, import_node_child_process.spawnSync)(command, [...args], { encoding: "utf8" });
|
|
5759
|
+
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
function resolveGitBranch(run, cwd) {
|
|
5763
|
+
const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
5764
|
+
if (result.status !== 0) {
|
|
5765
|
+
return {};
|
|
5766
|
+
}
|
|
5767
|
+
const name = result.stdout.trim();
|
|
5768
|
+
if (name === "") {
|
|
5769
|
+
return {};
|
|
5770
|
+
}
|
|
5771
|
+
if (name === "HEAD") {
|
|
5772
|
+
return { branchDetached: true };
|
|
5773
|
+
}
|
|
5774
|
+
return { branch: name, branchDetached: false };
|
|
5775
|
+
}
|
|
5776
|
+
var realProcPort = {
|
|
5777
|
+
env: process.env,
|
|
5778
|
+
argv: process.argv.slice(2),
|
|
5779
|
+
exit: (code) => process.exit(code)
|
|
5780
|
+
};
|
|
5781
|
+
var EXECUTE_BITS_MASK = 73;
|
|
5782
|
+
function listVersionsDir(dir) {
|
|
5783
|
+
let entries;
|
|
5784
|
+
try {
|
|
5785
|
+
entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
|
|
5786
|
+
} catch (error) {
|
|
5787
|
+
if (isEnoent3(error)) {
|
|
5788
|
+
return [];
|
|
5789
|
+
}
|
|
5790
|
+
throw error;
|
|
5791
|
+
}
|
|
5792
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
5793
|
+
const fullPath = import_node_path19.default.join(dir, entry.name);
|
|
5794
|
+
const stat = import_node_fs6.default.statSync(fullPath);
|
|
5795
|
+
return {
|
|
5796
|
+
name: entry.name,
|
|
5797
|
+
isFile: true,
|
|
5798
|
+
isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
|
|
5799
|
+
sizeBytes: stat.size
|
|
5800
|
+
};
|
|
5801
|
+
});
|
|
5802
|
+
}
|
|
5803
|
+
var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
|
|
5804
|
+
function windowsPathExtensions(pathext) {
|
|
5805
|
+
return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
|
|
5806
|
+
}
|
|
5807
|
+
function resolveExecutableCandidate(dir, name, env) {
|
|
5808
|
+
const candidate = import_node_path19.default.join(dir, name);
|
|
5809
|
+
if (env.platform === "win32") {
|
|
5810
|
+
const extensions = windowsPathExtensions(env.pathext);
|
|
5811
|
+
const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
|
|
5812
|
+
if (alreadyHasRecognisedExtension) {
|
|
5813
|
+
return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
|
|
5814
|
+
}
|
|
5815
|
+
for (const ext of extensions) {
|
|
5816
|
+
const withExt = candidate + ext;
|
|
5817
|
+
if (env.statFileMode(withExt) !== void 0) {
|
|
5818
|
+
return withExt;
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
return void 0;
|
|
5822
|
+
}
|
|
5823
|
+
const mode = env.statFileMode(candidate);
|
|
5824
|
+
return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
|
|
5825
|
+
}
|
|
5826
|
+
function realStatFileMode(candidate) {
|
|
5827
|
+
try {
|
|
5828
|
+
const stat = import_node_fs6.default.statSync(candidate);
|
|
5829
|
+
return stat.isFile() ? stat.mode : void 0;
|
|
5830
|
+
} catch (error) {
|
|
5831
|
+
if (isEnoent3(error)) {
|
|
5832
|
+
return void 0;
|
|
5833
|
+
}
|
|
5834
|
+
throw error;
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
function findExecutableInDir(dir, name) {
|
|
5838
|
+
return resolveExecutableCandidate(dir, name, {
|
|
5839
|
+
platform: process.platform,
|
|
5840
|
+
pathext: process.env.PATHEXT,
|
|
5841
|
+
statFileMode: realStatFileMode
|
|
5842
|
+
});
|
|
5843
|
+
}
|
|
5844
|
+
function searchPathForExecutable(pathDirs, name, findExecutable) {
|
|
5845
|
+
for (const dir of pathDirs) {
|
|
5846
|
+
const found = findExecutable(dir, name);
|
|
5847
|
+
if (found !== void 0) {
|
|
5848
|
+
return found;
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
return void 0;
|
|
5852
|
+
}
|
|
5853
|
+
function resolveOwnExecutablePath(env) {
|
|
5854
|
+
if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
|
|
5855
|
+
return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
|
|
5856
|
+
}
|
|
5857
|
+
const candidate = env.argv1 === void 0 ? env.execPath : import_node_path19.default.resolve(env.cwd, env.argv1);
|
|
5858
|
+
const candidateDir = import_node_path19.default.dirname(candidate);
|
|
5859
|
+
const candidateDirOnPath = env.pathDirs.some((dir) => import_node_path19.default.resolve(dir) === candidateDir);
|
|
5860
|
+
if (candidateDirOnPath) {
|
|
5861
|
+
return candidate;
|
|
5862
|
+
}
|
|
5863
|
+
const viaPath = searchPathForExecutable(env.pathDirs, import_node_path19.default.basename(candidate), env.findExecutableInDir);
|
|
5864
|
+
return viaPath ?? candidate;
|
|
5865
|
+
}
|
|
5866
|
+
function realOwnExecutablePath() {
|
|
5867
|
+
return resolveOwnExecutablePath({
|
|
5868
|
+
argv1: process.argv[1],
|
|
5869
|
+
execPath: process.execPath,
|
|
5870
|
+
cwd: process.cwd(),
|
|
5871
|
+
pathDirs: (process.env.PATH ?? "").split(import_node_path19.default.delimiter).filter((dir) => dir !== ""),
|
|
5872
|
+
findExecutableInDir
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5875
|
+
function realResolveClaudeBinary(ownInstallDirs) {
|
|
5876
|
+
return () => discoverClaudeBinary({
|
|
5877
|
+
listVersionsDir,
|
|
5878
|
+
pathDirs: (process.env.PATH ?? "").split(import_node_path19.default.delimiter).filter((dir) => dir !== ""),
|
|
5879
|
+
findExecutableInDir,
|
|
5880
|
+
ownInstallDirs: [...ownInstallDirs]
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5883
|
+
|
|
5884
|
+
// src/checkReport.ts
|
|
5885
|
+
var import_node_path20 = __toESM(require("node:path"), 1);
|
|
5886
|
+
var import_zod10 = require("zod");
|
|
5887
|
+
|
|
5888
|
+
// src/cli/bool.ts
|
|
5889
|
+
function parseBoolWord(input) {
|
|
5890
|
+
switch (input) {
|
|
5891
|
+
case "true":
|
|
5892
|
+
case "1":
|
|
5893
|
+
return true;
|
|
5894
|
+
case "false":
|
|
5895
|
+
case "0":
|
|
5896
|
+
return false;
|
|
5897
|
+
default:
|
|
5898
|
+
return void 0;
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
// src/cli/envBool.ts
|
|
5903
|
+
var InvalidEnvBoolError = class extends UsageError {
|
|
5904
|
+
constructor(variable, value) {
|
|
5905
|
+
super(`${variable} must be one of true, false, 1 or 0, got "${value}".`);
|
|
5906
|
+
this.variable = variable;
|
|
5907
|
+
this.value = value;
|
|
5908
|
+
this.name = "InvalidEnvBoolError";
|
|
5909
|
+
}
|
|
5910
|
+
variable;
|
|
5911
|
+
value;
|
|
5912
|
+
};
|
|
5913
|
+
function parseEnvBool(variable, value) {
|
|
5914
|
+
if (value === void 0 || value === "") {
|
|
5915
|
+
return void 0;
|
|
5916
|
+
}
|
|
5917
|
+
const parsed = parseBoolWord(value);
|
|
5918
|
+
if (parsed === void 0) {
|
|
5919
|
+
throw new InvalidEnvBoolError(variable, value);
|
|
5920
|
+
}
|
|
5921
|
+
return parsed;
|
|
5922
|
+
}
|
|
5923
|
+
|
|
5924
|
+
// src/checkReport.ts
|
|
5925
|
+
var HISTORY_PROJECTS_KEY_PREFIX = "history/projects/";
|
|
5926
|
+
function projectFragmentOf(rawKey) {
|
|
5927
|
+
return rawKey.startsWith(HISTORY_PROJECTS_KEY_PREFIX) ? rawKey.slice(HISTORY_PROJECTS_KEY_PREFIX.length) : void 0;
|
|
5928
|
+
}
|
|
5929
|
+
function existingProjectNames(facts) {
|
|
5930
|
+
const names = /* @__PURE__ */ new Set();
|
|
5931
|
+
const prefix = "projects/";
|
|
5932
|
+
for (const rel of facts.entries.keys()) {
|
|
5933
|
+
if (!rel.startsWith(prefix)) {
|
|
5934
|
+
continue;
|
|
5935
|
+
}
|
|
5936
|
+
const rest = rel.slice(prefix.length);
|
|
5937
|
+
const head = rest.split("/")[0];
|
|
5938
|
+
if (head !== void 0 && head !== "") {
|
|
5939
|
+
names.add(head);
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
return [...names];
|
|
5943
|
+
}
|
|
5944
|
+
function flagAmbiguousEncodings(flattened, facts) {
|
|
5945
|
+
const fragments = [];
|
|
5946
|
+
for (const rule of flattened.rules.values()) {
|
|
5947
|
+
const fragment = projectFragmentOf(rule.rawKey);
|
|
5948
|
+
if (fragment !== void 0) {
|
|
5949
|
+
fragments.push(fragment);
|
|
5950
|
+
}
|
|
5951
|
+
}
|
|
5952
|
+
if (fragments.length === 0) {
|
|
5953
|
+
return [];
|
|
5954
|
+
}
|
|
5955
|
+
return detectEncodingAmbiguity(fragments, { home: facts.home, existingNames: existingProjectNames(facts) });
|
|
5956
|
+
}
|
|
5957
|
+
function formatDecision(decision) {
|
|
5958
|
+
const status = decision.shared ? "shared" : "hidden";
|
|
5959
|
+
let reason;
|
|
5960
|
+
switch (decision.via) {
|
|
5961
|
+
case "secret-floor":
|
|
5962
|
+
reason = "secret (never shared, cannot be overridden by any layer)";
|
|
5963
|
+
break;
|
|
5964
|
+
case "unclassified":
|
|
5965
|
+
reason = "unclassified entry (no category recognises it)";
|
|
5966
|
+
break;
|
|
5967
|
+
case "entry-rule":
|
|
5968
|
+
reason = decision.rule === void 0 ? "an entries rule" : `entries rule "${decision.rule.rawKey}" from layer ${String(decision.rule.layer)}`;
|
|
5969
|
+
break;
|
|
5970
|
+
case "category-override":
|
|
5971
|
+
reason = `category "${decision.category ?? "?"}" overridden by a layer`;
|
|
5972
|
+
break;
|
|
5973
|
+
case "category-default":
|
|
5974
|
+
reason = `category "${decision.category ?? "?"}" shipped default`;
|
|
5975
|
+
break;
|
|
5976
|
+
}
|
|
5977
|
+
const eliminatedNote = decision.eliminated !== void 0 && decision.eliminated.length > 0 ? ` [${String(decision.eliminated.length)} more specific rule(s) eliminated by a failing when-condition]` : "";
|
|
5978
|
+
return `${decision.relPath}: ${status} \u2014 ${reason}${eliminatedNote}`;
|
|
5979
|
+
}
|
|
5980
|
+
function parseKeychainServiceName(stderr) {
|
|
5981
|
+
const match = /"svce"<blob>="([^"]*)"/.exec(stderr);
|
|
5982
|
+
return match?.[1];
|
|
5983
|
+
}
|
|
5984
|
+
function lookupKeychainService(run, farmRoot) {
|
|
5985
|
+
const result = run.run("security", ["find-generic-password", "-a", farmRoot, "-g"]);
|
|
5986
|
+
if (result.status !== 0) {
|
|
5987
|
+
return {
|
|
5988
|
+
checked: true,
|
|
5989
|
+
found: false,
|
|
5990
|
+
note: `No Keychain entry found for account "${farmRoot}" (security exited ${result.status === null ? "with no status" : String(result.status)}).`
|
|
5991
|
+
};
|
|
5992
|
+
}
|
|
5993
|
+
const serviceName = parseKeychainServiceName(result.stderr);
|
|
5994
|
+
return {
|
|
5995
|
+
checked: true,
|
|
5996
|
+
found: true,
|
|
5997
|
+
...serviceName === void 0 ? {} : { serviceName },
|
|
5998
|
+
note: serviceName === void 0 ? `A Keychain entry was found for account "${farmRoot}" but its service name could not be parsed from security's output.` : `Keychain service name for this identity: "${serviceName}".`
|
|
5999
|
+
};
|
|
6000
|
+
}
|
|
6001
|
+
var SettingsSecretsShapeSchema = import_zod10.z.looseObject({
|
|
6002
|
+
env: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.unknown()).optional(),
|
|
6003
|
+
hooks: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.array(import_zod10.z.unknown())).optional()
|
|
6004
|
+
});
|
|
6005
|
+
function hookCommandCountOf(group) {
|
|
6006
|
+
if (typeof group !== "object" || group === null) {
|
|
6007
|
+
return 0;
|
|
6008
|
+
}
|
|
6009
|
+
if (!("hooks" in group)) {
|
|
6010
|
+
return 0;
|
|
6011
|
+
}
|
|
6012
|
+
return Array.isArray(group.hooks) ? group.hooks.length : 0;
|
|
6013
|
+
}
|
|
6014
|
+
function countHookCommands(hooks) {
|
|
6015
|
+
if (hooks === void 0) {
|
|
6016
|
+
return 0;
|
|
6017
|
+
}
|
|
6018
|
+
let total = 0;
|
|
6019
|
+
for (const groups of Object.values(hooks)) {
|
|
6020
|
+
for (const group of groups) {
|
|
6021
|
+
total += hookCommandCountOf(group);
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
return total;
|
|
6025
|
+
}
|
|
6026
|
+
function inspectSettingsExposure(params) {
|
|
6027
|
+
if (!params.settingsShared) {
|
|
6028
|
+
return [];
|
|
6029
|
+
}
|
|
6030
|
+
const reports = [];
|
|
6031
|
+
for (const [file, raw] of Object.entries(params.files)) {
|
|
6032
|
+
if (raw === void 0) {
|
|
6033
|
+
continue;
|
|
6034
|
+
}
|
|
6035
|
+
let parsed;
|
|
6036
|
+
try {
|
|
6037
|
+
parsed = JSON.parse(raw);
|
|
6038
|
+
} catch {
|
|
6039
|
+
continue;
|
|
6040
|
+
}
|
|
6041
|
+
const result = SettingsSecretsShapeSchema.safeParse(parsed);
|
|
6042
|
+
if (!result.success) {
|
|
6043
|
+
continue;
|
|
6044
|
+
}
|
|
6045
|
+
const envKeyNames = Object.keys(result.data.env ?? {});
|
|
6046
|
+
const hookEventNames = Object.keys(result.data.hooks ?? {});
|
|
6047
|
+
const hookCommandCount = countHookCommands(result.data.hooks);
|
|
6048
|
+
if (envKeyNames.length === 0 && hookCommandCount === 0) {
|
|
6049
|
+
continue;
|
|
6050
|
+
}
|
|
6051
|
+
reports.push({ file, envKeyNames, hookEventNames, hookCommandCount });
|
|
6052
|
+
}
|
|
6053
|
+
return reports;
|
|
6054
|
+
}
|
|
6055
|
+
function buildCredentialReport(provider, identity) {
|
|
6056
|
+
const identityCredential = identity?.credential === void 0 ? void 0 : summariseCredential(identity.credential);
|
|
6057
|
+
return {
|
|
6058
|
+
applies: provider !== void 0 ? "provider" : identityCredential !== void 0 ? "identity" : "stored-login",
|
|
6059
|
+
...provider === void 0 ? {} : {
|
|
6060
|
+
provider: provider.problem === void 0 ? { name: provider.name, credential: summariseCredential(provider.definition.credential) } : { name: provider.name, problem: provider.problem }
|
|
6061
|
+
},
|
|
6062
|
+
...identityCredential === void 0 ? {} : { identity: identityCredential }
|
|
6063
|
+
};
|
|
6064
|
+
}
|
|
6065
|
+
function runCheck(params) {
|
|
6066
|
+
const facts = buildEntryFacts({
|
|
6067
|
+
fs: params.farmFs,
|
|
6068
|
+
claudeHome: params.claudeHome,
|
|
6069
|
+
home: params.home,
|
|
6070
|
+
cwd: params.cwd,
|
|
6071
|
+
nowMs: params.nowMs,
|
|
6072
|
+
env: params.env,
|
|
6073
|
+
...params.branch === void 0 ? {} : { branch: params.branch },
|
|
6074
|
+
...params.branchDetached === void 0 ? {} : { branchDetached: params.branchDetached }
|
|
6075
|
+
});
|
|
6076
|
+
const resolved = resolveDecisions({ facts, cascade: params.cascade, classification: params.classification });
|
|
6077
|
+
const decisionLines = [...resolved.decisions.values()].slice().sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0).map(formatDecision);
|
|
6078
|
+
const projectEncodingAmbiguities = flagAmbiguousEncodings(resolved.flattened, facts);
|
|
6079
|
+
const ambientCredential = evaluateAmbientCredentialGuard({
|
|
6080
|
+
env: params.env,
|
|
6081
|
+
allowAmbientCredential: params.identity?.allowAmbientCredential ?? false,
|
|
6082
|
+
allowAmbientCredentialOverride: parseEnvBool("AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL", params.env.AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL) === true,
|
|
6083
|
+
...params.identityName === void 0 ? {} : { identityName: params.identityName }
|
|
6084
|
+
});
|
|
6085
|
+
const settingsShared = resolved.flattened.categories.get("settings") ?? SHIPPED_CATEGORY_DEFAULTS.settings;
|
|
6086
|
+
const settingsExposure = inspectSettingsExposure({ settingsShared, files: params.settingsFiles });
|
|
6087
|
+
const keychain = params.platform === "darwin" && params.run !== void 0 && params.farmRoot !== void 0 ? lookupKeychainService(params.run, params.farmRoot) : void 0;
|
|
6088
|
+
return {
|
|
6089
|
+
...params.identityName === void 0 ? {} : { identityName: params.identityName },
|
|
6090
|
+
identitySource: params.identitySource,
|
|
6091
|
+
...params.poolPick === void 0 ? {} : { poolPick: params.poolPick },
|
|
6092
|
+
...params.configProfileName === void 0 ? {} : { configProfileName: params.configProfileName },
|
|
6093
|
+
configProfileSource: params.configProfileSource,
|
|
6094
|
+
resolved,
|
|
6095
|
+
decisionLines,
|
|
6096
|
+
projectEncodingAmbiguities,
|
|
6097
|
+
ambientCredential,
|
|
6098
|
+
...keychain === void 0 ? {} : { keychain },
|
|
6099
|
+
settingsExposure,
|
|
6100
|
+
credential: buildCredentialReport(params.provider, params.identity)
|
|
6101
|
+
};
|
|
6102
|
+
}
|
|
6103
|
+
function formatCheckReport(report) {
|
|
6104
|
+
const lines = [];
|
|
6105
|
+
lines.push(`Identity: ${report.identityName ?? "(none)"} (${report.identitySource})`);
|
|
6106
|
+
if (report.poolPick !== void 0) {
|
|
6107
|
+
const top = report.poolPick.candidates.find((candidate) => candidate.identity === report.poolPick?.pick);
|
|
6108
|
+
lines.push(`Pool: ${report.poolPick.pool}${top === void 0 ? ", every member is refused right now" : `, would pick ${top.identity} (${top.reasons.join("; ")})`}`);
|
|
6109
|
+
}
|
|
6110
|
+
lines.push(`Configuration profile: ${report.configProfileName ?? "(none)"} (${report.configProfileSource})`);
|
|
6111
|
+
lines.push("", "Layers (shallowest/earliest first):");
|
|
6112
|
+
for (const layer of report.resolved.assembled.layers) {
|
|
6113
|
+
lines.push(` [${String(layer.id)}] ${layer.kind}: ${layer.source}`);
|
|
6114
|
+
}
|
|
6115
|
+
lines.push("", "Resolved entries:");
|
|
6116
|
+
if (report.decisionLines.length === 0) {
|
|
6117
|
+
lines.push(" (nothing under ~/.claude to report)");
|
|
6118
|
+
}
|
|
6119
|
+
for (const line of report.decisionLines) {
|
|
6120
|
+
lines.push(` ${line}`);
|
|
6121
|
+
}
|
|
6122
|
+
if (report.projectEncodingAmbiguities.length > 0) {
|
|
6123
|
+
lines.push("", "Ambiguous history/projects/ encodings:");
|
|
6124
|
+
for (const ambiguity of report.projectEncodingAmbiguities) {
|
|
6125
|
+
lines.push(` "${ambiguity.fragment}" -> "${ambiguity.encoded}" (${ambiguity.reason}): ${ambiguity.detail}`);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
if (report.resolved.diagnostics.length > 0) {
|
|
6129
|
+
lines.push("", "Diagnostics:");
|
|
6130
|
+
for (const diagnostic of report.resolved.diagnostics) {
|
|
6131
|
+
lines.push(` [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
lines.push("", "Credential (source kinds and targets only, never values):");
|
|
6135
|
+
const { credential } = report;
|
|
6136
|
+
if (credential.provider !== void 0) {
|
|
6137
|
+
lines.push(
|
|
6138
|
+
` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem ?? ""}` : formatCredentialSummary(credential.provider.credential)}`
|
|
6139
|
+
);
|
|
6140
|
+
}
|
|
6141
|
+
if (credential.identity !== void 0) {
|
|
6142
|
+
lines.push(
|
|
6143
|
+
` Identity ${report.identityName ?? "(none)"}: ${formatCredentialSummary(credential.identity)}${credential.applies === "provider" ? " (not used: the provider's credential applies)" : ""}`
|
|
6144
|
+
);
|
|
6145
|
+
}
|
|
6146
|
+
if (credential.applies === "stored-login") {
|
|
6147
|
+
lines.push(" The identity's stored login (no credential block applies).");
|
|
6148
|
+
}
|
|
6149
|
+
lines.push("", "Ambient-credential exposure:");
|
|
6150
|
+
lines.push(
|
|
6151
|
+
report.ambientCredential.ok ? ` OK \u2014 none of ${AMBIENT_CREDENTIAL_VARS.join(", ")} is set to a non-empty value.` : report.ambientCredential.message.split("\n").map((line) => ` ${line}`).join("\n")
|
|
6152
|
+
);
|
|
6153
|
+
if (report.keychain !== void 0) {
|
|
6154
|
+
lines.push("", "macOS Keychain:");
|
|
6155
|
+
lines.push(` ${report.keychain.note}`);
|
|
6156
|
+
}
|
|
6157
|
+
if (report.settingsExposure.length > 0) {
|
|
6158
|
+
lines.push("", "Settings exposure (names and counts only, never values):");
|
|
6159
|
+
for (const exposure of report.settingsExposure) {
|
|
6160
|
+
lines.push(
|
|
6161
|
+
` ${exposure.file}: ${String(exposure.envKeyNames.length)} env key(s) [${exposure.envKeyNames.join(", ")}], ${String(exposure.hookEventNames.length)} hook event(s) [${exposure.hookEventNames.join(", ")}], ${String(exposure.hookCommandCount)} hook command(s)`
|
|
6162
|
+
);
|
|
6163
|
+
}
|
|
6164
|
+
}
|
|
6165
|
+
return lines;
|
|
6166
|
+
}
|
|
6167
|
+
function checkReportToJson(report) {
|
|
6168
|
+
const decisions = [...report.resolved.decisions.values()].sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
|
|
6169
|
+
return {
|
|
6170
|
+
identity: { name: report.identityName ?? null, source: report.identitySource },
|
|
6171
|
+
...report.poolPick === void 0 ? {} : { pool: report.poolPick },
|
|
6172
|
+
configProfile: { name: report.configProfileName ?? null, source: report.configProfileSource },
|
|
6173
|
+
layers: report.resolved.assembled.layers.map((layer) => ({ id: layer.id, kind: layer.kind, source: layer.source })),
|
|
6174
|
+
entries: decisions.map((decision) => ({
|
|
6175
|
+
path: decision.relPath,
|
|
6176
|
+
shared: decision.shared,
|
|
6177
|
+
via: decision.via,
|
|
6178
|
+
category: decision.category,
|
|
6179
|
+
...decision.rule === void 0 ? {} : { rule: { key: decision.rule.rawKey, layer: decision.rule.layer } },
|
|
6180
|
+
...decision.eliminated === void 0 || decision.eliminated.length === 0 ? {} : { eliminated: decision.eliminated.map((eliminated) => ({ key: eliminated.rule.rawKey, failed: eliminated.failed })) }
|
|
6181
|
+
})),
|
|
6182
|
+
projectEncodingAmbiguities: report.projectEncodingAmbiguities,
|
|
6183
|
+
diagnostics: report.resolved.diagnostics,
|
|
6184
|
+
ambientCredential: report.ambientCredential.ok ? { ok: true } : { ok: false, variable: report.ambientCredential.variable, message: report.ambientCredential.message },
|
|
6185
|
+
...report.keychain === void 0 ? {} : { keychain: report.keychain },
|
|
6186
|
+
settingsExposure: report.settingsExposure,
|
|
6187
|
+
credential: report.credential
|
|
6188
|
+
};
|
|
6189
|
+
}
|
|
6190
|
+
function checkReportHasWarnings(report) {
|
|
6191
|
+
return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0;
|
|
6192
|
+
}
|
|
6193
|
+
function loadCheckProvider(paths, name) {
|
|
6194
|
+
let definition;
|
|
6195
|
+
try {
|
|
6196
|
+
definition = readProvider(paths, name);
|
|
6197
|
+
} catch (error) {
|
|
6198
|
+
if (error instanceof ConfigValidationError || error instanceof LegacyProviderFileError) {
|
|
6199
|
+
return { name, problem: error.message };
|
|
6200
|
+
}
|
|
6201
|
+
throw error;
|
|
6202
|
+
}
|
|
6203
|
+
return definition === void 0 ? { name, problem: `no provider named "${name}"` } : { name, definition };
|
|
6204
|
+
}
|
|
6205
|
+
function collectCheckReport(params) {
|
|
6206
|
+
const { paths, cwd } = params;
|
|
6207
|
+
const home = import_node_os3.default.homedir();
|
|
6208
|
+
const claudeHome = resolveClaudeHome();
|
|
6209
|
+
const read = cosmiconfigReader();
|
|
6210
|
+
const classification = loadClassification(paths);
|
|
6211
|
+
const loaded = loadCascadeInput({ paths, home, cwd, read });
|
|
6212
|
+
const selections = readDirectorySelections(loaded);
|
|
6213
|
+
const git = resolveGitBranch(realRunPort, cwd);
|
|
6214
|
+
const decidedIdentity = decideIdentity({
|
|
6215
|
+
env: params.env,
|
|
6216
|
+
argv0Identity: params.identity,
|
|
6217
|
+
directoryPinnedIdentity: selections.identity,
|
|
6218
|
+
readActiveIdentityFile: () => {
|
|
6219
|
+
const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
|
|
6220
|
+
if (raw === void 0) {
|
|
6221
|
+
return void 0;
|
|
6222
|
+
}
|
|
6223
|
+
const trimmed = raw.trim();
|
|
6224
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
6225
|
+
}
|
|
6226
|
+
});
|
|
6227
|
+
let poolPick;
|
|
6228
|
+
if (decidedIdentity.pool !== void 0) {
|
|
6229
|
+
const pool = loaded.globalConfig?.pools?.[decidedIdentity.pool];
|
|
6230
|
+
if (pool === void 0) {
|
|
6231
|
+
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
6232
|
+
}
|
|
6233
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pool, directory: cwd, nowMs: Date.now() });
|
|
6234
|
+
}
|
|
6235
|
+
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
6236
|
+
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
6237
|
+
const configProfileDecision = decideConfigProfile({
|
|
6238
|
+
env: params.env,
|
|
6239
|
+
directoryRuleConfigProfile: selections.configProfile,
|
|
6240
|
+
identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
|
|
6241
|
+
globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
|
|
6242
|
+
});
|
|
6243
|
+
const cascade = loadCascadeInput({
|
|
6244
|
+
paths,
|
|
6245
|
+
home,
|
|
6246
|
+
cwd,
|
|
6247
|
+
read,
|
|
6248
|
+
...configProfileDecision.name === void 0 ? {} : { baseConfigProfile: configProfileDecision.name }
|
|
6249
|
+
}).input;
|
|
6250
|
+
const providerName = cascadeProviderName(cascade);
|
|
6251
|
+
const provider = providerName === void 0 ? void 0 : loadCheckProvider(paths, providerName);
|
|
6252
|
+
const farmRoot = identityDecision.name === void 0 ? void 0 : import_node_path20.default.join(paths.identitiesDir, identityDecision.name);
|
|
6253
|
+
const settingsFiles = {
|
|
6254
|
+
"settings.json": realFsPort.readFileUtf8(import_node_path20.default.join(claudeHome, "settings.json")),
|
|
6255
|
+
"settings.local.json": realFsPort.readFileUtf8(import_node_path20.default.join(claudeHome, "settings.local.json"))
|
|
6256
|
+
};
|
|
6257
|
+
const report = runCheck({
|
|
6258
|
+
cwd,
|
|
6259
|
+
home,
|
|
6260
|
+
claudeHome,
|
|
6261
|
+
env: params.env,
|
|
6262
|
+
...git.branch === void 0 ? {} : { branch: git.branch },
|
|
6263
|
+
...git.branchDetached === void 0 ? {} : { branchDetached: git.branchDetached },
|
|
6264
|
+
nowMs: Date.now(),
|
|
6265
|
+
farmFs: realFarmFs,
|
|
6266
|
+
cascade,
|
|
6267
|
+
classification,
|
|
6268
|
+
...identityDecision.name === void 0 ? {} : { identityName: identityDecision.name },
|
|
6269
|
+
identitySource: identityDecision.source,
|
|
6270
|
+
...poolPick === void 0 ? {} : { poolPick },
|
|
6271
|
+
...configProfileDecision.name === void 0 ? {} : { configProfileName: configProfileDecision.name },
|
|
6272
|
+
configProfileSource: configProfileDecision.source,
|
|
6273
|
+
...loadedIdentity === void 0 ? {} : { identity: loadedIdentity.config },
|
|
6274
|
+
settingsFiles,
|
|
6275
|
+
run: realRunPort,
|
|
6276
|
+
...farmRoot === void 0 ? {} : { farmRoot },
|
|
6277
|
+
platform: process.platform,
|
|
6278
|
+
...provider === void 0 ? {} : { provider }
|
|
6279
|
+
});
|
|
6280
|
+
return report;
|
|
6281
|
+
}
|
|
6282
|
+
|
|
6283
|
+
// src/claudeShim.ts
|
|
6284
|
+
var import_node_fs7 = __toESM(require("node:fs"), 1);
|
|
6285
|
+
var import_node_path21 = __toESM(require("node:path"), 1);
|
|
6286
|
+
var import_zod11 = require("zod");
|
|
6287
|
+
var ClaudeShimStateSchema = import_zod11.z.strictObject({
|
|
6288
|
+
targetPath: import_zod11.z.string().min(1),
|
|
6289
|
+
method: import_zod11.z.enum(["hardlink", "copy"]),
|
|
6290
|
+
installedAtMs: import_zod11.z.number()
|
|
6291
|
+
});
|
|
6292
|
+
function commandFilename(ownExecutablePath, commandName) {
|
|
6293
|
+
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
6294
|
+
}
|
|
6295
|
+
function resolveOwnInstallDirs(paths, ownExecutablePath) {
|
|
6296
|
+
const dirs = [import_node_path21.default.dirname(ownExecutablePath)];
|
|
6297
|
+
const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
6298
|
+
if (state !== void 0) {
|
|
6299
|
+
dirs.push(import_node_path21.default.dirname(state.targetPath));
|
|
6300
|
+
}
|
|
6301
|
+
return dirs;
|
|
6302
|
+
}
|
|
6303
|
+
var nodeLinkFs = { link: import_node_fs7.default.linkSync, copyFile: import_node_fs7.default.copyFileSync, chmod: import_node_fs7.default.chmodSync };
|
|
6304
|
+
function findPathShadow(params) {
|
|
6305
|
+
const resolvedTargetDir = import_node_path21.default.resolve(params.targetDir);
|
|
6306
|
+
for (const dir of params.pathDirs) {
|
|
6307
|
+
const found = params.findExecutableInDir(dir, params.targetFilename);
|
|
6308
|
+
if (found === void 0) {
|
|
6309
|
+
continue;
|
|
6310
|
+
}
|
|
6311
|
+
return import_node_path21.default.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
|
|
6312
|
+
}
|
|
6313
|
+
return params.pathDirs.some((dir) => import_node_path21.default.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
|
|
6314
|
+
}
|
|
6315
|
+
|
|
6316
|
+
// src/doctorReport.ts
|
|
6317
|
+
var import_node_fs8 = __toESM(require("node:fs"), 1);
|
|
6318
|
+
var import_node_path22 = __toESM(require("node:path"), 1);
|
|
6319
|
+
function refinePathShadow(status, ownExecutablePath, realpath) {
|
|
6320
|
+
if (status.status !== "shadowed") {
|
|
6321
|
+
return status;
|
|
6322
|
+
}
|
|
6323
|
+
return realpath(status.by) === realpath(ownExecutablePath) ? { status: "ok" } : status;
|
|
6324
|
+
}
|
|
6325
|
+
function validateJson(schema, input) {
|
|
6326
|
+
if (input.raw === void 0) {
|
|
6327
|
+
return { ok: false, message: `${input.path} is missing.` };
|
|
6328
|
+
}
|
|
6329
|
+
let parsed;
|
|
6330
|
+
try {
|
|
6331
|
+
parsed = JSON.parse(input.raw);
|
|
6332
|
+
} catch (error) {
|
|
6333
|
+
return { ok: false, message: `${input.path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
6334
|
+
}
|
|
6335
|
+
const result = schema.safeParse(parsed);
|
|
6336
|
+
if (!result.success) {
|
|
6337
|
+
return { ok: false, message: new ConfigValidationError(input.path, result.error.issues).message };
|
|
6338
|
+
}
|
|
6339
|
+
return { ok: true, data: result.data };
|
|
6340
|
+
}
|
|
6341
|
+
function pushPathResolution(push, resolution) {
|
|
6342
|
+
const ownDir = import_node_path22.default.dirname(resolution.ownExecutablePath);
|
|
6343
|
+
switch (resolution.agentShim.status) {
|
|
6344
|
+
case "ok":
|
|
6345
|
+
push("path-resolution", "pass", `\`agent-shim\` on PATH resolves to this running executable, ${resolution.ownExecutablePath}.`, "agent-shim");
|
|
6346
|
+
break;
|
|
6347
|
+
case "not-on-path":
|
|
6348
|
+
push(
|
|
6349
|
+
"path-resolution",
|
|
6350
|
+
"warn",
|
|
6351
|
+
`${ownDir} is not on PATH, so a bare \`agent-shim\` does not reach ${resolution.ownExecutablePath}. Add it to PATH, or keep invoking this executable by its full path.`,
|
|
6352
|
+
"agent-shim"
|
|
6353
|
+
);
|
|
6354
|
+
break;
|
|
6355
|
+
case "shadowed":
|
|
6356
|
+
push(
|
|
6357
|
+
"path-resolution",
|
|
6358
|
+
"fail",
|
|
6359
|
+
`\`agent-shim\` on PATH resolves to ${resolution.agentShim.by}, not this running executable, ${resolution.ownExecutablePath}. Every command you type runs that one instead, at whatever version it happens to be \u2014 including the checks in this report, which describe this executable. Remove ${resolution.agentShim.by}, repoint it at ${resolution.ownExecutablePath}, or put ${ownDir} ahead of it on PATH.`,
|
|
6360
|
+
"agent-shim"
|
|
6361
|
+
);
|
|
6362
|
+
break;
|
|
6363
|
+
}
|
|
6364
|
+
if (resolution.claude === void 0) {
|
|
6365
|
+
return;
|
|
6366
|
+
}
|
|
6367
|
+
switch (resolution.claude.status) {
|
|
6368
|
+
case "ok":
|
|
6369
|
+
push("path-resolution", "pass", "`claude` on PATH resolves to the enabled shim.", "claude");
|
|
6370
|
+
break;
|
|
6371
|
+
case "not-on-path":
|
|
6372
|
+
push("path-resolution", "warn", "The enabled `claude` shim's directory is not on PATH \u2014 add it, or use `agent-shim run` instead.", "claude");
|
|
6373
|
+
break;
|
|
6374
|
+
case "shadowed":
|
|
6375
|
+
push(
|
|
6376
|
+
"path-resolution",
|
|
6377
|
+
"warn",
|
|
6378
|
+
`\`claude\` on PATH resolves to ${resolution.claude.by}, not the enabled shim. Put the shim's directory ahead of it on PATH, or run \`agent-shim shim disable\` if you meant to launch that one directly.`,
|
|
6379
|
+
"claude"
|
|
6380
|
+
);
|
|
6381
|
+
break;
|
|
6382
|
+
}
|
|
6383
|
+
}
|
|
6384
|
+
function pushProvider(push, entry) {
|
|
6385
|
+
if (entry.raw === void 0) {
|
|
6386
|
+
push("provider", "fail", `${entry.path} is missing.`, entry.name);
|
|
6387
|
+
return;
|
|
6388
|
+
}
|
|
6389
|
+
let parsed;
|
|
6390
|
+
try {
|
|
6391
|
+
parsed = JSON.parse(entry.raw);
|
|
6392
|
+
} catch (error) {
|
|
6393
|
+
push("provider", "fail", `${entry.path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, entry.name);
|
|
6394
|
+
return;
|
|
6395
|
+
}
|
|
6396
|
+
const conversion = legacyProviderConversion(parsed);
|
|
6397
|
+
if (conversion !== void 0) {
|
|
6398
|
+
push("provider", "fail", new LegacyProviderFileError(entry.path, conversion).message, entry.name);
|
|
6399
|
+
return;
|
|
6400
|
+
}
|
|
6401
|
+
const validated = ProviderSchema.safeParse(parsed);
|
|
6402
|
+
if (!validated.success) {
|
|
6403
|
+
push("provider", "fail", new ConfigValidationError(entry.path, validated.error.issues).message, entry.name);
|
|
6404
|
+
return;
|
|
6405
|
+
}
|
|
6406
|
+
push("provider", "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)}).`, entry.name);
|
|
6407
|
+
}
|
|
6408
|
+
function pushLegacyName(push, params) {
|
|
6409
|
+
const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
|
|
6410
|
+
if (legacyVariables.length === 0 && import_node_path22.default.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
|
|
6411
|
+
push("legacy-name", "pass", "Nothing here still uses the former `claude-use` name.");
|
|
6412
|
+
return;
|
|
6413
|
+
}
|
|
6414
|
+
for (const name of legacyVariables) {
|
|
6415
|
+
push("legacy-name", "warn", `${name} is the former name of ${name.replace(LEGACY_ENV_PREFIX, ENV_PREFIX)}. Rename it; the old name still works but is only a fallback.`, name);
|
|
6416
|
+
}
|
|
6417
|
+
if (import_node_path22.default.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
|
|
6418
|
+
push(
|
|
6419
|
+
"legacy-name",
|
|
6420
|
+
"warn",
|
|
6421
|
+
`The state root is the former ${params.rootPath}, used in place. Leave it where it is: macOS Claude Code keys each identity's Keychain login on its exact configuration directory path, so moving it signs every identity out.`
|
|
6422
|
+
);
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
function runDoctor(params) {
|
|
6426
|
+
const findings = [];
|
|
6427
|
+
const push = (section, severity, message, subject) => {
|
|
6428
|
+
findings.push({ section, severity, message, ...subject === void 0 ? {} : { subject } });
|
|
6429
|
+
};
|
|
6430
|
+
const ambient = detectAmbientCredential(params.env);
|
|
6431
|
+
if (ambient === void 0) {
|
|
6432
|
+
push("ambient-credential", "pass", "No ambient-credential environment variable is set.");
|
|
6433
|
+
} else {
|
|
6434
|
+
push("ambient-credential", "warn", formatAmbientCredentialGuardMessage(ambient.variable));
|
|
6435
|
+
}
|
|
6436
|
+
if (params.binaryDiscovery.ok) {
|
|
6437
|
+
const { binary } = params.binaryDiscovery;
|
|
6438
|
+
const versionNote = binary.version === void 0 ? "" : `, version ${binary.version}`;
|
|
6439
|
+
push("binary-discovery", "pass", `Found ${binary.path} (${binary.source}${versionNote}).`);
|
|
6440
|
+
} else {
|
|
6441
|
+
push("binary-discovery", "fail", params.binaryDiscovery.message);
|
|
6442
|
+
}
|
|
6443
|
+
if (params.claudeShim.state === void 0) {
|
|
6444
|
+
push("claude-shim", "pass", "No `claude` command shim enabled (the default). Run `agent-shim shim enable` to add one.");
|
|
6445
|
+
} else if (!params.claudeShim.targetExists) {
|
|
6446
|
+
push(
|
|
6447
|
+
"claude-shim",
|
|
6448
|
+
"warn",
|
|
6449
|
+
`claude-shim.json records a \`claude\` shim at ${params.claudeShim.state.targetPath}, but nothing is there. Run \`agent-shim shim enable\` again, or \`agent-shim shim disable\` to clear the stale record.`
|
|
6450
|
+
);
|
|
6451
|
+
} else {
|
|
6452
|
+
push(
|
|
6453
|
+
"claude-shim",
|
|
6454
|
+
"pass",
|
|
6455
|
+
`\`claude\` is enabled at ${params.claudeShim.state.targetPath} (${params.claudeShim.state.method}). If you've upgraded agent-shim since, re-run \`agent-shim shim enable\` to refresh it.`
|
|
6456
|
+
);
|
|
6457
|
+
}
|
|
6458
|
+
pushPathResolution(push, params.pathResolution);
|
|
6459
|
+
pushLegacyName(push, params);
|
|
6460
|
+
const profileSources = /* @__PURE__ */ new Map();
|
|
6461
|
+
for (const entry of params.configProfiles) {
|
|
6462
|
+
const validated = validateJson(ConfigProfileSchema, entry);
|
|
6463
|
+
if (!validated.ok) {
|
|
6464
|
+
push("config-profile", "fail", validated.message, entry.name);
|
|
6465
|
+
continue;
|
|
6466
|
+
}
|
|
6467
|
+
profileSources.set(entry.name, { name: entry.name, profile: validated.data });
|
|
6468
|
+
}
|
|
6469
|
+
const loadProfile = (name) => profileSources.get(name);
|
|
6470
|
+
for (const name of profileSources.keys()) {
|
|
6471
|
+
const linearised = lineariseProfile(name, loadProfile);
|
|
6472
|
+
if (linearised.diagnostics.length === 0) {
|
|
6473
|
+
push("config-profile", "pass", `${name} is valid and its extends chain resolves cleanly.`, name);
|
|
6474
|
+
} else {
|
|
6475
|
+
for (const diagnostic of linearised.diagnostics) {
|
|
6476
|
+
push("config-profile", "fail", diagnostic.message, name);
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
const validProfileNames = new Set(profileSources.keys());
|
|
6481
|
+
const validIdentityNames = /* @__PURE__ */ new Set();
|
|
6482
|
+
for (const entry of params.identities) {
|
|
6483
|
+
const validated = validateJson(IdentitySchema, entry);
|
|
6484
|
+
if (!validated.ok) {
|
|
6485
|
+
push("identity", "fail", validated.message, entry.name);
|
|
6486
|
+
continue;
|
|
6487
|
+
}
|
|
6488
|
+
validIdentityNames.add(entry.name);
|
|
6489
|
+
const defaultProfile = validated.data.defaultConfigProfile;
|
|
6490
|
+
if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
|
|
6491
|
+
push(
|
|
6492
|
+
"identity",
|
|
6493
|
+
"fail",
|
|
6494
|
+
`Identity "${entry.name}" names configuration profile "${defaultProfile}" as its default, but no such profile exists.`,
|
|
6495
|
+
entry.name
|
|
6496
|
+
);
|
|
6497
|
+
} else {
|
|
6498
|
+
const credential = validated.data.credential;
|
|
6499
|
+
push(
|
|
6500
|
+
"identity",
|
|
6501
|
+
"pass",
|
|
6502
|
+
`${entry.name} is valid and authenticates with ${credential === void 0 ? "its stored login" : `credential ${describeCredential(credential)}`}.`,
|
|
6503
|
+
entry.name
|
|
6504
|
+
);
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
|
|
6508
|
+
const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
|
|
6509
|
+
for (const [poolName, pool] of Object.entries(pools)) {
|
|
6510
|
+
const missing = pool.identities.filter((member) => !validIdentityNames.has(member));
|
|
6511
|
+
if (missing.length === 0) {
|
|
6512
|
+
push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
|
|
6513
|
+
} else {
|
|
6514
|
+
push("pool", "fail", `Pool "${poolName}" names ${missing.map((member) => `identity "${member}"`).join(" and ")}, which ${missing.length === 1 ? "does" : "do"} not exist.`, poolName);
|
|
6515
|
+
}
|
|
6516
|
+
}
|
|
6517
|
+
const selectionExists = (selector) => {
|
|
6518
|
+
const poolName = poolNameOf(selector);
|
|
6519
|
+
return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
|
|
6520
|
+
};
|
|
6521
|
+
for (const entry of params.providers) {
|
|
6522
|
+
pushProvider(push, entry);
|
|
6523
|
+
}
|
|
6524
|
+
if (params.platform !== "darwin" || params.run === void 0) {
|
|
6525
|
+
push("keychain", "pass", "Skipped (not macOS).");
|
|
6526
|
+
} else {
|
|
6527
|
+
for (const entry of params.identities) {
|
|
6528
|
+
const result = lookupKeychainService(params.run, entry.farmRoot);
|
|
6529
|
+
push("keychain", result.found ? "pass" : "warn", result.note, entry.name);
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
if (params.directoryRules.raw === void 0) {
|
|
6533
|
+
push("directory-rules", "pass", "No directory-rules.json configured.");
|
|
6534
|
+
} else {
|
|
6535
|
+
const validated = validateJson(DirectoryRulesSchema, params.directoryRules);
|
|
6536
|
+
if (!validated.ok) {
|
|
6537
|
+
push("directory-rules", "fail", validated.message);
|
|
6538
|
+
} else {
|
|
6539
|
+
push("directory-rules", "pass", `${params.directoryRules.path} is valid.`);
|
|
6540
|
+
for (const rule of validated.data.rules) {
|
|
6541
|
+
const badRefs = [];
|
|
6542
|
+
if (rule.identity !== void 0 && !selectionExists(rule.identity)) {
|
|
6543
|
+
badRefs.push(poolNameOf(rule.identity) === void 0 ? `identity "${rule.identity}"` : `pool "${poolNameOf(rule.identity) ?? ""}"`);
|
|
6544
|
+
}
|
|
6545
|
+
if (rule.configProfile !== void 0 && !validProfileNames.has(rule.configProfile)) {
|
|
6546
|
+
badRefs.push(`configuration profile "${rule.configProfile}"`);
|
|
6547
|
+
}
|
|
6548
|
+
if (badRefs.length > 0) {
|
|
6549
|
+
push("directory-rules", "fail", `Rule for "${rule.path}" names ${badRefs.join(" and ")}, which do not exist.`, rule.path);
|
|
6550
|
+
} else {
|
|
6551
|
+
push("directory-rules", "pass", `Rule for "${rule.path}" is valid.`, rule.path);
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
}
|
|
6556
|
+
if (params.globalConfig.raw === void 0) {
|
|
6557
|
+
push("global-config", "pass", "No config.json configured.");
|
|
6558
|
+
} else {
|
|
6559
|
+
const validated = validateJson(GlobalConfigSchema, params.globalConfig);
|
|
6560
|
+
if (!validated.ok) {
|
|
6561
|
+
push("global-config", "fail", validated.message);
|
|
6562
|
+
} else {
|
|
6563
|
+
const defaultProfile = validated.data.defaultConfigProfile;
|
|
6564
|
+
if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
|
|
6565
|
+
push(
|
|
6566
|
+
"global-config",
|
|
6567
|
+
"fail",
|
|
6568
|
+
`config.json names configuration profile "${defaultProfile}" as its default, but no such profile exists.`
|
|
6569
|
+
);
|
|
6570
|
+
} else {
|
|
6571
|
+
push("global-config", "pass", `${params.globalConfig.path} is valid.`);
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
if (params.categoriesLocal.raw === void 0) {
|
|
6576
|
+
push("categories-local", "pass", "No categories.local.json configured.");
|
|
6577
|
+
} else {
|
|
6578
|
+
const validated = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
|
|
6579
|
+
push(
|
|
6580
|
+
"categories-local",
|
|
6581
|
+
validated.ok ? "pass" : "fail",
|
|
6582
|
+
validated.ok ? `${params.categoriesLocal.path} is valid.` : validated.message
|
|
6583
|
+
);
|
|
6584
|
+
}
|
|
6585
|
+
if (params.headroom.state.raw === void 0) {
|
|
6586
|
+
push("headroom", "pass", "No headroom daemon has ever run; nothing to check.");
|
|
6587
|
+
} else {
|
|
6588
|
+
const validated = validateJson(HeadroomStateSchema, params.headroom.state);
|
|
6589
|
+
if (!validated.ok) {
|
|
6590
|
+
push("headroom", "fail", validated.message);
|
|
6591
|
+
} else {
|
|
6592
|
+
const state = validated.data;
|
|
6593
|
+
const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
|
|
6594
|
+
const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
|
|
6595
|
+
if (state.supervisorPid === void 0) {
|
|
6596
|
+
if (state.lastError === void 0) {
|
|
6597
|
+
push("headroom", "pass", "Headroom daemon is stopped (idle shutdown) with no recorded error.");
|
|
6598
|
+
} else {
|
|
6599
|
+
push("headroom", "warn", `Headroom supervisor is not running; last error: ${state.lastError}`);
|
|
6600
|
+
}
|
|
6601
|
+
} else if (!supervisorAlive) {
|
|
6602
|
+
push(
|
|
6603
|
+
"headroom",
|
|
6604
|
+
"warn",
|
|
6605
|
+
`Headroom state names supervisor pid ${String(state.supervisorPid)}, which is not running; the next launch through headroom will start a replacement.`
|
|
6606
|
+
);
|
|
6607
|
+
} else if (state.headroomPid === void 0 || state.port === void 0) {
|
|
6608
|
+
push(
|
|
6609
|
+
"headroom",
|
|
6610
|
+
"warn",
|
|
6611
|
+
state.lastError === void 0 ? "Headroom supervisor is running but the daemon has no healthy process; it should be restarting it." : `Headroom supervisor is running but the daemon is down; last error: ${state.lastError}`
|
|
6612
|
+
);
|
|
6613
|
+
} else if (!headroomAlive) {
|
|
6614
|
+
push(
|
|
6615
|
+
"headroom",
|
|
6616
|
+
"warn",
|
|
6617
|
+
`Headroom state names daemon pid ${String(state.headroomPid)}, which is not running; its supervisor should be restarting it.`
|
|
6618
|
+
);
|
|
6619
|
+
} else {
|
|
6620
|
+
push(
|
|
6621
|
+
"headroom",
|
|
6622
|
+
"pass",
|
|
6623
|
+
`Headroom daemon is up on 127.0.0.1:${String(state.port)} (supervisor ${String(state.supervisorPid)}, daemon ${String(state.headroomPid)}).`
|
|
6624
|
+
);
|
|
6625
|
+
}
|
|
6626
|
+
if (state.lastError !== void 0 && supervisorAlive) {
|
|
6627
|
+
push("headroom", "warn", `Headroom recorded a previous error: ${state.lastError}`);
|
|
6628
|
+
}
|
|
6629
|
+
if (state.installedSource !== void 0 && isMovingGitSource(state.installedSource)) {
|
|
6630
|
+
push(
|
|
6631
|
+
"headroom",
|
|
6632
|
+
"warn",
|
|
6633
|
+
`Headroom is installed from a git ref that can move (${state.installedSource}); pin headroom.source to a full commit SHA so a rebased or deleted branch cannot change or break the install.`
|
|
6634
|
+
);
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
if (params.activeIdentity.raw === void 0) {
|
|
6639
|
+
push("active-identity", "pass", "No active identity set.");
|
|
6640
|
+
} else {
|
|
6641
|
+
const trimmed = params.activeIdentity.raw.trim();
|
|
6642
|
+
if (trimmed === "") {
|
|
6643
|
+
push("active-identity", "warn", "active-identity is present but empty \u2014 treated the same as unset.");
|
|
6644
|
+
} else if (!selectionExists(trimmed)) {
|
|
6645
|
+
push("active-identity", "fail", `active-identity names "${trimmed}", which does not exist.`);
|
|
6646
|
+
} else {
|
|
6647
|
+
push("active-identity", "pass", `Active identity "${trimmed}" is valid.`);
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
return { findings, ok: !findings.some((finding) => finding.severity === "fail") };
|
|
6651
|
+
}
|
|
6652
|
+
var SECTION_TITLES = {
|
|
6653
|
+
"ambient-credential": "Ambient-credential exposure",
|
|
6654
|
+
"binary-discovery": "Claude Code binary discovery",
|
|
6655
|
+
"claude-shim": "`claude` command shim",
|
|
6656
|
+
"path-resolution": "PATH resolution",
|
|
6657
|
+
"legacy-name": "Former `claude-use` name",
|
|
6658
|
+
"config-profile": "Configuration profiles",
|
|
6659
|
+
identity: "Identities",
|
|
6660
|
+
pool: "Pools",
|
|
6661
|
+
provider: "Providers",
|
|
6662
|
+
keychain: "macOS Keychain",
|
|
6663
|
+
"directory-rules": "Directory rules",
|
|
6664
|
+
"global-config": "Global config",
|
|
6665
|
+
headroom: "Headroom daemon",
|
|
6666
|
+
"categories-local": "categories.local.json",
|
|
6667
|
+
"active-identity": "Active identity"
|
|
6668
|
+
};
|
|
6669
|
+
var SECTION_ORDER = [
|
|
6670
|
+
"ambient-credential",
|
|
6671
|
+
"binary-discovery",
|
|
6672
|
+
"claude-shim",
|
|
6673
|
+
"path-resolution",
|
|
6674
|
+
"config-profile",
|
|
6675
|
+
"identity",
|
|
6676
|
+
"pool",
|
|
6677
|
+
"provider",
|
|
6678
|
+
"keychain",
|
|
6679
|
+
"directory-rules",
|
|
6680
|
+
"global-config",
|
|
6681
|
+
"headroom",
|
|
6682
|
+
"categories-local",
|
|
6683
|
+
"active-identity"
|
|
6684
|
+
];
|
|
6685
|
+
function severityPrefix(severity) {
|
|
6686
|
+
switch (severity) {
|
|
6687
|
+
case "pass":
|
|
6688
|
+
return "[PASS]";
|
|
6689
|
+
case "warn":
|
|
6690
|
+
return "[WARN]";
|
|
6691
|
+
case "fail":
|
|
6692
|
+
return "[FAIL]";
|
|
6693
|
+
default:
|
|
6694
|
+
return severity;
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
function formatDoctorReport(report) {
|
|
6698
|
+
const lines = [];
|
|
6699
|
+
for (const section of SECTION_ORDER) {
|
|
6700
|
+
const sectionFindings = report.findings.filter((finding) => finding.section === section);
|
|
6701
|
+
if (sectionFindings.length === 0) {
|
|
6702
|
+
continue;
|
|
6703
|
+
}
|
|
6704
|
+
lines.push("", `${SECTION_TITLES[section]}:`);
|
|
6705
|
+
for (const finding of sectionFindings) {
|
|
6706
|
+
const subject = finding.subject === void 0 ? "" : `${finding.subject}: `;
|
|
6707
|
+
lines.push(` ${severityPrefix(finding.severity)} ${subject}${finding.message}`);
|
|
6708
|
+
}
|
|
6709
|
+
}
|
|
6710
|
+
const failCount = report.findings.filter((finding) => finding.severity === "fail").length;
|
|
6711
|
+
lines.push("", report.ok ? "All checks passed." : `${String(failCount)} check(s) failed.`);
|
|
6712
|
+
return lines;
|
|
6713
|
+
}
|
|
6714
|
+
function realpathOrSelf(target) {
|
|
6715
|
+
try {
|
|
6716
|
+
return import_node_fs8.default.realpathSync(target);
|
|
6717
|
+
} catch {
|
|
6718
|
+
return target;
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
function collectDoctorReport(params) {
|
|
6722
|
+
const { paths } = params;
|
|
6723
|
+
const identityNames = import_node_fs8.default.existsSync(paths.identitiesDir) ? import_node_fs8.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
|
|
6724
|
+
const identities = identityNames.map((name) => {
|
|
6725
|
+
const farmRoot = import_node_path22.default.join(paths.identitiesDir, name);
|
|
6726
|
+
const identityPath = import_node_path22.default.join(farmRoot, "identity.json");
|
|
6727
|
+
return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot };
|
|
6728
|
+
});
|
|
6729
|
+
const profileNames = import_node_fs8.default.existsSync(paths.configProfilesDir) ? import_node_fs8.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
|
|
6730
|
+
const configProfiles = profileNames.map((name) => {
|
|
6731
|
+
const profilePath = import_node_path22.default.join(paths.configProfilesDir, `${name}.json`);
|
|
6732
|
+
return { name, path: profilePath, raw: realFsPort.readFileUtf8(profilePath) };
|
|
6733
|
+
});
|
|
6734
|
+
const providerNames = import_node_fs8.default.existsSync(paths.providersDir) ? import_node_fs8.default.readdirSync(paths.providersDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
|
|
6735
|
+
const providers = providerNames.map((name) => {
|
|
6736
|
+
const providerPath = import_node_path22.default.join(paths.providersDir, `${name}.json`);
|
|
6737
|
+
return { name, path: providerPath, raw: realFsPort.readFileUtf8(providerPath) };
|
|
6738
|
+
});
|
|
6739
|
+
const ownExecutablePath = realOwnExecutablePath();
|
|
6740
|
+
let binaryDiscovery;
|
|
6741
|
+
try {
|
|
6742
|
+
const binary = realResolveClaudeBinary(resolveOwnInstallDirs(paths, ownExecutablePath))();
|
|
6743
|
+
binaryDiscovery = { ok: true, binary };
|
|
6744
|
+
} catch (error) {
|
|
6745
|
+
binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
6746
|
+
}
|
|
6747
|
+
const shimState = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
6748
|
+
const pathDirs = (params.env.PATH ?? "").split(import_node_path22.default.delimiter).filter((dir) => dir !== "");
|
|
6749
|
+
const claudeShimShadow = shimState === void 0 ? void 0 : refinePathShadow(
|
|
6750
|
+
findPathShadow({
|
|
6751
|
+
pathDirs,
|
|
6752
|
+
targetDir: import_node_path22.default.dirname(shimState.targetPath),
|
|
6753
|
+
targetFilename: import_node_path22.default.basename(shimState.targetPath),
|
|
6754
|
+
findExecutableInDir
|
|
6755
|
+
}),
|
|
6756
|
+
shimState.targetPath,
|
|
6757
|
+
realpathOrSelf
|
|
6758
|
+
);
|
|
6759
|
+
const report = runDoctor({
|
|
6760
|
+
env: params.env,
|
|
6761
|
+
identities,
|
|
6762
|
+
configProfiles,
|
|
6763
|
+
providers,
|
|
6764
|
+
directoryRules: { path: paths.directoryRulesFile, raw: realFsPort.readFileUtf8(paths.directoryRulesFile) },
|
|
6765
|
+
globalConfig: { path: paths.globalConfigFile, raw: realFsPort.readFileUtf8(paths.globalConfigFile) },
|
|
6766
|
+
categoriesLocal: { path: paths.categoriesLocalFile, raw: realFsPort.readFileUtf8(paths.categoriesLocalFile) },
|
|
6767
|
+
activeIdentity: { path: paths.activeIdentityFile, raw: realFsPort.readFileUtf8(paths.activeIdentityFile) },
|
|
6768
|
+
binaryDiscovery,
|
|
6769
|
+
claudeShim: { state: shimState, targetExists: shimState !== void 0 && import_node_fs8.default.existsSync(shimState.targetPath) },
|
|
6770
|
+
rootPath: paths.root,
|
|
6771
|
+
pathResolution: {
|
|
6772
|
+
ownExecutablePath,
|
|
6773
|
+
agentShim: refinePathShadow(
|
|
6774
|
+
findPathShadow({
|
|
6775
|
+
pathDirs,
|
|
6776
|
+
targetDir: import_node_path22.default.dirname(ownExecutablePath),
|
|
6777
|
+
targetFilename: commandFilename(ownExecutablePath, "agent-shim"),
|
|
6778
|
+
findExecutableInDir
|
|
6779
|
+
}),
|
|
6780
|
+
ownExecutablePath,
|
|
6781
|
+
realpathOrSelf
|
|
6782
|
+
),
|
|
6783
|
+
...claudeShimShadow === void 0 ? {} : { claude: claudeShimShadow }
|
|
6784
|
+
},
|
|
6785
|
+
run: realRunPort,
|
|
6786
|
+
platform: process.platform,
|
|
6787
|
+
headroom: {
|
|
6788
|
+
state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) },
|
|
6789
|
+
isRunning: realIsProcessRunning
|
|
6790
|
+
}
|
|
6791
|
+
});
|
|
6792
|
+
return report;
|
|
6793
|
+
}
|
|
4640
6794
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4641
6795
|
0 && (module.exports = {
|
|
4642
6796
|
AMBIENT_CREDENTIAL_VARS,
|
|
@@ -4692,6 +6846,10 @@ function removeDirectoryRule(paths, rulePath) {
|
|
|
4692
6846
|
buildEntryFacts,
|
|
4693
6847
|
buildLayoutPaths,
|
|
4694
6848
|
carryOver,
|
|
6849
|
+
checkReportHasWarnings,
|
|
6850
|
+
checkReportToJson,
|
|
6851
|
+
collectCheckReport,
|
|
6852
|
+
collectDoctorReport,
|
|
4695
6853
|
createLeafCache,
|
|
4696
6854
|
createProfile,
|
|
4697
6855
|
describeProviderEndpoint,
|
|
@@ -4701,6 +6859,8 @@ function removeDirectoryRule(paths, rulePath) {
|
|
|
4701
6859
|
ensureHeadroom,
|
|
4702
6860
|
evaluateAmbientCredentialGuard,
|
|
4703
6861
|
formatAmbientCredentialGuardMessage,
|
|
6862
|
+
formatCheckReport,
|
|
6863
|
+
formatDoctorReport,
|
|
4704
6864
|
forwardableHeaders,
|
|
4705
6865
|
generateCa,
|
|
4706
6866
|
identityExists,
|
|
@@ -4740,6 +6900,8 @@ function removeDirectoryRule(paths, rulePath) {
|
|
|
4740
6900
|
resolveLayoutPaths,
|
|
4741
6901
|
resolveSupervisorConfig,
|
|
4742
6902
|
resyncFarm,
|
|
6903
|
+
runCheck,
|
|
6904
|
+
runDoctor,
|
|
4743
6905
|
runFrontDoorSupervisor,
|
|
4744
6906
|
runSupervisor,
|
|
4745
6907
|
servedByPipeline,
|