agent-shim 8.9.0 → 8.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,7 +95,8 @@ agent-shim identity resolve-conflicts <name> # interactively resolve a retained
95
95
  agent-shim profile add [name] [--extends <profile>]... [--description <text>] # interactive with no options on a terminal [--json]
96
96
  agent-shim profile set <name> [--category <category>=<bool>]... [--entry <category>/<path>=<bool>]... [--json]
97
97
  agent-shim profile set <name> [--extends <profile>]... [--no-extends] [--description <text> | --no-description] [--json]
98
- agent-shim profile set <name> [--[no-]launch-skip-permissions] [--[no-]launch-remote-control] [--[no-]launch-headroom] [--[no-]launch-track-usage] [--json]
98
+ agent-shim profile set <name> [--[no-]launch-skip-permissions] [--[no-]launch-remote-control] [--[no-]launch-headroom] [--[no-]launch-track-usage]
99
+ agent-shim profile set <name> [--launch-claude-version <version> | --no-launch-claude-version] [--json] [--json]
99
100
  agent-shim profile set <name> [--launch-provider <provider> | --no-launch-provider] [--json]
100
101
  agent-shim profile list [--json]
101
102
  agent-shim profile show <name> [--json]
@@ -150,6 +151,7 @@ agent-shim run [@<identity>] [launch flags] [claude arguments]
150
151
  # --identity <name> --config-profile <name> --provider <name> | --no-provider
151
152
  # --category <category>=<bool> --share <category>/<path> --hide <category>/<path> (each repeatable)
152
153
  # --[no-]skip-permissions --[no-]remote-control --[no-]headroom --[no-]track-usage --[no-]wait
154
+ # --claude-version <version> run exactly this installed Claude Code version
153
155
  # --native run the real claude with nothing from agent-shim applied; cannot be combined with the flags above
154
156
  claude @<identity> ... # the same, once `agent-shim shim enable` has run
155
157
  ```
package/dist/cli.cjs CHANGED
@@ -55043,9 +55043,9 @@ function applyPatch(filePath, schema, patch, options = {}) {
55043
55043
  if (!result.success) {
55044
55044
  throw new ConfigValidationError(filePath, result.error.issues);
55045
55045
  }
55046
- const validated = result.data;
55047
- writeJsonAtomic(filePath, validated, storeFs);
55048
- return validated;
55046
+ const validated2 = result.data;
55047
+ writeJsonAtomic(filePath, validated2, storeFs);
55048
+ return validated2;
55049
55049
  }
55050
55050
 
55051
55051
  // src/realPorts.ts
@@ -55085,6 +55085,23 @@ function compareVersions(a2, b) {
55085
55085
  }
55086
55086
  return 0;
55087
55087
  }
55088
+ var ClaudeVersionNotInstalledError = class extends CliError {
55089
+ constructor(version2, versionsDir, installed) {
55090
+ super(
55091
+ `Claude Code ${version2} is pinned for this launch but is not installed under "${versionsDir}"` + (installed.length === 0 ? " (no versions are installed there)." : `. Installed: ${installed.join(", ")}.`) + " Install it, or change or remove the pin (--claude-version, AGENT_SHIM_CLAUDE_VERSION, or launch.claudeVersion)."
55092
+ );
55093
+ this.version = version2;
55094
+ this.versionsDir = versionsDir;
55095
+ this.installed = installed;
55096
+ this.name = "ClaudeVersionNotInstalledError";
55097
+ }
55098
+ version;
55099
+ versionsDir;
55100
+ installed;
55101
+ };
55102
+ function installedVersions(entries) {
55103
+ return entries.filter((entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)).map((entry) => entry.name).sort(compareVersions);
55104
+ }
55088
55105
  function pickHighestVersion(entries) {
55089
55106
  const candidates = entries.filter(
55090
55107
  (entry) => entry.isFile && entry.isExecutable && entry.sizeBytes > 0 && isNumericDottedVersion(entry.name)
@@ -55100,6 +55117,13 @@ function discoverClaudeBinary(options) {
55100
55117
  const versionsDir = options.versionsDir ?? defaultVersionsDir();
55101
55118
  const entries = options.listVersionsDir(versionsDir);
55102
55119
  const highestVersion = pickHighestVersion(entries);
55120
+ if (options.version !== void 0) {
55121
+ const installed = installedVersions(entries);
55122
+ if (!installed.includes(options.version)) {
55123
+ throw new ClaudeVersionNotInstalledError(options.version, versionsDir, installed);
55124
+ }
55125
+ return { path: import_node_path3.default.join(versionsDir, options.version), source: "versions-dir", version: options.version };
55126
+ }
55103
55127
  if (highestVersion !== void 0) {
55104
55128
  return {
55105
55129
  path: import_node_path3.default.join(versionsDir, highestVersion),
@@ -55167,7 +55191,9 @@ var LaunchSchema = external_exports.strictObject({
55167
55191
  remoteControl: external_exports.boolean().optional(),
55168
55192
  provider: external_exports.string().min(1).optional(),
55169
55193
  headroom: external_exports.boolean().optional(),
55170
- trackUsage: external_exports.boolean().optional()
55194
+ trackUsage: external_exports.boolean().optional(),
55195
+ /** The exact Claude Code version to run, as it is named in Claude Code's versions directory (`2.1.220`). A pin that is not installed fails the launch; it never falls back to the highest version. */
55196
+ claudeVersion: external_exports.string().regex(NUMERIC_DOTTED_VERSION_RE, "must be an exact dotted-numeric Claude Code version such as 2.1.220").optional()
55171
55197
  });
55172
55198
  var CREDENTIAL_TARGETS = ["bearer", "apiKey", "oauthToken"];
55173
55199
  var PROVIDER_CREDENTIAL_TARGETS = ["bearer", "apiKey"];
@@ -56501,13 +56527,17 @@ function realContentSourcePath() {
56501
56527
  return resolveContentSourcePath({ isSea: (0, import_node_sea.isSea)(), execPath: process.execPath, argv1: process.argv[1] });
56502
56528
  }
56503
56529
  function realResolveClaudeBinary(isOwnBinary) {
56504
- return () => discoverClaudeBinary({
56530
+ return (request7) => discoverClaudeBinary({
56505
56531
  listVersionsDir,
56506
56532
  pathDirs: (process.env.PATH ?? "").split(import_node_path8.default.delimiter).filter((dir) => dir !== ""),
56507
56533
  findExecutableInDir,
56508
- isOwnBinary
56534
+ isOwnBinary,
56535
+ ...request7?.version === void 0 ? {} : { version: request7.version }
56509
56536
  });
56510
56537
  }
56538
+ function realInstalledClaudeVersions() {
56539
+ return installedVersions(listVersionsDir(defaultVersionsDir()));
56540
+ }
56511
56541
 
56512
56542
  // src/claudeShim.ts
56513
56543
  var EXECUTABLE_FILE_MODE = 493;
@@ -59284,6 +59314,9 @@ function flattenLayers(layers, options) {
59284
59314
  if (layer.launch.trackUsage !== void 0) {
59285
59315
  launch.trackUsage = layer.launch.trackUsage;
59286
59316
  }
59317
+ if (layer.launch.claudeVersion !== void 0) {
59318
+ launch.claudeVersion = layer.launch.claudeVersion;
59319
+ }
59287
59320
  }
59288
59321
  if (layer.entries === void 0) {
59289
59322
  continue;
@@ -61264,6 +61297,9 @@ function applyProfileSet(paths, name, options) {
61264
61297
  if (options.launchProvider !== void 0) {
61265
61298
  launchPatch.provider = options.launchProvider === false ? void 0 : options.launchProvider;
61266
61299
  }
61300
+ if (options.launchClaudeVersion !== void 0) {
61301
+ launchPatch.claudeVersion = options.launchClaudeVersion === false ? void 0 : options.launchClaudeVersion;
61302
+ }
61267
61303
  if (Object.keys(launchPatch).length > 0) {
61268
61304
  setProfileLaunchFlags(paths, name, launchPatch);
61269
61305
  touched = true;
@@ -61305,7 +61341,7 @@ function registerProfileCommand(program2, deps) {
61305
61341
  withExamples(
61306
61342
  profile.command("set <name>").description(
61307
61343
  "Update a configuration profile's categories, entries, extends list, description or launch settings. With no options on a terminal, walks through its categories."
61308
- ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
61344
+ ).option("--category <category=bool>", "Share (true) or hide (false) a whole category (repeatable).", collectBoolPair).option("--entry <path=bool>", "Share (true) or hide (false) one <category>/<path> entry (repeatable).", collectBoolPair).option("--extends <profile>", "Replace the extends list with these profiles (repeatable, in order).", collectRepeated).option("--no-extends", "Clear the extends list.").option("--description <text>", "Replace the description.").option("--no-description", "Clear the description.").option("--launch-skip-permissions", "Launches under this profile skip permission prompts.").option("--no-launch-skip-permissions", "Launches under this profile keep permission prompts.").option("--launch-remote-control", "Launches under this profile enable Remote Control.").option("--no-launch-remote-control", "Launches under this profile leave Remote Control off.").option("--launch-headroom", "Launches under this profile route through headroom.").option("--no-launch-headroom", "Launches under this profile route direct.").option("--launch-track-usage", "Launches under this profile route through the front door so their requests and quota are recorded, with no headroom needed.").option("--no-launch-track-usage", "Launches under this profile are not recorded unless a provider or headroom routes them anyway.").option("--launch-provider <provider>", "Launches under this profile route through this provider.").option("--no-launch-provider", "Clear this profile's provider selection.").option("--launch-claude-version <version>", "Launches under this profile run exactly this installed Claude Code version (such as 2.1.220); a version that is not installed fails the launch.").option("--no-launch-claude-version", "Clear this profile's Claude Code version pin.").option("--json", "Print the result as JSON.").action(async (name, allOptions) => {
61309
61345
  const { json: json2, ...options } = allOptions;
61310
61346
  requireProfileExists(paths, name);
61311
61347
  if (Object.entries(allOptions).every(([key, value2]) => key === "json" || value2 === void 0)) {
@@ -61673,7 +61709,7 @@ function registerIdentityCommand(program2, deps) {
61673
61709
  // package.json
61674
61710
  var package_default = {
61675
61711
  name: "agent-shim",
61676
- version: "8.9.0",
61712
+ version: "8.10.0",
61677
61713
  description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
61678
61714
  license: "MIT",
61679
61715
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -63553,6 +63589,34 @@ function parseEnvBool(variable, value2) {
63553
63589
  return parsed;
63554
63590
  }
63555
63591
 
63592
+ // src/launcher/claudeVersion.ts
63593
+ var InvalidClaudeVersionError = class extends UsageError {
63594
+ constructor(value2, origin) {
63595
+ super(`${origin} names Claude Code version "${value2}", which is not an exact dotted-numeric version such as 2.1.220.`);
63596
+ this.value = value2;
63597
+ this.origin = origin;
63598
+ this.name = "InvalidClaudeVersionError";
63599
+ }
63600
+ value;
63601
+ origin;
63602
+ };
63603
+ function validated(value2, origin) {
63604
+ if (!NUMERIC_DOTTED_VERSION_RE.test(value2)) {
63605
+ throw new InvalidClaudeVersionError(value2, origin);
63606
+ }
63607
+ return value2;
63608
+ }
63609
+ function resolveClaudeVersion(params) {
63610
+ if (params.flag !== void 0) {
63611
+ return { version: validated(params.flag, "--claude-version"), source: "flag" };
63612
+ }
63613
+ const fromEnv = params.env.AGENT_SHIM_CLAUDE_VERSION;
63614
+ if (fromEnv !== void 0 && fromEnv !== "") {
63615
+ return { version: validated(fromEnv, "AGENT_SHIM_CLAUDE_VERSION"), source: "environment" };
63616
+ }
63617
+ return params.cascade === void 0 ? void 0 : { version: params.cascade, source: "cascade" };
63618
+ }
63619
+
63556
63620
  // src/launcher/guard.ts
63557
63621
  var AMBIENT_CREDENTIAL_VARS = [
63558
63622
  "ANTHROPIC_API_KEY",
@@ -63750,6 +63814,19 @@ function buildCredentialReport(provider, identity, identityName, cacheEnv) {
63750
63814
  ...identityCached === void 0 ? {} : { identityCached }
63751
63815
  };
63752
63816
  }
63817
+ function buildClaudeVersionReport(params, cascadePin) {
63818
+ const installed = params.installedClaudeVersions;
63819
+ if (installed === void 0) {
63820
+ return void 0;
63821
+ }
63822
+ const pin = resolveClaudeVersion({ env: params.env, ...cascadePin === void 0 ? {} : { cascade: cascadePin } });
63823
+ const highestInstalled = installed.at(-1);
63824
+ return {
63825
+ ...pin === void 0 ? {} : { pinned: { ...pin, installed: installed.includes(pin.version) } },
63826
+ ...highestInstalled === void 0 ? {} : { highestInstalled },
63827
+ installed
63828
+ };
63829
+ }
63753
63830
  function runCheck(params) {
63754
63831
  const facts = buildEntryFacts({
63755
63832
  fs: params.farmFs,
@@ -63774,6 +63851,7 @@ function runCheck(params) {
63774
63851
  const settingsShared = resolved.flattened.categories.get("settings") ?? SHIPPED_CATEGORY_DEFAULTS.settings;
63775
63852
  const settingsExposure = inspectSettingsExposure({ settingsShared, files: params.settingsFiles });
63776
63853
  const keychain = params.platform === "darwin" && params.run !== void 0 && params.farmRoot !== void 0 ? lookupKeychainService(params.run, params.farmRoot) : void 0;
63854
+ const claudeVersion = buildClaudeVersionReport(params, resolved.flattened.launch.claudeVersion);
63777
63855
  return {
63778
63856
  ...params.identityName === void 0 ? {} : { identityName: params.identityName },
63779
63857
  identitySource: params.identitySource,
@@ -63786,7 +63864,8 @@ function runCheck(params) {
63786
63864
  ambientCredential,
63787
63865
  ...keychain === void 0 ? {} : { keychain },
63788
63866
  settingsExposure,
63789
- credential: buildCredentialReport(params.provider, params.identity, params.identityName, params.credentialCache)
63867
+ credential: buildCredentialReport(params.provider, params.identity, params.identityName, params.credentialCache),
63868
+ ...claudeVersion === void 0 ? {} : { claudeVersion }
63790
63869
  };
63791
63870
  }
63792
63871
  function formatCheckReport(report) {
@@ -63835,6 +63914,17 @@ function formatCheckReport(report) {
63835
63914
  if (credential.applies === "stored-login") {
63836
63915
  lines.push(" The identity's stored login (no credential block applies).");
63837
63916
  }
63917
+ if (report.claudeVersion !== void 0) {
63918
+ lines.push("", "Claude Code version:");
63919
+ const { pinned, highestInstalled, installed } = report.claudeVersion;
63920
+ if (pinned !== void 0) {
63921
+ lines.push(
63922
+ pinned.installed ? ` ${pinned.version}, pinned by the ${pinned.source} (installed)` : ` ${pinned.version}, pinned by the ${pinned.source}, but NOT installed; a launch here fails. Installed: ${installed.length === 0 ? "none" : installed.join(", ")}`
63923
+ );
63924
+ } else {
63925
+ lines.push(highestInstalled === void 0 ? " none installed in the versions directory (a launch falls back to a claude on PATH)" : ` ${highestInstalled} (the highest installed; nothing pins a version)`);
63926
+ }
63927
+ }
63838
63928
  lines.push("", "Ambient-credential exposure:");
63839
63929
  lines.push(
63840
63930
  report.ambientCredential.ok ? ` OK \u2014 none of ${AMBIENT_CREDENTIAL_VARS.join(", ")} is set to a non-empty value.` : report.ambientCredential.message.split("\n").map((line) => ` ${line}`).join("\n")
@@ -63873,11 +63963,12 @@ function checkReportToJson(report) {
63873
63963
  ambientCredential: report.ambientCredential.ok ? { ok: true } : { ok: false, variable: report.ambientCredential.variable, message: report.ambientCredential.message },
63874
63964
  ...report.keychain === void 0 ? {} : { keychain: report.keychain },
63875
63965
  settingsExposure: report.settingsExposure,
63876
- credential: report.credential
63966
+ credential: report.credential,
63967
+ ...report.claudeVersion === void 0 ? {} : { claudeVersion: report.claudeVersion }
63877
63968
  };
63878
63969
  }
63879
63970
  function checkReportHasWarnings(report) {
63880
- return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0;
63971
+ return report.resolved.diagnostics.some((diagnostic) => diagnostic.severity !== "info") || report.projectEncodingAmbiguities.length > 0 || !report.ambientCredential.ok || report.credential.provider?.problem !== void 0 || report.claudeVersion?.pinned?.installed === false;
63881
63972
  }
63882
63973
  function loadCheckProvider(paths, name) {
63883
63974
  let definition;
@@ -63963,6 +64054,7 @@ function collectCheckReport(params) {
63963
64054
  settingsFiles,
63964
64055
  run: realRunPort,
63965
64056
  credentialCache: realCredentialCacheEnv(paths),
64057
+ installedClaudeVersions: realInstalledClaudeVersions(),
63966
64058
  ...farmRoot === void 0 ? {} : { farmRoot },
63967
64059
  platform: process.platform,
63968
64060
  ...provider === void 0 ? {} : { provider }
@@ -68686,11 +68778,11 @@ async function validateORPCError(map3, error62) {
68686
68778
  if (!config2.data) {
68687
68779
  return defined ? error62 : new ORPCError(code, { defined: true, status, message, data, cause });
68688
68780
  }
68689
- const validated = await config2.data["~standard"].validate(error62.data);
68690
- if (validated.issues) {
68781
+ const validated2 = await config2.data["~standard"].validate(error62.data);
68782
+ if (validated2.issues) {
68691
68783
  return defined ? new ORPCError(code, { defined: false, status, message, data, cause }) : error62;
68692
68784
  }
68693
- return new ORPCError(code, { defined: true, status, message, data: validated.value, cause });
68785
+ return new ORPCError(code, { defined: true, status, message, data: validated2.value, cause });
68694
68786
  }
68695
68787
  var ContractProcedure = class {
68696
68788
  /**
@@ -72649,7 +72741,7 @@ var ConflictingIdentityError = class extends UsageError {
72649
72741
  positional;
72650
72742
  flag;
72651
72743
  };
72652
- var VALUED_FLAGS = ["--identity", "--config-profile", "--provider", "--category", "--share", "--hide"];
72744
+ var VALUED_FLAGS = ["--identity", "--config-profile", "--provider", "--claude-version", "--category", "--share", "--hide"];
72653
72745
  var BOOLEAN_FLAGS = [
72654
72746
  { flag: "--headroom", key: "headroom" },
72655
72747
  { flag: "--track-usage", key: "trackUsage" },
@@ -72695,6 +72787,7 @@ function parseLauncherArgv(argv) {
72695
72787
  let flagIdentity;
72696
72788
  let configProfile;
72697
72789
  let provider;
72790
+ let claudeVersion;
72698
72791
  const booleans = {};
72699
72792
  const categoryFlags = [];
72700
72793
  const shareFlags = [];
@@ -72749,6 +72842,9 @@ function parseLauncherArgv(argv) {
72749
72842
  case "--provider":
72750
72843
  provider = value2;
72751
72844
  break;
72845
+ case "--claude-version":
72846
+ claudeVersion = value2;
72847
+ break;
72752
72848
  case "--category":
72753
72849
  categoryFlags.push(value2);
72754
72850
  break;
@@ -72770,6 +72866,7 @@ function parseLauncherArgv(argv) {
72770
72866
  ...identity === void 0 ? {} : { identity },
72771
72867
  ...configProfile === void 0 ? {} : { configProfile },
72772
72868
  ...provider === void 0 ? {} : { provider },
72869
+ ...claudeVersion === void 0 ? {} : { claudeVersion },
72773
72870
  ...native ? { native: true } : {},
72774
72871
  ...booleans,
72775
72872
  categoryFlags,
@@ -73173,13 +73270,13 @@ function pushProvider(push, entry, cacheEnv) {
73173
73270
  push("provider", "fail", new LegacyProviderFileError(entry.path, conversion).message, entry.name);
73174
73271
  return;
73175
73272
  }
73176
- const validated = ProviderSchema.safeParse(parsed);
73177
- if (!validated.success) {
73178
- push("provider", "fail", new ConfigValidationError(entry.path, validated.error.issues).message, entry.name);
73273
+ const validated2 = ProviderSchema.safeParse(parsed);
73274
+ if (!validated2.success) {
73275
+ push("provider", "fail", new ConfigValidationError(entry.path, validated2.error.issues).message, entry.name);
73179
73276
  return;
73180
73277
  }
73181
- const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated.data.credential);
73182
- push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated.data)}, credential ${describeCredential(validated.data.credential)})${cache.text}.`, entry.name);
73278
+ const cache = cacheClause(cacheEnv, `provider ${entry.name}`, validated2.data.credential);
73279
+ push("provider", cache.unreadable ? "fail" : "pass", `${entry.name} is valid (${describeProviderEndpoint(validated2.data)}, credential ${describeCredential(validated2.data.credential)})${cache.text}.`, entry.name);
73183
73280
  }
73184
73281
  function pushLegacyName(push, params) {
73185
73282
  const legacyVariables = Object.keys(params.env).filter((name) => name.startsWith(LEGACY_ENV_PREFIX) && params.env[name] !== void 0).sort();
@@ -73203,6 +73300,13 @@ function runDoctor(params) {
73203
73300
  const push = (section, severity, message, subject) => {
73204
73301
  findings.push({ section, severity, message, ...subject === void 0 ? {} : { subject } });
73205
73302
  };
73303
+ const checkPin = (section, subject, where, version2) => {
73304
+ const installed = params.installedClaudeVersions;
73305
+ if (version2 === void 0 || installed === void 0 || installed.includes(version2)) {
73306
+ return;
73307
+ }
73308
+ push(section, "warn", `${where} pins Claude Code ${version2}, which is not installed (installed: ${installed.length === 0 ? "none" : installed.join(", ")}); a launch that resolves to it fails.`, subject);
73309
+ };
73206
73310
  const ambient = detectAmbientCredential(params.env);
73207
73311
  if (ambient === void 0) {
73208
73312
  push("ambient-credential", "pass", "No ambient-credential environment variable is set.");
@@ -73235,12 +73339,13 @@ function runDoctor(params) {
73235
73339
  pushLegacyName(push, params);
73236
73340
  const profileSources = /* @__PURE__ */ new Map();
73237
73341
  for (const entry of params.configProfiles) {
73238
- const validated = validateJson(ConfigProfileSchema, entry);
73239
- if (!validated.ok) {
73240
- push("config-profile", "fail", validated.message, entry.name);
73342
+ const validated2 = validateJson(ConfigProfileSchema, entry);
73343
+ if (!validated2.ok) {
73344
+ push("config-profile", "fail", validated2.message, entry.name);
73241
73345
  continue;
73242
73346
  }
73243
- profileSources.set(entry.name, { name: entry.name, profile: validated.data });
73347
+ profileSources.set(entry.name, { name: entry.name, profile: validated2.data });
73348
+ checkPin("config-profile", entry.name, `Configuration profile "${entry.name}"`, validated2.data.launch?.claudeVersion);
73244
73349
  }
73245
73350
  const loadProfile = (name) => profileSources.get(name);
73246
73351
  for (const name of profileSources.keys()) {
@@ -73256,13 +73361,13 @@ function runDoctor(params) {
73256
73361
  const validProfileNames = new Set(profileSources.keys());
73257
73362
  const validIdentityNames = /* @__PURE__ */ new Set();
73258
73363
  for (const entry of params.identities) {
73259
- const validated = validateJson(IdentitySchema, entry);
73260
- if (!validated.ok) {
73261
- push("identity", "fail", validated.message, entry.name);
73364
+ const validated2 = validateJson(IdentitySchema, entry);
73365
+ if (!validated2.ok) {
73366
+ push("identity", "fail", validated2.message, entry.name);
73262
73367
  continue;
73263
73368
  }
73264
73369
  validIdentityNames.add(entry.name);
73265
- const defaultProfile = validated.data.defaultConfigProfile;
73370
+ const defaultProfile = validated2.data.defaultConfigProfile;
73266
73371
  if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
73267
73372
  push(
73268
73373
  "identity",
@@ -73271,7 +73376,7 @@ function runDoctor(params) {
73271
73376
  entry.name
73272
73377
  );
73273
73378
  } else {
73274
- const credential = validated.data.credential;
73379
+ const credential = validated2.data.credential;
73275
73380
  const cache = credential === void 0 ? { text: "", unreadable: false } : cacheClause(params.credentialCache, `identity ${entry.name}`, credential);
73276
73381
  push(
73277
73382
  "identity",
@@ -73309,12 +73414,12 @@ function runDoctor(params) {
73309
73414
  if (params.directoryRules.raw === void 0) {
73310
73415
  push("directory-rules", "pass", "No directory-rules.json configured.");
73311
73416
  } else {
73312
- const validated = validateJson(DirectoryRulesSchema, params.directoryRules);
73313
- if (!validated.ok) {
73314
- push("directory-rules", "fail", validated.message);
73417
+ const validated2 = validateJson(DirectoryRulesSchema, params.directoryRules);
73418
+ if (!validated2.ok) {
73419
+ push("directory-rules", "fail", validated2.message);
73315
73420
  } else {
73316
73421
  push("directory-rules", "pass", `${params.directoryRules.path} is valid.`);
73317
- for (const rule of validated.data.rules) {
73422
+ for (const rule of validated2.data.rules) {
73318
73423
  const badRefs = [];
73319
73424
  if (rule.identity !== void 0 && !selectionExists(rule.identity)) {
73320
73425
  badRefs.push(poolNameOf(rule.identity) === void 0 ? `identity "${rule.identity}"` : `pool "${poolNameOf(rule.identity) ?? ""}"`);
@@ -73322,6 +73427,7 @@ function runDoctor(params) {
73322
73427
  if (rule.configProfile !== void 0 && !validProfileNames.has(rule.configProfile)) {
73323
73428
  badRefs.push(`configuration profile "${rule.configProfile}"`);
73324
73429
  }
73430
+ checkPin("directory-rules", rule.path, `Rule for "${rule.path}"`, rule.launch?.claudeVersion);
73325
73431
  if (badRefs.length > 0) {
73326
73432
  push("directory-rules", "fail", `Rule for "${rule.path}" names ${badRefs.join(" and ")}, which do not exist.`, rule.path);
73327
73433
  } else {
@@ -73333,11 +73439,11 @@ function runDoctor(params) {
73333
73439
  if (params.globalConfig.raw === void 0) {
73334
73440
  push("global-config", "pass", "No config.json configured.");
73335
73441
  } else {
73336
- const validated = validateJson(GlobalConfigSchema, params.globalConfig);
73337
- if (!validated.ok) {
73338
- push("global-config", "fail", validated.message);
73442
+ const validated2 = validateJson(GlobalConfigSchema, params.globalConfig);
73443
+ if (!validated2.ok) {
73444
+ push("global-config", "fail", validated2.message);
73339
73445
  } else {
73340
- const defaultProfile = validated.data.defaultConfigProfile;
73446
+ const defaultProfile = validated2.data.defaultConfigProfile;
73341
73447
  if (defaultProfile !== void 0 && !validProfileNames.has(defaultProfile)) {
73342
73448
  push(
73343
73449
  "global-config",
@@ -73346,27 +73452,28 @@ function runDoctor(params) {
73346
73452
  );
73347
73453
  } else {
73348
73454
  push("global-config", "pass", `${params.globalConfig.path} is valid.`);
73455
+ checkPin("global-config", void 0, "config.json", validated2.data.launch?.claudeVersion);
73349
73456
  }
73350
73457
  }
73351
73458
  }
73352
73459
  if (params.categoriesLocal.raw === void 0) {
73353
73460
  push("categories-local", "pass", "No categories.local.json configured.");
73354
73461
  } else {
73355
- const validated = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
73462
+ const validated2 = validateJson(CategoryClassificationOverlaySchema, params.categoriesLocal);
73356
73463
  push(
73357
73464
  "categories-local",
73358
- validated.ok ? "pass" : "fail",
73359
- validated.ok ? `${params.categoriesLocal.path} is valid.` : validated.message
73465
+ validated2.ok ? "pass" : "fail",
73466
+ validated2.ok ? `${params.categoriesLocal.path} is valid.` : validated2.message
73360
73467
  );
73361
73468
  }
73362
73469
  if (params.headroom.state.raw === void 0) {
73363
73470
  push("headroom", "pass", "No headroom daemon has ever run; nothing to check.");
73364
73471
  } else {
73365
- const validated = validateJson(HeadroomStateSchema, params.headroom.state);
73366
- if (!validated.ok) {
73367
- push("headroom", "fail", validated.message);
73472
+ const validated2 = validateJson(HeadroomStateSchema, params.headroom.state);
73473
+ if (!validated2.ok) {
73474
+ push("headroom", "fail", validated2.message);
73368
73475
  } else {
73369
- const state = validated.data;
73476
+ const state = validated2.data;
73370
73477
  const supervisorAlive = state.supervisorPid !== void 0 && params.headroom.isRunning(state.supervisorPid);
73371
73478
  const headroomAlive = state.headroomPid !== void 0 && params.headroom.isRunning(state.headroomPid);
73372
73479
  if (state.supervisorPid === void 0) {
@@ -73536,6 +73643,7 @@ function collectDoctorReport(params) {
73536
73643
  const report = runDoctor({
73537
73644
  env: params.env,
73538
73645
  credentialCache: realCredentialCacheEnv(paths),
73646
+ installedClaudeVersions: realInstalledClaudeVersions(),
73539
73647
  identities,
73540
73648
  configProfiles,
73541
73649
  providers,
@@ -74686,6 +74794,7 @@ Launching:
74686
74794
  --category <category=bool> share or hide a category (repeatable)
74687
74795
  --share <path>, --hide <path> share or hide one <category>/<path> entry (repeatable)
74688
74796
  --[no-]skip-permissions, --[no-]remote-control, --[no-]headroom, --[no-]track-usage
74797
+ --claude-version <version> run exactly this installed Claude Code version (not the highest)
74689
74798
  --[no-]wait with @pool:<name>, sleep until the earliest member returns when all are refused
74690
74799
  --native run the real claude with nothing from agent-shim applied (no other launch flag allowed)
74691
74800
 
@@ -75173,7 +75282,11 @@ function prepareLaunch(params) {
75173
75282
  } else if (params.farm !== void 0) {
75174
75283
  cascadeLaunch = flattenLayers(assembleCascade(params.farm.loadCascade(configProfileDecision.name, cliOverride)).layers, { home: params.farm.home }).launch;
75175
75284
  }
75176
- const discovered = params.resolveClaudeBinary();
75285
+ const pinnedVersion = resolveClaudeVersion({ env, ...parsedArgv.claudeVersion === void 0 ? {} : { flag: parsedArgv.claudeVersion }, ...cascadeLaunch?.claudeVersion === void 0 ? {} : { cascade: cascadeLaunch.claudeVersion } });
75286
+ const discovered = params.resolveClaudeBinary(pinnedVersion === void 0 ? void 0 : { version: pinnedVersion.version });
75287
+ if (pinnedVersion !== void 0) {
75288
+ log.info(`agent-shim: Claude Code ${pinnedVersion.version} (pinned by ${pinnedVersion.source})`);
75289
+ }
75177
75290
  const resolvedFlags = resolveLaunchFlags({
75178
75291
  env,
75179
75292
  // A pool pick reads the usage the front door records, so a pool launch records its own unless something says otherwise.
@@ -75319,6 +75432,7 @@ function otherLaunchFlags(parsed) {
75319
75432
  ...parsed.identity === void 0 ? [] : ["@<identity> / --identity"],
75320
75433
  ...parsed.configProfile === void 0 ? [] : ["--config-profile"],
75321
75434
  ...parsed.provider === void 0 ? [] : [parsed.provider === false ? "--no-provider" : "--provider"],
75435
+ ...parsed.claudeVersion === void 0 ? [] : ["--claude-version"],
75322
75436
  ...parsed.headroom === void 0 ? [] : ["--[no-]headroom"],
75323
75437
  ...parsed.trackUsage === void 0 ? [] : ["--[no-]track-usage"],
75324
75438
  ...parsed.skipPermissions === void 0 ? [] : ["--[no-]skip-permissions"],