agent-shim 7.7.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 +4 -2
- package/dist/cli.cjs +3983 -3955
- package/dist/index.cjs +3214 -173
- package/dist/index.mjs +3136 -158
- package/dist/types/checkReport.d.ts +154 -0
- package/dist/types/claudeShim.d.ts +114 -0
- package/dist/types/cli/bool.d.ts +8 -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/configProfilesStore.d.ts +61 -0
- package/dist/types/configure.d.ts +170 -0
- package/dist/types/credential.d.ts +136 -0
- package/dist/types/credentialCache.d.ts +46 -0
- package/dist/types/directoryRulesStore.d.ts +43 -0
- package/dist/types/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/headroom/headers.d.ts +11 -0
- package/dist/types/identityStore.d.ts +85 -0
- package/dist/types/index.d.ts +12 -1
- package/dist/types/launcher/cascade.d.ts +42 -0
- package/dist/types/launcher/flags.d.ts +96 -0
- package/dist/types/launcher/identity.d.ts +95 -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/poolStore.d.ts +18 -0
- package/dist/types/providersStore.d.ts +137 -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/index.mjs
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
// src/config/classify.ts
|
|
2
2
|
import picomatch from "picomatch";
|
|
3
3
|
|
|
4
|
+
// src/config/categories.default.json
|
|
5
|
+
var categories_default_default = {
|
|
6
|
+
secret: [".credentials.json", "backups", "settings.json.bak*"],
|
|
7
|
+
runtime: [
|
|
8
|
+
"daemon*",
|
|
9
|
+
".git*",
|
|
10
|
+
".DS_Store",
|
|
11
|
+
"mcp-needs-auth-cache.json",
|
|
12
|
+
"codex-quota.json",
|
|
13
|
+
"file-transfers",
|
|
14
|
+
"shell-snapshots",
|
|
15
|
+
"statsig",
|
|
16
|
+
"telemetry",
|
|
17
|
+
"stats-cache.json",
|
|
18
|
+
"usage-data",
|
|
19
|
+
"ide",
|
|
20
|
+
"cache",
|
|
21
|
+
"scheduled_tasks.lock"
|
|
22
|
+
],
|
|
23
|
+
history: [
|
|
24
|
+
"projects",
|
|
25
|
+
"sessions",
|
|
26
|
+
"session-env",
|
|
27
|
+
"teams",
|
|
28
|
+
"tasks",
|
|
29
|
+
"todos",
|
|
30
|
+
"history.jsonl",
|
|
31
|
+
"transcripts",
|
|
32
|
+
"paste-cache",
|
|
33
|
+
"file-history",
|
|
34
|
+
"plans",
|
|
35
|
+
"workflows",
|
|
36
|
+
"jobs",
|
|
37
|
+
"debug",
|
|
38
|
+
"downloads",
|
|
39
|
+
"chrome"
|
|
40
|
+
],
|
|
41
|
+
knowledge: [
|
|
42
|
+
"skills",
|
|
43
|
+
"agents",
|
|
44
|
+
"rules",
|
|
45
|
+
"memory",
|
|
46
|
+
"commands",
|
|
47
|
+
"plugins",
|
|
48
|
+
"hooks",
|
|
49
|
+
"AGENTS.md",
|
|
50
|
+
"CLAUDE.md",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
settings: ["settings.json", "settings.local.json"]
|
|
54
|
+
};
|
|
55
|
+
|
|
4
56
|
// src/config/schema.ts
|
|
5
57
|
import path from "node:path";
|
|
6
58
|
import { z } from "zod";
|
|
@@ -243,6 +295,11 @@ var SHIPPED_CATEGORY_DEFAULTS = Object.freeze({
|
|
|
243
295
|
settings: true
|
|
244
296
|
});
|
|
245
297
|
|
|
298
|
+
// src/config/store.ts
|
|
299
|
+
import fs from "node:fs";
|
|
300
|
+
import path2 from "node:path";
|
|
301
|
+
import { randomUUID } from "node:crypto";
|
|
302
|
+
|
|
246
303
|
// src/config/load.ts
|
|
247
304
|
import { cosmiconfigSync } from "cosmiconfig";
|
|
248
305
|
|
|
@@ -261,6 +318,158 @@ var UsageError = class extends CliError {
|
|
|
261
318
|
}
|
|
262
319
|
};
|
|
263
320
|
|
|
321
|
+
// src/config/load.ts
|
|
322
|
+
function createExplorer(moduleName = "agent-shim") {
|
|
323
|
+
return cosmiconfigSync(moduleName, { searchPlaces: [] });
|
|
324
|
+
}
|
|
325
|
+
function isRecord(value) {
|
|
326
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
327
|
+
}
|
|
328
|
+
function captureEntryOrder(value) {
|
|
329
|
+
if (!isRecord(value)) {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
const entries = value.entries;
|
|
333
|
+
if (!isRecord(entries)) {
|
|
334
|
+
return [];
|
|
335
|
+
}
|
|
336
|
+
return Object.keys(entries);
|
|
337
|
+
}
|
|
338
|
+
function captureRuleEntryOrders(value) {
|
|
339
|
+
if (!isRecord(value)) {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
const rules = value.rules;
|
|
343
|
+
if (!Array.isArray(rules)) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
return rules.map((rule) => captureEntryOrder(rule));
|
|
347
|
+
}
|
|
348
|
+
var ConfigValidationError = class extends CliError {
|
|
349
|
+
constructor(filepath, issues) {
|
|
350
|
+
const detail = issues.map((issue) => ` ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
351
|
+
super(`Invalid configuration in ${filepath}:
|
|
352
|
+
${detail}`);
|
|
353
|
+
this.filepath = filepath;
|
|
354
|
+
this.issues = issues;
|
|
355
|
+
this.name = "ConfigValidationError";
|
|
356
|
+
}
|
|
357
|
+
filepath;
|
|
358
|
+
issues;
|
|
359
|
+
};
|
|
360
|
+
function cosmiconfigReader(explorer = createExplorer()) {
|
|
361
|
+
return (filepath) => {
|
|
362
|
+
let result;
|
|
363
|
+
try {
|
|
364
|
+
result = explorer.load(filepath);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
const code = isRecord(error) ? error.code : void 0;
|
|
367
|
+
if (code === "ENOENT") {
|
|
368
|
+
return void 0;
|
|
369
|
+
}
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
if (result === null || result.isEmpty === true) {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
return result.config;
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function loadConfigFile(filepath, schema, read = cosmiconfigReader()) {
|
|
379
|
+
const raw = read(filepath);
|
|
380
|
+
if (raw === void 0) {
|
|
381
|
+
return void 0;
|
|
382
|
+
}
|
|
383
|
+
const parsed = schema.safeParse(raw);
|
|
384
|
+
if (!parsed.success) {
|
|
385
|
+
throw new ConfigValidationError(filepath, parsed.error.issues);
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
filepath,
|
|
389
|
+
config: parsed.data,
|
|
390
|
+
entryOrder: captureEntryOrder(raw),
|
|
391
|
+
ruleEntryOrders: captureRuleEntryOrders(raw)
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/config/store.ts
|
|
396
|
+
function isEnoent(error) {
|
|
397
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
398
|
+
}
|
|
399
|
+
var nodeStoreFs = {
|
|
400
|
+
readFileUtf8(filePath) {
|
|
401
|
+
try {
|
|
402
|
+
return fs.readFileSync(filePath, "utf8");
|
|
403
|
+
} catch (error) {
|
|
404
|
+
if (isEnoent(error)) {
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
writeFileUtf8(filePath, contents) {
|
|
411
|
+
fs.writeFileSync(filePath, contents, "utf8");
|
|
412
|
+
},
|
|
413
|
+
renameSync(from, to) {
|
|
414
|
+
fs.renameSync(from, to);
|
|
415
|
+
},
|
|
416
|
+
mkdirSync(dirPath) {
|
|
417
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
418
|
+
},
|
|
419
|
+
unlinkSync(filePath) {
|
|
420
|
+
try {
|
|
421
|
+
fs.unlinkSync(filePath);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
if (!isEnoent(error)) {
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
function readJson(filePath, schema, storeFs = nodeStoreFs) {
|
|
430
|
+
const raw = storeFs.readFileUtf8(filePath);
|
|
431
|
+
if (raw === void 0) {
|
|
432
|
+
return void 0;
|
|
433
|
+
}
|
|
434
|
+
const parsed = JSON.parse(raw);
|
|
435
|
+
const result = schema.safeParse(parsed);
|
|
436
|
+
if (!result.success) {
|
|
437
|
+
throw new ConfigValidationError(filePath, result.error.issues);
|
|
438
|
+
}
|
|
439
|
+
return result.data;
|
|
440
|
+
}
|
|
441
|
+
function writeTextAtomic(filePath, contents, storeFs = nodeStoreFs) {
|
|
442
|
+
const dir = path2.dirname(filePath);
|
|
443
|
+
storeFs.mkdirSync(dir);
|
|
444
|
+
const tempPath = path2.join(dir, `.${path2.basename(filePath)}.${String(process.pid)}.${randomUUID()}.tmp`);
|
|
445
|
+
storeFs.writeFileUtf8(tempPath, contents);
|
|
446
|
+
try {
|
|
447
|
+
storeFs.renameSync(tempPath, filePath);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
storeFs.unlinkSync(tempPath);
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function writeJsonAtomic(filePath, data, storeFs = nodeStoreFs) {
|
|
454
|
+
writeTextAtomic(filePath, `${JSON.stringify(data, null, 2)}
|
|
455
|
+
`, storeFs);
|
|
456
|
+
}
|
|
457
|
+
function applyPatch(filePath, schema, patch, options = {}) {
|
|
458
|
+
const storeFs = options.storeFs ?? nodeStoreFs;
|
|
459
|
+
const existing = readJson(filePath, schema, storeFs) ?? options.defaults;
|
|
460
|
+
if (existing === void 0) {
|
|
461
|
+
throw new Error(`Cannot patch ${filePath}: it does not exist yet and no defaults were provided.`);
|
|
462
|
+
}
|
|
463
|
+
const merged = { ...existing, ...patch };
|
|
464
|
+
const result = schema.safeParse(merged);
|
|
465
|
+
if (!result.success) {
|
|
466
|
+
throw new ConfigValidationError(filePath, result.error.issues);
|
|
467
|
+
}
|
|
468
|
+
const validated = result.data;
|
|
469
|
+
writeJsonAtomic(filePath, validated, storeFs);
|
|
470
|
+
return validated;
|
|
471
|
+
}
|
|
472
|
+
|
|
264
473
|
// src/config/classify.ts
|
|
265
474
|
var GLOB_METACHARACTERS = /[*?[\]{}!()]/;
|
|
266
475
|
function isExactPattern(pattern) {
|
|
@@ -309,6 +518,13 @@ function compareClassificationPatterns(a, b) {
|
|
|
309
518
|
}
|
|
310
519
|
return a.ordinal - b.ordinal;
|
|
311
520
|
}
|
|
521
|
+
function loadClassification(paths) {
|
|
522
|
+
const overlay = readJson(paths.categoriesLocalFile, CategoryClassificationOverlaySchema);
|
|
523
|
+
return {
|
|
524
|
+
defaults: CategoryClassificationSchema.parse(categories_default_default),
|
|
525
|
+
...overlay === void 0 ? {} : { overlay }
|
|
526
|
+
};
|
|
527
|
+
}
|
|
312
528
|
function classifyEntries(names, options) {
|
|
313
529
|
const patterns = compileClassificationPatterns(options.defaults, options.overlay);
|
|
314
530
|
const classification = /* @__PURE__ */ new Map();
|
|
@@ -420,6 +636,7 @@ import picomatch3 from "picomatch";
|
|
|
420
636
|
|
|
421
637
|
// src/resolve/projects.ts
|
|
422
638
|
var NON_ALPHANUMERIC = /[^A-Za-z0-9]/g;
|
|
639
|
+
var NON_ALPHANUMERIC_CHARACTER = /[^A-Za-z0-9]/;
|
|
423
640
|
var WILDCARD_TOKENS = ["**", "*", "?"];
|
|
424
641
|
function encodeProjectPath(realPath) {
|
|
425
642
|
return realPath.replace(NON_ALPHANUMERIC, "-");
|
|
@@ -473,6 +690,64 @@ function encodeProjectPattern(fragment, options) {
|
|
|
473
690
|
}
|
|
474
691
|
return splitOnWildcards(expanded).map((piece) => piece.kind === "literal" ? encodeProjectPath(piece.text) : piece.text).join("");
|
|
475
692
|
}
|
|
693
|
+
function lossyCharacters(literal) {
|
|
694
|
+
const found = /* @__PURE__ */ new Set();
|
|
695
|
+
for (const character of literal) {
|
|
696
|
+
if (character !== "/" && NON_ALPHANUMERIC_CHARACTER.test(character)) {
|
|
697
|
+
found.add(character);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return [...found];
|
|
701
|
+
}
|
|
702
|
+
function detectEncodingAmbiguity(fragments, options) {
|
|
703
|
+
const ambiguities = [];
|
|
704
|
+
const encodedByFragment = /* @__PURE__ */ new Map();
|
|
705
|
+
for (const fragment of fragments) {
|
|
706
|
+
let encoded;
|
|
707
|
+
try {
|
|
708
|
+
encoded = encodeProjectPattern(fragment, { home: options.home });
|
|
709
|
+
} catch {
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
encodedByFragment.set(fragment, encoded);
|
|
713
|
+
const expanded = expandHome(fragment, options.home);
|
|
714
|
+
const literalText = splitOnWildcards(expanded).filter((piece) => piece.kind === "literal").map((piece) => piece.text).join("");
|
|
715
|
+
const lossy = lossyCharacters(literalText);
|
|
716
|
+
if (lossy.length > 0) {
|
|
717
|
+
const matched = options.existingNames?.filter((name) => name === encoded).length;
|
|
718
|
+
const suffix = matched === void 0 ? "" : ` It currently matches ${String(matched)} existing project directory name(s).`;
|
|
719
|
+
ambiguities.push({
|
|
720
|
+
fragment,
|
|
721
|
+
encoded,
|
|
722
|
+
reason: "lossy-characters",
|
|
723
|
+
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}`
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
const byEncoded = /* @__PURE__ */ new Map();
|
|
728
|
+
for (const [fragment, encoded] of encodedByFragment) {
|
|
729
|
+
const group = byEncoded.get(encoded);
|
|
730
|
+
if (group === void 0) {
|
|
731
|
+
byEncoded.set(encoded, [fragment]);
|
|
732
|
+
} else {
|
|
733
|
+
group.push(fragment);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
for (const [encoded, group] of byEncoded) {
|
|
737
|
+
if (group.length < 2) {
|
|
738
|
+
continue;
|
|
739
|
+
}
|
|
740
|
+
for (const fragment of group) {
|
|
741
|
+
ambiguities.push({
|
|
742
|
+
fragment,
|
|
743
|
+
encoded,
|
|
744
|
+
reason: "collides-with-sibling-pattern",
|
|
745
|
+
detail: `Encodes identically to ${group.filter((other) => other !== fragment).map((other) => JSON.stringify(other)).join(", ")}.`
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return ambiguities;
|
|
750
|
+
}
|
|
476
751
|
|
|
477
752
|
// src/resolve/match.ts
|
|
478
753
|
var GLOB_METACHARACTERS2 = /[*?[\]{}!()]/;
|
|
@@ -852,7 +1127,7 @@ function dedupeDiagnostics(diagnostics) {
|
|
|
852
1127
|
}
|
|
853
1128
|
|
|
854
1129
|
// src/resolve/plan.ts
|
|
855
|
-
import
|
|
1130
|
+
import path3 from "node:path";
|
|
856
1131
|
function parentOf(rel) {
|
|
857
1132
|
const index = rel.lastIndexOf("/");
|
|
858
1133
|
return index === -1 ? "" : rel.slice(0, index);
|
|
@@ -902,7 +1177,7 @@ function planFarm(params) {
|
|
|
902
1177
|
}
|
|
903
1178
|
return value;
|
|
904
1179
|
};
|
|
905
|
-
const linkTarget = (rel) =>
|
|
1180
|
+
const linkTarget = (rel) => path3.join(params.facts.claudeHome, rel);
|
|
906
1181
|
const visit = (rel) => {
|
|
907
1182
|
const fact = params.facts.entries.get(rel);
|
|
908
1183
|
const decision = params.decisions.get(rel);
|
|
@@ -948,6 +1223,9 @@ function planFarm(params) {
|
|
|
948
1223
|
return { entries, materialised, links };
|
|
949
1224
|
}
|
|
950
1225
|
|
|
1226
|
+
// src/resolve/walk.ts
|
|
1227
|
+
import path4 from "node:path";
|
|
1228
|
+
|
|
951
1229
|
// src/resolve/extends.ts
|
|
952
1230
|
function lineariseProfile(rootName, load) {
|
|
953
1231
|
const stack = [];
|
|
@@ -1013,6 +1291,27 @@ function profileLayers(rootName, load, startId) {
|
|
|
1013
1291
|
}
|
|
1014
1292
|
|
|
1015
1293
|
// src/resolve/walk.ts
|
|
1294
|
+
function walkDirectoryAncestors(cwd, options) {
|
|
1295
|
+
const limit = path4.resolve(options.limit ?? options.home);
|
|
1296
|
+
const isReadable = options.isReadable ?? (() => true);
|
|
1297
|
+
const collected = [];
|
|
1298
|
+
let current = path4.resolve(cwd);
|
|
1299
|
+
for (; ; ) {
|
|
1300
|
+
if (!isReadable(current)) {
|
|
1301
|
+
break;
|
|
1302
|
+
}
|
|
1303
|
+
collected.push(current);
|
|
1304
|
+
if (current === limit) {
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
const parent = path4.dirname(current);
|
|
1308
|
+
if (parent === current) {
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
current = parent;
|
|
1312
|
+
}
|
|
1313
|
+
return collected.reverse();
|
|
1314
|
+
}
|
|
1016
1315
|
function assembleCascade(input) {
|
|
1017
1316
|
const layers = [];
|
|
1018
1317
|
const diagnostics = [];
|
|
@@ -1126,7 +1425,7 @@ function resolveDecisions(input) {
|
|
|
1126
1425
|
}
|
|
1127
1426
|
|
|
1128
1427
|
// src/launcher/farm.ts
|
|
1129
|
-
import
|
|
1428
|
+
import path6 from "node:path";
|
|
1130
1429
|
import { z as z2 } from "zod";
|
|
1131
1430
|
|
|
1132
1431
|
// src/resolve/reconcile.ts
|
|
@@ -1220,13 +1519,17 @@ function planReconciliation(params) {
|
|
|
1220
1519
|
}
|
|
1221
1520
|
|
|
1222
1521
|
// src/legacy.ts
|
|
1522
|
+
var ENV_PREFIX = "AGENT_SHIM_";
|
|
1523
|
+
var LEGACY_ENV_PREFIX = "CLAUDE_USE_";
|
|
1223
1524
|
var HOME_DIRNAME = ".agent-shim";
|
|
1224
1525
|
var LEGACY_HOME_DIRNAME = ".claude-use";
|
|
1526
|
+
var LEGACY_PORTABLE_CONFIG_FILENAME = ".claude-use.json";
|
|
1527
|
+
var LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME = ".claude-use.local.json";
|
|
1225
1528
|
var LEGACY_FARM_MANIFEST_FILENAME = ".claude-use-farm.json";
|
|
1226
1529
|
|
|
1227
1530
|
// src/launcher/lock.ts
|
|
1228
|
-
import { randomUUID } from "node:crypto";
|
|
1229
|
-
import
|
|
1531
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1532
|
+
import path5 from "node:path";
|
|
1230
1533
|
var DEFAULT_STALE_AFTER_MS = 12e4;
|
|
1231
1534
|
var DEFAULT_RETRY_DELAY_MS = 50;
|
|
1232
1535
|
var DEFAULT_MAX_ATTEMPTS = 200;
|
|
@@ -1245,7 +1548,7 @@ var IdentityLockBusyError = class extends CliError {
|
|
|
1245
1548
|
holderPid;
|
|
1246
1549
|
};
|
|
1247
1550
|
function identityLockPath(dir, identity) {
|
|
1248
|
-
return
|
|
1551
|
+
return path5.join(dir, `.${identity}.lock`);
|
|
1249
1552
|
}
|
|
1250
1553
|
function parseLockRecord(raw) {
|
|
1251
1554
|
if (raw === void 0) {
|
|
@@ -1274,7 +1577,7 @@ function acquireIdentityLock(params) {
|
|
|
1274
1577
|
const staleAfterMs = params.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
1275
1578
|
const retryDelayMs = params.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
1276
1579
|
const maxAttempts = params.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
1277
|
-
const token =
|
|
1580
|
+
const token = randomUUID2();
|
|
1278
1581
|
if (params.fs.lstat(params.dir) === void 0) {
|
|
1279
1582
|
params.fs.mkdirp(params.dir);
|
|
1280
1583
|
}
|
|
@@ -1323,8 +1626,8 @@ var FarmManifestSchema = z2.strictObject({
|
|
|
1323
1626
|
materialised: z2.array(z2.string()),
|
|
1324
1627
|
links: z2.array(z2.strictObject({ rel: z2.string(), target: z2.string() }))
|
|
1325
1628
|
});
|
|
1326
|
-
function readFarmManifest(
|
|
1327
|
-
const raw =
|
|
1629
|
+
function readFarmManifest(fs10, farmRoot) {
|
|
1630
|
+
const raw = fs10.readFileUtf8(path6.join(farmRoot, FARM_MANIFEST_FILENAME)) ?? fs10.readFileUtf8(path6.join(farmRoot, LEGACY_FARM_MANIFEST_FILENAME));
|
|
1328
1631
|
if (raw === void 0) {
|
|
1329
1632
|
return void 0;
|
|
1330
1633
|
}
|
|
@@ -1340,7 +1643,7 @@ function readFarmManifest(fs3, farmRoot) {
|
|
|
1340
1643
|
function buildEntryFacts(params) {
|
|
1341
1644
|
const entries = /* @__PURE__ */ new Map();
|
|
1342
1645
|
const visit = (rel) => {
|
|
1343
|
-
const absolute =
|
|
1646
|
+
const absolute = path6.join(params.claudeHome, rel);
|
|
1344
1647
|
const stat = params.fs.lstat(absolute);
|
|
1345
1648
|
if (stat === void 0) {
|
|
1346
1649
|
return void 0;
|
|
@@ -1382,9 +1685,9 @@ function buildEntryFacts(params) {
|
|
|
1382
1685
|
entries
|
|
1383
1686
|
};
|
|
1384
1687
|
}
|
|
1385
|
-
function listSubtree(
|
|
1386
|
-
const absolute =
|
|
1387
|
-
const stat =
|
|
1688
|
+
function listSubtree(fs10, root, rel) {
|
|
1689
|
+
const absolute = path6.join(root, rel);
|
|
1690
|
+
const stat = fs10.lstat(absolute);
|
|
1388
1691
|
if (stat === void 0) {
|
|
1389
1692
|
return [];
|
|
1390
1693
|
}
|
|
@@ -1392,10 +1695,10 @@ function listSubtree(fs3, root, rel) {
|
|
|
1392
1695
|
return [{ rel, kind: "symlink" }];
|
|
1393
1696
|
}
|
|
1394
1697
|
if (stat.kind === "dir") {
|
|
1395
|
-
const children = [...
|
|
1698
|
+
const children = [...fs10.readdir(absolute)].sort().flatMap((name) => listSubtree(fs10, root, `${rel}/${name}`));
|
|
1396
1699
|
return [{ rel, kind: "dir" }, ...children];
|
|
1397
1700
|
}
|
|
1398
|
-
const contentHash =
|
|
1701
|
+
const contentHash = fs10.hashFile(absolute);
|
|
1399
1702
|
return [{ rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } }];
|
|
1400
1703
|
}
|
|
1401
1704
|
function topLevelScopeRoots(roots) {
|
|
@@ -1409,25 +1712,25 @@ function topLevelScopeRoots(roots) {
|
|
|
1409
1712
|
}
|
|
1410
1713
|
return kept;
|
|
1411
1714
|
}
|
|
1412
|
-
function reconciliationScope(
|
|
1715
|
+
function reconciliationScope(fs10, farmRoot, manifest) {
|
|
1413
1716
|
if (manifest !== void 0) {
|
|
1414
1717
|
return topLevelScopeRoots(manifest.materialised);
|
|
1415
1718
|
}
|
|
1416
|
-
return [...
|
|
1719
|
+
return [...fs10.readdir(farmRoot)].filter((name) => name !== FARM_MANIFEST_FILENAME && name !== LEGACY_FARM_MANIFEST_FILENAME && fs10.lstat(path6.join(farmRoot, name))?.kind === "dir").sort();
|
|
1417
1720
|
}
|
|
1418
|
-
function collectFarmListing(
|
|
1419
|
-
return scopeRoots.flatMap((root) => listSubtree(
|
|
1721
|
+
function collectFarmListing(fs10, farmRoot, scopeRoots) {
|
|
1722
|
+
return scopeRoots.flatMap((root) => listSubtree(fs10, farmRoot, root));
|
|
1420
1723
|
}
|
|
1421
|
-
function collectCanonicalCounterparts(
|
|
1724
|
+
function collectCanonicalCounterparts(fs10, claudeHome, farmListing) {
|
|
1422
1725
|
const out = [];
|
|
1423
1726
|
for (const entry of farmListing) {
|
|
1424
|
-
const absolute =
|
|
1425
|
-
const stat =
|
|
1727
|
+
const absolute = path6.join(claudeHome, entry.rel);
|
|
1728
|
+
const stat = fs10.lstat(absolute);
|
|
1426
1729
|
if (stat === void 0) {
|
|
1427
1730
|
continue;
|
|
1428
1731
|
}
|
|
1429
1732
|
if (stat.kind === "file") {
|
|
1430
|
-
const contentHash =
|
|
1733
|
+
const contentHash = fs10.hashFile(absolute);
|
|
1431
1734
|
out.push({ rel: entry.rel, kind: "file", ...contentHash === void 0 ? {} : { contentHash } });
|
|
1432
1735
|
continue;
|
|
1433
1736
|
}
|
|
@@ -1451,21 +1754,21 @@ function carryOver(params) {
|
|
|
1451
1754
|
if (accounted.has(name)) {
|
|
1452
1755
|
continue;
|
|
1453
1756
|
}
|
|
1454
|
-
const stat = params.fs.lstat(
|
|
1757
|
+
const stat = params.fs.lstat(path6.join(params.previousRoot, name));
|
|
1455
1758
|
if (stat === void 0 || stat.kind === "symlink") {
|
|
1456
1759
|
continue;
|
|
1457
1760
|
}
|
|
1458
|
-
if (params.fs.lstat(
|
|
1761
|
+
if (params.fs.lstat(path6.join(params.farmRoot, name)) !== void 0) {
|
|
1459
1762
|
const category = params.classification === void 0 ? void 0 : classifyEntries([name], params.classification).classification.get(name);
|
|
1460
1763
|
if (category === "runtime") {
|
|
1461
|
-
params.fs.removeRecursive(
|
|
1764
|
+
params.fs.removeRecursive(path6.join(params.previousRoot, name));
|
|
1462
1765
|
autoResolved.push(name);
|
|
1463
1766
|
continue;
|
|
1464
1767
|
}
|
|
1465
1768
|
collided.push(name);
|
|
1466
1769
|
continue;
|
|
1467
1770
|
}
|
|
1468
|
-
params.fs.rename(
|
|
1771
|
+
params.fs.rename(path6.join(params.previousRoot, name), path6.join(params.farmRoot, name));
|
|
1469
1772
|
carried.push(name);
|
|
1470
1773
|
}
|
|
1471
1774
|
return { carried, autoResolved, collided };
|
|
@@ -1473,9 +1776,9 @@ function carryOver(params) {
|
|
|
1473
1776
|
function buildScratchTree(params) {
|
|
1474
1777
|
params.fs.mkdirp(params.scratchRoot);
|
|
1475
1778
|
for (const entry of params.plan.entries) {
|
|
1476
|
-
const absolute =
|
|
1779
|
+
const absolute = path6.join(params.scratchRoot, entry.rel);
|
|
1477
1780
|
if (entry.kind === "link") {
|
|
1478
|
-
params.fs.mkdirp(
|
|
1781
|
+
params.fs.mkdirp(path6.dirname(absolute));
|
|
1479
1782
|
params.fs.symlink(entry.target, absolute);
|
|
1480
1783
|
continue;
|
|
1481
1784
|
}
|
|
@@ -1484,14 +1787,14 @@ function buildScratchTree(params) {
|
|
|
1484
1787
|
}
|
|
1485
1788
|
}
|
|
1486
1789
|
params.fs.writeFileUtf8(
|
|
1487
|
-
|
|
1790
|
+
path6.join(params.scratchRoot, FARM_MANIFEST_FILENAME),
|
|
1488
1791
|
`${JSON.stringify(params.manifest, null, 2)}
|
|
1489
1792
|
`
|
|
1490
1793
|
);
|
|
1491
1794
|
}
|
|
1492
1795
|
function swapIn(params) {
|
|
1493
1796
|
if (params.fs.lstat(params.farmRoot) === void 0) {
|
|
1494
|
-
params.fs.mkdirp(
|
|
1797
|
+
params.fs.mkdirp(path6.dirname(params.farmRoot));
|
|
1495
1798
|
params.fs.rename(params.scratchRoot, params.farmRoot);
|
|
1496
1799
|
return { carried: [], autoResolved: [], collided: [] };
|
|
1497
1800
|
}
|
|
@@ -1515,14 +1818,14 @@ function recoverInterruptedSwap(params) {
|
|
|
1515
1818
|
const names = [...params.fs.readdir(params.identitiesDir)].sort();
|
|
1516
1819
|
const removedScratch = [];
|
|
1517
1820
|
for (const name of names.filter((candidate) => candidate.startsWith(scratchPrefix))) {
|
|
1518
|
-
params.fs.removeRecursive(
|
|
1821
|
+
params.fs.removeRecursive(path6.join(params.identitiesDir, name));
|
|
1519
1822
|
removedScratch.push(name);
|
|
1520
1823
|
}
|
|
1521
1824
|
let previous = names.filter((candidate) => candidate.startsWith(previousPrefix));
|
|
1522
1825
|
let restoredFrom;
|
|
1523
1826
|
const first = previous[0];
|
|
1524
1827
|
if (first !== void 0 && params.fs.lstat(params.farmRoot) === void 0) {
|
|
1525
|
-
params.fs.rename(
|
|
1828
|
+
params.fs.rename(path6.join(params.identitiesDir, first), params.farmRoot);
|
|
1526
1829
|
restoredFrom = first;
|
|
1527
1830
|
previous = previous.slice(1);
|
|
1528
1831
|
}
|
|
@@ -1530,7 +1833,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1530
1833
|
const autoResolved = [];
|
|
1531
1834
|
const retained = [];
|
|
1532
1835
|
for (const name of previous) {
|
|
1533
|
-
const previousRoot =
|
|
1836
|
+
const previousRoot = path6.join(params.identitiesDir, name);
|
|
1534
1837
|
const result = carryOver({
|
|
1535
1838
|
fs: params.fs,
|
|
1536
1839
|
previousRoot,
|
|
@@ -1555,7 +1858,7 @@ function recoverInterruptedSwap(params) {
|
|
|
1555
1858
|
};
|
|
1556
1859
|
}
|
|
1557
1860
|
function recoverFarm(params) {
|
|
1558
|
-
const farmRoot =
|
|
1861
|
+
const farmRoot = path6.join(params.identitiesDir, params.identity);
|
|
1559
1862
|
const lock = acquireIdentityLock({
|
|
1560
1863
|
identity: params.identity,
|
|
1561
1864
|
dir: params.identitiesDir,
|
|
@@ -1614,16 +1917,16 @@ function executeReconciliation(params) {
|
|
|
1614
1917
|
});
|
|
1615
1918
|
continue;
|
|
1616
1919
|
}
|
|
1617
|
-
const from =
|
|
1618
|
-
const canonical =
|
|
1920
|
+
const from = path6.join(params.farmRoot, action.rel);
|
|
1921
|
+
const canonical = path6.join(params.claudeHome, action.rel);
|
|
1619
1922
|
if (action.kind === "adopt") {
|
|
1620
|
-
params.fs.mkdirp(
|
|
1923
|
+
params.fs.mkdirp(path6.dirname(canonical));
|
|
1621
1924
|
params.fs.copyRecursive(from, canonical);
|
|
1622
1925
|
adopted.push(action.rel);
|
|
1623
1926
|
continue;
|
|
1624
1927
|
}
|
|
1625
1928
|
const preserved = `${canonical}.farm-conflict-${String(params.nowMs)}`;
|
|
1626
|
-
params.fs.mkdirp(
|
|
1929
|
+
params.fs.mkdirp(path6.dirname(canonical));
|
|
1627
1930
|
params.fs.copyRecursive(from, preserved);
|
|
1628
1931
|
conflicts.push(action.rel);
|
|
1629
1932
|
}
|
|
@@ -1648,7 +1951,7 @@ function farmAlreadyMatches(existing, next) {
|
|
|
1648
1951
|
return existing.identity === next.identity && existing.claudeHome === next.claudeHome && sameStrings(existing.materialised, next.materialised) && sameLinks(existing.links, next.links);
|
|
1649
1952
|
}
|
|
1650
1953
|
function resyncFarm(params) {
|
|
1651
|
-
const farmRoot =
|
|
1954
|
+
const farmRoot = path6.join(params.identitiesDir, params.identity);
|
|
1652
1955
|
const lock = acquireIdentityLock({
|
|
1653
1956
|
identity: params.identity,
|
|
1654
1957
|
dir: params.identitiesDir,
|
|
@@ -1727,8 +2030,8 @@ function resyncFarm(params) {
|
|
|
1727
2030
|
diagnostics
|
|
1728
2031
|
};
|
|
1729
2032
|
}
|
|
1730
|
-
const scratchRoot =
|
|
1731
|
-
const previousRoot =
|
|
2033
|
+
const scratchRoot = path6.join(params.identitiesDir, `.${params.identity}.scratch.${params.uniqueSuffix}`);
|
|
2034
|
+
const previousRoot = path6.join(params.identitiesDir, `.${params.identity}.previous.${params.uniqueSuffix}`);
|
|
1732
2035
|
buildScratchTree({ fs: params.fs, scratchRoot, plan: resolved.farm, manifest });
|
|
1733
2036
|
const swap = swapIn({ fs: params.fs, farmRoot, scratchRoot, previousRoot, classification: params.classification });
|
|
1734
2037
|
if (swap.retainedPrevious !== void 0) {
|
|
@@ -1786,7 +2089,7 @@ function recoveryDiagnostics(recovery, identity) {
|
|
|
1786
2089
|
|
|
1787
2090
|
// src/frontdoor/connect.ts
|
|
1788
2091
|
import { X509Certificate as X509Certificate2, createPrivateKey as createPrivateKey2 } from "node:crypto";
|
|
1789
|
-
import * as
|
|
2092
|
+
import * as fs2 from "node:fs";
|
|
1790
2093
|
|
|
1791
2094
|
// src/frontdoor/x509.ts
|
|
1792
2095
|
import { createHash, createPrivateKey, generateKeyPairSync, randomBytes, sign, X509Certificate } from "node:crypto";
|
|
@@ -2132,7 +2435,7 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2132
2435
|
var AUTH_HEADER = "x-agent-shim-auth";
|
|
2133
2436
|
|
|
2134
2437
|
// src/frontdoor/state.ts
|
|
2135
|
-
import
|
|
2438
|
+
import path7 from "node:path";
|
|
2136
2439
|
import { z as z3 } from "zod";
|
|
2137
2440
|
var FRONT_DOOR_PROTOCOL = 1;
|
|
2138
2441
|
var FrontDoorSessionSchema = z3.strictObject({ pid: z3.number().int().positive(), startedAt: z3.number(), token: z3.string().min(1) });
|
|
@@ -2166,10 +2469,10 @@ var FrontDoorStateSchema = z3.strictObject({
|
|
|
2166
2469
|
/** The last fatal error, kept across shutdowns so `frontdoor status` can explain a front door that is not running. */
|
|
2167
2470
|
lastError: z3.string().optional()
|
|
2168
2471
|
});
|
|
2169
|
-
function readFrontDoorState(
|
|
2472
|
+
function readFrontDoorState(fs10, stateFile) {
|
|
2170
2473
|
let raw;
|
|
2171
2474
|
try {
|
|
2172
|
-
raw =
|
|
2475
|
+
raw = fs10.readFileUtf8(stateFile);
|
|
2173
2476
|
} catch {
|
|
2174
2477
|
return void 0;
|
|
2175
2478
|
}
|
|
@@ -2183,28 +2486,28 @@ function readFrontDoorState(fs3, stateFile) {
|
|
|
2183
2486
|
return void 0;
|
|
2184
2487
|
}
|
|
2185
2488
|
}
|
|
2186
|
-
function writeFrontDoorState(
|
|
2187
|
-
|
|
2188
|
-
|
|
2489
|
+
function writeFrontDoorState(fs10, stateFile, state) {
|
|
2490
|
+
fs10.mkdirp(path7.dirname(stateFile));
|
|
2491
|
+
fs10.writeFileUtf8(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2189
2492
|
`);
|
|
2190
2493
|
}
|
|
2191
2494
|
function frontDoorOrigin(state) {
|
|
2192
2495
|
const port = state?.directPort ?? state?.lastDirectPort;
|
|
2193
2496
|
return port === void 0 ? void 0 : `http://127.0.0.1:${String(port)}`;
|
|
2194
2497
|
}
|
|
2195
|
-
function writeFrontDoorSession(
|
|
2196
|
-
|
|
2197
|
-
|
|
2498
|
+
function writeFrontDoorSession(fs10, sessionsDir, session) {
|
|
2499
|
+
fs10.mkdirPrivate(sessionsDir);
|
|
2500
|
+
fs10.writeFilePrivate(path7.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
2198
2501
|
`);
|
|
2199
2502
|
}
|
|
2200
|
-
function listFrontDoorSessions(
|
|
2503
|
+
function listFrontDoorSessions(fs10, sessionsDir) {
|
|
2201
2504
|
const sessions = [];
|
|
2202
|
-
for (const name of
|
|
2505
|
+
for (const name of fs10.readdir(sessionsDir)) {
|
|
2203
2506
|
if (!name.endsWith(".json")) {
|
|
2204
2507
|
continue;
|
|
2205
2508
|
}
|
|
2206
2509
|
try {
|
|
2207
|
-
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(
|
|
2510
|
+
const parsed = FrontDoorSessionSchema.safeParse(JSON.parse(fs10.readFileUtf8(path7.join(sessionsDir, name)) ?? ""));
|
|
2208
2511
|
if (parsed.success) {
|
|
2209
2512
|
sessions.push({ pid: parsed.data.pid, startedAt: parsed.data.startedAt });
|
|
2210
2513
|
}
|
|
@@ -2214,14 +2517,14 @@ function listFrontDoorSessions(fs3, sessionsDir) {
|
|
|
2214
2517
|
}
|
|
2215
2518
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2216
2519
|
}
|
|
2217
|
-
function removeFrontDoorSession(
|
|
2218
|
-
|
|
2520
|
+
function removeFrontDoorSession(fs10, sessionsDir, pid) {
|
|
2521
|
+
fs10.removeRecursive(path7.join(sessionsDir, `${String(pid)}.json`));
|
|
2219
2522
|
}
|
|
2220
|
-
function pruneDeadFrontDoorSessions(
|
|
2523
|
+
function pruneDeadFrontDoorSessions(fs10, sessionsDir, isRunning) {
|
|
2221
2524
|
const removed = [];
|
|
2222
|
-
for (const session of listFrontDoorSessions(
|
|
2525
|
+
for (const session of listFrontDoorSessions(fs10, sessionsDir)) {
|
|
2223
2526
|
if (!isRunning(session.pid)) {
|
|
2224
|
-
removeFrontDoorSession(
|
|
2527
|
+
removeFrontDoorSession(fs10, sessionsDir, session.pid);
|
|
2225
2528
|
removed.push(session.pid);
|
|
2226
2529
|
}
|
|
2227
2530
|
}
|
|
@@ -2233,8 +2536,8 @@ var FRONTDOOR_SUPERVISOR_STILL_RUNNING = -1;
|
|
|
2233
2536
|
var FRONTDOOR_POLL_MS = 1e3;
|
|
2234
2537
|
var MS_PER_MINUTE = 6e4;
|
|
2235
2538
|
async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {}) {
|
|
2236
|
-
const { fs:
|
|
2237
|
-
const previous = readFrontDoorState(
|
|
2539
|
+
const { fs: fs10, paths } = ports;
|
|
2540
|
+
const previous = readFrontDoorState(fs10, paths.frontdoorStateFile);
|
|
2238
2541
|
let lastPort = previous?.lastPort;
|
|
2239
2542
|
let lastConnectPort = previous?.lastConnectPort;
|
|
2240
2543
|
let lastDirectPort = previous?.lastDirectPort;
|
|
@@ -2244,12 +2547,12 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2244
2547
|
...lastDirectPort === void 0 ? {} : { lastDirectPort }
|
|
2245
2548
|
});
|
|
2246
2549
|
const fail = (message) => {
|
|
2247
|
-
writeFrontDoorState(
|
|
2248
|
-
|
|
2550
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { lastError: message, ...sticky() });
|
|
2551
|
+
fs10.removeRecursive(paths.frontdoorLockFile);
|
|
2249
2552
|
ports.log(`agent-shim frontdoor supervisor: ${message}`);
|
|
2250
2553
|
return 1;
|
|
2251
2554
|
};
|
|
2252
|
-
writeFrontDoorState(
|
|
2555
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { supervisorPid: ports.ownPid, ...sticky() });
|
|
2253
2556
|
const previousSticky = lastPort;
|
|
2254
2557
|
let http2;
|
|
2255
2558
|
try {
|
|
@@ -2286,8 +2589,8 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2286
2589
|
ports.log(`agent-shim frontdoor supervisor: sticky direct port ${String(previousStickyDirect)} was occupied; moving to ${String(direct.port)} (headroom must be restarted to admit the new address)`);
|
|
2287
2590
|
}
|
|
2288
2591
|
lastDirectPort = direct.port;
|
|
2289
|
-
writeFrontDoorState(
|
|
2290
|
-
|
|
2592
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, { protocol: FRONT_DOOR_PROTOCOL, supervisorPid: ports.ownPid, port: http2.port, connectPort: connect3.port, directPort: direct.port, ...sticky() });
|
|
2593
|
+
fs10.removeRecursive(paths.frontdoorLockFile);
|
|
2291
2594
|
ports.log(`agent-shim frontdoor supervisor ${String(ports.ownPid)}: front door on 127.0.0.1:${String(http2.port)}, CONNECT surface on 127.0.0.1:${String(connect3.port)}`);
|
|
2292
2595
|
let idleSince;
|
|
2293
2596
|
let ticks = 0;
|
|
@@ -2296,15 +2599,15 @@ async function runFrontDoorSupervisor(idleShutdownMinutes, ports, options = {})
|
|
|
2296
2599
|
return FRONTDOOR_SUPERVISOR_STILL_RUNNING;
|
|
2297
2600
|
}
|
|
2298
2601
|
ticks += 1;
|
|
2299
|
-
pruneDeadFrontDoorSessions(
|
|
2300
|
-
if (listFrontDoorSessions(
|
|
2602
|
+
pruneDeadFrontDoorSessions(fs10, paths.frontdoorSessionsDir, ports.isRunning);
|
|
2603
|
+
if (listFrontDoorSessions(fs10, paths.frontdoorSessionsDir).length === 0) {
|
|
2301
2604
|
idleSince ??= ports.now();
|
|
2302
2605
|
if (ports.now() - idleSince >= idleShutdownMinutes * MS_PER_MINUTE) {
|
|
2303
2606
|
ports.log(`agent-shim frontdoor supervisor: no sessions for ${String(idleShutdownMinutes)} minute(s); closing the front door and exiting`);
|
|
2304
2607
|
await direct.close();
|
|
2305
2608
|
await connect3.close();
|
|
2306
2609
|
await http2.close();
|
|
2307
|
-
writeFrontDoorState(
|
|
2610
|
+
writeFrontDoorState(fs10, paths.frontdoorStateFile, sticky());
|
|
2308
2611
|
return 0;
|
|
2309
2612
|
}
|
|
2310
2613
|
} else {
|
|
@@ -2359,8 +2662,8 @@ function parseConnectTarget(authority) {
|
|
|
2359
2662
|
function isInterceptedHost(host, interceptHosts) {
|
|
2360
2663
|
return interceptHosts.includes(host);
|
|
2361
2664
|
}
|
|
2362
|
-
function servedByPipeline(
|
|
2363
|
-
return
|
|
2665
|
+
function servedByPipeline(path23) {
|
|
2666
|
+
return path23?.startsWith(ROUTED_PATH_PREFIX) === true;
|
|
2364
2667
|
}
|
|
2365
2668
|
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
2366
2669
|
"connection",
|
|
@@ -2658,15 +2961,15 @@ async function blindTunnel(socket, target, effects) {
|
|
|
2658
2961
|
socket.destroy();
|
|
2659
2962
|
});
|
|
2660
2963
|
}
|
|
2661
|
-
function
|
|
2964
|
+
function isEnoent2(error) {
|
|
2662
2965
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2663
2966
|
}
|
|
2664
2967
|
function realConnectCertStore(paths) {
|
|
2665
2968
|
const read = (file) => {
|
|
2666
2969
|
try {
|
|
2667
|
-
return
|
|
2970
|
+
return fs2.readFileSync(file, "utf8");
|
|
2668
2971
|
} catch (error) {
|
|
2669
|
-
if (
|
|
2972
|
+
if (isEnoent2(error)) {
|
|
2670
2973
|
return void 0;
|
|
2671
2974
|
}
|
|
2672
2975
|
throw error;
|
|
@@ -2679,10 +2982,10 @@ function realConnectCertStore(paths) {
|
|
|
2679
2982
|
return certPem === void 0 || keyPem2 === void 0 ? void 0 : { certPem, keyPem: keyPem2 };
|
|
2680
2983
|
},
|
|
2681
2984
|
writeCa: (ca) => {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2985
|
+
fs2.mkdirSync(paths.frontdoorCaDir, { recursive: true });
|
|
2986
|
+
fs2.writeFileSync(paths.frontdoorCaCertFile, ca.certPem, "utf8");
|
|
2987
|
+
fs2.writeFileSync(paths.frontdoorCaKeyFile, ca.keyPem, { encoding: "utf8", mode: CA_KEY_FILE_MODE });
|
|
2988
|
+
fs2.chmodSync(paths.frontdoorCaKeyFile, CA_KEY_FILE_MODE);
|
|
2686
2989
|
}
|
|
2687
2990
|
};
|
|
2688
2991
|
}
|
|
@@ -2851,11 +3154,11 @@ function realConnectEffects() {
|
|
|
2851
3154
|
}
|
|
2852
3155
|
|
|
2853
3156
|
// src/frontdoor/ensure.ts
|
|
2854
|
-
import { randomUUID as
|
|
3157
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2855
3158
|
|
|
2856
3159
|
// src/headroom/state.ts
|
|
2857
3160
|
import { createHash as createHash2 } from "node:crypto";
|
|
2858
|
-
import
|
|
3161
|
+
import path8 from "node:path";
|
|
2859
3162
|
import { z as z4 } from "zod";
|
|
2860
3163
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
2861
3164
|
var HeadroomStateSchema = z4.strictObject({
|
|
@@ -2889,16 +3192,16 @@ function parseJson(raw, schema) {
|
|
|
2889
3192
|
const parsed = schema.safeParse(JSON.parse(raw));
|
|
2890
3193
|
return parsed.success ? parsed.data : void 0;
|
|
2891
3194
|
}
|
|
2892
|
-
function readHeadroomState(
|
|
3195
|
+
function readHeadroomState(fs10, stateFile) {
|
|
2893
3196
|
try {
|
|
2894
|
-
return parseJson(
|
|
3197
|
+
return parseJson(fs10.readFileUtf8(stateFile), HeadroomStateSchema);
|
|
2895
3198
|
} catch {
|
|
2896
3199
|
return void 0;
|
|
2897
3200
|
}
|
|
2898
3201
|
}
|
|
2899
|
-
function writeHeadroomState(
|
|
2900
|
-
|
|
2901
|
-
|
|
3202
|
+
function writeHeadroomState(fs10, stateFile, state) {
|
|
3203
|
+
fs10.mkdirp(path8.dirname(stateFile));
|
|
3204
|
+
fs10.writeFilePrivate(stateFile, `${JSON.stringify(state, null, 2)}
|
|
2902
3205
|
`);
|
|
2903
3206
|
}
|
|
2904
3207
|
function headroomAllowlist(providers) {
|
|
@@ -2918,14 +3221,14 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
2918
3221
|
function hashAllowlist(allowlist) {
|
|
2919
3222
|
return createHash2("sha256").update(allowlist.join("\n")).digest("hex");
|
|
2920
3223
|
}
|
|
2921
|
-
function readAllProviders(
|
|
3224
|
+
function readAllProviders(fs10, providersDir) {
|
|
2922
3225
|
const result = [];
|
|
2923
|
-
for (const name of
|
|
3226
|
+
for (const name of fs10.readdir(providersDir)) {
|
|
2924
3227
|
if (!name.endsWith(".json")) {
|
|
2925
3228
|
continue;
|
|
2926
3229
|
}
|
|
2927
3230
|
try {
|
|
2928
|
-
const parsed = parseJson(
|
|
3231
|
+
const parsed = parseJson(fs10.readFileUtf8(path8.join(providersDir, name)), ProviderSchema);
|
|
2929
3232
|
if (parsed !== void 0) {
|
|
2930
3233
|
result.push({ name: name.slice(0, -".json".length), provider: parsed });
|
|
2931
3234
|
}
|
|
@@ -2935,22 +3238,22 @@ function readAllProviders(fs3, providersDir) {
|
|
|
2935
3238
|
}
|
|
2936
3239
|
return result;
|
|
2937
3240
|
}
|
|
2938
|
-
function writeSession(
|
|
2939
|
-
|
|
2940
|
-
|
|
3241
|
+
function writeSession(fs10, sessionsDir, session) {
|
|
3242
|
+
fs10.mkdirp(sessionsDir);
|
|
3243
|
+
fs10.writeFileUtf8(path8.join(sessionsDir, `${String(session.pid)}.json`), `${JSON.stringify(session, null, 2)}
|
|
2941
3244
|
`);
|
|
2942
3245
|
}
|
|
2943
|
-
function removeSession(
|
|
2944
|
-
|
|
3246
|
+
function removeSession(fs10, sessionsDir, pid) {
|
|
3247
|
+
fs10.removeRecursive(path8.join(sessionsDir, `${String(pid)}.json`));
|
|
2945
3248
|
}
|
|
2946
|
-
function listSessions(
|
|
3249
|
+
function listSessions(fs10, sessionsDir) {
|
|
2947
3250
|
const sessions = [];
|
|
2948
|
-
for (const name of
|
|
3251
|
+
for (const name of fs10.readdir(sessionsDir)) {
|
|
2949
3252
|
if (!name.endsWith(".json")) {
|
|
2950
3253
|
continue;
|
|
2951
3254
|
}
|
|
2952
3255
|
try {
|
|
2953
|
-
const parsed = parseJson(
|
|
3256
|
+
const parsed = parseJson(fs10.readFileUtf8(path8.join(sessionsDir, name)), HeadroomSessionSchema);
|
|
2954
3257
|
if (parsed !== void 0) {
|
|
2955
3258
|
sessions.push(parsed);
|
|
2956
3259
|
}
|
|
@@ -2960,22 +3263,22 @@ function listSessions(fs3, sessionsDir) {
|
|
|
2960
3263
|
}
|
|
2961
3264
|
return sessions.sort((a, b) => a.pid - b.pid);
|
|
2962
3265
|
}
|
|
2963
|
-
function sessionsForSupervisor(
|
|
2964
|
-
return listSessions(
|
|
3266
|
+
function sessionsForSupervisor(fs10, sessionsDir, supervisorPid) {
|
|
3267
|
+
return listSessions(fs10, sessionsDir).filter((session) => session.supervisorPid === supervisorPid);
|
|
2965
3268
|
}
|
|
2966
|
-
function pruneDeadSessions(
|
|
3269
|
+
function pruneDeadSessions(fs10, sessionsDir, isRunning) {
|
|
2967
3270
|
const removed = [];
|
|
2968
|
-
for (const session of listSessions(
|
|
3271
|
+
for (const session of listSessions(fs10, sessionsDir)) {
|
|
2969
3272
|
if (!isRunning(session.pid)) {
|
|
2970
|
-
removeSession(
|
|
3273
|
+
removeSession(fs10, sessionsDir, session.pid);
|
|
2971
3274
|
removed.push(session.pid);
|
|
2972
3275
|
}
|
|
2973
3276
|
}
|
|
2974
3277
|
return removed;
|
|
2975
3278
|
}
|
|
2976
|
-
function readStartLock(
|
|
3279
|
+
function readStartLock(fs10, lockFile) {
|
|
2977
3280
|
try {
|
|
2978
|
-
return parseJson(
|
|
3281
|
+
return parseJson(fs10.readFileUtf8(lockFile), HeadroomLockSchema);
|
|
2979
3282
|
} catch {
|
|
2980
3283
|
return void 0;
|
|
2981
3284
|
}
|
|
@@ -3015,7 +3318,7 @@ function ensureFrontDoor(params) {
|
|
|
3015
3318
|
continue;
|
|
3016
3319
|
}
|
|
3017
3320
|
if (verdict.ok) {
|
|
3018
|
-
const token =
|
|
3321
|
+
const token = randomUUID3();
|
|
3019
3322
|
writeFrontDoorSession(ports.fs, paths.frontdoorSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), token });
|
|
3020
3323
|
return { port: state.port, connectPort: state.connectPort, token };
|
|
3021
3324
|
}
|
|
@@ -3055,7 +3358,10 @@ function ensureFrontDoor(params) {
|
|
|
3055
3358
|
}
|
|
3056
3359
|
|
|
3057
3360
|
// src/usage/read.ts
|
|
3058
|
-
import
|
|
3361
|
+
import path10 from "node:path";
|
|
3362
|
+
|
|
3363
|
+
// src/usage/account.ts
|
|
3364
|
+
import path9 from "node:path";
|
|
3059
3365
|
|
|
3060
3366
|
// src/usage/schema.ts
|
|
3061
3367
|
import { z as z5 } from "zod";
|
|
@@ -3205,11 +3511,111 @@ var UsageSnapshotSchema = z5.strictObject({
|
|
|
3205
3511
|
});
|
|
3206
3512
|
|
|
3207
3513
|
// src/usage/account.ts
|
|
3514
|
+
var CLAUDE_JSON = ".claude.json";
|
|
3515
|
+
var AccountMetadataError = class extends CliError {
|
|
3516
|
+
constructor(file, detail) {
|
|
3517
|
+
super(`${file}: ${detail}, so its account metadata cannot be read.`);
|
|
3518
|
+
this.file = file;
|
|
3519
|
+
this.name = "AccountMetadataError";
|
|
3520
|
+
}
|
|
3521
|
+
file;
|
|
3522
|
+
};
|
|
3523
|
+
function isObject(value) {
|
|
3524
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3525
|
+
}
|
|
3208
3526
|
function isIdentityName(name) {
|
|
3209
3527
|
return IdentitySchema.shape.name.safeParse(name).success;
|
|
3210
3528
|
}
|
|
3529
|
+
function claudeJsonPath(identitiesDir, identity) {
|
|
3530
|
+
return path9.join(identitiesDir, identity, CLAUDE_JSON);
|
|
3531
|
+
}
|
|
3532
|
+
function readAccountMetadata(fs10, identitiesDir, identity) {
|
|
3533
|
+
if (!isIdentityName(identity)) {
|
|
3534
|
+
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3535
|
+
}
|
|
3536
|
+
const file = claudeJsonPath(identitiesDir, identity);
|
|
3537
|
+
const raw = fs10.readFileUtf8(file);
|
|
3538
|
+
if (raw === void 0) {
|
|
3539
|
+
return void 0;
|
|
3540
|
+
}
|
|
3541
|
+
let parsed;
|
|
3542
|
+
try {
|
|
3543
|
+
parsed = JSON.parse(raw);
|
|
3544
|
+
} catch {
|
|
3545
|
+
throw new AccountMetadataError(file, "not valid JSON");
|
|
3546
|
+
}
|
|
3547
|
+
if (!isObject(parsed)) {
|
|
3548
|
+
throw new AccountMetadataError(file, "not a JSON object");
|
|
3549
|
+
}
|
|
3550
|
+
const oauthAccount = parsed.oauthAccount;
|
|
3551
|
+
if (!isObject(oauthAccount)) {
|
|
3552
|
+
return void 0;
|
|
3553
|
+
}
|
|
3554
|
+
const picked = {};
|
|
3555
|
+
for (const key of Object.keys(AccountMetadataSchema.shape)) {
|
|
3556
|
+
const value = oauthAccount[key];
|
|
3557
|
+
if (value !== null && value !== void 0) {
|
|
3558
|
+
picked[key] = value;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
const account = AccountMetadataSchema.safeParse(picked);
|
|
3562
|
+
if (!account.success) {
|
|
3563
|
+
throw new AccountMetadataError(file, account.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
|
|
3564
|
+
}
|
|
3565
|
+
return account.data;
|
|
3566
|
+
}
|
|
3211
3567
|
|
|
3212
3568
|
// src/usage/read.ts
|
|
3569
|
+
var SEGMENT_RE = /^(\d{4}-\d{2}-\d{2})\.(\d+)\.jsonl$/;
|
|
3570
|
+
var DAY_MS = 864e5;
|
|
3571
|
+
function listLogSegments(fs10, logDir) {
|
|
3572
|
+
return fs10.readdir(logDir).flatMap((name) => {
|
|
3573
|
+
const match = SEGMENT_RE.exec(name);
|
|
3574
|
+
const day = match?.[1];
|
|
3575
|
+
if (day === void 0) {
|
|
3576
|
+
return [];
|
|
3577
|
+
}
|
|
3578
|
+
return [{ name, day, endsAt: Date.parse(`${day}T00:00:00.000Z`) + DAY_MS }];
|
|
3579
|
+
}).sort((left, right) => left.day.localeCompare(right.day) || left.name.localeCompare(right.name));
|
|
3580
|
+
}
|
|
3581
|
+
function readUsageLog(fs10, logDir, options = {}) {
|
|
3582
|
+
const { sinceMs } = options;
|
|
3583
|
+
const records = [];
|
|
3584
|
+
let invalidLines = 0;
|
|
3585
|
+
for (const segment of listLogSegments(fs10, logDir)) {
|
|
3586
|
+
if (sinceMs !== void 0 && segment.endsAt <= sinceMs) {
|
|
3587
|
+
continue;
|
|
3588
|
+
}
|
|
3589
|
+
const contents = fs10.readFileUtf8(path10.join(logDir, segment.name));
|
|
3590
|
+
if (contents === void 0) {
|
|
3591
|
+
continue;
|
|
3592
|
+
}
|
|
3593
|
+
const lines = contents.split("\n");
|
|
3594
|
+
lines.pop();
|
|
3595
|
+
for (const line of lines) {
|
|
3596
|
+
if (line === "") {
|
|
3597
|
+
continue;
|
|
3598
|
+
}
|
|
3599
|
+
let raw;
|
|
3600
|
+
try {
|
|
3601
|
+
raw = JSON.parse(line);
|
|
3602
|
+
} catch {
|
|
3603
|
+
invalidLines += 1;
|
|
3604
|
+
continue;
|
|
3605
|
+
}
|
|
3606
|
+
const parsed = UsageRecordSchema.safeParse(raw);
|
|
3607
|
+
if (!parsed.success) {
|
|
3608
|
+
invalidLines += 1;
|
|
3609
|
+
continue;
|
|
3610
|
+
}
|
|
3611
|
+
if (sinceMs === void 0 || Date.parse(parsed.data.at) >= sinceMs) {
|
|
3612
|
+
records.push(parsed.data);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
}
|
|
3616
|
+
records.sort((left, right) => left.at.localeCompare(right.at));
|
|
3617
|
+
return { records, invalidLines };
|
|
3618
|
+
}
|
|
3213
3619
|
var UsageSnapshotError = class extends Error {
|
|
3214
3620
|
constructor(file, detail) {
|
|
3215
3621
|
super(`${file} is not a usage snapshot this agent-shim can read (schema version ${String(USAGE_SCHEMA_VERSION)}): ${detail}`);
|
|
@@ -3222,11 +3628,11 @@ function snapshotPath(snapshotsDir, identity) {
|
|
|
3222
3628
|
if (!isIdentityName(identity)) {
|
|
3223
3629
|
throw new Error(`"${identity}" is not a valid identity name.`);
|
|
3224
3630
|
}
|
|
3225
|
-
return
|
|
3631
|
+
return path10.join(snapshotsDir, `${identity}.json`);
|
|
3226
3632
|
}
|
|
3227
|
-
function readUsageSnapshot(
|
|
3633
|
+
function readUsageSnapshot(fs10, snapshotsDir, identity) {
|
|
3228
3634
|
const file = snapshotPath(snapshotsDir, identity);
|
|
3229
|
-
const raw =
|
|
3635
|
+
const raw = fs10.readFileUtf8(file);
|
|
3230
3636
|
if (raw === void 0) {
|
|
3231
3637
|
return void 0;
|
|
3232
3638
|
}
|
|
@@ -3242,8 +3648,8 @@ function readUsageSnapshot(fs3, snapshotsDir, identity) {
|
|
|
3242
3648
|
}
|
|
3243
3649
|
return snapshot.data;
|
|
3244
3650
|
}
|
|
3245
|
-
function listUsageSnapshots(
|
|
3246
|
-
return
|
|
3651
|
+
function listUsageSnapshots(fs10, snapshotsDir) {
|
|
3652
|
+
return fs10.readdir(snapshotsDir).flatMap((name) => {
|
|
3247
3653
|
if (!name.endsWith(".json")) {
|
|
3248
3654
|
return [];
|
|
3249
3655
|
}
|
|
@@ -3251,7 +3657,7 @@ function listUsageSnapshots(fs3, snapshotsDir) {
|
|
|
3251
3657
|
if (!isIdentityName(identity)) {
|
|
3252
3658
|
return [];
|
|
3253
3659
|
}
|
|
3254
|
-
const snapshot = readUsageSnapshot(
|
|
3660
|
+
const snapshot = readUsageSnapshot(fs10, snapshotsDir, identity);
|
|
3255
3661
|
return snapshot === void 0 ? [] : [snapshot];
|
|
3256
3662
|
}).sort((left, right) => left.identity.localeCompare(right.identity));
|
|
3257
3663
|
}
|
|
@@ -3344,6 +3750,14 @@ import { z as z6 } from "zod";
|
|
|
3344
3750
|
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
3345
3751
|
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
3346
3752
|
var DirectUrlSchema = z6.object({ vcs_info: z6.object({ commit_id: z6.string() }) });
|
|
3753
|
+
function isMovingGitSource(spec) {
|
|
3754
|
+
const match = GIT_SPEC_RE.exec(spec);
|
|
3755
|
+
if (match === null) {
|
|
3756
|
+
return false;
|
|
3757
|
+
}
|
|
3758
|
+
const ref = match[1];
|
|
3759
|
+
return ref === void 0 || !COMMIT_SHA_RE.test(ref);
|
|
3760
|
+
}
|
|
3347
3761
|
function pinnedGitCommit(spec) {
|
|
3348
3762
|
const ref = GIT_SPEC_RE.exec(spec)?.[1];
|
|
3349
3763
|
return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
|
|
@@ -3418,9 +3832,9 @@ function compareVersions(a, b) {
|
|
|
3418
3832
|
}
|
|
3419
3833
|
var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
|
|
3420
3834
|
async function runSupervisor(initialConfig, ports, options = {}) {
|
|
3421
|
-
const { fs:
|
|
3835
|
+
const { fs: fs10, paths } = ports;
|
|
3422
3836
|
let config = initialConfig;
|
|
3423
|
-
const previousState = readHeadroomState(
|
|
3837
|
+
const previousState = readHeadroomState(fs10, paths.headroomStateFile);
|
|
3424
3838
|
if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
|
|
3425
3839
|
ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
|
|
3426
3840
|
return 0;
|
|
@@ -3429,19 +3843,19 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3429
3843
|
let lastPort = previousState?.lastPort;
|
|
3430
3844
|
const sticky = () => lastPort === void 0 ? {} : { lastPort };
|
|
3431
3845
|
const ownsState = () => {
|
|
3432
|
-
const owner = readHeadroomState(
|
|
3846
|
+
const owner = readHeadroomState(fs10, paths.headroomStateFile)?.supervisorPid;
|
|
3433
3847
|
return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
|
|
3434
3848
|
};
|
|
3435
3849
|
const writeOwnState = (state) => {
|
|
3436
3850
|
if (!ownsState()) {
|
|
3437
3851
|
return false;
|
|
3438
3852
|
}
|
|
3439
|
-
writeHeadroomState(
|
|
3853
|
+
writeHeadroomState(fs10, paths.headroomStateFile, state);
|
|
3440
3854
|
return true;
|
|
3441
3855
|
};
|
|
3442
3856
|
const fail = (message) => {
|
|
3443
3857
|
if (writeOwnState({ lastError: message, ...sticky() })) {
|
|
3444
|
-
|
|
3858
|
+
fs10.removeRecursive(paths.headroomLockFile);
|
|
3445
3859
|
}
|
|
3446
3860
|
ports.log(`agent-shim headroom supervisor: ${message}`);
|
|
3447
3861
|
return 1;
|
|
@@ -3490,7 +3904,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3490
3904
|
const settingsHash = hashSettings(config.settings);
|
|
3491
3905
|
const allowlist = allowlistOf(ports);
|
|
3492
3906
|
const allowlistHash = hashAllowlist(allowlist);
|
|
3493
|
-
pruneDeadSessions(
|
|
3907
|
+
pruneDeadSessions(fs10, paths.headroomSessionsDir, ports.isRunning);
|
|
3494
3908
|
const ownsNow = ownsState();
|
|
3495
3909
|
if (!ownsNow && !superseded) {
|
|
3496
3910
|
ports.log("agent-shim headroom supervisor: another supervisor now owns state.json; serving this daemon's own sessions until they end");
|
|
@@ -3542,7 +3956,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3542
3956
|
...sticky()
|
|
3543
3957
|
});
|
|
3544
3958
|
if (recorded) {
|
|
3545
|
-
|
|
3959
|
+
fs10.removeRecursive(paths.headroomLockFile);
|
|
3546
3960
|
}
|
|
3547
3961
|
idleSince = void 0;
|
|
3548
3962
|
ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on 127.0.0.1:${String(port)}`);
|
|
@@ -3557,7 +3971,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3557
3971
|
}
|
|
3558
3972
|
} else {
|
|
3559
3973
|
if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
|
|
3560
|
-
if (sessionsForSupervisor(
|
|
3974
|
+
if (sessionsForSupervisor(fs10, paths.headroomSessionsDir, ports.ownPid).length === 0) {
|
|
3561
3975
|
ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
|
|
3562
3976
|
ports.stopProcess(headroomPid);
|
|
3563
3977
|
headroomPid = void 0;
|
|
@@ -3566,7 +3980,7 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3566
3980
|
continue;
|
|
3567
3981
|
}
|
|
3568
3982
|
}
|
|
3569
|
-
const sessions = sessionsForSupervisor(
|
|
3983
|
+
const sessions = sessionsForSupervisor(fs10, paths.headroomSessionsDir, ports.ownPid);
|
|
3570
3984
|
if (sessions.length === 0) {
|
|
3571
3985
|
idleSince ??= ports.now();
|
|
3572
3986
|
const idleMs = ports.now() - idleSince;
|
|
@@ -3588,9 +4002,9 @@ async function runSupervisor(initialConfig, ports, options = {}) {
|
|
|
3588
4002
|
await ports.sleep(HEADROOM_POLL_MS);
|
|
3589
4003
|
}
|
|
3590
4004
|
}
|
|
3591
|
-
function currentHeadroomAllowlist(
|
|
3592
|
-
const providers = readAllProviders(
|
|
3593
|
-
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(
|
|
4005
|
+
function currentHeadroomAllowlist(fs10, paths) {
|
|
4006
|
+
const providers = readAllProviders(fs10, paths.providersDir).map((entry) => entry.provider);
|
|
4007
|
+
return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs10, paths.frontdoorStateFile))));
|
|
3594
4008
|
}
|
|
3595
4009
|
function allowlistOf(ports) {
|
|
3596
4010
|
return currentHeadroomAllowlist(ports.fs, ports.paths);
|
|
@@ -3656,63 +4070,2564 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
3656
4070
|
}
|
|
3657
4071
|
|
|
3658
4072
|
// src/paths.ts
|
|
3659
|
-
import
|
|
4073
|
+
import fs3 from "node:fs";
|
|
3660
4074
|
import os from "node:os";
|
|
3661
|
-
import
|
|
4075
|
+
import path11 from "node:path";
|
|
3662
4076
|
function resolveAgentShimHome(env = process.env, home = os.homedir(), isDirectory = isExistingDirectory) {
|
|
3663
4077
|
const fromEnv = env.AGENT_SHIM_HOME;
|
|
3664
4078
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3665
4079
|
return fromEnv;
|
|
3666
4080
|
}
|
|
3667
|
-
const current =
|
|
3668
|
-
const legacy =
|
|
4081
|
+
const current = path11.join(home, HOME_DIRNAME);
|
|
4082
|
+
const legacy = path11.join(home, LEGACY_HOME_DIRNAME);
|
|
3669
4083
|
return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
|
|
3670
4084
|
}
|
|
3671
4085
|
function isExistingDirectory(candidate) {
|
|
3672
|
-
return
|
|
4086
|
+
return fs3.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
|
|
3673
4087
|
}
|
|
3674
4088
|
function resolveClaudeHome() {
|
|
3675
4089
|
const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
|
|
3676
4090
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
3677
4091
|
return fromEnv;
|
|
3678
4092
|
}
|
|
3679
|
-
return
|
|
4093
|
+
return path11.join(os.homedir(), ".claude");
|
|
3680
4094
|
}
|
|
3681
4095
|
function buildLayoutPaths(root) {
|
|
3682
4096
|
return {
|
|
3683
4097
|
root,
|
|
3684
|
-
identitiesDir:
|
|
3685
|
-
configProfilesDir:
|
|
3686
|
-
providersDir:
|
|
3687
|
-
directoryRulesFile:
|
|
3688
|
-
activeIdentityFile:
|
|
3689
|
-
globalConfigFile:
|
|
3690
|
-
categoriesLocalFile:
|
|
3691
|
-
claudeShimFile:
|
|
3692
|
-
headroomDir:
|
|
3693
|
-
headroomStateFile:
|
|
3694
|
-
headroomLockFile:
|
|
3695
|
-
headroomSessionsDir:
|
|
3696
|
-
logsDir:
|
|
3697
|
-
headroomLogPath:
|
|
3698
|
-
frontdoorDir:
|
|
3699
|
-
frontdoorCaDir:
|
|
3700
|
-
frontdoorCaCertFile:
|
|
3701
|
-
frontdoorCaKeyFile:
|
|
3702
|
-
frontdoorCaBundlesDir:
|
|
3703
|
-
frontdoorStateFile:
|
|
3704
|
-
frontdoorLockFile:
|
|
3705
|
-
frontdoorSessionsDir:
|
|
3706
|
-
frontdoorLogPath:
|
|
3707
|
-
usageDir:
|
|
3708
|
-
usageLogDir:
|
|
3709
|
-
usageSnapshotsDir:
|
|
3710
|
-
usagePicksFile:
|
|
4098
|
+
identitiesDir: path11.join(root, "identities"),
|
|
4099
|
+
configProfilesDir: path11.join(root, "config-profiles"),
|
|
4100
|
+
providersDir: path11.join(root, "providers"),
|
|
4101
|
+
directoryRulesFile: path11.join(root, "directory-rules.json"),
|
|
4102
|
+
activeIdentityFile: path11.join(root, "active-identity"),
|
|
4103
|
+
globalConfigFile: path11.join(root, "config.json"),
|
|
4104
|
+
categoriesLocalFile: path11.join(root, "categories.local.json"),
|
|
4105
|
+
claudeShimFile: path11.join(root, "claude-shim.json"),
|
|
4106
|
+
headroomDir: path11.join(root, "headroom"),
|
|
4107
|
+
headroomStateFile: path11.join(root, "headroom", "state.json"),
|
|
4108
|
+
headroomLockFile: path11.join(root, "headroom", "start.lock"),
|
|
4109
|
+
headroomSessionsDir: path11.join(root, "headroom", "sessions"),
|
|
4110
|
+
logsDir: path11.join(root, "logs"),
|
|
4111
|
+
headroomLogPath: path11.join(root, "logs", "headroom.log"),
|
|
4112
|
+
frontdoorDir: path11.join(root, "frontdoor"),
|
|
4113
|
+
frontdoorCaDir: path11.join(root, "frontdoor", "ca"),
|
|
4114
|
+
frontdoorCaCertFile: path11.join(root, "frontdoor", "ca", "ca.pem"),
|
|
4115
|
+
frontdoorCaKeyFile: path11.join(root, "frontdoor", "ca", "ca.key"),
|
|
4116
|
+
frontdoorCaBundlesDir: path11.join(root, "frontdoor", "ca", "bundles"),
|
|
4117
|
+
frontdoorStateFile: path11.join(root, "frontdoor", "state.json"),
|
|
4118
|
+
frontdoorLockFile: path11.join(root, "frontdoor", "start.lock"),
|
|
4119
|
+
frontdoorSessionsDir: path11.join(root, "frontdoor", "sessions"),
|
|
4120
|
+
frontdoorLogPath: path11.join(root, "logs", "frontdoor.log"),
|
|
4121
|
+
usageDir: path11.join(root, "usage"),
|
|
4122
|
+
usageLogDir: path11.join(root, "usage", "log"),
|
|
4123
|
+
usageSnapshotsDir: path11.join(root, "usage", "snapshots"),
|
|
4124
|
+
usagePicksFile: path11.join(root, "usage", "picks.json")
|
|
3711
4125
|
};
|
|
3712
4126
|
}
|
|
3713
4127
|
function resolveLayoutPaths() {
|
|
3714
4128
|
return buildLayoutPaths(resolveAgentShimHome());
|
|
3715
4129
|
}
|
|
4130
|
+
|
|
4131
|
+
// src/identityStore.ts
|
|
4132
|
+
import fs5 from "node:fs";
|
|
4133
|
+
import path14 from "node:path";
|
|
4134
|
+
|
|
4135
|
+
// src/launcher/identity.ts
|
|
4136
|
+
import path12 from "node:path";
|
|
4137
|
+
function poolNameOf(selector) {
|
|
4138
|
+
return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
|
|
4139
|
+
}
|
|
4140
|
+
function isNonEmpty(value) {
|
|
4141
|
+
return value !== void 0 && value !== "";
|
|
4142
|
+
}
|
|
4143
|
+
function selected(selector, source) {
|
|
4144
|
+
const pool = poolNameOf(selector);
|
|
4145
|
+
return pool === void 0 ? { name: selector, source, configDirEscapeHatch: false } : { pool, source, configDirEscapeHatch: false };
|
|
4146
|
+
}
|
|
4147
|
+
function decideIdentity(params) {
|
|
4148
|
+
if (isNonEmpty(params.env.CLAUDE_CONFIG_DIR)) {
|
|
4149
|
+
return { source: "config-dir-escape-hatch", configDirEscapeHatch: true };
|
|
4150
|
+
}
|
|
4151
|
+
if (isNonEmpty(params.argv0Identity)) {
|
|
4152
|
+
return selected(params.argv0Identity, "argv");
|
|
4153
|
+
}
|
|
4154
|
+
if (isNonEmpty(params.env.AGENT_SHIM_IDENTITY)) {
|
|
4155
|
+
return selected(params.env.AGENT_SHIM_IDENTITY, "env");
|
|
4156
|
+
}
|
|
4157
|
+
if (isNonEmpty(params.directoryPinnedIdentity)) {
|
|
4158
|
+
return selected(params.directoryPinnedIdentity, "directory-pin");
|
|
4159
|
+
}
|
|
4160
|
+
const persisted = params.readActiveIdentityFile();
|
|
4161
|
+
if (isNonEmpty(persisted)) {
|
|
4162
|
+
return selected(persisted, "active-identity-file");
|
|
4163
|
+
}
|
|
4164
|
+
return { source: "none", configDirEscapeHatch: false };
|
|
4165
|
+
}
|
|
4166
|
+
function decideConfigProfile(params) {
|
|
4167
|
+
if (isNonEmpty(params.cliFlagConfigProfile)) {
|
|
4168
|
+
return { name: params.cliFlagConfigProfile, source: "cli-flag" };
|
|
4169
|
+
}
|
|
4170
|
+
if (isNonEmpty(params.env.AGENT_SHIM_CONFIG_PROFILE)) {
|
|
4171
|
+
return { name: params.env.AGENT_SHIM_CONFIG_PROFILE, source: "env" };
|
|
4172
|
+
}
|
|
4173
|
+
if (isNonEmpty(params.directoryRuleConfigProfile)) {
|
|
4174
|
+
return { name: params.directoryRuleConfigProfile, source: "directory-rule" };
|
|
4175
|
+
}
|
|
4176
|
+
if (isNonEmpty(params.identityDefaultConfigProfile)) {
|
|
4177
|
+
return { name: params.identityDefaultConfigProfile, source: "identity-default" };
|
|
4178
|
+
}
|
|
4179
|
+
if (isNonEmpty(params.globalDefaultConfigProfile)) {
|
|
4180
|
+
return { name: params.globalDefaultConfigProfile, source: "global-default" };
|
|
4181
|
+
}
|
|
4182
|
+
return { source: "none" };
|
|
4183
|
+
}
|
|
4184
|
+
function loadIdentity(identitiesDir, name, fs10) {
|
|
4185
|
+
return loadConfigFile(path12.join(identitiesDir, name, "identity.json"), IdentitySchema, fs10.readConfigFile);
|
|
4186
|
+
}
|
|
4187
|
+
|
|
4188
|
+
// src/configProfilesStore.ts
|
|
4189
|
+
import fs4 from "node:fs";
|
|
4190
|
+
import path13 from "node:path";
|
|
4191
|
+
var ProfileNotFoundError = class extends CliError {
|
|
4192
|
+
constructor(profileName) {
|
|
4193
|
+
super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
|
|
4194
|
+
this.profileName = profileName;
|
|
4195
|
+
this.name = "ProfileNotFoundError";
|
|
4196
|
+
}
|
|
4197
|
+
profileName;
|
|
4198
|
+
};
|
|
4199
|
+
var ProfileAlreadyExistsError = class extends CliError {
|
|
4200
|
+
constructor(profileName) {
|
|
4201
|
+
super(`A configuration profile named "${profileName}" already exists.`);
|
|
4202
|
+
this.profileName = profileName;
|
|
4203
|
+
this.name = "ProfileAlreadyExistsError";
|
|
4204
|
+
}
|
|
4205
|
+
profileName;
|
|
4206
|
+
};
|
|
4207
|
+
var InvalidCategoryNameError = class extends CliError {
|
|
4208
|
+
constructor(categoryName) {
|
|
4209
|
+
super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
|
|
4210
|
+
this.categoryName = categoryName;
|
|
4211
|
+
this.name = "InvalidCategoryNameError";
|
|
4212
|
+
}
|
|
4213
|
+
categoryName;
|
|
4214
|
+
};
|
|
4215
|
+
function profileJsonPath(paths, name) {
|
|
4216
|
+
return path13.join(paths.configProfilesDir, `${name}.json`);
|
|
4217
|
+
}
|
|
4218
|
+
function profileExists(paths, name) {
|
|
4219
|
+
return fs4.existsSync(profileJsonPath(paths, name));
|
|
4220
|
+
}
|
|
4221
|
+
function requireProfileExists(paths, name) {
|
|
4222
|
+
if (!profileExists(paths, name)) {
|
|
4223
|
+
throw new ProfileNotFoundError(name);
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
function readProfile(paths, name) {
|
|
4227
|
+
return readJson(profileJsonPath(paths, name), ConfigProfileSchema);
|
|
4228
|
+
}
|
|
4229
|
+
function createProfile(paths, name, extendsList, description) {
|
|
4230
|
+
if (profileExists(paths, name)) {
|
|
4231
|
+
throw new ProfileAlreadyExistsError(name);
|
|
4232
|
+
}
|
|
4233
|
+
const filePath = profileJsonPath(paths, name);
|
|
4234
|
+
const parsed = ConfigProfileSchema.safeParse({
|
|
4235
|
+
...description === void 0 ? {} : { description },
|
|
4236
|
+
...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
|
|
4237
|
+
});
|
|
4238
|
+
if (!parsed.success) {
|
|
4239
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
4240
|
+
}
|
|
4241
|
+
writeJsonAtomic(filePath, parsed.data);
|
|
4242
|
+
return parsed.data;
|
|
4243
|
+
}
|
|
4244
|
+
function listProfiles(paths) {
|
|
4245
|
+
if (!fs4.existsSync(paths.configProfilesDir)) {
|
|
4246
|
+
return [];
|
|
4247
|
+
}
|
|
4248
|
+
const names = fs4.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
|
|
4249
|
+
const result = [];
|
|
4250
|
+
for (const name of names) {
|
|
4251
|
+
const profile = readProfile(paths, name);
|
|
4252
|
+
if (profile !== void 0) {
|
|
4253
|
+
result.push({ name, profile });
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
return result;
|
|
4257
|
+
}
|
|
4258
|
+
function globalConfigPath(paths) {
|
|
4259
|
+
return paths.globalConfigFile;
|
|
4260
|
+
}
|
|
4261
|
+
function readGlobalConfig(paths) {
|
|
4262
|
+
return readJson(globalConfigPath(paths), GlobalConfigSchema);
|
|
4263
|
+
}
|
|
4264
|
+
function setGlobalDefaultProfile(paths, name) {
|
|
4265
|
+
return applyPatch(
|
|
4266
|
+
globalConfigPath(paths),
|
|
4267
|
+
GlobalConfigSchema,
|
|
4268
|
+
{ defaultConfigProfile: name },
|
|
4269
|
+
{ defaults: {} }
|
|
4270
|
+
);
|
|
4271
|
+
}
|
|
4272
|
+
function validateCategoryNames(patch) {
|
|
4273
|
+
for (const key of Object.keys(patch)) {
|
|
4274
|
+
if (!isOverridableCategory(key)) {
|
|
4275
|
+
throw new InvalidCategoryNameError(key);
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
}
|
|
4279
|
+
function setProfileCategories(paths, name, patch) {
|
|
4280
|
+
requireProfileExists(paths, name);
|
|
4281
|
+
const expandedPatch = expandAllCategoryKey(patch);
|
|
4282
|
+
validateCategoryNames(expandedPatch);
|
|
4283
|
+
const existing = readProfile(paths, name) ?? {};
|
|
4284
|
+
const mergedCategories = { ...existing.categories, ...expandedPatch };
|
|
4285
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { categories: mergedCategories });
|
|
4286
|
+
}
|
|
4287
|
+
function setProfileEntries(paths, name, patch) {
|
|
4288
|
+
requireProfileExists(paths, name);
|
|
4289
|
+
const existing = readProfile(paths, name) ?? {};
|
|
4290
|
+
const mergedEntries = { ...existing.entries, ...patch };
|
|
4291
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
|
|
4292
|
+
}
|
|
4293
|
+
function setProfileLaunchFlags(paths, name, patch) {
|
|
4294
|
+
requireProfileExists(paths, name);
|
|
4295
|
+
const existing = readProfile(paths, name) ?? {};
|
|
4296
|
+
const mergedLaunch = { ...existing.launch, ...patch };
|
|
4297
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
4298
|
+
// Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
|
|
4299
|
+
launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
function setProfileMetadata(paths, name, patch) {
|
|
4303
|
+
requireProfileExists(paths, name);
|
|
4304
|
+
return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
|
|
4305
|
+
...patch.extends === void 0 ? {} : { extends: patch.extends.length === 0 ? void 0 : [...patch.extends] },
|
|
4306
|
+
...patch.description === void 0 ? {} : { description: patch.description === false ? void 0 : patch.description }
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
function removeProfile(paths, name) {
|
|
4310
|
+
requireProfileExists(paths, name);
|
|
4311
|
+
fs4.rmSync(profileJsonPath(paths, name));
|
|
4312
|
+
}
|
|
4313
|
+
|
|
4314
|
+
// src/poolStore.ts
|
|
4315
|
+
var PoolNotFoundError = class extends CliError {
|
|
4316
|
+
constructor(poolName) {
|
|
4317
|
+
super(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
|
|
4318
|
+
this.poolName = poolName;
|
|
4319
|
+
this.name = "PoolNotFoundError";
|
|
4320
|
+
}
|
|
4321
|
+
poolName;
|
|
4322
|
+
};
|
|
4323
|
+
var PoolAlreadyExistsError = class extends CliError {
|
|
4324
|
+
constructor(poolName) {
|
|
4325
|
+
super(`A pool named "${poolName}" already exists. Use \`agent-shim pool set ${poolName}\` to change its members.`);
|
|
4326
|
+
this.poolName = poolName;
|
|
4327
|
+
this.name = "PoolAlreadyExistsError";
|
|
4328
|
+
}
|
|
4329
|
+
poolName;
|
|
4330
|
+
};
|
|
4331
|
+
var InvalidPoolNameError = class extends CliError {
|
|
4332
|
+
constructor(attemptedName) {
|
|
4333
|
+
super(`"${attemptedName}" is not a valid pool name: it must start with a letter or number and may then contain letters, numbers, dots, hyphens and underscores.`);
|
|
4334
|
+
this.attemptedName = attemptedName;
|
|
4335
|
+
this.name = "InvalidPoolNameError";
|
|
4336
|
+
}
|
|
4337
|
+
attemptedName;
|
|
4338
|
+
};
|
|
4339
|
+
function readPools(paths) {
|
|
4340
|
+
return readGlobalConfig(paths)?.pools ?? {};
|
|
4341
|
+
}
|
|
4342
|
+
function requirePool(paths, name) {
|
|
4343
|
+
const pool = readPools(paths)[name];
|
|
4344
|
+
if (pool === void 0) {
|
|
4345
|
+
throw new PoolNotFoundError(name);
|
|
4346
|
+
}
|
|
4347
|
+
return pool;
|
|
4348
|
+
}
|
|
4349
|
+
function writePools(paths, pools) {
|
|
4350
|
+
applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
|
|
4351
|
+
}
|
|
4352
|
+
function addPool(paths, name, identities) {
|
|
4353
|
+
if (!PoolNameSchema.safeParse(name).success) {
|
|
4354
|
+
throw new InvalidPoolNameError(name);
|
|
4355
|
+
}
|
|
4356
|
+
const pools = readPools(paths);
|
|
4357
|
+
if (pools[name] !== void 0) {
|
|
4358
|
+
throw new PoolAlreadyExistsError(name);
|
|
4359
|
+
}
|
|
4360
|
+
const pool = { identities: [...identities] };
|
|
4361
|
+
writePools(paths, { ...pools, [name]: pool });
|
|
4362
|
+
return pool;
|
|
4363
|
+
}
|
|
4364
|
+
function setPool(paths, name, identities) {
|
|
4365
|
+
requirePool(paths, name);
|
|
4366
|
+
const pool = { identities: [...identities] };
|
|
4367
|
+
writePools(paths, { ...readPools(paths), [name]: pool });
|
|
4368
|
+
return pool;
|
|
4369
|
+
}
|
|
4370
|
+
function removePool(paths, name) {
|
|
4371
|
+
requirePool(paths, name);
|
|
4372
|
+
const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
|
|
4373
|
+
writePools(paths, remaining);
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
// src/identityStore.ts
|
|
4377
|
+
var IdentityNotFoundError = class extends CliError {
|
|
4378
|
+
constructor(name) {
|
|
4379
|
+
super(`No identity named "${name}". Run \`agent-shim identity add ${name}\` first.`);
|
|
4380
|
+
this.name = name;
|
|
4381
|
+
this.name = "IdentityNotFoundError";
|
|
4382
|
+
}
|
|
4383
|
+
name;
|
|
4384
|
+
};
|
|
4385
|
+
var IdentityAlreadyExistsError = class extends CliError {
|
|
4386
|
+
constructor(identityName) {
|
|
4387
|
+
super(`An identity named "${identityName}" already exists.`);
|
|
4388
|
+
this.identityName = identityName;
|
|
4389
|
+
this.name = "IdentityAlreadyExistsError";
|
|
4390
|
+
}
|
|
4391
|
+
identityName;
|
|
4392
|
+
};
|
|
4393
|
+
var InvalidIdentityNameError = class extends CliError {
|
|
4394
|
+
constructor(attemptedName) {
|
|
4395
|
+
super(
|
|
4396
|
+
`"${attemptedName}" is not a valid identity name \u2014 identity names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
|
|
4397
|
+
);
|
|
4398
|
+
this.attemptedName = attemptedName;
|
|
4399
|
+
this.name = "InvalidIdentityNameError";
|
|
4400
|
+
}
|
|
4401
|
+
attemptedName;
|
|
4402
|
+
};
|
|
4403
|
+
function identityJsonPath(paths, name) {
|
|
4404
|
+
return path14.join(paths.identitiesDir, name, "identity.json");
|
|
4405
|
+
}
|
|
4406
|
+
function identityExists(paths, name) {
|
|
4407
|
+
return fs5.existsSync(identityJsonPath(paths, name));
|
|
4408
|
+
}
|
|
4409
|
+
function readIdentity(paths, name) {
|
|
4410
|
+
return readJson(identityJsonPath(paths, name), IdentitySchema);
|
|
4411
|
+
}
|
|
4412
|
+
function addIdentity(paths, name) {
|
|
4413
|
+
if (identityExists(paths, name)) {
|
|
4414
|
+
throw new IdentityAlreadyExistsError(name);
|
|
4415
|
+
}
|
|
4416
|
+
const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
|
|
4417
|
+
if (!parsed.success) {
|
|
4418
|
+
throw new InvalidIdentityNameError(name);
|
|
4419
|
+
}
|
|
4420
|
+
writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
|
|
4421
|
+
return parsed.data;
|
|
4422
|
+
}
|
|
4423
|
+
function useIdentity(paths, name) {
|
|
4424
|
+
const pool = poolNameOf(name);
|
|
4425
|
+
if (pool !== void 0) {
|
|
4426
|
+
requirePool(paths, pool);
|
|
4427
|
+
} else if (!identityExists(paths, name)) {
|
|
4428
|
+
throw new IdentityNotFoundError(name);
|
|
4429
|
+
}
|
|
4430
|
+
writeTextAtomic(paths.activeIdentityFile, `${name}
|
|
4431
|
+
`);
|
|
4432
|
+
}
|
|
4433
|
+
function readActiveIdentity(paths) {
|
|
4434
|
+
if (!fs5.existsSync(paths.activeIdentityFile)) {
|
|
4435
|
+
return void 0;
|
|
4436
|
+
}
|
|
4437
|
+
const raw = fs5.readFileSync(paths.activeIdentityFile, "utf8").trim();
|
|
4438
|
+
return raw === "" ? void 0 : raw;
|
|
4439
|
+
}
|
|
4440
|
+
function isIdentityDirectoryName(name) {
|
|
4441
|
+
return !name.startsWith(".");
|
|
4442
|
+
}
|
|
4443
|
+
function readIdentityForListing(paths, name) {
|
|
4444
|
+
try {
|
|
4445
|
+
return readIdentity(paths, name);
|
|
4446
|
+
} catch (error) {
|
|
4447
|
+
if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
|
|
4448
|
+
return { problem: error.message.replace(/\s*\n\s*/g, " ") };
|
|
4449
|
+
}
|
|
4450
|
+
throw error;
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
function listIdentities(paths) {
|
|
4454
|
+
if (!fs5.existsSync(paths.identitiesDir)) {
|
|
4455
|
+
return [];
|
|
4456
|
+
}
|
|
4457
|
+
const active = readActiveIdentity(paths);
|
|
4458
|
+
const names = fs5.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
|
|
4459
|
+
const result = [];
|
|
4460
|
+
for (const name of names) {
|
|
4461
|
+
const read = readIdentityForListing(paths, name);
|
|
4462
|
+
if (read === void 0) {
|
|
4463
|
+
continue;
|
|
4464
|
+
}
|
|
4465
|
+
const isActive = name === active;
|
|
4466
|
+
result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
|
|
4467
|
+
}
|
|
4468
|
+
return result;
|
|
4469
|
+
}
|
|
4470
|
+
function setDefaultConfigProfile(paths, identityName, profileName) {
|
|
4471
|
+
if (!identityExists(paths, identityName)) {
|
|
4472
|
+
throw new IdentityNotFoundError(identityName);
|
|
4473
|
+
}
|
|
4474
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4475
|
+
defaultConfigProfile: profileName
|
|
4476
|
+
});
|
|
4477
|
+
}
|
|
4478
|
+
function setAllowAmbientCredential(paths, identityName, allow) {
|
|
4479
|
+
if (!identityExists(paths, identityName)) {
|
|
4480
|
+
throw new IdentityNotFoundError(identityName);
|
|
4481
|
+
}
|
|
4482
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4483
|
+
allowAmbientCredential: allow
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
function setIdentityCredential(paths, identityName, change) {
|
|
4487
|
+
const existing = readIdentity(paths, identityName);
|
|
4488
|
+
if (existing === void 0) {
|
|
4489
|
+
throw new IdentityNotFoundError(identityName);
|
|
4490
|
+
}
|
|
4491
|
+
if (change === false) {
|
|
4492
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { credential: void 0 });
|
|
4493
|
+
}
|
|
4494
|
+
const sources = change.sources ?? existing.credential?.sources;
|
|
4495
|
+
if (sources === void 0) {
|
|
4496
|
+
throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
|
|
4497
|
+
}
|
|
4498
|
+
const target = change.target ?? existing.credential?.target;
|
|
4499
|
+
const cache = change.cache === void 0 ? existing.credential?.cache : change.cache === false ? void 0 : change.cache;
|
|
4500
|
+
return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
|
|
4501
|
+
credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
|
|
4502
|
+
});
|
|
4503
|
+
}
|
|
4504
|
+
function belongsToIdentity(entry, name) {
|
|
4505
|
+
return entry === name || entry === path14.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
|
|
4506
|
+
}
|
|
4507
|
+
function removeIdentity(paths, name) {
|
|
4508
|
+
if (!identityExists(paths, name)) {
|
|
4509
|
+
throw new IdentityNotFoundError(name);
|
|
4510
|
+
}
|
|
4511
|
+
for (const entry of fs5.readdirSync(paths.identitiesDir)) {
|
|
4512
|
+
if (belongsToIdentity(entry, name)) {
|
|
4513
|
+
fs5.rmSync(path14.join(paths.identitiesDir, entry), { recursive: true, force: true });
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
if (readActiveIdentity(paths) === name) {
|
|
4517
|
+
fs5.rmSync(paths.activeIdentityFile);
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
// src/providersStore.ts
|
|
4522
|
+
import fs6 from "node:fs";
|
|
4523
|
+
import path15 from "node:path";
|
|
4524
|
+
|
|
4525
|
+
// src/credentialCache.ts
|
|
4526
|
+
import { z as z7 } from "zod";
|
|
4527
|
+
var CachedCredentialSchema = z7.strictObject({
|
|
4528
|
+
token: z7.string().min(1),
|
|
4529
|
+
fetchedAt: z7.number().int().nonnegative(),
|
|
4530
|
+
source: CredentialSourceSchema
|
|
4531
|
+
});
|
|
4532
|
+
|
|
4533
|
+
// src/credential.ts
|
|
4534
|
+
function summariseSource(source) {
|
|
4535
|
+
if ("env" in source) {
|
|
4536
|
+
return { kind: "env", variable: source.env };
|
|
4537
|
+
}
|
|
4538
|
+
if ("file" in source) {
|
|
4539
|
+
return { kind: "file", path: source.file };
|
|
4540
|
+
}
|
|
4541
|
+
if ("command" in source) {
|
|
4542
|
+
return { kind: "command", program: source.command[0] };
|
|
4543
|
+
}
|
|
4544
|
+
if ("op" in source) {
|
|
4545
|
+
return { kind: "op", reference: source.op };
|
|
4546
|
+
}
|
|
4547
|
+
if ("keychain" in source) {
|
|
4548
|
+
return {
|
|
4549
|
+
kind: "keychain",
|
|
4550
|
+
service: source.keychain.service,
|
|
4551
|
+
...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
|
|
4552
|
+
};
|
|
4553
|
+
}
|
|
4554
|
+
return { kind: "literal" };
|
|
4555
|
+
}
|
|
4556
|
+
function summariseCredential(credential) {
|
|
4557
|
+
return {
|
|
4558
|
+
target: credential.target ?? "bearer",
|
|
4559
|
+
sources: credential.sources.map(summariseSource),
|
|
4560
|
+
...credential.cache === void 0 ? {} : { cache: credential.cache }
|
|
4561
|
+
};
|
|
4562
|
+
}
|
|
4563
|
+
function formatSourceSummary(summary) {
|
|
4564
|
+
switch (summary.kind) {
|
|
4565
|
+
case "env":
|
|
4566
|
+
return `env ${summary.variable}`;
|
|
4567
|
+
case "file":
|
|
4568
|
+
return `file ${summary.path}`;
|
|
4569
|
+
case "command":
|
|
4570
|
+
return `command ${summary.program}`;
|
|
4571
|
+
case "op":
|
|
4572
|
+
return `op ${summary.reference}`;
|
|
4573
|
+
case "keychain":
|
|
4574
|
+
return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
|
|
4575
|
+
case "literal":
|
|
4576
|
+
return "literal (non-secret placeholder)";
|
|
4577
|
+
default:
|
|
4578
|
+
return summary;
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
function formatCredentialSummary(summary) {
|
|
4582
|
+
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`}`;
|
|
4583
|
+
return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
|
|
4584
|
+
}
|
|
4585
|
+
function describeCredential(credential) {
|
|
4586
|
+
return formatCredentialSummary(summariseCredential(credential));
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
// src/providersStore.ts
|
|
4590
|
+
var ProviderNotFoundError = class extends CliError {
|
|
4591
|
+
constructor(name) {
|
|
4592
|
+
super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
|
|
4593
|
+
this.name = name;
|
|
4594
|
+
this.name = "ProviderNotFoundError";
|
|
4595
|
+
}
|
|
4596
|
+
name;
|
|
4597
|
+
};
|
|
4598
|
+
var ProviderAlreadyExistsError = class extends CliError {
|
|
4599
|
+
constructor(providerName) {
|
|
4600
|
+
super(`A provider named "${providerName}" already exists.`);
|
|
4601
|
+
this.providerName = providerName;
|
|
4602
|
+
this.name = "ProviderAlreadyExistsError";
|
|
4603
|
+
}
|
|
4604
|
+
providerName;
|
|
4605
|
+
};
|
|
4606
|
+
var InvalidProviderNameError = class extends CliError {
|
|
4607
|
+
constructor(attemptedName) {
|
|
4608
|
+
super(
|
|
4609
|
+
`"${attemptedName}" is not a valid provider name. Provider names must start with a letter or number and may then contain letters, numbers, dots, hyphens, underscores, and at signs.`
|
|
4610
|
+
);
|
|
4611
|
+
this.attemptedName = attemptedName;
|
|
4612
|
+
this.name = "InvalidProviderNameError";
|
|
4613
|
+
}
|
|
4614
|
+
attemptedName;
|
|
4615
|
+
};
|
|
4616
|
+
var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
|
|
4617
|
+
function providerJsonPath(paths, name) {
|
|
4618
|
+
return path15.join(paths.providersDir, `${name}.json`);
|
|
4619
|
+
}
|
|
4620
|
+
function providerExists(paths, name) {
|
|
4621
|
+
return fs6.existsSync(providerJsonPath(paths, name));
|
|
4622
|
+
}
|
|
4623
|
+
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
4624
|
+
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
4625
|
+
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
4626
|
+
function isRecord2(value) {
|
|
4627
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4628
|
+
}
|
|
4629
|
+
function legacyProviderConversion(raw) {
|
|
4630
|
+
if (!isRecord2(raw) || "credential" in raw) {
|
|
4631
|
+
return void 0;
|
|
4632
|
+
}
|
|
4633
|
+
const env = isRecord2(raw.env) ? raw.env : void 0;
|
|
4634
|
+
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
4635
|
+
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
4636
|
+
if (fields.length === 0) {
|
|
4637
|
+
return void 0;
|
|
4638
|
+
}
|
|
4639
|
+
const sources = [];
|
|
4640
|
+
if (typeof raw.tokenEnv === "string") {
|
|
4641
|
+
sources.push({ env: raw.tokenEnv });
|
|
4642
|
+
}
|
|
4643
|
+
if (Array.isArray(raw.tokenCommand)) {
|
|
4644
|
+
sources.push({ command: raw.tokenCommand });
|
|
4645
|
+
}
|
|
4646
|
+
if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
|
|
4647
|
+
sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
|
|
4648
|
+
}
|
|
4649
|
+
const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
|
|
4650
|
+
const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
|
|
4651
|
+
const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
|
|
4652
|
+
return {
|
|
4653
|
+
fields,
|
|
4654
|
+
replacement: {
|
|
4655
|
+
...rest,
|
|
4656
|
+
credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
|
|
4657
|
+
...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
}
|
|
4661
|
+
var LegacyProviderFileError = class extends CliError {
|
|
4662
|
+
constructor(filePath, conversion) {
|
|
4663
|
+
super(
|
|
4664
|
+
`${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
|
|
4665
|
+
` + JSON.stringify(conversion.replacement, null, 2)
|
|
4666
|
+
);
|
|
4667
|
+
this.filePath = filePath;
|
|
4668
|
+
this.conversion = conversion;
|
|
4669
|
+
this.name = "LegacyProviderFileError";
|
|
4670
|
+
}
|
|
4671
|
+
filePath;
|
|
4672
|
+
conversion;
|
|
4673
|
+
};
|
|
4674
|
+
function parseProviderFile(filePath, raw) {
|
|
4675
|
+
const conversion = legacyProviderConversion(raw);
|
|
4676
|
+
if (conversion !== void 0) {
|
|
4677
|
+
throw new LegacyProviderFileError(filePath, conversion);
|
|
4678
|
+
}
|
|
4679
|
+
const parsed = ProviderSchema.safeParse(raw);
|
|
4680
|
+
if (!parsed.success) {
|
|
4681
|
+
throw new ConfigValidationError(filePath, parsed.error.issues);
|
|
4682
|
+
}
|
|
4683
|
+
return parsed.data;
|
|
4684
|
+
}
|
|
4685
|
+
function readProvider(paths, name) {
|
|
4686
|
+
const filePath = providerJsonPath(paths, name);
|
|
4687
|
+
if (!fs6.existsSync(filePath)) {
|
|
4688
|
+
return void 0;
|
|
4689
|
+
}
|
|
4690
|
+
const raw = JSON.parse(fs6.readFileSync(filePath, "utf8"));
|
|
4691
|
+
return parseProviderFile(filePath, raw);
|
|
4692
|
+
}
|
|
4693
|
+
function listProviders(paths) {
|
|
4694
|
+
const names = listProviderNames(
|
|
4695
|
+
(dir) => fs6.readdirSync(dir),
|
|
4696
|
+
paths.providersDir
|
|
4697
|
+
);
|
|
4698
|
+
const result = [];
|
|
4699
|
+
for (const name of names) {
|
|
4700
|
+
let provider;
|
|
4701
|
+
try {
|
|
4702
|
+
provider = readProvider(paths, name);
|
|
4703
|
+
} catch (error) {
|
|
4704
|
+
if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
|
|
4705
|
+
throw error;
|
|
4706
|
+
}
|
|
4707
|
+
console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
|
|
4708
|
+
continue;
|
|
4709
|
+
}
|
|
4710
|
+
if (provider !== void 0) {
|
|
4711
|
+
result.push({ name, provider });
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
return result;
|
|
4715
|
+
}
|
|
4716
|
+
function listProviderNames(readdir, providersDir) {
|
|
4717
|
+
let entries;
|
|
4718
|
+
try {
|
|
4719
|
+
entries = readdir(providersDir);
|
|
4720
|
+
} catch {
|
|
4721
|
+
return [];
|
|
4722
|
+
}
|
|
4723
|
+
return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
|
|
4724
|
+
}
|
|
4725
|
+
function describeProviderEndpoint(provider) {
|
|
4726
|
+
return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
|
|
4727
|
+
}
|
|
4728
|
+
var ProviderKindMismatchError = class extends UsageError {
|
|
4729
|
+
constructor(message) {
|
|
4730
|
+
super(message);
|
|
4731
|
+
this.name = "ProviderKindMismatchError";
|
|
4732
|
+
}
|
|
4733
|
+
};
|
|
4734
|
+
function isEmptyCodexConfig(config) {
|
|
4735
|
+
return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
|
|
4736
|
+
}
|
|
4737
|
+
function addProvider(paths, name, input) {
|
|
4738
|
+
if (providerExists(paths, name)) {
|
|
4739
|
+
throw new ProviderAlreadyExistsError(name);
|
|
4740
|
+
}
|
|
4741
|
+
if (!PROVIDER_NAME_RE.test(name)) {
|
|
4742
|
+
throw new InvalidProviderNameError(name);
|
|
4743
|
+
}
|
|
4744
|
+
const kind = input.kind ?? "http";
|
|
4745
|
+
if (kind === "codex" && input.baseUrl !== void 0) {
|
|
4746
|
+
throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
|
|
4747
|
+
}
|
|
4748
|
+
if (kind === "http" && input.baseUrl === void 0) {
|
|
4749
|
+
throw new ProviderKindMismatchError("An http provider needs --base-url.");
|
|
4750
|
+
}
|
|
4751
|
+
if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
|
|
4752
|
+
throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
|
|
4753
|
+
}
|
|
4754
|
+
const shared = {
|
|
4755
|
+
displayName: input.displayName,
|
|
4756
|
+
credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
|
|
4757
|
+
...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
|
|
4758
|
+
};
|
|
4759
|
+
const parsed = ProviderSchema.safeParse(
|
|
4760
|
+
kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
|
|
4761
|
+
);
|
|
4762
|
+
if (!parsed.success) {
|
|
4763
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
4764
|
+
}
|
|
4765
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
4766
|
+
return parsed.data;
|
|
4767
|
+
}
|
|
4768
|
+
function updateProvider(paths, name, input) {
|
|
4769
|
+
const existing = readProvider(paths, name);
|
|
4770
|
+
if (existing === void 0) {
|
|
4771
|
+
throw new ProviderNotFoundError(name);
|
|
4772
|
+
}
|
|
4773
|
+
const unset = new Set(input.unsetEnv);
|
|
4774
|
+
const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
|
|
4775
|
+
const target = input.target ?? existing.credential.target;
|
|
4776
|
+
const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
|
|
4777
|
+
if (isCodexProvider(existing) && input.baseUrl !== void 0) {
|
|
4778
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
|
|
4779
|
+
}
|
|
4780
|
+
if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
|
|
4781
|
+
throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
|
|
4782
|
+
}
|
|
4783
|
+
const shared = {
|
|
4784
|
+
...input.displayName === void 0 ? {} : { displayName: input.displayName },
|
|
4785
|
+
credential: {
|
|
4786
|
+
sources: input.sources ?? existing.credential.sources,
|
|
4787
|
+
...target === void 0 ? {} : { target },
|
|
4788
|
+
...cache === void 0 ? {} : { cache }
|
|
4789
|
+
},
|
|
4790
|
+
env: Object.keys(env).length === 0 ? void 0 : env
|
|
4791
|
+
};
|
|
4792
|
+
let candidate;
|
|
4793
|
+
if (isCodexProvider(existing)) {
|
|
4794
|
+
const codex = {
|
|
4795
|
+
...existing.codex,
|
|
4796
|
+
...input.codex,
|
|
4797
|
+
...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
|
|
4798
|
+
};
|
|
4799
|
+
candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
|
|
4800
|
+
} else {
|
|
4801
|
+
candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
|
|
4802
|
+
}
|
|
4803
|
+
const parsed = ProviderSchema.safeParse(candidate);
|
|
4804
|
+
if (!parsed.success) {
|
|
4805
|
+
throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
|
|
4806
|
+
}
|
|
4807
|
+
writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
|
|
4808
|
+
return parsed.data;
|
|
4809
|
+
}
|
|
4810
|
+
function removeProvider(paths, name) {
|
|
4811
|
+
if (!providerExists(paths, name)) {
|
|
4812
|
+
throw new ProviderNotFoundError(name);
|
|
4813
|
+
}
|
|
4814
|
+
fs6.rmSync(providerJsonPath(paths, name));
|
|
4815
|
+
}
|
|
4816
|
+
function cascadeProviderName(cascade) {
|
|
4817
|
+
let name;
|
|
4818
|
+
for (const layer of assembleCascade(cascade).layers) {
|
|
4819
|
+
if (layer.launch?.provider !== void 0) {
|
|
4820
|
+
name = layer.launch.provider;
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
return name;
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
// src/directoryRulesStore.ts
|
|
4827
|
+
var DirectoryRuleNotFoundError = class extends CliError {
|
|
4828
|
+
constructor(rulePath) {
|
|
4829
|
+
super(`No directory rule found for path "${rulePath}".`);
|
|
4830
|
+
this.rulePath = rulePath;
|
|
4831
|
+
this.name = "DirectoryRuleNotFoundError";
|
|
4832
|
+
}
|
|
4833
|
+
rulePath;
|
|
4834
|
+
};
|
|
4835
|
+
var DirectoryRuleMissingTargetError = class extends CliError {
|
|
4836
|
+
constructor() {
|
|
4837
|
+
super("A directory rule must set at least one of --config-profile or --identity.");
|
|
4838
|
+
this.name = "DirectoryRuleMissingTargetError";
|
|
4839
|
+
}
|
|
4840
|
+
};
|
|
4841
|
+
var DirectoryRuleAlreadyExistsError = class extends CliError {
|
|
4842
|
+
constructor(rulePath) {
|
|
4843
|
+
super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
|
|
4844
|
+
this.rulePath = rulePath;
|
|
4845
|
+
this.name = "DirectoryRuleAlreadyExistsError";
|
|
4846
|
+
}
|
|
4847
|
+
rulePath;
|
|
4848
|
+
};
|
|
4849
|
+
function readDirectoryRules(paths) {
|
|
4850
|
+
return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
|
|
4851
|
+
}
|
|
4852
|
+
function writeDirectoryRules(paths, rules) {
|
|
4853
|
+
const parsed = DirectoryRulesSchema.safeParse(rules);
|
|
4854
|
+
if (!parsed.success) {
|
|
4855
|
+
throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
|
|
4856
|
+
}
|
|
4857
|
+
writeJsonAtomic(paths.directoryRulesFile, parsed.data);
|
|
4858
|
+
}
|
|
4859
|
+
function listDirectoryRules(paths) {
|
|
4860
|
+
return readDirectoryRules(paths).rules;
|
|
4861
|
+
}
|
|
4862
|
+
function addDirectoryRule(paths, rulePath, options) {
|
|
4863
|
+
if (options.configProfile === void 0 && options.identity === void 0) {
|
|
4864
|
+
throw new DirectoryRuleMissingTargetError();
|
|
4865
|
+
}
|
|
4866
|
+
const current = readDirectoryRules(paths);
|
|
4867
|
+
if (current.rules.some((rule) => rule.path === rulePath)) {
|
|
4868
|
+
throw new DirectoryRuleAlreadyExistsError(rulePath);
|
|
4869
|
+
}
|
|
4870
|
+
const created = buildNewRule(rulePath, options);
|
|
4871
|
+
writeDirectoryRules(paths, { ...current, rules: [...current.rules, created] });
|
|
4872
|
+
return created;
|
|
4873
|
+
}
|
|
4874
|
+
function ruleHasEffect(rule) {
|
|
4875
|
+
return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
|
|
4876
|
+
}
|
|
4877
|
+
function updateDirectoryRule(paths, rulePath, options) {
|
|
4878
|
+
const current = readDirectoryRules(paths);
|
|
4879
|
+
const index = current.rules.findIndex((rule) => rule.path === rulePath);
|
|
4880
|
+
const existing = index === -1 ? void 0 : current.rules[index];
|
|
4881
|
+
if (existing === void 0) {
|
|
4882
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
4883
|
+
}
|
|
4884
|
+
const { configProfile, identity, ...rest } = existing;
|
|
4885
|
+
const nextConfigProfile = options.configProfile === void 0 ? configProfile : options.configProfile === false ? void 0 : options.configProfile;
|
|
4886
|
+
const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
|
|
4887
|
+
const updated = {
|
|
4888
|
+
...rest,
|
|
4889
|
+
...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
|
|
4890
|
+
...nextIdentity === void 0 ? {} : { identity: nextIdentity }
|
|
4891
|
+
};
|
|
4892
|
+
if (!ruleHasEffect(updated)) {
|
|
4893
|
+
throw new DirectoryRuleMissingTargetError();
|
|
4894
|
+
}
|
|
4895
|
+
const nextRules = [...current.rules];
|
|
4896
|
+
nextRules[index] = updated;
|
|
4897
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
4898
|
+
return updated;
|
|
4899
|
+
}
|
|
4900
|
+
function buildNewRule(rulePath, options) {
|
|
4901
|
+
return {
|
|
4902
|
+
path: rulePath,
|
|
4903
|
+
...options.configProfile !== void 0 ? { configProfile: options.configProfile } : {},
|
|
4904
|
+
...options.identity !== void 0 ? { identity: options.identity } : {}
|
|
4905
|
+
};
|
|
4906
|
+
}
|
|
4907
|
+
function removeDirectoryRule(paths, rulePath) {
|
|
4908
|
+
const current = readDirectoryRules(paths);
|
|
4909
|
+
const nextRules = current.rules.filter((rule) => rule.path !== rulePath);
|
|
4910
|
+
if (nextRules.length === current.rules.length) {
|
|
4911
|
+
throw new DirectoryRuleNotFoundError(rulePath);
|
|
4912
|
+
}
|
|
4913
|
+
writeDirectoryRules(paths, { ...current, rules: nextRules });
|
|
4914
|
+
}
|
|
4915
|
+
|
|
4916
|
+
// src/checkReport.ts
|
|
4917
|
+
import os3 from "node:os";
|
|
4918
|
+
|
|
4919
|
+
// src/launcher/cascade.ts
|
|
4920
|
+
import path17 from "node:path";
|
|
4921
|
+
|
|
4922
|
+
// src/pathNorm.ts
|
|
4923
|
+
import path16 from "node:path";
|
|
4924
|
+
function expandTilde(value, home) {
|
|
4925
|
+
if (value === "~") {
|
|
4926
|
+
return home;
|
|
4927
|
+
}
|
|
4928
|
+
if (value.startsWith("~/")) {
|
|
4929
|
+
return path16.join(home, value.slice(2));
|
|
4930
|
+
}
|
|
4931
|
+
return value;
|
|
4932
|
+
}
|
|
4933
|
+
function normaliseRulePath(rulePath, home) {
|
|
4934
|
+
return path16.resolve(expandTilde(rulePath, home));
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4937
|
+
// src/launcher/cascade.ts
|
|
4938
|
+
var PORTABLE_CONFIG_FILENAME = ".agent-shim.json";
|
|
4939
|
+
var PORTABLE_LOCAL_CONFIG_FILENAME = ".agent-shim.local.json";
|
|
4940
|
+
function loadCascadeInput(params) {
|
|
4941
|
+
const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
|
|
4942
|
+
const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
|
|
4943
|
+
const loadProfile = (name) => {
|
|
4944
|
+
const filepath = path17.join(params.paths.configProfilesDir, `${name}.json`);
|
|
4945
|
+
const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
|
|
4946
|
+
if (loaded === void 0) {
|
|
4947
|
+
return void 0;
|
|
4948
|
+
}
|
|
4949
|
+
return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
|
|
4950
|
+
};
|
|
4951
|
+
const limit = globalConfig?.config.walkUpLimit;
|
|
4952
|
+
const dirs = walkDirectoryAncestors(params.cwd, {
|
|
4953
|
+
home: params.home,
|
|
4954
|
+
...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
|
|
4955
|
+
...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
|
|
4956
|
+
});
|
|
4957
|
+
const levels = [];
|
|
4958
|
+
for (const dir of dirs) {
|
|
4959
|
+
const portable = loadConfigFile(path17.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path17.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
4960
|
+
const portableLocal = loadConfigFile(path17.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(path17.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
|
|
4961
|
+
const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
|
|
4962
|
+
const entryOrder = directoryRules?.ruleEntryOrders[index];
|
|
4963
|
+
return {
|
|
4964
|
+
rule,
|
|
4965
|
+
...entryOrder === void 0 ? {} : { entryOrder },
|
|
4966
|
+
filepath: params.paths.directoryRulesFile
|
|
4967
|
+
};
|
|
4968
|
+
});
|
|
4969
|
+
if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
|
|
4970
|
+
continue;
|
|
4971
|
+
}
|
|
4972
|
+
levels.push({
|
|
4973
|
+
dir,
|
|
4974
|
+
...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
|
|
4975
|
+
...rules.length === 0 ? {} : { rules },
|
|
4976
|
+
...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
|
|
4977
|
+
});
|
|
4978
|
+
}
|
|
4979
|
+
const input = {
|
|
4980
|
+
home: params.home,
|
|
4981
|
+
...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
|
|
4982
|
+
...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
|
|
4983
|
+
loadProfile,
|
|
4984
|
+
levels,
|
|
4985
|
+
...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
|
|
4986
|
+
};
|
|
4987
|
+
return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
|
|
4988
|
+
}
|
|
4989
|
+
function readDirectorySelections(loaded) {
|
|
4990
|
+
let identity;
|
|
4991
|
+
let configProfile;
|
|
4992
|
+
for (const level of loaded.input.levels ?? []) {
|
|
4993
|
+
for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
|
|
4994
|
+
if (source === void 0) {
|
|
4995
|
+
continue;
|
|
4996
|
+
}
|
|
4997
|
+
if (source.identity !== void 0) {
|
|
4998
|
+
identity = source.identity;
|
|
4999
|
+
}
|
|
5000
|
+
if (source.configProfile !== void 0) {
|
|
5001
|
+
configProfile = source.configProfile;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
|
|
5006
|
+
}
|
|
5007
|
+
|
|
5008
|
+
// src/usage/preflight.ts
|
|
5009
|
+
var MS_PER_SECOND2 = 1e3;
|
|
5010
|
+
var SECONDS_PER_MINUTE = 60;
|
|
5011
|
+
var MINUTES_PER_HOUR = 60;
|
|
5012
|
+
var HOURS_PER_DAY = 24;
|
|
5013
|
+
function formatAge(ms) {
|
|
5014
|
+
const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
|
|
5015
|
+
if (minutes < MINUTES_PER_HOUR) {
|
|
5016
|
+
return `${String(Math.max(minutes, 0))}m`;
|
|
5017
|
+
}
|
|
5018
|
+
const hours = Math.floor(minutes / MINUTES_PER_HOUR);
|
|
5019
|
+
return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
|
|
5020
|
+
}
|
|
5021
|
+
function effectiveWindow(window, nowMs) {
|
|
5022
|
+
const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
|
|
5023
|
+
if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
|
|
5024
|
+
return { reset: true, utilization: 0 };
|
|
5025
|
+
}
|
|
5026
|
+
return {
|
|
5027
|
+
reset: false,
|
|
5028
|
+
...window.utilization === void 0 ? {} : { utilization: window.utilization },
|
|
5029
|
+
...resetsAtMs === void 0 ? {} : { resetsAtMs },
|
|
5030
|
+
...window.status === void 0 ? {} : { status: window.status }
|
|
5031
|
+
};
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
// src/usage/poolPick.ts
|
|
5035
|
+
import { z as z9 } from "zod";
|
|
5036
|
+
|
|
5037
|
+
// src/usage/plan.ts
|
|
5038
|
+
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
5039
|
+
var ZERO_TIER = /(?:^|_)zero$/;
|
|
5040
|
+
var BASE_TIER = /(?:^|_)(?:pro|free)$/;
|
|
5041
|
+
function planOf(account) {
|
|
5042
|
+
const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
|
|
5043
|
+
const tierField = tier === void 0 ? {} : { tier };
|
|
5044
|
+
if (tier === void 0) {
|
|
5045
|
+
return { kind: "subscription", capacity: 1, recognised: false };
|
|
5046
|
+
}
|
|
5047
|
+
if (ZERO_TIER.test(tier)) {
|
|
5048
|
+
return { kind: "pay-per-use", ...tierField };
|
|
5049
|
+
}
|
|
5050
|
+
const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
|
|
5051
|
+
if (multiplier !== void 0) {
|
|
5052
|
+
return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
|
|
5053
|
+
}
|
|
5054
|
+
return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
// src/usage/rateLimit.ts
|
|
5058
|
+
var MS_PER_SECOND3 = 1e3;
|
|
5059
|
+
function epochSecondsToIso(value) {
|
|
5060
|
+
if (value === void 0) {
|
|
5061
|
+
return void 0;
|
|
5062
|
+
}
|
|
5063
|
+
const seconds = Number(value);
|
|
5064
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
|
|
5065
|
+
}
|
|
5066
|
+
function nonNegativeNumber(value) {
|
|
5067
|
+
if (value === void 0 || value.trim() === "") {
|
|
5068
|
+
return void 0;
|
|
5069
|
+
}
|
|
5070
|
+
const parsed = Number(value);
|
|
5071
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
5072
|
+
}
|
|
5073
|
+
var UNIFIED_PREFIX = "anthropic-ratelimit-unified-";
|
|
5074
|
+
function unifiedWindow(headers, window) {
|
|
5075
|
+
const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
|
|
5076
|
+
const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
|
|
5077
|
+
const status = headers[`${UNIFIED_PREFIX}${window}-status`];
|
|
5078
|
+
if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
|
|
5079
|
+
return void 0;
|
|
5080
|
+
}
|
|
5081
|
+
return { ...utilization === void 0 ? {} : { utilization }, ...resetsAt === void 0 ? {} : { resetsAt }, ...status === void 0 ? {} : { status } };
|
|
5082
|
+
}
|
|
5083
|
+
function parseUnifiedRateLimit(headers) {
|
|
5084
|
+
if (!Object.keys(headers).some((name) => name.startsWith(UNIFIED_PREFIX))) {
|
|
5085
|
+
return void 0;
|
|
5086
|
+
}
|
|
5087
|
+
const status = headers[`${UNIFIED_PREFIX}status`];
|
|
5088
|
+
const fiveHour = unifiedWindow(headers, "5h");
|
|
5089
|
+
const sevenDay = unifiedWindow(headers, "7d");
|
|
5090
|
+
const representativeClaim = headers[`${UNIFIED_PREFIX}representative-claim`];
|
|
5091
|
+
const resetAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}reset`]);
|
|
5092
|
+
const overageStatus = headers[`${UNIFIED_PREFIX}overage-status`];
|
|
5093
|
+
return {
|
|
5094
|
+
...status === void 0 ? {} : { status },
|
|
5095
|
+
...fiveHour === void 0 ? {} : { fiveHour },
|
|
5096
|
+
...sevenDay === void 0 ? {} : { sevenDay },
|
|
5097
|
+
...representativeClaim === void 0 ? {} : { representativeClaim },
|
|
5098
|
+
...resetAt === void 0 ? {} : { resetAt },
|
|
5099
|
+
...overageStatus === void 0 ? {} : { overageStatus }
|
|
5100
|
+
};
|
|
5101
|
+
}
|
|
5102
|
+
|
|
5103
|
+
// src/usage/scan.ts
|
|
5104
|
+
import { z as z8 } from "zod";
|
|
5105
|
+
var AnthropicUsageSchema = z8.looseObject({
|
|
5106
|
+
input_tokens: z8.number().int().nonnegative().nullish(),
|
|
5107
|
+
output_tokens: z8.number().int().nonnegative().nullish(),
|
|
5108
|
+
cache_creation_input_tokens: z8.number().int().nonnegative().nullish(),
|
|
5109
|
+
cache_read_input_tokens: z8.number().int().nonnegative().nullish()
|
|
5110
|
+
});
|
|
5111
|
+
var ErrorObjectSchema = z8.looseObject({
|
|
5112
|
+
type: z8.string().optional(),
|
|
5113
|
+
code: z8.union([z8.string(), z8.number()]).optional(),
|
|
5114
|
+
message: z8.string().optional()
|
|
5115
|
+
});
|
|
5116
|
+
|
|
5117
|
+
// src/usage/middleware.ts
|
|
5118
|
+
var ANTHROPIC_PROVIDER = "anthropic";
|
|
5119
|
+
|
|
5120
|
+
// src/usage/pick.ts
|
|
5121
|
+
var MS_PER_SECOND4 = 1e3;
|
|
5122
|
+
var MS_PER_HOUR = 36e5;
|
|
5123
|
+
var HOURS_PER_DAY2 = 24;
|
|
5124
|
+
var FIVE_HOURS = 5;
|
|
5125
|
+
var PROMPT_CACHE_TTL_HOURS = 1;
|
|
5126
|
+
var PERCENT = 100;
|
|
5127
|
+
var SEVEN_DAYS = 7;
|
|
5128
|
+
var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
|
|
5129
|
+
var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
|
|
5130
|
+
var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
|
|
5131
|
+
var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
|
|
5132
|
+
function unifiedOf(state) {
|
|
5133
|
+
return state?.rateLimit?.unified;
|
|
5134
|
+
}
|
|
5135
|
+
function limitBlockedUntil(state, nowMs) {
|
|
5136
|
+
const limit = state?.lastLimit;
|
|
5137
|
+
if (state === void 0 || limit === void 0) {
|
|
5138
|
+
return void 0;
|
|
5139
|
+
}
|
|
5140
|
+
if (Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
|
|
5141
|
+
return void 0;
|
|
5142
|
+
}
|
|
5143
|
+
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);
|
|
5144
|
+
return until !== void 0 && until > nowMs ? until : void 0;
|
|
5145
|
+
}
|
|
5146
|
+
function windowBlockedUntil(windows) {
|
|
5147
|
+
const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
|
|
5148
|
+
return resets.length === 0 ? void 0 : Math.max(...resets);
|
|
5149
|
+
}
|
|
5150
|
+
function fiveHourBurn(records) {
|
|
5151
|
+
const points = [];
|
|
5152
|
+
for (const record of records) {
|
|
5153
|
+
const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
|
|
5154
|
+
if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
|
|
5155
|
+
points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
const latest = points.at(-1)?.resetsAt;
|
|
5159
|
+
const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
|
|
5160
|
+
const first = current.at(0);
|
|
5161
|
+
const last = current.at(-1);
|
|
5162
|
+
if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
|
|
5163
|
+
return void 0;
|
|
5164
|
+
}
|
|
5165
|
+
return (last.utilization - first.utilization) / (last.atMs - first.atMs);
|
|
5166
|
+
}
|
|
5167
|
+
function percent(fraction) {
|
|
5168
|
+
return `${String(Math.round(fraction * PERCENT))}%`;
|
|
5169
|
+
}
|
|
5170
|
+
function describeWindow(label, window, effective, nowMs) {
|
|
5171
|
+
if (effective.reset) {
|
|
5172
|
+
return `${label} window has reset`;
|
|
5173
|
+
}
|
|
5174
|
+
const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
|
|
5175
|
+
const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
|
|
5176
|
+
return `${label} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
|
|
5177
|
+
}
|
|
5178
|
+
function capacityOf(plan) {
|
|
5179
|
+
return plan.kind === "subscription" ? plan.capacity : 1;
|
|
5180
|
+
}
|
|
5181
|
+
function assess(member, nowMs) {
|
|
5182
|
+
const plan = planOf(member.account);
|
|
5183
|
+
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`];
|
|
5184
|
+
const base = { identity: member.identity, plan };
|
|
5185
|
+
if (member.readError !== void 0) {
|
|
5186
|
+
return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
|
|
5187
|
+
}
|
|
5188
|
+
const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
|
|
5189
|
+
const unified = unifiedOf(state);
|
|
5190
|
+
if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
|
|
5191
|
+
return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
|
|
5192
|
+
}
|
|
5193
|
+
const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
|
|
5194
|
+
const fiveHourWindow = unified.fiveHour;
|
|
5195
|
+
const sevenDayWindow = unified.sevenDay;
|
|
5196
|
+
const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
|
|
5197
|
+
const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
|
|
5198
|
+
const windowReasons = [
|
|
5199
|
+
...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
|
|
5200
|
+
...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
|
|
5201
|
+
];
|
|
5202
|
+
const blockedUntil = Math.max(
|
|
5203
|
+
windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
|
|
5204
|
+
limitBlockedUntil(state, nowMs) ?? 0
|
|
5205
|
+
);
|
|
5206
|
+
if (blockedUntil > nowMs) {
|
|
5207
|
+
if (unified.overageStatus === "allowed") {
|
|
5208
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
|
|
5209
|
+
}
|
|
5210
|
+
return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
|
|
5211
|
+
}
|
|
5212
|
+
if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
|
|
5213
|
+
return { candidate: { ...base, class: "pay-per-use", reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"]] } };
|
|
5214
|
+
}
|
|
5215
|
+
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;
|
|
5216
|
+
if (basis?.window.utilization === void 0) {
|
|
5217
|
+
return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
|
|
5218
|
+
}
|
|
5219
|
+
const remaining = Math.max(0, 1 - basis.window.utilization);
|
|
5220
|
+
const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5221
|
+
const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
|
|
5222
|
+
const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
|
|
5223
|
+
const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
|
|
5224
|
+
const burn = fiveHourBurn(member.records);
|
|
5225
|
+
return {
|
|
5226
|
+
candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
|
|
5227
|
+
...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
|
|
5228
|
+
...burn === void 0 ? {} : { burn }
|
|
5229
|
+
};
|
|
5230
|
+
}
|
|
5231
|
+
function rankPool(input) {
|
|
5232
|
+
const { members, nowMs } = input;
|
|
5233
|
+
const assessments = members.map((member) => assess(member, nowMs));
|
|
5234
|
+
const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
|
|
5235
|
+
const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
|
|
5236
|
+
const candidates = assessments.map((assessment) => {
|
|
5237
|
+
const { candidate, fiveHour, burn } = assessment;
|
|
5238
|
+
const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
|
|
5239
|
+
if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
|
|
5240
|
+
return { ...candidate, feasible: true };
|
|
5241
|
+
}
|
|
5242
|
+
const runsDryInMs = fiveHour.remaining / pace;
|
|
5243
|
+
if (runsDryInMs >= fiveHour.untilResetMs) {
|
|
5244
|
+
return { ...candidate, feasible: true };
|
|
5245
|
+
}
|
|
5246
|
+
return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
|
|
5247
|
+
});
|
|
5248
|
+
const ordered = [...candidates].sort(
|
|
5249
|
+
(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)
|
|
5250
|
+
);
|
|
5251
|
+
const sticky = input.sticky;
|
|
5252
|
+
const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
|
|
5253
|
+
const stickyCandidate = ordered[stickyIndex];
|
|
5254
|
+
if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
|
|
5255
|
+
const ageMs = nowMs - Date.parse(sticky.at);
|
|
5256
|
+
const warm = ageMs <= PROMPT_CACHE_TTL_MS;
|
|
5257
|
+
if (input.resuming || warm) {
|
|
5258
|
+
const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
|
|
5259
|
+
ordered.splice(stickyIndex, 1);
|
|
5260
|
+
ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
const pick = ordered.find((candidate) => candidate.class !== "ineligible");
|
|
5264
|
+
const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
|
|
5265
|
+
const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
|
|
5266
|
+
return {
|
|
5267
|
+
candidates: ordered,
|
|
5268
|
+
...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
|
|
5269
|
+
};
|
|
5270
|
+
}
|
|
5271
|
+
|
|
5272
|
+
// src/usage/store.ts
|
|
5273
|
+
var DAY_MS2 = 864e5;
|
|
5274
|
+
var DAYS_IN_WEEK = 7;
|
|
5275
|
+
var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
5276
|
+
|
|
5277
|
+
// src/usage/poolPick.ts
|
|
5278
|
+
var SCHEMA_VERSION = 1;
|
|
5279
|
+
var PickEntrySchema = z9.strictObject({ identity: z9.string().min(1), at: z9.iso.datetime() });
|
|
5280
|
+
var PicksFileSchema = z9.strictObject({
|
|
5281
|
+
schemaVersion: z9.literal(SCHEMA_VERSION),
|
|
5282
|
+
picks: z9.record(z9.string().min(1), PickEntrySchema)
|
|
5283
|
+
});
|
|
5284
|
+
function errorMessage(error) {
|
|
5285
|
+
return error instanceof Error ? error.message : String(error);
|
|
5286
|
+
}
|
|
5287
|
+
function loadPoolMembers(fs10, paths, identities, nowMs) {
|
|
5288
|
+
const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
|
|
5289
|
+
const log = readUsageLog(fs10, paths.usageLogDir, { sinceMs: windowStartMs });
|
|
5290
|
+
return identities.map((identity) => {
|
|
5291
|
+
const records = log.records.filter((record) => record.identity === identity && record.provider === ANTHROPIC_PROVIDER && Date.parse(record.at) >= windowStartMs);
|
|
5292
|
+
try {
|
|
5293
|
+
const snapshot = readUsageSnapshot(fs10, paths.usageSnapshotsDir, identity);
|
|
5294
|
+
const account = readAccountMetadata(fs10, paths.identitiesDir, identity) ?? snapshot?.account;
|
|
5295
|
+
return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
|
|
5296
|
+
} catch (error) {
|
|
5297
|
+
if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
|
|
5298
|
+
return { identity, records, readError: errorMessage(error) };
|
|
5299
|
+
}
|
|
5300
|
+
throw error;
|
|
5301
|
+
}
|
|
5302
|
+
});
|
|
5303
|
+
}
|
|
5304
|
+
function rankPoolFromStore(params) {
|
|
5305
|
+
return rankPool({
|
|
5306
|
+
members: loadPoolMembers(params.fs, params.paths, params.identities, params.nowMs),
|
|
5307
|
+
nowMs: params.nowMs,
|
|
5308
|
+
resuming: params.resuming,
|
|
5309
|
+
...params.sticky === void 0 ? {} : { sticky: params.sticky }
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
function readPicksFile(fs10, file) {
|
|
5313
|
+
const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
|
|
5314
|
+
const raw = fs10.readFileUtf8(file);
|
|
5315
|
+
if (raw === void 0) {
|
|
5316
|
+
return { file: empty };
|
|
5317
|
+
}
|
|
5318
|
+
let parsed;
|
|
5319
|
+
try {
|
|
5320
|
+
parsed = JSON.parse(raw);
|
|
5321
|
+
} catch {
|
|
5322
|
+
return { file: empty, problem: `${file} is not valid JSON` };
|
|
5323
|
+
}
|
|
5324
|
+
const result = PicksFileSchema.safeParse(parsed);
|
|
5325
|
+
return result.success ? { file: result.data } : { file: empty, problem: `${file} is not a picks file this agent-shim can read` };
|
|
5326
|
+
}
|
|
5327
|
+
function readStickyPick(fs10, picksFile, directory) {
|
|
5328
|
+
const read = readPicksFile(fs10, picksFile);
|
|
5329
|
+
const entry = read.file.picks[directory];
|
|
5330
|
+
return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
|
|
5331
|
+
}
|
|
5332
|
+
|
|
5333
|
+
// src/launcher/pool.ts
|
|
5334
|
+
function splitMembers(pool, paths, fs10) {
|
|
5335
|
+
const exists = (name) => loadIdentity(paths.identitiesDir, name, fs10) !== void 0;
|
|
5336
|
+
return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
|
|
5337
|
+
}
|
|
5338
|
+
|
|
5339
|
+
// src/poolPickReport.ts
|
|
5340
|
+
function candidateView(candidate) {
|
|
5341
|
+
return {
|
|
5342
|
+
identity: candidate.identity,
|
|
5343
|
+
class: candidate.class,
|
|
5344
|
+
...candidate.score === void 0 ? {} : { score: candidate.score },
|
|
5345
|
+
feasible: candidate.feasible,
|
|
5346
|
+
...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
|
|
5347
|
+
plan: candidate.plan,
|
|
5348
|
+
reasons: candidate.reasons
|
|
5349
|
+
};
|
|
5350
|
+
}
|
|
5351
|
+
function collectPoolPick(params) {
|
|
5352
|
+
const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
|
|
5353
|
+
const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
|
|
5354
|
+
const ranking = rankPoolFromStore({
|
|
5355
|
+
fs: params.usageFs,
|
|
5356
|
+
paths: params.paths,
|
|
5357
|
+
identities: present,
|
|
5358
|
+
nowMs: params.nowMs,
|
|
5359
|
+
resuming: false,
|
|
5360
|
+
...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
|
|
5361
|
+
});
|
|
5362
|
+
return {
|
|
5363
|
+
pool: params.poolName,
|
|
5364
|
+
directory: params.directory,
|
|
5365
|
+
...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
|
|
5366
|
+
candidates: ranking.candidates.map(candidateView),
|
|
5367
|
+
missing,
|
|
5368
|
+
...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
|
|
5369
|
+
...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5373
|
+
// src/realPorts.ts
|
|
5374
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5375
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5376
|
+
import fs7 from "node:fs";
|
|
5377
|
+
import path19 from "node:path";
|
|
5378
|
+
import { isSea } from "node:sea";
|
|
5379
|
+
|
|
5380
|
+
// src/versionDiscovery.ts
|
|
5381
|
+
import os2 from "node:os";
|
|
5382
|
+
import path18 from "node:path";
|
|
5383
|
+
function defaultVersionsDir() {
|
|
5384
|
+
return path18.join(os2.homedir(), ".local", "share", "claude", "versions");
|
|
5385
|
+
}
|
|
5386
|
+
var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
|
|
5387
|
+
function isNumericDottedVersion(name) {
|
|
5388
|
+
return NUMERIC_DOTTED_VERSION_RE.test(name);
|
|
5389
|
+
}
|
|
5390
|
+
function compareVersions2(a, b) {
|
|
5391
|
+
if (!isNumericDottedVersion(a)) {
|
|
5392
|
+
throw new Error(`compareVersions: "${a}" is not a valid plain dotted-numeric version`);
|
|
5393
|
+
}
|
|
5394
|
+
if (!isNumericDottedVersion(b)) {
|
|
5395
|
+
throw new Error(`compareVersions: "${b}" is not a valid plain dotted-numeric version`);
|
|
5396
|
+
}
|
|
5397
|
+
const segmentsA = a.split(".").map(Number);
|
|
5398
|
+
const segmentsB = b.split(".").map(Number);
|
|
5399
|
+
const length = Math.max(segmentsA.length, segmentsB.length);
|
|
5400
|
+
for (let i = 0; i < length; i += 1) {
|
|
5401
|
+
const segmentA = segmentsA[i] ?? 0;
|
|
5402
|
+
const segmentB = segmentsB[i] ?? 0;
|
|
5403
|
+
if (segmentA !== segmentB) {
|
|
5404
|
+
return segmentA - segmentB;
|
|
5405
|
+
}
|
|
5406
|
+
}
|
|
5407
|
+
return 0;
|
|
5408
|
+
}
|
|
5409
|
+
function pickHighestVersion(entries) {
|
|
5410
|
+
const candidates = entries.filter(
|
|
5411
|
+
(entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)
|
|
5412
|
+
);
|
|
5413
|
+
if (candidates.length === 0) {
|
|
5414
|
+
return void 0;
|
|
5415
|
+
}
|
|
5416
|
+
return candidates.reduce(
|
|
5417
|
+
(highest, candidate) => compareVersions2(candidate.name, highest.name) > 0 ? candidate : highest
|
|
5418
|
+
).name;
|
|
5419
|
+
}
|
|
5420
|
+
function discoverClaudeBinary(options) {
|
|
5421
|
+
const versionsDir = options.versionsDir ?? defaultVersionsDir();
|
|
5422
|
+
const entries = options.listVersionsDir(versionsDir);
|
|
5423
|
+
const highestVersion = pickHighestVersion(entries);
|
|
5424
|
+
if (highestVersion !== void 0) {
|
|
5425
|
+
return {
|
|
5426
|
+
path: path18.join(versionsDir, highestVersion),
|
|
5427
|
+
source: "versions-dir",
|
|
5428
|
+
version: highestVersion
|
|
5429
|
+
};
|
|
5430
|
+
}
|
|
5431
|
+
const ownInstallDirs = new Set(options.ownInstallDirs.map((dir) => path18.resolve(dir)));
|
|
5432
|
+
for (const dir of options.pathDirs) {
|
|
5433
|
+
if (ownInstallDirs.has(path18.resolve(dir))) {
|
|
5434
|
+
continue;
|
|
5435
|
+
}
|
|
5436
|
+
const found = options.findExecutableInDir(dir, "claude");
|
|
5437
|
+
if (found !== void 0) {
|
|
5438
|
+
return { path: found, source: "path-fallback" };
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
throw new Error(
|
|
5442
|
+
`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.`
|
|
5443
|
+
);
|
|
5444
|
+
}
|
|
5445
|
+
|
|
5446
|
+
// src/realPorts.ts
|
|
5447
|
+
function isEnoent3(error) {
|
|
5448
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5449
|
+
}
|
|
5450
|
+
var realFsPort = {
|
|
5451
|
+
readFileUtf8(filePath) {
|
|
5452
|
+
try {
|
|
5453
|
+
return fs7.readFileSync(filePath, "utf8");
|
|
5454
|
+
} catch (error) {
|
|
5455
|
+
if (isEnoent3(error)) {
|
|
5456
|
+
return void 0;
|
|
5457
|
+
}
|
|
5458
|
+
throw error;
|
|
5459
|
+
}
|
|
5460
|
+
},
|
|
5461
|
+
readConfigFile: cosmiconfigReader(),
|
|
5462
|
+
readdir(dirPath) {
|
|
5463
|
+
try {
|
|
5464
|
+
return fs7.readdirSync(dirPath);
|
|
5465
|
+
} catch (error) {
|
|
5466
|
+
if (isEnoent3(error)) {
|
|
5467
|
+
return [];
|
|
5468
|
+
}
|
|
5469
|
+
throw error;
|
|
5470
|
+
}
|
|
5471
|
+
}
|
|
5472
|
+
};
|
|
5473
|
+
function isErrorWithCode(error, code) {
|
|
5474
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
5475
|
+
}
|
|
5476
|
+
var OWNER_ONLY_DIR_MODE = 448;
|
|
5477
|
+
var OWNER_ONLY_FILE_MODE = 384;
|
|
5478
|
+
var PERMISSION_BITS = 511;
|
|
5479
|
+
var realFarmFs = {
|
|
5480
|
+
lstat(filePath) {
|
|
5481
|
+
let stat;
|
|
5482
|
+
try {
|
|
5483
|
+
stat = fs7.lstatSync(filePath);
|
|
5484
|
+
} catch (error) {
|
|
5485
|
+
if (isEnoent3(error)) {
|
|
5486
|
+
return void 0;
|
|
5487
|
+
}
|
|
5488
|
+
throw error;
|
|
5489
|
+
}
|
|
5490
|
+
const kind = stat.isSymbolicLink() ? "symlink" : stat.isDirectory() ? "dir" : "file";
|
|
5491
|
+
return { kind, mtimeMs: stat.mtimeMs, sizeBytes: stat.size };
|
|
5492
|
+
},
|
|
5493
|
+
readdir(dirPath) {
|
|
5494
|
+
try {
|
|
5495
|
+
return fs7.readdirSync(dirPath);
|
|
5496
|
+
} catch (error) {
|
|
5497
|
+
if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
|
|
5498
|
+
return [];
|
|
5499
|
+
}
|
|
5500
|
+
throw error;
|
|
5501
|
+
}
|
|
5502
|
+
},
|
|
5503
|
+
mkdirp(dirPath) {
|
|
5504
|
+
fs7.mkdirSync(dirPath, { recursive: true });
|
|
5505
|
+
},
|
|
5506
|
+
symlink(target, linkPath) {
|
|
5507
|
+
fs7.symlinkSync(target, linkPath);
|
|
5508
|
+
},
|
|
5509
|
+
rename(from, to) {
|
|
5510
|
+
fs7.renameSync(from, to);
|
|
5511
|
+
},
|
|
5512
|
+
removeRecursive(targetPath) {
|
|
5513
|
+
fs7.rmSync(targetPath, { recursive: true, force: true });
|
|
5514
|
+
},
|
|
5515
|
+
copyRecursive(from, to) {
|
|
5516
|
+
fs7.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5517
|
+
},
|
|
5518
|
+
readFileUtf8(filePath) {
|
|
5519
|
+
try {
|
|
5520
|
+
return fs7.readFileSync(filePath, "utf8");
|
|
5521
|
+
} catch (error) {
|
|
5522
|
+
if (isEnoent3(error)) {
|
|
5523
|
+
return void 0;
|
|
5524
|
+
}
|
|
5525
|
+
throw error;
|
|
5526
|
+
}
|
|
5527
|
+
},
|
|
5528
|
+
writeFileUtf8(filePath, contents) {
|
|
5529
|
+
fs7.writeFileSync(filePath, contents, "utf8");
|
|
5530
|
+
},
|
|
5531
|
+
mkdirPrivate(dirPath) {
|
|
5532
|
+
fs7.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
|
|
5533
|
+
fs7.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
|
|
5534
|
+
},
|
|
5535
|
+
writeFilePrivate(filePath, contents) {
|
|
5536
|
+
const temp = `${filePath}.${String(process.pid)}.tmp`;
|
|
5537
|
+
fs7.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
|
|
5538
|
+
fs7.chmodSync(temp, OWNER_ONLY_FILE_MODE);
|
|
5539
|
+
fs7.renameSync(temp, filePath);
|
|
5540
|
+
},
|
|
5541
|
+
appendFilePrivate(filePath, contents) {
|
|
5542
|
+
const descriptor = fs7.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
|
|
5543
|
+
try {
|
|
5544
|
+
if ((fs7.fstatSync(descriptor).mode & PERMISSION_BITS) !== OWNER_ONLY_FILE_MODE) {
|
|
5545
|
+
fs7.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
|
|
5546
|
+
}
|
|
5547
|
+
const bytes = Buffer.from(contents, "utf8");
|
|
5548
|
+
let written = 0;
|
|
5549
|
+
while (written < bytes.length) {
|
|
5550
|
+
written += fs7.writeSync(descriptor, bytes, written, bytes.length - written);
|
|
5551
|
+
}
|
|
5552
|
+
} finally {
|
|
5553
|
+
fs7.closeSync(descriptor);
|
|
5554
|
+
}
|
|
5555
|
+
},
|
|
5556
|
+
writeFileExclusive(filePath, contents) {
|
|
5557
|
+
try {
|
|
5558
|
+
fs7.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
|
|
5559
|
+
return true;
|
|
5560
|
+
} catch (error) {
|
|
5561
|
+
if (isErrorWithCode(error, "EEXIST")) {
|
|
5562
|
+
return false;
|
|
5563
|
+
}
|
|
5564
|
+
throw error;
|
|
5565
|
+
}
|
|
5566
|
+
},
|
|
5567
|
+
hashFile(filePath) {
|
|
5568
|
+
try {
|
|
5569
|
+
return createHash4("sha256").update(fs7.readFileSync(filePath)).digest("hex");
|
|
5570
|
+
} catch (error) {
|
|
5571
|
+
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
5572
|
+
return void 0;
|
|
5573
|
+
}
|
|
5574
|
+
throw error;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
};
|
|
5578
|
+
function realIsProcessAlive(pid) {
|
|
5579
|
+
try {
|
|
5580
|
+
process.kill(pid, 0);
|
|
5581
|
+
return true;
|
|
5582
|
+
} catch (error) {
|
|
5583
|
+
return isErrorWithCode(error, "EPERM");
|
|
5584
|
+
}
|
|
5585
|
+
}
|
|
5586
|
+
function realIsProcessZombie(pid) {
|
|
5587
|
+
const result = spawnSync("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
|
|
5588
|
+
return result.status === 0 && result.stdout.trim().startsWith("Z");
|
|
5589
|
+
}
|
|
5590
|
+
function realIsProcessRunning(pid) {
|
|
5591
|
+
return realIsProcessAlive(pid) && !realIsProcessZombie(pid);
|
|
5592
|
+
}
|
|
5593
|
+
var realRunPort = {
|
|
5594
|
+
run(command, args) {
|
|
5595
|
+
const result = spawnSync(command, [...args], { encoding: "utf8" });
|
|
5596
|
+
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
5597
|
+
}
|
|
5598
|
+
};
|
|
5599
|
+
function resolveGitBranch(run, cwd) {
|
|
5600
|
+
const result = run.run("git", ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
5601
|
+
if (result.status !== 0) {
|
|
5602
|
+
return {};
|
|
5603
|
+
}
|
|
5604
|
+
const name = result.stdout.trim();
|
|
5605
|
+
if (name === "") {
|
|
5606
|
+
return {};
|
|
5607
|
+
}
|
|
5608
|
+
if (name === "HEAD") {
|
|
5609
|
+
return { branchDetached: true };
|
|
5610
|
+
}
|
|
5611
|
+
return { branch: name, branchDetached: false };
|
|
5612
|
+
}
|
|
5613
|
+
var realProcPort = {
|
|
5614
|
+
env: process.env,
|
|
5615
|
+
argv: process.argv.slice(2),
|
|
5616
|
+
exit: (code) => process.exit(code)
|
|
5617
|
+
};
|
|
5618
|
+
var EXECUTE_BITS_MASK = 73;
|
|
5619
|
+
function listVersionsDir(dir) {
|
|
5620
|
+
let entries;
|
|
5621
|
+
try {
|
|
5622
|
+
entries = fs7.readdirSync(dir, { withFileTypes: true });
|
|
5623
|
+
} catch (error) {
|
|
5624
|
+
if (isEnoent3(error)) {
|
|
5625
|
+
return [];
|
|
5626
|
+
}
|
|
5627
|
+
throw error;
|
|
5628
|
+
}
|
|
5629
|
+
return entries.filter((entry) => entry.isFile()).map((entry) => {
|
|
5630
|
+
const fullPath = path19.join(dir, entry.name);
|
|
5631
|
+
const stat = fs7.statSync(fullPath);
|
|
5632
|
+
return {
|
|
5633
|
+
name: entry.name,
|
|
5634
|
+
isFile: true,
|
|
5635
|
+
isExecutable: (stat.mode & EXECUTE_BITS_MASK) !== 0,
|
|
5636
|
+
sizeBytes: stat.size
|
|
5637
|
+
};
|
|
5638
|
+
});
|
|
5639
|
+
}
|
|
5640
|
+
var WINDOWS_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.PS1";
|
|
5641
|
+
function windowsPathExtensions(pathext) {
|
|
5642
|
+
return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
|
|
5643
|
+
}
|
|
5644
|
+
function resolveExecutableCandidate(dir, name, env) {
|
|
5645
|
+
const candidate = path19.join(dir, name);
|
|
5646
|
+
if (env.platform === "win32") {
|
|
5647
|
+
const extensions = windowsPathExtensions(env.pathext);
|
|
5648
|
+
const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
|
|
5649
|
+
if (alreadyHasRecognisedExtension) {
|
|
5650
|
+
return env.statFileMode(candidate) !== void 0 ? candidate : void 0;
|
|
5651
|
+
}
|
|
5652
|
+
for (const ext of extensions) {
|
|
5653
|
+
const withExt = candidate + ext;
|
|
5654
|
+
if (env.statFileMode(withExt) !== void 0) {
|
|
5655
|
+
return withExt;
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
return void 0;
|
|
5659
|
+
}
|
|
5660
|
+
const mode = env.statFileMode(candidate);
|
|
5661
|
+
return mode !== void 0 && (mode & EXECUTE_BITS_MASK) !== 0 ? candidate : void 0;
|
|
5662
|
+
}
|
|
5663
|
+
function realStatFileMode(candidate) {
|
|
5664
|
+
try {
|
|
5665
|
+
const stat = fs7.statSync(candidate);
|
|
5666
|
+
return stat.isFile() ? stat.mode : void 0;
|
|
5667
|
+
} catch (error) {
|
|
5668
|
+
if (isEnoent3(error)) {
|
|
5669
|
+
return void 0;
|
|
5670
|
+
}
|
|
5671
|
+
throw error;
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
function findExecutableInDir(dir, name) {
|
|
5675
|
+
return resolveExecutableCandidate(dir, name, {
|
|
5676
|
+
platform: process.platform,
|
|
5677
|
+
pathext: process.env.PATHEXT,
|
|
5678
|
+
statFileMode: realStatFileMode
|
|
5679
|
+
});
|
|
5680
|
+
}
|
|
5681
|
+
function searchPathForExecutable(pathDirs, name, findExecutable) {
|
|
5682
|
+
for (const dir of pathDirs) {
|
|
5683
|
+
const found = findExecutable(dir, name);
|
|
5684
|
+
if (found !== void 0) {
|
|
5685
|
+
return found;
|
|
5686
|
+
}
|
|
5687
|
+
}
|
|
5688
|
+
return void 0;
|
|
5689
|
+
}
|
|
5690
|
+
function resolveOwnExecutablePath(env) {
|
|
5691
|
+
if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
|
|
5692
|
+
return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
|
|
5693
|
+
}
|
|
5694
|
+
const candidate = env.argv1 === void 0 ? env.execPath : path19.resolve(env.cwd, env.argv1);
|
|
5695
|
+
const candidateDir = path19.dirname(candidate);
|
|
5696
|
+
const candidateDirOnPath = env.pathDirs.some((dir) => path19.resolve(dir) === candidateDir);
|
|
5697
|
+
if (candidateDirOnPath) {
|
|
5698
|
+
return candidate;
|
|
5699
|
+
}
|
|
5700
|
+
const viaPath = searchPathForExecutable(env.pathDirs, path19.basename(candidate), env.findExecutableInDir);
|
|
5701
|
+
return viaPath ?? candidate;
|
|
5702
|
+
}
|
|
5703
|
+
function realOwnExecutablePath() {
|
|
5704
|
+
return resolveOwnExecutablePath({
|
|
5705
|
+
argv1: process.argv[1],
|
|
5706
|
+
execPath: process.execPath,
|
|
5707
|
+
cwd: process.cwd(),
|
|
5708
|
+
pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
|
|
5709
|
+
findExecutableInDir
|
|
5710
|
+
});
|
|
5711
|
+
}
|
|
5712
|
+
function realResolveClaudeBinary(ownInstallDirs) {
|
|
5713
|
+
return () => discoverClaudeBinary({
|
|
5714
|
+
listVersionsDir,
|
|
5715
|
+
pathDirs: (process.env.PATH ?? "").split(path19.delimiter).filter((dir) => dir !== ""),
|
|
5716
|
+
findExecutableInDir,
|
|
5717
|
+
ownInstallDirs: [...ownInstallDirs]
|
|
5718
|
+
});
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5721
|
+
// src/checkReport.ts
|
|
5722
|
+
import path20 from "node:path";
|
|
5723
|
+
import { z as z10 } from "zod";
|
|
5724
|
+
|
|
5725
|
+
// src/cli/bool.ts
|
|
5726
|
+
function parseBoolWord(input) {
|
|
5727
|
+
switch (input) {
|
|
5728
|
+
case "true":
|
|
5729
|
+
case "1":
|
|
5730
|
+
return true;
|
|
5731
|
+
case "false":
|
|
5732
|
+
case "0":
|
|
5733
|
+
return false;
|
|
5734
|
+
default:
|
|
5735
|
+
return void 0;
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
|
|
5739
|
+
// src/cli/envBool.ts
|
|
5740
|
+
var InvalidEnvBoolError = class extends UsageError {
|
|
5741
|
+
constructor(variable, value) {
|
|
5742
|
+
super(`${variable} must be one of true, false, 1 or 0, got "${value}".`);
|
|
5743
|
+
this.variable = variable;
|
|
5744
|
+
this.value = value;
|
|
5745
|
+
this.name = "InvalidEnvBoolError";
|
|
5746
|
+
}
|
|
5747
|
+
variable;
|
|
5748
|
+
value;
|
|
5749
|
+
};
|
|
5750
|
+
function parseEnvBool(variable, value) {
|
|
5751
|
+
if (value === void 0 || value === "") {
|
|
5752
|
+
return void 0;
|
|
5753
|
+
}
|
|
5754
|
+
const parsed = parseBoolWord(value);
|
|
5755
|
+
if (parsed === void 0) {
|
|
5756
|
+
throw new InvalidEnvBoolError(variable, value);
|
|
5757
|
+
}
|
|
5758
|
+
return parsed;
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
// src/checkReport.ts
|
|
5762
|
+
var HISTORY_PROJECTS_KEY_PREFIX = "history/projects/";
|
|
5763
|
+
function projectFragmentOf(rawKey) {
|
|
5764
|
+
return rawKey.startsWith(HISTORY_PROJECTS_KEY_PREFIX) ? rawKey.slice(HISTORY_PROJECTS_KEY_PREFIX.length) : void 0;
|
|
5765
|
+
}
|
|
5766
|
+
function existingProjectNames(facts) {
|
|
5767
|
+
const names = /* @__PURE__ */ new Set();
|
|
5768
|
+
const prefix = "projects/";
|
|
5769
|
+
for (const rel of facts.entries.keys()) {
|
|
5770
|
+
if (!rel.startsWith(prefix)) {
|
|
5771
|
+
continue;
|
|
5772
|
+
}
|
|
5773
|
+
const rest = rel.slice(prefix.length);
|
|
5774
|
+
const head = rest.split("/")[0];
|
|
5775
|
+
if (head !== void 0 && head !== "") {
|
|
5776
|
+
names.add(head);
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
return [...names];
|
|
5780
|
+
}
|
|
5781
|
+
function flagAmbiguousEncodings(flattened, facts) {
|
|
5782
|
+
const fragments = [];
|
|
5783
|
+
for (const rule of flattened.rules.values()) {
|
|
5784
|
+
const fragment = projectFragmentOf(rule.rawKey);
|
|
5785
|
+
if (fragment !== void 0) {
|
|
5786
|
+
fragments.push(fragment);
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
if (fragments.length === 0) {
|
|
5790
|
+
return [];
|
|
5791
|
+
}
|
|
5792
|
+
return detectEncodingAmbiguity(fragments, { home: facts.home, existingNames: existingProjectNames(facts) });
|
|
5793
|
+
}
|
|
5794
|
+
function formatDecision(decision) {
|
|
5795
|
+
const status = decision.shared ? "shared" : "hidden";
|
|
5796
|
+
let reason;
|
|
5797
|
+
switch (decision.via) {
|
|
5798
|
+
case "secret-floor":
|
|
5799
|
+
reason = "secret (never shared, cannot be overridden by any layer)";
|
|
5800
|
+
break;
|
|
5801
|
+
case "unclassified":
|
|
5802
|
+
reason = "unclassified entry (no category recognises it)";
|
|
5803
|
+
break;
|
|
5804
|
+
case "entry-rule":
|
|
5805
|
+
reason = decision.rule === void 0 ? "an entries rule" : `entries rule "${decision.rule.rawKey}" from layer ${String(decision.rule.layer)}`;
|
|
5806
|
+
break;
|
|
5807
|
+
case "category-override":
|
|
5808
|
+
reason = `category "${decision.category ?? "?"}" overridden by a layer`;
|
|
5809
|
+
break;
|
|
5810
|
+
case "category-default":
|
|
5811
|
+
reason = `category "${decision.category ?? "?"}" shipped default`;
|
|
5812
|
+
break;
|
|
5813
|
+
}
|
|
5814
|
+
const eliminatedNote = decision.eliminated !== void 0 && decision.eliminated.length > 0 ? ` [${String(decision.eliminated.length)} more specific rule(s) eliminated by a failing when-condition]` : "";
|
|
5815
|
+
return `${decision.relPath}: ${status} \u2014 ${reason}${eliminatedNote}`;
|
|
5816
|
+
}
|
|
5817
|
+
function parseKeychainServiceName(stderr) {
|
|
5818
|
+
const match = /"svce"<blob>="([^"]*)"/.exec(stderr);
|
|
5819
|
+
return match?.[1];
|
|
5820
|
+
}
|
|
5821
|
+
function lookupKeychainService(run, farmRoot) {
|
|
5822
|
+
const result = run.run("security", ["find-generic-password", "-a", farmRoot, "-g"]);
|
|
5823
|
+
if (result.status !== 0) {
|
|
5824
|
+
return {
|
|
5825
|
+
checked: true,
|
|
5826
|
+
found: false,
|
|
5827
|
+
note: `No Keychain entry found for account "${farmRoot}" (security exited ${result.status === null ? "with no status" : String(result.status)}).`
|
|
5828
|
+
};
|
|
5829
|
+
}
|
|
5830
|
+
const serviceName = parseKeychainServiceName(result.stderr);
|
|
5831
|
+
return {
|
|
5832
|
+
checked: true,
|
|
5833
|
+
found: true,
|
|
5834
|
+
...serviceName === void 0 ? {} : { serviceName },
|
|
5835
|
+
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}".`
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
var SettingsSecretsShapeSchema = z10.looseObject({
|
|
5839
|
+
env: z10.record(z10.string(), z10.unknown()).optional(),
|
|
5840
|
+
hooks: z10.record(z10.string(), z10.array(z10.unknown())).optional()
|
|
5841
|
+
});
|
|
5842
|
+
function hookCommandCountOf(group) {
|
|
5843
|
+
if (typeof group !== "object" || group === null) {
|
|
5844
|
+
return 0;
|
|
5845
|
+
}
|
|
5846
|
+
if (!("hooks" in group)) {
|
|
5847
|
+
return 0;
|
|
5848
|
+
}
|
|
5849
|
+
return Array.isArray(group.hooks) ? group.hooks.length : 0;
|
|
5850
|
+
}
|
|
5851
|
+
function countHookCommands(hooks) {
|
|
5852
|
+
if (hooks === void 0) {
|
|
5853
|
+
return 0;
|
|
5854
|
+
}
|
|
5855
|
+
let total = 0;
|
|
5856
|
+
for (const groups of Object.values(hooks)) {
|
|
5857
|
+
for (const group of groups) {
|
|
5858
|
+
total += hookCommandCountOf(group);
|
|
5859
|
+
}
|
|
5860
|
+
}
|
|
5861
|
+
return total;
|
|
5862
|
+
}
|
|
5863
|
+
function inspectSettingsExposure(params) {
|
|
5864
|
+
if (!params.settingsShared) {
|
|
5865
|
+
return [];
|
|
5866
|
+
}
|
|
5867
|
+
const reports = [];
|
|
5868
|
+
for (const [file, raw] of Object.entries(params.files)) {
|
|
5869
|
+
if (raw === void 0) {
|
|
5870
|
+
continue;
|
|
5871
|
+
}
|
|
5872
|
+
let parsed;
|
|
5873
|
+
try {
|
|
5874
|
+
parsed = JSON.parse(raw);
|
|
5875
|
+
} catch {
|
|
5876
|
+
continue;
|
|
5877
|
+
}
|
|
5878
|
+
const result = SettingsSecretsShapeSchema.safeParse(parsed);
|
|
5879
|
+
if (!result.success) {
|
|
5880
|
+
continue;
|
|
5881
|
+
}
|
|
5882
|
+
const envKeyNames = Object.keys(result.data.env ?? {});
|
|
5883
|
+
const hookEventNames = Object.keys(result.data.hooks ?? {});
|
|
5884
|
+
const hookCommandCount = countHookCommands(result.data.hooks);
|
|
5885
|
+
if (envKeyNames.length === 0 && hookCommandCount === 0) {
|
|
5886
|
+
continue;
|
|
5887
|
+
}
|
|
5888
|
+
reports.push({ file, envKeyNames, hookEventNames, hookCommandCount });
|
|
5889
|
+
}
|
|
5890
|
+
return reports;
|
|
5891
|
+
}
|
|
5892
|
+
function buildCredentialReport(provider, identity) {
|
|
5893
|
+
const identityCredential = identity?.credential === void 0 ? void 0 : summariseCredential(identity.credential);
|
|
5894
|
+
return {
|
|
5895
|
+
applies: provider !== void 0 ? "provider" : identityCredential !== void 0 ? "identity" : "stored-login",
|
|
5896
|
+
...provider === void 0 ? {} : {
|
|
5897
|
+
provider: provider.problem === void 0 ? { name: provider.name, credential: summariseCredential(provider.definition.credential) } : { name: provider.name, problem: provider.problem }
|
|
5898
|
+
},
|
|
5899
|
+
...identityCredential === void 0 ? {} : { identity: identityCredential }
|
|
5900
|
+
};
|
|
5901
|
+
}
|
|
5902
|
+
function runCheck(params) {
|
|
5903
|
+
const facts = buildEntryFacts({
|
|
5904
|
+
fs: params.farmFs,
|
|
5905
|
+
claudeHome: params.claudeHome,
|
|
5906
|
+
home: params.home,
|
|
5907
|
+
cwd: params.cwd,
|
|
5908
|
+
nowMs: params.nowMs,
|
|
5909
|
+
env: params.env,
|
|
5910
|
+
...params.branch === void 0 ? {} : { branch: params.branch },
|
|
5911
|
+
...params.branchDetached === void 0 ? {} : { branchDetached: params.branchDetached }
|
|
5912
|
+
});
|
|
5913
|
+
const resolved = resolveDecisions({ facts, cascade: params.cascade, classification: params.classification });
|
|
5914
|
+
const decisionLines = [...resolved.decisions.values()].slice().sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0).map(formatDecision);
|
|
5915
|
+
const projectEncodingAmbiguities = flagAmbiguousEncodings(resolved.flattened, facts);
|
|
5916
|
+
const ambientCredential = evaluateAmbientCredentialGuard({
|
|
5917
|
+
env: params.env,
|
|
5918
|
+
allowAmbientCredential: params.identity?.allowAmbientCredential ?? false,
|
|
5919
|
+
allowAmbientCredentialOverride: parseEnvBool("AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL", params.env.AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL) === true,
|
|
5920
|
+
...params.identityName === void 0 ? {} : { identityName: params.identityName }
|
|
5921
|
+
});
|
|
5922
|
+
const settingsShared = resolved.flattened.categories.get("settings") ?? SHIPPED_CATEGORY_DEFAULTS.settings;
|
|
5923
|
+
const settingsExposure = inspectSettingsExposure({ settingsShared, files: params.settingsFiles });
|
|
5924
|
+
const keychain = params.platform === "darwin" && params.run !== void 0 && params.farmRoot !== void 0 ? lookupKeychainService(params.run, params.farmRoot) : void 0;
|
|
5925
|
+
return {
|
|
5926
|
+
...params.identityName === void 0 ? {} : { identityName: params.identityName },
|
|
5927
|
+
identitySource: params.identitySource,
|
|
5928
|
+
...params.poolPick === void 0 ? {} : { poolPick: params.poolPick },
|
|
5929
|
+
...params.configProfileName === void 0 ? {} : { configProfileName: params.configProfileName },
|
|
5930
|
+
configProfileSource: params.configProfileSource,
|
|
5931
|
+
resolved,
|
|
5932
|
+
decisionLines,
|
|
5933
|
+
projectEncodingAmbiguities,
|
|
5934
|
+
ambientCredential,
|
|
5935
|
+
...keychain === void 0 ? {} : { keychain },
|
|
5936
|
+
settingsExposure,
|
|
5937
|
+
credential: buildCredentialReport(params.provider, params.identity)
|
|
5938
|
+
};
|
|
5939
|
+
}
|
|
5940
|
+
function formatCheckReport(report) {
|
|
5941
|
+
const lines = [];
|
|
5942
|
+
lines.push(`Identity: ${report.identityName ?? "(none)"} (${report.identitySource})`);
|
|
5943
|
+
if (report.poolPick !== void 0) {
|
|
5944
|
+
const top = report.poolPick.candidates.find((candidate) => candidate.identity === report.poolPick?.pick);
|
|
5945
|
+
lines.push(`Pool: ${report.poolPick.pool}${top === void 0 ? ", every member is refused right now" : `, would pick ${top.identity} (${top.reasons.join("; ")})`}`);
|
|
5946
|
+
}
|
|
5947
|
+
lines.push(`Configuration profile: ${report.configProfileName ?? "(none)"} (${report.configProfileSource})`);
|
|
5948
|
+
lines.push("", "Layers (shallowest/earliest first):");
|
|
5949
|
+
for (const layer of report.resolved.assembled.layers) {
|
|
5950
|
+
lines.push(` [${String(layer.id)}] ${layer.kind}: ${layer.source}`);
|
|
5951
|
+
}
|
|
5952
|
+
lines.push("", "Resolved entries:");
|
|
5953
|
+
if (report.decisionLines.length === 0) {
|
|
5954
|
+
lines.push(" (nothing under ~/.claude to report)");
|
|
5955
|
+
}
|
|
5956
|
+
for (const line of report.decisionLines) {
|
|
5957
|
+
lines.push(` ${line}`);
|
|
5958
|
+
}
|
|
5959
|
+
if (report.projectEncodingAmbiguities.length > 0) {
|
|
5960
|
+
lines.push("", "Ambiguous history/projects/ encodings:");
|
|
5961
|
+
for (const ambiguity of report.projectEncodingAmbiguities) {
|
|
5962
|
+
lines.push(` "${ambiguity.fragment}" -> "${ambiguity.encoded}" (${ambiguity.reason}): ${ambiguity.detail}`);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
if (report.resolved.diagnostics.length > 0) {
|
|
5966
|
+
lines.push("", "Diagnostics:");
|
|
5967
|
+
for (const diagnostic of report.resolved.diagnostics) {
|
|
5968
|
+
lines.push(` [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`);
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
lines.push("", "Credential (source kinds and targets only, never values):");
|
|
5972
|
+
const { credential } = report;
|
|
5973
|
+
if (credential.provider !== void 0) {
|
|
5974
|
+
lines.push(
|
|
5975
|
+
` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem ?? ""}` : formatCredentialSummary(credential.provider.credential)}`
|
|
5976
|
+
);
|
|
5977
|
+
}
|
|
5978
|
+
if (credential.identity !== void 0) {
|
|
5979
|
+
lines.push(
|
|
5980
|
+
` Identity ${report.identityName ?? "(none)"}: ${formatCredentialSummary(credential.identity)}${credential.applies === "provider" ? " (not used: the provider's credential applies)" : ""}`
|
|
5981
|
+
);
|
|
5982
|
+
}
|
|
5983
|
+
if (credential.applies === "stored-login") {
|
|
5984
|
+
lines.push(" The identity's stored login (no credential block applies).");
|
|
5985
|
+
}
|
|
5986
|
+
lines.push("", "Ambient-credential exposure:");
|
|
5987
|
+
lines.push(
|
|
5988
|
+
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")
|
|
5989
|
+
);
|
|
5990
|
+
if (report.keychain !== void 0) {
|
|
5991
|
+
lines.push("", "macOS Keychain:");
|
|
5992
|
+
lines.push(` ${report.keychain.note}`);
|
|
5993
|
+
}
|
|
5994
|
+
if (report.settingsExposure.length > 0) {
|
|
5995
|
+
lines.push("", "Settings exposure (names and counts only, never values):");
|
|
5996
|
+
for (const exposure of report.settingsExposure) {
|
|
5997
|
+
lines.push(
|
|
5998
|
+
` ${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)`
|
|
5999
|
+
);
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
return lines;
|
|
6003
|
+
}
|
|
6004
|
+
function checkReportToJson(report) {
|
|
6005
|
+
const decisions = [...report.resolved.decisions.values()].sort((a, b) => a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0);
|
|
6006
|
+
return {
|
|
6007
|
+
identity: { name: report.identityName ?? null, source: report.identitySource },
|
|
6008
|
+
...report.poolPick === void 0 ? {} : { pool: report.poolPick },
|
|
6009
|
+
configProfile: { name: report.configProfileName ?? null, source: report.configProfileSource },
|
|
6010
|
+
layers: report.resolved.assembled.layers.map((layer) => ({ id: layer.id, kind: layer.kind, source: layer.source })),
|
|
6011
|
+
entries: decisions.map((decision) => ({
|
|
6012
|
+
path: decision.relPath,
|
|
6013
|
+
shared: decision.shared,
|
|
6014
|
+
via: decision.via,
|
|
6015
|
+
category: decision.category,
|
|
6016
|
+
...decision.rule === void 0 ? {} : { rule: { key: decision.rule.rawKey, layer: decision.rule.layer } },
|
|
6017
|
+
...decision.eliminated === void 0 || decision.eliminated.length === 0 ? {} : { eliminated: decision.eliminated.map((eliminated) => ({ key: eliminated.rule.rawKey, failed: eliminated.failed })) }
|
|
6018
|
+
})),
|
|
6019
|
+
projectEncodingAmbiguities: report.projectEncodingAmbiguities,
|
|
6020
|
+
diagnostics: report.resolved.diagnostics,
|
|
6021
|
+
ambientCredential: report.ambientCredential.ok ? { ok: true } : { ok: false, variable: report.ambientCredential.variable, message: report.ambientCredential.message },
|
|
6022
|
+
...report.keychain === void 0 ? {} : { keychain: report.keychain },
|
|
6023
|
+
settingsExposure: report.settingsExposure,
|
|
6024
|
+
credential: report.credential
|
|
6025
|
+
};
|
|
6026
|
+
}
|
|
6027
|
+
function checkReportHasWarnings(report) {
|
|
6028
|
+
return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0;
|
|
6029
|
+
}
|
|
6030
|
+
function loadCheckProvider(paths, name) {
|
|
6031
|
+
let definition;
|
|
6032
|
+
try {
|
|
6033
|
+
definition = readProvider(paths, name);
|
|
6034
|
+
} catch (error) {
|
|
6035
|
+
if (error instanceof ConfigValidationError || error instanceof LegacyProviderFileError) {
|
|
6036
|
+
return { name, problem: error.message };
|
|
6037
|
+
}
|
|
6038
|
+
throw error;
|
|
6039
|
+
}
|
|
6040
|
+
return definition === void 0 ? { name, problem: `no provider named "${name}"` } : { name, definition };
|
|
6041
|
+
}
|
|
6042
|
+
function collectCheckReport(params) {
|
|
6043
|
+
const { paths, cwd } = params;
|
|
6044
|
+
const home = os3.homedir();
|
|
6045
|
+
const claudeHome = resolveClaudeHome();
|
|
6046
|
+
const read = cosmiconfigReader();
|
|
6047
|
+
const classification = loadClassification(paths);
|
|
6048
|
+
const loaded = loadCascadeInput({ paths, home, cwd, read });
|
|
6049
|
+
const selections = readDirectorySelections(loaded);
|
|
6050
|
+
const git = resolveGitBranch(realRunPort, cwd);
|
|
6051
|
+
const decidedIdentity = decideIdentity({
|
|
6052
|
+
env: params.env,
|
|
6053
|
+
argv0Identity: params.identity,
|
|
6054
|
+
directoryPinnedIdentity: selections.identity,
|
|
6055
|
+
readActiveIdentityFile: () => {
|
|
6056
|
+
const raw = realFsPort.readFileUtf8(paths.activeIdentityFile);
|
|
6057
|
+
if (raw === void 0) {
|
|
6058
|
+
return void 0;
|
|
6059
|
+
}
|
|
6060
|
+
const trimmed = raw.trim();
|
|
6061
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
6062
|
+
}
|
|
6063
|
+
});
|
|
6064
|
+
let poolPick;
|
|
6065
|
+
if (decidedIdentity.pool !== void 0) {
|
|
6066
|
+
const pool = loaded.globalConfig?.pools?.[decidedIdentity.pool];
|
|
6067
|
+
if (pool === void 0) {
|
|
6068
|
+
throw new PoolNotFoundError(decidedIdentity.pool);
|
|
6069
|
+
}
|
|
6070
|
+
poolPick = collectPoolPick({ paths, fs: realFsPort, usageFs: realFarmFs, poolName: decidedIdentity.pool, pool, directory: cwd, nowMs: Date.now() });
|
|
6071
|
+
}
|
|
6072
|
+
const identityDecision = poolPick?.pick === void 0 ? decidedIdentity : { ...decidedIdentity, name: poolPick.pick };
|
|
6073
|
+
const loadedIdentity = identityDecision.name === void 0 ? void 0 : loadIdentity(paths.identitiesDir, identityDecision.name, realFsPort);
|
|
6074
|
+
const configProfileDecision = decideConfigProfile({
|
|
6075
|
+
env: params.env,
|
|
6076
|
+
directoryRuleConfigProfile: selections.configProfile,
|
|
6077
|
+
identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
|
|
6078
|
+
globalDefaultConfigProfile: loaded.globalConfig?.defaultConfigProfile
|
|
6079
|
+
});
|
|
6080
|
+
const cascade = loadCascadeInput({
|
|
6081
|
+
paths,
|
|
6082
|
+
home,
|
|
6083
|
+
cwd,
|
|
6084
|
+
read,
|
|
6085
|
+
...configProfileDecision.name === void 0 ? {} : { baseConfigProfile: configProfileDecision.name }
|
|
6086
|
+
}).input;
|
|
6087
|
+
const providerName = cascadeProviderName(cascade);
|
|
6088
|
+
const provider = providerName === void 0 ? void 0 : loadCheckProvider(paths, providerName);
|
|
6089
|
+
const farmRoot = identityDecision.name === void 0 ? void 0 : path20.join(paths.identitiesDir, identityDecision.name);
|
|
6090
|
+
const settingsFiles = {
|
|
6091
|
+
"settings.json": realFsPort.readFileUtf8(path20.join(claudeHome, "settings.json")),
|
|
6092
|
+
"settings.local.json": realFsPort.readFileUtf8(path20.join(claudeHome, "settings.local.json"))
|
|
6093
|
+
};
|
|
6094
|
+
const report = runCheck({
|
|
6095
|
+
cwd,
|
|
6096
|
+
home,
|
|
6097
|
+
claudeHome,
|
|
6098
|
+
env: params.env,
|
|
6099
|
+
...git.branch === void 0 ? {} : { branch: git.branch },
|
|
6100
|
+
...git.branchDetached === void 0 ? {} : { branchDetached: git.branchDetached },
|
|
6101
|
+
nowMs: Date.now(),
|
|
6102
|
+
farmFs: realFarmFs,
|
|
6103
|
+
cascade,
|
|
6104
|
+
classification,
|
|
6105
|
+
...identityDecision.name === void 0 ? {} : { identityName: identityDecision.name },
|
|
6106
|
+
identitySource: identityDecision.source,
|
|
6107
|
+
...poolPick === void 0 ? {} : { poolPick },
|
|
6108
|
+
...configProfileDecision.name === void 0 ? {} : { configProfileName: configProfileDecision.name },
|
|
6109
|
+
configProfileSource: configProfileDecision.source,
|
|
6110
|
+
...loadedIdentity === void 0 ? {} : { identity: loadedIdentity.config },
|
|
6111
|
+
settingsFiles,
|
|
6112
|
+
run: realRunPort,
|
|
6113
|
+
...farmRoot === void 0 ? {} : { farmRoot },
|
|
6114
|
+
platform: process.platform,
|
|
6115
|
+
...provider === void 0 ? {} : { provider }
|
|
6116
|
+
});
|
|
6117
|
+
return report;
|
|
6118
|
+
}
|
|
6119
|
+
|
|
6120
|
+
// src/claudeShim.ts
|
|
6121
|
+
import fs8 from "node:fs";
|
|
6122
|
+
import path21 from "node:path";
|
|
6123
|
+
import { z as z11 } from "zod";
|
|
6124
|
+
var ClaudeShimStateSchema = z11.strictObject({
|
|
6125
|
+
targetPath: z11.string().min(1),
|
|
6126
|
+
method: z11.enum(["hardlink", "copy"]),
|
|
6127
|
+
installedAtMs: z11.number()
|
|
6128
|
+
});
|
|
6129
|
+
function commandFilename(ownExecutablePath, commandName) {
|
|
6130
|
+
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
6131
|
+
}
|
|
6132
|
+
function resolveOwnInstallDirs(paths, ownExecutablePath) {
|
|
6133
|
+
const dirs = [path21.dirname(ownExecutablePath)];
|
|
6134
|
+
const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
6135
|
+
if (state !== void 0) {
|
|
6136
|
+
dirs.push(path21.dirname(state.targetPath));
|
|
6137
|
+
}
|
|
6138
|
+
return dirs;
|
|
6139
|
+
}
|
|
6140
|
+
var nodeLinkFs = { link: fs8.linkSync, copyFile: fs8.copyFileSync, chmod: fs8.chmodSync };
|
|
6141
|
+
function findPathShadow(params) {
|
|
6142
|
+
const resolvedTargetDir = path21.resolve(params.targetDir);
|
|
6143
|
+
for (const dir of params.pathDirs) {
|
|
6144
|
+
const found = params.findExecutableInDir(dir, params.targetFilename);
|
|
6145
|
+
if (found === void 0) {
|
|
6146
|
+
continue;
|
|
6147
|
+
}
|
|
6148
|
+
return path21.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
|
|
6149
|
+
}
|
|
6150
|
+
return params.pathDirs.some((dir) => path21.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
|
|
6151
|
+
}
|
|
6152
|
+
|
|
6153
|
+
// src/doctorReport.ts
|
|
6154
|
+
import fs9 from "node:fs";
|
|
6155
|
+
import path22 from "node:path";
|
|
6156
|
+
function refinePathShadow(status, ownExecutablePath, realpath) {
|
|
6157
|
+
if (status.status !== "shadowed") {
|
|
6158
|
+
return status;
|
|
6159
|
+
}
|
|
6160
|
+
return realpath(status.by) === realpath(ownExecutablePath) ? { status: "ok" } : status;
|
|
6161
|
+
}
|
|
6162
|
+
function validateJson(schema, input) {
|
|
6163
|
+
if (input.raw === void 0) {
|
|
6164
|
+
return { ok: false, message: `${input.path} is missing.` };
|
|
6165
|
+
}
|
|
6166
|
+
let parsed;
|
|
6167
|
+
try {
|
|
6168
|
+
parsed = JSON.parse(input.raw);
|
|
6169
|
+
} catch (error) {
|
|
6170
|
+
return { ok: false, message: `${input.path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
6171
|
+
}
|
|
6172
|
+
const result = schema.safeParse(parsed);
|
|
6173
|
+
if (!result.success) {
|
|
6174
|
+
return { ok: false, message: new ConfigValidationError(input.path, result.error.issues).message };
|
|
6175
|
+
}
|
|
6176
|
+
return { ok: true, data: result.data };
|
|
6177
|
+
}
|
|
6178
|
+
function pushPathResolution(push, resolution) {
|
|
6179
|
+
const ownDir = path22.dirname(resolution.ownExecutablePath);
|
|
6180
|
+
switch (resolution.agentShim.status) {
|
|
6181
|
+
case "ok":
|
|
6182
|
+
push("path-resolution", "pass", `\`agent-shim\` on PATH resolves to this running executable, ${resolution.ownExecutablePath}.`, "agent-shim");
|
|
6183
|
+
break;
|
|
6184
|
+
case "not-on-path":
|
|
6185
|
+
push(
|
|
6186
|
+
"path-resolution",
|
|
6187
|
+
"warn",
|
|
6188
|
+
`${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.`,
|
|
6189
|
+
"agent-shim"
|
|
6190
|
+
);
|
|
6191
|
+
break;
|
|
6192
|
+
case "shadowed":
|
|
6193
|
+
push(
|
|
6194
|
+
"path-resolution",
|
|
6195
|
+
"fail",
|
|
6196
|
+
`\`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.`,
|
|
6197
|
+
"agent-shim"
|
|
6198
|
+
);
|
|
6199
|
+
break;
|
|
6200
|
+
}
|
|
6201
|
+
if (resolution.claude === void 0) {
|
|
6202
|
+
return;
|
|
6203
|
+
}
|
|
6204
|
+
switch (resolution.claude.status) {
|
|
6205
|
+
case "ok":
|
|
6206
|
+
push("path-resolution", "pass", "`claude` on PATH resolves to the enabled shim.", "claude");
|
|
6207
|
+
break;
|
|
6208
|
+
case "not-on-path":
|
|
6209
|
+
push("path-resolution", "warn", "The enabled `claude` shim's directory is not on PATH \u2014 add it, or use `agent-shim run` instead.", "claude");
|
|
6210
|
+
break;
|
|
6211
|
+
case "shadowed":
|
|
6212
|
+
push(
|
|
6213
|
+
"path-resolution",
|
|
6214
|
+
"warn",
|
|
6215
|
+
`\`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.`,
|
|
6216
|
+
"claude"
|
|
6217
|
+
);
|
|
6218
|
+
break;
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
function pushProvider(push, entry) {
|
|
6222
|
+
if (entry.raw === void 0) {
|
|
6223
|
+
push("provider", "fail", `${entry.path} is missing.`, entry.name);
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
let parsed;
|
|
6227
|
+
try {
|
|
6228
|
+
parsed = JSON.parse(entry.raw);
|
|
6229
|
+
} catch (error) {
|
|
6230
|
+
push("provider", "fail", `${entry.path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, entry.name);
|
|
6231
|
+
return;
|
|
6232
|
+
}
|
|
6233
|
+
const conversion = legacyProviderConversion(parsed);
|
|
6234
|
+
if (conversion !== void 0) {
|
|
6235
|
+
push("provider", "fail", new LegacyProviderFileError(entry.path, conversion).message, entry.name);
|
|
6236
|
+
return;
|
|
6237
|
+
}
|
|
6238
|
+
const validated = ProviderSchema.safeParse(parsed);
|
|
6239
|
+
if (!validated.success) {
|
|
6240
|
+
push("provider", "fail", new ConfigValidationError(entry.path, validated.error.issues).message, entry.name);
|
|
6241
|
+
return;
|
|
6242
|
+
}
|
|
6243
|
+
push("provider", "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)}).`, entry.name);
|
|
6244
|
+
}
|
|
6245
|
+
function pushLegacyName(push, params) {
|
|
6246
|
+
const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
|
|
6247
|
+
if (legacyVariables.length === 0 && path22.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
|
|
6248
|
+
push("legacy-name", "pass", "Nothing here still uses the former `claude-use` name.");
|
|
6249
|
+
return;
|
|
6250
|
+
}
|
|
6251
|
+
for (const name of legacyVariables) {
|
|
6252
|
+
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);
|
|
6253
|
+
}
|
|
6254
|
+
if (path22.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
|
|
6255
|
+
push(
|
|
6256
|
+
"legacy-name",
|
|
6257
|
+
"warn",
|
|
6258
|
+
`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.`
|
|
6259
|
+
);
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
function runDoctor(params) {
|
|
6263
|
+
const findings = [];
|
|
6264
|
+
const push = (section, severity, message, subject) => {
|
|
6265
|
+
findings.push({ section, severity, message, ...subject === void 0 ? {} : { subject } });
|
|
6266
|
+
};
|
|
6267
|
+
const ambient = detectAmbientCredential(params.env);
|
|
6268
|
+
if (ambient === void 0) {
|
|
6269
|
+
push("ambient-credential", "pass", "No ambient-credential environment variable is set.");
|
|
6270
|
+
} else {
|
|
6271
|
+
push("ambient-credential", "warn", formatAmbientCredentialGuardMessage(ambient.variable));
|
|
6272
|
+
}
|
|
6273
|
+
if (params.binaryDiscovery.ok) {
|
|
6274
|
+
const { binary } = params.binaryDiscovery;
|
|
6275
|
+
const versionNote = binary.version === void 0 ? "" : `, version ${binary.version}`;
|
|
6276
|
+
push("binary-discovery", "pass", `Found ${binary.path} (${binary.source}${versionNote}).`);
|
|
6277
|
+
} else {
|
|
6278
|
+
push("binary-discovery", "fail", params.binaryDiscovery.message);
|
|
6279
|
+
}
|
|
6280
|
+
if (params.claudeShim.state === void 0) {
|
|
6281
|
+
push("claude-shim", "pass", "No `claude` command shim enabled (the default). Run `agent-shim shim enable` to add one.");
|
|
6282
|
+
} else if (!params.claudeShim.targetExists) {
|
|
6283
|
+
push(
|
|
6284
|
+
"claude-shim",
|
|
6285
|
+
"warn",
|
|
6286
|
+
`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.`
|
|
6287
|
+
);
|
|
6288
|
+
} else {
|
|
6289
|
+
push(
|
|
6290
|
+
"claude-shim",
|
|
6291
|
+
"pass",
|
|
6292
|
+
`\`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.`
|
|
6293
|
+
);
|
|
6294
|
+
}
|
|
6295
|
+
pushPathResolution(push, params.pathResolution);
|
|
6296
|
+
pushLegacyName(push, params);
|
|
6297
|
+
const profileSources = /* @__PURE__ */ new Map();
|
|
6298
|
+
for (const entry of params.configProfiles) {
|
|
6299
|
+
const validated = validateJson(ConfigProfileSchema, entry);
|
|
6300
|
+
if (!validated.ok) {
|
|
6301
|
+
push("config-profile", "fail", validated.message, entry.name);
|
|
6302
|
+
continue;
|
|
6303
|
+
}
|
|
6304
|
+
profileSources.set(entry.name, { name: entry.name, profile: validated.data });
|
|
6305
|
+
}
|
|
6306
|
+
const loadProfile = (name) => profileSources.get(name);
|
|
6307
|
+
for (const name of profileSources.keys()) {
|
|
6308
|
+
const linearised = lineariseProfile(name, loadProfile);
|
|
6309
|
+
if (linearised.diagnostics.length === 0) {
|
|
6310
|
+
push("config-profile", "pass", `${name} is valid and its extends chain resolves cleanly.`, name);
|
|
6311
|
+
} else {
|
|
6312
|
+
for (const diagnostic of linearised.diagnostics) {
|
|
6313
|
+
push("config-profile", "fail", diagnostic.message, name);
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
const validProfileNames = new Set(profileSources.keys());
|
|
6318
|
+
const validIdentityNames = /* @__PURE__ */ new Set();
|
|
6319
|
+
for (const entry of params.identities) {
|
|
6320
|
+
const validated = validateJson(IdentitySchema, entry);
|
|
6321
|
+
if (!validated.ok) {
|
|
6322
|
+
push("identity", "fail", validated.message, entry.name);
|
|
6323
|
+
continue;
|
|
6324
|
+
}
|
|
6325
|
+
validIdentityNames.add(entry.name);
|
|
6326
|
+
const defaultProfile = validated.data.defaultConfigProfile;
|
|
6327
|
+
if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
|
|
6328
|
+
push(
|
|
6329
|
+
"identity",
|
|
6330
|
+
"fail",
|
|
6331
|
+
`Identity "${entry.name}" names configuration profile "${defaultProfile}" as its default, but no such profile exists.`,
|
|
6332
|
+
entry.name
|
|
6333
|
+
);
|
|
6334
|
+
} else {
|
|
6335
|
+
const credential = validated.data.credential;
|
|
6336
|
+
push(
|
|
6337
|
+
"identity",
|
|
6338
|
+
"pass",
|
|
6339
|
+
`${entry.name} is valid and authenticates with ${credential === void 0 ? "its stored login" : `credential ${describeCredential(credential)}`}.`,
|
|
6340
|
+
entry.name
|
|
6341
|
+
);
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
|
|
6345
|
+
const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
|
|
6346
|
+
for (const [poolName, pool] of Object.entries(pools)) {
|
|
6347
|
+
const missing = pool.identities.filter((member) => !validIdentityNames.has(member));
|
|
6348
|
+
if (missing.length === 0) {
|
|
6349
|
+
push("pool", "pass", `${poolName} is valid (${pool.identities.join(", ")}).`, poolName);
|
|
6350
|
+
} else {
|
|
6351
|
+
push("pool", "fail", `Pool "${poolName}" names ${missing.map((member) => `identity "${member}"`).join(" and ")}, which ${missing.length === 1 ? "does" : "do"} not exist.`, poolName);
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
const selectionExists = (selector) => {
|
|
6355
|
+
const poolName = poolNameOf(selector);
|
|
6356
|
+
return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
|
|
6357
|
+
};
|
|
6358
|
+
for (const entry of params.providers) {
|
|
6359
|
+
pushProvider(push, entry);
|
|
6360
|
+
}
|
|
6361
|
+
if (params.platform !== "darwin" || params.run === void 0) {
|
|
6362
|
+
push("keychain", "pass", "Skipped (not macOS).");
|
|
6363
|
+
} else {
|
|
6364
|
+
for (const entry of params.identities) {
|
|
6365
|
+
const result = lookupKeychainService(params.run, entry.farmRoot);
|
|
6366
|
+
push("keychain", result.found ? "pass" : "warn", result.note, entry.name);
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
if (params.directoryRules.raw === void 0) {
|
|
6370
|
+
push("directory-rules", "pass", "No directory-rules.json configured.");
|
|
6371
|
+
} else {
|
|
6372
|
+
const validated = validateJson(DirectoryRulesSchema, params.directoryRules);
|
|
6373
|
+
if (!validated.ok) {
|
|
6374
|
+
push("directory-rules", "fail", validated.message);
|
|
6375
|
+
} else {
|
|
6376
|
+
push("directory-rules", "pass", `${params.directoryRules.path} is valid.`);
|
|
6377
|
+
for (const rule of validated.data.rules) {
|
|
6378
|
+
const badRefs = [];
|
|
6379
|
+
if (rule.identity !== void 0 && !selectionExists(rule.identity)) {
|
|
6380
|
+
badRefs.push(poolNameOf(rule.identity) === void 0 ? `identity "${rule.identity}"` : `pool "${poolNameOf(rule.identity) ?? ""}"`);
|
|
6381
|
+
}
|
|
6382
|
+
if (rule.configProfile !== void 0 && !validProfileNames.has(rule.configProfile)) {
|
|
6383
|
+
badRefs.push(`configuration profile "${rule.configProfile}"`);
|
|
6384
|
+
}
|
|
6385
|
+
if (badRefs.length > 0) {
|
|
6386
|
+
push("directory-rules", "fail", `Rule for "${rule.path}" names ${badRefs.join(" and ")}, which do not exist.`, rule.path);
|
|
6387
|
+
} else {
|
|
6388
|
+
push("directory-rules", "pass", `Rule for "${rule.path}" is valid.`, rule.path);
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
if (params.globalConfig.raw === void 0) {
|
|
6394
|
+
push("global-config", "pass", "No config.json configured.");
|
|
6395
|
+
} else {
|
|
6396
|
+
const validated = validateJson(GlobalConfigSchema, params.globalConfig);
|
|
6397
|
+
if (!validated.ok) {
|
|
6398
|
+
push("global-config", "fail", validated.message);
|
|
6399
|
+
} else {
|
|
6400
|
+
const defaultProfile = validated.data.defaultConfigProfile;
|
|
6401
|
+
if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
|
|
6402
|
+
push(
|
|
6403
|
+
"global-config",
|
|
6404
|
+
"fail",
|
|
6405
|
+
`config.json names configuration profile "${defaultProfile}" as its default, but no such profile exists.`
|
|
6406
|
+
);
|
|
6407
|
+
} else {
|
|
6408
|
+
push("global-config", "pass", `${params.globalConfig.path} is valid.`);
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
if (params.categoriesLocal.raw === void 0) {
|
|
6413
|
+
push("categories-local", "pass", "No categories.local.json configured.");
|
|
6414
|
+
} else {
|
|
6415
|
+
const validated = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
|
|
6416
|
+
push(
|
|
6417
|
+
"categories-local",
|
|
6418
|
+
validated.ok ? "pass" : "fail",
|
|
6419
|
+
validated.ok ? `${params.categoriesLocal.path} is valid.` : validated.message
|
|
6420
|
+
);
|
|
6421
|
+
}
|
|
6422
|
+
if (params.headroom.state.raw === void 0) {
|
|
6423
|
+
push("headroom", "pass", "No headroom daemon has ever run; nothing to check.");
|
|
6424
|
+
} else {
|
|
6425
|
+
const validated = validateJson(HeadroomStateSchema, params.headroom.state);
|
|
6426
|
+
if (!validated.ok) {
|
|
6427
|
+
push("headroom", "fail", validated.message);
|
|
6428
|
+
} else {
|
|
6429
|
+
const state = validated.data;
|
|
6430
|
+
const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
|
|
6431
|
+
const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
|
|
6432
|
+
if (state.supervisorPid === void 0) {
|
|
6433
|
+
if (state.lastError === void 0) {
|
|
6434
|
+
push("headroom", "pass", "Headroom daemon is stopped (idle shutdown) with no recorded error.");
|
|
6435
|
+
} else {
|
|
6436
|
+
push("headroom", "warn", `Headroom supervisor is not running; last error: ${state.lastError}`);
|
|
6437
|
+
}
|
|
6438
|
+
} else if (!supervisorAlive) {
|
|
6439
|
+
push(
|
|
6440
|
+
"headroom",
|
|
6441
|
+
"warn",
|
|
6442
|
+
`Headroom state names supervisor pid ${String(state.supervisorPid)}, which is not running; the next launch through headroom will start a replacement.`
|
|
6443
|
+
);
|
|
6444
|
+
} else if (state.headroomPid === void 0 || state.port === void 0) {
|
|
6445
|
+
push(
|
|
6446
|
+
"headroom",
|
|
6447
|
+
"warn",
|
|
6448
|
+
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}`
|
|
6449
|
+
);
|
|
6450
|
+
} else if (!headroomAlive) {
|
|
6451
|
+
push(
|
|
6452
|
+
"headroom",
|
|
6453
|
+
"warn",
|
|
6454
|
+
`Headroom state names daemon pid ${String(state.headroomPid)}, which is not running; its supervisor should be restarting it.`
|
|
6455
|
+
);
|
|
6456
|
+
} else {
|
|
6457
|
+
push(
|
|
6458
|
+
"headroom",
|
|
6459
|
+
"pass",
|
|
6460
|
+
`Headroom daemon is up on 127.0.0.1:${String(state.port)} (supervisor ${String(state.supervisorPid)}, daemon ${String(state.headroomPid)}).`
|
|
6461
|
+
);
|
|
6462
|
+
}
|
|
6463
|
+
if (state.lastError !== void 0 && supervisorAlive) {
|
|
6464
|
+
push("headroom", "warn", `Headroom recorded a previous error: ${state.lastError}`);
|
|
6465
|
+
}
|
|
6466
|
+
if (state.installedSource !== void 0 && isMovingGitSource(state.installedSource)) {
|
|
6467
|
+
push(
|
|
6468
|
+
"headroom",
|
|
6469
|
+
"warn",
|
|
6470
|
+
`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.`
|
|
6471
|
+
);
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
if (params.activeIdentity.raw === void 0) {
|
|
6476
|
+
push("active-identity", "pass", "No active identity set.");
|
|
6477
|
+
} else {
|
|
6478
|
+
const trimmed = params.activeIdentity.raw.trim();
|
|
6479
|
+
if (trimmed === "") {
|
|
6480
|
+
push("active-identity", "warn", "active-identity is present but empty \u2014 treated the same as unset.");
|
|
6481
|
+
} else if (!selectionExists(trimmed)) {
|
|
6482
|
+
push("active-identity", "fail", `active-identity names "${trimmed}", which does not exist.`);
|
|
6483
|
+
} else {
|
|
6484
|
+
push("active-identity", "pass", `Active identity "${trimmed}" is valid.`);
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
return { findings, ok: !findings.some((finding) => finding.severity === "fail") };
|
|
6488
|
+
}
|
|
6489
|
+
var SECTION_TITLES = {
|
|
6490
|
+
"ambient-credential": "Ambient-credential exposure",
|
|
6491
|
+
"binary-discovery": "Claude Code binary discovery",
|
|
6492
|
+
"claude-shim": "`claude` command shim",
|
|
6493
|
+
"path-resolution": "PATH resolution",
|
|
6494
|
+
"legacy-name": "Former `claude-use` name",
|
|
6495
|
+
"config-profile": "Configuration profiles",
|
|
6496
|
+
identity: "Identities",
|
|
6497
|
+
pool: "Pools",
|
|
6498
|
+
provider: "Providers",
|
|
6499
|
+
keychain: "macOS Keychain",
|
|
6500
|
+
"directory-rules": "Directory rules",
|
|
6501
|
+
"global-config": "Global config",
|
|
6502
|
+
headroom: "Headroom daemon",
|
|
6503
|
+
"categories-local": "categories.local.json",
|
|
6504
|
+
"active-identity": "Active identity"
|
|
6505
|
+
};
|
|
6506
|
+
var SECTION_ORDER = [
|
|
6507
|
+
"ambient-credential",
|
|
6508
|
+
"binary-discovery",
|
|
6509
|
+
"claude-shim",
|
|
6510
|
+
"path-resolution",
|
|
6511
|
+
"config-profile",
|
|
6512
|
+
"identity",
|
|
6513
|
+
"pool",
|
|
6514
|
+
"provider",
|
|
6515
|
+
"keychain",
|
|
6516
|
+
"directory-rules",
|
|
6517
|
+
"global-config",
|
|
6518
|
+
"headroom",
|
|
6519
|
+
"categories-local",
|
|
6520
|
+
"active-identity"
|
|
6521
|
+
];
|
|
6522
|
+
function severityPrefix(severity) {
|
|
6523
|
+
switch (severity) {
|
|
6524
|
+
case "pass":
|
|
6525
|
+
return "[PASS]";
|
|
6526
|
+
case "warn":
|
|
6527
|
+
return "[WARN]";
|
|
6528
|
+
case "fail":
|
|
6529
|
+
return "[FAIL]";
|
|
6530
|
+
default:
|
|
6531
|
+
return severity;
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
function formatDoctorReport(report) {
|
|
6535
|
+
const lines = [];
|
|
6536
|
+
for (const section of SECTION_ORDER) {
|
|
6537
|
+
const sectionFindings = report.findings.filter((finding) => finding.section === section);
|
|
6538
|
+
if (sectionFindings.length === 0) {
|
|
6539
|
+
continue;
|
|
6540
|
+
}
|
|
6541
|
+
lines.push("", `${SECTION_TITLES[section]}:`);
|
|
6542
|
+
for (const finding of sectionFindings) {
|
|
6543
|
+
const subject = finding.subject === void 0 ? "" : `${finding.subject}: `;
|
|
6544
|
+
lines.push(` ${severityPrefix(finding.severity)} ${subject}${finding.message}`);
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
const failCount = report.findings.filter((finding) => finding.severity === "fail").length;
|
|
6548
|
+
lines.push("", report.ok ? "All checks passed." : `${String(failCount)} check(s) failed.`);
|
|
6549
|
+
return lines;
|
|
6550
|
+
}
|
|
6551
|
+
function realpathOrSelf(target) {
|
|
6552
|
+
try {
|
|
6553
|
+
return fs9.realpathSync(target);
|
|
6554
|
+
} catch {
|
|
6555
|
+
return target;
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
function collectDoctorReport(params) {
|
|
6559
|
+
const { paths } = params;
|
|
6560
|
+
const identityNames = fs9.existsSync(paths.identitiesDir) ? fs9.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
|
|
6561
|
+
const identities = identityNames.map((name) => {
|
|
6562
|
+
const farmRoot = path22.join(paths.identitiesDir, name);
|
|
6563
|
+
const identityPath = path22.join(farmRoot, "identity.json");
|
|
6564
|
+
return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot };
|
|
6565
|
+
});
|
|
6566
|
+
const profileNames = fs9.existsSync(paths.configProfilesDir) ? fs9.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
|
|
6567
|
+
const configProfiles = profileNames.map((name) => {
|
|
6568
|
+
const profilePath = path22.join(paths.configProfilesDir, `${name}.json`);
|
|
6569
|
+
return { name, path: profilePath, raw: realFsPort.readFileUtf8(profilePath) };
|
|
6570
|
+
});
|
|
6571
|
+
const providerNames = fs9.existsSync(paths.providersDir) ? fs9.readdirSync(paths.providersDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
|
|
6572
|
+
const providers = providerNames.map((name) => {
|
|
6573
|
+
const providerPath = path22.join(paths.providersDir, `${name}.json`);
|
|
6574
|
+
return { name, path: providerPath, raw: realFsPort.readFileUtf8(providerPath) };
|
|
6575
|
+
});
|
|
6576
|
+
const ownExecutablePath = realOwnExecutablePath();
|
|
6577
|
+
let binaryDiscovery;
|
|
6578
|
+
try {
|
|
6579
|
+
const binary = realResolveClaudeBinary(resolveOwnInstallDirs(paths, ownExecutablePath))();
|
|
6580
|
+
binaryDiscovery = { ok: true, binary };
|
|
6581
|
+
} catch (error) {
|
|
6582
|
+
binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
6583
|
+
}
|
|
6584
|
+
const shimState = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
|
|
6585
|
+
const pathDirs = (params.env.PATH ?? "").split(path22.delimiter).filter((dir) => dir !== "");
|
|
6586
|
+
const claudeShimShadow = shimState === void 0 ? void 0 : refinePathShadow(
|
|
6587
|
+
findPathShadow({
|
|
6588
|
+
pathDirs,
|
|
6589
|
+
targetDir: path22.dirname(shimState.targetPath),
|
|
6590
|
+
targetFilename: path22.basename(shimState.targetPath),
|
|
6591
|
+
findExecutableInDir
|
|
6592
|
+
}),
|
|
6593
|
+
shimState.targetPath,
|
|
6594
|
+
realpathOrSelf
|
|
6595
|
+
);
|
|
6596
|
+
const report = runDoctor({
|
|
6597
|
+
env: params.env,
|
|
6598
|
+
identities,
|
|
6599
|
+
configProfiles,
|
|
6600
|
+
providers,
|
|
6601
|
+
directoryRules: { path: paths.directoryRulesFile, raw: realFsPort.readFileUtf8(paths.directoryRulesFile) },
|
|
6602
|
+
globalConfig: { path: paths.globalConfigFile, raw: realFsPort.readFileUtf8(paths.globalConfigFile) },
|
|
6603
|
+
categoriesLocal: { path: paths.categoriesLocalFile, raw: realFsPort.readFileUtf8(paths.categoriesLocalFile) },
|
|
6604
|
+
activeIdentity: { path: paths.activeIdentityFile, raw: realFsPort.readFileUtf8(paths.activeIdentityFile) },
|
|
6605
|
+
binaryDiscovery,
|
|
6606
|
+
claudeShim: { state: shimState, targetExists: shimState !== void 0 && fs9.existsSync(shimState.targetPath) },
|
|
6607
|
+
rootPath: paths.root,
|
|
6608
|
+
pathResolution: {
|
|
6609
|
+
ownExecutablePath,
|
|
6610
|
+
agentShim: refinePathShadow(
|
|
6611
|
+
findPathShadow({
|
|
6612
|
+
pathDirs,
|
|
6613
|
+
targetDir: path22.dirname(ownExecutablePath),
|
|
6614
|
+
targetFilename: commandFilename(ownExecutablePath, "agent-shim"),
|
|
6615
|
+
findExecutableInDir
|
|
6616
|
+
}),
|
|
6617
|
+
ownExecutablePath,
|
|
6618
|
+
realpathOrSelf
|
|
6619
|
+
),
|
|
6620
|
+
...claudeShimShadow === void 0 ? {} : { claude: claudeShimShadow }
|
|
6621
|
+
},
|
|
6622
|
+
run: realRunPort,
|
|
6623
|
+
platform: process.platform,
|
|
6624
|
+
headroom: {
|
|
6625
|
+
state: { path: paths.headroomStateFile, raw: realFsPort.readFileUtf8(paths.headroomStateFile) },
|
|
6626
|
+
isRunning: realIsProcessRunning
|
|
6627
|
+
}
|
|
6628
|
+
});
|
|
6629
|
+
return report;
|
|
6630
|
+
}
|
|
3716
6631
|
export {
|
|
3717
6632
|
AMBIENT_CREDENTIAL_VARS,
|
|
3718
6633
|
CONNECT_INTERCEPT_HOST,
|
|
@@ -3726,6 +6641,9 @@ export {
|
|
|
3726
6641
|
CredentialCacheSchema,
|
|
3727
6642
|
CredentialSchema,
|
|
3728
6643
|
CredentialSourceSchema,
|
|
6644
|
+
DirectoryRuleAlreadyExistsError,
|
|
6645
|
+
DirectoryRuleMissingTargetError,
|
|
6646
|
+
DirectoryRuleNotFoundError,
|
|
3729
6647
|
DirectoryRuleSchema,
|
|
3730
6648
|
DirectoryRulesSchema,
|
|
3731
6649
|
EXIT_FAILURE,
|
|
@@ -3735,48 +6653,108 @@ export {
|
|
|
3735
6653
|
FrontDoorStartError,
|
|
3736
6654
|
GlobalConfigSchema,
|
|
3737
6655
|
HeadroomStartError,
|
|
6656
|
+
IdentityAlreadyExistsError,
|
|
6657
|
+
IdentityNotFoundError,
|
|
3738
6658
|
IdentitySchema,
|
|
6659
|
+
InvalidCategoryNameError,
|
|
6660
|
+
InvalidIdentityNameError,
|
|
6661
|
+
InvalidProviderNameError,
|
|
6662
|
+
LegacyProviderFileError,
|
|
3739
6663
|
PoolNameSchema,
|
|
6664
|
+
PoolNotFoundError,
|
|
3740
6665
|
PoolSchema,
|
|
3741
6666
|
PortableConfigSchema,
|
|
6667
|
+
ProfileAlreadyExistsError,
|
|
6668
|
+
ProfileNotFoundError,
|
|
6669
|
+
ProviderAlreadyExistsError,
|
|
6670
|
+
ProviderKindMismatchError,
|
|
6671
|
+
ProviderNotFoundError,
|
|
3742
6672
|
ProviderSchema,
|
|
3743
6673
|
ROUTED_PATH_PREFIX,
|
|
3744
6674
|
UsageError,
|
|
3745
6675
|
UsageSnapshotError,
|
|
3746
6676
|
UsageSnapshotSchema,
|
|
3747
6677
|
WhenSchema,
|
|
6678
|
+
addDirectoryRule,
|
|
6679
|
+
addIdentity,
|
|
6680
|
+
addPool,
|
|
6681
|
+
addProvider,
|
|
3748
6682
|
buildEntryFacts,
|
|
3749
6683
|
buildLayoutPaths,
|
|
3750
6684
|
carryOver,
|
|
6685
|
+
checkReportHasWarnings,
|
|
6686
|
+
checkReportToJson,
|
|
6687
|
+
collectCheckReport,
|
|
6688
|
+
collectDoctorReport,
|
|
3751
6689
|
createLeafCache,
|
|
6690
|
+
createProfile,
|
|
6691
|
+
describeProviderEndpoint,
|
|
3752
6692
|
detectAmbientCredential,
|
|
3753
6693
|
ensureCa,
|
|
3754
6694
|
ensureFrontDoor,
|
|
3755
6695
|
ensureHeadroom,
|
|
3756
6696
|
evaluateAmbientCredentialGuard,
|
|
3757
6697
|
formatAmbientCredentialGuardMessage,
|
|
6698
|
+
formatCheckReport,
|
|
6699
|
+
formatDoctorReport,
|
|
3758
6700
|
forwardableHeaders,
|
|
3759
6701
|
generateCa,
|
|
6702
|
+
identityExists,
|
|
6703
|
+
isIdentityDirectoryName,
|
|
3760
6704
|
isInterceptedHost,
|
|
6705
|
+
listDirectoryRules,
|
|
6706
|
+
listIdentities,
|
|
6707
|
+
listProfiles,
|
|
6708
|
+
listProviders,
|
|
3761
6709
|
listUsageSnapshots,
|
|
3762
6710
|
mintLeaf,
|
|
3763
6711
|
parseConnectTarget,
|
|
6712
|
+
profileExists,
|
|
6713
|
+
providerExists,
|
|
6714
|
+
readActiveIdentity,
|
|
6715
|
+
readDirectoryRules,
|
|
3764
6716
|
readFarmManifest,
|
|
6717
|
+
readGlobalConfig,
|
|
6718
|
+
readIdentity,
|
|
6719
|
+
readPools,
|
|
6720
|
+
readProfile,
|
|
6721
|
+
readProvider,
|
|
3765
6722
|
readUsageSnapshot,
|
|
3766
6723
|
realConnectCertStore,
|
|
3767
6724
|
realConnectEffects,
|
|
3768
6725
|
recoverFarm,
|
|
3769
6726
|
recoveryDiagnostics,
|
|
6727
|
+
removeDirectoryRule,
|
|
6728
|
+
removeIdentity,
|
|
6729
|
+
removePool,
|
|
6730
|
+
removeProfile,
|
|
6731
|
+
removeProvider,
|
|
6732
|
+
requirePool,
|
|
3770
6733
|
resolveAgentShimHome,
|
|
3771
6734
|
resolveClaudeHome,
|
|
3772
6735
|
resolveDecisions,
|
|
3773
6736
|
resolveLayoutPaths,
|
|
3774
6737
|
resolveSupervisorConfig,
|
|
3775
6738
|
resyncFarm,
|
|
6739
|
+
runCheck,
|
|
6740
|
+
runDoctor,
|
|
3776
6741
|
runFrontDoorSupervisor,
|
|
3777
6742
|
runSupervisor,
|
|
3778
6743
|
servedByPipeline,
|
|
6744
|
+
setAllowAmbientCredential,
|
|
6745
|
+
setDefaultConfigProfile,
|
|
6746
|
+
setGlobalDefaultProfile,
|
|
6747
|
+
setIdentityCredential,
|
|
6748
|
+
setPool,
|
|
6749
|
+
setProfileCategories,
|
|
6750
|
+
setProfileEntries,
|
|
6751
|
+
setProfileLaunchFlags,
|
|
6752
|
+
setProfileMetadata,
|
|
3779
6753
|
snapshotPath,
|
|
3780
6754
|
startConnectServer,
|
|
3781
|
-
topLevelNames
|
|
6755
|
+
topLevelNames,
|
|
6756
|
+
updateDirectoryRule,
|
|
6757
|
+
updateProvider,
|
|
6758
|
+
useIdentity,
|
|
6759
|
+
writeDirectoryRules
|
|
3782
6760
|
};
|