agent-shim 11.79.0 → 11.81.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). 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).
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. Members that hold the same login (the same account in the same organisation) share one quota window, so a pool ranks them as one candidate and never fails over from one to the other; `pool add`, `pool set` and `doctor` warn when a pool lists two of them. 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
 
@@ -85,7 +85,7 @@ agent-shim <noun> <verb> [name] [options] # nouns: identity, profile, pool,
85
85
  agent-shim identity add <name> [--json]
86
86
  agent-shim identity list [--json]
87
87
  agent-shim identity show <name> [--json]
88
- agent-shim identity set <name> [--default-profile <profile> | --no-default-profile] [--provider <provider> | --no-provider] [--[no-]allow-ambient-credential] [--json]
88
+ agent-shim identity set <name> [--label <text> | --no-label] [--default-profile <profile> | --no-default-profile] [--provider <provider> | --no-provider] [--[no-]allow-ambient-credential] [--json]
89
89
  agent-shim identity set <name> [--credential <source>]... [--credential-target <bearer|apiKey|oauthToken>] [--no-credential] [--json]
90
90
  agent-shim identity set <name> [--anchor <days>@<HH:MM>[/<zone>]]... [--no-anchor] [--json]
91
91
  agent-shim identity remove <name> [--yes] [--json]
package/dist/cli.cjs CHANGED
@@ -25123,7 +25123,7 @@ function providerFilePath(directory, name, source) {
25123
25123
  }
25124
25124
  return import_node_path4.default.join(directory, `${name}.json`);
25125
25125
  }
25126
- var import_node_path4, FILESYSTEM_COMPONENT_BYTES, MAX_PID_DIGITS, UUID_CHARS, ATOMIC_WRITE_OVERHEAD_BYTES, FARM_SWAP_OVERHEAD_BYTES, MAX_NAME_BYTES, FILE_NAME_PATTERN, FILE_NAME, FILE_NAME_RULE, STORED_NAME_RULE, FileNameSchema, StoredNameSchema, UnsafeNameError, InvalidProviderNameError;
25126
+ var import_node_path4, FILESYSTEM_COMPONENT_BYTES, MAX_PID_DIGITS, UUID_CHARS, ATOMIC_WRITE_OVERHEAD_BYTES, FARM_SWAP_OVERHEAD_BYTES, MAX_NAME_BYTES, FILE_NAME_PATTERN, FILE_NAME, FILE_NAME_RULE, STORED_NAME_RULE, FileNameSchema, StoredNameSchema, LABEL_FORBIDDEN, IDENTITY_LABEL_RULE, IdentityLabelSchema, UnsafeNameError, InvalidProviderNameError;
25127
25127
  var init_names = __esm({
25128
25128
  "src/config/names.ts"() {
25129
25129
  "use strict";
@@ -25142,6 +25142,9 @@ var init_names = __esm({
25142
25142
  STORED_NAME_RULE = `must be non-empty, at most ${String(MAX_NAME_BYTES)} bytes, not "." or "..", and contain no path separator or NUL`;
25143
25143
  FileNameSchema = external_exports.string().min(1).regex(FILE_NAME, FILE_NAME_RULE);
25144
25144
  StoredNameSchema = external_exports.string().min(1).max(MAX_NAME_BYTES).refine(isStoredName, STORED_NAME_RULE);
25145
+ LABEL_FORBIDDEN = /[\p{Cc}\p{Zl}\p{Zp}]/u;
25146
+ IDENTITY_LABEL_RULE = `must be non-empty single-line text of at most ${String(MAX_NAME_BYTES)} characters, with no control characters and no leading or trailing whitespace`;
25147
+ IdentityLabelSchema = external_exports.string().min(1).max(MAX_NAME_BYTES).refine((label2) => label2 === label2.trim() && !LABEL_FORBIDDEN.test(label2), IDENTITY_LABEL_RULE);
25145
25148
  UnsafeNameError = class extends CliError {
25146
25149
  constructor(attemptedName, kind, rule = STORED_NAME_RULE, source) {
25147
25150
  super(`${source === void 0 ? "" : `${source}: `}${JSON.stringify(attemptedName)} is not a valid ${kind} name: a name ${rule}.`);
@@ -28489,6 +28492,8 @@ var init_schema = __esm({
28489
28492
  IdentitySchema = external_exports.strictObject({
28490
28493
  $schema: external_exports.string().optional(),
28491
28494
  name: FileNameSchema,
28495
+ /** What the identity is for, in the person's words, drawn beside its name in `identity list` and `show`, the launch banner and the statusline. Display only: the name stays the key for lookup, paths, keychain service names and pools. */
28496
+ label: IdentityLabelSchema.optional(),
28492
28497
  defaultConfigProfile: StoredNameSchema.optional(),
28493
28498
  /** The provider this identity exists for: a launch on the identity routes through it unless `--provider`, `--no-provider` or a cascade layer's `launch.provider` says otherwise. Its sessions run on the provider's credential, so the identity needs no stored login. */
28494
28499
  provider: StoredNameSchema.optional(),
@@ -38192,13 +38197,95 @@ var init_account = __esm({
38192
38197
  }
38193
38198
  });
38194
38199
 
38200
+ // src/usage/accountDescription.ts
38201
+ function describeAccountOrganisation(account) {
38202
+ const parts = [account.organizationName, account.organizationType].filter((part) => part !== void 0);
38203
+ return parts.length === 0 ? void 0 : parts.join(", ");
38204
+ }
38205
+ function describeAccountPlan(account) {
38206
+ const parts = [account.billingType === void 0 ? void 0 : `billing ${account.billingType}`, account.seatTier === void 0 ? void 0 : `seat tier ${account.seatTier}`].filter((part) => part !== void 0);
38207
+ return parts.length === 0 ? void 0 : parts.join(", ");
38208
+ }
38209
+ var init_accountDescription = __esm({
38210
+ "src/usage/accountDescription.ts"() {
38211
+ "use strict";
38212
+ }
38213
+ });
38214
+
38215
+ // src/identityAccounts.ts
38216
+ function readIdentityAccount(fs31, identitiesDir, identity) {
38217
+ try {
38218
+ const account = readAccountMetadata(fs31, identitiesDir, identity);
38219
+ return account === void 0 ? void 0 : { account };
38220
+ } catch (error63) {
38221
+ if (error63 instanceof AccountMetadataError) {
38222
+ return { problem: error63.message };
38223
+ }
38224
+ throw error63;
38225
+ }
38226
+ }
38227
+ function summariseIdentityAccount(account) {
38228
+ const summary = {
38229
+ ...account.emailAddress === void 0 ? {} : { emailAddress: account.emailAddress },
38230
+ ...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
38231
+ ...account.organizationType === void 0 ? {} : { organizationType: account.organizationType },
38232
+ ...account.organizationRole === void 0 ? {} : { organizationRole: account.organizationRole },
38233
+ ...account.billingType === void 0 ? {} : { billingType: account.billingType },
38234
+ ...account.seatTier === void 0 ? {} : { seatTier: account.seatTier }
38235
+ };
38236
+ return Object.keys(summary).length === 0 ? void 0 : summary;
38237
+ }
38238
+ function loginKeyOf(account) {
38239
+ return account.accountUuid === void 0 ? void 0 : JSON.stringify([account.accountUuid, account.organizationUuid ?? null]);
38240
+ }
38241
+ function collectByLogin(items, loginOf) {
38242
+ const groups = /* @__PURE__ */ new Map();
38243
+ for (const item of items) {
38244
+ const key = loginOf(item) ?? /* @__PURE__ */ Symbol("no login");
38245
+ const group = groups.get(key);
38246
+ if (group === void 0) {
38247
+ groups.set(key, [item]);
38248
+ } else {
38249
+ group.push(item);
38250
+ }
38251
+ }
38252
+ return [...groups.values()];
38253
+ }
38254
+ function groupByLogin(items, loginOf) {
38255
+ return collectByLogin(items, loginOf).flat();
38256
+ }
38257
+ function groupSharedLogins(items, loginOf) {
38258
+ return collectByLogin(items, loginOf).filter((group) => group.length > 1);
38259
+ }
38260
+ function joinNames(names) {
38261
+ const last = names.at(-1);
38262
+ return names.length < 2 || last === void 0 ? names.join("") : `${names.slice(0, -1).join(", ")} and ${last}`;
38263
+ }
38264
+ function describeAccountHolder(summary) {
38265
+ const organisation = describeAccountOrganisation(summary);
38266
+ if (summary.emailAddress === void 0) {
38267
+ return organisation;
38268
+ }
38269
+ return organisation === void 0 ? summary.emailAddress : `${summary.emailAddress} (${organisation})`;
38270
+ }
38271
+ function describeIdentityAccount(summary) {
38272
+ return [describeAccountHolder(summary), summary.organizationRole === void 0 ? void 0 : `role ${summary.organizationRole}`, describeAccountPlan(summary)].filter((part) => part !== void 0).join("; ");
38273
+ }
38274
+ var init_identityAccounts = __esm({
38275
+ "src/identityAccounts.ts"() {
38276
+ "use strict";
38277
+ init_account();
38278
+ init_accountDescription();
38279
+ }
38280
+ });
38281
+
38195
38282
  // package.json
38196
38283
  var package_default;
38197
38284
  var init_package = __esm({
38198
38285
  "package.json"() {
38199
38286
  package_default = {
38200
38287
  name: "agent-shim",
38201
- version: "11.79.0",
38288
+ version: "11.81.0",
38202
38289
  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
38290
  license: "MIT",
38204
38291
  author: "Joseph Mearman <joseph@mearman.co.uk>",
@@ -38918,6 +39005,9 @@ function assessRefusedNested(member, nested, nowMs) {
38918
39005
  };
38919
39006
  }
38920
39007
  function assess(member, nowMs) {
39008
+ if (member.sameLoginAs !== void 0) {
39009
+ return { candidate: { identity: member.identity, class: "ineligible", plan: planOf(member.account), reasons: [`same login as ${member.sameLoginAs}`] } };
39010
+ }
38921
39011
  const nested = member.nested;
38922
39012
  if (nested === void 0) {
38923
39013
  return assessIdentity(member, nowMs);
@@ -39461,6 +39551,29 @@ function loadPoolMembers(fs31, paths, identities, nowMs) {
39461
39551
  }
39462
39552
  });
39463
39553
  }
39554
+ function snapshotAgeKey(member) {
39555
+ return member.snapshot === void 0 ? Number.NEGATIVE_INFINITY : Date.parse(member.snapshot.updatedAt);
39556
+ }
39557
+ function collapseSharedLogins(members2) {
39558
+ const groups = groupSharedLogins(members2, (member) => member.nested === void 0 && member.account !== void 0 ? loginKeyOf(member.account) : void 0);
39559
+ const representativeOf = /* @__PURE__ */ new Map();
39560
+ for (const group of groups) {
39561
+ const representative = group.reduce((freshest, member) => snapshotAgeKey(member) > snapshotAgeKey(freshest) ? member : freshest);
39562
+ for (const member of group) {
39563
+ if (member !== representative) {
39564
+ representativeOf.set(member, representative.identity);
39565
+ }
39566
+ }
39567
+ }
39568
+ return members2.map((member) => {
39569
+ const representative = representativeOf.get(member);
39570
+ return representative === void 0 ? member : { ...member, sameLoginAs: representative };
39571
+ });
39572
+ }
39573
+ function stickyOnRepresentative(sticky, members2) {
39574
+ const twin = sticky === void 0 ? void 0 : members2.find((member) => member.identity === sticky.identity && member.sameLoginAs !== void 0);
39575
+ return sticky === void 0 || twin?.sameLoginAs === void 0 ? sticky : { ...sticky, identity: twin.sameLoginAs };
39576
+ }
39464
39577
  function rankPoolGraph(input2) {
39465
39578
  return rankNamedPool(input2, input2.poolName, [], input2.sticky);
39466
39579
  }
@@ -39488,13 +39601,15 @@ function rankNamedPool(input2, poolName, stack, sticky) {
39488
39601
  members2.push(typeof entry === "string" ? nested.member : { ...nested.member, policy: entry.when });
39489
39602
  missing.push(...nested.missing);
39490
39603
  }
39604
+ const collapsed = collapseSharedLogins(members2);
39605
+ const stickyPick = stickyOnRepresentative(sticky, collapsed);
39491
39606
  const ranking = rankPool({
39492
- members: members2,
39607
+ members: collapsed,
39493
39608
  nowMs: input2.nowMs,
39494
39609
  resuming: input2.resuming,
39495
39610
  ...input2.resumeContextTokens === void 0 ? {} : { resumeContextTokens: input2.resumeContextTokens },
39496
39611
  ...input2.committedContextTokens === void 0 ? {} : { committedContextTokens: input2.committedContextTokens },
39497
- ...sticky === void 0 ? {} : { sticky },
39612
+ ...stickyPick === void 0 ? {} : { sticky: stickyPick },
39498
39613
  ...pool.preference === void 0 ? {} : { preference: pool.preference }
39499
39614
  });
39500
39615
  return { ranking, missing };
@@ -39561,6 +39676,7 @@ var init_poolPick = __esm({
39561
39676
  init_cliError();
39562
39677
  init_identity();
39563
39678
  init_schema();
39679
+ init_identityAccounts();
39564
39680
  init_account();
39565
39681
  init_pick();
39566
39682
  init_read();
@@ -67997,9 +68113,15 @@ function movedRows(facts) {
67997
68113
  }
67998
68114
  return [];
67999
68115
  }
68116
+ function identityText(facts) {
68117
+ if (facts.identity === void 0) {
68118
+ return "(none)";
68119
+ }
68120
+ return facts.identityLabel === void 0 ? facts.identity : `${facts.identity} (${facts.identityLabel})`;
68121
+ }
68000
68122
  function decisionRows(facts) {
68001
68123
  return [
68002
- { label: "identity", value: facts.identity ?? "(none)", detail: facts.identitySource },
68124
+ { label: "identity", value: identityText(facts), detail: facts.identitySource },
68003
68125
  ...facts.pool === void 0 ? [] : [
68004
68126
  {
68005
68127
  label: "pool",
@@ -70165,6 +70287,7 @@ function decideLaunch(params, readOnly) {
70165
70287
  return {
70166
70288
  parsedArgv,
70167
70289
  identityDecision,
70290
+ identityLabel: loadedIdentity?.config.label,
70168
70291
  poolPick,
70169
70292
  poolRanking,
70170
70293
  lastRanAs,
@@ -70195,6 +70318,7 @@ function prepareLaunch(params) {
70195
70318
  const rows = [
70196
70319
  ...decisionRows({
70197
70320
  identity: identityDecision.name,
70321
+ identityLabel: decided.identityLabel,
70198
70322
  identitySource: identityDecision.source,
70199
70323
  pool: poolPick,
70200
70324
  ranking: resolvedFlags.explain ? poolRanking : void 0,
@@ -116557,6 +116681,12 @@ function setIdentityProvider(paths, identityName, providerName) {
116557
116681
  }
116558
116682
  return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { provider: providerName });
116559
116683
  }
116684
+ function setIdentityLabel(paths, identityName, label2) {
116685
+ if (!identityExists(paths, identityName)) {
116686
+ throw new IdentityNotFoundError(identityName);
116687
+ }
116688
+ return applyPatch(identityJsonPath(paths, identityName), IdentitySchema, { label: label2 });
116689
+ }
116560
116690
  function setAllowAmbientCredential(paths, identityName, allow) {
116561
116691
  if (!identityExists(paths, identityName)) {
116562
116692
  throw new IdentityNotFoundError(identityName);
@@ -117701,80 +117831,12 @@ function registerProfileCommand(program2, deps) {
117701
117831
  }
117702
117832
 
117703
117833
  // src/identityManager.ts
117834
+ init_names();
117704
117835
  init_providersStore();
117705
117836
  init_identity();
117706
117837
  init_realPorts();
117707
-
117708
- // src/identityAccounts.ts
117709
- init_account();
117710
-
117711
- // src/usage/accountDescription.ts
117712
- function describeAccountOrganisation(account) {
117713
- const parts = [account.organizationName, account.organizationType].filter((part) => part !== void 0);
117714
- return parts.length === 0 ? void 0 : parts.join(", ");
117715
- }
117716
- function describeAccountPlan(account) {
117717
- const parts = [account.billingType === void 0 ? void 0 : `billing ${account.billingType}`, account.seatTier === void 0 ? void 0 : `seat tier ${account.seatTier}`].filter((part) => part !== void 0);
117718
- return parts.length === 0 ? void 0 : parts.join(", ");
117719
- }
117720
-
117721
- // src/identityAccounts.ts
117722
- function readIdentityAccount(fs31, identitiesDir, identity) {
117723
- try {
117724
- const account = readAccountMetadata(fs31, identitiesDir, identity);
117725
- return account === void 0 ? void 0 : { account };
117726
- } catch (error63) {
117727
- if (error63 instanceof AccountMetadataError) {
117728
- return { problem: error63.message };
117729
- }
117730
- throw error63;
117731
- }
117732
- }
117733
- function summariseIdentityAccount(account) {
117734
- const summary = {
117735
- ...account.emailAddress === void 0 ? {} : { emailAddress: account.emailAddress },
117736
- ...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
117737
- ...account.organizationType === void 0 ? {} : { organizationType: account.organizationType },
117738
- ...account.organizationRole === void 0 ? {} : { organizationRole: account.organizationRole },
117739
- ...account.billingType === void 0 ? {} : { billingType: account.billingType },
117740
- ...account.seatTier === void 0 ? {} : { seatTier: account.seatTier }
117741
- };
117742
- return Object.keys(summary).length === 0 ? void 0 : summary;
117743
- }
117744
- function loginKeyOf(account) {
117745
- return account.accountUuid === void 0 ? void 0 : JSON.stringify([account.accountUuid, account.organizationUuid ?? null]);
117746
- }
117747
- function collectByLogin(items, loginOf) {
117748
- const groups = /* @__PURE__ */ new Map();
117749
- for (const item of items) {
117750
- const key = loginOf(item) ?? /* @__PURE__ */ Symbol("no login");
117751
- const group = groups.get(key);
117752
- if (group === void 0) {
117753
- groups.set(key, [item]);
117754
- } else {
117755
- group.push(item);
117756
- }
117757
- }
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);
117765
- }
117766
- function describeAccountHolder(summary) {
117767
- const organisation = describeAccountOrganisation(summary);
117768
- if (summary.emailAddress === void 0) {
117769
- return organisation;
117770
- }
117771
- return organisation === void 0 ? summary.emailAddress : `${summary.emailAddress} (${organisation})`;
117772
- }
117773
- function describeIdentityAccount(summary) {
117774
- return [describeAccountHolder(summary), summary.organizationRole === void 0 ? void 0 : `role ${summary.organizationRole}`, describeAccountPlan(summary)].filter((part) => part !== void 0).join("; ");
117775
- }
117776
-
117777
- // src/identityManager.ts
117838
+ init_identityAccounts();
117839
+ init_accountDescription();
117778
117840
  async function runIdentityWizard(prompts, paths, name, options = {}) {
117779
117841
  if (!IdentitySchema.safeParse({ name, allowAmbientCredential: false }).success) {
117780
117842
  throw new InvalidIdentityNameError(name);
@@ -117845,6 +117907,7 @@ function toIdentityView(paths, { entry, read }, sharesLoginWith) {
117845
117907
  active: entry.isActive,
117846
117908
  directory: import_node_path25.default.join(paths.identitiesDir, entry.name),
117847
117909
  ...entry.problem === void 0 ? {
117910
+ ...entry.identity.label === void 0 ? {} : { label: entry.identity.label },
117848
117911
  ...entry.identity.defaultConfigProfile === void 0 ? {} : { defaultConfigProfile: entry.identity.defaultConfigProfile },
117849
117912
  ...entry.identity.provider === void 0 ? {} : { provider: entry.identity.provider },
117850
117913
  allowAmbientCredential: entry.identity.allowAmbientCredential,
@@ -117872,11 +117935,12 @@ function formatIdentityLine(entry, view) {
117872
117935
  if (entry.problem !== void 0) {
117873
117936
  return `${marker}${entry.name} [unreadable: ${entry.problem}]${account}${accountProblem}`;
117874
117937
  }
117938
+ const label2 = entry.identity.label === void 0 ? "" : ` (label: ${entry.identity.label})`;
117875
117939
  const defaultProfile = entry.identity.defaultConfigProfile === void 0 ? "" : ` (default profile: ${entry.identity.defaultConfigProfile})`;
117876
117940
  const provider = entry.identity.provider === void 0 ? "" : ` (provider: ${entry.identity.provider})`;
117877
117941
  const ambient = entry.identity.allowAmbientCredential ? " [allows ambient credential]" : "";
117878
117942
  const credential = entry.identity.credential === void 0 ? "" : ` [credential ${describeCredential(entry.identity.credential)}]`;
117879
- return `${marker}${entry.name}${defaultProfile}${provider}${ambient}${credential}${account}${accountProblem}`;
117943
+ return `${marker}${entry.name}${label2}${defaultProfile}${provider}${ambient}${credential}${account}${accountProblem}`;
117880
117944
  }
117881
117945
  function registerIdentityCommand(program2, deps) {
117882
117946
  const { paths } = deps;
@@ -117948,6 +118012,7 @@ function registerIdentityCommand(program2, deps) {
117948
118012
  console.log(`Unreadable: ${view.problem}`);
117949
118013
  return;
117950
118014
  }
118015
+ console.log(`Label: ${view.label ?? "(none)"}`);
117951
118016
  console.log(`Default configuration profile: ${view.defaultConfigProfile ?? "(none)"}`);
117952
118017
  console.log(`Provider: ${view.provider ?? "(none)"}`);
117953
118018
  console.log(`Allows ambient credential: ${view.allowAmbientCredential === true ? "yes" : "no"}`);
@@ -117956,7 +118021,7 @@ function registerIdentityCommand(program2, deps) {
117956
118021
  }),
117957
118022
  ["agent-shim identity show work", "agent-shim identity show work --json"]
117958
118023
  );
117959
- const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--json", "Print the result as JSON.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--provider <provider>", "Provider this identity exists for: a launch on it routes through the provider unless a flag, directory rule or profile selects another, and doctor expects no stored login.").option("--no-provider", "Clear this identity's provider.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
118024
+ const identitySet = identity.command("set <name>").description("Update an identity's settings.").option("--json", "Print the result as JSON.").option("--label <text>", "Free text shown beside this identity's name in `identity list` and `show`, the launch banner and the statusline. Display only: the name stays the key.").option("--no-label", "Clear this identity's label.").option("--default-profile <profile>", "Configuration profile this identity uses when nothing more specific selects one.").option("--no-default-profile", "Clear this identity's default configuration profile.").option("--provider <provider>", "Provider this identity exists for: a launch on it routes through the provider unless a flag, directory rule or profile selects another, and doctor expects no stored login.").option("--no-provider", "Clear this identity's provider.").option("--allow-ambient-credential", "Allow this identity to launch even with an ambient credential env var set.").option("--no-allow-ambient-credential", "Disallow ambient credential env vars for this identity (the default).").option(
117960
118025
  "--credential <source>",
117961
118026
  `Authenticate this identity from a token instead of its stored login, replacing any existing sources (repeatable, in the order tried): ${CREDENTIAL_SOURCE_SYNTAX}.`,
117962
118027
  collectCredentialSource
@@ -117976,16 +118041,25 @@ function registerIdentityCommand(program2, deps) {
117976
118041
  const { json: json2, ...options } = allOptions;
117977
118042
  if (Object.entries(allOptions).every(([key, value2]) => key === "json" || value2 === void 0)) {
117978
118043
  throw new UsageError(
117979
- "Nothing to change: pass --default-profile, --no-default-profile, --provider, --no-provider, --allow-ambient-credential, --no-allow-ambient-credential, --credential, --no-credential, --credential-target, --anchor, --no-anchor or a --credential-cache option."
118044
+ "Nothing to change: pass --label, --no-label, --default-profile, --no-default-profile, --provider, --no-provider, --allow-ambient-credential, --no-allow-ambient-credential, --credential, --no-credential, --credential-target, --anchor, --no-anchor or a --credential-cache option."
117980
118045
  );
117981
118046
  }
117982
118047
  if (!identityExists(paths, name)) {
117983
118048
  throw new IdentityNotFoundError(name);
117984
118049
  }
118050
+ const label2 = typeof options.label === "string" ? IdentityLabelSchema.safeParse(options.label) : void 0;
118051
+ if (label2?.success === false) {
118052
+ throw new UsageError(`Identity label ${JSON.stringify(options.label)} ${IDENTITY_LABEL_RULE}.`);
118053
+ }
117985
118054
  if (typeof options.provider === "string") {
117986
118055
  requireProviderExists(paths, options.provider);
117987
118056
  }
117988
118057
  const messages = [];
118058
+ if (options.label !== void 0) {
118059
+ const labelText = options.label === false ? void 0 : options.label;
118060
+ setIdentityLabel(paths, name, labelText);
118061
+ messages.push(labelText === void 0 ? `Identity "${name}" no longer has a label.` : `Identity "${name}" is now labelled ${JSON.stringify(labelText)}.`);
118062
+ }
117989
118063
  if (options.defaultProfile !== void 0) {
117990
118064
  const profileName = options.defaultProfile === false ? void 0 : options.defaultProfile;
117991
118065
  if (profileName !== void 0) {
@@ -118030,6 +118104,7 @@ function registerIdentityCommand(program2, deps) {
118030
118104
  });
118031
118105
  }),
118032
118106
  [
118107
+ 'agent-shim identity set work --label "Example Org"',
118033
118108
  "agent-shim identity set work --default-profile client-acme",
118034
118109
  "agent-shim identity set minimax --provider minimax",
118035
118110
  "agent-shim identity set work --allow-ambient-credential",
@@ -123234,6 +123309,7 @@ init_realPorts();
123234
123309
  var import_node_fs20 = __toESM(require("node:fs"), 1);
123235
123310
  var import_node_path47 = __toESM(require("node:path"), 1);
123236
123311
  init_signIn();
123312
+ init_identityAccounts();
123237
123313
  init_load();
123238
123314
  init_unrecognisedKeys();
123239
123315
  init_schema();
@@ -123508,6 +123584,27 @@ init_providerAliases();
123508
123584
  init_providersStore();
123509
123585
  init_guard();
123510
123586
  init_identity();
123587
+
123588
+ // src/poolSharedLogins.ts
123589
+ init_identityAccounts();
123590
+ init_identity();
123591
+ function sharedLoginMembers(members2, accountOf) {
123592
+ const known = members2.flatMap((identity) => {
123593
+ const account = poolNameOf(identity) === void 0 ? accountOf(identity) : void 0;
123594
+ return account === void 0 ? [] : [{ identity, account }];
123595
+ });
123596
+ return groupSharedLogins(known, (entry) => loginKeyOf(entry.account)).flatMap((group) => {
123597
+ const first = group[0];
123598
+ return first === void 0 ? [] : [{ members: group.map((entry) => entry.identity), account: first.account }];
123599
+ });
123600
+ }
123601
+ function describeSharedLoginMembers(poolName, shared) {
123602
+ const summary = summariseIdentityAccount(shared.account);
123603
+ const holder = summary === void 0 ? void 0 : describeAccountHolder(summary);
123604
+ return `Pool "${poolName}" lists ${joinNames(shared.members)}, which hold the same login${holder === void 0 ? "" : ` (${holder})`}, so they share one quota window. A launch ranks them as one candidate and never fails over from one to another; keep one of them in the pool.`;
123605
+ }
123606
+
123607
+ // src/doctorReport.ts
123511
123608
  init_extends();
123512
123609
  var DOCTOR_SEVERITIES = ["pass", "warn", "fail"];
123513
123610
  var DOCTOR_SECTIONS = [
@@ -123530,10 +123627,6 @@ var DOCTOR_SECTIONS = [
123530
123627
  "frontdoor",
123531
123628
  "models"
123532
123629
  ];
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
- }
123537
123630
  function refinePathShadow(status, ownExecutablePath, realpath) {
123538
123631
  if (status.status !== "shadowed") {
123539
123632
  return status;
@@ -123846,6 +123939,7 @@ function runDoctor(params) {
123846
123939
  }
123847
123940
  const validatedGlobalConfig = params.globalConfig.raw === void 0 ? void 0 : validateJson(GlobalConfigSchema, params.globalConfig);
123848
123941
  const pools = validatedGlobalConfig?.ok === true ? validatedGlobalConfig.data.pools ?? {} : {};
123942
+ const accountByIdentity = new Map(params.identities.flatMap((entry) => validIdentityNames.has(entry.name) && entry.account !== void 0 ? [[entry.name, entry.account]] : []));
123849
123943
  for (const [poolName, pool] of Object.entries(pools)) {
123850
123944
  const missing = pool.identities.filter((member) => poolNameOf(poolMemberIdentity(member)) === void 0 && !validIdentityNames.has(poolMemberIdentity(member)));
123851
123945
  const undefinedPools = pool.identities.flatMap((member) => {
@@ -123861,6 +123955,9 @@ function runDoctor(params) {
123861
123955
  ];
123862
123956
  push("pool", "fail", `Pool "${poolName}" names ${problems.join(" and ")}.`, poolName);
123863
123957
  }
123958
+ for (const shared of sharedLoginMembers(pool.identities.map(poolMemberIdentity), (identityName) => accountByIdentity.get(identityName))) {
123959
+ push("pool", "warn", describeSharedLoginMembers(poolName, shared), poolName);
123960
+ }
123864
123961
  }
123865
123962
  const reportedCycles = /* @__PURE__ */ new Set();
123866
123963
  for (const poolName of Object.keys(pools).sort()) {
@@ -133188,6 +133285,7 @@ init_providersStore();
133188
133285
  // src/frontdoor/configSchemas.ts
133189
133286
  init_zod();
133190
133287
  init_schema();
133288
+ init_names();
133191
133289
  init_statusline();
133192
133290
  var RemovalConfirmationSchema = external_exports.boolean();
133193
133291
  var CredentialWriteSchema = (targets) => external_exports.strictObject({
@@ -133213,6 +133311,7 @@ var mutationOutput = (kind, action) => external_exports.strictObject({ action: e
133213
133311
  var storedMutationOutput = (kind, action, value2) => external_exports.strictObject({ action: external_exports.literal(action), kind: external_exports.literal(kind), name: external_exports.string(), value: value2 });
133214
133312
  var IdentityValueSchema = external_exports.strictObject({
133215
133313
  name: external_exports.string(),
133314
+ label: external_exports.string().optional(),
133216
133315
  defaultConfigProfile: external_exports.string().optional(),
133217
133316
  provider: external_exports.string().optional(),
133218
133317
  allowAmbientCredential: external_exports.boolean(),
@@ -133233,6 +133332,8 @@ var ConfigNameInputSchema = external_exports.strictObject({ name: external_expor
133233
133332
  var ConfigIdentityAddOutputSchema = storedMutationOutput("identity", "created", IdentityValueSchema);
133234
133333
  var ConfigIdentitySetInputSchema = external_exports.strictObject({
133235
133334
  name: external_exports.string().min(1),
133335
+ /** The identity's display label; `false` clears it. */
133336
+ label: external_exports.union([IdentityLabelSchema, external_exports.literal(false)]).optional(),
133236
133337
  defaultConfigProfile: external_exports.union([external_exports.string().min(1), external_exports.literal(false)]).optional(),
133237
133338
  /** The provider the identity exists for; `false` clears it. */
133238
133339
  provider: external_exports.union([external_exports.string().min(1), external_exports.literal(false)]).optional(),
@@ -133379,6 +133480,7 @@ function identityValue(name, paths) {
133379
133480
  }
133380
133481
  return {
133381
133482
  name: identity.name,
133483
+ ...identity.label === void 0 ? {} : { label: identity.label },
133382
133484
  ...identity.defaultConfigProfile === void 0 ? {} : { defaultConfigProfile: identity.defaultConfigProfile },
133383
133485
  ...identity.provider === void 0 ? {} : { provider: identity.provider },
133384
133486
  allowAmbientCredential: identity.allowAmbientCredential,
@@ -133413,9 +133515,9 @@ function createConfigApiRouter(deps) {
133413
133515
  return { action: "created", kind: "identity", name: input2.name, value: identityValue(input2.name, paths) };
133414
133516
  })
133415
133517
  ),
133416
- set: route("/rest/config/identity/set", "Update an identity's default profile, provider, ambient-credential setting, credential or anchors").input(ConfigIdentitySetInputSchema).output(ConfigIdentitySetOutputSchema).handler(
133518
+ set: route("/rest/config/identity/set", "Update an identity's label, default profile, provider, ambient-credential setting, credential or anchors").input(ConfigIdentitySetInputSchema).output(ConfigIdentitySetOutputSchema).handler(
133417
133519
  ({ input: input2 }) => guarded(() => {
133418
- requireChange(input2.defaultConfigProfile !== void 0 || input2.provider !== void 0 || input2.allowAmbientCredential !== void 0 || input2.credential !== void 0 || input2.anchors !== void 0, "defaultConfigProfile, provider, allowAmbientCredential, credential or anchors");
133520
+ requireChange(input2.label !== void 0 || input2.defaultConfigProfile !== void 0 || input2.provider !== void 0 || input2.allowAmbientCredential !== void 0 || input2.credential !== void 0 || input2.anchors !== void 0, "label, defaultConfigProfile, provider, allowAmbientCredential, credential or anchors");
133419
133521
  if (!identityExists(paths, input2.name)) {
133420
133522
  throw new IdentityNotFoundError(input2.name);
133421
133523
  }
@@ -133425,6 +133527,9 @@ function createConfigApiRouter(deps) {
133425
133527
  if (typeof input2.provider === "string") {
133426
133528
  requireProviderExists(paths, input2.provider);
133427
133529
  }
133530
+ if (input2.label !== void 0) {
133531
+ setIdentityLabel(paths, input2.name, input2.label === false ? void 0 : input2.label);
133532
+ }
133428
133533
  if (input2.defaultConfigProfile !== void 0) {
133429
133534
  setDefaultConfigProfile(paths, input2.name, input2.defaultConfigProfile === false ? void 0 : input2.defaultConfigProfile);
133430
133535
  }
@@ -140963,6 +141068,7 @@ function enrichStatuslinePayload(params) {
140963
141068
  const account = clientEmail === void 0 ? params.account : void 0;
140964
141069
  let summary = {
140965
141070
  identity,
141071
+ identityLabel: params.identityLabel,
140966
141072
  provider: params.env.AGENT_SHIM_PROVIDER === void 0 || params.env.AGENT_SHIM_PROVIDER === "" ? void 0 : params.env.AGENT_SHIM_PROVIDER,
140967
141073
  providerAccountEmail: void 0,
140968
141074
  providerAccountSub: void 0,
@@ -142285,7 +142391,13 @@ function headroomSegment({ summary, t: t10 }) {
142285
142391
 
142286
142392
  // src/statusline/elements/header/identity.ts
142287
142393
  function identitySegment({ summary }) {
142288
- if (summary.identity !== void 0 && summary.provider !== void 0 && summary.identity !== summary.providerAccountEmail && summary.identity !== summary.loginAccountEmail) {
142394
+ if (summary.identity === void 0) {
142395
+ return void 0;
142396
+ }
142397
+ if (summary.identityLabel !== void 0) {
142398
+ return `${summary.identity} (${summary.identityLabel})`;
142399
+ }
142400
+ if (summary.provider !== void 0 && summary.identity !== summary.providerAccountEmail && summary.identity !== summary.loginAccountEmail) {
142289
142401
  return summary.identity;
142290
142402
  }
142291
142403
  return void 0;
@@ -145319,7 +145431,13 @@ function registerStatuslineCommand(program2, deps) {
145319
145431
  const identity = identityOf(process.env);
145320
145432
  let snapshot;
145321
145433
  let account;
145434
+ let identityLabel;
145322
145435
  if (identity !== void 0) {
145436
+ try {
145437
+ identityLabel = readIdentity(deps.paths, identity)?.label;
145438
+ } catch {
145439
+ identityLabel = void 0;
145440
+ }
145323
145441
  try {
145324
145442
  snapshot = readUsageSnapshot(realFarmFs, deps.paths.usageSnapshotsDir, identity);
145325
145443
  } catch {
@@ -145371,7 +145489,7 @@ function registerStatuslineCommand(program2, deps) {
145371
145489
  }
145372
145490
  }
145373
145491
  const clientPayload = parseClientPayload(payload);
145374
- const enrichParams = { payload, env: process.env, snapshot, account, providerName, nowMs, headroom, routedTarget, ...modelChoice === void 0 ? {} : { modelChoice } };
145492
+ const enrichParams = { payload, env: process.env, identityLabel, snapshot, account, providerName, nowMs, headroom, routedTarget, ...modelChoice === void 0 ? {} : { modelChoice } };
145375
145493
  const renderer = process.env.AGENT_SHIM_STATUSLINE_CMD;
145376
145494
  if (renderer !== void 0 && renderer !== "") {
145377
145495
  const child = (0, import_node_child_process9.spawn)("/bin/sh", ["-c", renderer], { stdio: ["pipe", "inherit", "inherit"] });
@@ -156376,6 +156494,7 @@ init_schema();
156376
156494
  init_realPorts();
156377
156495
  init_candidateText();
156378
156496
  init_preflight();
156497
+ init_identityAccounts();
156379
156498
  function formatPoolPick(report, nowMs) {
156380
156499
  const head = report.pick === void 0 ? `Pool ${report.pool}: every member is refused right now${report.earliestReturn === void 0 ? "" : `; ${report.earliestReturn.identity} returns at ${report.earliestReturn.at} (in ${formatAge(Date.parse(report.earliestReturn.at) - nowMs)})`}` : `Pool ${report.pool}: a launch from ${report.directory} would run as ${report.pick}`;
156381
156500
  const rows = report.candidates.map((candidate, index) => {
@@ -156384,6 +156503,11 @@ function formatPoolPick(report, nowMs) {
156384
156503
  });
156385
156504
  return [head, ...rows, ...report.missing.map((name) => ` (skipped: ${name} is not an identity)`), ...report.stickyProblem === void 0 ? [] : [`Note: ${report.stickyProblem}; the last-pick record was ignored.`]];
156386
156505
  }
156506
+ function warnSharedLogins(paths, poolName, members2) {
156507
+ for (const shared of sharedLoginMembers(members2, (identity) => readIdentityAccount(realFsPort, paths.identitiesDir, identity)?.account)) {
156508
+ console.error(`agent-shim: warning: ${describeSharedLoginMembers(poolName, shared)}`);
156509
+ }
156510
+ }
156387
156511
  function policyNotes(entry) {
156388
156512
  const notes = [
156389
156513
  ...entry?.preference === void 0 ? [] : [`preference: ${entry.preference}`],
@@ -156416,6 +156540,7 @@ function registerPoolCommand(program2, deps) {
156416
156540
  reportMutation(options.json, { action: "created", kind: "pool", name, value: created }, () => {
156417
156541
  console.log(`Created pool "${name}" with ${members2.join(", ")}.`);
156418
156542
  });
156543
+ warnSharedLogins(paths, name, created.identities.map(poolMemberIdentity));
156419
156544
  }),
156420
156545
  ["agent-shim pool add subs --identity work --identity personal", "agent-shim pool add client --identity client-main --identity spare --preference listed"]
156421
156546
  );
@@ -156433,6 +156558,7 @@ function registerPoolCommand(program2, deps) {
156433
156558
  reportMutation(options.json, { action: "updated", kind: "pool", name, value: updated }, () => {
156434
156559
  console.log(`Pool "${name}" now has ${updated.identities.map(poolMemberIdentity).join(", ")}.`);
156435
156560
  });
156561
+ warnSharedLogins(paths, name, updated.identities.map(poolMemberIdentity));
156436
156562
  }),
156437
156563
  ["agent-shim pool set subs --identity work --identity personal --identity spare", "agent-shim pool set subs --preference listed", "agent-shim pool set subs --resume-on-limit --compact-before-limit --compact-at-utilisation 85"]
156438
156564
  );
@@ -157481,6 +157607,7 @@ var import_node_path70 = __toESM(require("node:path"), 1);
157481
157607
  init_commandDeps();
157482
157608
  init_realPorts();
157483
157609
  init_account();
157610
+ init_accountDescription();
157484
157611
  init_signIn();
157485
157612
  init_resets();
157486
157613
  init_read();
@@ -157839,6 +157966,7 @@ function promptsIdentityPicker(prompts) {
157839
157966
  // src/launcher/runPicker.ts
157840
157967
  init_cliError();
157841
157968
  init_schema();
157969
+ init_identityAccounts();
157842
157970
  init_account();
157843
157971
  init_candidateText();
157844
157972
  init_pick();