agent-shim 8.6.0 → 8.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -63,8 +63,11 @@ __export(index_exports, {
63
63
  InvalidProviderNameError: () => InvalidProviderNameError,
64
64
  LaunchRefusedError: () => LaunchRefusedError,
65
65
  LegacyProviderFileError: () => LegacyProviderFileError,
66
+ PROMPT_CACHE_TTL_MS: () => PROMPT_CACHE_TTL_MS,
67
+ PlanClassSchema: () => PlanClassSchema,
66
68
  PoolNameSchema: () => PoolNameSchema,
67
69
  PoolNotFoundError: () => PoolNotFoundError,
70
+ PoolPickReportSchema: () => PoolPickReportSchema,
68
71
  PoolSchema: () => PoolSchema,
69
72
  PortableConfigSchema: () => PortableConfigSchema,
70
73
  ProfileAlreadyExistsError: () => ProfileAlreadyExistsError,
@@ -101,16 +104,19 @@ __export(index_exports, {
101
104
  checkReportToJson: () => checkReportToJson,
102
105
  collectCheckReport: () => collectCheckReport,
103
106
  collectDoctorReport: () => collectDoctorReport,
107
+ collectPoolPick: () => collectPoolPick,
104
108
  createLeafCache: () => createLeafCache,
105
109
  createProfile: () => createProfile,
106
110
  createRcControlHandler: () => createRcControlHandler,
107
111
  createRcSessionTracker: () => createRcSessionTracker,
108
112
  describeProviderEndpoint: () => describeProviderEndpoint,
109
113
  detectAmbientCredential: () => detectAmbientCredential,
114
+ effectiveWindow: () => effectiveWindow,
110
115
  ensureCa: () => ensureCa,
111
116
  ensureFrontDoor: () => ensureFrontDoor,
112
117
  ensureHeadroom: () => ensureHeadroom,
113
118
  evaluateAmbientCredentialGuard: () => evaluateAmbientCredentialGuard,
119
+ evaluateWhen: () => evaluateWhen,
114
120
  formatAmbientCredentialGuardMessage: () => formatAmbientCredentialGuardMessage,
115
121
  formatCheckReport: () => formatCheckReport,
116
122
  formatDoctorReport: () => formatDoctorReport,
@@ -128,13 +134,16 @@ __export(index_exports, {
128
134
  listProfiles: () => listProfiles,
129
135
  listProviders: () => listProviders,
130
136
  listUsageSnapshots: () => listUsageSnapshots,
137
+ matchBranch: () => matchBranch,
131
138
  mintLeaf: () => mintLeaf,
132
139
  observingRoutedRoute: () => observingRoutedRoute,
133
140
  parseConnectTarget: () => parseConnectTarget,
141
+ planOf: () => planOf,
134
142
  prepareClaudeLaunch: () => prepareClaudeLaunch,
135
143
  prepareLaunch: () => prepareLaunch,
136
144
  profileExists: () => profileExists,
137
145
  providerExists: () => providerExists,
146
+ rankPool: () => rankPool,
138
147
  rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
139
148
  rcObserverAsPassthrough: () => rcObserverAsPassthrough,
140
149
  readActiveIdentity: () => readActiveIdentity,
@@ -3515,25 +3524,19 @@ function realConnectEffects() {
3515
3524
  }
3516
3525
  };
3517
3526
  },
3518
- connectTlsUpstream: async (host, port, clientAlpn) => await new Promise((resolve, reject) => {
3519
- const dial = (localPort) => {
3520
- const raw = net2.connect({ host, port, localPort, lookup: realAddressLookup });
3521
- return tls.connect({ socket: raw, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
3522
- };
3523
- const settle = (candidate) => {
3524
- candidate.once("secureConnect", () => {
3525
- resolve(candidate);
3526
- });
3527
- candidate.once("error", (error) => {
3528
- if ("code" in error && error.code === "EADDRINUSE") {
3529
- settle(dial(upstreamLocalPort()));
3530
- return;
3531
- }
3532
- reject(error);
3527
+ connectTlsUpstream: async (host, port, clientAlpn) => {
3528
+ const dialled = await reservedDial({ host, port });
3529
+ if (dialled.error !== null) {
3530
+ throw dialled.error;
3531
+ }
3532
+ return await new Promise((resolve, reject) => {
3533
+ const secure = tls.connect({ socket: dialled.socket, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
3534
+ secure.once("secureConnect", () => {
3535
+ resolve(secure);
3533
3536
  });
3534
- };
3535
- settle(dial(upstreamLocalPort()));
3536
- }),
3537
+ secure.once("error", reject);
3538
+ });
3539
+ },
3537
3540
  connectTcp: async (host, port) => await new Promise((resolve, reject) => {
3538
3541
  const socket = net2.connect({ host, port });
3539
3542
  socket.once("connect", () => {
@@ -5095,547 +5098,380 @@ function listUsageSnapshots(fs12, snapshotsDir) {
5095
5098
  }).sort((left, right) => left.identity.localeCompare(right.identity));
5096
5099
  }
5097
5100
 
5098
- // src/headroom/ensure.ts
5099
- var HEADROOM_START_TIMEOUT_MS = 12e4;
5100
- var HEADROOM_LAUNCHER_POLL_MS = 250;
5101
- var HeadroomStartError = class extends CliError {
5102
- constructor(message) {
5103
- super(message);
5104
- this.name = "HeadroomStartError";
5105
- }
5106
- };
5107
- function ensureHeadroom(params) {
5108
- const { paths, ports } = params;
5109
- const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
5110
- let spawnedSupervisor = false;
5111
- let awaitingAllowlist = false;
5112
- for (; ; ) {
5113
- const state = readHeadroomState(ports.fs, paths.headroomStateFile);
5114
- if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
5115
- const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
5116
- if (daemonUp && state.socketPath !== void 0) {
5117
- const required = params.requiredAllowlistHash?.();
5118
- if (required === void 0 || state.allowlistHash === required) {
5119
- writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
5120
- return { socketPath: state.socketPath };
5121
- }
5122
- const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
5123
- if (holders.length > 0) {
5124
- throw new HeadroomStartError(
5125
- `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
5126
- );
5127
- }
5128
- awaitingAllowlist = true;
5129
- }
5130
- if (state.lastError !== void 0) {
5131
- throw new HeadroomStartError(
5132
- `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
5133
- );
5134
- }
5135
- } else if (!spawnedSupervisor) {
5136
- ports.fs.mkdirp(paths.headroomDir);
5137
- const created = ports.fs.writeFileExclusive(
5138
- paths.headroomLockFile,
5139
- `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
5140
- `
5141
- );
5142
- if (created) {
5143
- ports.spawnSupervisor(paths);
5144
- spawnedSupervisor = true;
5145
- } else {
5146
- const lock = readStartLock(ports.fs, paths.headroomLockFile);
5147
- if (lock === void 0 || !ports.isRunning(lock.pid)) {
5148
- ports.fs.removeRecursive(paths.headroomLockFile);
5149
- continue;
5150
- }
5151
- }
5152
- }
5153
- if (ports.now() >= deadline) {
5154
- const lastError = state?.lastError;
5155
- throw new HeadroomStartError(
5156
- `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
5157
- );
5158
- }
5159
- ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
5101
+ // src/usage/preflight.ts
5102
+ var MS_PER_SECOND2 = 1e3;
5103
+ var SECONDS_PER_MINUTE = 60;
5104
+ var MINUTES_PER_HOUR = 60;
5105
+ var HOURS_PER_DAY = 24;
5106
+ var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
5107
+ function formatAge(ms) {
5108
+ const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
5109
+ if (minutes < MINUTES_PER_HOUR) {
5110
+ return `${String(Math.max(minutes, 0))}m`;
5160
5111
  }
5112
+ const hours = Math.floor(minutes / MINUTES_PER_HOUR);
5113
+ return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
5161
5114
  }
5162
-
5163
- // src/headroom/settings.ts
5164
- var import_node_crypto8 = require("node:crypto");
5165
- function settingsOf(config) {
5115
+ function effectiveWindow(window, nowMs) {
5116
+ const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
5117
+ if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
5118
+ return { reset: true, utilization: 0 };
5119
+ }
5166
5120
  return {
5167
- ...config.mode === void 0 ? {} : { mode: config.mode },
5168
- ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
5169
- ...config.ccr === void 0 ? {} : { ccr: config.ccr },
5170
- ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
5171
- ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
5172
- ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
5121
+ reset: false,
5122
+ ...window.utilization === void 0 ? {} : { utilization: window.utilization },
5123
+ ...resetsAtMs === void 0 ? {} : { resetsAtMs },
5124
+ ...window.status === void 0 ? {} : { status: window.status }
5173
5125
  };
5174
5126
  }
5175
- function settingsArgs(settings) {
5176
- return [
5177
- ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
5178
- ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
5179
- ...settings.ccr === "lossless" ? ["--lossless"] : [],
5180
- ...settings.ccr === "none" ? ["--no-ccr"] : [],
5181
- ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
5182
- ...settings.readMaturation === true ? ["--read-maturation"] : []
5127
+ function quotaWarnings(snapshot, provider, nowMs) {
5128
+ const rateLimit = snapshot?.providers[provider]?.rateLimit;
5129
+ const unified = rateLimit?.unified;
5130
+ if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
5131
+ return [];
5132
+ }
5133
+ const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
5134
+ const windows = [
5135
+ { name: "five-hour", window: unified.fiveHour },
5136
+ { name: "seven-day", window: unified.sevenDay }
5183
5137
  ];
5184
- }
5185
- function settingsEnv(settings) {
5186
- return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
5187
- }
5188
- function hashSettings(settings) {
5189
- return (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
5138
+ return windows.flatMap(({ name, window }) => {
5139
+ const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
5140
+ if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
5141
+ return [];
5142
+ }
5143
+ const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
5144
+ const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
5145
+ return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
5146
+ });
5190
5147
  }
5191
5148
 
5192
- // src/headroom/socket.ts
5193
- var import_node_path11 = __toESM(require("node:path"), 1);
5194
- var GROUP_OR_OTHER_BITS = 63;
5195
- var OCTAL_RADIX = 8;
5196
- var MODE_DIGITS = 4;
5197
- var HEADROOM_SOCKET_SUFFIX = ".sock";
5198
- var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
5199
- var UNIX_SOCKET_PATH_MAX_BYTES = 103;
5200
- var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
5201
- function headroomSocketPath(paths, supervisorPid) {
5202
- return import_node_path11.default.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
5203
- }
5204
- function platformRefusal(platform) {
5205
- return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
5206
- }
5207
- function ownerOnlyRefusal(label, target, stat, expected, uid) {
5208
- if (stat === void 0) {
5209
- return `the headroom ${label} ${target} does not exist`;
5210
- }
5211
- if (stat.kind === "symlink") {
5212
- return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
5213
- }
5214
- if (stat.kind !== expected) {
5215
- return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
5149
+ // src/usage/plan.ts
5150
+ var import_zod6 = require("zod");
5151
+ var PlanClassSchema = import_zod6.z.union([
5152
+ import_zod6.z.strictObject({
5153
+ kind: import_zod6.z.literal("subscription"),
5154
+ /** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
5155
+ capacity: import_zod6.z.number().positive(),
5156
+ /** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
5157
+ recognised: import_zod6.z.boolean(),
5158
+ /** The tier string the classification rests on, when the account had one. */
5159
+ tier: import_zod6.z.string().optional()
5160
+ }),
5161
+ import_zod6.z.strictObject({
5162
+ kind: import_zod6.z.literal("pay-per-use"),
5163
+ tier: import_zod6.z.string().optional()
5164
+ })
5165
+ ]);
5166
+ var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
5167
+ var ZERO_TIER = /(?:^|_)zero$/;
5168
+ var BASE_TIER = /(?:^|_)(?:pro|free)$/;
5169
+ function planOf(account) {
5170
+ const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
5171
+ const tierField = tier === void 0 ? {} : { tier };
5172
+ if (tier === void 0) {
5173
+ return { kind: "subscription", capacity: 1, recognised: false };
5216
5174
  }
5217
- if (stat.uid !== uid) {
5218
- return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
5175
+ if (ZERO_TIER.test(tier)) {
5176
+ return { kind: "pay-per-use", ...tierField };
5219
5177
  }
5220
- if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
5221
- return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
5178
+ const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
5179
+ if (multiplier !== void 0) {
5180
+ return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
5222
5181
  }
5223
- return void 0;
5182
+ return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
5224
5183
  }
5225
- function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
5226
- const unsupported = platformRefusal(ports.platform);
5227
- if (unsupported !== void 0) {
5228
- return { refused: unsupported };
5229
- }
5230
- const socketPath = headroomSocketPath(paths, supervisorPid);
5231
- const length = Buffer.byteLength(socketPath, "utf8");
5232
- if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
5233
- return {
5234
- refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
5235
- };
5236
- }
5237
- if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
5238
- return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
5184
+
5185
+ // src/usage/rateLimit.ts
5186
+ var MS_PER_SECOND3 = 1e3;
5187
+ function epochSecondsToIso(value) {
5188
+ if (value === void 0) {
5189
+ return void 0;
5239
5190
  }
5240
- fs12.mkdirPrivate(paths.headroomSocketDir);
5241
- const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
5242
- return refusal === void 0 ? { socketPath } : { refused: refusal };
5191
+ const seconds = Number(value);
5192
+ return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
5243
5193
  }
5244
- function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
5245
- const removed = [];
5246
- for (const name of fs12.readdir(paths.headroomSocketDir)) {
5247
- const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
5248
- if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
5249
- continue;
5250
- }
5251
- fs12.removeRecursive(import_node_path11.default.join(paths.headroomSocketDir, name));
5252
- removed.push(name);
5194
+ function nonNegativeNumber(value) {
5195
+ if (value === void 0 || value.trim() === "") {
5196
+ return void 0;
5253
5197
  }
5254
- return removed;
5198
+ const parsed = Number(value);
5199
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
5255
5200
  }
5256
-
5257
- // src/headroom/source.ts
5258
- var import_zod6 = require("zod");
5259
- var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
5260
- var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
5261
- var DirectUrlSchema = import_zod6.z.object({ vcs_info: import_zod6.z.object({ commit_id: import_zod6.z.string() }) });
5262
- function isMovingGitSource(spec) {
5263
- const match = GIT_SPEC_RE.exec(spec);
5264
- if (match === null) {
5265
- return false;
5201
+ var UNIFIED_PREFIX = "anthropic-ratelimit-unified-";
5202
+ function unifiedWindow(headers, window) {
5203
+ const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
5204
+ const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
5205
+ const status = headers[`${UNIFIED_PREFIX}${window}-status`];
5206
+ if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
5207
+ return void 0;
5266
5208
  }
5267
- const ref = match[1];
5268
- return ref === void 0 || !COMMIT_SHA_RE.test(ref);
5269
- }
5270
- function pinnedGitCommit(spec) {
5271
- const ref = GIT_SPEC_RE.exec(spec)?.[1];
5272
- return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
5209
+ return { ...utilization === void 0 ? {} : { utilization }, ...resetsAt === void 0 ? {} : { resetsAt }, ...status === void 0 ? {} : { status } };
5273
5210
  }
5274
-
5275
- // src/headroom/supervisor.ts
5276
- function resolveSupervisorConfig(configured) {
5211
+ function parseUnifiedRateLimit(headers) {
5212
+ if (!Object.keys(headers).some((name) => name.startsWith(UNIFIED_PREFIX))) {
5213
+ return void 0;
5214
+ }
5215
+ const status = headers[`${UNIFIED_PREFIX}status`];
5216
+ const fiveHour = unifiedWindow(headers, "5h");
5217
+ const sevenDay = unifiedWindow(headers, "7d");
5218
+ const representativeClaim = headers[`${UNIFIED_PREFIX}representative-claim`];
5219
+ const resetAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}reset`]);
5220
+ const overageStatus = headers[`${UNIFIED_PREFIX}overage-status`];
5277
5221
  return {
5278
- source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
5279
- idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
5280
- settings: settingsOf(configured)
5222
+ ...status === void 0 ? {} : { status },
5223
+ ...fiveHour === void 0 ? {} : { fiveHour },
5224
+ ...sevenDay === void 0 ? {} : { sevenDay },
5225
+ ...representativeClaim === void 0 ? {} : { representativeClaim },
5226
+ ...resetAt === void 0 ? {} : { resetAt },
5227
+ ...overageStatus === void 0 ? {} : { overageStatus }
5281
5228
  };
5282
5229
  }
5283
- var HEADROOM_BACKOFF_BASE_MS = 500;
5284
- var HEADROOM_BACKOFF_CAP_MS = 3e4;
5285
- var HEADROOM_START_RETRY_BUDGET = 5;
5286
- var HEADROOM_READY_TIMEOUT_MS = 15e3;
5287
- var HEADROOM_POLL_MS = 1e3;
5288
- var MS_PER_MINUTE2 = 6e4;
5289
- function backoffForAttempt(attempt) {
5290
- return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
5230
+
5231
+ // src/usage/scan.ts
5232
+ var import_zod7 = require("zod");
5233
+ var AnthropicUsageSchema = import_zod7.z.looseObject({
5234
+ input_tokens: import_zod7.z.number().int().nonnegative().nullish(),
5235
+ output_tokens: import_zod7.z.number().int().nonnegative().nullish(),
5236
+ cache_creation_input_tokens: import_zod7.z.number().int().nonnegative().nullish(),
5237
+ cache_read_input_tokens: import_zod7.z.number().int().nonnegative().nullish()
5238
+ });
5239
+ var ErrorObjectSchema = import_zod7.z.looseObject({
5240
+ type: import_zod7.z.string().optional(),
5241
+ code: import_zod7.z.union([import_zod7.z.string(), import_zod7.z.number()]).optional(),
5242
+ message: import_zod7.z.string().optional()
5243
+ });
5244
+
5245
+ // src/usage/middleware.ts
5246
+ var ANTHROPIC_PROVIDER = "anthropic";
5247
+
5248
+ // src/usage/pick.ts
5249
+ var MS_PER_SECOND4 = 1e3;
5250
+ var MS_PER_HOUR = 36e5;
5251
+ var HOURS_PER_DAY2 = 24;
5252
+ var FIVE_HOURS = 5;
5253
+ var PROMPT_CACHE_TTL_HOURS = 1;
5254
+ var PERCENT = 100;
5255
+ var SEVEN_DAYS = 7;
5256
+ var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
5257
+ var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
5258
+ var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
5259
+ var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
5260
+ function unifiedOf(state) {
5261
+ return state?.rateLimit?.unified;
5291
5262
  }
5292
- function versionSatisfies(version, source) {
5293
- if (version === void 0) {
5294
- return false;
5295
- }
5296
- const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
5297
- if (specifier === null) {
5298
- return true;
5299
- }
5300
- const installed = parseVersion(version);
5301
- const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
5302
- if (installed === void 0 || wanted === void 0) {
5303
- return true;
5263
+ function limitBlockedUntil(state, nowMs) {
5264
+ const limit = state?.lastLimit;
5265
+ if (state === void 0 || limit === void 0) {
5266
+ return void 0;
5304
5267
  }
5305
- const cmp = compareVersions(installed, wanted);
5306
- switch (specifier[0]) {
5307
- case "==":
5308
- return cmp === 0;
5309
- case "!=":
5310
- return cmp !== 0;
5311
- case ">=":
5312
- return cmp >= 0;
5313
- case "<=":
5314
- return cmp <= 0;
5315
- case ">":
5316
- return cmp > 0;
5317
- case "<":
5318
- return cmp < 0;
5319
- case "~=":
5320
- return compareVersions(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
5321
- default:
5322
- return true;
5268
+ if (Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
5269
+ return void 0;
5323
5270
  }
5271
+ 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);
5272
+ return until !== void 0 && until > nowMs ? until : void 0;
5324
5273
  }
5325
- function parseVersion(input) {
5326
- const match = /(\d+)(\.\d+)*/.exec(input);
5327
- if (match === null) {
5274
+ function windowBlockedUntil(windows) {
5275
+ const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
5276
+ return resets.length === 0 ? void 0 : Math.max(...resets);
5277
+ }
5278
+ function fiveHourBurn(records) {
5279
+ const points = [];
5280
+ for (const record of records) {
5281
+ const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
5282
+ if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
5283
+ points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
5284
+ }
5285
+ }
5286
+ const latest = points.at(-1)?.resetsAt;
5287
+ const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
5288
+ const first = current.at(0);
5289
+ const last = current.at(-1);
5290
+ if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
5328
5291
  return void 0;
5329
5292
  }
5330
- return match[0].split(".").map((part) => Number.parseInt(part, 10));
5293
+ return (last.utilization - first.utilization) / (last.atMs - first.atMs);
5331
5294
  }
5332
- function compareVersions(a, b) {
5333
- for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
5334
- const left = a[index] ?? 0;
5335
- const right = b[index] ?? 0;
5336
- if (left !== right) {
5337
- return left - right;
5338
- }
5295
+ function percent(fraction) {
5296
+ return `${String(Math.round(fraction * PERCENT))}%`;
5297
+ }
5298
+ function describeWindow(label, window, effective, nowMs) {
5299
+ if (effective.reset) {
5300
+ return `${label} window has reset`;
5339
5301
  }
5340
- return 0;
5302
+ const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
5303
+ const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
5304
+ return `${label} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
5341
5305
  }
5342
- var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
5343
- async function runSupervisor(initialConfig, ports, options = {}) {
5344
- const { fs: fs12, paths } = ports;
5345
- let config = initialConfig;
5346
- const previousState = readHeadroomState(fs12, paths.headroomStateFile);
5347
- if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
5348
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
5349
- return 0;
5306
+ function capacityOf(plan) {
5307
+ return plan.kind === "subscription" ? plan.capacity : 1;
5308
+ }
5309
+ function assess(member, nowMs) {
5310
+ const plan = planOf(member.account);
5311
+ 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`];
5312
+ const base = { identity: member.identity, plan };
5313
+ if (member.readError !== void 0) {
5314
+ return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
5350
5315
  }
5351
- let installedSource = previousState?.installedSource;
5352
- const ownsState = () => {
5353
- const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
5354
- return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
5355
- };
5356
- const writeOwnState = (state) => {
5357
- if (!ownsState()) {
5358
- return false;
5359
- }
5360
- writeHeadroomState(fs12, paths.headroomStateFile, state);
5361
- return true;
5362
- };
5363
- const fail = (message) => {
5364
- if (writeOwnState({ lastError: message })) {
5365
- fs12.removeRecursive(paths.headroomLockFile);
5366
- }
5367
- ports.log(`agent-shim headroom supervisor: ${message}`);
5368
- return 1;
5369
- };
5370
- const ensureInstalled = () => {
5371
- const version2 = ports.headroomVersion();
5372
- const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
5373
- const pinned = pinnedGitCommit(config.source);
5374
- const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
5375
- if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
5376
- installedSource = config.source;
5377
- return true;
5378
- }
5379
- ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
5380
- const result = ports.install(config.source);
5381
- if (!result.ok) {
5382
- return false;
5316
+ const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
5317
+ const unified = unifiedOf(state);
5318
+ if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
5319
+ return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
5320
+ }
5321
+ const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
5322
+ const fiveHourWindow = unified.fiveHour;
5323
+ const sevenDayWindow = unified.sevenDay;
5324
+ const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
5325
+ const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
5326
+ const windowReasons = [
5327
+ ...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
5328
+ ...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
5329
+ ];
5330
+ const blockedUntil = Math.max(
5331
+ windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
5332
+ limitBlockedUntil(state, nowMs) ?? 0
5333
+ );
5334
+ if (blockedUntil > nowMs) {
5335
+ if (unified.overageStatus === "allowed") {
5336
+ return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
5383
5337
  }
5384
- installedSource = config.source;
5385
- return true;
5386
- };
5387
- const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
5388
- if (socket.refused !== void 0) {
5389
- return fail(socket.refused);
5338
+ return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
5390
5339
  }
5391
- const { socketPath } = socket;
5392
- pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
5393
- if (!ensureInstalled()) {
5394
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
5340
+ if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
5341
+ return { candidate: { ...base, class: "pay-per-use", reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"]] } };
5395
5342
  }
5396
- const version = ports.headroomVersion() ?? "unknown";
5397
- writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
5398
- ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
5399
- const orphanPid = previousState?.headroomPid;
5400
- if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
5401
- ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
5402
- ports.stopProcess(orphanPid);
5343
+ 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;
5344
+ if (basis?.window.utilization === void 0) {
5345
+ return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
5403
5346
  }
5404
- let headroomPid;
5405
- let runningHash;
5406
- let runningSettingsHash;
5407
- let consecutiveFailures = 0;
5408
- let idleSince;
5409
- let superseded = false;
5410
- let ticks = 0;
5411
- for (; ; ) {
5412
- if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
5413
- return HEADROOM_SUPERVISOR_STILL_RUNNING;
5347
+ const remaining = Math.max(0, 1 - basis.window.utilization);
5348
+ const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND4);
5349
+ const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
5350
+ const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
5351
+ const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
5352
+ const burn = fiveHourBurn(member.records);
5353
+ return {
5354
+ candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
5355
+ ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
5356
+ ...burn === void 0 ? {} : { burn }
5357
+ };
5358
+ }
5359
+ function rankPool(input) {
5360
+ const { members, nowMs } = input;
5361
+ const assessments = members.map((member) => assess(member, nowMs));
5362
+ const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
5363
+ const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
5364
+ const candidates = assessments.map((assessment) => {
5365
+ const { candidate, fiveHour, burn } = assessment;
5366
+ const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
5367
+ if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
5368
+ return { ...candidate, feasible: true };
5414
5369
  }
5415
- ticks += 1;
5416
- config = ports.readConfig();
5417
- const settingsHash = hashSettings(config.settings);
5418
- const allowlist = allowlistOf(ports);
5419
- const allowlistHash = hashAllowlist(allowlist);
5420
- pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
5421
- const ownsNow = ownsState();
5422
- if (!ownsNow && !superseded) {
5423
- ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
5370
+ const runsDryInMs = fiveHour.remaining / pace;
5371
+ if (runsDryInMs >= fiveHour.untilResetMs) {
5372
+ return { ...candidate, feasible: true };
5424
5373
  }
5425
- superseded = !ownsNow;
5426
- const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
5427
- if (headroomPid === void 0 || crashed) {
5428
- if (crashed) {
5429
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
5430
- headroomPid = void 0;
5431
- runningHash = void 0;
5432
- runningSettingsHash = void 0;
5433
- writeOwnState({
5434
- supervisorPid: ports.ownPid,
5435
- version,
5436
- installedSource
5437
- });
5438
- }
5439
- if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
5440
- return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
5441
- }
5442
- if (installedSource !== config.source && !ensureInstalled()) {
5443
- return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
5444
- }
5445
- const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
5446
- const ready = await waitUntilReady(ports, socketPath);
5447
- if (ready) {
5448
- consecutiveFailures = 0;
5449
- headroomPid = pid;
5450
- runningHash = allowlistHash;
5451
- runningSettingsHash = settingsHash;
5452
- const recorded = writeOwnState({
5453
- supervisorPid: ports.ownPid,
5454
- headroomPid: pid,
5455
- socketPath,
5456
- version,
5457
- allowlistHash,
5458
- settingsHash,
5459
- installedSource: config.source
5460
- });
5461
- if (recorded) {
5462
- fs12.removeRecursive(paths.headroomLockFile);
5463
- }
5464
- idleSince = void 0;
5465
- ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
5466
- } else {
5467
- ports.stopProcess(pid);
5468
- consecutiveFailures += 1;
5469
- ports.log(
5470
- `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
5471
- );
5472
- await ports.sleep(backoffForAttempt(consecutiveFailures));
5473
- continue;
5474
- }
5475
- } else {
5476
- if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
5477
- if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
5478
- ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
5479
- ports.stopProcess(headroomPid);
5480
- headroomPid = void 0;
5481
- runningHash = void 0;
5482
- runningSettingsHash = void 0;
5483
- continue;
5484
- }
5485
- }
5486
- const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
5487
- if (sessions.length === 0) {
5488
- idleSince ??= ports.now();
5489
- const idleMs = ports.now() - idleSince;
5490
- if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
5491
- ports.log(
5492
- `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
5493
- );
5494
- ports.stopProcess(headroomPid);
5495
- writeOwnState({ version });
5496
- return 0;
5497
- }
5498
- } else {
5499
- idleSince = void 0;
5500
- }
5501
- }
5502
- await ports.sleep(HEADROOM_POLL_MS);
5503
- }
5504
- }
5505
- function currentHeadroomAllowlist(fs12, paths) {
5506
- const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
5507
- return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
5508
- }
5509
- function allowlistOf(ports) {
5510
- return currentHeadroomAllowlist(ports.fs, ports.paths);
5511
- }
5512
- async function waitUntilReady(ports, socketPath) {
5513
- const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
5514
- for (; ; ) {
5515
- if (await ports.ready(socketPath)) {
5516
- return true;
5517
- }
5518
- if (ports.now() >= deadline) {
5519
- return false;
5520
- }
5521
- await ports.sleep(HEADROOM_POLL_MS);
5522
- }
5523
- }
5524
-
5525
- // src/launcher/guard.ts
5526
- var AMBIENT_CREDENTIAL_VARS = [
5527
- "ANTHROPIC_API_KEY",
5528
- "ANTHROPIC_AUTH_TOKEN",
5529
- "CLAUDE_CODE_OAUTH_TOKEN",
5530
- "CLAUDE_CODE_USE_BEDROCK",
5531
- "CLAUDE_CODE_USE_VERTEX",
5532
- "CLAUDE_CODE_USE_FOUNDRY"
5533
- ];
5534
- function detectAmbientCredential(env, injected) {
5535
- for (const variable of AMBIENT_CREDENTIAL_VARS) {
5536
- const value = env[variable];
5537
- if (value === void 0 || value === "") {
5538
- continue;
5539
- }
5540
- if (injected?.variable === variable && value === injected.token) {
5541
- continue;
5374
+ return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
5375
+ });
5376
+ const ordered = [...candidates].sort(
5377
+ (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)
5378
+ );
5379
+ const sticky = input.sticky;
5380
+ const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
5381
+ const stickyCandidate = ordered[stickyIndex];
5382
+ if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
5383
+ const ageMs = nowMs - Date.parse(sticky.at);
5384
+ const warm = ageMs <= PROMPT_CACHE_TTL_MS;
5385
+ if (input.resuming || warm) {
5386
+ const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
5387
+ ordered.splice(stickyIndex, 1);
5388
+ ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
5542
5389
  }
5543
- return { variable };
5544
- }
5545
- return void 0;
5546
- }
5547
- function formatAmbientCredentialGuardMessage(variable, identityName) {
5548
- const identitySetCommand = identityName === void 0 ? "agent-shim identity set <name> --allow-ambient-credential" : `agent-shim identity set ${identityName} --allow-ambient-credential`;
5549
- return [
5550
- `error: ${variable} is set in the environment. This identity's isolated`,
5551
- "credential would be bypassed \u2014 every identity authenticates as this same key",
5552
- "while it's set. Unset it, or if this is deliberate, opt in per-launch with",
5553
- "AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1, or persistently for this identity with",
5554
- `\`${identitySetCommand}\`.`
5555
- ].join("\n");
5556
- }
5557
- function evaluateAmbientCredentialGuard(params) {
5558
- const detected = detectAmbientCredential(params.env, params.injectedCredential);
5559
- if (detected === void 0) {
5560
- return { ok: true };
5561
- }
5562
- if (params.allowAmbientCredentialOverride || params.allowAmbientCredential || params.providerSelected === true) {
5563
- return { ok: true };
5564
5390
  }
5391
+ const pick = ordered.find((candidate) => candidate.class !== "ineligible");
5392
+ const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
5393
+ const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
5565
5394
  return {
5566
- ok: false,
5567
- variable: detected.variable,
5568
- message: formatAmbientCredentialGuardMessage(detected.variable, params.identityName)
5395
+ candidates: ordered,
5396
+ ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
5569
5397
  };
5570
5398
  }
5571
5399
 
5572
- // src/paths.ts
5573
- var import_node_fs2 = __toESM(require("node:fs"), 1);
5574
- var import_node_os = __toESM(require("node:os"), 1);
5575
- var import_node_path12 = __toESM(require("node:path"), 1);
5576
- function resolveAgentShimHome(env = process.env, home = import_node_os.default.homedir(), isDirectory = isExistingDirectory) {
5577
- const fromEnv = env.AGENT_SHIM_HOME;
5578
- if (fromEnv !== void 0 && fromEnv !== "") {
5579
- return fromEnv;
5580
- }
5581
- const current = import_node_path12.default.join(home, HOME_DIRNAME);
5582
- const legacy = import_node_path12.default.join(home, LEGACY_HOME_DIRNAME);
5583
- return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
5400
+ // src/usage/poolPick.ts
5401
+ var import_node_path11 = __toESM(require("node:path"), 1);
5402
+ var import_zod8 = require("zod");
5403
+
5404
+ // src/usage/store.ts
5405
+ var DAY_MS2 = 864e5;
5406
+ var DAYS_IN_WEEK = 7;
5407
+ var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
5408
+
5409
+ // src/usage/poolPick.ts
5410
+ var SCHEMA_VERSION = 1;
5411
+ var PickEntrySchema = import_zod8.z.strictObject({ identity: import_zod8.z.string().min(1), at: import_zod8.z.iso.datetime() });
5412
+ var PicksFileSchema = import_zod8.z.strictObject({
5413
+ schemaVersion: import_zod8.z.literal(SCHEMA_VERSION),
5414
+ picks: import_zod8.z.record(import_zod8.z.string().min(1), PickEntrySchema)
5415
+ });
5416
+ function errorMessage(error) {
5417
+ return error instanceof Error ? error.message : String(error);
5584
5418
  }
5585
- function isExistingDirectory(candidate) {
5586
- return import_node_fs2.default.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
5419
+ function loadPoolMembers(fs12, paths, identities, nowMs) {
5420
+ const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
5421
+ const log = readUsageLog(fs12, paths.usageLogDir, { sinceMs: windowStartMs });
5422
+ return identities.map((identity) => {
5423
+ const records = log.records.filter((record) => record.identity === identity && record.provider === ANTHROPIC_PROVIDER && Date.parse(record.at) >= windowStartMs);
5424
+ try {
5425
+ const snapshot = readUsageSnapshot(fs12, paths.usageSnapshotsDir, identity);
5426
+ const account = readAccountMetadata(fs12, paths.identitiesDir, identity) ?? snapshot?.account;
5427
+ return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
5428
+ } catch (error) {
5429
+ if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
5430
+ return { identity, records, readError: errorMessage(error) };
5431
+ }
5432
+ throw error;
5433
+ }
5434
+ });
5587
5435
  }
5588
- function resolveClaudeHome() {
5589
- const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
5590
- if (fromEnv !== void 0 && fromEnv !== "") {
5591
- return fromEnv;
5436
+ function rankPoolFromStore(params) {
5437
+ return rankPool({
5438
+ members: loadPoolMembers(params.fs, params.paths, params.identities, params.nowMs),
5439
+ nowMs: params.nowMs,
5440
+ resuming: params.resuming,
5441
+ ...params.sticky === void 0 ? {} : { sticky: params.sticky }
5442
+ });
5443
+ }
5444
+ function readPicksFile(fs12, file) {
5445
+ const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
5446
+ const raw = fs12.readFileUtf8(file);
5447
+ if (raw === void 0) {
5448
+ return { file: empty };
5449
+ }
5450
+ let parsed;
5451
+ try {
5452
+ parsed = JSON.parse(raw);
5453
+ } catch {
5454
+ return { file: empty, problem: `${file} is not valid JSON` };
5592
5455
  }
5593
- return import_node_path12.default.join(import_node_os.default.homedir(), ".claude");
5456
+ const result = PicksFileSchema.safeParse(parsed);
5457
+ return result.success ? { file: result.data } : { file: empty, problem: `${file} is not a picks file this agent-shim can read` };
5594
5458
  }
5595
- function buildLayoutPaths(root) {
5596
- return {
5597
- root,
5598
- identitiesDir: import_node_path12.default.join(root, "identities"),
5599
- configProfilesDir: import_node_path12.default.join(root, "config-profiles"),
5600
- providersDir: import_node_path12.default.join(root, "providers"),
5601
- directoryRulesFile: import_node_path12.default.join(root, "directory-rules.json"),
5602
- activeIdentityFile: import_node_path12.default.join(root, "active-identity"),
5603
- globalConfigFile: import_node_path12.default.join(root, "config.json"),
5604
- categoriesLocalFile: import_node_path12.default.join(root, "categories.local.json"),
5605
- claudeShimFile: import_node_path12.default.join(root, "claude-shim.json"),
5606
- headroomDir: import_node_path12.default.join(root, "headroom"),
5607
- headroomStateFile: import_node_path12.default.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
5608
- headroomSocketDir: import_node_path12.default.join(root, "headroom", "run"),
5609
- headroomLockFile: import_node_path12.default.join(root, "headroom", "start.lock"),
5610
- headroomSessionsDir: import_node_path12.default.join(root, "headroom", "sessions"),
5611
- logsDir: import_node_path12.default.join(root, "logs"),
5612
- headroomLogPath: import_node_path12.default.join(root, "logs", "headroom.log"),
5613
- frontdoorDir: import_node_path12.default.join(root, "frontdoor"),
5614
- frontdoorCaDir: import_node_path12.default.join(root, "frontdoor", "ca"),
5615
- frontdoorCaCertFile: import_node_path12.default.join(root, "frontdoor", "ca", "ca.pem"),
5616
- frontdoorCaKeyFile: import_node_path12.default.join(root, "frontdoor", "ca", "ca.key"),
5617
- frontdoorCaBundlesDir: import_node_path12.default.join(root, "frontdoor", "ca", "bundles"),
5618
- frontdoorStateFile: import_node_path12.default.join(root, "frontdoor", "state.json"),
5619
- frontdoorLockFile: import_node_path12.default.join(root, "frontdoor", "start.lock"),
5620
- frontdoorControlTokenFile: import_node_path12.default.join(root, "frontdoor", "control-token"),
5621
- frontdoorSessionsDir: import_node_path12.default.join(root, "frontdoor", "sessions"),
5622
- frontdoorLogPath: import_node_path12.default.join(root, "logs", "frontdoor.log"),
5623
- usageDir: import_node_path12.default.join(root, "usage"),
5624
- usageLogDir: import_node_path12.default.join(root, "usage", "log"),
5625
- usageSnapshotsDir: import_node_path12.default.join(root, "usage", "snapshots"),
5626
- usagePicksFile: import_node_path12.default.join(root, "usage", "picks.json")
5627
- };
5459
+ function readStickyPick(fs12, picksFile, directory) {
5460
+ const read = readPicksFile(fs12, picksFile);
5461
+ const entry = read.file.picks[directory];
5462
+ return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
5628
5463
  }
5629
- function resolveLayoutPaths() {
5630
- return buildLayoutPaths(resolveAgentShimHome());
5464
+ function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
5465
+ const current = readPicksFile(fs12, picksFile).file.picks;
5466
+ const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
5467
+ const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
5468
+ fs12.mkdirPrivate(import_node_path11.default.dirname(picksFile));
5469
+ fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
5470
+ `);
5631
5471
  }
5632
5472
 
5633
- // src/identityStore.ts
5634
- var import_node_fs4 = __toESM(require("node:fs"), 1);
5635
- var import_node_path15 = __toESM(require("node:path"), 1);
5636
-
5637
5473
  // src/launcher/identity.ts
5638
- var import_node_path13 = __toESM(require("node:path"), 1);
5474
+ var import_node_path12 = __toESM(require("node:path"), 1);
5639
5475
  function poolNameOf(selector) {
5640
5476
  return selector.startsWith(POOL_SELECTOR_PREFIX) ? selector.slice(POOL_SELECTOR_PREFIX.length) : void 0;
5641
5477
  }
@@ -5684,1445 +5520,1775 @@ function decideConfigProfile(params) {
5684
5520
  return { source: "none" };
5685
5521
  }
5686
5522
  function loadIdentity(identitiesDir, name, fs12) {
5687
- return loadConfigFile(import_node_path13.default.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
5523
+ return loadConfigFile(import_node_path12.default.join(identitiesDir, name, "identity.json"), IdentitySchema, fs12.readConfigFile);
5688
5524
  }
5689
5525
 
5690
- // src/configProfilesStore.ts
5691
- var import_node_fs3 = __toESM(require("node:fs"), 1);
5692
- var import_node_path14 = __toESM(require("node:path"), 1);
5693
- var ProfileNotFoundError = class extends CliError {
5694
- constructor(profileName) {
5695
- super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
5696
- this.profileName = profileName;
5697
- this.name = "ProfileNotFoundError";
5698
- }
5699
- profileName;
5700
- };
5701
- var ProfileAlreadyExistsError = class extends CliError {
5702
- constructor(profileName) {
5703
- super(`A configuration profile named "${profileName}" already exists.`);
5704
- this.profileName = profileName;
5705
- this.name = "ProfileAlreadyExistsError";
5706
- }
5707
- profileName;
5708
- };
5709
- var InvalidCategoryNameError = class extends CliError {
5710
- constructor(categoryName) {
5711
- super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
5712
- this.categoryName = categoryName;
5713
- this.name = "InvalidCategoryNameError";
5714
- }
5715
- categoryName;
5716
- };
5717
- function profileJsonPath(paths, name) {
5718
- return import_node_path14.default.join(paths.configProfilesDir, `${name}.json`);
5526
+ // src/launcher/pool.ts
5527
+ var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
5528
+ var TERMINATOR = "--";
5529
+ var REASONS_LOGGED = 3;
5530
+ function splitMembers(pool, paths, fs12) {
5531
+ const exists = (name) => loadIdentity(paths.identitiesDir, name, fs12) !== void 0;
5532
+ return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
5719
5533
  }
5720
- function profileExists(paths, name) {
5721
- return import_node_fs3.default.existsSync(profileJsonPath(paths, name));
5534
+ function isResuming(passthrough) {
5535
+ const end = passthrough.indexOf(TERMINATOR);
5536
+ return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
5722
5537
  }
5723
- function requireProfileExists(paths, name) {
5724
- if (!profileExists(paths, name)) {
5725
- throw new ProfileNotFoundError(name);
5538
+ function resolvePoolLaunch(params) {
5539
+ const { poolName, log } = params;
5540
+ const pool = params.pools?.[poolName];
5541
+ if (pool === void 0) {
5542
+ return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
5726
5543
  }
5727
- }
5728
- function readProfile(paths, name) {
5729
- return readJson(profileJsonPath(paths, name), ConfigProfileSchema);
5730
- }
5731
- function createProfile(paths, name, extendsList, description) {
5732
- if (profileExists(paths, name)) {
5733
- throw new ProfileAlreadyExistsError(name);
5544
+ const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
5545
+ for (const name of missing) {
5546
+ log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
5734
5547
  }
5735
- const filePath = profileJsonPath(paths, name);
5736
- const parsed = ConfigProfileSchema.safeParse({
5737
- ...description === void 0 ? {} : { description },
5738
- ...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
5739
- });
5740
- if (!parsed.success) {
5741
- throw new ConfigValidationError(filePath, parsed.error.issues);
5548
+ if (identities.length === 0) {
5549
+ return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
5742
5550
  }
5743
- writeJsonAtomic(filePath, parsed.data);
5744
- return parsed.data;
5745
- }
5746
- function listProfiles(paths) {
5747
- if (!import_node_fs3.default.existsSync(paths.configProfilesDir)) {
5748
- return [];
5551
+ const resuming = isResuming(params.passthrough);
5552
+ const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
5553
+ if (stickyRead.problem !== void 0) {
5554
+ log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
5749
5555
  }
5750
- const names = import_node_fs3.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
5751
- const result = [];
5752
- for (const name of names) {
5753
- const profile = readProfile(paths, name);
5754
- if (profile !== void 0) {
5755
- result.push({ name, profile });
5556
+ for (; ; ) {
5557
+ const nowMs = params.now();
5558
+ const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
5559
+ const { pick, earliestReturn } = ranking;
5560
+ if (pick !== void 0) {
5561
+ recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
5562
+ return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
5756
5563
  }
5757
- }
5758
- return result;
5759
- }
5760
- function globalConfigPath(paths) {
5761
- return paths.globalConfigFile;
5762
- }
5763
- function readGlobalConfig(paths) {
5764
- return readJson(globalConfigPath(paths), GlobalConfigSchema);
5765
- }
5766
- function setGlobalDefaultProfile(paths, name) {
5767
- return applyPatch(
5768
- globalConfigPath(paths),
5769
- GlobalConfigSchema,
5770
- { defaultConfigProfile: name },
5771
- { defaults: {} }
5772
- );
5773
- }
5774
- function validateCategoryNames(patch) {
5775
- for (const key of Object.keys(patch)) {
5776
- if (!isOverridableCategory(key)) {
5777
- throw new InvalidCategoryNameError(key);
5564
+ if (earliestReturn === void 0) {
5565
+ return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
5778
5566
  }
5567
+ const until = new Date(earliestReturn.atMs).toISOString();
5568
+ if (!params.wait) {
5569
+ return {
5570
+ ok: false,
5571
+ message: `agent-shim: every member of pool "${poolName}" is refused right now; the earliest, ${earliestReturn.identity}, returns at ${until} (in ${formatAge(earliestReturn.atMs - nowMs)}). Pass --wait to sleep until then.`
5572
+ };
5573
+ }
5574
+ log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
5575
+ params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
5779
5576
  }
5780
5577
  }
5781
- function setProfileCategories(paths, name, patch) {
5782
- requireProfileExists(paths, name);
5783
- const expandedPatch = expandAllCategoryKey(patch);
5784
- validateCategoryNames(expandedPatch);
5785
- const existing = readProfile(paths, name) ?? {};
5786
- const mergedCategories = { ...existing.categories, ...expandedPatch };
5787
- return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { categories: mergedCategories });
5788
- }
5789
- function setProfileEntries(paths, name, patch) {
5790
- requireProfileExists(paths, name);
5791
- const existing = readProfile(paths, name) ?? {};
5792
- const mergedEntries = { ...existing.entries, ...patch };
5793
- return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
5794
- }
5795
- function setProfileLaunchFlags(paths, name, patch) {
5796
- requireProfileExists(paths, name);
5797
- const existing = readProfile(paths, name) ?? {};
5798
- const mergedLaunch = { ...existing.launch, ...patch };
5799
- return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
5800
- // Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
5801
- launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
5802
- });
5578
+
5579
+ // src/poolPickReport.ts
5580
+ function candidateView(candidate) {
5581
+ return {
5582
+ identity: candidate.identity,
5583
+ class: candidate.class,
5584
+ ...candidate.score === void 0 ? {} : { score: candidate.score },
5585
+ feasible: candidate.feasible,
5586
+ ...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
5587
+ plan: candidate.plan,
5588
+ reasons: [...candidate.reasons]
5589
+ };
5803
5590
  }
5804
- function setProfileMetadata(paths, name, patch) {
5805
- requireProfileExists(paths, name);
5806
- return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
5807
- ...patch.extends === void 0 ? {} : { extends: patch.extends.length === 0 ? void 0 : [...patch.extends] },
5808
- ...patch.description === void 0 ? {} : { description: patch.description === false ? void 0 : patch.description }
5591
+ function collectPoolPick(params) {
5592
+ const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
5593
+ const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
5594
+ const ranking = rankPoolFromStore({
5595
+ fs: params.usageFs,
5596
+ paths: params.paths,
5597
+ identities: present,
5598
+ nowMs: params.nowMs,
5599
+ resuming: false,
5600
+ ...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
5809
5601
  });
5810
- }
5811
- function removeProfile(paths, name) {
5812
- requireProfileExists(paths, name);
5813
- import_node_fs3.default.rmSync(profileJsonPath(paths, name));
5602
+ return {
5603
+ pool: params.poolName,
5604
+ directory: params.directory,
5605
+ ...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
5606
+ candidates: ranking.candidates.map(candidateView),
5607
+ missing: [...missing],
5608
+ ...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
5609
+ ...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
5610
+ };
5814
5611
  }
5815
5612
 
5816
- // src/poolStore.ts
5817
- var PoolNotFoundError = class extends CliError {
5818
- constructor(poolName) {
5819
- super(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
5820
- this.poolName = poolName;
5821
- this.name = "PoolNotFoundError";
5822
- }
5823
- poolName;
5824
- };
5825
- var PoolAlreadyExistsError = class extends CliError {
5826
- constructor(poolName) {
5827
- super(`A pool named "${poolName}" already exists. Use \`agent-shim pool set ${poolName}\` to change its members.`);
5828
- this.poolName = poolName;
5829
- this.name = "PoolAlreadyExistsError";
5830
- }
5831
- poolName;
5832
- };
5833
- var InvalidPoolNameError = class extends CliError {
5834
- constructor(attemptedName) {
5835
- 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.`);
5836
- this.attemptedName = attemptedName;
5837
- this.name = "InvalidPoolNameError";
5838
- }
5839
- attemptedName;
5840
- };
5841
- function readPools(paths) {
5842
- return readGlobalConfig(paths)?.pools ?? {};
5843
- }
5844
- function requirePool(paths, name) {
5845
- const pool = readPools(paths)[name];
5846
- if (pool === void 0) {
5847
- throw new PoolNotFoundError(name);
5848
- }
5849
- return pool;
5850
- }
5851
- function writePools(paths, pools) {
5852
- applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
5853
- }
5854
- function addPool(paths, name, identities) {
5855
- if (!PoolNameSchema.safeParse(name).success) {
5856
- throw new InvalidPoolNameError(name);
5857
- }
5858
- const pools = readPools(paths);
5859
- if (pools[name] !== void 0) {
5860
- throw new PoolAlreadyExistsError(name);
5861
- }
5862
- const pool = { identities: [...identities] };
5863
- writePools(paths, { ...pools, [name]: pool });
5864
- return pool;
5865
- }
5866
- function setPool(paths, name, identities) {
5867
- requirePool(paths, name);
5868
- const pool = { identities: [...identities] };
5869
- writePools(paths, { ...readPools(paths), [name]: pool });
5870
- return pool;
5871
- }
5872
- function removePool(paths, name) {
5873
- requirePool(paths, name);
5874
- const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
5875
- writePools(paths, remaining);
5876
- }
5613
+ // src/usage/pickReportSchema.ts
5614
+ var import_zod9 = require("zod");
5615
+ var PoolPickCandidateViewSchema = import_zod9.z.strictObject({
5616
+ identity: import_zod9.z.string(),
5617
+ /** In pick order: `scored` has usable quota data, `unknown` has none, `pay-per-use` bills by use, `ineligible` is refused right now. */
5618
+ class: import_zod9.z.enum(["scored", "unknown", "pay-per-use", "ineligible"]),
5619
+ /** Plan-size-weighted remaining quota per hour until its reset; present for `scored` only. */
5620
+ score: import_zod9.z.number().optional(),
5621
+ /** False when the five-hour window would run dry, at the observed pace, before it resets. */
5622
+ feasible: import_zod9.z.boolean(),
5623
+ /** When an `ineligible` member can be used again, as an ISO instant. */
5624
+ blockedUntil: import_zod9.z.iso.datetime().optional(),
5625
+ plan: PlanClassSchema,
5626
+ /** The facts behind the class and score, in the order they matter. */
5627
+ reasons: import_zod9.z.array(import_zod9.z.string())
5628
+ });
5629
+ var PoolPickReportSchema = import_zod9.z.strictObject({
5630
+ pool: import_zod9.z.string(),
5631
+ directory: import_zod9.z.string(),
5632
+ /** The member a launch would run as, absent when every member is refused. */
5633
+ pick: import_zod9.z.string().optional(),
5634
+ candidates: import_zod9.z.array(PoolPickCandidateViewSchema),
5635
+ /** Members that name an identity that does not exist; a launch skips them. */
5636
+ missing: import_zod9.z.array(import_zod9.z.string()),
5637
+ /** When nothing can be picked, the soonest any member returns. */
5638
+ earliestReturn: import_zod9.z.strictObject({ identity: import_zod9.z.string(), at: import_zod9.z.iso.datetime() }).optional(),
5639
+ /** Why the last-pick record could not be read, when that is the case. */
5640
+ stickyProblem: import_zod9.z.string().optional()
5641
+ });
5877
5642
 
5878
- // src/identityStore.ts
5879
- var IdentityNotFoundError = class extends CliError {
5880
- constructor(name) {
5881
- super(`No identity named "${name}". Run \`agent-shim identity add ${name}\` first.`);
5882
- this.name = name;
5883
- this.name = "IdentityNotFoundError";
5643
+ // src/headroom/ensure.ts
5644
+ var HEADROOM_START_TIMEOUT_MS = 12e4;
5645
+ var HEADROOM_LAUNCHER_POLL_MS = 250;
5646
+ var HeadroomStartError = class extends CliError {
5647
+ constructor(message) {
5648
+ super(message);
5649
+ this.name = "HeadroomStartError";
5884
5650
  }
5885
- name;
5886
5651
  };
5887
- var IdentityAlreadyExistsError = class extends CliError {
5888
- constructor(identityName) {
5889
- super(`An identity named "${identityName}" already exists.`);
5890
- this.identityName = identityName;
5891
- this.name = "IdentityAlreadyExistsError";
5652
+ function ensureHeadroom(params) {
5653
+ const { paths, ports } = params;
5654
+ const deadline = ports.now() + HEADROOM_START_TIMEOUT_MS;
5655
+ let spawnedSupervisor = false;
5656
+ let awaitingAllowlist = false;
5657
+ for (; ; ) {
5658
+ const state = readHeadroomState(ports.fs, paths.headroomStateFile);
5659
+ if (state?.supervisorPid !== void 0 && ports.isRunning(state.supervisorPid)) {
5660
+ const daemonUp = state.socketPath !== void 0 && state.headroomPid !== void 0 && ports.isRunning(state.headroomPid);
5661
+ if (daemonUp && state.socketPath !== void 0) {
5662
+ const required = params.requiredAllowlistHash?.();
5663
+ if (required === void 0 || state.allowlistHash === required) {
5664
+ writeSession(ports.fs, paths.headroomSessionsDir, { pid: params.launcherPid, startedAt: ports.now(), supervisorPid: state.supervisorPid });
5665
+ return { socketPath: state.socketPath };
5666
+ }
5667
+ const holders = sessionsForSupervisor(ports.fs, paths.headroomSessionsDir, state.supervisorPid).filter((session) => ports.isRunning(session.pid));
5668
+ if (holders.length > 0) {
5669
+ throw new HeadroomStartError(
5670
+ `the running headroom daemon was started before this launch's front door address and would refuse every request this launch sends through it. It restarts once the launches using it end (pid ${holders.map((session) => String(session.pid)).join(", ")}); end them and launch again, or launch with --no-headroom.`
5671
+ );
5672
+ }
5673
+ awaitingAllowlist = true;
5674
+ }
5675
+ if (state.lastError !== void 0) {
5676
+ throw new HeadroomStartError(
5677
+ `the headroom daemon reported a fatal error and is not serving: ${state.lastError} (daemon log: ${paths.headroomLogPath})`
5678
+ );
5679
+ }
5680
+ } else if (!spawnedSupervisor) {
5681
+ ports.fs.mkdirp(paths.headroomDir);
5682
+ const created = ports.fs.writeFileExclusive(
5683
+ paths.headroomLockFile,
5684
+ `${JSON.stringify({ pid: params.launcherPid, at: ports.now() })}
5685
+ `
5686
+ );
5687
+ if (created) {
5688
+ ports.spawnSupervisor(paths);
5689
+ spawnedSupervisor = true;
5690
+ } else {
5691
+ const lock = readStartLock(ports.fs, paths.headroomLockFile);
5692
+ if (lock === void 0 || !ports.isRunning(lock.pid)) {
5693
+ ports.fs.removeRecursive(paths.headroomLockFile);
5694
+ continue;
5695
+ }
5696
+ }
5697
+ }
5698
+ if (ports.now() >= deadline) {
5699
+ const lastError = state?.lastError;
5700
+ throw new HeadroomStartError(
5701
+ `the headroom daemon did not become ready within ${String(HEADROOM_START_TIMEOUT_MS)}ms` + (lastError === void 0 ? "" : ` (last error: ${lastError})`) + (awaitingAllowlist ? " (the running daemon's allowlist does not admit this launch's front door, and it did not restart)" : "") + `. Daemon log: ${paths.headroomLogPath}`
5702
+ );
5703
+ }
5704
+ ports.sleep(HEADROOM_LAUNCHER_POLL_MS);
5892
5705
  }
5893
- identityName;
5894
- };
5895
- var InvalidIdentityNameError = class extends CliError {
5896
- constructor(attemptedName) {
5897
- super(
5898
- `"${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.`
5899
- );
5900
- this.attemptedName = attemptedName;
5901
- this.name = "InvalidIdentityNameError";
5706
+ }
5707
+
5708
+ // src/headroom/settings.ts
5709
+ var import_node_crypto8 = require("node:crypto");
5710
+ function settingsOf(config) {
5711
+ return {
5712
+ ...config.mode === void 0 ? {} : { mode: config.mode },
5713
+ ...config.targetRatio === void 0 ? {} : { targetRatio: config.targetRatio },
5714
+ ...config.ccr === void 0 ? {} : { ccr: config.ccr },
5715
+ ...config.rolloutChannel === void 0 ? {} : { rolloutChannel: config.rolloutChannel },
5716
+ ...config.interceptToolResults === void 0 ? {} : { interceptToolResults: config.interceptToolResults },
5717
+ ...config.readMaturation === void 0 ? {} : { readMaturation: config.readMaturation }
5718
+ };
5719
+ }
5720
+ function settingsArgs(settings) {
5721
+ return [
5722
+ ...settings.mode === void 0 ? [] : ["--mode", settings.mode],
5723
+ ...settings.targetRatio === void 0 ? [] : ["--target-ratio", String(settings.targetRatio)],
5724
+ ...settings.ccr === "lossless" ? ["--lossless"] : [],
5725
+ ...settings.ccr === "none" ? ["--no-ccr"] : [],
5726
+ ...settings.interceptToolResults === true ? ["--intercept-tool-results"] : [],
5727
+ ...settings.readMaturation === true ? ["--read-maturation"] : []
5728
+ ];
5729
+ }
5730
+ function settingsEnv(settings) {
5731
+ return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
5732
+ }
5733
+ function hashSettings(settings) {
5734
+ return (0, import_node_crypto8.createHash)("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
5735
+ }
5736
+
5737
+ // src/headroom/socket.ts
5738
+ var import_node_path13 = __toESM(require("node:path"), 1);
5739
+ var GROUP_OR_OTHER_BITS = 63;
5740
+ var OCTAL_RADIX = 8;
5741
+ var MODE_DIGITS = 4;
5742
+ var HEADROOM_SOCKET_SUFFIX = ".sock";
5743
+ var HEADROOM_SOCKET_NAME = /^(\d+)\.sock$/;
5744
+ var UNIX_SOCKET_PATH_MAX_BYTES = 103;
5745
+ var PLATFORMS_WITHOUT_UNIX_SOCKETS = ["win32"];
5746
+ function headroomSocketPath(paths, supervisorPid) {
5747
+ return import_node_path13.default.join(paths.headroomSocketDir, `${String(supervisorPid)}${HEADROOM_SOCKET_SUFFIX}`);
5748
+ }
5749
+ function platformRefusal(platform) {
5750
+ return PLATFORMS_WITHOUT_UNIX_SOCKETS.includes(platform) ? `headroom routing needs a unix domain socket, which headroom cannot serve on ${platform}; agent-shim never falls back to a TCP port for this hop` : void 0;
5751
+ }
5752
+ function ownerOnlyRefusal(label, target, stat, expected, uid) {
5753
+ if (stat === void 0) {
5754
+ return `the headroom ${label} ${target} does not exist`;
5902
5755
  }
5903
- attemptedName;
5904
- };
5905
- function identityJsonPath(paths, name) {
5906
- return import_node_path15.default.join(paths.identitiesDir, name, "identity.json");
5756
+ if (stat.kind === "symlink") {
5757
+ return `the headroom ${label} ${target} is a symlink; it must be the real ${expected === "dir" ? "directory" : "socket"}`;
5758
+ }
5759
+ if (stat.kind !== expected) {
5760
+ return `the headroom ${label} ${target} is not a ${expected === "dir" ? "directory" : "socket"}`;
5761
+ }
5762
+ if (stat.uid !== uid) {
5763
+ return `the headroom ${label} ${target} is owned by uid ${String(stat.uid)}, not by this user (uid ${String(uid)})`;
5764
+ }
5765
+ if ((stat.mode & GROUP_OR_OTHER_BITS) !== 0) {
5766
+ return `the headroom ${label} ${target} has mode ${stat.mode.toString(OCTAL_RADIX).padStart(MODE_DIGITS, "0")}, which lets other users reach it; it must be accessible to its owner only`;
5767
+ }
5768
+ return void 0;
5907
5769
  }
5908
- function identityExists(paths, name) {
5909
- return import_node_fs4.default.existsSync(identityJsonPath(paths, name));
5770
+ function prepareHeadroomSocket(fs12, ports, paths, supervisorPid) {
5771
+ const unsupported = platformRefusal(ports.platform);
5772
+ if (unsupported !== void 0) {
5773
+ return { refused: unsupported };
5774
+ }
5775
+ const socketPath = headroomSocketPath(paths, supervisorPid);
5776
+ const length = Buffer.byteLength(socketPath, "utf8");
5777
+ if (length > UNIX_SOCKET_PATH_MAX_BYTES) {
5778
+ return {
5779
+ refused: `the headroom socket path ${socketPath} is ${String(length)} bytes, longer than the ${String(UNIX_SOCKET_PATH_MAX_BYTES)} a unix socket path can hold; set AGENT_SHIM_HOME to a shorter directory`
5780
+ };
5781
+ }
5782
+ if (ports.lstat(paths.headroomSocketDir)?.kind === "symlink") {
5783
+ return { refused: `the headroom socket directory ${paths.headroomSocketDir} is a symlink; it must be the real directory` };
5784
+ }
5785
+ fs12.mkdirPrivate(paths.headroomSocketDir);
5786
+ const refusal = ownerOnlyRefusal("socket directory", paths.headroomSocketDir, ports.lstat(paths.headroomSocketDir), "dir", ports.currentUid());
5787
+ return refusal === void 0 ? { socketPath } : { refused: refusal };
5910
5788
  }
5911
- function readIdentity(paths, name) {
5912
- return readJson(identityJsonPath(paths, name), IdentitySchema);
5789
+ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
5790
+ const removed = [];
5791
+ for (const name of fs12.readdir(paths.headroomSocketDir)) {
5792
+ const pid = HEADROOM_SOCKET_NAME.exec(name)?.[1];
5793
+ if (pid === void 0 || isRunning(Number.parseInt(pid, 10))) {
5794
+ continue;
5795
+ }
5796
+ fs12.removeRecursive(import_node_path13.default.join(paths.headroomSocketDir, name));
5797
+ removed.push(name);
5798
+ }
5799
+ return removed;
5913
5800
  }
5914
- function addIdentity(paths, name) {
5915
- if (identityExists(paths, name)) {
5916
- throw new IdentityAlreadyExistsError(name);
5801
+
5802
+ // src/headroom/source.ts
5803
+ var import_zod10 = require("zod");
5804
+ var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
5805
+ var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
5806
+ var DirectUrlSchema = import_zod10.z.object({ vcs_info: import_zod10.z.object({ commit_id: import_zod10.z.string() }) });
5807
+ function isMovingGitSource(spec) {
5808
+ const match = GIT_SPEC_RE.exec(spec);
5809
+ if (match === null) {
5810
+ return false;
5917
5811
  }
5918
- const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
5919
- if (!parsed.success) {
5920
- throw new InvalidIdentityNameError(name);
5812
+ const ref = match[1];
5813
+ return ref === void 0 || !COMMIT_SHA_RE.test(ref);
5814
+ }
5815
+ function pinnedGitCommit(spec) {
5816
+ const ref = GIT_SPEC_RE.exec(spec)?.[1];
5817
+ return ref !== void 0 && COMMIT_SHA_RE.test(ref) ? ref.toLowerCase() : void 0;
5818
+ }
5819
+
5820
+ // src/headroom/supervisor.ts
5821
+ function resolveSupervisorConfig(configured) {
5822
+ return {
5823
+ source: configured.source ?? HEADROOM_DEFAULT_SOURCE,
5824
+ idleShutdownMinutes: configured.idleShutdownMinutes ?? HEADROOM_DEFAULT_IDLE_SHUTDOWN_MINUTES,
5825
+ settings: settingsOf(configured)
5826
+ };
5827
+ }
5828
+ var HEADROOM_BACKOFF_BASE_MS = 500;
5829
+ var HEADROOM_BACKOFF_CAP_MS = 3e4;
5830
+ var HEADROOM_START_RETRY_BUDGET = 5;
5831
+ var HEADROOM_READY_TIMEOUT_MS = 15e3;
5832
+ var HEADROOM_POLL_MS = 1e3;
5833
+ var MS_PER_MINUTE2 = 6e4;
5834
+ function backoffForAttempt(attempt) {
5835
+ return Math.min(HEADROOM_BACKOFF_CAP_MS, HEADROOM_BACKOFF_BASE_MS * 2 ** (attempt - 1));
5836
+ }
5837
+ function versionSatisfies(version, source) {
5838
+ if (version === void 0) {
5839
+ return false;
5840
+ }
5841
+ const specifier = /==|>=|<=|!=|>|<|~=/.exec(source);
5842
+ if (specifier === null) {
5843
+ return true;
5844
+ }
5845
+ const installed = parseVersion(version);
5846
+ const wanted = parseVersion(source.slice(specifier.index + specifier[0].length));
5847
+ if (installed === void 0 || wanted === void 0) {
5848
+ return true;
5849
+ }
5850
+ const cmp = compareVersions(installed, wanted);
5851
+ switch (specifier[0]) {
5852
+ case "==":
5853
+ return cmp === 0;
5854
+ case "!=":
5855
+ return cmp !== 0;
5856
+ case ">=":
5857
+ return cmp >= 0;
5858
+ case "<=":
5859
+ return cmp <= 0;
5860
+ case ">":
5861
+ return cmp > 0;
5862
+ case "<":
5863
+ return cmp < 0;
5864
+ case "~=":
5865
+ return compareVersions(installed.slice(0, Math.max(0, wanted.length - 1)), wanted.slice(0, Math.max(0, wanted.length - 1))) === 0 && cmp >= 0;
5866
+ default:
5867
+ return true;
5921
5868
  }
5922
- writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
5923
- return parsed.data;
5924
5869
  }
5925
- function useIdentity(paths, name) {
5926
- const pool = poolNameOf(name);
5927
- if (pool !== void 0) {
5928
- requirePool(paths, pool);
5929
- } else if (!identityExists(paths, name)) {
5930
- throw new IdentityNotFoundError(name);
5870
+ function parseVersion(input) {
5871
+ const match = /(\d+)(\.\d+)*/.exec(input);
5872
+ if (match === null) {
5873
+ return void 0;
5874
+ }
5875
+ return match[0].split(".").map((part) => Number.parseInt(part, 10));
5876
+ }
5877
+ function compareVersions(a, b) {
5878
+ for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
5879
+ const left = a[index] ?? 0;
5880
+ const right = b[index] ?? 0;
5881
+ if (left !== right) {
5882
+ return left - right;
5883
+ }
5884
+ }
5885
+ return 0;
5886
+ }
5887
+ var HEADROOM_SUPERVISOR_STILL_RUNNING = -1;
5888
+ async function runSupervisor(initialConfig, ports, options = {}) {
5889
+ const { fs: fs12, paths } = ports;
5890
+ let config = initialConfig;
5891
+ const previousState = readHeadroomState(fs12, paths.headroomStateFile);
5892
+ if (previousState?.supervisorPid !== void 0 && previousState.supervisorPid !== ports.ownPid && ports.isRunning(previousState.supervisorPid)) {
5893
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: supervisor ${String(previousState.supervisorPid)} already owns the daemon; exiting`);
5894
+ return 0;
5895
+ }
5896
+ let installedSource = previousState?.installedSource;
5897
+ const ownsState = () => {
5898
+ const owner = readHeadroomState(fs12, paths.headroomStateFile)?.supervisorPid;
5899
+ return owner === void 0 || owner === ports.ownPid || !ports.isRunning(owner);
5900
+ };
5901
+ const writeOwnState = (state) => {
5902
+ if (!ownsState()) {
5903
+ return false;
5904
+ }
5905
+ writeHeadroomState(fs12, paths.headroomStateFile, state);
5906
+ return true;
5907
+ };
5908
+ const fail = (message) => {
5909
+ if (writeOwnState({ lastError: message })) {
5910
+ fs12.removeRecursive(paths.headroomLockFile);
5911
+ }
5912
+ ports.log(`agent-shim headroom supervisor: ${message}`);
5913
+ return 1;
5914
+ };
5915
+ const ensureInstalled = () => {
5916
+ const version2 = ports.headroomVersion();
5917
+ const versionOk = version2 !== void 0 && versionSatisfies(version2, config.source);
5918
+ const pinned = pinnedGitCommit(config.source);
5919
+ const commitOk = pinned === void 0 || ports.installedCommit() === pinned;
5920
+ if (versionOk && commitOk && (installedSource === void 0 || installedSource === config.source)) {
5921
+ installedSource = config.source;
5922
+ return true;
5923
+ }
5924
+ ports.log(`agent-shim headroom supervisor: installing headroom from ${config.source}`);
5925
+ const result = ports.install(config.source);
5926
+ if (!result.ok) {
5927
+ return false;
5928
+ }
5929
+ installedSource = config.source;
5930
+ return true;
5931
+ };
5932
+ const socket = prepareHeadroomSocket(fs12, ports.socketTrust, paths, ports.ownPid);
5933
+ if (socket.refused !== void 0) {
5934
+ return fail(socket.refused);
5935
+ }
5936
+ const { socketPath } = socket;
5937
+ pruneStaleHeadroomSockets(fs12, paths, ports.isRunning);
5938
+ if (!ensureInstalled()) {
5939
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
5940
+ }
5941
+ const version = ports.headroomVersion() ?? "unknown";
5942
+ writeOwnState({ supervisorPid: ports.ownPid, version, installedSource });
5943
+ ports.log(`agent-shim headroom supervisor ${String(ports.ownPid)}: managing headroom on allowlist [${allowlistOf(ports).join(", ")}]`);
5944
+ const orphanPid = previousState?.headroomPid;
5945
+ if (orphanPid !== void 0 && ports.isRunning(orphanPid)) {
5946
+ ports.log(`agent-shim headroom supervisor: stopping daemon pid ${String(orphanPid)} left behind by the previous supervisor`);
5947
+ ports.stopProcess(orphanPid);
5948
+ }
5949
+ let headroomPid;
5950
+ let runningHash;
5951
+ let runningSettingsHash;
5952
+ let consecutiveFailures = 0;
5953
+ let idleSince;
5954
+ let superseded = false;
5955
+ let ticks = 0;
5956
+ for (; ; ) {
5957
+ if (options.tickLimit !== void 0 && ticks >= options.tickLimit) {
5958
+ return HEADROOM_SUPERVISOR_STILL_RUNNING;
5959
+ }
5960
+ ticks += 1;
5961
+ config = ports.readConfig();
5962
+ const settingsHash = hashSettings(config.settings);
5963
+ const allowlist = allowlistOf(ports);
5964
+ const allowlistHash = hashAllowlist(allowlist);
5965
+ pruneDeadSessions(fs12, paths.headroomSessionsDir, ports.isRunning);
5966
+ const ownsNow = ownsState();
5967
+ if (!ownsNow && !superseded) {
5968
+ ports.log("agent-shim headroom supervisor: another supervisor now owns the state file; serving this daemon's own sessions until they end");
5969
+ }
5970
+ superseded = !ownsNow;
5971
+ const crashed = headroomPid !== void 0 && !ports.isRunning(headroomPid);
5972
+ if (headroomPid === void 0 || crashed) {
5973
+ if (crashed) {
5974
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(headroomPid)} died`);
5975
+ headroomPid = void 0;
5976
+ runningHash = void 0;
5977
+ runningSettingsHash = void 0;
5978
+ writeOwnState({
5979
+ supervisorPid: ports.ownPid,
5980
+ version,
5981
+ installedSource
5982
+ });
5983
+ }
5984
+ if (consecutiveFailures >= HEADROOM_START_RETRY_BUDGET) {
5985
+ return fail(`headroom failed to become ready ${String(consecutiveFailures)} times in a row; giving up`);
5986
+ }
5987
+ if (installedSource !== config.source && !ensureInstalled()) {
5988
+ return fail(`could not install headroom from ${config.source}: install failed (is uv installed and on PATH?)`);
5989
+ }
5990
+ const pid = ports.spawnHeadroom(socketPath, allowlist, config.settings);
5991
+ const ready = await waitUntilReady(ports, socketPath);
5992
+ if (ready) {
5993
+ consecutiveFailures = 0;
5994
+ headroomPid = pid;
5995
+ runningHash = allowlistHash;
5996
+ runningSettingsHash = settingsHash;
5997
+ const recorded = writeOwnState({
5998
+ supervisorPid: ports.ownPid,
5999
+ headroomPid: pid,
6000
+ socketPath,
6001
+ version,
6002
+ allowlistHash,
6003
+ settingsHash,
6004
+ installedSource: config.source
6005
+ });
6006
+ if (recorded) {
6007
+ fs12.removeRecursive(paths.headroomLockFile);
6008
+ }
6009
+ idleSince = void 0;
6010
+ ports.log(`agent-shim headroom supervisor: headroom pid ${String(pid)} ready on unix socket ${socketPath}`);
6011
+ } else {
6012
+ ports.stopProcess(pid);
6013
+ consecutiveFailures += 1;
6014
+ ports.log(
6015
+ `agent-shim headroom supervisor: headroom did not become ready on unix socket ${socketPath} (attempt ${String(consecutiveFailures)} of ${String(HEADROOM_START_RETRY_BUDGET)})`
6016
+ );
6017
+ await ports.sleep(backoffForAttempt(consecutiveFailures));
6018
+ continue;
6019
+ }
6020
+ } else {
6021
+ if (runningHash !== allowlistHash || runningSettingsHash !== settingsHash || installedSource !== config.source) {
6022
+ if (sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid).length === 0) {
6023
+ ports.log("agent-shim headroom supervisor: configuration drifted and no sessions are live; restarting headroom");
6024
+ ports.stopProcess(headroomPid);
6025
+ headroomPid = void 0;
6026
+ runningHash = void 0;
6027
+ runningSettingsHash = void 0;
6028
+ continue;
6029
+ }
6030
+ }
6031
+ const sessions = sessionsForSupervisor(fs12, paths.headroomSessionsDir, ports.ownPid);
6032
+ if (sessions.length === 0) {
6033
+ idleSince ??= ports.now();
6034
+ const idleMs = ports.now() - idleSince;
6035
+ if (idleMs >= config.idleShutdownMinutes * MS_PER_MINUTE2) {
6036
+ ports.log(
6037
+ `agent-shim headroom supervisor: no sessions for ${String(config.idleShutdownMinutes)} minute(s); stopping headroom and exiting`
6038
+ );
6039
+ ports.stopProcess(headroomPid);
6040
+ writeOwnState({ version });
6041
+ return 0;
6042
+ }
6043
+ } else {
6044
+ idleSince = void 0;
6045
+ }
6046
+ }
6047
+ await ports.sleep(HEADROOM_POLL_MS);
5931
6048
  }
5932
- writeTextAtomic(paths.activeIdentityFile, `${name}
5933
- `);
5934
6049
  }
5935
- function readActiveIdentity(paths) {
5936
- if (!import_node_fs4.default.existsSync(paths.activeIdentityFile)) {
5937
- return void 0;
5938
- }
5939
- const raw = import_node_fs4.default.readFileSync(paths.activeIdentityFile, "utf8").trim();
5940
- return raw === "" ? void 0 : raw;
6050
+ function currentHeadroomAllowlist(fs12, paths) {
6051
+ const providers = readAllProviders(fs12, paths.providersDir).map((entry) => entry.provider);
6052
+ return headroomAllowlist(headroomUpstreams(providers, frontDoorOrigin(readFrontDoorState(fs12, paths.frontdoorStateFile))));
5941
6053
  }
5942
- function isIdentityDirectoryName(name) {
5943
- return !name.startsWith(".");
6054
+ function allowlistOf(ports) {
6055
+ return currentHeadroomAllowlist(ports.fs, ports.paths);
5944
6056
  }
5945
- function readIdentityForListing(paths, name) {
5946
- try {
5947
- return readIdentity(paths, name);
5948
- } catch (error) {
5949
- if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
5950
- return { problem: error.message.replace(/\s*\n\s*/g, " ") };
6057
+ async function waitUntilReady(ports, socketPath) {
6058
+ const deadline = ports.now() + HEADROOM_READY_TIMEOUT_MS;
6059
+ for (; ; ) {
6060
+ if (await ports.ready(socketPath)) {
6061
+ return true;
5951
6062
  }
5952
- throw error;
6063
+ if (ports.now() >= deadline) {
6064
+ return false;
6065
+ }
6066
+ await ports.sleep(HEADROOM_POLL_MS);
5953
6067
  }
5954
6068
  }
5955
- function listIdentities(paths) {
5956
- if (!import_node_fs4.default.existsSync(paths.identitiesDir)) {
5957
- return [];
5958
- }
5959
- const active = readActiveIdentity(paths);
5960
- const names = import_node_fs4.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
5961
- const result = [];
5962
- for (const name of names) {
5963
- const read = readIdentityForListing(paths, name);
5964
- if (read === void 0) {
6069
+
6070
+ // src/launcher/guard.ts
6071
+ var AMBIENT_CREDENTIAL_VARS = [
6072
+ "ANTHROPIC_API_KEY",
6073
+ "ANTHROPIC_AUTH_TOKEN",
6074
+ "CLAUDE_CODE_OAUTH_TOKEN",
6075
+ "CLAUDE_CODE_USE_BEDROCK",
6076
+ "CLAUDE_CODE_USE_VERTEX",
6077
+ "CLAUDE_CODE_USE_FOUNDRY"
6078
+ ];
6079
+ function detectAmbientCredential(env, injected) {
6080
+ for (const variable of AMBIENT_CREDENTIAL_VARS) {
6081
+ const value = env[variable];
6082
+ if (value === void 0 || value === "") {
5965
6083
  continue;
5966
6084
  }
5967
- const isActive = name === active;
5968
- result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
5969
- }
5970
- return result;
5971
- }
5972
- function setDefaultConfigProfile(paths, identityName, profileName) {
5973
- if (!identityExists(paths, identityName)) {
5974
- throw new IdentityNotFoundError(identityName);
6085
+ if (injected?.variable === variable && value === injected.token) {
6086
+ continue;
6087
+ }
6088
+ return { variable };
5975
6089
  }
5976
- return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
5977
- defaultConfigProfile: profileName
5978
- });
6090
+ return void 0;
5979
6091
  }
5980
- function setAllowAmbientCredential(paths, identityName, allow) {
5981
- if (!identityExists(paths, identityName)) {
5982
- throw new IdentityNotFoundError(identityName);
5983
- }
5984
- return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
5985
- allowAmbientCredential: allow
5986
- });
6092
+ function formatAmbientCredentialGuardMessage(variable, identityName) {
6093
+ const identitySetCommand = identityName === void 0 ? "agent-shim identity set <name> --allow-ambient-credential" : `agent-shim identity set ${identityName} --allow-ambient-credential`;
6094
+ return [
6095
+ `error: ${variable} is set in the environment. This identity's isolated`,
6096
+ "credential would be bypassed \u2014 every identity authenticates as this same key",
6097
+ "while it's set. Unset it, or if this is deliberate, opt in per-launch with",
6098
+ "AGENT_SHIM_ALLOW_AMBIENT_CREDENTIAL=1, or persistently for this identity with",
6099
+ `\`${identitySetCommand}\`.`
6100
+ ].join("\n");
5987
6101
  }
5988
- function setIdentityCredential(paths, identityName, change) {
5989
- const existing = readIdentity(paths, identityName);
5990
- if (existing === void 0) {
5991
- throw new IdentityNotFoundError(identityName);
6102
+ function evaluateAmbientCredentialGuard(params) {
6103
+ const detected = detectAmbientCredential(params.env, params.injectedCredential);
6104
+ if (detected === void 0) {
6105
+ return { ok: true };
5992
6106
  }
5993
- if (change === false) {
5994
- return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { credential: void 0 });
6107
+ if (params.allowAmbientCredentialOverride || params.allowAmbientCredential || params.providerSelected === true) {
6108
+ return { ok: true };
5995
6109
  }
5996
- const sources = change.sources ?? existing.credential?.sources;
5997
- if (sources === void 0) {
5998
- throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
6110
+ return {
6111
+ ok: false,
6112
+ variable: detected.variable,
6113
+ message: formatAmbientCredentialGuardMessage(detected.variable, params.identityName)
6114
+ };
6115
+ }
6116
+
6117
+ // src/paths.ts
6118
+ var import_node_fs2 = __toESM(require("node:fs"), 1);
6119
+ var import_node_os = __toESM(require("node:os"), 1);
6120
+ var import_node_path14 = __toESM(require("node:path"), 1);
6121
+ function resolveAgentShimHome(env = process.env, home = import_node_os.default.homedir(), isDirectory = isExistingDirectory) {
6122
+ const fromEnv = env.AGENT_SHIM_HOME;
6123
+ if (fromEnv !== void 0 && fromEnv !== "") {
6124
+ return fromEnv;
5999
6125
  }
6000
- const target = change.target ?? existing.credential?.target;
6001
- const cache = change.cache === void 0 ? existing.credential?.cache : change.cache === false ? void 0 : change.cache;
6002
- return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
6003
- credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
6004
- });
6126
+ const current = import_node_path14.default.join(home, HOME_DIRNAME);
6127
+ const legacy = import_node_path14.default.join(home, LEGACY_HOME_DIRNAME);
6128
+ return !isDirectory(current) && isDirectory(legacy) ? legacy : current;
6005
6129
  }
6006
- function belongsToIdentity(entry, name) {
6007
- return entry === name || entry === import_node_path15.default.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
6130
+ function isExistingDirectory(candidate) {
6131
+ return import_node_fs2.default.statSync(candidate, { throwIfNoEntry: false })?.isDirectory() ?? false;
6008
6132
  }
6009
- function removeIdentity(paths, name) {
6010
- if (!identityExists(paths, name)) {
6011
- throw new IdentityNotFoundError(name);
6012
- }
6013
- for (const entry of import_node_fs4.default.readdirSync(paths.identitiesDir)) {
6014
- if (belongsToIdentity(entry, name)) {
6015
- import_node_fs4.default.rmSync(import_node_path15.default.join(paths.identitiesDir, entry), { recursive: true, force: true });
6016
- }
6017
- }
6018
- if (readActiveIdentity(paths) === name) {
6019
- import_node_fs4.default.rmSync(paths.activeIdentityFile);
6133
+ function resolveClaudeHome() {
6134
+ const fromEnv = process.env.AGENT_SHIM_CLAUDE_HOME;
6135
+ if (fromEnv !== void 0 && fromEnv !== "") {
6136
+ return fromEnv;
6020
6137
  }
6138
+ return import_node_path14.default.join(import_node_os.default.homedir(), ".claude");
6139
+ }
6140
+ function buildLayoutPaths(root) {
6141
+ return {
6142
+ root,
6143
+ identitiesDir: import_node_path14.default.join(root, "identities"),
6144
+ configProfilesDir: import_node_path14.default.join(root, "config-profiles"),
6145
+ providersDir: import_node_path14.default.join(root, "providers"),
6146
+ directoryRulesFile: import_node_path14.default.join(root, "directory-rules.json"),
6147
+ activeIdentityFile: import_node_path14.default.join(root, "active-identity"),
6148
+ globalConfigFile: import_node_path14.default.join(root, "config.json"),
6149
+ categoriesLocalFile: import_node_path14.default.join(root, "categories.local.json"),
6150
+ claudeShimFile: import_node_path14.default.join(root, "claude-shim.json"),
6151
+ headroomDir: import_node_path14.default.join(root, "headroom"),
6152
+ headroomStateFile: import_node_path14.default.join(root, "headroom", `state.v${String(HEADROOM_STATE_SCHEMA_VERSION)}.json`),
6153
+ headroomSocketDir: import_node_path14.default.join(root, "headroom", "run"),
6154
+ headroomLockFile: import_node_path14.default.join(root, "headroom", "start.lock"),
6155
+ headroomSessionsDir: import_node_path14.default.join(root, "headroom", "sessions"),
6156
+ logsDir: import_node_path14.default.join(root, "logs"),
6157
+ headroomLogPath: import_node_path14.default.join(root, "logs", "headroom.log"),
6158
+ frontdoorDir: import_node_path14.default.join(root, "frontdoor"),
6159
+ frontdoorCaDir: import_node_path14.default.join(root, "frontdoor", "ca"),
6160
+ frontdoorCaCertFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.pem"),
6161
+ frontdoorCaKeyFile: import_node_path14.default.join(root, "frontdoor", "ca", "ca.key"),
6162
+ frontdoorCaBundlesDir: import_node_path14.default.join(root, "frontdoor", "ca", "bundles"),
6163
+ frontdoorStateFile: import_node_path14.default.join(root, "frontdoor", "state.json"),
6164
+ frontdoorLockFile: import_node_path14.default.join(root, "frontdoor", "start.lock"),
6165
+ frontdoorControlTokenFile: import_node_path14.default.join(root, "frontdoor", "control-token"),
6166
+ frontdoorSessionsDir: import_node_path14.default.join(root, "frontdoor", "sessions"),
6167
+ frontdoorLogPath: import_node_path14.default.join(root, "logs", "frontdoor.log"),
6168
+ usageDir: import_node_path14.default.join(root, "usage"),
6169
+ usageLogDir: import_node_path14.default.join(root, "usage", "log"),
6170
+ usageSnapshotsDir: import_node_path14.default.join(root, "usage", "snapshots"),
6171
+ usagePicksFile: import_node_path14.default.join(root, "usage", "picks.json")
6172
+ };
6173
+ }
6174
+ function resolveLayoutPaths() {
6175
+ return buildLayoutPaths(resolveAgentShimHome());
6021
6176
  }
6022
6177
 
6023
- // src/providersStore.ts
6024
- var import_node_fs5 = __toESM(require("node:fs"), 1);
6178
+ // src/identityStore.ts
6179
+ var import_node_fs4 = __toESM(require("node:fs"), 1);
6025
6180
  var import_node_path16 = __toESM(require("node:path"), 1);
6026
6181
 
6027
- // src/credentialCache.ts
6028
- var import_zod7 = require("zod");
6029
- var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
6030
- var CachedCredentialSchema = import_zod7.z.strictObject({
6031
- token: import_zod7.z.string().min(1),
6032
- fetchedAt: import_zod7.z.number().int().nonnegative(),
6033
- source: CredentialSourceSchema
6034
- });
6035
- function effectiveStore(cache, platform) {
6036
- return cache.store ?? (platform === "darwin" ? "keychain" : "file");
6037
- }
6038
- function ttlMs(cache) {
6039
- if (cache.ttl === void 0) {
6040
- return void 0;
6041
- }
6042
- const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
6043
- if (multiplier === void 0) {
6044
- throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
6045
- }
6046
- return Number(cache.ttl.slice(0, -1)) * multiplier;
6047
- }
6048
- function isFresh(entry, cache, now) {
6049
- const limit = ttlMs(cache);
6050
- if (entry.fetchedAt > now) {
6051
- return false;
6182
+ // src/configProfilesStore.ts
6183
+ var import_node_fs3 = __toESM(require("node:fs"), 1);
6184
+ var import_node_path15 = __toESM(require("node:path"), 1);
6185
+ var ProfileNotFoundError = class extends CliError {
6186
+ constructor(profileName) {
6187
+ super(`No configuration profile named "${profileName}". Run \`agent-shim profile add ${profileName}\` first.`);
6188
+ this.profileName = profileName;
6189
+ this.name = "ProfileNotFoundError";
6052
6190
  }
6053
- return limit === void 0 || now - entry.fetchedAt < limit;
6054
- }
6055
-
6056
- // src/credential.ts
6057
- var CREDENTIAL_UNAVAILABLE_EXIT = 64;
6058
- var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
6059
- var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
6060
- var GROUP_OTHER_PERMISSION_BITS = 63;
6061
- var PERMISSION_BITS = 511;
6062
- var OCTAL_RADIX2 = 8;
6063
- function commandArgv(source) {
6064
- if ("command" in source) {
6065
- return source.command;
6191
+ profileName;
6192
+ };
6193
+ var ProfileAlreadyExistsError = class extends CliError {
6194
+ constructor(profileName) {
6195
+ super(`A configuration profile named "${profileName}" already exists.`);
6196
+ this.profileName = profileName;
6197
+ this.name = "ProfileAlreadyExistsError";
6066
6198
  }
6067
- if ("op" in source) {
6068
- return ["op", "read", source.op];
6199
+ profileName;
6200
+ };
6201
+ var InvalidCategoryNameError = class extends CliError {
6202
+ constructor(categoryName) {
6203
+ super(`"${categoryName}" is not a category a configuration profile may toggle (runtime, history, knowledge, settings).`);
6204
+ this.categoryName = categoryName;
6205
+ this.name = "InvalidCategoryNameError";
6069
6206
  }
6070
- const account = source.keychain.account;
6071
- return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
6207
+ categoryName;
6208
+ };
6209
+ function profileJsonPath(paths, name) {
6210
+ return import_node_path15.default.join(paths.configProfilesDir, `${name}.json`);
6072
6211
  }
6073
- function isInteractiveSource(source, env) {
6074
- if ("command" in source || "keychain" in source) {
6075
- return source.interactive ?? false;
6076
- }
6077
- if ("op" in source) {
6078
- return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
6079
- }
6080
- return false;
6212
+ function profileExists(paths, name) {
6213
+ return import_node_fs3.default.existsSync(profileJsonPath(paths, name));
6081
6214
  }
6082
- function summariseSource(source) {
6083
- if ("env" in source) {
6084
- return { kind: "env", variable: source.env };
6215
+ function requireProfileExists(paths, name) {
6216
+ if (!profileExists(paths, name)) {
6217
+ throw new ProfileNotFoundError(name);
6085
6218
  }
6086
- if ("file" in source) {
6087
- return { kind: "file", path: source.file };
6219
+ }
6220
+ function readProfile(paths, name) {
6221
+ return readJson(profileJsonPath(paths, name), ConfigProfileSchema);
6222
+ }
6223
+ function createProfile(paths, name, extendsList, description) {
6224
+ if (profileExists(paths, name)) {
6225
+ throw new ProfileAlreadyExistsError(name);
6088
6226
  }
6089
- if ("command" in source) {
6090
- return { kind: "command", program: source.command[0] };
6227
+ const filePath = profileJsonPath(paths, name);
6228
+ const parsed = ConfigProfileSchema.safeParse({
6229
+ ...description === void 0 ? {} : { description },
6230
+ ...extendsList !== void 0 && extendsList.length > 0 ? { extends: [...extendsList] } : {}
6231
+ });
6232
+ if (!parsed.success) {
6233
+ throw new ConfigValidationError(filePath, parsed.error.issues);
6091
6234
  }
6092
- if ("op" in source) {
6093
- return { kind: "op", reference: source.op };
6235
+ writeJsonAtomic(filePath, parsed.data);
6236
+ return parsed.data;
6237
+ }
6238
+ function listProfiles(paths) {
6239
+ if (!import_node_fs3.default.existsSync(paths.configProfilesDir)) {
6240
+ return [];
6094
6241
  }
6095
- if ("keychain" in source) {
6096
- return {
6097
- kind: "keychain",
6098
- service: source.keychain.service,
6099
- ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
6100
- };
6242
+ const names = import_node_fs3.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort();
6243
+ const result = [];
6244
+ for (const name of names) {
6245
+ const profile = readProfile(paths, name);
6246
+ if (profile !== void 0) {
6247
+ result.push({ name, profile });
6248
+ }
6101
6249
  }
6102
- return { kind: "literal" };
6250
+ return result;
6103
6251
  }
6104
- function summariseCredential(credential) {
6105
- return {
6106
- target: credential.target ?? "bearer",
6107
- sources: credential.sources.map(summariseSource),
6108
- ...credential.cache === void 0 ? {} : { cache: credential.cache }
6109
- };
6252
+ function globalConfigPath(paths) {
6253
+ return paths.globalConfigFile;
6110
6254
  }
6111
- function formatSourceSummary(summary) {
6112
- switch (summary.kind) {
6113
- case "env":
6114
- return `env ${summary.variable}`;
6115
- case "file":
6116
- return `file ${summary.path}`;
6117
- case "command":
6118
- return `command ${summary.program}`;
6119
- case "op":
6120
- return `op ${summary.reference}`;
6121
- case "keychain":
6122
- return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
6123
- case "literal":
6124
- return "literal (non-secret placeholder)";
6125
- default:
6126
- return summary;
6255
+ function readGlobalConfig(paths) {
6256
+ return readJson(globalConfigPath(paths), GlobalConfigSchema);
6257
+ }
6258
+ function setGlobalDefaultProfile(paths, name) {
6259
+ return applyPatch(
6260
+ globalConfigPath(paths),
6261
+ GlobalConfigSchema,
6262
+ { defaultConfigProfile: name },
6263
+ { defaults: {} }
6264
+ );
6265
+ }
6266
+ function validateCategoryNames(patch) {
6267
+ for (const key of Object.keys(patch)) {
6268
+ if (!isOverridableCategory(key)) {
6269
+ throw new InvalidCategoryNameError(key);
6270
+ }
6127
6271
  }
6128
6272
  }
6129
- function describeSource(source) {
6130
- return formatSourceSummary(summariseSource(source));
6273
+ function setProfileCategories(paths, name, patch) {
6274
+ requireProfileExists(paths, name);
6275
+ const expandedPatch = expandAllCategoryKey(patch);
6276
+ validateCategoryNames(expandedPatch);
6277
+ const existing = readProfile(paths, name) ?? {};
6278
+ const mergedCategories = { ...existing.categories, ...expandedPatch };
6279
+ return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { categories: mergedCategories });
6131
6280
  }
6132
- function formatCredentialSummary(summary) {
6133
- 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`}`;
6134
- return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
6281
+ function setProfileEntries(paths, name, patch) {
6282
+ requireProfileExists(paths, name);
6283
+ const existing = readProfile(paths, name) ?? {};
6284
+ const mergedEntries = { ...existing.entries, ...patch };
6285
+ return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, { entries: mergedEntries });
6135
6286
  }
6136
- function describeCredential(credential) {
6137
- return formatCredentialSummary(summariseCredential(credential));
6287
+ function setProfileLaunchFlags(paths, name, patch) {
6288
+ requireProfileExists(paths, name);
6289
+ const existing = readProfile(paths, name) ?? {};
6290
+ const mergedLaunch = { ...existing.launch, ...patch };
6291
+ return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
6292
+ // Read as `unknown` so the check sees the `undefined` a cleared key holds, which LaunchFlags' optional properties hide from the value type.
6293
+ launch: Object.values(mergedLaunch).every((value) => value === void 0) ? void 0 : mergedLaunch
6294
+ });
6138
6295
  }
6139
- function trySource(source, params) {
6140
- const none = (reason, warn = false) => ({ ok: false, reason, warn });
6141
- if ("env" in source) {
6142
- const value = params.env[source.env];
6143
- return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
6144
- }
6145
- if ("literal" in source) {
6146
- return { ok: true, token: source.literal };
6147
- }
6148
- if ("file" in source) {
6149
- const read = params.port.readSecretFile(source.file);
6150
- if (!read.found) {
6151
- return none("does not exist");
6152
- }
6153
- if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
6154
- return none(
6155
- `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
6156
- true
6157
- );
6158
- }
6159
- const token2 = read.content.trim();
6160
- return token2 === "" ? none("is empty") : { ok: true, token: token2 };
6296
+ function setProfileMetadata(paths, name, patch) {
6297
+ requireProfileExists(paths, name);
6298
+ return applyPatch(profileJsonPath(paths, name), ConfigProfileSchema, {
6299
+ ...patch.extends === void 0 ? {} : { extends: patch.extends.length === 0 ? void 0 : [...patch.extends] },
6300
+ ...patch.description === void 0 ? {} : { description: patch.description === false ? void 0 : patch.description }
6301
+ });
6302
+ }
6303
+ function removeProfile(paths, name) {
6304
+ requireProfileExists(paths, name);
6305
+ import_node_fs3.default.rmSync(profileJsonPath(paths, name));
6306
+ }
6307
+
6308
+ // src/poolStore.ts
6309
+ var PoolNotFoundError = class extends CliError {
6310
+ constructor(poolName) {
6311
+ super(`No pool named "${poolName}". Run \`agent-shim pool add ${poolName} --identity <name>...\` first.`);
6312
+ this.poolName = poolName;
6313
+ this.name = "PoolNotFoundError";
6161
6314
  }
6162
- const argv = commandArgv(source);
6163
- const interactive = isInteractiveSource(source, params.env);
6164
- if (interactive && !params.port.personPresent()) {
6165
- return none("needs a person to approve it, but there is no terminal or desktop session");
6315
+ poolName;
6316
+ };
6317
+ var PoolAlreadyExistsError = class extends CliError {
6318
+ constructor(poolName) {
6319
+ super(`A pool named "${poolName}" already exists. Use \`agent-shim pool set ${poolName}\` to change its members.`);
6320
+ this.poolName = poolName;
6321
+ this.name = "PoolAlreadyExistsError";
6166
6322
  }
6167
- const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
6168
- const result = params.port.runCommand(argv, { timeoutMs, interactive });
6169
- if (result.timedOut) {
6170
- return none(`timed out after ${String(timeoutMs)}ms`);
6323
+ poolName;
6324
+ };
6325
+ var InvalidPoolNameError = class extends CliError {
6326
+ constructor(attemptedName) {
6327
+ 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.`);
6328
+ this.attemptedName = attemptedName;
6329
+ this.name = "InvalidPoolNameError";
6171
6330
  }
6172
- if (result.status !== 0) {
6173
- const stderr = result.stderr.trim();
6174
- const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
6175
- return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
6331
+ attemptedName;
6332
+ };
6333
+ function readPools(paths) {
6334
+ return readGlobalConfig(paths)?.pools ?? {};
6335
+ }
6336
+ function requirePool(paths, name) {
6337
+ const pool = readPools(paths)[name];
6338
+ if (pool === void 0) {
6339
+ throw new PoolNotFoundError(name);
6176
6340
  }
6177
- const token = result.stdout.trim();
6178
- return token === "" ? none("printed no token") : { ok: true, token };
6341
+ return pool;
6179
6342
  }
6180
- function warmCommandFor(subject) {
6181
- const [kind, name] = subject.split(" ");
6182
- return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
6343
+ function writePools(paths, pools) {
6344
+ applyPatch(paths.globalConfigFile, GlobalConfigSchema, { pools }, { defaults: {} });
6183
6345
  }
6184
- function resolveCredential(params) {
6185
- const target = params.credential.target ?? "bearer";
6186
- const cacheBlock = params.credential.cache;
6187
- const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
6188
- const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
6189
- if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
6190
- const entry = cache.port.read(store, params.subject);
6191
- if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
6192
- return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
6193
- }
6346
+ function addPool(paths, name, identities) {
6347
+ if (!PoolNameSchema.safeParse(name).success) {
6348
+ throw new InvalidPoolNameError(name);
6194
6349
  }
6195
- const attempts = [];
6196
- const warnings = [];
6197
- for (const source of params.credential.sources) {
6198
- const outcome = trySource(source, params);
6199
- if (outcome.ok) {
6200
- if (cache !== void 0 && store !== void 0) {
6201
- cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
6202
- }
6203
- return { ok: true, credential: { target, token: outcome.token, source, warnings } };
6204
- }
6205
- const line = `${describeSource(source)} ${outcome.reason}`;
6206
- attempts.push(line);
6207
- if (outcome.warn) {
6208
- warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
6209
- }
6350
+ const pools = readPools(paths);
6351
+ if (pools[name] !== void 0) {
6352
+ throw new PoolAlreadyExistsError(name);
6210
6353
  }
6211
- const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
6212
- return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
6354
+ const pool = { identities: [...identities] };
6355
+ writePools(paths, { ...pools, [name]: pool });
6356
+ return pool;
6213
6357
  }
6214
- function credentialVariables(target, token) {
6215
- return {
6216
- ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
6217
- ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
6218
- CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
6219
- };
6358
+ function setPool(paths, name, identities) {
6359
+ requirePool(paths, name);
6360
+ const pool = { identities: [...identities] };
6361
+ writePools(paths, { ...readPools(paths), [name]: pool });
6362
+ return pool;
6363
+ }
6364
+ function removePool(paths, name) {
6365
+ requirePool(paths, name);
6366
+ const remaining = Object.fromEntries(Object.entries(readPools(paths)).filter(([existing]) => existing !== name));
6367
+ writePools(paths, remaining);
6220
6368
  }
6221
6369
 
6222
- // src/providersStore.ts
6223
- var ProviderNotFoundError = class extends CliError {
6370
+ // src/identityStore.ts
6371
+ var IdentityNotFoundError = class extends CliError {
6224
6372
  constructor(name) {
6225
- super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
6373
+ super(`No identity named "${name}". Run \`agent-shim identity add ${name}\` first.`);
6226
6374
  this.name = name;
6227
- this.name = "ProviderNotFoundError";
6375
+ this.name = "IdentityNotFoundError";
6228
6376
  }
6229
6377
  name;
6230
6378
  };
6231
- var ProviderAlreadyExistsError = class extends CliError {
6232
- constructor(providerName) {
6233
- super(`A provider named "${providerName}" already exists.`);
6234
- this.providerName = providerName;
6235
- this.name = "ProviderAlreadyExistsError";
6379
+ var IdentityAlreadyExistsError = class extends CliError {
6380
+ constructor(identityName) {
6381
+ super(`An identity named "${identityName}" already exists.`);
6382
+ this.identityName = identityName;
6383
+ this.name = "IdentityAlreadyExistsError";
6236
6384
  }
6237
- providerName;
6385
+ identityName;
6238
6386
  };
6239
- var InvalidProviderNameError = class extends CliError {
6387
+ var InvalidIdentityNameError = class extends CliError {
6240
6388
  constructor(attemptedName) {
6241
6389
  super(
6242
- `"${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.`
6390
+ `"${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.`
6243
6391
  );
6244
6392
  this.attemptedName = attemptedName;
6245
- this.name = "InvalidProviderNameError";
6393
+ this.name = "InvalidIdentityNameError";
6246
6394
  }
6247
6395
  attemptedName;
6248
6396
  };
6249
- var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
6250
- function providerJsonPath(paths, name) {
6251
- return import_node_path16.default.join(paths.providersDir, `${name}.json`);
6252
- }
6253
- function providerExists(paths, name) {
6254
- return import_node_fs5.default.existsSync(providerJsonPath(paths, name));
6397
+ function identityJsonPath(paths, name) {
6398
+ return import_node_path16.default.join(paths.identitiesDir, name, "identity.json");
6255
6399
  }
6256
- var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
6257
- var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
6258
- var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
6259
- function isRecord3(value) {
6260
- return typeof value === "object" && value !== null && !Array.isArray(value);
6400
+ function identityExists(paths, name) {
6401
+ return import_node_fs4.default.existsSync(identityJsonPath(paths, name));
6261
6402
  }
6262
- function legacyProviderConversion(raw) {
6263
- if (!isRecord3(raw) || "credential" in raw) {
6264
- return void 0;
6265
- }
6266
- const env = isRecord3(raw.env) ? raw.env : void 0;
6267
- const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
6268
- const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
6269
- if (fields.length === 0) {
6270
- return void 0;
6271
- }
6272
- const sources = [];
6273
- if (typeof raw.tokenEnv === "string") {
6274
- sources.push({ env: raw.tokenEnv });
6275
- }
6276
- if (Array.isArray(raw.tokenCommand)) {
6277
- sources.push({ command: raw.tokenCommand });
6278
- }
6279
- if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
6280
- sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
6281
- }
6282
- const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
6283
- const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
6284
- const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
6285
- return {
6286
- fields,
6287
- replacement: {
6288
- ...rest,
6289
- credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
6290
- ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
6291
- }
6292
- };
6403
+ function readIdentity(paths, name) {
6404
+ return readJson(identityJsonPath(paths, name), IdentitySchema);
6293
6405
  }
6294
- var LegacyProviderFileError = class extends CliError {
6295
- constructor(filePath, conversion) {
6296
- super(
6297
- `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
6298
- ` + JSON.stringify(conversion.replacement, null, 2)
6299
- );
6300
- this.filePath = filePath;
6301
- this.conversion = conversion;
6302
- this.name = "LegacyProviderFileError";
6303
- }
6304
- filePath;
6305
- conversion;
6306
- };
6307
- function parseProviderFile(filePath, raw) {
6308
- const conversion = legacyProviderConversion(raw);
6309
- if (conversion !== void 0) {
6310
- throw new LegacyProviderFileError(filePath, conversion);
6406
+ function addIdentity(paths, name) {
6407
+ if (identityExists(paths, name)) {
6408
+ throw new IdentityAlreadyExistsError(name);
6311
6409
  }
6312
- const parsed = ProviderSchema.safeParse(raw);
6410
+ const parsed = IdentitySchema.safeParse({ name, allowAmbientCredential: false });
6313
6411
  if (!parsed.success) {
6314
- throw new ConfigValidationError(filePath, parsed.error.issues);
6412
+ throw new InvalidIdentityNameError(name);
6315
6413
  }
6414
+ writeJsonAtomic(identityJsonPath(paths, name), parsed.data);
6316
6415
  return parsed.data;
6317
6416
  }
6318
- function readProvider(paths, name) {
6319
- const filePath = providerJsonPath(paths, name);
6320
- if (!import_node_fs5.default.existsSync(filePath)) {
6417
+ function useIdentity(paths, name) {
6418
+ const pool = poolNameOf(name);
6419
+ if (pool !== void 0) {
6420
+ requirePool(paths, pool);
6421
+ } else if (!identityExists(paths, name)) {
6422
+ throw new IdentityNotFoundError(name);
6423
+ }
6424
+ writeTextAtomic(paths.activeIdentityFile, `${name}
6425
+ `);
6426
+ }
6427
+ function readActiveIdentity(paths) {
6428
+ if (!import_node_fs4.default.existsSync(paths.activeIdentityFile)) {
6321
6429
  return void 0;
6322
6430
  }
6323
- const raw = JSON.parse(import_node_fs5.default.readFileSync(filePath, "utf8"));
6324
- return parseProviderFile(filePath, raw);
6431
+ const raw = import_node_fs4.default.readFileSync(paths.activeIdentityFile, "utf8").trim();
6432
+ return raw === "" ? void 0 : raw;
6325
6433
  }
6326
- function loadProvider(providersDir, name, port) {
6327
- const filePath = import_node_path16.default.join(providersDir, `${name}.json`);
6328
- const raw = port.readConfigFile(filePath);
6329
- return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
6434
+ function isIdentityDirectoryName(name) {
6435
+ return !name.startsWith(".");
6330
6436
  }
6331
- function listProviders(paths) {
6332
- const names = listProviderNames(
6333
- (dir) => import_node_fs5.default.readdirSync(dir),
6334
- paths.providersDir
6335
- );
6437
+ function readIdentityForListing(paths, name) {
6438
+ try {
6439
+ return readIdentity(paths, name);
6440
+ } catch (error) {
6441
+ if (error instanceof ConfigValidationError || error instanceof SyntaxError) {
6442
+ return { problem: error.message.replace(/\s*\n\s*/g, " ") };
6443
+ }
6444
+ throw error;
6445
+ }
6446
+ }
6447
+ function listIdentities(paths) {
6448
+ if (!import_node_fs4.default.existsSync(paths.identitiesDir)) {
6449
+ return [];
6450
+ }
6451
+ const active = readActiveIdentity(paths);
6452
+ const names = import_node_fs4.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort();
6336
6453
  const result = [];
6337
6454
  for (const name of names) {
6338
- let provider;
6339
- try {
6340
- provider = readProvider(paths, name);
6341
- } catch (error) {
6342
- if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
6343
- throw error;
6344
- }
6345
- console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
6455
+ const read = readIdentityForListing(paths, name);
6456
+ if (read === void 0) {
6346
6457
  continue;
6347
6458
  }
6348
- if (provider !== void 0) {
6349
- result.push({ name, provider });
6350
- }
6459
+ const isActive = name === active;
6460
+ result.push("problem" in read ? { name, isActive, problem: read.problem } : { name, identity: read, isActive });
6351
6461
  }
6352
6462
  return result;
6353
6463
  }
6354
- function listProviderNames(readdir, providersDir) {
6355
- let entries;
6464
+ function setDefaultConfigProfile(paths, identityName, profileName) {
6465
+ if (!identityExists(paths, identityName)) {
6466
+ throw new IdentityNotFoundError(identityName);
6467
+ }
6468
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
6469
+ defaultConfigProfile: profileName
6470
+ });
6471
+ }
6472
+ function setAllowAmbientCredential(paths, identityName, allow) {
6473
+ if (!identityExists(paths, identityName)) {
6474
+ throw new IdentityNotFoundError(identityName);
6475
+ }
6476
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
6477
+ allowAmbientCredential: allow
6478
+ });
6479
+ }
6480
+ function setIdentityCredential(paths, identityName, change) {
6481
+ const existing = readIdentity(paths, identityName);
6482
+ if (existing === void 0) {
6483
+ throw new IdentityNotFoundError(identityName);
6484
+ }
6485
+ if (change === false) {
6486
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { credential: void 0 });
6487
+ }
6488
+ const sources = change.sources ?? existing.credential?.sources;
6489
+ if (sources === void 0) {
6490
+ throw new UsageError(`Identity "${identityName}" has no credential yet: pass --credential to give it one before choosing its target.`);
6491
+ }
6492
+ const target = change.target ?? existing.credential?.target;
6493
+ const cache = change.cache === void 0 ? existing.credential?.cache : change.cache === false ? void 0 : change.cache;
6494
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, {
6495
+ credential: { sources: [...sources], ...target === void 0 ? {} : { target }, ...cache === void 0 ? {} : { cache } }
6496
+ });
6497
+ }
6498
+ function belongsToIdentity(entry, name) {
6499
+ return entry === name || entry === import_node_path16.default.basename(identityLockPath("", name)) || entry.startsWith(`.${name}.scratch.`) || entry.startsWith(`.${name}.previous.`);
6500
+ }
6501
+ function removeIdentity(paths, name) {
6502
+ if (!identityExists(paths, name)) {
6503
+ throw new IdentityNotFoundError(name);
6504
+ }
6505
+ for (const entry of import_node_fs4.default.readdirSync(paths.identitiesDir)) {
6506
+ if (belongsToIdentity(entry, name)) {
6507
+ import_node_fs4.default.rmSync(import_node_path16.default.join(paths.identitiesDir, entry), { recursive: true, force: true });
6508
+ }
6509
+ }
6510
+ if (readActiveIdentity(paths) === name) {
6511
+ import_node_fs4.default.rmSync(paths.activeIdentityFile);
6512
+ }
6513
+ }
6514
+
6515
+ // src/providersStore.ts
6516
+ var import_node_fs5 = __toESM(require("node:fs"), 1);
6517
+ var import_node_path17 = __toESM(require("node:path"), 1);
6518
+
6519
+ // src/credentialCache.ts
6520
+ var import_zod11 = require("zod");
6521
+ var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
6522
+ var CachedCredentialSchema = import_zod11.z.strictObject({
6523
+ token: import_zod11.z.string().min(1),
6524
+ fetchedAt: import_zod11.z.number().int().nonnegative(),
6525
+ source: CredentialSourceSchema
6526
+ });
6527
+ function effectiveStore(cache, platform) {
6528
+ return cache.store ?? (platform === "darwin" ? "keychain" : "file");
6529
+ }
6530
+ function ttlMs(cache) {
6531
+ if (cache.ttl === void 0) {
6532
+ return void 0;
6533
+ }
6534
+ const multiplier = MS_PER_UNIT[cache.ttl.slice(-1)];
6535
+ if (multiplier === void 0) {
6536
+ throw new Error(`credential cache ttl "${cache.ttl}" has no recognised unit`);
6537
+ }
6538
+ return Number(cache.ttl.slice(0, -1)) * multiplier;
6539
+ }
6540
+ function isFresh(entry, cache, now) {
6541
+ const limit = ttlMs(cache);
6542
+ if (entry.fetchedAt > now) {
6543
+ return false;
6544
+ }
6545
+ return limit === void 0 || now - entry.fetchedAt < limit;
6546
+ }
6547
+ function cacheAgeMs(entry, now) {
6548
+ return Math.max(0, now - entry.fetchedAt);
6549
+ }
6550
+ function describeCachedCredential(params) {
6551
+ const store = effectiveStore(params.cache, params.platform);
6552
+ let entry;
6356
6553
  try {
6357
- entries = readdir(providersDir);
6358
- } catch {
6359
- return [];
6554
+ entry = params.port.read(store, params.owner);
6555
+ } catch (error) {
6556
+ return { store, status: "unreadable", reason: error instanceof Error ? error.message : String(error) };
6360
6557
  }
6361
- return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
6558
+ if (entry === void 0) {
6559
+ return { store, status: "empty" };
6560
+ }
6561
+ const limit = ttlMs(params.cache);
6562
+ const ageMs = cacheAgeMs(entry, params.nowMs);
6563
+ return {
6564
+ store,
6565
+ status: isFresh(entry, params.cache, params.nowMs) ? "fresh" : "expired",
6566
+ ageMs,
6567
+ ...limit === void 0 ? {} : { expiresInMs: limit - ageMs }
6568
+ };
6362
6569
  }
6363
- function describeProviderEndpoint(provider) {
6364
- return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
6570
+ function formatCachedCredentialState(state) {
6571
+ if (state.status === "empty") {
6572
+ return `no cached entry in the ${state.store} store yet`;
6573
+ }
6574
+ if (state.status === "unreadable") {
6575
+ return `the ${state.store} store cannot be read here: ${state.reason}`;
6576
+ }
6577
+ if (state.status === "expired" && (state.expiresInMs === void 0 || state.expiresInMs > 0)) {
6578
+ return "cached with a timestamp in the future, treated as expired";
6579
+ }
6580
+ const age = `cached ${formatAge(state.ageMs)} ago`;
6581
+ if (state.expiresInMs === void 0) {
6582
+ return `${age}, no expiry`;
6583
+ }
6584
+ return state.status === "fresh" ? `${age}, expires in ${formatAge(state.expiresInMs)}` : `${age}, expired ${formatAge(-state.expiresInMs)} ago`;
6365
6585
  }
6366
- var ProviderKindMismatchError = class extends UsageError {
6367
- constructor(message) {
6368
- super(message);
6369
- this.name = "ProviderKindMismatchError";
6586
+
6587
+ // src/credential.ts
6588
+ var CREDENTIAL_UNAVAILABLE_EXIT = 64;
6589
+ var CREDENTIAL_COMMAND_TIMEOUT_MS = 1e4;
6590
+ var CREDENTIAL_INTERACTIVE_TIMEOUT_MS = 12e4;
6591
+ var GROUP_OTHER_PERMISSION_BITS = 63;
6592
+ var PERMISSION_BITS = 511;
6593
+ var OCTAL_RADIX2 = 8;
6594
+ function commandArgv(source) {
6595
+ if ("command" in source) {
6596
+ return source.command;
6597
+ }
6598
+ if ("op" in source) {
6599
+ return ["op", "read", source.op];
6600
+ }
6601
+ const account = source.keychain.account;
6602
+ return ["security", "find-generic-password", "-s", source.keychain.service, ...account === void 0 ? [] : ["-a", account], "-w"];
6603
+ }
6604
+ function isInteractiveSource(source, env) {
6605
+ if ("command" in source || "keychain" in source) {
6606
+ return source.interactive ?? false;
6370
6607
  }
6371
- };
6372
- function isEmptyCodexConfig(config) {
6373
- return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
6608
+ if ("op" in source) {
6609
+ return source.interactive ?? (env.OP_SERVICE_ACCOUNT_TOKEN ?? "") === "";
6610
+ }
6611
+ return false;
6374
6612
  }
6375
- function addProvider(paths, name, input) {
6376
- if (providerExists(paths, name)) {
6377
- throw new ProviderAlreadyExistsError(name);
6613
+ function summariseSource(source) {
6614
+ if ("env" in source) {
6615
+ return { kind: "env", variable: source.env };
6378
6616
  }
6379
- if (!PROVIDER_NAME_RE.test(name)) {
6380
- throw new InvalidProviderNameError(name);
6617
+ if ("file" in source) {
6618
+ return { kind: "file", path: source.file };
6381
6619
  }
6382
- const kind = input.kind ?? "http";
6383
- if (kind === "codex" && input.baseUrl !== void 0) {
6384
- throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
6620
+ if ("command" in source) {
6621
+ return { kind: "command", program: source.command[0] };
6385
6622
  }
6386
- if (kind === "http" && input.baseUrl === void 0) {
6387
- throw new ProviderKindMismatchError("An http provider needs --base-url.");
6623
+ if ("op" in source) {
6624
+ return { kind: "op", reference: source.op };
6388
6625
  }
6389
- if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
6390
- throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
6626
+ if ("keychain" in source) {
6627
+ return {
6628
+ kind: "keychain",
6629
+ service: source.keychain.service,
6630
+ ...source.keychain.account === void 0 ? {} : { account: source.keychain.account }
6631
+ };
6391
6632
  }
6392
- const shared = {
6393
- displayName: input.displayName,
6394
- credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
6395
- ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
6633
+ return { kind: "literal" };
6634
+ }
6635
+ function summariseCredential(credential) {
6636
+ return {
6637
+ target: credential.target ?? "bearer",
6638
+ sources: credential.sources.map(summariseSource),
6639
+ ...credential.cache === void 0 ? {} : { cache: credential.cache }
6396
6640
  };
6397
- const parsed = ProviderSchema.safeParse(
6398
- kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
6399
- );
6400
- if (!parsed.success) {
6401
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
6641
+ }
6642
+ function formatSourceSummary(summary) {
6643
+ switch (summary.kind) {
6644
+ case "env":
6645
+ return `env ${summary.variable}`;
6646
+ case "file":
6647
+ return `file ${summary.path}`;
6648
+ case "command":
6649
+ return `command ${summary.program}`;
6650
+ case "op":
6651
+ return `op ${summary.reference}`;
6652
+ case "keychain":
6653
+ return `keychain ${summary.service}${summary.account === void 0 ? "" : ` (account ${summary.account})`}`;
6654
+ case "literal":
6655
+ return "literal (non-secret placeholder)";
6656
+ default:
6657
+ return summary;
6402
6658
  }
6403
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
6404
- return parsed.data;
6405
6659
  }
6406
- function updateProvider(paths, name, input) {
6407
- const existing = readProvider(paths, name);
6408
- if (existing === void 0) {
6409
- throw new ProviderNotFoundError(name);
6660
+ function describeSource(source) {
6661
+ return formatSourceSummary(summariseSource(source));
6662
+ }
6663
+ function formatCredentialSummary(summary) {
6664
+ 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`}`;
6665
+ return `${summary.target} from ${summary.sources.map(formatSourceSummary).join(", then ")}${cache}`;
6666
+ }
6667
+ function describeCredential(credential) {
6668
+ return formatCredentialSummary(summariseCredential(credential));
6669
+ }
6670
+ function trySource(source, params) {
6671
+ const none = (reason, warn = false) => ({ ok: false, reason, warn });
6672
+ if ("env" in source) {
6673
+ const value = params.env[source.env];
6674
+ return value === void 0 || value === "" ? none("is unset or empty") : { ok: true, token: value };
6410
6675
  }
6411
- const unset = new Set(input.unsetEnv);
6412
- const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
6413
- const target = input.target ?? existing.credential.target;
6414
- const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
6415
- if (isCodexProvider(existing) && input.baseUrl !== void 0) {
6416
- throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
6676
+ if ("literal" in source) {
6677
+ return { ok: true, token: source.literal };
6417
6678
  }
6418
- if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
6419
- throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
6679
+ if ("file" in source) {
6680
+ const read = params.port.readSecretFile(source.file);
6681
+ if (!read.found) {
6682
+ return none("does not exist");
6683
+ }
6684
+ if (read.mode !== void 0 && (read.mode & GROUP_OTHER_PERMISSION_BITS) !== 0) {
6685
+ return none(
6686
+ `is readable or writable by group or others (mode ${(read.mode & PERMISSION_BITS).toString(OCTAL_RADIX2)}); run \`chmod 600 ${source.file}\``,
6687
+ true
6688
+ );
6689
+ }
6690
+ const token2 = read.content.trim();
6691
+ return token2 === "" ? none("is empty") : { ok: true, token: token2 };
6420
6692
  }
6421
- const shared = {
6422
- ...input.displayName === void 0 ? {} : { displayName: input.displayName },
6423
- credential: {
6424
- sources: input.sources ?? existing.credential.sources,
6425
- ...target === void 0 ? {} : { target },
6426
- ...cache === void 0 ? {} : { cache }
6427
- },
6428
- env: Object.keys(env).length === 0 ? void 0 : env
6429
- };
6430
- let candidate;
6431
- if (isCodexProvider(existing)) {
6432
- const codex = {
6433
- ...existing.codex,
6434
- ...input.codex,
6435
- ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
6436
- };
6437
- candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
6438
- } else {
6439
- candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
6693
+ const argv = commandArgv(source);
6694
+ const interactive = isInteractiveSource(source, params.env);
6695
+ if (interactive && !params.port.personPresent()) {
6696
+ return none("needs a person to approve it, but there is no terminal or desktop session");
6440
6697
  }
6441
- const parsed = ProviderSchema.safeParse(candidate);
6442
- if (!parsed.success) {
6443
- throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
6698
+ const timeoutMs = source.timeoutMs ?? (interactive ? CREDENTIAL_INTERACTIVE_TIMEOUT_MS : CREDENTIAL_COMMAND_TIMEOUT_MS);
6699
+ const result = params.port.runCommand(argv, { timeoutMs, interactive });
6700
+ if (result.timedOut) {
6701
+ return none(`timed out after ${String(timeoutMs)}ms`);
6444
6702
  }
6445
- writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
6446
- return parsed.data;
6447
- }
6448
- function removeProvider(paths, name) {
6449
- if (!providerExists(paths, name)) {
6450
- throw new ProviderNotFoundError(name);
6703
+ if (result.status !== 0) {
6704
+ const stderr = result.stderr.trim();
6705
+ const outcome = result.status === null ? "could not be run or was killed by a signal" : `exited with status ${String(result.status)}`;
6706
+ return none(`${outcome}${stderr === "" ? "" : `: ${stderr}`}`);
6451
6707
  }
6452
- import_node_fs5.default.rmSync(providerJsonPath(paths, name));
6708
+ const token = result.stdout.trim();
6709
+ return token === "" ? none("printed no token") : { ok: true, token };
6453
6710
  }
6454
- function cascadeProviderName(cascade) {
6455
- let name;
6456
- for (const layer of assembleCascade(cascade).layers) {
6457
- if (layer.launch?.provider !== void 0) {
6458
- name = layer.launch.provider;
6459
- }
6460
- }
6461
- return name;
6711
+ function warmCommandFor(subject) {
6712
+ const [kind, name] = subject.split(" ");
6713
+ return kind === "provider" ? `agent-shim credential warm --provider ${name ?? ""}` : `agent-shim credential warm ${name ?? ""}`;
6462
6714
  }
6463
- function resolveProvider(params) {
6464
- const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
6465
- const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
6466
- if (name === void 0) {
6467
- return void 0;
6468
- }
6469
- const definition = loadProvider(params.paths.providersDir, name, params.port);
6470
- if (definition === void 0) {
6471
- const known = listProviderNames(params.port.readdir, params.paths.providersDir);
6472
- return {
6473
- ok: false,
6474
- status: 1,
6475
- message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
6476
- };
6477
- }
6478
- if (params.credentials === void 0) {
6479
- return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
6715
+ function resolveCredential(params) {
6716
+ const target = params.credential.target ?? "bearer";
6717
+ const cacheBlock = params.credential.cache;
6718
+ const cache = cacheBlock === void 0 ? void 0 : params.port.cache;
6719
+ const store = cacheBlock === void 0 || cache === void 0 ? void 0 : effectiveStore(cacheBlock, cache.platform);
6720
+ if (cacheBlock !== void 0 && cache !== void 0 && store !== void 0 && params.refresh !== true) {
6721
+ const entry = cache.port.read(store, params.subject);
6722
+ if (entry !== void 0 && isFresh(entry, cacheBlock, cache.now())) {
6723
+ return { ok: true, credential: { target, token: entry.token, source: entry.source, warnings: [], cachedAt: entry.fetchedAt } };
6724
+ }
6480
6725
  }
6481
- const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
6482
- if (!resolution.ok) {
6483
- return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
6726
+ const attempts = [];
6727
+ const warnings = [];
6728
+ for (const source of params.credential.sources) {
6729
+ const outcome = trySource(source, params);
6730
+ if (outcome.ok) {
6731
+ if (cache !== void 0 && store !== void 0) {
6732
+ cache.port.write(store, params.subject, { token: outcome.token, fetchedAt: cache.now(), source });
6733
+ }
6734
+ return { ok: true, credential: { target, token: outcome.token, source, warnings } };
6735
+ }
6736
+ const line = `${describeSource(source)} ${outcome.reason}`;
6737
+ attempts.push(line);
6738
+ if (outcome.warn) {
6739
+ warnings.push(`agent-shim: ${params.subject}: skipped ${line}`);
6740
+ }
6484
6741
  }
6485
- return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
6742
+ const hint = cacheBlock === void 0 ? "" : `; fill the cache from a terminal with \`${warmCommandFor(params.subject)}\``;
6743
+ return { ok: false, message: `agent-shim: ${params.subject} has no usable credential: ${attempts.join("; ")}${hint}` };
6744
+ }
6745
+ function credentialVariables(target, token) {
6746
+ return {
6747
+ ANTHROPIC_AUTH_TOKEN: target === "bearer" ? token : void 0,
6748
+ ANTHROPIC_API_KEY: target === "apiKey" ? token : void 0,
6749
+ CLAUDE_CODE_OAUTH_TOKEN: target === "oauthToken" ? token : void 0
6750
+ };
6486
6751
  }
6487
6752
 
6488
- // src/directoryRulesStore.ts
6489
- var DirectoryRuleNotFoundError = class extends CliError {
6490
- constructor(rulePath) {
6491
- super(`No directory rule found for path "${rulePath}".`);
6492
- this.rulePath = rulePath;
6493
- this.name = "DirectoryRuleNotFoundError";
6753
+ // src/providersStore.ts
6754
+ var ProviderNotFoundError = class extends CliError {
6755
+ constructor(name) {
6756
+ super(`No provider named "${name}". Run \`agent-shim provider add ${name}\` first.`);
6757
+ this.name = name;
6758
+ this.name = "ProviderNotFoundError";
6494
6759
  }
6495
- rulePath;
6760
+ name;
6496
6761
  };
6497
- var DirectoryRuleMissingTargetError = class extends CliError {
6498
- constructor() {
6499
- super("A directory rule must set at least one of --config-profile or --identity.");
6500
- this.name = "DirectoryRuleMissingTargetError";
6762
+ var ProviderAlreadyExistsError = class extends CliError {
6763
+ constructor(providerName) {
6764
+ super(`A provider named "${providerName}" already exists.`);
6765
+ this.providerName = providerName;
6766
+ this.name = "ProviderAlreadyExistsError";
6501
6767
  }
6768
+ providerName;
6502
6769
  };
6503
- var DirectoryRuleAlreadyExistsError = class extends CliError {
6504
- constructor(rulePath) {
6505
- super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
6506
- this.rulePath = rulePath;
6507
- this.name = "DirectoryRuleAlreadyExistsError";
6770
+ var InvalidProviderNameError = class extends CliError {
6771
+ constructor(attemptedName) {
6772
+ super(
6773
+ `"${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.`
6774
+ );
6775
+ this.attemptedName = attemptedName;
6776
+ this.name = "InvalidProviderNameError";
6508
6777
  }
6509
- rulePath;
6778
+ attemptedName;
6510
6779
  };
6511
- function readDirectoryRules(paths) {
6512
- return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
6780
+ var PROVIDER_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._@-]*$/;
6781
+ function providerJsonPath(paths, name) {
6782
+ return import_node_path17.default.join(paths.providersDir, `${name}.json`);
6513
6783
  }
6514
- function writeDirectoryRules(paths, rules) {
6515
- const parsed = DirectoryRulesSchema.safeParse(rules);
6516
- if (!parsed.success) {
6517
- throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
6518
- }
6519
- writeJsonAtomic(paths.directoryRulesFile, parsed.data);
6784
+ function providerExists(paths, name) {
6785
+ return import_node_fs5.default.existsSync(providerJsonPath(paths, name));
6520
6786
  }
6521
- function listDirectoryRules(paths) {
6522
- return readDirectoryRules(paths).rules;
6787
+ var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
6788
+ var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
6789
+ var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
6790
+ function isRecord3(value) {
6791
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6523
6792
  }
6524
- function addDirectoryRule(paths, rulePath, options) {
6525
- if (options.configProfile === void 0 && options.identity === void 0) {
6526
- throw new DirectoryRuleMissingTargetError();
6793
+ function legacyProviderConversion(raw) {
6794
+ if (!isRecord3(raw) || "credential" in raw) {
6795
+ return void 0;
6527
6796
  }
6528
- const current = readDirectoryRules(paths);
6529
- if (current.rules.some((rule) => rule.path === rulePath)) {
6530
- throw new DirectoryRuleAlreadyExistsError(rulePath);
6797
+ const env = isRecord3(raw.env) ? raw.env : void 0;
6798
+ const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
6799
+ const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
6800
+ if (fields.length === 0) {
6801
+ return void 0;
6531
6802
  }
6532
- const created = buildNewRule(rulePath, options);
6533
- writeDirectoryRules(paths, { ...current, rules: [...current.rules, created] });
6534
- return created;
6535
- }
6536
- function ruleHasEffect(rule) {
6537
- return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
6538
- }
6539
- function updateDirectoryRule(paths, rulePath, options) {
6540
- const current = readDirectoryRules(paths);
6541
- const index = current.rules.findIndex((rule) => rule.path === rulePath);
6542
- const existing = index === -1 ? void 0 : current.rules[index];
6543
- if (existing === void 0) {
6544
- throw new DirectoryRuleNotFoundError(rulePath);
6803
+ const sources = [];
6804
+ if (typeof raw.tokenEnv === "string") {
6805
+ sources.push({ env: raw.tokenEnv });
6545
6806
  }
6546
- const { configProfile, identity, ...rest } = existing;
6547
- const nextConfigProfile = options.configProfile === void 0 ? configProfile : options.configProfile === false ? void 0 : options.configProfile;
6548
- const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
6549
- const updated = {
6550
- ...rest,
6551
- ...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
6552
- ...nextIdentity === void 0 ? {} : { identity: nextIdentity }
6553
- };
6554
- if (!ruleHasEffect(updated)) {
6555
- throw new DirectoryRuleMissingTargetError();
6807
+ if (Array.isArray(raw.tokenCommand)) {
6808
+ sources.push({ command: raw.tokenCommand });
6556
6809
  }
6557
- const nextRules = [...current.rules];
6558
- nextRules[index] = updated;
6559
- writeDirectoryRules(paths, { ...current, rules: nextRules });
6560
- return updated;
6561
- }
6562
- function buildNewRule(rulePath, options) {
6810
+ if (typeof env?.ANTHROPIC_AUTH_TOKEN === "string" && env.ANTHROPIC_AUTH_TOKEN !== "") {
6811
+ sources.push({ literal: LEGACY_LITERAL_PLACEHOLDER });
6812
+ }
6813
+ const remainingEnv = env === void 0 ? void 0 : Object.fromEntries(Object.entries(env).filter(([key]) => !LEGACY_ENV_CREDENTIAL_KEYS.includes(key)));
6814
+ const replacedKeys = [...LEGACY_PROVIDER_FIELDS, "env"];
6815
+ const rest = Object.fromEntries(Object.entries(raw).filter(([key]) => !replacedKeys.includes(key)));
6563
6816
  return {
6564
- path: rulePath,
6565
- ...options.configProfile !== void 0 ? { configProfile: options.configProfile } : {},
6566
- ...options.identity !== void 0 ? { identity: options.identity } : {}
6817
+ fields,
6818
+ replacement: {
6819
+ ...rest,
6820
+ credential: { sources, ...raw.authScheme === "apiKey" ? { target: "apiKey" } : {} },
6821
+ ...remainingEnv === void 0 || Object.keys(remainingEnv).length === 0 ? {} : { env: remainingEnv }
6822
+ }
6567
6823
  };
6568
6824
  }
6569
- function removeDirectoryRule(paths, rulePath) {
6570
- const current = readDirectoryRules(paths);
6571
- const nextRules = current.rules.filter((rule) => rule.path !== rulePath);
6572
- if (nextRules.length === current.rules.length) {
6573
- throw new DirectoryRuleNotFoundError(rulePath);
6825
+ var LegacyProviderFileError = class extends CliError {
6826
+ constructor(filePath, conversion) {
6827
+ super(
6828
+ `${filePath} uses ${conversion.fields.join(", ")}, which a credential block replaced. Rewrite it as:
6829
+ ` + JSON.stringify(conversion.replacement, null, 2)
6830
+ );
6831
+ this.filePath = filePath;
6832
+ this.conversion = conversion;
6833
+ this.name = "LegacyProviderFileError";
6574
6834
  }
6575
- writeDirectoryRules(paths, { ...current, rules: nextRules });
6576
- }
6577
-
6578
- // src/checkReport.ts
6579
- var import_node_os4 = __toESM(require("node:os"), 1);
6580
-
6581
- // src/launcher/cascade.ts
6582
- var import_node_path18 = __toESM(require("node:path"), 1);
6583
-
6584
- // src/pathNorm.ts
6585
- var import_node_path17 = __toESM(require("node:path"), 1);
6586
- function expandTilde(value, home) {
6587
- if (value === "~") {
6588
- return home;
6835
+ filePath;
6836
+ conversion;
6837
+ };
6838
+ function parseProviderFile(filePath, raw) {
6839
+ const conversion = legacyProviderConversion(raw);
6840
+ if (conversion !== void 0) {
6841
+ throw new LegacyProviderFileError(filePath, conversion);
6589
6842
  }
6590
- if (value.startsWith("~/")) {
6591
- return import_node_path17.default.join(home, value.slice(2));
6843
+ const parsed = ProviderSchema.safeParse(raw);
6844
+ if (!parsed.success) {
6845
+ throw new ConfigValidationError(filePath, parsed.error.issues);
6592
6846
  }
6593
- return value;
6594
- }
6595
- function normaliseRulePath(rulePath, home) {
6596
- return import_node_path17.default.resolve(expandTilde(rulePath, home));
6847
+ return parsed.data;
6597
6848
  }
6598
-
6599
- // src/launcher/cascade.ts
6600
- var PORTABLE_CONFIG_FILENAME = ".agent-shim.json";
6601
- var PORTABLE_LOCAL_CONFIG_FILENAME = ".agent-shim.local.json";
6602
- function loadCascadeInput(params) {
6603
- const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
6604
- const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
6605
- const loadProfile = (name) => {
6606
- const filepath = import_node_path18.default.join(params.paths.configProfilesDir, `${name}.json`);
6607
- const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
6608
- if (loaded === void 0) {
6609
- return void 0;
6610
- }
6611
- return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
6612
- };
6613
- const limit = globalConfig?.config.walkUpLimit;
6614
- const dirs = walkDirectoryAncestors(params.cwd, {
6615
- home: params.home,
6616
- ...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
6617
- ...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
6618
- });
6619
- const levels = [];
6620
- for (const dir of dirs) {
6621
- const portable = loadConfigFile(import_node_path18.default.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path18.default.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
6622
- const portableLocal = loadConfigFile(import_node_path18.default.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path18.default.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
6623
- const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
6624
- const entryOrder = directoryRules?.ruleEntryOrders[index];
6625
- return {
6626
- rule,
6627
- ...entryOrder === void 0 ? {} : { entryOrder },
6628
- filepath: params.paths.directoryRulesFile
6629
- };
6630
- });
6631
- if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
6632
- continue;
6633
- }
6634
- levels.push({
6635
- dir,
6636
- ...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
6637
- ...rules.length === 0 ? {} : { rules },
6638
- ...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
6639
- });
6849
+ function readProvider(paths, name) {
6850
+ const filePath = providerJsonPath(paths, name);
6851
+ if (!import_node_fs5.default.existsSync(filePath)) {
6852
+ return void 0;
6640
6853
  }
6641
- const input = {
6642
- home: params.home,
6643
- ...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
6644
- ...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
6645
- loadProfile,
6646
- levels,
6647
- ...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
6648
- };
6649
- return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
6854
+ const raw = JSON.parse(import_node_fs5.default.readFileSync(filePath, "utf8"));
6855
+ return parseProviderFile(filePath, raw);
6650
6856
  }
6651
- function readDirectorySelections(loaded) {
6652
- let identity;
6653
- let configProfile;
6654
- for (const level of loaded.input.levels ?? []) {
6655
- for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
6656
- if (source === void 0) {
6657
- continue;
6658
- }
6659
- if (source.identity !== void 0) {
6660
- identity = source.identity;
6661
- }
6662
- if (source.configProfile !== void 0) {
6663
- configProfile = source.configProfile;
6857
+ function loadProvider(providersDir, name, port) {
6858
+ const filePath = import_node_path17.default.join(providersDir, `${name}.json`);
6859
+ const raw = port.readConfigFile(filePath);
6860
+ return raw === void 0 ? void 0 : parseProviderFile(filePath, raw);
6861
+ }
6862
+ function listProviders(paths) {
6863
+ const names = listProviderNames(
6864
+ (dir) => import_node_fs5.default.readdirSync(dir),
6865
+ paths.providersDir
6866
+ );
6867
+ const result = [];
6868
+ for (const name of names) {
6869
+ let provider;
6870
+ try {
6871
+ provider = readProvider(paths, name);
6872
+ } catch (error) {
6873
+ if (!(error instanceof ConfigValidationError || error instanceof LegacyProviderFileError)) {
6874
+ throw error;
6664
6875
  }
6876
+ console.error(`agent-shim: providers/${name}.json is invalid and was skipped: ${error.message}`);
6877
+ continue;
6878
+ }
6879
+ if (provider !== void 0) {
6880
+ result.push({ name, provider });
6665
6881
  }
6666
6882
  }
6667
- return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
6668
- }
6669
-
6670
- // src/usage/preflight.ts
6671
- var MS_PER_SECOND2 = 1e3;
6672
- var SECONDS_PER_MINUTE = 60;
6673
- var MINUTES_PER_HOUR = 60;
6674
- var HOURS_PER_DAY = 24;
6675
- var WARNING_STATUSES = /* @__PURE__ */ new Set(["allowed_warning", "rejected"]);
6676
- function formatAge(ms) {
6677
- const minutes = Math.floor(ms / (MS_PER_SECOND2 * SECONDS_PER_MINUTE));
6678
- if (minutes < MINUTES_PER_HOUR) {
6679
- return `${String(Math.max(minutes, 0))}m`;
6680
- }
6681
- const hours = Math.floor(minutes / MINUTES_PER_HOUR);
6682
- return hours < HOURS_PER_DAY ? `${String(hours)}h` : `${String(Math.floor(hours / HOURS_PER_DAY))}d`;
6683
- }
6684
- function effectiveWindow(window, nowMs) {
6685
- const resetsAtMs = window.resetsAt === void 0 ? void 0 : Date.parse(window.resetsAt);
6686
- if (resetsAtMs !== void 0 && resetsAtMs <= nowMs) {
6687
- return { reset: true, utilization: 0 };
6688
- }
6689
- return {
6690
- reset: false,
6691
- ...window.utilization === void 0 ? {} : { utilization: window.utilization },
6692
- ...resetsAtMs === void 0 ? {} : { resetsAtMs },
6693
- ...window.status === void 0 ? {} : { status: window.status }
6694
- };
6883
+ return result;
6695
6884
  }
6696
- function quotaWarnings(snapshot, provider, nowMs) {
6697
- const rateLimit = snapshot?.providers[provider]?.rateLimit;
6698
- const unified = rateLimit?.unified;
6699
- if (snapshot === void 0 || rateLimit === void 0 || unified === void 0) {
6885
+ function listProviderNames(readdir, providersDir) {
6886
+ let entries;
6887
+ try {
6888
+ entries = readdir(providersDir);
6889
+ } catch {
6700
6890
  return [];
6701
6891
  }
6702
- const age = formatAge(nowMs - Date.parse(rateLimit.observedAt));
6703
- const windows = [
6704
- { name: "five-hour", window: unified.fiveHour },
6705
- { name: "seven-day", window: unified.sevenDay }
6706
- ];
6707
- return windows.flatMap(({ name, window }) => {
6708
- const effective = window === void 0 ? void 0 : effectiveWindow(window, nowMs);
6709
- if (window === void 0 || effective?.status === void 0 || !WARNING_STATUSES.has(effective.status)) {
6710
- return [];
6711
- }
6712
- const reset = window.resetsAt === void 0 ? "" : `, resets ${window.resetsAt}`;
6713
- const verdict = effective.status === "rejected" ? "exhausted" : "nearly used";
6714
- return [`agent-shim: identity ${snapshot.identity}: the ${name} quota is ${verdict}${reset} (last seen ${age} ago)`];
6715
- });
6892
+ return entries.filter((entry) => entry.endsWith(".json")).map((entry) => entry.slice(0, -".json".length)).sort();
6716
6893
  }
6717
-
6718
- // src/usage/poolPick.ts
6719
- var import_node_path19 = __toESM(require("node:path"), 1);
6720
- var import_zod9 = require("zod");
6721
-
6722
- // src/usage/plan.ts
6723
- var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
6724
- var ZERO_TIER = /(?:^|_)zero$/;
6725
- var BASE_TIER = /(?:^|_)(?:pro|free)$/;
6726
- function planOf(account) {
6727
- const tier = account?.organizationRateLimitTier ?? account?.userRateLimitTier;
6728
- const tierField = tier === void 0 ? {} : { tier };
6729
- if (tier === void 0) {
6730
- return { kind: "subscription", capacity: 1, recognised: false };
6894
+ function describeProviderEndpoint(provider) {
6895
+ return isCodexProvider(provider) ? "codex translation daemon" : provider.baseUrl;
6896
+ }
6897
+ var ProviderKindMismatchError = class extends UsageError {
6898
+ constructor(message) {
6899
+ super(message);
6900
+ this.name = "ProviderKindMismatchError";
6731
6901
  }
6732
- if (ZERO_TIER.test(tier)) {
6733
- return { kind: "pay-per-use", ...tierField };
6902
+ };
6903
+ function isEmptyCodexConfig(config) {
6904
+ return config === void 0 || config.defaultModel === void 0 && config.effort === void 0 && (config.models === void 0 || Object.keys(config.models).length === 0);
6905
+ }
6906
+ function addProvider(paths, name, input) {
6907
+ if (providerExists(paths, name)) {
6908
+ throw new ProviderAlreadyExistsError(name);
6734
6909
  }
6735
- const multiplier = TIER_MULTIPLIER.exec(tier)?.[1];
6736
- if (multiplier !== void 0) {
6737
- return { kind: "subscription", capacity: Number(multiplier), recognised: true, ...tierField };
6910
+ if (!PROVIDER_NAME_RE.test(name)) {
6911
+ throw new InvalidProviderNameError(name);
6738
6912
  }
6739
- return { kind: "subscription", capacity: 1, recognised: BASE_TIER.test(tier), ...tierField };
6740
- }
6741
-
6742
- // src/usage/rateLimit.ts
6743
- var MS_PER_SECOND3 = 1e3;
6744
- function epochSecondsToIso(value) {
6745
- if (value === void 0) {
6746
- return void 0;
6913
+ const kind = input.kind ?? "http";
6914
+ if (kind === "codex" && input.baseUrl !== void 0) {
6915
+ throw new ProviderKindMismatchError("A codex provider has no base URL: the launcher points its sessions at the front-door daemon. Drop --base-url.");
6747
6916
  }
6748
- const seconds = Number(value);
6749
- return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * MS_PER_SECOND3).toISOString() : void 0;
6750
- }
6751
- function nonNegativeNumber(value) {
6752
- if (value === void 0 || value.trim() === "") {
6753
- return void 0;
6917
+ if (kind === "http" && input.baseUrl === void 0) {
6918
+ throw new ProviderKindMismatchError("An http provider needs --base-url.");
6754
6919
  }
6755
- const parsed = Number(value);
6756
- return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
6757
- }
6758
- var UNIFIED_PREFIX = "anthropic-ratelimit-unified-";
6759
- function unifiedWindow(headers, window) {
6760
- const utilization = nonNegativeNumber(headers[`${UNIFIED_PREFIX}${window}-utilization`]);
6761
- const resetsAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}${window}-reset`]);
6762
- const status = headers[`${UNIFIED_PREFIX}${window}-status`];
6763
- if (utilization === void 0 && resetsAt === void 0 && status === void 0) {
6764
- return void 0;
6920
+ if (kind === "http" && !isEmptyCodexConfig(input.codex)) {
6921
+ throw new ProviderKindMismatchError("Codex settings apply only to a codex provider (--kind codex).");
6765
6922
  }
6766
- return { ...utilization === void 0 ? {} : { utilization }, ...resetsAt === void 0 ? {} : { resetsAt }, ...status === void 0 ? {} : { status } };
6923
+ const shared = {
6924
+ displayName: input.displayName,
6925
+ credential: { sources: input.sources, ...input.target === void 0 ? {} : { target: input.target } },
6926
+ ...input.env === void 0 || Object.keys(input.env).length === 0 ? {} : { env: input.env }
6927
+ };
6928
+ const parsed = ProviderSchema.safeParse(
6929
+ kind === "codex" ? { kind, ...shared, ...isEmptyCodexConfig(input.codex) ? {} : { codex: input.codex } } : { ...shared, baseUrl: input.baseUrl }
6930
+ );
6931
+ if (!parsed.success) {
6932
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
6933
+ }
6934
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
6935
+ return parsed.data;
6767
6936
  }
6768
- function parseUnifiedRateLimit(headers) {
6769
- if (!Object.keys(headers).some((name) => name.startsWith(UNIFIED_PREFIX))) {
6770
- return void 0;
6937
+ function updateProvider(paths, name, input) {
6938
+ const existing = readProvider(paths, name);
6939
+ if (existing === void 0) {
6940
+ throw new ProviderNotFoundError(name);
6771
6941
  }
6772
- const status = headers[`${UNIFIED_PREFIX}status`];
6773
- const fiveHour = unifiedWindow(headers, "5h");
6774
- const sevenDay = unifiedWindow(headers, "7d");
6775
- const representativeClaim = headers[`${UNIFIED_PREFIX}representative-claim`];
6776
- const resetAt = epochSecondsToIso(headers[`${UNIFIED_PREFIX}reset`]);
6777
- const overageStatus = headers[`${UNIFIED_PREFIX}overage-status`];
6778
- return {
6779
- ...status === void 0 ? {} : { status },
6780
- ...fiveHour === void 0 ? {} : { fiveHour },
6781
- ...sevenDay === void 0 ? {} : { sevenDay },
6782
- ...representativeClaim === void 0 ? {} : { representativeClaim },
6783
- ...resetAt === void 0 ? {} : { resetAt },
6784
- ...overageStatus === void 0 ? {} : { overageStatus }
6942
+ const unset = new Set(input.unsetEnv);
6943
+ const env = Object.fromEntries(Object.entries({ ...existing.env, ...input.env }).filter(([key]) => !unset.has(key)));
6944
+ const target = input.target ?? existing.credential.target;
6945
+ const cache = input.cache === void 0 ? existing.credential.cache : input.cache === false ? void 0 : input.cache;
6946
+ if (isCodexProvider(existing) && input.baseUrl !== void 0) {
6947
+ throw new ProviderKindMismatchError(`Provider "${name}" is a codex provider, which has no base URL.`);
6948
+ }
6949
+ if (!isCodexProvider(existing) && !isEmptyCodexConfig(input.codex)) {
6950
+ throw new ProviderKindMismatchError(`Provider "${name}" is an http provider; codex settings apply only to a codex provider.`);
6951
+ }
6952
+ const shared = {
6953
+ ...input.displayName === void 0 ? {} : { displayName: input.displayName },
6954
+ credential: {
6955
+ sources: input.sources ?? existing.credential.sources,
6956
+ ...target === void 0 ? {} : { target },
6957
+ ...cache === void 0 ? {} : { cache }
6958
+ },
6959
+ env: Object.keys(env).length === 0 ? void 0 : env
6785
6960
  };
6786
- }
6787
-
6788
- // src/usage/scan.ts
6789
- var import_zod8 = require("zod");
6790
- var AnthropicUsageSchema = import_zod8.z.looseObject({
6791
- input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6792
- output_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6793
- cache_creation_input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
6794
- cache_read_input_tokens: import_zod8.z.number().int().nonnegative().nullish()
6795
- });
6796
- var ErrorObjectSchema = import_zod8.z.looseObject({
6797
- type: import_zod8.z.string().optional(),
6798
- code: import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number()]).optional(),
6799
- message: import_zod8.z.string().optional()
6800
- });
6801
-
6802
- // src/usage/middleware.ts
6803
- var ANTHROPIC_PROVIDER = "anthropic";
6804
-
6805
- // src/usage/pick.ts
6806
- var MS_PER_SECOND4 = 1e3;
6807
- var MS_PER_HOUR = 36e5;
6808
- var HOURS_PER_DAY2 = 24;
6809
- var FIVE_HOURS = 5;
6810
- var PROMPT_CACHE_TTL_HOURS = 1;
6811
- var PERCENT = 100;
6812
- var SEVEN_DAYS = 7;
6813
- var FIVE_HOUR_WINDOW_MS = FIVE_HOURS * MS_PER_HOUR;
6814
- var SEVEN_DAY_WINDOW_MS = SEVEN_DAYS * HOURS_PER_DAY2 * MS_PER_HOUR;
6815
- var PROMPT_CACHE_TTL_MS = PROMPT_CACHE_TTL_HOURS * MS_PER_HOUR;
6816
- var CLASS_ORDER = { scored: 0, unknown: 1, "pay-per-use": 2, ineligible: 3 };
6817
- function unifiedOf(state) {
6818
- return state?.rateLimit?.unified;
6819
- }
6820
- function limitBlockedUntil(state, nowMs) {
6821
- const limit = state?.lastLimit;
6822
- if (state === void 0 || limit === void 0) {
6823
- return void 0;
6961
+ let candidate;
6962
+ if (isCodexProvider(existing)) {
6963
+ const codex = {
6964
+ ...existing.codex,
6965
+ ...input.codex,
6966
+ ...existing.codex?.models === void 0 && input.codex?.models === void 0 ? {} : { models: { ...existing.codex?.models, ...input.codex?.models } }
6967
+ };
6968
+ candidate = { ...existing, ...shared, codex: isEmptyCodexConfig(codex) ? void 0 : codex };
6969
+ } else {
6970
+ candidate = { ...existing, ...shared, ...input.baseUrl === void 0 ? {} : { baseUrl: input.baseUrl } };
6824
6971
  }
6825
- if (Date.parse(state.lastRequestAt) > Date.parse(limit.observedAt)) {
6826
- return void 0;
6972
+ const parsed = ProviderSchema.safeParse(candidate);
6973
+ if (!parsed.success) {
6974
+ throw new ConfigValidationError(providerJsonPath(paths, name), parsed.error.issues);
6827
6975
  }
6828
- 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);
6829
- return until !== void 0 && until > nowMs ? until : void 0;
6976
+ writeJsonAtomic(providerJsonPath(paths, name), parsed.data);
6977
+ return parsed.data;
6830
6978
  }
6831
- function windowBlockedUntil(windows) {
6832
- const resets = windows.flatMap((window) => window.status === "rejected" && window.resetsAtMs !== void 0 ? [window.resetsAtMs] : []);
6833
- return resets.length === 0 ? void 0 : Math.max(...resets);
6979
+ function removeProvider(paths, name) {
6980
+ if (!providerExists(paths, name)) {
6981
+ throw new ProviderNotFoundError(name);
6982
+ }
6983
+ import_node_fs5.default.rmSync(providerJsonPath(paths, name));
6834
6984
  }
6835
- function fiveHourBurn(records) {
6836
- const points = [];
6837
- for (const record of records) {
6838
- const window = record.rateLimitHeaders === void 0 ? void 0 : parseUnifiedRateLimit(record.rateLimitHeaders)?.fiveHour;
6839
- if (window?.utilization !== void 0 && window.resetsAt !== void 0) {
6840
- points.push({ resetsAt: window.resetsAt, point: { atMs: Date.parse(record.at), utilization: window.utilization } });
6985
+ function cascadeProviderName(cascade) {
6986
+ let name;
6987
+ for (const layer of assembleCascade(cascade).layers) {
6988
+ if (layer.launch?.provider !== void 0) {
6989
+ name = layer.launch.provider;
6841
6990
  }
6842
6991
  }
6843
- const latest = points.at(-1)?.resetsAt;
6844
- const current = points.filter((entry) => entry.resetsAt === latest).map((entry) => entry.point);
6845
- const first = current.at(0);
6846
- const last = current.at(-1);
6847
- if (first === void 0 || last === void 0 || last.atMs <= first.atMs || last.utilization <= first.utilization) {
6992
+ return name;
6993
+ }
6994
+ function resolveProvider(params) {
6995
+ const cliName = params.cliProvider !== void 0 && params.cliProvider !== "" ? params.cliProvider : void 0;
6996
+ const name = cliName ?? (params.cascade === void 0 ? void 0 : cascadeProviderName(params.cascade));
6997
+ if (name === void 0) {
6848
6998
  return void 0;
6849
6999
  }
6850
- return (last.utilization - first.utilization) / (last.atMs - first.atMs);
7000
+ const definition = loadProvider(params.paths.providersDir, name, params.port);
7001
+ if (definition === void 0) {
7002
+ const known = listProviderNames(params.port.readdir, params.paths.providersDir);
7003
+ return {
7004
+ ok: false,
7005
+ status: 1,
7006
+ message: `agent-shim: no provider named "${name}". ` + (known.length > 0 ? `Known providers: ${known.join(", ")}.` : "No providers are defined yet; run `agent-shim provider add`.")
7007
+ };
7008
+ }
7009
+ if (params.credentials === void 0) {
7010
+ return { ok: false, status: 1, message: `agent-shim: provider ${name} needs its credential resolved, but this launcher has no credential port wired` };
7011
+ }
7012
+ const resolution = resolveCredential({ credential: definition.credential, env: params.env, port: params.credentials, subject: `provider ${name}` });
7013
+ if (!resolution.ok) {
7014
+ return { ok: false, status: CREDENTIAL_UNAVAILABLE_EXIT, message: resolution.message };
7015
+ }
7016
+ return { ok: true, provider: { name, definition, credential: resolution.credential }, warnings: resolution.credential.warnings };
6851
7017
  }
6852
- function percent(fraction) {
6853
- return `${String(Math.round(fraction * PERCENT))}%`;
7018
+
7019
+ // src/directoryRulesStore.ts
7020
+ var DirectoryRuleNotFoundError = class extends CliError {
7021
+ constructor(rulePath) {
7022
+ super(`No directory rule found for path "${rulePath}".`);
7023
+ this.rulePath = rulePath;
7024
+ this.name = "DirectoryRuleNotFoundError";
7025
+ }
7026
+ rulePath;
7027
+ };
7028
+ var DirectoryRuleMissingTargetError = class extends CliError {
7029
+ constructor() {
7030
+ super("A directory rule must set at least one of --config-profile or --identity.");
7031
+ this.name = "DirectoryRuleMissingTargetError";
7032
+ }
7033
+ };
7034
+ var DirectoryRuleAlreadyExistsError = class extends CliError {
7035
+ constructor(rulePath) {
7036
+ super(`A directory rule for path "${rulePath}" already exists. Use \`agent-shim rule set\` to change it.`);
7037
+ this.rulePath = rulePath;
7038
+ this.name = "DirectoryRuleAlreadyExistsError";
7039
+ }
7040
+ rulePath;
7041
+ };
7042
+ function readDirectoryRules(paths) {
7043
+ return readJson(paths.directoryRulesFile, DirectoryRulesSchema) ?? { rules: [] };
6854
7044
  }
6855
- function describeWindow(label, window, effective, nowMs) {
6856
- if (effective.reset) {
6857
- return `${label} window has reset`;
7045
+ function writeDirectoryRules(paths, rules) {
7046
+ const parsed = DirectoryRulesSchema.safeParse(rules);
7047
+ if (!parsed.success) {
7048
+ throw new ConfigValidationError(paths.directoryRulesFile, parsed.error.issues);
6858
7049
  }
6859
- const used = effective.utilization === void 0 ? "usage unknown" : `${percent(effective.utilization)} used`;
6860
- const resets = effective.resetsAtMs === void 0 ? "" : `, resets in ${formatAge(effective.resetsAtMs - nowMs)}`;
6861
- return `${label} ${used}${resets}${window.status === "allowed_warning" ? " (nearly used)" : ""}`;
7050
+ writeJsonAtomic(paths.directoryRulesFile, parsed.data);
6862
7051
  }
6863
- function capacityOf(plan) {
6864
- return plan.kind === "subscription" ? plan.capacity : 1;
7052
+ function listDirectoryRules(paths) {
7053
+ return readDirectoryRules(paths).rules;
6865
7054
  }
6866
- function assess(member, nowMs) {
6867
- const plan = planOf(member.account);
6868
- 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`];
6869
- const base = { identity: member.identity, plan };
6870
- if (member.readError !== void 0) {
6871
- return { candidate: { ...base, class: "unknown", reasons: [`usage state unreadable: ${member.readError}`, ...planReasons] } };
6872
- }
6873
- const state = member.snapshot?.providers[ANTHROPIC_PROVIDER];
6874
- const unified = unifiedOf(state);
6875
- if (member.snapshot === void 0 || state === void 0 || unified === void 0) {
6876
- return { candidate: { ...base, class: "unknown", reasons: ["no usage recorded yet, so its quota is unknown (turn on trackUsage for it)", ...planReasons] } };
7055
+ function addDirectoryRule(paths, rulePath, options) {
7056
+ if (options.configProfile === void 0 && options.identity === void 0) {
7057
+ throw new DirectoryRuleMissingTargetError();
6877
7058
  }
6878
- const observedAgo = state.rateLimit === void 0 ? "" : ` (last seen ${formatAge(nowMs - Date.parse(state.rateLimit.observedAt))} ago)`;
6879
- const fiveHourWindow = unified.fiveHour;
6880
- const sevenDayWindow = unified.sevenDay;
6881
- const five = fiveHourWindow === void 0 ? void 0 : effectiveWindow(fiveHourWindow, nowMs);
6882
- const seven = sevenDayWindow === void 0 ? void 0 : effectiveWindow(sevenDayWindow, nowMs);
6883
- const windowReasons = [
6884
- ...fiveHourWindow === void 0 || five === void 0 ? [] : [describeWindow("5h", fiveHourWindow, five, nowMs)],
6885
- ...sevenDayWindow === void 0 || seven === void 0 ? [] : [describeWindow("7d", sevenDayWindow, seven, nowMs)]
6886
- ];
6887
- const blockedUntil = Math.max(
6888
- windowBlockedUntil([five, seven].flatMap((window) => window === void 0 ? [] : [window])) ?? 0,
6889
- limitBlockedUntil(state, nowMs) ?? 0
6890
- );
6891
- if (blockedUntil > nowMs) {
6892
- if (unified.overageStatus === "allowed") {
6893
- return { candidate: { ...base, class: "pay-per-use", reasons: [`plan exhausted until ${new Date(blockedUntil).toISOString()}, continues as extra usage`, ...windowReasons, ...planReasons] } };
6894
- }
6895
- return { candidate: { ...base, class: "ineligible", blockedUntilMs: blockedUntil, reasons: [`refused until ${new Date(blockedUntil).toISOString()} (in ${formatAge(blockedUntil - nowMs)})`, ...windowReasons, ...planReasons] } };
7059
+ const current = readDirectoryRules(paths);
7060
+ if (current.rules.some((rule) => rule.path === rulePath)) {
7061
+ throw new DirectoryRuleAlreadyExistsError(rulePath);
6896
7062
  }
6897
- if (plan.kind === "pay-per-use" || fiveHourWindow === void 0 && sevenDayWindow === void 0) {
6898
- return { candidate: { ...base, class: "pay-per-use", reasons: [...plan.kind === "pay-per-use" ? planReasons : ["no plan windows reported, so usage bills as extra usage"]] } };
7063
+ const created = buildNewRule(rulePath, options);
7064
+ writeDirectoryRules(paths, { ...current, rules: [...current.rules, created] });
7065
+ return created;
7066
+ }
7067
+ function ruleHasEffect(rule) {
7068
+ return rule.configProfile !== void 0 || rule.identity !== void 0 || rule.categories !== void 0 || rule.entries !== void 0 || rule.launch !== void 0;
7069
+ }
7070
+ function updateDirectoryRule(paths, rulePath, options) {
7071
+ const current = readDirectoryRules(paths);
7072
+ const index = current.rules.findIndex((rule) => rule.path === rulePath);
7073
+ const existing = index === -1 ? void 0 : current.rules[index];
7074
+ if (existing === void 0) {
7075
+ throw new DirectoryRuleNotFoundError(rulePath);
6899
7076
  }
6900
- 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;
6901
- if (basis?.window.utilization === void 0) {
6902
- return { candidate: { ...base, class: "unknown", reasons: ["quota windows reported no utilisation", ...windowReasons, ...planReasons] } };
7077
+ const { configProfile, identity, ...rest } = existing;
7078
+ const nextConfigProfile = options.configProfile === void 0 ? configProfile : options.configProfile === false ? void 0 : options.configProfile;
7079
+ const nextIdentity = options.identity === void 0 ? identity : options.identity === false ? void 0 : options.identity;
7080
+ const updated = {
7081
+ ...rest,
7082
+ ...nextConfigProfile === void 0 ? {} : { configProfile: nextConfigProfile },
7083
+ ...nextIdentity === void 0 ? {} : { identity: nextIdentity }
7084
+ };
7085
+ if (!ruleHasEffect(updated)) {
7086
+ throw new DirectoryRuleMissingTargetError();
6903
7087
  }
6904
- const remaining = Math.max(0, 1 - basis.window.utilization);
6905
- const untilResetMs = basis.window.resetsAtMs === void 0 ? basis.windowMs : Math.max(basis.window.resetsAtMs - nowMs, MS_PER_SECOND4);
6906
- const score = remaining * plan.capacity / (untilResetMs / MS_PER_HOUR);
6907
- const fiveRemaining = five?.utilization === void 0 ? void 0 : Math.max(0, 1 - five.utilization);
6908
- const fiveUntil = five?.resetsAtMs === void 0 ? FIVE_HOUR_WINDOW_MS : Math.max(five.resetsAtMs - nowMs, MS_PER_SECOND4);
6909
- const burn = fiveHourBurn(member.records);
7088
+ const nextRules = [...current.rules];
7089
+ nextRules[index] = updated;
7090
+ writeDirectoryRules(paths, { ...current, rules: nextRules });
7091
+ return updated;
7092
+ }
7093
+ function buildNewRule(rulePath, options) {
6910
7094
  return {
6911
- candidate: { ...base, class: "scored", score, reasons: [...windowReasons.map((reason, index) => index === windowReasons.length - 1 ? `${reason}${observedAgo}` : reason), ...planReasons] },
6912
- ...fiveRemaining === void 0 ? {} : { fiveHour: { remaining: fiveRemaining, untilResetMs: fiveUntil } },
6913
- ...burn === void 0 ? {} : { burn }
7095
+ path: rulePath,
7096
+ ...options.configProfile !== void 0 ? { configProfile: options.configProfile } : {},
7097
+ ...options.identity !== void 0 ? { identity: options.identity } : {}
6914
7098
  };
6915
7099
  }
6916
- function rankPool(input) {
6917
- const { members, nowMs } = input;
6918
- const assessments = members.map((member) => assess(member, nowMs));
6919
- const demands = assessments.flatMap(({ candidate, burn }) => burn === void 0 ? [] : [burn * capacityOf(candidate.plan)]);
6920
- const sharedDemand = demands.length === 0 ? void 0 : Math.max(...demands);
6921
- const candidates = assessments.map((assessment) => {
6922
- const { candidate, fiveHour, burn } = assessment;
6923
- const pace = burn ?? (sharedDemand === void 0 ? void 0 : sharedDemand / capacityOf(candidate.plan));
6924
- if (candidate.class !== "scored" || fiveHour === void 0 || pace === void 0) {
6925
- return { ...candidate, feasible: true };
6926
- }
6927
- const runsDryInMs = fiveHour.remaining / pace;
6928
- if (runsDryInMs >= fiveHour.untilResetMs) {
6929
- return { ...candidate, feasible: true };
6930
- }
6931
- return { ...candidate, feasible: false, reasons: [`5h window would run dry in ${formatAge(runsDryInMs)}, before it resets in ${formatAge(fiveHour.untilResetMs)}`, ...candidate.reasons] };
6932
- });
6933
- const ordered = [...candidates].sort(
6934
- (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)
6935
- );
6936
- const sticky = input.sticky;
6937
- const stickyIndex = sticky === void 0 ? -1 : ordered.findIndex((candidate) => candidate.identity === sticky.identity);
6938
- const stickyCandidate = ordered[stickyIndex];
6939
- if (sticky !== void 0 && stickyCandidate?.class === "scored" && stickyCandidate.feasible) {
6940
- const ageMs = nowMs - Date.parse(sticky.at);
6941
- const warm = ageMs <= PROMPT_CACHE_TTL_MS;
6942
- if (input.resuming || warm) {
6943
- const why = warm ? `used here ${formatAge(ageMs)} ago, prompt cache still warm` : "resuming a conversation started on this account";
6944
- ordered.splice(stickyIndex, 1);
6945
- ordered.unshift({ ...stickyCandidate, reasons: [why, ...stickyCandidate.reasons] });
6946
- }
7100
+ function removeDirectoryRule(paths, rulePath) {
7101
+ const current = readDirectoryRules(paths);
7102
+ const nextRules = current.rules.filter((rule) => rule.path !== rulePath);
7103
+ if (nextRules.length === current.rules.length) {
7104
+ throw new DirectoryRuleNotFoundError(rulePath);
6947
7105
  }
6948
- const pick = ordered.find((candidate) => candidate.class !== "ineligible");
6949
- const returns = ordered.flatMap((candidate) => candidate.blockedUntilMs === void 0 ? [] : [{ identity: candidate.identity, atMs: candidate.blockedUntilMs }]);
6950
- const earliestReturn = returns.length === 0 ? void 0 : returns.reduce((soonest, entry) => entry.atMs < soonest.atMs ? entry : soonest);
6951
- return {
6952
- candidates: ordered,
6953
- ...pick === void 0 ? { ...earliestReturn === void 0 ? {} : { earliestReturn } } : { pick }
6954
- };
7106
+ writeDirectoryRules(paths, { ...current, rules: nextRules });
6955
7107
  }
6956
7108
 
6957
- // src/usage/store.ts
6958
- var DAY_MS2 = 864e5;
6959
- var DAYS_IN_WEEK = 7;
6960
- var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
7109
+ // src/checkReport.ts
7110
+ var import_node_os4 = __toESM(require("node:os"), 1);
6961
7111
 
6962
- // src/usage/poolPick.ts
6963
- var SCHEMA_VERSION = 1;
6964
- var PickEntrySchema = import_zod9.z.strictObject({ identity: import_zod9.z.string().min(1), at: import_zod9.z.iso.datetime() });
6965
- var PicksFileSchema = import_zod9.z.strictObject({
6966
- schemaVersion: import_zod9.z.literal(SCHEMA_VERSION),
6967
- picks: import_zod9.z.record(import_zod9.z.string().min(1), PickEntrySchema)
6968
- });
6969
- function errorMessage(error) {
6970
- return error instanceof Error ? error.message : String(error);
6971
- }
6972
- function loadPoolMembers(fs12, paths, identities, nowMs) {
6973
- const windowStartMs = nowMs - FIVE_HOUR_WINDOW_MS;
6974
- const log = readUsageLog(fs12, paths.usageLogDir, { sinceMs: windowStartMs });
6975
- return identities.map((identity) => {
6976
- const records = log.records.filter((record) => record.identity === identity && record.provider === ANTHROPIC_PROVIDER && Date.parse(record.at) >= windowStartMs);
6977
- try {
6978
- const snapshot = readUsageSnapshot(fs12, paths.usageSnapshotsDir, identity);
6979
- const account = readAccountMetadata(fs12, paths.identitiesDir, identity) ?? snapshot?.account;
6980
- return { identity, records, ...snapshot === void 0 ? {} : { snapshot }, ...account === void 0 ? {} : { account } };
6981
- } catch (error) {
6982
- if (error instanceof UsageSnapshotError || error instanceof AccountMetadataError) {
6983
- return { identity, records, readError: errorMessage(error) };
6984
- }
6985
- throw error;
6986
- }
6987
- });
6988
- }
6989
- function rankPoolFromStore(params) {
6990
- return rankPool({
6991
- members: loadPoolMembers(params.fs, params.paths, params.identities, params.nowMs),
6992
- nowMs: params.nowMs,
6993
- resuming: params.resuming,
6994
- ...params.sticky === void 0 ? {} : { sticky: params.sticky }
6995
- });
6996
- }
6997
- function readPicksFile(fs12, file) {
6998
- const empty = { schemaVersion: SCHEMA_VERSION, picks: {} };
6999
- const raw = fs12.readFileUtf8(file);
7000
- if (raw === void 0) {
7001
- return { file: empty };
7112
+ // src/launcher/cascade.ts
7113
+ var import_node_path19 = __toESM(require("node:path"), 1);
7114
+
7115
+ // src/pathNorm.ts
7116
+ var import_node_path18 = __toESM(require("node:path"), 1);
7117
+ function expandTilde(value, home) {
7118
+ if (value === "~") {
7119
+ return home;
7002
7120
  }
7003
- let parsed;
7004
- try {
7005
- parsed = JSON.parse(raw);
7006
- } catch {
7007
- return { file: empty, problem: `${file} is not valid JSON` };
7121
+ if (value.startsWith("~/")) {
7122
+ return import_node_path18.default.join(home, value.slice(2));
7008
7123
  }
7009
- const result = PicksFileSchema.safeParse(parsed);
7010
- return result.success ? { file: result.data } : { file: empty, problem: `${file} is not a picks file this agent-shim can read` };
7011
- }
7012
- function readStickyPick(fs12, picksFile, directory) {
7013
- const read = readPicksFile(fs12, picksFile);
7014
- const entry = read.file.picks[directory];
7015
- return { ...entry === void 0 ? {} : { sticky: entry }, ...read.problem === void 0 ? {} : { problem: read.problem } };
7124
+ return value;
7016
7125
  }
7017
- function recordStickyPick(fs12, picksFile, directory, identity, nowMs) {
7018
- const current = readPicksFile(fs12, picksFile).file.picks;
7019
- const kept = Object.entries(current).filter(([, entry]) => nowMs - Date.parse(entry.at) <= USAGE_RETENTION_MS);
7020
- const next = { schemaVersion: SCHEMA_VERSION, picks: { ...Object.fromEntries(kept), [directory]: { identity, at: new Date(nowMs).toISOString() } } };
7021
- fs12.mkdirPrivate(import_node_path19.default.dirname(picksFile));
7022
- fs12.writeFilePrivate(picksFile, `${JSON.stringify(next, null, 2)}
7023
- `);
7126
+ function normaliseRulePath(rulePath, home) {
7127
+ return import_node_path18.default.resolve(expandTilde(rulePath, home));
7024
7128
  }
7025
7129
 
7026
- // src/launcher/pool.ts
7027
- var RESUME_FLAGS = /* @__PURE__ */ new Set(["--continue", "-c", "--resume", "-r"]);
7028
- var TERMINATOR = "--";
7029
- var REASONS_LOGGED = 3;
7030
- function splitMembers(pool, paths, fs12) {
7031
- const exists = (name) => loadIdentity(paths.identitiesDir, name, fs12) !== void 0;
7032
- return { present: pool.identities.filter(exists), missing: pool.identities.filter((name) => !exists(name)) };
7033
- }
7034
- function isResuming(passthrough) {
7035
- const end = passthrough.indexOf(TERMINATOR);
7036
- return (end === -1 ? passthrough : passthrough.slice(0, end)).some((token) => RESUME_FLAGS.has(token));
7037
- }
7038
- function resolvePoolLaunch(params) {
7039
- const { poolName, log } = params;
7040
- const pool = params.pools?.[poolName];
7041
- if (pool === void 0) {
7042
- return { ok: false, message: `agent-shim: no pool named "${poolName}" (selected via ${params.selectedVia}). Run \`agent-shim pool add ${poolName} --identity <name>...\` first.` };
7043
- }
7044
- const { present: identities, missing } = splitMembers(pool, params.paths, params.fs);
7045
- for (const name of missing) {
7046
- log.warn(`agent-shim: pool "${poolName}" names identity "${name}", which does not exist; skipping it`);
7047
- }
7048
- if (identities.length === 0) {
7049
- return { ok: false, message: `agent-shim: no member of pool "${poolName}" is an existing identity.` };
7050
- }
7051
- const resuming = isResuming(params.passthrough);
7052
- const stickyRead = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd);
7053
- if (stickyRead.problem !== void 0) {
7054
- log.warn(`agent-shim: ${stickyRead.problem}; ignoring the last-pick record`);
7055
- }
7056
- for (; ; ) {
7057
- const nowMs = params.now();
7058
- const ranking = rankPoolFromStore({ fs: params.usageFs, paths: params.paths, identities, nowMs, resuming, ...stickyRead.sticky === void 0 ? {} : { sticky: stickyRead.sticky } });
7059
- const { pick, earliestReturn } = ranking;
7060
- if (pick !== void 0) {
7061
- recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, pick.identity, nowMs);
7062
- return { ok: true, identity: pick.identity, explanation: `pool ${poolName}: ${pick.reasons.slice(0, REASONS_LOGGED).join("; ")}` };
7063
- }
7064
- if (earliestReturn === void 0) {
7065
- return { ok: false, message: `agent-shim: no member of pool "${poolName}" can be picked.` };
7130
+ // src/launcher/cascade.ts
7131
+ var PORTABLE_CONFIG_FILENAME = ".agent-shim.json";
7132
+ var PORTABLE_LOCAL_CONFIG_FILENAME = ".agent-shim.local.json";
7133
+ function loadCascadeInput(params) {
7134
+ const globalConfig = loadConfigFile(params.paths.globalConfigFile, GlobalConfigSchema, params.read);
7135
+ const directoryRules = loadConfigFile(params.paths.directoryRulesFile, DirectoryRulesSchema, params.read);
7136
+ const loadProfile = (name) => {
7137
+ const filepath = import_node_path19.default.join(params.paths.configProfilesDir, `${name}.json`);
7138
+ const loaded = loadConfigFile(filepath, ConfigProfileSchema, params.read);
7139
+ if (loaded === void 0) {
7140
+ return void 0;
7066
7141
  }
7067
- const until = new Date(earliestReturn.atMs).toISOString();
7068
- if (!params.wait) {
7142
+ return { name, profile: loaded.config, entryOrder: loaded.entryOrder, filepath: loaded.filepath };
7143
+ };
7144
+ const limit = globalConfig?.config.walkUpLimit;
7145
+ const dirs = walkDirectoryAncestors(params.cwd, {
7146
+ home: params.home,
7147
+ ...limit === void 0 ? {} : { limit: expandTilde(limit, params.home) },
7148
+ ...params.isReadable === void 0 ? {} : { isReadable: params.isReadable }
7149
+ });
7150
+ const levels = [];
7151
+ for (const dir of dirs) {
7152
+ const portable = loadConfigFile(import_node_path19.default.join(dir, PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path19.default.join(dir, LEGACY_PORTABLE_CONFIG_FILENAME), PortableConfigSchema, params.read);
7153
+ const portableLocal = loadConfigFile(import_node_path19.default.join(dir, PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read) ?? loadConfigFile(import_node_path19.default.join(dir, LEGACY_PORTABLE_LOCAL_CONFIG_FILENAME), PortableConfigSchema, params.read);
7154
+ const rules = (directoryRules?.config.rules ?? []).map((rule, index) => ({ rule, index })).filter(({ rule }) => normaliseRulePath(rule.path, params.home) === dir).map(({ rule, index }) => {
7155
+ const entryOrder = directoryRules?.ruleEntryOrders[index];
7069
7156
  return {
7070
- ok: false,
7071
- message: `agent-shim: every member of pool "${poolName}" is refused right now; the earliest, ${earliestReturn.identity}, returns at ${until} (in ${formatAge(earliestReturn.atMs - nowMs)}). Pass --wait to sleep until then.`
7157
+ rule,
7158
+ ...entryOrder === void 0 ? {} : { entryOrder },
7159
+ filepath: params.paths.directoryRulesFile
7072
7160
  };
7161
+ });
7162
+ if (portable === void 0 && portableLocal === void 0 && rules.length === 0) {
7163
+ continue;
7164
+ }
7165
+ levels.push({
7166
+ dir,
7167
+ ...portable === void 0 ? {} : { portable: { config: portable.config, entryOrder: portable.entryOrder, filepath: portable.filepath } },
7168
+ ...rules.length === 0 ? {} : { rules },
7169
+ ...portableLocal === void 0 ? {} : { portableLocal: { config: portableLocal.config, entryOrder: portableLocal.entryOrder, filepath: portableLocal.filepath } }
7170
+ });
7171
+ }
7172
+ const input = {
7173
+ home: params.home,
7174
+ ...globalConfig === void 0 ? {} : { globalConfig: { config: globalConfig.config, entryOrder: globalConfig.entryOrder, filepath: globalConfig.filepath } },
7175
+ ...params.baseConfigProfile === void 0 ? {} : { baseConfigProfile: params.baseConfigProfile },
7176
+ loadProfile,
7177
+ levels,
7178
+ ...params.cliOverride === void 0 ? {} : { cliOverride: params.cliOverride }
7179
+ };
7180
+ return { input, ...globalConfig === void 0 ? {} : { globalConfig: globalConfig.config } };
7181
+ }
7182
+ function readDirectorySelections(loaded) {
7183
+ let identity;
7184
+ let configProfile;
7185
+ for (const level of loaded.input.levels ?? []) {
7186
+ for (const source of [level.portable?.config, ...(level.rules ?? []).map((entry) => entry.rule), level.portableLocal?.config]) {
7187
+ if (source === void 0) {
7188
+ continue;
7189
+ }
7190
+ if (source.identity !== void 0) {
7191
+ identity = source.identity;
7192
+ }
7193
+ if (source.configProfile !== void 0) {
7194
+ configProfile = source.configProfile;
7195
+ }
7073
7196
  }
7074
- log.info(`agent-shim: every member of pool "${poolName}" is refused; waiting until ${until} for ${earliestReturn.identity}`);
7075
- params.sleep(Math.max(earliestReturn.atMs - nowMs, 0));
7076
7197
  }
7198
+ return { ...identity === void 0 ? {} : { identity }, ...configProfile === void 0 ? {} : { configProfile } };
7077
7199
  }
7078
7200
 
7079
- // src/poolPickReport.ts
7080
- function candidateView(candidate) {
7081
- return {
7082
- identity: candidate.identity,
7083
- class: candidate.class,
7084
- ...candidate.score === void 0 ? {} : { score: candidate.score },
7085
- feasible: candidate.feasible,
7086
- ...candidate.blockedUntilMs === void 0 ? {} : { blockedUntil: new Date(candidate.blockedUntilMs).toISOString() },
7087
- plan: candidate.plan,
7088
- reasons: candidate.reasons
7089
- };
7201
+ // src/realCredentialCache.ts
7202
+ var import_node_child_process = require("node:child_process");
7203
+ var import_node_fs6 = __toESM(require("node:fs"), 1);
7204
+ var import_node_path20 = __toESM(require("node:path"), 1);
7205
+ var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
7206
+ var CACHE_DIR_MODE = 448;
7207
+ var CACHE_FILE_MODE = 384;
7208
+ function cacheFileNameFor(owner) {
7209
+ return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
7090
7210
  }
7091
- function collectPoolPick(params) {
7092
- const { present, missing } = splitMembers(params.pool, params.paths, params.fs);
7093
- const sticky = readStickyPick(params.usageFs, params.paths.usagePicksFile, params.directory);
7094
- const ranking = rankPoolFromStore({
7095
- fs: params.usageFs,
7096
- paths: params.paths,
7097
- identities: present,
7098
- nowMs: params.nowMs,
7099
- resuming: false,
7100
- ...sticky.sticky === void 0 ? {} : { sticky: sticky.sticky }
7101
- });
7211
+ function parseEntry(raw) {
7212
+ let json;
7213
+ try {
7214
+ json = JSON.parse(raw);
7215
+ } catch {
7216
+ return void 0;
7217
+ }
7218
+ const parsed = CachedCredentialSchema.safeParse(json);
7219
+ return parsed.success ? parsed.data : void 0;
7220
+ }
7221
+ function requireKeychain() {
7222
+ if (process.platform !== "darwin") {
7223
+ throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
7224
+ }
7225
+ }
7226
+ function security(args, stdin) {
7227
+ const result = (0, import_node_child_process.spawnSync)("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
7228
+ return { status: result.status, stdout: result.stdout };
7229
+ }
7230
+ function createRealCredentialCache(root) {
7231
+ const dir = import_node_path20.default.join(root, "credential-cache");
7232
+ const filePath = (owner) => import_node_path20.default.join(dir, cacheFileNameFor(owner));
7102
7233
  return {
7103
- pool: params.poolName,
7104
- directory: params.directory,
7105
- ...ranking.pick === void 0 ? {} : { pick: ranking.pick.identity },
7106
- candidates: ranking.candidates.map(candidateView),
7107
- missing,
7108
- ...ranking.earliestReturn === void 0 ? {} : { earliestReturn: { identity: ranking.earliestReturn.identity, at: new Date(ranking.earliestReturn.atMs).toISOString() } },
7109
- ...sticky.problem === void 0 ? {} : { stickyProblem: sticky.problem }
7234
+ read(store, owner) {
7235
+ if (store === "file") {
7236
+ try {
7237
+ return parseEntry(import_node_fs6.default.readFileSync(filePath(owner), "utf8"));
7238
+ } catch {
7239
+ return void 0;
7240
+ }
7241
+ }
7242
+ requireKeychain();
7243
+ const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
7244
+ return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
7245
+ },
7246
+ write(store, owner, entry) {
7247
+ const serialised = JSON.stringify(entry);
7248
+ if (store === "file") {
7249
+ import_node_fs6.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
7250
+ import_node_fs6.default.chmodSync(dir, CACHE_DIR_MODE);
7251
+ const target = filePath(owner);
7252
+ const temp = `${target}.${String(process.pid)}.tmp`;
7253
+ import_node_fs6.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
7254
+ import_node_fs6.default.renameSync(temp, target);
7255
+ return;
7256
+ }
7257
+ requireKeychain();
7258
+ const encoded = Buffer.from(serialised, "utf8").toString("base64");
7259
+ const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
7260
+ `);
7261
+ if (result.status !== 0) {
7262
+ throw new Error(`could not write the keychain credential cache for ${owner}`);
7263
+ }
7264
+ },
7265
+ remove(store, owner) {
7266
+ if (store === "file") {
7267
+ import_node_fs6.default.rmSync(filePath(owner), { force: true });
7268
+ return;
7269
+ }
7270
+ requireKeychain();
7271
+ security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
7272
+ }
7110
7273
  };
7111
7274
  }
7275
+ function realCredentialCacheEnv(paths) {
7276
+ return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
7277
+ }
7112
7278
 
7113
7279
  // src/realPorts.ts
7114
- var import_node_child_process = require("node:child_process");
7280
+ var import_node_child_process2 = require("node:child_process");
7115
7281
  var import_node_crypto9 = require("node:crypto");
7116
- var import_node_fs6 = __toESM(require("node:fs"), 1);
7282
+ var import_node_fs7 = __toESM(require("node:fs"), 1);
7117
7283
  var import_node_os3 = __toESM(require("node:os"), 1);
7118
- var import_node_path21 = __toESM(require("node:path"), 1);
7284
+ var import_node_path22 = __toESM(require("node:path"), 1);
7119
7285
  var import_node_sea = require("node:sea");
7120
7286
 
7121
7287
  // src/versionDiscovery.ts
7122
7288
  var import_node_os2 = __toESM(require("node:os"), 1);
7123
- var import_node_path20 = __toESM(require("node:path"), 1);
7289
+ var import_node_path21 = __toESM(require("node:path"), 1);
7124
7290
  function defaultVersionsDir() {
7125
- return import_node_path20.default.join(import_node_os2.default.homedir(), ".local", "share", "claude", "versions");
7291
+ return import_node_path21.default.join(import_node_os2.default.homedir(), ".local", "share", "claude", "versions");
7126
7292
  }
7127
7293
  var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
7128
7294
  function isNumericDottedVersion(name) {
@@ -7164,7 +7330,7 @@ function discoverClaudeBinary(options) {
7164
7330
  const highestVersion = pickHighestVersion(entries);
7165
7331
  if (highestVersion !== void 0) {
7166
7332
  return {
7167
- path: import_node_path20.default.join(versionsDir, highestVersion),
7333
+ path: import_node_path21.default.join(versionsDir, highestVersion),
7168
7334
  source: "versions-dir",
7169
7335
  version: highestVersion
7170
7336
  };
@@ -7187,7 +7353,7 @@ function isEnoent3(error) {
7187
7353
  var realFsPort = {
7188
7354
  readFileUtf8(filePath) {
7189
7355
  try {
7190
- return import_node_fs6.default.readFileSync(filePath, "utf8");
7356
+ return import_node_fs7.default.readFileSync(filePath, "utf8");
7191
7357
  } catch (error) {
7192
7358
  if (isEnoent3(error)) {
7193
7359
  return void 0;
@@ -7198,7 +7364,7 @@ var realFsPort = {
7198
7364
  readConfigFile: cosmiconfigReader(),
7199
7365
  readdir(dirPath) {
7200
7366
  try {
7201
- return import_node_fs6.default.readdirSync(dirPath);
7367
+ return import_node_fs7.default.readdirSync(dirPath);
7202
7368
  } catch (error) {
7203
7369
  if (isEnoent3(error)) {
7204
7370
  return [];
@@ -7217,7 +7383,7 @@ var realFarmFs = {
7217
7383
  lstat(filePath) {
7218
7384
  let stat;
7219
7385
  try {
7220
- stat = import_node_fs6.default.lstatSync(filePath);
7386
+ stat = import_node_fs7.default.lstatSync(filePath);
7221
7387
  } catch (error) {
7222
7388
  if (isEnoent3(error)) {
7223
7389
  return void 0;
@@ -7229,7 +7395,7 @@ var realFarmFs = {
7229
7395
  },
7230
7396
  readdir(dirPath) {
7231
7397
  try {
7232
- return import_node_fs6.default.readdirSync(dirPath);
7398
+ return import_node_fs7.default.readdirSync(dirPath);
7233
7399
  } catch (error) {
7234
7400
  if (isEnoent3(error) || isErrorWithCode(error, "ENOTDIR")) {
7235
7401
  return [];
@@ -7238,23 +7404,23 @@ var realFarmFs = {
7238
7404
  }
7239
7405
  },
7240
7406
  mkdirp(dirPath) {
7241
- import_node_fs6.default.mkdirSync(dirPath, { recursive: true });
7407
+ import_node_fs7.default.mkdirSync(dirPath, { recursive: true });
7242
7408
  },
7243
7409
  symlink(target, linkPath) {
7244
- import_node_fs6.default.symlinkSync(target, linkPath);
7410
+ import_node_fs7.default.symlinkSync(target, linkPath);
7245
7411
  },
7246
7412
  rename(from, to) {
7247
- import_node_fs6.default.renameSync(from, to);
7413
+ import_node_fs7.default.renameSync(from, to);
7248
7414
  },
7249
7415
  removeRecursive(targetPath) {
7250
- import_node_fs6.default.rmSync(targetPath, { recursive: true, force: true });
7416
+ import_node_fs7.default.rmSync(targetPath, { recursive: true, force: true });
7251
7417
  },
7252
7418
  copyRecursive(from, to) {
7253
- import_node_fs6.default.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
7419
+ import_node_fs7.default.cpSync(from, to, { recursive: true, force: true, verbatimSymlinks: true });
7254
7420
  },
7255
7421
  readFileUtf8(filePath) {
7256
7422
  try {
7257
- return import_node_fs6.default.readFileSync(filePath, "utf8");
7423
+ return import_node_fs7.default.readFileSync(filePath, "utf8");
7258
7424
  } catch (error) {
7259
7425
  if (isEnoent3(error)) {
7260
7426
  return void 0;
@@ -7263,36 +7429,36 @@ var realFarmFs = {
7263
7429
  }
7264
7430
  },
7265
7431
  writeFileUtf8(filePath, contents) {
7266
- import_node_fs6.default.writeFileSync(filePath, contents, "utf8");
7432
+ import_node_fs7.default.writeFileSync(filePath, contents, "utf8");
7267
7433
  },
7268
7434
  mkdirPrivate(dirPath) {
7269
- import_node_fs6.default.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
7270
- import_node_fs6.default.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
7435
+ import_node_fs7.default.mkdirSync(dirPath, { recursive: true, mode: OWNER_ONLY_DIR_MODE });
7436
+ import_node_fs7.default.chmodSync(dirPath, OWNER_ONLY_DIR_MODE);
7271
7437
  },
7272
7438
  writeFilePrivate(filePath, contents) {
7273
7439
  const temp = `${filePath}.${String(process.pid)}.tmp`;
7274
- import_node_fs6.default.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
7275
- import_node_fs6.default.chmodSync(temp, OWNER_ONLY_FILE_MODE);
7276
- import_node_fs6.default.renameSync(temp, filePath);
7440
+ import_node_fs7.default.writeFileSync(temp, contents, { encoding: "utf8", mode: OWNER_ONLY_FILE_MODE });
7441
+ import_node_fs7.default.chmodSync(temp, OWNER_ONLY_FILE_MODE);
7442
+ import_node_fs7.default.renameSync(temp, filePath);
7277
7443
  },
7278
7444
  appendFilePrivate(filePath, contents) {
7279
- const descriptor = import_node_fs6.default.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
7445
+ const descriptor = import_node_fs7.default.openSync(filePath, "a", OWNER_ONLY_FILE_MODE);
7280
7446
  try {
7281
- if ((import_node_fs6.default.fstatSync(descriptor).mode & PERMISSION_BITS2) !== OWNER_ONLY_FILE_MODE) {
7282
- import_node_fs6.default.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
7447
+ if ((import_node_fs7.default.fstatSync(descriptor).mode & PERMISSION_BITS2) !== OWNER_ONLY_FILE_MODE) {
7448
+ import_node_fs7.default.fchmodSync(descriptor, OWNER_ONLY_FILE_MODE);
7283
7449
  }
7284
7450
  const bytes = Buffer.from(contents, "utf8");
7285
7451
  let written = 0;
7286
7452
  while (written < bytes.length) {
7287
- written += import_node_fs6.default.writeSync(descriptor, bytes, written, bytes.length - written);
7453
+ written += import_node_fs7.default.writeSync(descriptor, bytes, written, bytes.length - written);
7288
7454
  }
7289
7455
  } finally {
7290
- import_node_fs6.default.closeSync(descriptor);
7456
+ import_node_fs7.default.closeSync(descriptor);
7291
7457
  }
7292
7458
  },
7293
7459
  writeFileExclusive(filePath, contents) {
7294
7460
  try {
7295
- import_node_fs6.default.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
7461
+ import_node_fs7.default.writeFileSync(filePath, contents, { encoding: "utf8", flag: "wx" });
7296
7462
  return true;
7297
7463
  } catch (error) {
7298
7464
  if (isErrorWithCode(error, "EEXIST")) {
@@ -7303,7 +7469,7 @@ var realFarmFs = {
7303
7469
  },
7304
7470
  hashFile(filePath) {
7305
7471
  try {
7306
- return (0, import_node_crypto9.createHash)("sha256").update(import_node_fs6.default.readFileSync(filePath)).digest("hex");
7472
+ return (0, import_node_crypto9.createHash)("sha256").update(import_node_fs7.default.readFileSync(filePath)).digest("hex");
7307
7473
  } catch (error) {
7308
7474
  if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
7309
7475
  return void 0;
@@ -7325,7 +7491,7 @@ function realIsProcessAlive(pid) {
7325
7491
  }
7326
7492
  }
7327
7493
  function realIsProcessZombie(pid) {
7328
- const result = (0, import_node_child_process.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
7494
+ const result = (0, import_node_child_process2.spawnSync)("ps", ["-o", "stat=", "-p", String(pid)], { encoding: "utf8" });
7329
7495
  return result.status === 0 && result.stdout.trim().startsWith("Z");
7330
7496
  }
7331
7497
  function realIsProcessRunning(pid) {
@@ -7333,17 +7499,17 @@ function realIsProcessRunning(pid) {
7333
7499
  }
7334
7500
  var realRunPort = {
7335
7501
  run(command, args) {
7336
- const result = (0, import_node_child_process.spawnSync)(command, [...args], { encoding: "utf8" });
7502
+ const result = (0, import_node_child_process2.spawnSync)(command, [...args], { encoding: "utf8" });
7337
7503
  return { status: result.status, stdout: result.stdout, stderr: result.stderr };
7338
7504
  }
7339
7505
  };
7340
7506
  function expandHome2(filePath) {
7341
- return filePath.startsWith("~/") ? import_node_path21.default.join(import_node_os3.default.homedir(), filePath.slice(1)) : filePath;
7507
+ return filePath.startsWith("~/") ? import_node_path22.default.join(import_node_os3.default.homedir(), filePath.slice(1)) : filePath;
7342
7508
  }
7343
7509
  var MACOS_GUI_SESSION_MANAGER = "Aqua";
7344
7510
  function hasDesktopSession() {
7345
7511
  if (process.platform === "darwin") {
7346
- const result = (0, import_node_child_process.spawnSync)("launchctl", ["managername"], { encoding: "utf8" });
7512
+ const result = (0, import_node_child_process2.spawnSync)("launchctl", ["managername"], { encoding: "utf8" });
7347
7513
  return result.status === 0 && result.stdout.trim() === MACOS_GUI_SESSION_MANAGER;
7348
7514
  }
7349
7515
  if (process.platform === "win32") {
@@ -7357,8 +7523,8 @@ var realCredentialPort = {
7357
7523
  let content;
7358
7524
  let stat;
7359
7525
  try {
7360
- stat = import_node_fs6.default.statSync(resolved);
7361
- content = import_node_fs6.default.readFileSync(resolved, "utf8");
7526
+ stat = import_node_fs7.default.statSync(resolved);
7527
+ content = import_node_fs7.default.readFileSync(resolved, "utf8");
7362
7528
  } catch (error) {
7363
7529
  if (isEnoent3(error)) {
7364
7530
  return { found: false };
@@ -7369,7 +7535,7 @@ var realCredentialPort = {
7369
7535
  },
7370
7536
  runCommand(argv, options) {
7371
7537
  const [command, ...args] = argv;
7372
- const result = (0, import_node_child_process.spawnSync)(command, args, {
7538
+ const result = (0, import_node_child_process2.spawnSync)(command, args, {
7373
7539
  encoding: "utf8",
7374
7540
  timeout: options.timeoutMs,
7375
7541
  stdio: options.interactive ? ["inherit", "pipe", "inherit"] : ["ignore", "pipe", "pipe"]
@@ -7411,17 +7577,17 @@ function spawnDaemonThrough(executable) {
7411
7577
  return (paths, subcommand, logPath) => spawnDetachedDaemon({ command: executable, args: [subcommand] }, paths, subcommand, logPath);
7412
7578
  }
7413
7579
  function spawnDetachedDaemon(invocation, paths, subcommand, logPath) {
7414
- import_node_fs6.default.mkdirSync(import_node_path21.default.dirname(logPath), { recursive: true });
7415
- const logFd = import_node_fs6.default.openSync(logPath, "a");
7580
+ import_node_fs7.default.mkdirSync(import_node_path22.default.dirname(logPath), { recursive: true });
7581
+ const logFd = import_node_fs7.default.openSync(logPath, "a");
7416
7582
  try {
7417
- const child = (0, import_node_child_process.spawn)(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
7583
+ const child = (0, import_node_child_process2.spawn)(invocation.command, invocation.args, detachedDaemonSpawnOptions(logFd, { ...process.env, AGENT_SHIM_HOME: paths.root }));
7418
7584
  child.unref();
7419
7585
  if (child.pid === void 0) {
7420
7586
  throw new Error(`spawning ${subcommand} returned no pid`);
7421
7587
  }
7422
7588
  return child.pid;
7423
7589
  } finally {
7424
- import_node_fs6.default.closeSync(logFd);
7590
+ import_node_fs7.default.closeSync(logFd);
7425
7591
  }
7426
7592
  }
7427
7593
  function spawnHeadroomSupervisor(paths, spawnDaemon) {
@@ -7439,7 +7605,7 @@ var realHeadroomSocketTrust = {
7439
7605
  lstat: (target) => {
7440
7606
  let stat;
7441
7607
  try {
7442
- stat = import_node_fs6.default.lstatSync(target);
7608
+ stat = import_node_fs7.default.lstatSync(target);
7443
7609
  } catch (error) {
7444
7610
  if (isEnoent3(error)) {
7445
7611
  return void 0;
@@ -7485,7 +7651,7 @@ var EXECUTE_BITS_MASK = 73;
7485
7651
  function listVersionsDir(dir) {
7486
7652
  let entries;
7487
7653
  try {
7488
- entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
7654
+ entries = import_node_fs7.default.readdirSync(dir, { withFileTypes: true });
7489
7655
  } catch (error) {
7490
7656
  if (isEnoent3(error)) {
7491
7657
  return [];
@@ -7493,8 +7659,8 @@ function listVersionsDir(dir) {
7493
7659
  throw error;
7494
7660
  }
7495
7661
  return entries.filter((entry) => entry.isFile()).map((entry) => {
7496
- const fullPath = import_node_path21.default.join(dir, entry.name);
7497
- const stat = import_node_fs6.default.statSync(fullPath);
7662
+ const fullPath = import_node_path22.default.join(dir, entry.name);
7663
+ const stat = import_node_fs7.default.statSync(fullPath);
7498
7664
  return {
7499
7665
  name: entry.name,
7500
7666
  isFile: true,
@@ -7508,7 +7674,7 @@ function windowsPathExtensions(pathext) {
7508
7674
  return (pathext ?? WINDOWS_DEFAULT_PATHEXT).split(";").map((ext) => ext.trim()).filter((ext) => ext.length > 0);
7509
7675
  }
7510
7676
  function resolveExecutableCandidate(dir, name, env) {
7511
- const candidate = import_node_path21.default.join(dir, name);
7677
+ const candidate = import_node_path22.default.join(dir, name);
7512
7678
  if (env.platform === "win32") {
7513
7679
  const extensions = windowsPathExtensions(env.pathext);
7514
7680
  const alreadyHasRecognisedExtension = extensions.some((ext) => candidate.toLowerCase().endsWith(ext.toLowerCase()));
@@ -7528,7 +7694,7 @@ function resolveExecutableCandidate(dir, name, env) {
7528
7694
  }
7529
7695
  function realStatFileMode(candidate) {
7530
7696
  try {
7531
- const stat = import_node_fs6.default.statSync(candidate);
7697
+ const stat = import_node_fs7.default.statSync(candidate);
7532
7698
  return stat.isFile() ? stat.mode : void 0;
7533
7699
  } catch (error) {
7534
7700
  if (isEnoent3(error)) {
@@ -7557,13 +7723,13 @@ function resolveOwnExecutablePath(env) {
7557
7723
  if (env.argv1 !== void 0 && !env.argv1.includes("/") && !env.argv1.includes("\\")) {
7558
7724
  return searchPathForExecutable(env.pathDirs, env.argv1, env.findExecutableInDir) ?? env.execPath;
7559
7725
  }
7560
- const candidate = env.argv1 === void 0 ? env.execPath : import_node_path21.default.resolve(env.cwd, env.argv1);
7561
- const candidateDir = import_node_path21.default.dirname(candidate);
7562
- const candidateDirOnPath = env.pathDirs.some((dir) => import_node_path21.default.resolve(dir) === candidateDir);
7726
+ const candidate = env.argv1 === void 0 ? env.execPath : import_node_path22.default.resolve(env.cwd, env.argv1);
7727
+ const candidateDir = import_node_path22.default.dirname(candidate);
7728
+ const candidateDirOnPath = env.pathDirs.some((dir) => import_node_path22.default.resolve(dir) === candidateDir);
7563
7729
  if (candidateDirOnPath) {
7564
7730
  return candidate;
7565
7731
  }
7566
- const viaPath = searchPathForExecutable(env.pathDirs, import_node_path21.default.basename(candidate), env.findExecutableInDir);
7732
+ const viaPath = searchPathForExecutable(env.pathDirs, import_node_path22.default.basename(candidate), env.findExecutableInDir);
7567
7733
  return viaPath ?? candidate;
7568
7734
  }
7569
7735
  function realOwnExecutablePath() {
@@ -7571,7 +7737,7 @@ function realOwnExecutablePath() {
7571
7737
  argv1: process.argv[1],
7572
7738
  execPath: process.execPath,
7573
7739
  cwd: process.cwd(),
7574
- pathDirs: (process.env.PATH ?? "").split(import_node_path21.default.delimiter).filter((dir) => dir !== ""),
7740
+ pathDirs: (process.env.PATH ?? "").split(import_node_path22.default.delimiter).filter((dir) => dir !== ""),
7575
7741
  findExecutableInDir
7576
7742
  });
7577
7743
  }
@@ -7584,15 +7750,15 @@ function realContentSourcePath() {
7584
7750
  function realResolveClaudeBinary(isOwnBinary) {
7585
7751
  return () => discoverClaudeBinary({
7586
7752
  listVersionsDir,
7587
- pathDirs: (process.env.PATH ?? "").split(import_node_path21.default.delimiter).filter((dir) => dir !== ""),
7753
+ pathDirs: (process.env.PATH ?? "").split(import_node_path22.default.delimiter).filter((dir) => dir !== ""),
7588
7754
  findExecutableInDir,
7589
7755
  isOwnBinary
7590
7756
  });
7591
7757
  }
7592
7758
 
7593
7759
  // src/checkReport.ts
7594
- var import_node_path22 = __toESM(require("node:path"), 1);
7595
- var import_zod10 = require("zod");
7760
+ var import_node_path23 = __toESM(require("node:path"), 1);
7761
+ var import_zod12 = require("zod");
7596
7762
 
7597
7763
  // src/cli/bool.ts
7598
7764
  function parseBoolWord(input) {
@@ -7707,9 +7873,9 @@ function lookupKeychainService(run, farmRoot) {
7707
7873
  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}".`
7708
7874
  };
7709
7875
  }
7710
- var SettingsSecretsShapeSchema = import_zod10.z.looseObject({
7711
- env: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.unknown()).optional(),
7712
- hooks: import_zod10.z.record(import_zod10.z.string(), import_zod10.z.array(import_zod10.z.unknown())).optional()
7876
+ var SettingsSecretsShapeSchema = import_zod12.z.looseObject({
7877
+ env: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.unknown()).optional(),
7878
+ hooks: import_zod12.z.record(import_zod12.z.string(), import_zod12.z.array(import_zod12.z.unknown())).optional()
7713
7879
  });
7714
7880
  function hookCommandCountOf(group) {
7715
7881
  if (typeof group !== "object" || group === null) {
@@ -7761,14 +7927,23 @@ function inspectSettingsExposure(params) {
7761
7927
  }
7762
7928
  return reports;
7763
7929
  }
7764
- function buildCredentialReport(provider, identity) {
7930
+ function cachedState(cacheEnv, owner, block) {
7931
+ if (cacheEnv === void 0 || block.cache === void 0) {
7932
+ return void 0;
7933
+ }
7934
+ return describeCachedCredential({ port: cacheEnv.port, platform: cacheEnv.platform, owner, cache: block.cache, nowMs: cacheEnv.now() });
7935
+ }
7936
+ function buildCredentialReport(provider, identity, identityName, cacheEnv) {
7765
7937
  const identityCredential = identity?.credential === void 0 ? void 0 : summariseCredential(identity.credential);
7938
+ const identityCached = identity?.credential === void 0 || identityName === void 0 ? void 0 : cachedState(cacheEnv, `identity ${identityName}`, identity.credential);
7939
+ const providerCached = provider?.definition === void 0 ? void 0 : cachedState(cacheEnv, `provider ${provider.name}`, provider.definition.credential);
7766
7940
  return {
7767
7941
  applies: provider !== void 0 ? "provider" : identityCredential !== void 0 ? "identity" : "stored-login",
7768
7942
  ...provider === void 0 ? {} : {
7769
- provider: provider.problem === void 0 ? { name: provider.name, credential: summariseCredential(provider.definition.credential) } : { name: provider.name, problem: provider.problem }
7943
+ provider: provider.problem === void 0 ? { name: provider.name, credential: summariseCredential(provider.definition.credential), ...providerCached === void 0 ? {} : { cached: providerCached } } : { name: provider.name, problem: provider.problem }
7770
7944
  },
7771
- ...identityCredential === void 0 ? {} : { identity: identityCredential }
7945
+ ...identityCredential === void 0 ? {} : { identity: identityCredential },
7946
+ ...identityCached === void 0 ? {} : { identityCached }
7772
7947
  };
7773
7948
  }
7774
7949
  function runCheck(params) {
@@ -7807,7 +7982,7 @@ function runCheck(params) {
7807
7982
  ambientCredential,
7808
7983
  ...keychain === void 0 ? {} : { keychain },
7809
7984
  settingsExposure,
7810
- credential: buildCredentialReport(params.provider, params.identity)
7985
+ credential: buildCredentialReport(params.provider, params.identity, params.identityName, params.credentialCache)
7811
7986
  };
7812
7987
  }
7813
7988
  function formatCheckReport(report) {
@@ -7845,12 +8020,12 @@ function formatCheckReport(report) {
7845
8020
  const { credential } = report;
7846
8021
  if (credential.provider !== void 0) {
7847
8022
  lines.push(
7848
- ` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem ?? ""}` : formatCredentialSummary(credential.provider.credential)}`
8023
+ ` Provider ${credential.provider.name}: ${credential.provider.credential === void 0 ? `unusable: ${credential.provider.problem ?? ""}` : formatCredentialSummary(credential.provider.credential)}${credential.provider.cached === void 0 ? "" : `; ${formatCachedCredentialState(credential.provider.cached)}`}`
7849
8024
  );
7850
8025
  }
7851
8026
  if (credential.identity !== void 0) {
7852
8027
  lines.push(
7853
- ` Identity ${report.identityName ?? "(none)"}: ${formatCredentialSummary(credential.identity)}${credential.applies === "provider" ? " (not used: the provider's credential applies)" : ""}`
8028
+ ` Identity ${report.identityName ?? "(none)"}: ${formatCredentialSummary(credential.identity)}${credential.identityCached === void 0 ? "" : `; ${formatCachedCredentialState(credential.identityCached)}`}${credential.applies === "provider" ? " (not used: the provider's credential applies)" : ""}`
7854
8029
  );
7855
8030
  }
7856
8031
  if (credential.applies === "stored-login") {
@@ -7959,10 +8134,10 @@ function collectCheckReport(params) {
7959
8134
  }).input;
7960
8135
  const providerName = cascadeProviderName(cascade);
7961
8136
  const provider = providerName === void 0 ? void 0 : loadCheckProvider(paths, providerName);
7962
- const farmRoot = identityDecision.name === void 0 ? void 0 : import_node_path22.default.join(paths.identitiesDir, identityDecision.name);
8137
+ const farmRoot = identityDecision.name === void 0 ? void 0 : import_node_path23.default.join(paths.identitiesDir, identityDecision.name);
7963
8138
  const settingsFiles = {
7964
- "settings.json": realFsPort.readFileUtf8(import_node_path22.default.join(claudeHome, "settings.json")),
7965
- "settings.local.json": realFsPort.readFileUtf8(import_node_path22.default.join(claudeHome, "settings.local.json"))
8139
+ "settings.json": realFsPort.readFileUtf8(import_node_path23.default.join(claudeHome, "settings.json")),
8140
+ "settings.local.json": realFsPort.readFileUtf8(import_node_path23.default.join(claudeHome, "settings.local.json"))
7966
8141
  };
7967
8142
  const report = runCheck({
7968
8143
  cwd,
@@ -7983,6 +8158,7 @@ function collectCheckReport(params) {
7983
8158
  ...loadedIdentity === void 0 ? {} : { identity: loadedIdentity.config },
7984
8159
  settingsFiles,
7985
8160
  run: realRunPort,
8161
+ credentialCache: realCredentialCacheEnv(paths),
7986
8162
  ...farmRoot === void 0 ? {} : { farmRoot },
7987
8163
  platform: process.platform,
7988
8164
  ...provider === void 0 ? {} : { provider }
@@ -7991,23 +8167,23 @@ function collectCheckReport(params) {
7991
8167
  }
7992
8168
 
7993
8169
  // src/claudeShim.ts
7994
- var import_node_fs7 = __toESM(require("node:fs"), 1);
7995
- var import_node_path23 = __toESM(require("node:path"), 1);
7996
- var import_zod11 = require("zod");
7997
- var ClaudeShimStateSchema = import_zod11.z.strictObject({
7998
- targetPath: import_zod11.z.string().min(1),
7999
- method: import_zod11.z.enum(["hardlink", "copy"]),
8000
- installedAtMs: import_zod11.z.number()
8170
+ var import_node_fs8 = __toESM(require("node:fs"), 1);
8171
+ var import_node_path24 = __toESM(require("node:path"), 1);
8172
+ var import_zod13 = require("zod");
8173
+ var ClaudeShimStateSchema = import_zod13.z.strictObject({
8174
+ targetPath: import_zod13.z.string().min(1),
8175
+ method: import_zod13.z.enum(["hardlink", "copy"]),
8176
+ installedAtMs: import_zod13.z.number()
8001
8177
  });
8002
8178
  function commandFilename(ownExecutablePath, commandName) {
8003
8179
  return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
8004
8180
  }
8005
8181
  function resolveOwnBinaryCheck(paths, contentSourcePath) {
8006
8182
  const state = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
8007
- const recordedTarget = state === void 0 ? void 0 : import_node_path23.default.resolve(state.targetPath);
8183
+ const recordedTarget = state === void 0 ? void 0 : import_node_path24.default.resolve(state.targetPath);
8008
8184
  let ownStat;
8009
8185
  return (candidate) => {
8010
- if (recordedTarget !== void 0 && import_node_path23.default.resolve(candidate) === recordedTarget) {
8186
+ if (recordedTarget !== void 0 && import_node_path24.default.resolve(candidate) === recordedTarget) {
8011
8187
  return true;
8012
8188
  }
8013
8189
  ownStat ??= statOrUndefined(contentSourcePath);
@@ -8021,7 +8197,7 @@ function isEnoent4(error) {
8021
8197
  }
8022
8198
  function statOrUndefined(target) {
8023
8199
  try {
8024
- return import_node_fs7.default.statSync(target);
8200
+ return import_node_fs8.default.statSync(target);
8025
8201
  } catch (error) {
8026
8202
  if (isEnoent4(error)) {
8027
8203
  return void 0;
@@ -8029,22 +8205,22 @@ function statOrUndefined(target) {
8029
8205
  throw error;
8030
8206
  }
8031
8207
  }
8032
- var nodeLinkFs = { link: import_node_fs7.default.linkSync, copyFile: import_node_fs7.default.copyFileSync, chmod: import_node_fs7.default.chmodSync };
8208
+ var nodeLinkFs = { link: import_node_fs8.default.linkSync, copyFile: import_node_fs8.default.copyFileSync, chmod: import_node_fs8.default.chmodSync };
8033
8209
  function findPathShadow(params) {
8034
- const resolvedTargetDir = import_node_path23.default.resolve(params.targetDir);
8210
+ const resolvedTargetDir = import_node_path24.default.resolve(params.targetDir);
8035
8211
  for (const dir of params.pathDirs) {
8036
8212
  const found = params.findExecutableInDir(dir, params.targetFilename);
8037
8213
  if (found === void 0) {
8038
8214
  continue;
8039
8215
  }
8040
- return import_node_path23.default.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
8216
+ return import_node_path24.default.resolve(dir) === resolvedTargetDir ? { status: "ok" } : { status: "shadowed", by: found };
8041
8217
  }
8042
- return params.pathDirs.some((dir) => import_node_path23.default.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
8218
+ return params.pathDirs.some((dir) => import_node_path24.default.resolve(dir) === resolvedTargetDir) ? { status: "ok" } : { status: "not-on-path" };
8043
8219
  }
8044
8220
 
8045
8221
  // src/doctorReport.ts
8046
- var import_node_fs8 = __toESM(require("node:fs"), 1);
8047
- var import_node_path24 = __toESM(require("node:path"), 1);
8222
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
8223
+ var import_node_path25 = __toESM(require("node:path"), 1);
8048
8224
  function refinePathShadow(status, ownExecutablePath, realpath) {
8049
8225
  if (status.status !== "shadowed") {
8050
8226
  return status;
@@ -8068,7 +8244,7 @@ function validateJson(schema, input) {
8068
8244
  return { ok: true, data: result.data };
8069
8245
  }
8070
8246
  function pushPathResolution(push, resolution) {
8071
- const ownDir = import_node_path24.default.dirname(resolution.ownExecutablePath);
8247
+ const ownDir = import_node_path25.default.dirname(resolution.ownExecutablePath);
8072
8248
  switch (resolution.agentShim.status) {
8073
8249
  case "ok":
8074
8250
  push("path-resolution", "pass", `\`agent-shim\` on PATH resolves to this running executable, ${resolution.ownExecutablePath}.`, "agent-shim");
@@ -8110,7 +8286,14 @@ function pushPathResolution(push, resolution) {
8110
8286
  break;
8111
8287
  }
8112
8288
  }
8113
- function pushProvider(push, entry) {
8289
+ function cacheClause(cacheEnv, owner, block) {
8290
+ if (cacheEnv === void 0 || block.cache === void 0) {
8291
+ return { text: "", unreadable: false };
8292
+ }
8293
+ const state = describeCachedCredential({ port: cacheEnv.port, platform: cacheEnv.platform, owner, cache: block.cache, nowMs: cacheEnv.now() });
8294
+ return { text: `; ${formatCachedCredentialState(state)}`, unreadable: state.status === "unreadable" };
8295
+ }
8296
+ function pushProvider(push, entry, cacheEnv) {
8114
8297
  if (entry.raw === void 0) {
8115
8298
  push("provider", "fail", `${entry.path} is missing.`, entry.name);
8116
8299
  return;
@@ -8132,18 +8315,19 @@ function pushProvider(push, entry) {
8132
8315
  push("provider", "fail", new ConfigValidationError(entry.path, validated.error.issues).message, entry.name);
8133
8316
  return;
8134
8317
  }
8135
- push("provider", "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)}).`, entry.name);
8318
+ const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated.data.credential);
8319
+ push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)})${cache.text}.`, entry.name);
8136
8320
  }
8137
8321
  function pushLegacyName(push, params) {
8138
8322
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
8139
- if (legacyVariables.length === 0 && import_node_path24.default.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
8323
+ if (legacyVariables.length === 0 && import_node_path25.default.basename(params.rootPath) !== LEGACY_HOME_DIRNAME) {
8140
8324
  push("legacy-name", "pass", "Nothing here still uses the former `claude-use` name.");
8141
8325
  return;
8142
8326
  }
8143
8327
  for (const name of legacyVariables) {
8144
8328
  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);
8145
8329
  }
8146
- if (import_node_path24.default.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
8330
+ if (import_node_path25.default.basename(params.rootPath) === LEGACY_HOME_DIRNAME) {
8147
8331
  push(
8148
8332
  "legacy-name",
8149
8333
  "warn",
@@ -8225,10 +8409,11 @@ function runDoctor(params) {
8225
8409
  );
8226
8410
  } else {
8227
8411
  const credential = validated.data.credential;
8412
+ const cache = credential === void 0 ? { text: "", unreadable: false } : cacheClause(params.credentialCache, `identity ${entry.name}`, credential);
8228
8413
  push(
8229
8414
  "identity",
8230
- "pass",
8231
- `${entry.name} is valid and authenticates with ${credential === void 0 ? "its stored login" : `credential ${describeCredential(credential)}`}.`,
8415
+ cache.unreadable ? "fail" : "pass",
8416
+ `${entry.name} is valid and authenticates with ${credential === void 0 ? "its stored login" : `credential ${describeCredential(credential)}`}${cache.text}.`,
8232
8417
  entry.name
8233
8418
  );
8234
8419
  }
@@ -8248,7 +8433,7 @@ function runDoctor(params) {
8248
8433
  return poolName === void 0 ? validIdentityNames.has(selector) : poolName in pools;
8249
8434
  };
8250
8435
  for (const entry of params.providers) {
8251
- pushProvider(push, entry);
8436
+ pushProvider(push, entry, params.credentialCache);
8252
8437
  }
8253
8438
  if (params.platform !== "darwin" || params.run === void 0) {
8254
8439
  push("keychain", "pass", "Skipped (not macOS).");
@@ -8442,27 +8627,27 @@ function formatDoctorReport(report) {
8442
8627
  }
8443
8628
  function realpathOrSelf(target) {
8444
8629
  try {
8445
- return import_node_fs8.default.realpathSync(target);
8630
+ return import_node_fs9.default.realpathSync(target);
8446
8631
  } catch {
8447
8632
  return target;
8448
8633
  }
8449
8634
  }
8450
8635
  function collectDoctorReport(params) {
8451
8636
  const { paths } = params;
8452
- const identityNames = import_node_fs8.default.existsSync(paths.identitiesDir) ? import_node_fs8.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
8637
+ const identityNames = import_node_fs9.default.existsSync(paths.identitiesDir) ? import_node_fs9.default.readdirSync(paths.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && isIdentityDirectoryName(entry.name)).map((entry) => entry.name).sort() : [];
8453
8638
  const identities = identityNames.map((name) => {
8454
- const farmRoot = import_node_path24.default.join(paths.identitiesDir, name);
8455
- const identityPath = import_node_path24.default.join(farmRoot, "identity.json");
8639
+ const farmRoot = import_node_path25.default.join(paths.identitiesDir, name);
8640
+ const identityPath = import_node_path25.default.join(farmRoot, "identity.json");
8456
8641
  return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot };
8457
8642
  });
8458
- const profileNames = import_node_fs8.default.existsSync(paths.configProfilesDir) ? import_node_fs8.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
8643
+ const profileNames = import_node_fs9.default.existsSync(paths.configProfilesDir) ? import_node_fs9.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
8459
8644
  const configProfiles = profileNames.map((name) => {
8460
- const profilePath = import_node_path24.default.join(paths.configProfilesDir, `${name}.json`);
8645
+ const profilePath = import_node_path25.default.join(paths.configProfilesDir, `${name}.json`);
8461
8646
  return { name, path: profilePath, raw: realFsPort.readFileUtf8(profilePath) };
8462
8647
  });
8463
- const providerNames = import_node_fs8.default.existsSync(paths.providersDir) ? import_node_fs8.default.readdirSync(paths.providersDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
8648
+ const providerNames = import_node_fs9.default.existsSync(paths.providersDir) ? import_node_fs9.default.readdirSync(paths.providersDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
8464
8649
  const providers = providerNames.map((name) => {
8465
- const providerPath = import_node_path24.default.join(paths.providersDir, `${name}.json`);
8650
+ const providerPath = import_node_path25.default.join(paths.providersDir, `${name}.json`);
8466
8651
  return { name, path: providerPath, raw: realFsPort.readFileUtf8(providerPath) };
8467
8652
  });
8468
8653
  const ownExecutablePath = realOwnExecutablePath();
@@ -8474,12 +8659,12 @@ function collectDoctorReport(params) {
8474
8659
  binaryDiscovery = { ok: false, message: error instanceof Error ? error.message : String(error) };
8475
8660
  }
8476
8661
  const shimState = readJson(paths.claudeShimFile, ClaudeShimStateSchema);
8477
- const pathDirs = (params.env.PATH ?? "").split(import_node_path24.default.delimiter).filter((dir) => dir !== "");
8662
+ const pathDirs = (params.env.PATH ?? "").split(import_node_path25.default.delimiter).filter((dir) => dir !== "");
8478
8663
  const claudeShimShadow = shimState === void 0 ? void 0 : refinePathShadow(
8479
8664
  findPathShadow({
8480
8665
  pathDirs,
8481
- targetDir: import_node_path24.default.dirname(shimState.targetPath),
8482
- targetFilename: import_node_path24.default.basename(shimState.targetPath),
8666
+ targetDir: import_node_path25.default.dirname(shimState.targetPath),
8667
+ targetFilename: import_node_path25.default.basename(shimState.targetPath),
8483
8668
  findExecutableInDir
8484
8669
  }),
8485
8670
  shimState.targetPath,
@@ -8487,6 +8672,7 @@ function collectDoctorReport(params) {
8487
8672
  );
8488
8673
  const report = runDoctor({
8489
8674
  env: params.env,
8675
+ credentialCache: realCredentialCacheEnv(paths),
8490
8676
  identities,
8491
8677
  configProfiles,
8492
8678
  providers,
@@ -8495,14 +8681,14 @@ function collectDoctorReport(params) {
8495
8681
  categoriesLocal: { path: paths.categoriesLocalFile, raw: realFsPort.readFileUtf8(paths.categoriesLocalFile) },
8496
8682
  activeIdentity: { path: paths.activeIdentityFile, raw: realFsPort.readFileUtf8(paths.activeIdentityFile) },
8497
8683
  binaryDiscovery,
8498
- claudeShim: { state: shimState, targetExists: shimState !== void 0 && import_node_fs8.default.existsSync(shimState.targetPath) },
8684
+ claudeShim: { state: shimState, targetExists: shimState !== void 0 && import_node_fs9.default.existsSync(shimState.targetPath) },
8499
8685
  rootPath: paths.root,
8500
8686
  pathResolution: {
8501
8687
  ownExecutablePath,
8502
8688
  agentShim: refinePathShadow(
8503
8689
  findPathShadow({
8504
8690
  pathDirs,
8505
- targetDir: import_node_path24.default.dirname(ownExecutablePath),
8691
+ targetDir: import_node_path25.default.dirname(ownExecutablePath),
8506
8692
  targetFilename: commandFilename(ownExecutablePath, "agent-shim"),
8507
8693
  findExecutableInDir
8508
8694
  }),
@@ -8523,7 +8709,7 @@ function collectDoctorReport(params) {
8523
8709
 
8524
8710
  // src/launcher.ts
8525
8711
  var import_node_crypto10 = require("node:crypto");
8526
- var import_node_path26 = __toESM(require("node:path"), 1);
8712
+ var import_node_path27 = __toESM(require("node:path"), 1);
8527
8713
 
8528
8714
  // src/launcher/argv.ts
8529
8715
  var ConflictingIdentityError = class extends UsageError {
@@ -8775,7 +8961,7 @@ function buildCliOverride(params) {
8775
8961
  }
8776
8962
 
8777
8963
  // src/launcher/flags.ts
8778
- var import_node_path25 = __toESM(require("node:path"), 1);
8964
+ var import_node_path26 = __toESM(require("node:path"), 1);
8779
8965
 
8780
8966
  // src/headroom/headers.ts
8781
8967
  function mergeAnthropicCustomHeaders(existing, additions) {
@@ -8845,7 +9031,7 @@ function buildEnv(params) {
8845
9031
  const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
8846
9032
  let providerCustomHeaders;
8847
9033
  if (!params.configDirEscapeHatch && params.resolvedIdentityName !== void 0) {
8848
- env.CLAUDE_CONFIG_DIR = import_node_path25.default.join(params.identitiesDir, params.resolvedIdentityName);
9034
+ env.CLAUDE_CONFIG_DIR = import_node_path26.default.join(params.identitiesDir, params.resolvedIdentityName);
8849
9035
  }
8850
9036
  if (params.provider !== void 0) {
8851
9037
  const providerEnv = params.provider.definition.env ?? {};
@@ -9006,7 +9192,7 @@ function prepareLaunch(params) {
9006
9192
  identityDefaultConfigProfile: loadedIdentity?.config.defaultConfigProfile,
9007
9193
  globalDefaultConfigProfile: params.globalDefaultConfigProfile
9008
9194
  });
9009
- if (configProfileDecision.name !== void 0 && params.allowMissingConfigProfile !== true && fs12.readConfigFile(import_node_path26.default.join(paths.configProfilesDir, `${configProfileDecision.name}.json`)) === void 0) {
9195
+ if (configProfileDecision.name !== void 0 && params.allowMissingConfigProfile !== true && fs12.readConfigFile(import_node_path27.default.join(paths.configProfilesDir, `${configProfileDecision.name}.json`)) === void 0) {
9010
9196
  log.error(
9011
9197
  `agent-shim: no configuration profile named "${configProfileDecision.name}" (selected via ${configProfileDecision.source}). Run \`agent-shim profile add ${configProfileDecision.name}\` first.`
9012
9198
  );
@@ -9179,84 +9365,6 @@ function prepareLaunch(params) {
9179
9365
  var import_node_os5 = __toESM(require("node:os"), 1);
9180
9366
  var import_node_crypto12 = require("node:crypto");
9181
9367
 
9182
- // src/realCredentialCache.ts
9183
- var import_node_child_process2 = require("node:child_process");
9184
- var import_node_fs9 = __toESM(require("node:fs"), 1);
9185
- var import_node_path27 = __toESM(require("node:path"), 1);
9186
- var KEYCHAIN_CACHE_SERVICE = "agent-shim-credential-cache";
9187
- var CACHE_DIR_MODE = 448;
9188
- var CACHE_FILE_MODE = 384;
9189
- function cacheFileNameFor(owner) {
9190
- return `${owner.replace(/[^A-Za-z0-9._-]/g, "_")}.json`;
9191
- }
9192
- function parseEntry(raw) {
9193
- let json;
9194
- try {
9195
- json = JSON.parse(raw);
9196
- } catch {
9197
- return void 0;
9198
- }
9199
- const parsed = CachedCredentialSchema.safeParse(json);
9200
- return parsed.success ? parsed.data : void 0;
9201
- }
9202
- function requireKeychain() {
9203
- if (process.platform !== "darwin") {
9204
- throw new Error("the keychain credential store needs macOS; use --credential-cache-store file");
9205
- }
9206
- }
9207
- function security(args, stdin) {
9208
- const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8", ...stdin === void 0 ? {} : { input: stdin }, stdio: ["pipe", "pipe", "ignore"] });
9209
- return { status: result.status, stdout: result.stdout };
9210
- }
9211
- function createRealCredentialCache(root) {
9212
- const dir = import_node_path27.default.join(root, "credential-cache");
9213
- const filePath = (owner) => import_node_path27.default.join(dir, cacheFileNameFor(owner));
9214
- return {
9215
- read(store, owner) {
9216
- if (store === "file") {
9217
- try {
9218
- return parseEntry(import_node_fs9.default.readFileSync(filePath(owner), "utf8"));
9219
- } catch {
9220
- return void 0;
9221
- }
9222
- }
9223
- requireKeychain();
9224
- const result = security(["find-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner, "-w"]);
9225
- return result.status === 0 ? parseEntry(Buffer.from(result.stdout.trim(), "base64").toString("utf8")) : void 0;
9226
- },
9227
- write(store, owner, entry) {
9228
- const serialised = JSON.stringify(entry);
9229
- if (store === "file") {
9230
- import_node_fs9.default.mkdirSync(dir, { recursive: true, mode: CACHE_DIR_MODE });
9231
- import_node_fs9.default.chmodSync(dir, CACHE_DIR_MODE);
9232
- const target = filePath(owner);
9233
- const temp = `${target}.${String(process.pid)}.tmp`;
9234
- import_node_fs9.default.writeFileSync(temp, serialised, { mode: CACHE_FILE_MODE });
9235
- import_node_fs9.default.renameSync(temp, target);
9236
- return;
9237
- }
9238
- requireKeychain();
9239
- const encoded = Buffer.from(serialised, "utf8").toString("base64");
9240
- const result = security(["-i"], `add-generic-password -U -s ${KEYCHAIN_CACHE_SERVICE} -a ${JSON.stringify(owner)} -w ${encoded}
9241
- `);
9242
- if (result.status !== 0) {
9243
- throw new Error(`could not write the keychain credential cache for ${owner}`);
9244
- }
9245
- },
9246
- remove(store, owner) {
9247
- if (store === "file") {
9248
- import_node_fs9.default.rmSync(filePath(owner), { force: true });
9249
- return;
9250
- }
9251
- requireKeychain();
9252
- security(["delete-generic-password", "-s", KEYCHAIN_CACHE_SERVICE, "-a", owner]);
9253
- }
9254
- };
9255
- }
9256
- function realCredentialCacheEnv(paths) {
9257
- return { port: createRealCredentialCache(paths.root), platform: process.platform, now: () => Date.now() };
9258
- }
9259
-
9260
9368
  // src/frontdoor/realFrontDoorPort.ts
9261
9369
  var import_node_fs10 = __toESM(require("node:fs"), 1);
9262
9370
 
@@ -9521,8 +9629,11 @@ function prepareClaudeLaunch(options) {
9521
9629
  InvalidProviderNameError,
9522
9630
  LaunchRefusedError,
9523
9631
  LegacyProviderFileError,
9632
+ PROMPT_CACHE_TTL_MS,
9633
+ PlanClassSchema,
9524
9634
  PoolNameSchema,
9525
9635
  PoolNotFoundError,
9636
+ PoolPickReportSchema,
9526
9637
  PoolSchema,
9527
9638
  PortableConfigSchema,
9528
9639
  ProfileAlreadyExistsError,
@@ -9559,16 +9670,19 @@ function prepareClaudeLaunch(options) {
9559
9670
  checkReportToJson,
9560
9671
  collectCheckReport,
9561
9672
  collectDoctorReport,
9673
+ collectPoolPick,
9562
9674
  createLeafCache,
9563
9675
  createProfile,
9564
9676
  createRcControlHandler,
9565
9677
  createRcSessionTracker,
9566
9678
  describeProviderEndpoint,
9567
9679
  detectAmbientCredential,
9680
+ effectiveWindow,
9568
9681
  ensureCa,
9569
9682
  ensureFrontDoor,
9570
9683
  ensureHeadroom,
9571
9684
  evaluateAmbientCredentialGuard,
9685
+ evaluateWhen,
9572
9686
  formatAmbientCredentialGuardMessage,
9573
9687
  formatCheckReport,
9574
9688
  formatDoctorReport,
@@ -9586,13 +9700,16 @@ function prepareClaudeLaunch(options) {
9586
9700
  listProfiles,
9587
9701
  listProviders,
9588
9702
  listUsageSnapshots,
9703
+ matchBranch,
9589
9704
  mintLeaf,
9590
9705
  observingRoutedRoute,
9591
9706
  parseConnectTarget,
9707
+ planOf,
9592
9708
  prepareClaudeLaunch,
9593
9709
  prepareLaunch,
9594
9710
  profileExists,
9595
9711
  providerExists,
9712
+ rankPool,
9596
9713
  rcEventWriteResultFromAnswer,
9597
9714
  rcObserverAsPassthrough,
9598
9715
  readActiveIdentity,