claudish 14.5.2 → 14.6.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.
Files changed (2) hide show
  1. package/dist/index.js +253 -48
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -4266,6 +4266,67 @@ var init_zod = __esm(() => {
4266
4266
  init_external();
4267
4267
  });
4268
4268
 
4269
+ // src/auth/accounts/account-label.ts
4270
+ function cleanPlan(plan) {
4271
+ const cleaned = (plan ?? "").toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, PLAN_MAX);
4272
+ return cleaned || undefined;
4273
+ }
4274
+ function labelTaken(taken, label) {
4275
+ const wanted = label.toLowerCase();
4276
+ for (const existing of taken)
4277
+ if (existing.toLowerCase() === wanted)
4278
+ return true;
4279
+ return false;
4280
+ }
4281
+ function emailMatches(rowEmail, selector) {
4282
+ return rowEmail !== undefined && rowEmail.toLowerCase() === selector.toLowerCase();
4283
+ }
4284
+ function shellWord(label) {
4285
+ if (/^[\w@+.-]+$/.test(label))
4286
+ return label;
4287
+ return `'${label.replace(/'/g, `'\\''`)}'`;
4288
+ }
4289
+ function baseOf(input) {
4290
+ const fromEmail = (input.email ?? "").trim().toLowerCase().replace(/[^a-z0-9_@+.-]/g, "_").replace(/^\W+/, "");
4291
+ if (fromEmail)
4292
+ return fromEmail.slice(0, ACCOUNT_LABEL_MAX);
4293
+ return (input.chatgptAccountId ?? "").toLowerCase().replace(/[^a-z0-9_-]/g, "").replace(/^\W+/, "").slice(0, 8);
4294
+ }
4295
+ function fit(base, suffix) {
4296
+ return base.slice(0, ACCOUNT_LABEL_MAX - suffix.length).replace(/[.\-+@]+$/, "") + suffix;
4297
+ }
4298
+ function deriveAccountLabel(input) {
4299
+ const base = baseOf(input);
4300
+ if (!base)
4301
+ throw new Error("The login carries no email or account id; pass --label.");
4302
+ const taken = new Set;
4303
+ for (const label of input.taken)
4304
+ taken.add(label.toLowerCase());
4305
+ const plan = cleanPlan(input.plan);
4306
+ const planSuffix = plan ? `-${plan}` : "";
4307
+ function* candidates() {
4308
+ yield fit(base, "");
4309
+ if (plan)
4310
+ yield fit(base, planSuffix);
4311
+ for (let n = 2;n <= NUMBER_MAX; n++)
4312
+ yield fit(base, `${planSuffix}-${n}`);
4313
+ }
4314
+ for (const candidate of candidates()) {
4315
+ if (taken.has(candidate.toLowerCase()))
4316
+ continue;
4317
+ if (!ACCOUNT_LABEL_PATTERN.test(candidate)) {
4318
+ throw new Error(`The derived label "${candidate}" is not valid; pass --label.`);
4319
+ }
4320
+ return candidate;
4321
+ }
4322
+ throw new Error("Could not derive an unused label; pass --label.");
4323
+ }
4324
+ var ACCOUNT_LABEL_MAX = 64, ACCOUNT_LABEL_PATTERN, ACCOUNT_LABEL_RULE, PLAN_MAX = 16, NUMBER_MAX = 999;
4325
+ var init_account_label = __esm(() => {
4326
+ ACCOUNT_LABEL_PATTERN = /^\w[\w@+. -]{0,63}$/;
4327
+ ACCOUNT_LABEL_RULE = "An account label starts with a letter, digit or underscore and has at most 64 letters, " + "digits, spaces, and the characters _ @ + . -";
4328
+ });
4329
+
4269
4330
  // src/auth/quota/codex-window-slots.ts
4270
4331
  var CODEX_WINDOW_SLOTS;
4271
4332
  var init_codex_window_slots = __esm(() => {
@@ -4326,6 +4387,7 @@ function requireWireApiForTransport(transport, wire, ctx) {
4326
4387
  var CustomEndpointAuthSchemeSchema, WireValueSchema, WirePointerSchema, WireAssignmentSchema, CustomEndpointWireSchema, CustomEndpointSimpleSchema, CustomEndpointComplexSchema, CustomEndpointSchema, PredefinedEndpointsConfigSchema, AccountIdentitySchema, AccountStoreKindSchema, SubscriptionAccountSchema, CodexWindowSlotSchema, SubscriptionAccountsEntrySchema, SubscriptionAccountsSchema;
4327
4388
  var init_config_schema = __esm(() => {
4328
4389
  init_zod();
4390
+ init_account_label();
4329
4391
  init_codex_window_slots();
4330
4392
  CustomEndpointAuthSchemeSchema = _enum(["bearer", "x-api-key", "none"]);
4331
4393
  WireValueSchema = union([string2(), number2(), boolean2()]);
@@ -4392,7 +4454,7 @@ var init_config_schema = __esm(() => {
4392
4454
  AccountStoreKindSchema = _enum(["keychain", "file", "legacy-codex-file"]);
4393
4455
  SubscriptionAccountSchema = object({
4394
4456
  id: string2().regex(/^[a-z0-9]{12}$/, "account ids are generated, 12 lowercase alphanumerics"),
4395
- label: string2().regex(/^[\w][\w .-]{0,31}$/, "labels are 1-32 word characters, . - or space"),
4457
+ label: string2().regex(ACCOUNT_LABEL_PATTERN, ACCOUNT_LABEL_RULE),
4396
4458
  identity: AccountIdentitySchema.optional(),
4397
4459
  store: AccountStoreKindSchema,
4398
4460
  enabled: boolean2().optional(),
@@ -4586,12 +4648,12 @@ function isAccountProvider(value) {
4586
4648
  function sameAccount(a, b) {
4587
4649
  return a.provider === b.provider && a.id === b.id;
4588
4650
  }
4589
- var ACCOUNT_PROVIDERS, DEFAULT_SWITCH_AT = 0.9, ACCOUNT_ID_PATTERN, LEGACY_CODEX_ACCOUNT_ID = "legacy-codex", ACCOUNT_LABEL_PATTERN;
4651
+ var ACCOUNT_PROVIDERS, DEFAULT_SWITCH_AT = 0.9, ACCOUNT_ID_PATTERN, LEGACY_CODEX_ACCOUNT_ID = "legacy-codex";
4590
4652
  var init_types = __esm(() => {
4591
4653
  init_errors3();
4654
+ init_account_label();
4592
4655
  ACCOUNT_PROVIDERS = ["native-anthropic", "openai-codex"];
4593
4656
  ACCOUNT_ID_PATTERN = /^[a-z0-9]{12}$/;
4594
- ACCOUNT_LABEL_PATTERN = /^[\w][\w .-]{0,31}$/;
4595
4657
  });
4596
4658
 
4597
4659
  // src/auth/accounts/config-validation.ts
@@ -5332,7 +5394,7 @@ var init_onepassword_config = __esm(() => {
5332
5394
  });
5333
5395
 
5334
5396
  // src/version.ts
5335
- var VERSION = "14.5.2";
5397
+ var VERSION = "14.6.0";
5336
5398
 
5337
5399
  // src/redact.ts
5338
5400
  function isPlaceholder(value) {
@@ -24516,7 +24578,7 @@ function vendorName(provider) {
24516
24578
  }
24517
24579
  function loginCommandFor(ref) {
24518
24580
  const vendor = ref.provider === "openai-codex" ? "codex" : "claude";
24519
- return `claudish account login ${vendor} ${ref.label}`;
24581
+ return `claudish account login ${vendor} ${shellWord(ref.label)}`;
24520
24582
  }
24521
24583
  function rethrowSelectionFailure(err) {
24522
24584
  if (classifyConnectionError(err) || isAccountsError(err))
@@ -24927,6 +24989,7 @@ var LEASE_FAILURE_DEPRIORITIZE_MS = 60000, AccountChangedDuringLeaseError, pools
24927
24989
  var init_pool = __esm(() => {
24928
24990
  init_connection_error();
24929
24991
  init_logger();
24992
+ init_account_label();
24930
24993
  init_allowance_service();
24931
24994
  init_exchange();
24932
24995
  init_paths();
@@ -43758,7 +43821,7 @@ function assertLegacyCodexLogoutPermitted(configPath = activeConfigFile()) {
43758
43821
  const rows = codexEntry(raw)?.accounts ?? [];
43759
43822
  if (rows.length > 0) {
43760
43823
  const legacy = rows.find((row) => row.store === "legacy-codex-file");
43761
- throw new Error(legacy ? `The legacy Codex login is account ${legacy.label}. Remove it with ` + `\`claudish account remove codex ${legacy.label} --delete-file\`.` : "Codex accounts are managed by `claudish account`; remove one with " + "`claudish account remove codex <label>`.");
43824
+ throw new Error(legacy ? `The legacy Codex login is account ${legacy.label}. Remove it with ` + `\`claudish account remove codex ${shellWord(legacy.label)} --delete-file\`.` : "Codex accounts are managed by `claudish account`; remove one with " + "`claudish account remove codex <label>`.");
43762
43825
  }
43763
43826
  return { config: raw, result: undefined };
43764
43827
  });
@@ -43783,10 +43846,23 @@ async function saveLegacyCodexLogin(write, opts = {}) {
43783
43846
  }
43784
43847
  }
43785
43848
  }
43786
- function display(identity) {
43849
+ function display(identity, label) {
43787
43850
  const raw = identity.email || identity.chatgptAccountId || "identity unavailable";
43788
- const shown = [...raw].filter((ch) => ch.charCodeAt(0) > 31 && ch !== "\x7F").join("");
43789
- return identity.plan ? `${shown}, ${identity.plan}` : shown;
43851
+ const shown = [...raw].filter((ch) => {
43852
+ const code = ch.charCodeAt(0);
43853
+ return code > 31 && !(code >= 127 && code <= 159);
43854
+ }).join("");
43855
+ const text = identity.email && label.toLowerCase() === identity.email.toLowerCase() ? "" : shown;
43856
+ if (!identity.plan)
43857
+ return text;
43858
+ return text ? `${text}, ${identity.plan}` : identity.plan;
43859
+ }
43860
+ function note(identity, label) {
43861
+ const text = display(identity, label);
43862
+ return text ? ` (${text})` : "";
43863
+ }
43864
+ function named(row) {
43865
+ return row.legacy ? `${row.label} (legacy login)` : `${row.label} (id ${row.id})`;
43790
43866
  }
43791
43867
  function stableIdentity(identity) {
43792
43868
  const stable = identity.chatgptAccountId?.trim();
@@ -43796,8 +43872,16 @@ function stableIdentity(identity) {
43796
43872
  return stable;
43797
43873
  }
43798
43874
  function validLabel(label) {
43799
- if (!ACCOUNT_LABEL_PATTERN.test(label)) {
43800
- throw new Error("An account label starts with a letter, digit or underscore and has at most 32 letters, " + "digits, spaces, dots, underscores or hyphens.");
43875
+ if (!ACCOUNT_LABEL_PATTERN.test(label))
43876
+ throw new Error(ACCOUNT_LABEL_RULE);
43877
+ }
43878
+ function validSelector(selector) {
43879
+ const control = [...selector].some((ch) => {
43880
+ const code = ch.charCodeAt(0);
43881
+ return code <= 31 || code >= 127 && code <= 159;
43882
+ });
43883
+ if (selector.length < 1 || selector.length > 254 || control) {
43884
+ throw new Error("An account selector is a label or an email: 1 to 254 characters, no control characters.");
43801
43885
  }
43802
43886
  }
43803
43887
  function asRecord(row) {
@@ -43848,16 +43932,41 @@ class CodexAccountManagement {
43848
43932
  result: read(codexEntry(raw) ?? { generation: 0, accounts: [] })
43849
43933
  }));
43850
43934
  }
43851
- resolve(entry, target) {
43935
+ rowsOf(entry) {
43852
43936
  const rows = entry.accounts.map(asRecord);
43853
43937
  const legacy = this.synthesizedLegacy(entry);
43854
43938
  if (legacy)
43855
43939
  rows.push(legacy);
43940
+ return rows;
43941
+ }
43942
+ resolve(entry, target) {
43943
+ const rows = this.rowsOf(entry);
43856
43944
  const matches = rows.filter((row) => (target.id === undefined || row.id === target.id) && (target.label === undefined || row.label === target.label));
43857
43945
  if (matches.length === 1)
43858
43946
  return matches[0];
43859
43947
  throw new Error(matches.length > 1 ? "Ambiguous Codex account; name it with --id <generated-id>." : "No such Codex account; run `claudish account list`.");
43860
43948
  }
43949
+ select(entry, target) {
43950
+ const rows = this.rowsOf(entry).filter((row) => target.id === undefined || row.id === target.id);
43951
+ const { selector } = target;
43952
+ if (selector === undefined) {
43953
+ if (rows.length === 1)
43954
+ return rows[0];
43955
+ throw new Error("No such Codex account; run `claudish account list`.");
43956
+ }
43957
+ const exact = rows.filter((row) => row.label === selector);
43958
+ if (exact.length === 1)
43959
+ return exact[0];
43960
+ if (exact.length > 1) {
43961
+ throw new Error("Ambiguous Codex account; name it with --id <generated-id>.");
43962
+ }
43963
+ const byEmail = rows.filter((row) => emailMatches(row.identity.email, selector));
43964
+ if (byEmail.length === 1)
43965
+ return byEmail[0];
43966
+ if (byEmail.length === 0)
43967
+ throw new Error("No such Codex account; run `claudish account list`.");
43968
+ throw new Error(`More than one Codex account has the email ${selector}: ${byEmail.map(named).join(", ")}. ` + "Name one by label, or pass --id <generated-id>." + (byEmail.some((row) => row.legacy) ? " The legacy login has no --id; name it by its label." : ""));
43969
+ }
43861
43970
  newId(entry, reserved) {
43862
43971
  for (let attempt = 0;attempt < 16; attempt++) {
43863
43972
  const id = this.deps.newId?.() ?? randomBytes8(6).toString("hex");
@@ -43869,21 +43978,39 @@ class CodexAccountManagement {
43869
43978
  throw new Error("Could not generate an unused account id.");
43870
43979
  }
43871
43980
  refuseDuplicate(entry, identity, label, except) {
43981
+ const rows = this.rowsOf(entry);
43982
+ for (const row of rows) {
43983
+ if (row.id !== except && row.label === label) {
43984
+ throw new Error(`A Codex account labelled ${label} already exists.`);
43985
+ }
43986
+ }
43987
+ this.refuseIdentity(rows, identity, except);
43988
+ }
43989
+ refuseIdentity(rows, identity, except) {
43872
43990
  const stable = stableIdentity(identity);
43873
- const rows = entry.accounts.map(asRecord);
43874
- const legacy = this.synthesizedLegacy(entry);
43875
- if (legacy)
43876
- rows.push(legacy);
43877
43991
  for (const row of rows) {
43878
43992
  if (row.id === except)
43879
43993
  continue;
43880
- if (row.label === label)
43881
- throw new Error(`A Codex account labelled ${label} already exists.`);
43882
43994
  if (row.identity.chatgptAccountId === stable) {
43883
43995
  throw new Error(`This Codex identity is already registered as ${row.label}.`);
43884
43996
  }
43885
43997
  }
43886
43998
  }
43999
+ chooseLabel(rows, identity, explicit) {
44000
+ const taken = rows.map((row) => row.label);
44001
+ if (explicit !== undefined) {
44002
+ if (labelTaken(taken, explicit)) {
44003
+ throw new Error(`A Codex account labelled ${explicit} already exists.`);
44004
+ }
44005
+ return explicit;
44006
+ }
44007
+ return deriveAccountLabel({
44008
+ email: identity.email,
44009
+ plan: identity.plan,
44010
+ chatgptAccountId: identity.chatgptAccountId,
44011
+ taken
44012
+ });
44013
+ }
43887
44014
  store(kind) {
43888
44015
  return this.deps.store?.(kind) ?? storeFor(kind, this.env);
43889
44016
  }
@@ -43936,30 +44063,35 @@ class CodexAccountManagement {
43936
44063
  };
43937
44064
  }
43938
44065
  async run(command) {
43939
- if (command.label !== undefined)
43940
- validLabel(command.label);
43941
- if (command.verb === "add")
44066
+ if (command.verb === "add") {
44067
+ if (command.label !== undefined)
44068
+ validLabel(command.label);
43942
44069
  return this.add(command.label);
44070
+ }
44071
+ if (command.verb === "rename")
44072
+ validLabel(command.newLabel);
43943
44073
  if (command.id !== undefined && !ACCOUNT_ID_PATTERN.test(command.id)) {
43944
44074
  throw new Error("--id takes a generated account id, as `claudish account list --json` shows.");
43945
44075
  }
43946
- if (command.label === undefined && command.id === undefined) {
43947
- throw new Error("Name the Codex account by label, or with --id <generated-id>.");
44076
+ if (command.selector === undefined && command.id === undefined) {
44077
+ throw new Error("Name the Codex account by label or email, or with --id <generated-id>.");
43948
44078
  }
43949
- const target = this.inspect((entry) => this.resolve(entry, command));
44079
+ if (command.selector !== undefined)
44080
+ validSelector(command.selector);
44081
+ const target = this.inspect((entry) => this.select(entry, command));
43950
44082
  if (command.verb === "login")
43951
44083
  return this.login(target);
44084
+ if (command.verb === "rename")
44085
+ return this.rename(target, command.newLabel);
43952
44086
  if (command.verb === "remove")
43953
44087
  return this.remove(target, command);
43954
44088
  return this.setEnabled(target, command.verb === "enable");
43955
44089
  }
43956
44090
  async add(requestedLabel) {
43957
- const label = requestedLabel ?? await (this.deps.label ?? (() => dist_default3({ message: "Codex account label:" })))();
43958
- validLabel(label);
43959
44091
  const initial = this.inspect((entry) => {
43960
44092
  const legacy = this.synthesizedLegacy(entry);
43961
- if (entry.accounts.some((row) => row.label === label) || legacy?.label === label) {
43962
- throw new Error(`A Codex account labelled ${label} already exists.`);
44093
+ if (requestedLabel !== undefined && labelTaken(this.rowsOf(entry).map((r) => r.label), requestedLabel)) {
44094
+ throw new Error(`A Codex account labelled ${requestedLabel} already exists.`);
43963
44095
  }
43964
44096
  if (legacy && !legacy.identity.chatgptAccountId) {
43965
44097
  throw new Error("The legacy Codex login records no account id, so enrolling it cannot be checked for duplicates. Run `claudish login codex` once to refresh it, then retry.");
@@ -43971,26 +44103,29 @@ class CodexAccountManagement {
43971
44103
  }
43972
44104
  const acceptedAt = new Date(this.now()).toISOString();
43973
44105
  if (initial.legacy) {
43974
- this.print(`This add also enrolls your working legacy login as ${initial.legacy.label} (${display(initial.legacy.identity)}), store legacy-codex-file. Its credential file stays where it is and is not copied.`);
44106
+ this.print(`This add also enrolls your working legacy login as ${initial.legacy.label}${note(initial.legacy.identity, initial.legacy.label)}, store legacy-codex-file. Its credential file stays where it is and is not copied.`);
43975
44107
  }
43976
44108
  const store = this.deps.newStore?.() ?? defaultAccountStore(this.env);
43977
- this.print(`New Codex account ${label}; store: ${store.kind}.` + (store.kind === "file" ? " Warning: its tokens are kept in plaintext files readable only by you (0600)." : ""));
44109
+ this.print(`New Codex account${requestedLabel === undefined ? "" : ` ${requestedLabel}`}; store: ${store.kind}.` + (store.kind === "file" ? " Warning: its tokens are kept in plaintext files readable only by you (0600)." : ""));
43978
44110
  const result = await (this.deps.pkce ?? loginCodexPkce)();
43979
44111
  stableIdentity(result.identity);
43980
44112
  const row = {
43981
44113
  id: initial.id,
43982
- label,
44114
+ label: "",
43983
44115
  identity: result.identity,
43984
44116
  store: store.kind,
43985
44117
  enabled: true,
43986
44118
  addedAt: new Date(this.now()).toISOString()
43987
44119
  };
43988
44120
  const record = asRecord(row);
44121
+ let finalLabel = "";
43989
44122
  await this.gate(record, async (holds) => {
43990
44123
  this.inspect((entry) => {
43991
44124
  if (entry.accounts.some((a) => a.id === row.id))
43992
44125
  throw new Error("The generated account id is taken; retry the add.");
43993
- this.refuseDuplicate(entry, result.identity, label);
44126
+ const rows = this.rowsOf(entry);
44127
+ this.refuseIdentity(rows, result.identity);
44128
+ this.chooseLabel(rows, result.identity, requestedLabel);
43994
44129
  });
43995
44130
  if (await store.read(record, { uncached: true })) {
43996
44131
  throw new Error("A token item already exists for the generated account id; retry the add.");
@@ -44003,8 +44138,10 @@ class CodexAccountManagement {
44003
44138
  this.fence(holds);
44004
44139
  if (entry.accounts.some((a) => a.id === row.id))
44005
44140
  throw new Error("The generated account id is taken; retry the add.");
44006
- this.refuseDuplicate(entry, result.identity, label);
44007
- const legacy = this.synthesizedLegacy(entry);
44141
+ const rows = this.rowsOf(entry);
44142
+ this.refuseIdentity(rows, result.identity);
44143
+ const label = this.chooseLabel(rows, result.identity, requestedLabel);
44144
+ const legacy = rows.find((r) => r.legacy);
44008
44145
  if (legacy) {
44009
44146
  if (legacy.identity.chatgptAccountId !== initial.legacy?.identity.chatgptAccountId) {
44010
44147
  throw new Error("The legacy Codex login changed during the add; retry to confirm its enrollment.");
@@ -44018,15 +44155,18 @@ class CodexAccountManagement {
44018
44155
  addedAt: legacy.addedAt
44019
44156
  });
44020
44157
  }
44021
- entry.accounts.push(row);
44158
+ entry.accounts.push({ ...row, label });
44022
44159
  entry.noticeAcceptedAt ??= acceptedAt;
44160
+ finalLabel = label;
44023
44161
  });
44024
44162
  } catch (error) {
44025
44163
  await this.cleanupUnpublished(store, record, error);
44026
44164
  }
44027
44165
  });
44028
44166
  await this.invalidate();
44029
- return [`Added Codex account ${label} (${display(result.identity)}); store: ${store.kind}.`];
44167
+ return [
44168
+ `Added Codex account ${finalLabel}${note(result.identity, finalLabel)}; store: ${store.kind}.`
44169
+ ];
44030
44170
  }
44031
44171
  async cleanupUnpublished(store, record, error) {
44032
44172
  let published;
@@ -44052,9 +44192,9 @@ class CodexAccountManagement {
44052
44192
  else
44053
44193
  await (await Promise.resolve().then(() => (init_codex_oauth(), exports_codex_oauth))).CodexOAuth.getInstance().login();
44054
44194
  await this.invalidate();
44055
- return [`Codex account ${target.label}: login saved.`];
44195
+ return [`Codex account ${named(target)}: login saved.`];
44056
44196
  }
44057
- this.print(`Logging in Codex account ${target.label}; store: ${target.store}.`);
44197
+ this.print(`Logging in Codex account ${named(target)}; store: ${target.store}.`);
44058
44198
  const result = await (this.deps.pkce ?? loginCodexPkce)();
44059
44199
  const stable = stableIdentity(result.identity);
44060
44200
  if (target.identity.chatgptAccountId && stable !== target.identity.chatgptAccountId) {
@@ -44081,12 +44221,12 @@ class CodexAccountManagement {
44081
44221
  });
44082
44222
  await this.invalidate();
44083
44223
  return [
44084
- `Codex account ${target.label} (${display(result.identity)}): login saved; store: ${target.store}. A recorded needs-login mark clears on the account's next use.`
44224
+ `Codex account ${named(target)}${note(result.identity, target.label)}: login saved; store: ${target.store}. A recorded needs-login mark clears on the account's next use.`
44085
44225
  ];
44086
44226
  }
44087
44227
  async setEnabled(target, enabled) {
44088
44228
  if (target.legacy) {
44089
- this.print(`This enrolls your legacy login ${target.label} (${display(target.identity)}) as a configured account, store legacy-codex-file; its credential file stays where it is.`);
44229
+ this.print(`This enrolls your legacy login ${named(target)}${note(target.identity, target.label)} as a configured account, store legacy-codex-file; its credential file stays where it is.`);
44090
44230
  }
44091
44231
  let changed = false;
44092
44232
  let poolDisabled = false;
@@ -44115,7 +44255,7 @@ class CodexAccountManagement {
44115
44255
  if (changed)
44116
44256
  await this.invalidate();
44117
44257
  const lines = [
44118
- `Codex account ${target.label} ${changed ? "" : "is already "}${enabled ? "enabled" : "disabled"}.`
44258
+ `Codex account ${named(target)} ${changed ? "" : "is already "}${enabled ? "enabled" : "disabled"}.`
44119
44259
  ];
44120
44260
  if (!enabled) {
44121
44261
  lines.push("Configured Codex accounts still reserve the subscription: with no enabled account, Codex requests fail visibly instead of using OPENAI_CODEX_API_KEY.");
@@ -44125,6 +44265,52 @@ class CodexAccountManagement {
44125
44265
  }
44126
44266
  return lines;
44127
44267
  }
44268
+ async rename(target, newLabel) {
44269
+ if (target.legacy) {
44270
+ if (target.label === newLabel) {
44271
+ return [`Codex account ${named(target)} is already labelled ${newLabel}.`];
44272
+ }
44273
+ if (!target.identity.chatgptAccountId) {
44274
+ throw new Error("The legacy Codex login records no account id, so enrolling it cannot be checked for duplicates. Run `claudish login codex` once to refresh it, then retry.");
44275
+ }
44276
+ }
44277
+ let changed = false;
44278
+ await this.gate(target, async (holds) => {
44279
+ changed = mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
44280
+ this.fence(holds);
44281
+ const current = this.resolve(entry, target.legacy ? { label: target.label } : { id: target.id });
44282
+ sameTarget(current, target);
44283
+ if (current.label === newLabel)
44284
+ return;
44285
+ const others = this.rowsOf(entry).filter((row) => row.id !== current.id).map((row) => row.label);
44286
+ if (labelTaken(others, newLabel)) {
44287
+ throw new Error(`A Codex account labelled ${newLabel} already exists.`);
44288
+ }
44289
+ if (current.legacy) {
44290
+ entry.accounts.push({
44291
+ id: this.newId(entry),
44292
+ label: newLabel,
44293
+ identity: current.identity,
44294
+ store: "legacy-codex-file",
44295
+ enabled: true,
44296
+ addedAt: current.addedAt
44297
+ });
44298
+ return;
44299
+ }
44300
+ const row = entry.accounts.find((a) => a.id === target.id);
44301
+ if (row)
44302
+ row.label = newLabel;
44303
+ });
44304
+ });
44305
+ if (changed)
44306
+ await this.invalidate();
44307
+ if (changed && target.legacy) {
44308
+ this.print(`This enrolled your legacy login ${named(target)}${note(target.identity, target.label)} as a configured account, store legacy-codex-file; its credential file stays where it is.`);
44309
+ }
44310
+ return [
44311
+ changed ? `Renamed Codex account ${named(target)} to ${newLabel}.` : `Codex account ${named(target)} is already labelled ${newLabel}.`
44312
+ ];
44313
+ }
44128
44314
  async remove(target, command) {
44129
44315
  const legacyFile = target.store === "legacy-codex-file";
44130
44316
  if (legacyFile && !command.deleteFile) {
@@ -44133,7 +44319,7 @@ class CodexAccountManagement {
44133
44319
  if (!legacyFile && command.deleteFile) {
44134
44320
  throw new Error(`--delete-file applies only to the legacy Codex login, not ${target.label}.`);
44135
44321
  }
44136
- if (!command.yes && !await this.ask(`Remove Codex account ${target.label} (${display(target.identity)})${legacyFile ? " and delete its legacy credential file" : ""}? Requests already in flight cannot be recalled.`)) {
44322
+ if (!command.yes && !await this.ask(`Remove Codex account ${named(target)}${note(target.identity, target.label)}${legacyFile ? " and delete its legacy credential file" : ""}? Requests already in flight cannot be recalled.`)) {
44137
44323
  return ["Cancelled; no account was removed."];
44138
44324
  }
44139
44325
  const warnings = [];
@@ -44175,7 +44361,7 @@ class CodexAccountManagement {
44175
44361
  key: "OPENAI_CODEX_API_KEY if one is configured, otherwise none"
44176
44362
  }[next];
44177
44363
  return [
44178
- `Removed Codex account ${target.label} (${display(target.identity)}). Next Codex credential for new requests: ${nextText}.`,
44364
+ `Removed Codex account ${named(target)}${note(target.identity, target.label)}. Next Codex credential for new requests: ${nextText}.`,
44179
44365
  ...next === "key" ? ["Warning: new Codex requests may now use OPENAI_CODEX_API_KEY, billed per token."] : [],
44180
44366
  ...warnings
44181
44367
  ];
@@ -44189,6 +44375,7 @@ var init_management = __esm(() => {
44189
44375
  init_dist12();
44190
44376
  init_profile_config();
44191
44377
  init_codex_pkce();
44378
+ init_account_label();
44192
44379
  init_config_lock();
44193
44380
  init_file_lock();
44194
44381
  init_paths();
@@ -89664,10 +89851,12 @@ ${h("SUBCOMMANDS")}
89664
89851
  ${bold("Subscription accounts:")}
89665
89852
  ${green("claudish account list")} Configured subscription accounts, per claudish provider
89666
89853
  ${green("claudish account status")} ${yellow("[--json]")} Usage per window, thresholds and blockers
89667
- ${green("claudish account add codex")} ${yellow("[--label <label>]")} Log in a Codex account you own and add it
89854
+ ${green("claudish account add codex")} ${yellow("[--label <label>]")} Log in a Codex account you own and add it ${dim("(label defaults to the login's email)")}
89668
89855
  ${green("claudish account login codex")} ${yellow("<label>")} Log the same Codex identity in again
89669
89856
  ${green("claudish account enable|disable codex")} ${yellow("<label>")} Include or skip it in selection
89857
+ ${green("claudish account rename codex")} ${yellow("<label> <new-label>")} Give an account a new label
89670
89858
  ${green("claudish account remove codex")} ${yellow("<label> [--yes] [--delete-file]")}
89859
+ ${dim("A <label> may also be the account's email.")}
89671
89860
  ${dim("--id <generated-id> may replace or confirm a label. Removing the legacy login (default)")}
89672
89861
  ${dim("requires --delete-file. New accounts use the macOS Keychain, or protected plaintext files")}
89673
89862
  ${dim("where there is none. No token input or export; Claude account mutations are not available.")}
@@ -104220,7 +104409,7 @@ function renderRow2(row, pool, now) {
104220
104409
  lines.push(` ${warn2(formatReset(row.blocker.resetAt, now, row.blocker.estimated === true))}`);
104221
104410
  }
104222
104411
  if (row.blocker?.kind === "needs-reauth") {
104223
- lines.push(` ${bad2("needs login")}: run \`claudish account login ${vendorOf(row.ref.provider)} ${row.record.label}\``);
104412
+ lines.push(` ${bad2("needs login")}: run \`claudish account login ${vendorOf(row.ref.provider)} ${shellWord(row.record.label)}\``);
104224
104413
  if (row.state.needsReauth?.lastReason) {
104225
104414
  lines.push(` ${dim4(row.state.needsReauth.lastReason)}`);
104226
104415
  }
@@ -104295,7 +104484,7 @@ ${strong2(vendorOf(report.provider))}`);
104295
104484
  if (!any) {
104296
104485
  console.log(`
104297
104486
  No subscription accounts are configured.`);
104298
- console.log(dim4("A Codex login (`claudish login codex`) is shown here as `default (legacy file)` " + "once it exists; add an account with `claudish account add codex --label <label>`."));
104487
+ console.log(dim4("A Codex login (`claudish login codex`) is shown here as `default (legacy file)` " + "once it exists; add an account with `claudish account add codex`."));
104299
104488
  }
104300
104489
  console.log("");
104301
104490
  }
@@ -104378,12 +104567,24 @@ function parseAccountMutation(args) {
104378
104567
  throw usage2("account add codex takes no positional argument; use --label.");
104379
104568
  return { verb: v, label: values.get("--label") };
104380
104569
  }
104570
+ if (v === "rename") {
104571
+ const hasId = values.has("--id");
104572
+ if (positional.length === 2 || positional.length === 1 && hasId) {
104573
+ return {
104574
+ verb: v,
104575
+ selector: positional.length === 2 ? positional[0] : undefined,
104576
+ id: values.get("--id"),
104577
+ newLabel: positional[positional.length - 1]
104578
+ };
104579
+ }
104580
+ throw usage2("account rename codex needs <label> <new-label>, or --id <generated-id> <new-label>.");
104581
+ }
104381
104582
  if (positional.length > 1)
104382
104583
  throw usage2(`account ${v} codex names one account.`);
104383
104584
  if (positional.length === 0 && !values.has("--id")) {
104384
104585
  throw usage2(`account ${v} codex needs a label or --id <generated-id>.`);
104385
104586
  }
104386
- const target = { label: positional[0], id: values.get("--id") };
104587
+ const target = { selector: positional[0], id: values.get("--id") };
104387
104588
  if (v === "remove") {
104388
104589
  return { verb: v, ...target, yes: flags.has("--yes"), deleteFile: flags.has("--delete-file") };
104389
104590
  }
@@ -104488,8 +104689,11 @@ var ACCOUNT_HELP = `Usage:
104488
104689
  claudish account forecast [--json] [--horizon <h|d>] [--provider codex]
104489
104690
  claudish account add codex [--label <label>]
104490
104691
  claudish account login|enable|disable codex <label> [--id <generated-id>]
104692
+ claudish account rename codex <label> <new-label> [--id <generated-id>]
104491
104693
  claudish account remove codex <label> [--id <generated-id>] [--yes] [--delete-file]
104492
104694
 
104695
+ --label on add is optional and overrides the default, the login's email.
104696
+ A command that takes <label> also takes the account's email.
104493
104697
  --id <generated-id> may replace or confirm the label; labels are unique per claudish provider.
104494
104698
  Removing the legacy login (default, legacy-codex-file) requires --delete-file.
104495
104699
  New accounts use the macOS Keychain, or protected plaintext files where there is none.
@@ -104503,13 +104707,14 @@ The allowance trajectory, alerts and sessions of a claudish provider's account p
104503
104707
  --horizon <h|d> how far ahead to project, 1h to 7d (default 7d)
104504
104708
  --provider codex the claudish provider (openai-codex); the only one with a model today`;
104505
104709
  var init_account_command = __esm(() => {
104710
+ init_account_label();
104506
104711
  init_policy2();
104507
104712
  init_pool();
104508
104713
  init_registry();
104509
104714
  init_state_store();
104510
104715
  init_types();
104511
104716
  init_ansi();
104512
- MUTATION_VERBS = ["add", "login", "enable", "disable", "remove"];
104717
+ MUTATION_VERBS = ["add", "login", "enable", "disable", "remove", "rename"];
104513
104718
  });
104514
104719
 
104515
104720
  // src/auth/antigravity-oauth.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "14.5.2",
3
+ "version": "14.6.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -71,10 +71,10 @@
71
71
  "ai"
72
72
  ],
73
73
  "optionalDependencies": {
74
- "@claudish/magmux-darwin-arm64": "14.5.2",
75
- "@claudish/magmux-darwin-x64": "14.5.2",
76
- "@claudish/magmux-linux-arm64": "14.5.2",
77
- "@claudish/magmux-linux-x64": "14.5.2"
74
+ "@claudish/magmux-darwin-arm64": "14.6.0",
75
+ "@claudish/magmux-darwin-x64": "14.6.0",
76
+ "@claudish/magmux-linux-arm64": "14.6.0",
77
+ "@claudish/magmux-linux-x64": "14.6.0"
78
78
  },
79
79
  "author": "Jack Rudenko <i@madappgang.com>",
80
80
  "license": "MIT",