agent-shim 11.77.0 → 11.79.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
@@ -57,7 +57,7 @@ That's it — with no further configuration, everything in `~/.claude` that isn'
57
57
  - **An identity** is a login: the thing that owns credentials and daemon state, selected with `agent-shim run @<name>` or, once `agent-shim shim enable` has run, `claude @<name>`.
58
58
  - **A configuration profile** is a reusable, named bundle of sharing rules, independent of any identity, resolved per working directory via directory rules or a committed `.agent-shim.json`.
59
59
 
60
- A third, optional thing sits on top of identities: **a pool** is a named list of them. Select `pool:<name>` where an identity name goes (`claude @pool:subs`) and agent-shim picks the member whose unused quota is closest to expiring, skipping any that are refused right now, using the usage it already records. A pool can instead declare `--preference listed`, which tries the members in the order the pool lists them, skipping only one that is refused right now. A member entry may itself name another pool (`pool:<name>`), which is ranked with its own policy and contributes its pick at that position, so "this identity first, then the fleet" nests the fleet rather than copying its member list; cycles are refused. See [docs/configuration-model.md](docs/configuration-model.md#pools-picking-an-account-at-launch).
60
+ A third, optional thing sits on top of identities: **a pool** is a named list of them. Select `pool:<name>` where an identity name goes (`claude @pool:subs`) and agent-shim picks the member whose unused quota is closest to expiring, skipping any that are refused right now, using the usage it already records. A pool can instead declare `--preference listed`, which tries the members in the order the pool lists them, skipping only one that is refused right now. A member entry may itself name another pool (`pool:<name>`), which is ranked with its own policy and contributes its pick at that position, so "this identity first, then the fleet" nests the fleet rather than copying its member list; cycles are refused. See [docs/configuration-model.md](docs/configuration-model.md#pools-picking-an-account-at-launch). On a terminal, `agent-shim run` asks which identity to run as when none resolves, when a pool pick would change account, or when the identity is refused right now (`--pick` forces the question, `--no-pick` never asks); see [docs/configuration-model.md](docs/configuration-model.md#choosing-the-identity-at-launch-the-picker).
61
61
 
62
62
  Every top-level entry in `~/.claude` is classified into one of five categories, shipped as a default map (`config/categories.default.json`):
63
63
 
@@ -200,6 +200,7 @@ agent-shim run [@<identity>] [launch flags] [claude arguments]
200
200
  # --claude-version <version> run exactly this installed Claude Code version
201
201
  # --delay <duration> wait this long after the launcher's own output before starting claude; --[no-]pause waits for a key press instead
202
202
  # --[no-]explain with @pool:<name>, list every member the pick ranked and why, not only the one picked
203
+ # --[no-]pick on a terminal, ask which identity to run as, even when one resolved (--no-pick never asks); a launch also asks by itself when no identity resolves, a pool pick would change account or organisation, or the identity is refused right now
203
204
  # --native run the real claude with nothing from agent-shim applied; cannot be combined with the flags above
204
205
  claude @<identity> ... # the same, once `agent-shim shim enable` has run
205
206
  ```
package/dist/cli.cjs CHANGED
@@ -38198,7 +38198,7 @@ var init_package = __esm({
38198
38198
  "package.json"() {
38199
38199
  package_default = {
38200
38200
  name: "agent-shim",
38201
- version: "11.77.0",
38201
+ version: "11.79.0",
38202
38202
  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.",
38203
38203
  license: "MIT",
38204
38204
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -67175,7 +67175,8 @@ var init_argv = __esm({
67175
67175
  { flag: "--remote-control", key: "remoteControl" },
67176
67176
  { flag: "--wait", key: "wait" },
67177
67177
  { flag: "--pause", key: "pause" },
67178
- { flag: "--explain", key: "explain" }
67178
+ { flag: "--explain", key: "explain" },
67179
+ { flag: "--pick", key: "pick" }
67179
67180
  ];
67180
67181
  LAUNCHER_FLAG_NAMES = [
67181
67182
  ...VALUED_FLAGS,
@@ -69956,7 +69957,7 @@ function decideLaunch(params, readOnly) {
69956
69957
  const configDirEscapeHatchApplies = env.CLAUDE_CONFIG_DIR !== void 0 && env.CLAUDE_CONFIG_DIR !== "";
69957
69958
  const decidedIdentity = decideIdentity({
69958
69959
  env,
69959
- argv0Identity: parsedArgv.identity,
69960
+ argv0Identity: params.pickedIdentity ?? parsedArgv.identity,
69960
69961
  directoryPinnedIdentity: params.directoryPinnedIdentity,
69961
69962
  readActiveIdentityFile: () => {
69962
69963
  const raw = fs31.readFileUtf8(paths.activeIdentityFile);
@@ -117743,7 +117744,7 @@ function summariseIdentityAccount(account) {
117743
117744
  function loginKeyOf(account) {
117744
117745
  return account.accountUuid === void 0 ? void 0 : JSON.stringify([account.accountUuid, account.organizationUuid ?? null]);
117745
117746
  }
117746
- function groupByLogin(items, loginOf) {
117747
+ function collectByLogin(items, loginOf) {
117747
117748
  const groups = /* @__PURE__ */ new Map();
117748
117749
  for (const item of items) {
117749
117750
  const key = loginOf(item) ?? /* @__PURE__ */ Symbol("no login");
@@ -117754,7 +117755,13 @@ function groupByLogin(items, loginOf) {
117754
117755
  group.push(item);
117755
117756
  }
117756
117757
  }
117757
- return [...groups.values()].flat();
117758
+ return [...groups.values()];
117759
+ }
117760
+ function groupByLogin(items, loginOf) {
117761
+ return collectByLogin(items, loginOf).flat();
117762
+ }
117763
+ function groupSharedLogins(items, loginOf) {
117764
+ return collectByLogin(items, loginOf).filter((group) => group.length > 1);
117758
117765
  }
117759
117766
  function describeAccountHolder(summary) {
117760
117767
  const organisation = describeAccountOrganisation(summary);
@@ -123523,6 +123530,10 @@ var DOCTOR_SECTIONS = [
123523
123530
  "frontdoor",
123524
123531
  "models"
123525
123532
  ];
123533
+ function joinNames(names) {
123534
+ const last = names.at(-1);
123535
+ return names.length < 2 || last === void 0 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${last}`;
123536
+ }
123526
123537
  function refinePathShadow(status, ownExecutablePath, realpath) {
123527
123538
  if (status.status !== "shadowed") {
123528
123539
  return status;
@@ -123770,6 +123781,7 @@ function runDoctor(params) {
123770
123781
  const validProfileNames = new Set(profileSources.keys());
123771
123782
  const validProviderNames = /* @__PURE__ */ new Set([...params.providers.map((entry) => entry.name), ...Object.keys(params.providerAliases ?? {})]);
123772
123783
  const validIdentityNames = /* @__PURE__ */ new Set();
123784
+ const signedInAccounts = [];
123773
123785
  for (const entry of params.identities) {
123774
123786
  const validated2 = validate3(IdentitySchema, entry, "identity", entry.name);
123775
123787
  if (!validated2.ok) {
@@ -123819,6 +123831,18 @@ function runDoctor(params) {
123819
123831
  signIn.state === "signed-out" ? `${entry.name} is signed out (${signIn.detail}). Run \`agent-shim run @${entry.name}\` and sign in.` : `${entry.name} ${standing} (${signIn.detail}).`,
123820
123832
  entry.name
123821
123833
  );
123834
+ if (signIn.state === "signed-in" && entry.account !== void 0) {
123835
+ signedInAccounts.push({ name: entry.name, account: entry.account });
123836
+ }
123837
+ }
123838
+ for (const group of groupSharedLogins(signedInAccounts, (entry) => loginKeyOf(entry.account))) {
123839
+ const names = group.map((entry) => entry.name);
123840
+ const holder = group.map((entry) => summariseIdentityAccount(entry.account)).map((summary) => summary === void 0 ? void 0 : describeAccountHolder(summary)).find((described) => described !== void 0);
123841
+ push(
123842
+ "sign-in",
123843
+ "warn",
123844
+ `Identities ${joinNames(names)} hold the same login${holder === void 0 ? "" : ` (${holder})`}, so they share one quota window and all but one are redundant. Remove the redundant ones with \`agent-shim identity remove <name>\`.`
123845
+ );
123822
123846
  }
123823
123847
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
123824
123848
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
@@ -124107,7 +124131,7 @@ function collectDoctorReport(params) {
124107
124131
  const identities = identityNames.map((name) => {
124108
124132
  const farmRoot = import_node_path47.default.join(paths.identitiesDir, name);
124109
124133
  const identityPath = import_node_path47.default.join(farmRoot, "identity.json");
124110
- return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: import_node_fs20.default.existsSync(credentialsFilePath(farmRoot)) };
124134
+ return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: import_node_fs20.default.existsSync(credentialsFilePath(farmRoot)), account: readIdentityAccount(realFsPort, paths.identitiesDir, name)?.account };
124111
124135
  });
124112
124136
  const profileNames = import_node_fs20.default.existsSync(paths.configProfilesDir) ? import_node_fs20.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
124113
124137
  const configProfiles = profileNames.map((name) => {
@@ -157797,6 +157821,241 @@ function buildProgram(deps) {
157797
157821
  // src/runClaude.ts
157798
157822
  init_cliError();
157799
157823
  init_configProfilesStore();
157824
+
157825
+ // src/launcher/pickerPrompt.ts
157826
+ function promptsIdentityPicker(prompts) {
157827
+ return {
157828
+ pick: async (request7) => {
157829
+ const answer = await prompts.select({
157830
+ message: request7.message,
157831
+ options: request7.entries.map((entry) => ({ value: entry.value, label: entry.label })),
157832
+ ...request7.initial === void 0 ? {} : { initialValue: request7.initial }
157833
+ });
157834
+ return prompts.isCancel(answer) ? { kind: "cancelled" } : { kind: "picked", value: answer };
157835
+ }
157836
+ };
157837
+ }
157838
+
157839
+ // src/launcher/runPicker.ts
157840
+ init_cliError();
157841
+ init_schema();
157842
+ init_account();
157843
+ init_candidateText();
157844
+ init_pick();
157845
+ init_poolPick();
157846
+ init_identity();
157847
+ init_launchReport();
157848
+ init_hold();
157849
+
157850
+ // src/launcher/pickerEntries.ts
157851
+ var PART_SEPARATOR = " | ";
157852
+ function pickerLabel(facts) {
157853
+ const name = [facts.rank === void 0 ? void 0 : `${String(facts.rank)}.`, facts.identity, facts.heading, facts.picked === true ? "(picked)" : void 0].filter((part) => part !== void 0).join(" ");
157854
+ return [name, facts.detail, facts.account, facts.quota].filter((part) => part !== void 0).join(PART_SEPARATOR);
157855
+ }
157856
+
157857
+ // src/launcher/pickerDecision.ts
157858
+ init_claudeSubcommand();
157859
+ function decidePicker(facts) {
157860
+ if (!facts.interactive || facts.configDirEscapeHatch || facts.pick === false) {
157861
+ return void 0;
157862
+ }
157863
+ if (facts.pick === true) {
157864
+ return "requested";
157865
+ }
157866
+ if (facts.unattended) {
157867
+ return void 0;
157868
+ }
157869
+ const { selection } = facts;
157870
+ switch (selection.kind) {
157871
+ case "none":
157872
+ return "no-identity";
157873
+ case "identity":
157874
+ return selection.limited ? "no-eligible-identity" : void 0;
157875
+ case "pool":
157876
+ if (selection.outcome === "none-eligible") {
157877
+ return facts.waiting ? void 0 : "no-eligible-identity";
157878
+ }
157879
+ return selection.outcome === "changes-account" ? "account-change" : void 0;
157880
+ default:
157881
+ return selection;
157882
+ }
157883
+ }
157884
+ var TERMINATOR4 = "--";
157885
+ var UNATTENDED_FLAGS = /* @__PURE__ */ new Set(["-p", "--print", "--bg"]);
157886
+ function isUnattendedLaunch(passthrough) {
157887
+ const end = passthrough.indexOf(TERMINATOR4);
157888
+ const head = end === -1 ? passthrough : passthrough.slice(0, end);
157889
+ return head.some((token) => UNATTENDED_FLAGS.has(token)) || isClaudeSubcommand(passthrough);
157890
+ }
157891
+
157892
+ // src/launcher/runPicker.ts
157893
+ init_pool();
157894
+ var IdentityPickerCancelledError = class extends CliError {
157895
+ exitCode = LAUNCH_CANCELLED_EXIT;
157896
+ constructor() {
157897
+ super("launch cancelled at the identity picker: no identity was chosen.");
157898
+ this.name = "IdentityPickerCancelledError";
157899
+ }
157900
+ };
157901
+ function memberTexts(member, nowMs) {
157902
+ if (member === void 0) {
157903
+ return {};
157904
+ }
157905
+ const billing = { login: member.account, snapshot: member.snapshot, provider: void 0, nowMs };
157906
+ const account = accountRows(billing)[0]?.value;
157907
+ const quotaRow2 = quotaRows(billing)[0];
157908
+ return {
157909
+ ...account === void 0 ? {} : { account },
157910
+ ...quotaRow2 === void 0 ? {} : { quota: quotaRow2.detail === void 0 ? quotaRow2.value : `${quotaRow2.value} (${quotaRow2.detail})` }
157911
+ };
157912
+ }
157913
+ function refusalOf(candidate) {
157914
+ return candidate.class === "ineligible" ? candidate.reasons[0] : void 0;
157915
+ }
157916
+ function accountsDiffer(members2, left, right) {
157917
+ const keyOf3 = (identity) => {
157918
+ const account = members2.find((member) => member.identity === identity)?.account;
157919
+ return account === void 0 ? void 0 : loginKeyOf(account);
157920
+ };
157921
+ const leftKey = keyOf3(left);
157922
+ const rightKey = keyOf3(right);
157923
+ return leftKey !== void 0 && rightKey !== void 0 && leftKey !== rightKey;
157924
+ }
157925
+ function poolContext(params, poolName, resuming, nowMs) {
157926
+ const { paths, usageFs } = params;
157927
+ const { sticky } = readStickyPick(usageFs, paths.usagePicksFile, params.cwd);
157928
+ const { ranking } = rankPoolGraph({
157929
+ fs: usageFs,
157930
+ paths,
157931
+ pools: params.pools ?? {},
157932
+ poolName,
157933
+ nowMs,
157934
+ resuming,
157935
+ identityExists: (name) => loadIdentity(paths.identitiesDir, name, params.fs) !== void 0,
157936
+ ...sticky === void 0 ? {} : { sticky }
157937
+ });
157938
+ const pick2 = ranking.pick?.identity;
157939
+ const named = [.../* @__PURE__ */ new Set([...ranking.candidates.map((candidate) => candidate.identity), ...sticky === void 0 ? [] : [sticky.identity]])].filter(isIdentityName);
157940
+ const members2 = loadPoolMembers(usageFs, paths, named, nowMs);
157941
+ const changes = pick2 !== void 0 && sticky !== void 0 && sticky.identity !== pick2 && accountsDiffer(members2, sticky.identity, pick2);
157942
+ return {
157943
+ selection: { kind: "pool", outcome: pick2 === void 0 ? "none-eligible" : changes ? "changes-account" : "keeps-account" },
157944
+ entries: ranking.candidates.map((candidate, index) => ({
157945
+ value: candidate.identity,
157946
+ label: pickerLabel({
157947
+ identity: candidate.identity,
157948
+ rank: index + 1,
157949
+ heading: candidateHeading(candidate),
157950
+ picked: candidate.identity === pick2,
157951
+ detail: refusalOf(candidate),
157952
+ ...memberTexts(members2.find((member) => member.identity === candidate.identity), nowMs)
157953
+ })
157954
+ })),
157955
+ initial: pick2,
157956
+ poolPick: pick2,
157957
+ message: (reason) => poolMessage(reason, poolName, pick2, sticky)
157958
+ };
157959
+ }
157960
+ function poolMessage(reason, poolName, pick2, sticky) {
157961
+ switch (reason) {
157962
+ case "account-change":
157963
+ return `Pool "${poolName}" would run this launch as ${pick2 ?? "(none)"}, a different account from ${sticky?.identity ?? "(none)"}, which this directory last ran as. Which identity should it run as?`;
157964
+ case "no-eligible-identity":
157965
+ return `Every member of pool "${poolName}" is refused right now. Which identity should this launch run as?`;
157966
+ case "requested":
157967
+ case "no-identity":
157968
+ return `Pool "${poolName}" is selected for this launch. Which identity should it run as?`;
157969
+ default:
157970
+ return reason;
157971
+ }
157972
+ }
157973
+ function identityContext(params, resuming, nowMs) {
157974
+ const { paths, usageFs } = params;
157975
+ const members2 = loadPoolMembers(usageFs, paths, params.identityNames(), nowMs);
157976
+ const { candidates } = rankPool({ members: members2, nowMs, resuming, preference: "listed" });
157977
+ const named = params.decision.name;
157978
+ const limited = named !== void 0 && candidates.some((candidate) => candidate.identity === named && candidate.class === "ineligible");
157979
+ const identityEntries = candidates.map((candidate) => ({
157980
+ value: candidate.identity,
157981
+ label: pickerLabel({ identity: candidate.identity, detail: refusalOf(candidate), ...memberTexts(members2.find((member) => member.identity === candidate.identity), nowMs) })
157982
+ }));
157983
+ const poolEntries = Object.entries(params.pools ?? {}).map(([name, pool]) => ({
157984
+ value: `pool:${name}`,
157985
+ label: pickerLabel({ identity: `pool:${name}`, detail: `members ${pool.identities.map(poolMemberIdentity).join(", ")}` })
157986
+ }));
157987
+ return {
157988
+ selection: named === void 0 ? { kind: "none" } : { kind: "identity", limited },
157989
+ entries: [...identityEntries, ...poolEntries],
157990
+ initial: named,
157991
+ poolPick: void 0,
157992
+ message: (reason) => {
157993
+ switch (reason) {
157994
+ case "no-identity":
157995
+ return "No identity is selected for this launch. Which identity should it run as?";
157996
+ case "no-eligible-identity":
157997
+ return `${named ?? "The selected identity"} is refused right now. Which identity should this launch run as?`;
157998
+ case "requested":
157999
+ case "account-change":
158000
+ return "Which identity should this launch run as?";
158001
+ default:
158002
+ return reason;
158003
+ }
158004
+ }
158005
+ };
158006
+ }
158007
+ async function runIdentityPicker(params) {
158008
+ if (!params.interactive) {
158009
+ return { kind: "unchanged" };
158010
+ }
158011
+ const nowMs = params.now();
158012
+ const resuming = isResuming(params.parsed.rest);
158013
+ const context = params.decision.pool === void 0 ? identityContext(params, resuming, nowMs) : poolContext(params, params.decision.pool, resuming, nowMs);
158014
+ const reason = decidePicker({
158015
+ interactive: params.interactive,
158016
+ unattended: isUnattendedLaunch(params.parsed.rest),
158017
+ pick: params.parsed.pick,
158018
+ configDirEscapeHatch: params.decision.configDirEscapeHatch,
158019
+ waiting: params.parsed.wait === true,
158020
+ selection: context.selection
158021
+ });
158022
+ if (reason === void 0) {
158023
+ return { kind: "unchanged" };
158024
+ }
158025
+ const request7 = { message: context.message(reason), entries: context.entries, ...context.initial === void 0 ? {} : { initial: context.initial } };
158026
+ const answer = await params.picker.pick(request7);
158027
+ if (answer.kind === "cancelled") {
158028
+ return { kind: "cancelled" };
158029
+ }
158030
+ const chosen = answer.value;
158031
+ if (chosen === (params.decision.pool === void 0 ? params.decision.name : context.poolPick)) {
158032
+ return { kind: "unchanged" };
158033
+ }
158034
+ if (params.decision.pool !== void 0 && poolNameOf(chosen) === void 0) {
158035
+ recordStickyPick(params.usageFs, params.paths.usagePicksFile, params.cwd, chosen, nowMs);
158036
+ }
158037
+ return { kind: "selected", selector: chosen };
158038
+ }
158039
+
158040
+ // src/launchPicker.ts
158041
+ init_realPorts();
158042
+ async function pickLaunchIdentity(params) {
158043
+ return await runIdentityPicker({
158044
+ parsed: params.parsed,
158045
+ decision: params.decision,
158046
+ interactive: process.stdin.isTTY && process.stdout.isTTY,
158047
+ paths: params.paths,
158048
+ fs: realFsPort,
158049
+ usageFs: params.farm.runtime.fs,
158050
+ pools: params.farm.pools,
158051
+ cwd: params.farm.runtime.cwd,
158052
+ now: params.farm.runtime.now,
158053
+ identityNames: () => listIdentities(params.paths).flatMap((listing) => "problem" in listing ? [] : [listing.name]),
158054
+ picker: promptsIdentityPicker(realPromptsPort)
158055
+ });
158056
+ }
158057
+
158058
+ // src/runClaude.ts
157800
158059
  init_launchWiring();
157801
158060
  init_paths();
157802
158061
 
@@ -158566,6 +158825,7 @@ function otherLaunchFlags(parsed) {
158566
158825
  ...parsed.skipPermissions === void 0 ? [] : ["--[no-]skip-permissions"],
158567
158826
  ...parsed.remoteControl === void 0 ? [] : ["--[no-]remote-control"],
158568
158827
  ...parsed.wait === void 0 ? [] : ["--[no-]wait"],
158828
+ ...parsed.pick === void 0 ? [] : ["--[no-]pick"],
158569
158829
  ...parsed.categoryFlags.length === 0 ? [] : ["--category"],
158570
158830
  ...parsed.shareFlags.length === 0 ? [] : ["--share"],
158571
158831
  ...parsed.hideFlags.length === 0 ? [] : ["--hide"]
@@ -158631,13 +158891,19 @@ async function runClaude(argvOverride) {
158631
158891
  }
158632
158892
  }
158633
158893
  }
158634
- await refreshPools({
158635
- paths,
158636
- cwd: process.cwd(),
158637
- poolNames: identityDecision.pool === void 0 ? [] : [identityDecision.pool],
158638
- spawnDaemon: spawnDetachedSupervisor,
158639
- log: realLogPort.warn
158640
- });
158894
+ const refreshUsage = async (poolName2) => {
158895
+ await refreshPools({ paths, cwd: process.cwd(), poolNames: poolName2 === void 0 ? [] : [poolName2], spawnDaemon: spawnDetachedSupervisor, log: realLogPort.warn });
158896
+ };
158897
+ await refreshUsage(identityDecision.pool);
158898
+ const picked = await pickLaunchIdentity({ paths, farm, parsed: nativeArgv, decision: identityDecision });
158899
+ if (picked.kind === "cancelled") {
158900
+ throw new IdentityPickerCancelledError();
158901
+ }
158902
+ const pickedIdentity = picked.kind === "selected" ? picked.selector : void 0;
158903
+ const launchDecision = pickedIdentity === void 0 ? identityDecision : decideIdentity({ env: procForDecision.env, argv0Identity: pickedIdentity, readActiveIdentityFile: () => void 0 });
158904
+ if (launchDecision.pool !== identityDecision.pool) {
158905
+ await refreshUsage(launchDecision.pool);
158906
+ }
158641
158907
  const prepareParams = {
158642
158908
  ...realPrepareLaunchParams(paths, {
158643
158909
  proc: argvOverride === void 0 ? realProcPort : { ...realProcPort, argv: argvOverride },
@@ -158646,10 +158912,11 @@ async function runClaude(argvOverride) {
158646
158912
  farm,
158647
158913
  allowMissingConfigProfile
158648
158914
  }),
158915
+ ...pickedIdentity === void 0 ? {} : { pickedIdentity },
158649
158916
  // Wired here rather than in realPrepareLaunchParams because the port re-invokes this very binary: correct for the agent-shim command line, wrong for a library host whose own executable is not agent-shim.
158650
158917
  update: realLaunchUpdatePort(paths)
158651
158918
  };
158652
- const poolName = identityDecision.pool;
158919
+ const poolName = launchDecision.pool;
158653
158920
  if (poolName !== void 0 && farm.pools !== void 0 && supervisesLaunch(farm.pools[poolName], nativeArgv.rest)) {
158654
158921
  const code = await runSupervised({
158655
158922
  paths,
package/dist/index.cjs CHANGED
@@ -4764,7 +4764,7 @@ var init_package = __esm({
4764
4764
  "package.json"() {
4765
4765
  package_default = {
4766
4766
  name: "agent-shim",
4767
- version: "11.77.0",
4767
+ version: "11.79.0",
4768
4768
  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.",
4769
4769
  license: "MIT",
4770
4770
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -10726,7 +10726,8 @@ var init_argv = __esm({
10726
10726
  { flag: "--remote-control", key: "remoteControl" },
10727
10727
  { flag: "--wait", key: "wait" },
10728
10728
  { flag: "--pause", key: "pause" },
10729
- { flag: "--explain", key: "explain" }
10729
+ { flag: "--explain", key: "explain" },
10730
+ { flag: "--pick", key: "pick" }
10730
10731
  ];
10731
10732
  LAUNCHER_FLAG_NAMES = [
10732
10733
  ...VALUED_FLAGS,
@@ -11812,7 +11813,7 @@ function decideLaunch(params, readOnly) {
11812
11813
  const configDirEscapeHatchApplies = env.CLAUDE_CONFIG_DIR !== void 0 && env.CLAUDE_CONFIG_DIR !== "";
11813
11814
  const decidedIdentity = decideIdentity({
11814
11815
  env,
11815
- argv0Identity: parsedArgv.identity,
11816
+ argv0Identity: params.pickedIdentity ?? parsedArgv.identity,
11816
11817
  directoryPinnedIdentity: params.directoryPinnedIdentity,
11817
11818
  readActiveIdentityFile: () => {
11818
11819
  const raw = fs14.readFileUtf8(paths.activeIdentityFile);
@@ -17084,6 +17085,65 @@ var import_node_fs10 = __toESM(require("node:fs"), 1);
17084
17085
  var import_node_path29 = __toESM(require("node:path"), 1);
17085
17086
  init_signIn();
17086
17087
 
17088
+ // src/identityAccounts.ts
17089
+ init_account();
17090
+
17091
+ // src/usage/accountDescription.ts
17092
+ function describeAccountOrganisation(account) {
17093
+ const parts = [account.organizationName, account.organizationType].filter((part) => part !== void 0);
17094
+ return parts.length === 0 ? void 0 : parts.join(", ");
17095
+ }
17096
+
17097
+ // src/identityAccounts.ts
17098
+ function readIdentityAccount(fs14, identitiesDir, identity) {
17099
+ try {
17100
+ const account = readAccountMetadata(fs14, identitiesDir, identity);
17101
+ return account === void 0 ? void 0 : { account };
17102
+ } catch (error) {
17103
+ if (error instanceof AccountMetadataError) {
17104
+ return { problem: error.message };
17105
+ }
17106
+ throw error;
17107
+ }
17108
+ }
17109
+ function summariseIdentityAccount(account) {
17110
+ const summary = {
17111
+ ...account.emailAddress === void 0 ? {} : { emailAddress: account.emailAddress },
17112
+ ...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
17113
+ ...account.organizationType === void 0 ? {} : { organizationType: account.organizationType },
17114
+ ...account.organizationRole === void 0 ? {} : { organizationRole: account.organizationRole },
17115
+ ...account.billingType === void 0 ? {} : { billingType: account.billingType },
17116
+ ...account.seatTier === void 0 ? {} : { seatTier: account.seatTier }
17117
+ };
17118
+ return Object.keys(summary).length === 0 ? void 0 : summary;
17119
+ }
17120
+ function loginKeyOf(account) {
17121
+ return account.accountUuid === void 0 ? void 0 : JSON.stringify([account.accountUuid, account.organizationUuid ?? null]);
17122
+ }
17123
+ function collectByLogin(items, loginOf) {
17124
+ const groups = /* @__PURE__ */ new Map();
17125
+ for (const item of items) {
17126
+ const key = loginOf(item) ?? /* @__PURE__ */ Symbol("no login");
17127
+ const group = groups.get(key);
17128
+ if (group === void 0) {
17129
+ groups.set(key, [item]);
17130
+ } else {
17131
+ group.push(item);
17132
+ }
17133
+ }
17134
+ return [...groups.values()];
17135
+ }
17136
+ function groupSharedLogins(items, loginOf) {
17137
+ return collectByLogin(items, loginOf).filter((group) => group.length > 1);
17138
+ }
17139
+ function describeAccountHolder(summary) {
17140
+ const organisation = describeAccountOrganisation(summary);
17141
+ if (summary.emailAddress === void 0) {
17142
+ return organisation;
17143
+ }
17144
+ return organisation === void 0 ? summary.emailAddress : `${summary.emailAddress} (${organisation})`;
17145
+ }
17146
+
17087
17147
  // src/codex/siwcStore.ts
17088
17148
  var import_zod26 = require("zod");
17089
17149
 
@@ -17458,6 +17518,10 @@ var DOCTOR_SECTIONS = [
17458
17518
  "frontdoor",
17459
17519
  "models"
17460
17520
  ];
17521
+ function joinNames(names) {
17522
+ const last = names.at(-1);
17523
+ return names.length < 2 || last === void 0 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${last}`;
17524
+ }
17461
17525
  function refinePathShadow(status, ownExecutablePath, realpath) {
17462
17526
  if (status.status !== "shadowed") {
17463
17527
  return status;
@@ -17705,6 +17769,7 @@ function runDoctor(params) {
17705
17769
  const validProfileNames = new Set(profileSources.keys());
17706
17770
  const validProviderNames = /* @__PURE__ */ new Set([...params.providers.map((entry) => entry.name), ...Object.keys(params.providerAliases ?? {})]);
17707
17771
  const validIdentityNames = /* @__PURE__ */ new Set();
17772
+ const signedInAccounts = [];
17708
17773
  for (const entry of params.identities) {
17709
17774
  const validated3 = validate(IdentitySchema, entry, "identity", entry.name);
17710
17775
  if (!validated3.ok) {
@@ -17754,6 +17819,18 @@ function runDoctor(params) {
17754
17819
  signIn.state === "signed-out" ? `${entry.name} is signed out (${signIn.detail}). Run \`agent-shim run @${entry.name}\` and sign in.` : `${entry.name} ${standing} (${signIn.detail}).`,
17755
17820
  entry.name
17756
17821
  );
17822
+ if (signIn.state === "signed-in" && entry.account !== void 0) {
17823
+ signedInAccounts.push({ name: entry.name, account: entry.account });
17824
+ }
17825
+ }
17826
+ for (const group of groupSharedLogins(signedInAccounts, (entry) => loginKeyOf(entry.account))) {
17827
+ const names = group.map((entry) => entry.name);
17828
+ const holder = group.map((entry) => summariseIdentityAccount(entry.account)).map((summary) => summary === void 0 ? void 0 : describeAccountHolder(summary)).find((described) => described !== void 0);
17829
+ push(
17830
+ "sign-in",
17831
+ "warn",
17832
+ `Identities ${joinNames(names)} hold the same login${holder === void 0 ? "" : ` (${holder})`}, so they share one quota window and all but one are redundant. Remove the redundant ones with \`agent-shim identity remove <name>\`.`
17833
+ );
17757
17834
  }
17758
17835
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
17759
17836
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
@@ -18042,7 +18119,7 @@ function collectDoctorReport(params) {
18042
18119
  const identities = identityNames.map((name) => {
18043
18120
  const farmRoot = import_node_path29.default.join(paths.identitiesDir, name);
18044
18121
  const identityPath = import_node_path29.default.join(farmRoot, "identity.json");
18045
- return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: import_node_fs10.default.existsSync(credentialsFilePath(farmRoot)) };
18122
+ return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: import_node_fs10.default.existsSync(credentialsFilePath(farmRoot)), account: readIdentityAccount(realFsPort, paths.identitiesDir, name)?.account };
18046
18123
  });
18047
18124
  const profileNames = import_node_fs10.default.existsSync(paths.configProfilesDir) ? import_node_fs10.default.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
18048
18125
  const configProfiles = profileNames.map((name) => {
package/dist/index.mjs CHANGED
@@ -4740,7 +4740,7 @@ var init_package = __esm({
4740
4740
  "package.json"() {
4741
4741
  package_default = {
4742
4742
  name: "agent-shim",
4743
- version: "11.77.0",
4743
+ version: "11.79.0",
4744
4744
  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.",
4745
4745
  license: "MIT",
4746
4746
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -10699,7 +10699,8 @@ var init_argv = __esm({
10699
10699
  { flag: "--remote-control", key: "remoteControl" },
10700
10700
  { flag: "--wait", key: "wait" },
10701
10701
  { flag: "--pause", key: "pause" },
10702
- { flag: "--explain", key: "explain" }
10702
+ { flag: "--explain", key: "explain" },
10703
+ { flag: "--pick", key: "pick" }
10703
10704
  ];
10704
10705
  LAUNCHER_FLAG_NAMES = [
10705
10706
  ...VALUED_FLAGS,
@@ -11787,7 +11788,7 @@ function decideLaunch(params, readOnly) {
11787
11788
  const configDirEscapeHatchApplies = env.CLAUDE_CONFIG_DIR !== void 0 && env.CLAUDE_CONFIG_DIR !== "";
11788
11789
  const decidedIdentity = decideIdentity({
11789
11790
  env,
11790
- argv0Identity: parsedArgv.identity,
11791
+ argv0Identity: params.pickedIdentity ?? parsedArgv.identity,
11791
11792
  directoryPinnedIdentity: params.directoryPinnedIdentity,
11792
11793
  readActiveIdentityFile: () => {
11793
11794
  const raw = fs14.readFileUtf8(paths.activeIdentityFile);
@@ -16669,6 +16670,65 @@ import fs11 from "node:fs";
16669
16670
  import path29 from "node:path";
16670
16671
  init_signIn();
16671
16672
 
16673
+ // src/identityAccounts.ts
16674
+ init_account();
16675
+
16676
+ // src/usage/accountDescription.ts
16677
+ function describeAccountOrganisation(account) {
16678
+ const parts = [account.organizationName, account.organizationType].filter((part) => part !== void 0);
16679
+ return parts.length === 0 ? void 0 : parts.join(", ");
16680
+ }
16681
+
16682
+ // src/identityAccounts.ts
16683
+ function readIdentityAccount(fs14, identitiesDir, identity) {
16684
+ try {
16685
+ const account = readAccountMetadata(fs14, identitiesDir, identity);
16686
+ return account === void 0 ? void 0 : { account };
16687
+ } catch (error) {
16688
+ if (error instanceof AccountMetadataError) {
16689
+ return { problem: error.message };
16690
+ }
16691
+ throw error;
16692
+ }
16693
+ }
16694
+ function summariseIdentityAccount(account) {
16695
+ const summary = {
16696
+ ...account.emailAddress === void 0 ? {} : { emailAddress: account.emailAddress },
16697
+ ...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
16698
+ ...account.organizationType === void 0 ? {} : { organizationType: account.organizationType },
16699
+ ...account.organizationRole === void 0 ? {} : { organizationRole: account.organizationRole },
16700
+ ...account.billingType === void 0 ? {} : { billingType: account.billingType },
16701
+ ...account.seatTier === void 0 ? {} : { seatTier: account.seatTier }
16702
+ };
16703
+ return Object.keys(summary).length === 0 ? void 0 : summary;
16704
+ }
16705
+ function loginKeyOf(account) {
16706
+ return account.accountUuid === void 0 ? void 0 : JSON.stringify([account.accountUuid, account.organizationUuid ?? null]);
16707
+ }
16708
+ function collectByLogin(items, loginOf) {
16709
+ const groups = /* @__PURE__ */ new Map();
16710
+ for (const item of items) {
16711
+ const key = loginOf(item) ?? /* @__PURE__ */ Symbol("no login");
16712
+ const group = groups.get(key);
16713
+ if (group === void 0) {
16714
+ groups.set(key, [item]);
16715
+ } else {
16716
+ group.push(item);
16717
+ }
16718
+ }
16719
+ return [...groups.values()];
16720
+ }
16721
+ function groupSharedLogins(items, loginOf) {
16722
+ return collectByLogin(items, loginOf).filter((group) => group.length > 1);
16723
+ }
16724
+ function describeAccountHolder(summary) {
16725
+ const organisation = describeAccountOrganisation(summary);
16726
+ if (summary.emailAddress === void 0) {
16727
+ return organisation;
16728
+ }
16729
+ return organisation === void 0 ? summary.emailAddress : `${summary.emailAddress} (${organisation})`;
16730
+ }
16731
+
16672
16732
  // src/codex/siwcStore.ts
16673
16733
  import { z as z26 } from "zod";
16674
16734
 
@@ -17043,6 +17103,10 @@ var DOCTOR_SECTIONS = [
17043
17103
  "frontdoor",
17044
17104
  "models"
17045
17105
  ];
17106
+ function joinNames(names) {
17107
+ const last = names.at(-1);
17108
+ return names.length < 2 || last === void 0 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${last}`;
17109
+ }
17046
17110
  function refinePathShadow(status, ownExecutablePath, realpath) {
17047
17111
  if (status.status !== "shadowed") {
17048
17112
  return status;
@@ -17290,6 +17354,7 @@ function runDoctor(params) {
17290
17354
  const validProfileNames = new Set(profileSources.keys());
17291
17355
  const validProviderNames = /* @__PURE__ */ new Set([...params.providers.map((entry) => entry.name), ...Object.keys(params.providerAliases ?? {})]);
17292
17356
  const validIdentityNames = /* @__PURE__ */ new Set();
17357
+ const signedInAccounts = [];
17293
17358
  for (const entry of params.identities) {
17294
17359
  const validated3 = validate(IdentitySchema, entry, "identity", entry.name);
17295
17360
  if (!validated3.ok) {
@@ -17339,6 +17404,18 @@ function runDoctor(params) {
17339
17404
  signIn.state === "signed-out" ? `${entry.name} is signed out (${signIn.detail}). Run \`agent-shim run @${entry.name}\` and sign in.` : `${entry.name} ${standing} (${signIn.detail}).`,
17340
17405
  entry.name
17341
17406
  );
17407
+ if (signIn.state === "signed-in" && entry.account !== void 0) {
17408
+ signedInAccounts.push({ name: entry.name, account: entry.account });
17409
+ }
17410
+ }
17411
+ for (const group of groupSharedLogins(signedInAccounts, (entry) => loginKeyOf(entry.account))) {
17412
+ const names = group.map((entry) => entry.name);
17413
+ const holder = group.map((entry) => summariseIdentityAccount(entry.account)).map((summary) => summary === void 0 ? void 0 : describeAccountHolder(summary)).find((described) => described !== void 0);
17414
+ push(
17415
+ "sign-in",
17416
+ "warn",
17417
+ `Identities ${joinNames(names)} hold the same login${holder === void 0 ? "" : ` (${holder})`}, so they share one quota window and all but one are redundant. Remove the redundant ones with \`agent-shim identity remove <name>\`.`
17418
+ );
17342
17419
  }
17343
17420
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
17344
17421
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
@@ -17627,7 +17704,7 @@ function collectDoctorReport(params) {
17627
17704
  const identities = identityNames.map((name) => {
17628
17705
  const farmRoot = path29.join(paths.identitiesDir, name);
17629
17706
  const identityPath = path29.join(farmRoot, "identity.json");
17630
- return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: fs11.existsSync(credentialsFilePath(farmRoot)) };
17707
+ return { name, path: identityPath, raw: realFsPort.readFileUtf8(identityPath), farmRoot, credentialsFilePresent: fs11.existsSync(credentialsFilePath(farmRoot)), account: readIdentityAccount(realFsPort, paths.identitiesDir, name)?.account };
17631
17708
  });
17632
17709
  const profileNames = fs11.existsSync(paths.configProfilesDir) ? fs11.readdirSync(paths.configProfilesDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.slice(0, -".json".length)).sort() : [];
17633
17710
  const configProfiles = profileNames.map((name) => {
@@ -8876,6 +8876,180 @@ type PathShadowStatus = {
8876
8876
  readonly by: string;
8877
8877
  };
8878
8878
 
8879
+ /** One routed request, appended to the usage log when its response has finished. */
8880
+ declare const UsageRecordSchema: z.ZodObject<{
8881
+ schemaVersion: z.ZodLiteral<1>;
8882
+ at: z.ZodISODateTime;
8883
+ identity: z.ZodOptional<z.ZodString>;
8884
+ provider: z.ZodString;
8885
+ sessionId: z.ZodOptional<z.ZodString>;
8886
+ clientSessionId: z.ZodOptional<z.ZodString>;
8887
+ project: z.ZodOptional<z.ZodString>;
8888
+ viaHeadroom: z.ZodOptional<z.ZodBoolean>;
8889
+ route: z.ZodString;
8890
+ method: z.ZodString;
8891
+ endpoint: z.ZodString;
8892
+ model: z.ZodOptional<z.ZodString>;
8893
+ requestedModel: z.ZodOptional<z.ZodString>;
8894
+ candidateProviders: z.ZodOptional<z.ZodArray<z.ZodString>>;
8895
+ status: z.ZodNumber;
8896
+ latencyMs: z.ZodNumber;
8897
+ durationMs: z.ZodNumber;
8898
+ outcome: z.ZodEnum<{
8899
+ completed: "completed";
8900
+ aborted: "aborted";
8901
+ }>;
8902
+ usage: z.ZodOptional<z.ZodObject<{
8903
+ inputTokens: z.ZodOptional<z.ZodNumber>;
8904
+ outputTokens: z.ZodOptional<z.ZodNumber>;
8905
+ cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
8906
+ cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
8907
+ }, z.core.$strict>>;
8908
+ rateLimitHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
8909
+ limit: z.ZodOptional<z.ZodObject<{
8910
+ kind: z.ZodEnum<{
8911
+ "rate-limited": "rate-limited";
8912
+ "quota-exhausted": "quota-exhausted";
8913
+ }>;
8914
+ retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
8915
+ resetAt: z.ZodOptional<z.ZodISODateTime>;
8916
+ window: z.ZodOptional<z.ZodString>;
8917
+ evidence: z.ZodArray<z.ZodString>;
8918
+ }, z.core.$strict>>;
8919
+ requestId: z.ZodOptional<z.ZodString>;
8920
+ }, z.core.$strict>;
8921
+ type UsageRecord = z.infer<typeof UsageRecordSchema>;
8922
+ /** One quota window from Anthropic's unified rate-limit headers. */
8923
+ declare const QuotaWindowSchema: z.ZodObject<{
8924
+ utilization: z.ZodOptional<z.ZodNumber>;
8925
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
8926
+ status: z.ZodOptional<z.ZodString>;
8927
+ graceUtilization: z.ZodOptional<z.ZodNumber>;
8928
+ }, z.core.$strict>;
8929
+ type QuotaWindow = z.infer<typeof QuotaWindowSchema>;
8930
+ /**
8931
+ * The account fields copied from an identity's stored Claude login (`oauthAccount` in the identity's `.claude.json`): who the account is and which plan and tier it is on. Never the token, which Claude Code keeps elsewhere. Each field is present only when the stored login has it; a setup-token identity has no profile scope and so none of them (see issue #44).
8932
+ */
8933
+ declare const AccountMetadataSchema: z.ZodObject<{
8934
+ accountUuid: z.ZodOptional<z.ZodString>;
8935
+ emailAddress: z.ZodOptional<z.ZodString>;
8936
+ displayName: z.ZodOptional<z.ZodString>;
8937
+ organizationUuid: z.ZodOptional<z.ZodString>;
8938
+ organizationName: z.ZodOptional<z.ZodString>;
8939
+ organizationType: z.ZodOptional<z.ZodString>;
8940
+ organizationRole: z.ZodOptional<z.ZodString>;
8941
+ workspaceRole: z.ZodOptional<z.ZodString>;
8942
+ billingType: z.ZodOptional<z.ZodString>;
8943
+ seatTier: z.ZodOptional<z.ZodString>;
8944
+ organizationRateLimitTier: z.ZodOptional<z.ZodString>;
8945
+ userRateLimitTier: z.ZodOptional<z.ZodString>;
8946
+ hasExtraUsageEnabled: z.ZodOptional<z.ZodBoolean>;
8947
+ subscriptionCreatedAt: z.ZodOptional<z.ZodString>;
8948
+ accountCreatedAt: z.ZodOptional<z.ZodString>;
8949
+ }, z.core.$strict>;
8950
+ type AccountMetadata = z.infer<typeof AccountMetadataSchema>;
8951
+ /**
8952
+ * The latest state for one identity, rewritten atomically after each recorded response: the snapshot file other tools read (`~/.agent-shim/usage/snapshots/<identity>.json`) without shelling out to `agent-shim`.
8953
+ */
8954
+ declare const UsageSnapshotSchema: z.ZodObject<{
8955
+ schemaVersion: z.ZodLiteral<1>;
8956
+ identity: z.ZodString;
8957
+ updatedAt: z.ZodISODateTime;
8958
+ account: z.ZodOptional<z.ZodObject<{
8959
+ accountUuid: z.ZodOptional<z.ZodString>;
8960
+ emailAddress: z.ZodOptional<z.ZodString>;
8961
+ displayName: z.ZodOptional<z.ZodString>;
8962
+ organizationUuid: z.ZodOptional<z.ZodString>;
8963
+ organizationName: z.ZodOptional<z.ZodString>;
8964
+ organizationType: z.ZodOptional<z.ZodString>;
8965
+ organizationRole: z.ZodOptional<z.ZodString>;
8966
+ workspaceRole: z.ZodOptional<z.ZodString>;
8967
+ billingType: z.ZodOptional<z.ZodString>;
8968
+ seatTier: z.ZodOptional<z.ZodString>;
8969
+ organizationRateLimitTier: z.ZodOptional<z.ZodString>;
8970
+ userRateLimitTier: z.ZodOptional<z.ZodString>;
8971
+ hasExtraUsageEnabled: z.ZodOptional<z.ZodBoolean>;
8972
+ subscriptionCreatedAt: z.ZodOptional<z.ZodString>;
8973
+ accountCreatedAt: z.ZodOptional<z.ZodString>;
8974
+ }, z.core.$strict>>;
8975
+ providers: z.ZodRecord<z.ZodString, z.ZodObject<{
8976
+ lastRequestAt: z.ZodOptional<z.ZodISODateTime>;
8977
+ lastStatus: z.ZodOptional<z.ZodNumber>;
8978
+ lastModel: z.ZodOptional<z.ZodString>;
8979
+ rateLimit: z.ZodOptional<z.ZodObject<{
8980
+ observedAt: z.ZodISODateTime;
8981
+ headers: z.ZodRecord<z.ZodString, z.ZodString>;
8982
+ unified: z.ZodOptional<z.ZodObject<{
8983
+ status: z.ZodOptional<z.ZodString>;
8984
+ fiveHour: z.ZodOptional<z.ZodObject<{
8985
+ utilization: z.ZodOptional<z.ZodNumber>;
8986
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
8987
+ status: z.ZodOptional<z.ZodString>;
8988
+ graceUtilization: z.ZodOptional<z.ZodNumber>;
8989
+ }, z.core.$strict>>;
8990
+ sevenDay: z.ZodOptional<z.ZodObject<{
8991
+ utilization: z.ZodOptional<z.ZodNumber>;
8992
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
8993
+ status: z.ZodOptional<z.ZodString>;
8994
+ graceUtilization: z.ZodOptional<z.ZodNumber>;
8995
+ }, z.core.$strict>>;
8996
+ sevenDayOverageIncluded: z.ZodOptional<z.ZodObject<{
8997
+ utilization: z.ZodOptional<z.ZodNumber>;
8998
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
8999
+ status: z.ZodOptional<z.ZodString>;
9000
+ graceUtilization: z.ZodOptional<z.ZodNumber>;
9001
+ }, z.core.$strict>>;
9002
+ representativeClaim: z.ZodOptional<z.ZodString>;
9003
+ resetAt: z.ZodOptional<z.ZodISODateTime>;
9004
+ overageStatus: z.ZodOptional<z.ZodString>;
9005
+ overageUtilization: z.ZodOptional<z.ZodNumber>;
9006
+ overageResetsAt: z.ZodOptional<z.ZodISODateTime>;
9007
+ overageDisabledReason: z.ZodOptional<z.ZodString>;
9008
+ overageInUse: z.ZodOptional<z.ZodBoolean>;
9009
+ extraUsageSpend: z.ZodOptional<z.ZodObject<{
9010
+ usedMinor: z.ZodNumber;
9011
+ limitMinor: z.ZodOptional<z.ZodNumber>;
9012
+ currency: z.ZodString;
9013
+ }, z.core.$strict>>;
9014
+ fallbackAvailable: z.ZodOptional<z.ZodBoolean>;
9015
+ }, z.core.$strict>>;
9016
+ planWindowsUnavailableAt: z.ZodOptional<z.ZodISODateTime>;
9017
+ }, z.core.$strict>>;
9018
+ lastLimit: z.ZodOptional<z.ZodObject<{
9019
+ kind: z.ZodEnum<{
9020
+ "rate-limited": "rate-limited";
9021
+ "quota-exhausted": "quota-exhausted";
9022
+ }>;
9023
+ retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
9024
+ resetAt: z.ZodOptional<z.ZodISODateTime>;
9025
+ window: z.ZodOptional<z.ZodString>;
9026
+ evidence: z.ZodArray<z.ZodString>;
9027
+ observedAt: z.ZodISODateTime;
9028
+ status: z.ZodNumber;
9029
+ }, z.core.$strict>>;
9030
+ quota: z.ZodOptional<z.ZodObject<{
9031
+ observedAt: z.ZodISODateTime;
9032
+ source: z.ZodString;
9033
+ level: z.ZodOptional<z.ZodString>;
9034
+ account: z.ZodOptional<z.ZodObject<{
9035
+ email: z.ZodOptional<z.ZodString>;
9036
+ accountId: z.ZodOptional<z.ZodString>;
9037
+ }, z.core.$strict>>;
9038
+ windows: z.ZodArray<z.ZodObject<{
9039
+ measures: z.ZodString;
9040
+ periodMs: z.ZodOptional<z.ZodNumber>;
9041
+ period: z.ZodOptional<z.ZodString>;
9042
+ utilization: z.ZodNumber;
9043
+ resetsAt: z.ZodOptional<z.ZodISODateTime>;
9044
+ limit: z.ZodOptional<z.ZodNumber>;
9045
+ used: z.ZodOptional<z.ZodNumber>;
9046
+ remaining: z.ZodOptional<z.ZodNumber>;
9047
+ }, z.core.$strict>>;
9048
+ }, z.core.$strict>>;
9049
+ }, z.core.$strict>>;
9050
+ }, z.core.$strict>;
9051
+ type UsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
9052
+
8879
9053
  /**
8880
9054
  * How the allowlist the headroom daemon serves lags the allowlist computed now. The daemon re-reads its allowlist file on every request that follows a change to it, and its supervisor rewrites the file and records the hash in the same tick, so a lag that lasts beyond one tick means the supervisor is not running or cannot write the file. A daemon started by an earlier release reads no file, and is reported as such instead.
8881
9055
  */
@@ -9085,6 +9259,8 @@ interface DoctorIdentityInput {
9085
9259
  readonly farmRoot: string;
9086
9260
  /** Whether the identity's `.credentials.json` exists, pre-resolved by the wiring layer since it is a file-existence check. */
9087
9261
  readonly credentialsFilePresent: boolean;
9262
+ /** The account metadata of the identity's stored login, pre-resolved by the wiring layer since it is a read of the identity's `.claude.json`. Undefined when the identity has no stored login or its metadata cannot be read. */
9263
+ readonly account: AccountMetadata | undefined;
9088
9264
  }
9089
9265
  /** One configuration profile's raw `<name>.json`, unparsed. */
9090
9266
  interface DoctorConfigProfileInput {
@@ -9192,180 +9368,6 @@ interface CollectDoctorReportParams {
9192
9368
  */
9193
9369
  declare function collectDoctorReport(params: CollectDoctorReportParams): DoctorReport;
9194
9370
 
9195
- /** One routed request, appended to the usage log when its response has finished. */
9196
- declare const UsageRecordSchema: z.ZodObject<{
9197
- schemaVersion: z.ZodLiteral<1>;
9198
- at: z.ZodISODateTime;
9199
- identity: z.ZodOptional<z.ZodString>;
9200
- provider: z.ZodString;
9201
- sessionId: z.ZodOptional<z.ZodString>;
9202
- clientSessionId: z.ZodOptional<z.ZodString>;
9203
- project: z.ZodOptional<z.ZodString>;
9204
- viaHeadroom: z.ZodOptional<z.ZodBoolean>;
9205
- route: z.ZodString;
9206
- method: z.ZodString;
9207
- endpoint: z.ZodString;
9208
- model: z.ZodOptional<z.ZodString>;
9209
- requestedModel: z.ZodOptional<z.ZodString>;
9210
- candidateProviders: z.ZodOptional<z.ZodArray<z.ZodString>>;
9211
- status: z.ZodNumber;
9212
- latencyMs: z.ZodNumber;
9213
- durationMs: z.ZodNumber;
9214
- outcome: z.ZodEnum<{
9215
- completed: "completed";
9216
- aborted: "aborted";
9217
- }>;
9218
- usage: z.ZodOptional<z.ZodObject<{
9219
- inputTokens: z.ZodOptional<z.ZodNumber>;
9220
- outputTokens: z.ZodOptional<z.ZodNumber>;
9221
- cacheCreationInputTokens: z.ZodOptional<z.ZodNumber>;
9222
- cacheReadInputTokens: z.ZodOptional<z.ZodNumber>;
9223
- }, z.core.$strict>>;
9224
- rateLimitHeaders: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
9225
- limit: z.ZodOptional<z.ZodObject<{
9226
- kind: z.ZodEnum<{
9227
- "rate-limited": "rate-limited";
9228
- "quota-exhausted": "quota-exhausted";
9229
- }>;
9230
- retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
9231
- resetAt: z.ZodOptional<z.ZodISODateTime>;
9232
- window: z.ZodOptional<z.ZodString>;
9233
- evidence: z.ZodArray<z.ZodString>;
9234
- }, z.core.$strict>>;
9235
- requestId: z.ZodOptional<z.ZodString>;
9236
- }, z.core.$strict>;
9237
- type UsageRecord = z.infer<typeof UsageRecordSchema>;
9238
- /** One quota window from Anthropic's unified rate-limit headers. */
9239
- declare const QuotaWindowSchema: z.ZodObject<{
9240
- utilization: z.ZodOptional<z.ZodNumber>;
9241
- resetsAt: z.ZodOptional<z.ZodISODateTime>;
9242
- status: z.ZodOptional<z.ZodString>;
9243
- graceUtilization: z.ZodOptional<z.ZodNumber>;
9244
- }, z.core.$strict>;
9245
- type QuotaWindow = z.infer<typeof QuotaWindowSchema>;
9246
- /**
9247
- * The account fields copied from an identity's stored Claude login (`oauthAccount` in the identity's `.claude.json`): who the account is and which plan and tier it is on. Never the token, which Claude Code keeps elsewhere. Each field is present only when the stored login has it; a setup-token identity has no profile scope and so none of them (see issue #44).
9248
- */
9249
- declare const AccountMetadataSchema: z.ZodObject<{
9250
- accountUuid: z.ZodOptional<z.ZodString>;
9251
- emailAddress: z.ZodOptional<z.ZodString>;
9252
- displayName: z.ZodOptional<z.ZodString>;
9253
- organizationUuid: z.ZodOptional<z.ZodString>;
9254
- organizationName: z.ZodOptional<z.ZodString>;
9255
- organizationType: z.ZodOptional<z.ZodString>;
9256
- organizationRole: z.ZodOptional<z.ZodString>;
9257
- workspaceRole: z.ZodOptional<z.ZodString>;
9258
- billingType: z.ZodOptional<z.ZodString>;
9259
- seatTier: z.ZodOptional<z.ZodString>;
9260
- organizationRateLimitTier: z.ZodOptional<z.ZodString>;
9261
- userRateLimitTier: z.ZodOptional<z.ZodString>;
9262
- hasExtraUsageEnabled: z.ZodOptional<z.ZodBoolean>;
9263
- subscriptionCreatedAt: z.ZodOptional<z.ZodString>;
9264
- accountCreatedAt: z.ZodOptional<z.ZodString>;
9265
- }, z.core.$strict>;
9266
- type AccountMetadata = z.infer<typeof AccountMetadataSchema>;
9267
- /**
9268
- * The latest state for one identity, rewritten atomically after each recorded response: the snapshot file other tools read (`~/.agent-shim/usage/snapshots/<identity>.json`) without shelling out to `agent-shim`.
9269
- */
9270
- declare const UsageSnapshotSchema: z.ZodObject<{
9271
- schemaVersion: z.ZodLiteral<1>;
9272
- identity: z.ZodString;
9273
- updatedAt: z.ZodISODateTime;
9274
- account: z.ZodOptional<z.ZodObject<{
9275
- accountUuid: z.ZodOptional<z.ZodString>;
9276
- emailAddress: z.ZodOptional<z.ZodString>;
9277
- displayName: z.ZodOptional<z.ZodString>;
9278
- organizationUuid: z.ZodOptional<z.ZodString>;
9279
- organizationName: z.ZodOptional<z.ZodString>;
9280
- organizationType: z.ZodOptional<z.ZodString>;
9281
- organizationRole: z.ZodOptional<z.ZodString>;
9282
- workspaceRole: z.ZodOptional<z.ZodString>;
9283
- billingType: z.ZodOptional<z.ZodString>;
9284
- seatTier: z.ZodOptional<z.ZodString>;
9285
- organizationRateLimitTier: z.ZodOptional<z.ZodString>;
9286
- userRateLimitTier: z.ZodOptional<z.ZodString>;
9287
- hasExtraUsageEnabled: z.ZodOptional<z.ZodBoolean>;
9288
- subscriptionCreatedAt: z.ZodOptional<z.ZodString>;
9289
- accountCreatedAt: z.ZodOptional<z.ZodString>;
9290
- }, z.core.$strict>>;
9291
- providers: z.ZodRecord<z.ZodString, z.ZodObject<{
9292
- lastRequestAt: z.ZodOptional<z.ZodISODateTime>;
9293
- lastStatus: z.ZodOptional<z.ZodNumber>;
9294
- lastModel: z.ZodOptional<z.ZodString>;
9295
- rateLimit: z.ZodOptional<z.ZodObject<{
9296
- observedAt: z.ZodISODateTime;
9297
- headers: z.ZodRecord<z.ZodString, z.ZodString>;
9298
- unified: z.ZodOptional<z.ZodObject<{
9299
- status: z.ZodOptional<z.ZodString>;
9300
- fiveHour: z.ZodOptional<z.ZodObject<{
9301
- utilization: z.ZodOptional<z.ZodNumber>;
9302
- resetsAt: z.ZodOptional<z.ZodISODateTime>;
9303
- status: z.ZodOptional<z.ZodString>;
9304
- graceUtilization: z.ZodOptional<z.ZodNumber>;
9305
- }, z.core.$strict>>;
9306
- sevenDay: z.ZodOptional<z.ZodObject<{
9307
- utilization: z.ZodOptional<z.ZodNumber>;
9308
- resetsAt: z.ZodOptional<z.ZodISODateTime>;
9309
- status: z.ZodOptional<z.ZodString>;
9310
- graceUtilization: z.ZodOptional<z.ZodNumber>;
9311
- }, z.core.$strict>>;
9312
- sevenDayOverageIncluded: z.ZodOptional<z.ZodObject<{
9313
- utilization: z.ZodOptional<z.ZodNumber>;
9314
- resetsAt: z.ZodOptional<z.ZodISODateTime>;
9315
- status: z.ZodOptional<z.ZodString>;
9316
- graceUtilization: z.ZodOptional<z.ZodNumber>;
9317
- }, z.core.$strict>>;
9318
- representativeClaim: z.ZodOptional<z.ZodString>;
9319
- resetAt: z.ZodOptional<z.ZodISODateTime>;
9320
- overageStatus: z.ZodOptional<z.ZodString>;
9321
- overageUtilization: z.ZodOptional<z.ZodNumber>;
9322
- overageResetsAt: z.ZodOptional<z.ZodISODateTime>;
9323
- overageDisabledReason: z.ZodOptional<z.ZodString>;
9324
- overageInUse: z.ZodOptional<z.ZodBoolean>;
9325
- extraUsageSpend: z.ZodOptional<z.ZodObject<{
9326
- usedMinor: z.ZodNumber;
9327
- limitMinor: z.ZodOptional<z.ZodNumber>;
9328
- currency: z.ZodString;
9329
- }, z.core.$strict>>;
9330
- fallbackAvailable: z.ZodOptional<z.ZodBoolean>;
9331
- }, z.core.$strict>>;
9332
- planWindowsUnavailableAt: z.ZodOptional<z.ZodISODateTime>;
9333
- }, z.core.$strict>>;
9334
- lastLimit: z.ZodOptional<z.ZodObject<{
9335
- kind: z.ZodEnum<{
9336
- "rate-limited": "rate-limited";
9337
- "quota-exhausted": "quota-exhausted";
9338
- }>;
9339
- retryAfterSeconds: z.ZodOptional<z.ZodNumber>;
9340
- resetAt: z.ZodOptional<z.ZodISODateTime>;
9341
- window: z.ZodOptional<z.ZodString>;
9342
- evidence: z.ZodArray<z.ZodString>;
9343
- observedAt: z.ZodISODateTime;
9344
- status: z.ZodNumber;
9345
- }, z.core.$strict>>;
9346
- quota: z.ZodOptional<z.ZodObject<{
9347
- observedAt: z.ZodISODateTime;
9348
- source: z.ZodString;
9349
- level: z.ZodOptional<z.ZodString>;
9350
- account: z.ZodOptional<z.ZodObject<{
9351
- email: z.ZodOptional<z.ZodString>;
9352
- accountId: z.ZodOptional<z.ZodString>;
9353
- }, z.core.$strict>>;
9354
- windows: z.ZodArray<z.ZodObject<{
9355
- measures: z.ZodString;
9356
- periodMs: z.ZodOptional<z.ZodNumber>;
9357
- period: z.ZodOptional<z.ZodString>;
9358
- utilization: z.ZodNumber;
9359
- resetsAt: z.ZodOptional<z.ZodISODateTime>;
9360
- limit: z.ZodOptional<z.ZodNumber>;
9361
- used: z.ZodOptional<z.ZodNumber>;
9362
- remaining: z.ZodOptional<z.ZodNumber>;
9363
- }, z.core.$strict>>;
9364
- }, z.core.$strict>>;
9365
- }, z.core.$strict>>;
9366
- }, z.core.$strict>;
9367
- type UsageSnapshot = z.infer<typeof UsageSnapshotSchema>;
9368
-
9369
9371
  /** How a sign-out went: whether OpenAI was told, so the person knows the grant is dead on its side too. */
9370
9372
  interface SiwcLogoutResult {
9371
9373
  readonly hadGrant: boolean;
@@ -14030,6 +14032,8 @@ interface PrepareLaunchParams {
14030
14032
  readonly log: LogPort;
14031
14033
  /** Discovers the real `claude` binary to spawn. Injected so `runLauncher` never depends on `src/versionDiscovery.ts`'s own filesystem/PATH inputs directly — the caller (`src/cli.ts`) wires the real discovery, tests wire a fake that returns a fixed path. */
14032
14034
  readonly resolveClaudeBinary: ClaudeBinaryResolver;
14035
+ /** The identity (or `pool:<name>`) the person chose at the identity picker, which takes the place of whatever the arguments named: the question was asked because that choice was not settled. */
14036
+ readonly pickedIdentity?: string;
14033
14037
  /** An identity pinned to `$PWD` by a directory rule. Accepted as an already-resolved value — the rules-loading code that produces it lands in Phase 4/5. */
14034
14038
  readonly directoryPinnedIdentity?: string;
14035
14039
  /** A directory rule's `configProfile` selection for `$PWD`. Accepted as an already-resolved value for the same reason. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-shim",
3
- "version": "11.77.0",
3
+ "version": "11.79.0",
4
4
  "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.",
5
5
  "license": "MIT",
6
6
  "author": "Joseph Mearman <joseph@mearman.co.uk>",