claudish 14.13.0 → 14.14.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 +1784 -1063
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -5491,7 +5491,7 @@ var init_onepassword_config = __esm(() => {
5491
5491
  });
5492
5492
 
5493
5493
  // src/version.ts
5494
- var VERSION = "14.13.0";
5494
+ var VERSION = "14.14.0";
5495
5495
 
5496
5496
  // src/redact.ts
5497
5497
  function isPlaceholder(value) {
@@ -21696,6 +21696,19 @@ class AccountRegistry {
21696
21696
  this.legacyStamp = undefined;
21697
21697
  return false;
21698
21698
  }
21699
+ if (this.configStatus.state === "failed" && this.lastGoodSettings !== undefined) {
21700
+ const path = this.legacyPath();
21701
+ const detail = "not re-read while the account list config cannot be read";
21702
+ this.legacyStamp = undefined;
21703
+ this.legacyStatus = {
21704
+ state: "failed",
21705
+ error: detail,
21706
+ failure: { source: "legacy", path, type: "io", detail },
21707
+ path,
21708
+ servingLastKnownGood: this.lastGoodLegacy !== undefined
21709
+ };
21710
+ return true;
21711
+ }
21699
21712
  this.refreshLegacy();
21700
21713
  return true;
21701
21714
  }
@@ -21756,10 +21769,7 @@ class AccountRegistry {
21756
21769
  }
21757
21770
  }
21758
21771
  isSynthesizedLegacyOnly() {
21759
- const snapshot = this.snapshot("openai-codex");
21760
- const rows = snapshot.accounts["openai-codex"];
21761
- const entry = snapshot.settings["openai-codex"];
21762
- return !entry?.legacyCodexSuppressed && !entry?.accounts.length && rows.length === 1 && rows[0].legacy;
21772
+ return isSynthesizedLegacyOnlySnapshot(this.snapshot("openai-codex"));
21763
21773
  }
21764
21774
  listAccounts(provider) {
21765
21775
  return this.snapshot(provider).accounts[provider];
@@ -21781,6 +21791,18 @@ class AccountRegistry {
21781
21791
  return { config: this.configStatus, legacy: this.legacyStatus };
21782
21792
  }
21783
21793
  }
21794
+ function isSynthesizedLegacyOnlySnapshot(snapshot) {
21795
+ const rows = snapshot.accounts["openai-codex"];
21796
+ const entry = snapshot.settings["openai-codex"];
21797
+ return !entry?.legacyCodexSuppressed && !entry?.accounts.length && rows.length === 1 && rows[0].legacy;
21798
+ }
21799
+ function codexSourceFailed(snapshot) {
21800
+ if (snapshot.config.state === "failed")
21801
+ return snapshot.config;
21802
+ if (snapshot.legacy.state === "failed")
21803
+ return snapshot.legacy;
21804
+ return;
21805
+ }
21784
21806
  function getAccountRegistry() {
21785
21807
  if (!registry2)
21786
21808
  registry2 = new AccountRegistry;
@@ -24428,6 +24450,10 @@ var init_keychain = __esm(() => {
24428
24450
  // src/auth/accounts/token-store.ts
24429
24451
  import { createHash as createHash2 } from "crypto";
24430
24452
  import { existsSync as existsSync9, lstatSync as lstatSync2, readFileSync as readFileSync14, rmSync as rmSync4 } from "fs";
24453
+ import { isDeepStrictEqual } from "util";
24454
+ function sameDurableRecord(a, b) {
24455
+ return a.accessToken === b.accessToken && a.refreshToken === b.refreshToken && a.expiresAt === b.expiresAt && a.accountId === b.accountId && a.generation === b.generation && a.fingerprint === b.fingerprint && isDeepStrictEqual(a.journal ?? null, b.journal ?? null);
24456
+ }
24431
24457
  function fingerprintOf(refreshToken) {
24432
24458
  return createHash2("sha256").update(refreshToken).digest("hex").slice(0, 16);
24433
24459
  }
@@ -24435,8 +24461,8 @@ function parseRecord(kind, text) {
24435
24461
  let raw;
24436
24462
  try {
24437
24463
  raw = JSON.parse(text);
24438
- } catch (err) {
24439
- throw new TokenStoreReadError(kind, `the stored record is not JSON (${err.message})`);
24464
+ } catch {
24465
+ throw new TokenStoreReadError(kind, "the stored record is not JSON (invalid JSON)");
24440
24466
  }
24441
24467
  if (typeof raw?.accessToken !== "string" || typeof raw?.refreshToken !== "string") {
24442
24468
  throw new TokenStoreReadError(kind, "the stored record has no token pair");
@@ -24598,8 +24624,8 @@ class LegacyCodexFileStore {
24598
24624
  let raw;
24599
24625
  try {
24600
24626
  raw = JSON.parse(text);
24601
- } catch (err) {
24602
- throw new TokenStoreReadError(this.kind, err instanceof Error ? err.message : String(err));
24627
+ } catch {
24628
+ throw new TokenStoreReadError(this.kind, "invalid JSON");
24603
24629
  }
24604
24630
  if (!raw?.access_token || !raw?.refresh_token) {
24605
24631
  throw new TokenStoreReadError(this.kind, "the credential file has no token pair");
@@ -24765,6 +24791,7 @@ class TokenManager {
24765
24791
  async resolve(ref, forced, rejectedFp) {
24766
24792
  const current = await this.readOrThrow(ref, { uncached: forced });
24767
24793
  if (!current) {
24794
+ this.validateAccount?.(ref, "store-empty");
24768
24795
  throw new NeedsReauthError(`No stored credential for ${ref.label}. Run a login for this account.`);
24769
24796
  }
24770
24797
  await this.healIfMaterialMoved(ref, current);
@@ -24849,6 +24876,7 @@ class TokenManager {
24849
24876
  this.validateAccount?.(ref, "before-exchange");
24850
24877
  let record = await this.readOrThrow(ref, { uncached: true });
24851
24878
  if (!record) {
24879
+ this.validateAccount?.(ref, "store-empty");
24852
24880
  throw new NeedsReauthError(`No stored credential for ${ref.label}. Run a login for this account.`);
24853
24881
  }
24854
24882
  await this.healIfMaterialMoved(ref, record);
@@ -24920,6 +24948,7 @@ class TokenManager {
24920
24948
  this.validateAccount?.(ref, "before-persist");
24921
24949
  const latest = await this.readOrThrow(ref, { uncached: true });
24922
24950
  if (!latest) {
24951
+ this.validateAccount?.(ref, "store-empty");
24923
24952
  throw new NeedsReauthError(`The stored credential for ${ref.label} was removed during refresh.`);
24924
24953
  }
24925
24954
  if (latest.generation > record.generation || latest.fingerprint !== record.fingerprint) {
@@ -25266,6 +25295,64 @@ function tokenOptionsFor(opts, reason) {
25266
25295
  ...opts.rejectedAccessToken ? { rejectedAccessToken: opts.rejectedAccessToken } : {}
25267
25296
  };
25268
25297
  }
25298
+
25299
+ class ReauthAttemptStamps {
25300
+ committed = new Map;
25301
+ inFlight = new Map;
25302
+ get(id) {
25303
+ let latest = this.committed.get(id);
25304
+ for (const mark of this.inFlight.get(id) ?? []) {
25305
+ if (latest === undefined || mark.at > latest)
25306
+ latest = mark.at;
25307
+ }
25308
+ return latest;
25309
+ }
25310
+ snapshot() {
25311
+ const out = new Map;
25312
+ for (const id of new Set([...this.committed.keys(), ...this.inFlight.keys()])) {
25313
+ const at = this.get(id);
25314
+ if (at !== undefined)
25315
+ out.set(id, at);
25316
+ }
25317
+ return out;
25318
+ }
25319
+ begin(id, at) {
25320
+ const mark = { at };
25321
+ let marks = this.inFlight.get(id);
25322
+ if (!marks) {
25323
+ marks = new Set;
25324
+ this.inFlight.set(id, marks);
25325
+ }
25326
+ marks.add(mark);
25327
+ const withdraw = () => {
25328
+ const current = this.inFlight.get(id);
25329
+ if (!current?.delete(mark))
25330
+ return false;
25331
+ if (current.size === 0)
25332
+ this.inFlight.delete(id);
25333
+ return true;
25334
+ };
25335
+ return {
25336
+ commit: () => {
25337
+ if (!withdraw())
25338
+ return;
25339
+ const previous = this.committed.get(id);
25340
+ if (previous === undefined || at > previous)
25341
+ this.committed.set(id, at);
25342
+ },
25343
+ restore: () => {
25344
+ withdraw();
25345
+ }
25346
+ };
25347
+ }
25348
+ clear(id) {
25349
+ this.committed.delete(id);
25350
+ this.inFlight.delete(id);
25351
+ }
25352
+ }
25353
+ function isStoreOnlyMove(leased, current) {
25354
+ return current !== undefined && current.id === leased.id && current.enabled && current.store !== leased.store && current.identity.chatgptAccountId === leased.identity.chatgptAccountId;
25355
+ }
25269
25356
  function leaseFailureBlocker(ref, err) {
25270
25357
  if (err instanceof AccountChangedDuringLeaseError) {
25271
25358
  return {
@@ -25317,7 +25404,7 @@ class AccountPool {
25317
25404
  storeFactory;
25318
25405
  env;
25319
25406
  managers = new Map;
25320
- lastReauthAttempt = new Map;
25407
+ lastReauthAttempt = new ReauthAttemptStamps;
25321
25408
  leaseFailures = new Map;
25322
25409
  constructor(provider, opts = {}) {
25323
25410
  this.provider = provider;
@@ -25341,7 +25428,7 @@ class AccountPool {
25341
25428
  provider: this.provider,
25342
25429
  failed: new Map,
25343
25430
  leaseFailures: new Map(this.leaseFailures),
25344
- reauthAttempts: new Map(this.lastReauthAttempt)
25431
+ reauthAttempts: this.lastReauthAttempt.snapshot()
25345
25432
  };
25346
25433
  if (allowance && this.allowanceStrategyOn()) {
25347
25434
  try {
@@ -25421,9 +25508,12 @@ class AccountPool {
25421
25508
  await this.healReauthMarks(context, opts.pin);
25422
25509
  evaluation = this.evaluate(opts, context);
25423
25510
  const attempts = evaluation.records.length + 1;
25511
+ let storeMovesLeft = evaluation.records.length + 1;
25512
+ let fresh = true;
25424
25513
  for (let attempt = 0;attempt < attempts; attempt++) {
25425
- if (attempt > 0)
25514
+ if (!fresh)
25426
25515
  evaluation = this.evaluate(opts, context);
25516
+ fresh = false;
25427
25517
  const selection = evaluation.selection;
25428
25518
  if (selection.kind === "none")
25429
25519
  throw this.acquireNoEligibleError(evaluation, context, opts);
@@ -25439,23 +25529,15 @@ class AccountPool {
25439
25529
  continue;
25440
25530
  }
25441
25531
  const at = this.now();
25442
- this.lastReauthAttempt.set(record.id, at);
25443
- context.reauthAttempts.set(record.id, at);
25444
- const token = await this.tryLease(record, opts, reason, context);
25445
- if (!token)
25446
- continue;
25447
- const stillConfigured = this.accounts().some((a) => a.id === record.id && a.enabled && a.store === record.store);
25448
- if (!stillConfigured) {
25449
- context.failed.set(record.id, {
25450
- kind: "unavailable",
25451
- ref: candidate.ref,
25452
- reason: "account changed while leasing",
25453
- upstreamStatus: 503
25454
- });
25455
- if (opts.pin)
25456
- throw new NoUsableAccountError(`Pinned ${vendorName(this.provider)} account ${record.label} changed while leasing.`);
25532
+ const step = await this.leaseStep(record, candidate.ref, reason, at, context, opts);
25533
+ if (step === STORE_MOVED) {
25534
+ if (storeMovesLeft-- > 0)
25535
+ attempt--;
25457
25536
  continue;
25458
25537
  }
25538
+ if (!step)
25539
+ continue;
25540
+ const token = step;
25459
25541
  this.leaseFailures.delete(record.id);
25460
25542
  context.leaseFailures.delete(record.id);
25461
25543
  const threadKey = evaluation.ranked ? context.allowance?.threadKey ?? null : null;
@@ -25490,6 +25572,95 @@ class AccountPool {
25490
25572
  }
25491
25573
  return error;
25492
25574
  }
25575
+ noteLeaseChanged(record, ref, rows, context, opts) {
25576
+ if (isStoreOnlyMove(record, rows.find((a) => a.id === record.id))) {
25577
+ if (opts.pin)
25578
+ throw new AccountStoreMovedDuringLeaseError(record.label);
25579
+ log(`[accounts] ${this.provider}: ${record.label} moved store while leasing; leasing it again`);
25580
+ return true;
25581
+ }
25582
+ context.failed.set(record.id, {
25583
+ kind: "unavailable",
25584
+ ref,
25585
+ reason: "account changed while leasing",
25586
+ upstreamStatus: 503
25587
+ });
25588
+ if (opts.pin)
25589
+ throw new NoUsableAccountError(`Pinned ${vendorName(this.provider)} account ${record.label} changed while leasing.`);
25590
+ return false;
25591
+ }
25592
+ async leaseStep(record, ref, reason, at, context, opts) {
25593
+ const stamps = this.stampReauthAttempt(record.id, at, context);
25594
+ let restored = false;
25595
+ const restore = () => {
25596
+ restored = true;
25597
+ stamps.restore();
25598
+ };
25599
+ try {
25600
+ const token = await this.tryLease(record, opts, reason, context);
25601
+ if (token === STORE_MOVED) {
25602
+ restore();
25603
+ return STORE_MOVED;
25604
+ }
25605
+ if (token === NOT_ATTEMPTED) {
25606
+ restore();
25607
+ return null;
25608
+ }
25609
+ if (!token)
25610
+ return null;
25611
+ const rows = this.accounts();
25612
+ const stillConfigured = rows.some((a) => a.id === record.id && a.enabled && a.store === record.store);
25613
+ if (stillConfigured)
25614
+ return token;
25615
+ if (!this.noteLeaseChanged(record, ref, rows, context, opts))
25616
+ return null;
25617
+ restore();
25618
+ return STORE_MOVED;
25619
+ } catch (err) {
25620
+ if (err instanceof AccountStoreMovedDuringLeaseError || err instanceof AccountListUnreadableDuringLeaseError) {
25621
+ restore();
25622
+ }
25623
+ throw err;
25624
+ } finally {
25625
+ if (!restored)
25626
+ stamps.commit();
25627
+ }
25628
+ }
25629
+ stampReauthAttempt(id, at, context) {
25630
+ const pool = this.lastReauthAttempt.begin(id, at);
25631
+ const request = context.reauthAttempts.get(id);
25632
+ context.reauthAttempts.set(id, at);
25633
+ return {
25634
+ restore: () => {
25635
+ pool.restore();
25636
+ if (request === undefined)
25637
+ context.reauthAttempts.delete(id);
25638
+ else
25639
+ context.reauthAttempts.set(id, request);
25640
+ },
25641
+ commit: () => pool.commit()
25642
+ };
25643
+ }
25644
+ unattemptedLease(record, ref, reason, context, err) {
25645
+ if (err instanceof AccountStoreMovedDuringLeaseError) {
25646
+ if (reason === "pinned")
25647
+ throw err;
25648
+ log(`[accounts] ${this.provider}: ${record.label} moved store while leasing; leasing it again`);
25649
+ return STORE_MOVED;
25650
+ }
25651
+ if (!(err instanceof AccountListUnreadableDuringLeaseError))
25652
+ return;
25653
+ context.failed.set(record.id, {
25654
+ kind: "unavailable",
25655
+ ref,
25656
+ reason: err.reason,
25657
+ upstreamStatus: err.upstreamStatus
25658
+ });
25659
+ if (reason === "pinned")
25660
+ throw err;
25661
+ log(`[accounts] ${this.provider}: ${record.label}'s account list could not be read; trying the next account`);
25662
+ return NOT_ATTEMPTED;
25663
+ }
25493
25664
  async tryLease(record, opts, reason, context) {
25494
25665
  const ref = this.refOf(record);
25495
25666
  try {
@@ -25497,6 +25668,9 @@ class AccountPool {
25497
25668
  } catch (err) {
25498
25669
  if (classifyConnectionError(err))
25499
25670
  throw err;
25671
+ const unattempted = this.unattemptedLease(record, ref, reason, context, err);
25672
+ if (unattempted)
25673
+ return unattempted;
25500
25674
  const blocker = leaseFailureBlocker(ref, err);
25501
25675
  if (!blocker)
25502
25676
  throw err;
@@ -25517,8 +25691,15 @@ class AccountPool {
25517
25691
  continue;
25518
25692
  if (!this.state.stateFor(ref).state.needsReauth)
25519
25693
  continue;
25520
- if (await this.managerFor(record).healNeedsReauth(ref)) {
25521
- this.lastReauthAttempt.delete(record.id);
25694
+ let healed = await this.managerFor(record).healNeedsReauth(ref);
25695
+ if (!healed) {
25696
+ const current = this.accounts().find((a) => a.id === record.id);
25697
+ if (current && isStoreOnlyMove(record, current)) {
25698
+ healed = await this.managerFor(current).healNeedsReauth(this.refOf(current));
25699
+ }
25700
+ }
25701
+ if (healed) {
25702
+ this.lastReauthAttempt.clear(record.id);
25522
25703
  context.reauthAttempts.delete(record.id);
25523
25704
  }
25524
25705
  }
@@ -25571,6 +25752,36 @@ class AccountPool {
25571
25752
  }
25572
25753
  return new NoUsableAccountError(buildUnusableMessage(input), blockers);
25573
25754
  }
25755
+ validateLeasedRow(record, phase) {
25756
+ const legacyExchange = phase === "before-exchange" && record.store === "legacy-codex-file";
25757
+ const reread = phase === "store-empty" || legacyExchange;
25758
+ if (reread)
25759
+ this.registry.invalidate();
25760
+ let snapshot;
25761
+ try {
25762
+ snapshot = this.registry.snapshot(this.provider);
25763
+ } catch (err) {
25764
+ if (phase === "before-persist")
25765
+ return;
25766
+ throw err;
25767
+ }
25768
+ const failedSource = reread ? this.failedSourceOf(snapshot) : undefined;
25769
+ if (failedSource) {
25770
+ throw new AccountListUnreadableDuringLeaseError(record.label, failedSource.path, legacyExchange ? "its legacy file token is about to be exchanged" : "its store is empty");
25771
+ }
25772
+ const current = snapshot.accounts[this.provider].find((row) => row.id === record.id);
25773
+ if (isStoreOnlyMove(record, current))
25774
+ throw new AccountStoreMovedDuringLeaseError(record.label);
25775
+ const rebound = !current || current.store !== record.store || current.identity.chatgptAccountId !== record.identity.chatgptAccountId;
25776
+ if (rebound || phase !== "before-persist" && !current.enabled) {
25777
+ throw new AccountChangedDuringLeaseError(`Account ${record.label} was removed, disabled or rebound during refresh.`);
25778
+ }
25779
+ }
25780
+ failedSourceOf(snapshot) {
25781
+ if (this.provider === "openai-codex")
25782
+ return codexSourceFailed(snapshot);
25783
+ return snapshot.config.state === "failed" ? snapshot.config : undefined;
25784
+ }
25574
25785
  managerFor(record) {
25575
25786
  const key = `${record.id}:${record.store}:${record.identity.chatgptAccountId ?? ""}`;
25576
25787
  const existing = this.managers.get(key);
@@ -25580,21 +25791,7 @@ class AccountPool {
25580
25791
  store: this.storeFactory(record),
25581
25792
  exchange: exchangeFor(this.provider),
25582
25793
  lockPathFor: () => accountRefreshLockPath(record, this.env),
25583
- validateAccount: (_ref, phase) => {
25584
- let rows;
25585
- try {
25586
- rows = this.registry.snapshot(this.provider).accounts[this.provider];
25587
- } catch (err) {
25588
- if (phase === "before-persist")
25589
- return;
25590
- throw err;
25591
- }
25592
- const current = rows.find((row) => row.id === record.id);
25593
- const rebound = !current || current.store !== record.store || current.identity.chatgptAccountId !== record.identity.chatgptAccountId;
25594
- if (rebound || phase === "before-exchange" && !current.enabled) {
25595
- throw new AccountChangedDuringLeaseError(`Account ${record.label} was removed, disabled or rebound during refresh.`);
25596
- }
25597
- },
25794
+ validateAccount: (_ref, phase) => this.validateLeasedRow(record, phase),
25598
25795
  state: this.state,
25599
25796
  now: this.now,
25600
25797
  env: this.env
@@ -25708,7 +25905,7 @@ function getAccountPool(provider) {
25708
25905
  pools.set(provider, pool);
25709
25906
  return pool;
25710
25907
  }
25711
- var LEASE_FAILURE_DEPRIORITIZE_MS = 60000, AccountChangedDuringLeaseError, pools;
25908
+ var LEASE_FAILURE_DEPRIORITIZE_MS = 60000, AccountChangedDuringLeaseError, AccountStoreMovedDuringLeaseError, AccountListUnreadableDuringLeaseError, STORE_MOVED, NOT_ATTEMPTED, pools;
25712
25909
  var init_pool = __esm(() => {
25713
25910
  init_connection_error();
25714
25911
  init_logger();
@@ -25724,6 +25921,18 @@ var init_pool = __esm(() => {
25724
25921
  init_types();
25725
25922
  AccountChangedDuringLeaseError = class AccountChangedDuringLeaseError extends NoUsableAccountError {
25726
25923
  };
25924
+ AccountStoreMovedDuringLeaseError = class AccountStoreMovedDuringLeaseError extends RefreshUnavailableError {
25925
+ constructor(label) {
25926
+ super("account store moved while leasing", `${label} now reads from its new store; retry`);
25927
+ }
25928
+ };
25929
+ AccountListUnreadableDuringLeaseError = class AccountListUnreadableDuringLeaseError extends RefreshUnavailableError {
25930
+ constructor(label, configPath, what = "its store is empty") {
25931
+ super("account list could not be read", `${label}: ${what} and ${configPath} could not be read; retry`);
25932
+ }
25933
+ };
25934
+ STORE_MOVED = Symbol("store-moved");
25935
+ NOT_ATTEMPTED = Symbol("not-attempted");
25727
25936
  pools = new Map;
25728
25937
  });
25729
25938
 
@@ -25962,11 +26171,7 @@ var init_codex = __esm(() => {
25962
26171
  return { kind: "headers" };
25963
26172
  },
25964
26173
  isAvailable() {
25965
- try {
25966
- return existsSync10(credentialsPath());
25967
- } catch {
25968
- return false;
25969
- }
26174
+ return getAccountRegistry().mayHaveConfiguredAccounts("openai-codex");
25970
26175
  },
25971
26176
  scrape(response) {
25972
26177
  return scrapeCodexHeaders(response.headers);
@@ -42073,188 +42278,6 @@ var init_billing_probe = __esm(() => {
42073
42278
  };
42074
42279
  });
42075
42280
 
42076
- // src/auth/codex-pkce.ts
42077
- import { exec as exec3 } from "child_process";
42078
- import { createHash as createHash12, randomBytes as randomBytes7 } from "crypto";
42079
- import { createServer } from "http";
42080
- import { promisify as promisify3 } from "util";
42081
- function loginClaims(idToken) {
42082
- try {
42083
- if (!idToken || idToken.split(".").length !== 3)
42084
- return { identity: {} };
42085
- const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString("utf-8"));
42086
- const auth = payload["https://api.openai.com/auth"];
42087
- const stable = auth?.chatgpt_account_id || payload.chatgpt_account_id;
42088
- const text = (value) => typeof value === "string" && value.trim() ? value : undefined;
42089
- const wireId = stable || auth?.user_id;
42090
- return {
42091
- accountId: wireId ? wireId : undefined,
42092
- identity: {
42093
- chatgptAccountId: text(stable),
42094
- email: text(payload.email),
42095
- plan: text(auth?.chatgpt_plan_type)
42096
- }
42097
- };
42098
- } catch {
42099
- return { identity: {} };
42100
- }
42101
- }
42102
- function buildAuthUrl(challenge, state, redirectUri) {
42103
- const params = [
42104
- "response_type=code",
42105
- `client_id=${encodeURIComponent(CODEX_OAUTH_CONFIG.clientId)}`,
42106
- `redirect_uri=${encodeURIComponent(redirectUri)}`,
42107
- `scope=${CODEX_OAUTH_CONFIG.scopes.join("+")}`,
42108
- `code_challenge=${encodeURIComponent(challenge)}`,
42109
- "code_challenge_method=S256",
42110
- "id_token_add_organizations=true",
42111
- "codex_cli_simplified_flow=true",
42112
- `state=${encodeURIComponent(state)}`,
42113
- "originator=opencode"
42114
- ].join("&");
42115
- return `${CODEX_OAUTH_CONFIG.authUrl}?${params}`;
42116
- }
42117
- async function openBrowser(url) {
42118
- const execAsync = promisify3(exec3);
42119
- try {
42120
- if (process.platform === "darwin")
42121
- await execAsync(`open "${url}"`);
42122
- else if (process.platform === "win32")
42123
- await execAsync(`start "${url}"`);
42124
- else
42125
- await execAsync(`xdg-open "${url}"`);
42126
- console.log(`
42127
- Opening browser for OpenAI authentication...`);
42128
- console.log(`If the browser doesn't open, visit this URL:
42129
- ${url}
42130
- `);
42131
- } catch {
42132
- console.log(`
42133
- Please open this URL in your browser to authenticate:`);
42134
- console.log(url);
42135
- console.log("");
42136
- }
42137
- }
42138
- function waitForCallback(options, deps) {
42139
- return new Promise((resolve, reject) => {
42140
- let redirectUri = "";
42141
- const schedule = deps.setTimeout ?? setTimeout;
42142
- const cancel = deps.clearTimeout ?? clearTimeout;
42143
- const finish = (error, result) => {
42144
- if (timer !== undefined)
42145
- cancel(timer);
42146
- server.close();
42147
- if (error)
42148
- reject(error);
42149
- else if (result)
42150
- resolve(result);
42151
- };
42152
- const server = createServer((req, res) => {
42153
- const url = new URL(req.url, redirectUri.replace("/auth/callback", ""));
42154
- if (url.pathname !== "/auth/callback") {
42155
- res.writeHead(404, { "Content-Type": "text/plain" });
42156
- res.end("Not found");
42157
- return;
42158
- }
42159
- const code = url.searchParams.get("code");
42160
- const state = url.searchParams.get("state");
42161
- const error = url.searchParams.get("error");
42162
- if (error || !state || state !== options.state || !code) {
42163
- const detail = error ? "OAuth authorization was refused." : !state || state !== options.state ? "Invalid state parameter. Possible CSRF attack." : "No authorization code received.";
42164
- res.writeHead(400, { "Content-Type": "text/html" });
42165
- res.end(`<html><body><h1>Authentication Failed</h1><p>${detail}</p><p>You can close this window.</p></body></html>`);
42166
- finish(new Error(error ? "OAuth authorization was refused" : !state || state !== options.state ? "Invalid OAuth state parameter (CSRF protection)" : "No authorization code received"));
42167
- return;
42168
- }
42169
- res.writeHead(200, { "Content-Type": "text/html" });
42170
- res.end("<html><body><h1>Authentication Successful!</h1><p>You can now close this window and return to your terminal.</p></body></html>");
42171
- finish(undefined, { authCode: code, state, redirectUri });
42172
- });
42173
- server.on("error", (err) => finish(new Error(`Failed to start callback server: ${err.message}`)));
42174
- server.listen(options.port, () => {
42175
- const address = server.address();
42176
- if (!address || typeof address === "string") {
42177
- finish(new Error("Failed to get server port"));
42178
- return;
42179
- }
42180
- redirectUri = `http://localhost:${address.port}/auth/callback`;
42181
- log(`[CodexOAuth] Callback server started on http://localhost:${address.port}`);
42182
- options.openBrowser(options.authorize(redirectUri)).catch(() => finish(new Error("Could not open OAuth browser")));
42183
- });
42184
- const timer = schedule(() => finish(new Error("OAuth login timed out after 5 minutes")), options.timeoutMs);
42185
- });
42186
- }
42187
- async function loginCodexPkce(deps = {}) {
42188
- log("[CodexOAuth] Starting OAuth login flow");
42189
- const verifier = randomBytes7(64).toString("base64url");
42190
- const challenge = createHash12("sha256").update(verifier).digest("base64url");
42191
- const state = randomBytes7(32).toString("base64url");
42192
- const options = {
42193
- port: 1455,
42194
- timeoutMs: 5 * 60 * 1000,
42195
- state,
42196
- authorize: (redirectUri) => buildAuthUrl(challenge, state, redirectUri),
42197
- openBrowser: deps.browser ?? openBrowser
42198
- };
42199
- const callback = await (deps.callback ? deps.callback(options) : waitForCallback(options, deps));
42200
- if (!callback.state || callback.state !== state)
42201
- throw new Error("Invalid OAuth state parameter (CSRF protection)");
42202
- if (!callback.authCode)
42203
- throw new Error("No authorization code received");
42204
- if (callback.redirectUri !== "http://localhost:1455/auth/callback")
42205
- throw new Error("Invalid OAuth redirect URI");
42206
- log("[CodexOAuth] Exchanging auth code for tokens");
42207
- let tokens;
42208
- try {
42209
- const response = await (deps.fetch ?? globalThis.fetch)(resolveUpstreamUrl(CODEX_OAUTH_CONFIG.tokenUrl), {
42210
- method: "POST",
42211
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
42212
- body: new URLSearchParams({
42213
- grant_type: "authorization_code",
42214
- code: callback.authCode,
42215
- redirect_uri: callback.redirectUri,
42216
- client_id: CODEX_OAUTH_CONFIG.clientId,
42217
- code_verifier: verifier
42218
- })
42219
- });
42220
- if (!response.ok) {
42221
- throw new Error(`Token exchange failed: ${response.status}`);
42222
- }
42223
- try {
42224
- tokens = await response.json();
42225
- } catch {
42226
- throw new Error("Invalid OAuth token response");
42227
- }
42228
- if (!tokens.access_token || !tokens.refresh_token)
42229
- throw new Error("Token response missing access_token or refresh_token");
42230
- } catch (error) {
42231
- const message = error instanceof Error ? error.message : "Token exchange failed";
42232
- const safe = message.split(callback.authCode).join("[redacted]").split(verifier).join("[redacted]");
42233
- throw new Error(`Failed to authenticate with OpenAI OAuth: ${safe}`);
42234
- }
42235
- const claims = loginClaims(tokens.id_token);
42236
- return {
42237
- credentials: {
42238
- access_token: tokens.access_token,
42239
- refresh_token: tokens.refresh_token,
42240
- expires_at: (deps.now ?? Date.now)() + tokens.expires_in * 1000,
42241
- account_id: claims.accountId
42242
- },
42243
- identity: claims.identity
42244
- };
42245
- }
42246
- var CODEX_OAUTH_CONFIG;
42247
- var init_codex_pkce = __esm(() => {
42248
- init_logger();
42249
- init_test_upstream();
42250
- CODEX_OAUTH_CONFIG = {
42251
- clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
42252
- authUrl: "https://auth.openai.com/oauth/authorize",
42253
- tokenUrl: "https://auth.openai.com/oauth/token",
42254
- scopes: ["openid", "profile", "email", "offline_access"]
42255
- };
42256
- });
42257
-
42258
42281
  // ../../node_modules/.bun/@inquirer+core@11.0.1+04f2146be16c61ef/node_modules/@inquirer/core/dist/lib/key.js
42259
42282
  var isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p", isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n", isBackspaceKey = (key) => key.name === "backspace", isTabKey = (key) => key.name === "tab", isNumberKey = (key) => "1234567890".includes(key.name), isEnterKey = (key) => key.name === "enter" || key.name === "return";
42260
42283
 
@@ -43192,7 +43215,7 @@ function wrapAnsi(string, columns, options) {
43192
43215
  return String(string).normalize().replaceAll(`\r
43193
43216
  `, `
43194
43217
  `).split(`
43195
- `).map((line) => exec4(line, columns, options)).join(`
43218
+ `).map((line) => exec3(line, columns, options)).join(`
43196
43219
  `);
43197
43220
  }
43198
43221
  var ESCAPES, END_CODE = 39, ANSI_ESCAPE_BELL = "\x07", ANSI_CSI = "[", ANSI_OSC = "]", ANSI_SGR_TERMINATOR = "m", ANSI_ESCAPE_LINK, wrapAnsiCode = (code) => `${ESCAPES.values().next().value}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`, wrapAnsiHyperlink = (url) => `${ESCAPES.values().next().value}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`, wordLengths = (string) => string.split(" ").map((character) => stringWidth(character)), wrapWord = (rows, word, columns) => {
@@ -43246,7 +43269,7 @@ var ESCAPES, END_CODE = 39, ANSI_ESCAPE_BELL = "\x07", ANSI_CSI = "[", ANSI_OSC
43246
43269
  return string;
43247
43270
  }
43248
43271
  return words.slice(0, last).join(" ") + words.slice(last).join("");
43249
- }, exec4 = (string, columns, options = {}) => {
43272
+ }, exec3 = (string, columns, options = {}) => {
43250
43273
  if (options.trim !== false && string.trim() === "") {
43251
43274
  return "";
43252
43275
  }
@@ -44525,9 +44548,727 @@ var init_dist12 = __esm(() => {
44525
44548
  init_dist11();
44526
44549
  });
44527
44550
 
44551
+ // src/auth/codex-pkce.ts
44552
+ import { exec as exec4 } from "child_process";
44553
+ import { createHash as createHash12, randomBytes as randomBytes7 } from "crypto";
44554
+ import { createServer } from "http";
44555
+ import { promisify as promisify3 } from "util";
44556
+ function loginClaims(idToken) {
44557
+ try {
44558
+ if (!idToken || idToken.split(".").length !== 3)
44559
+ return { identity: {} };
44560
+ const payload = JSON.parse(Buffer.from(idToken.split(".")[1], "base64url").toString("utf-8"));
44561
+ const auth = payload["https://api.openai.com/auth"];
44562
+ const stable = auth?.chatgpt_account_id || payload.chatgpt_account_id;
44563
+ const text = (value) => typeof value === "string" && value.trim() ? value : undefined;
44564
+ const wireId = stable || auth?.user_id;
44565
+ return {
44566
+ accountId: wireId ? wireId : undefined,
44567
+ identity: {
44568
+ chatgptAccountId: text(stable),
44569
+ email: text(payload.email),
44570
+ plan: text(auth?.chatgpt_plan_type)
44571
+ }
44572
+ };
44573
+ } catch {
44574
+ return { identity: {} };
44575
+ }
44576
+ }
44577
+ function buildAuthUrl(challenge, state, redirectUri) {
44578
+ const params = [
44579
+ "response_type=code",
44580
+ `client_id=${encodeURIComponent(CODEX_OAUTH_CONFIG.clientId)}`,
44581
+ `redirect_uri=${encodeURIComponent(redirectUri)}`,
44582
+ `scope=${CODEX_OAUTH_CONFIG.scopes.join("+")}`,
44583
+ `code_challenge=${encodeURIComponent(challenge)}`,
44584
+ "code_challenge_method=S256",
44585
+ "id_token_add_organizations=true",
44586
+ "codex_cli_simplified_flow=true",
44587
+ `state=${encodeURIComponent(state)}`,
44588
+ "originator=opencode"
44589
+ ].join("&");
44590
+ return `${CODEX_OAUTH_CONFIG.authUrl}?${params}`;
44591
+ }
44592
+ async function openBrowser(url) {
44593
+ const execAsync = promisify3(exec4);
44594
+ try {
44595
+ if (process.platform === "darwin")
44596
+ await execAsync(`open "${url}"`);
44597
+ else if (process.platform === "win32")
44598
+ await execAsync(`start "${url}"`);
44599
+ else
44600
+ await execAsync(`xdg-open "${url}"`);
44601
+ console.log(`
44602
+ Opening browser for OpenAI authentication...`);
44603
+ console.log(`If the browser doesn't open, visit this URL:
44604
+ ${url}
44605
+ `);
44606
+ } catch {
44607
+ console.log(`
44608
+ Please open this URL in your browser to authenticate:`);
44609
+ console.log(url);
44610
+ console.log("");
44611
+ }
44612
+ }
44613
+ function waitForCallback(options, deps) {
44614
+ return new Promise((resolve, reject) => {
44615
+ let redirectUri = "";
44616
+ const schedule = deps.setTimeout ?? setTimeout;
44617
+ const cancel = deps.clearTimeout ?? clearTimeout;
44618
+ const finish = (error, result) => {
44619
+ if (timer !== undefined)
44620
+ cancel(timer);
44621
+ server.close();
44622
+ if (error)
44623
+ reject(error);
44624
+ else if (result)
44625
+ resolve(result);
44626
+ };
44627
+ const server = createServer((req, res) => {
44628
+ const url = new URL(req.url, redirectUri.replace("/auth/callback", ""));
44629
+ if (url.pathname !== "/auth/callback") {
44630
+ res.writeHead(404, { "Content-Type": "text/plain" });
44631
+ res.end("Not found");
44632
+ return;
44633
+ }
44634
+ const code = url.searchParams.get("code");
44635
+ const state = url.searchParams.get("state");
44636
+ const error = url.searchParams.get("error");
44637
+ if (error || !state || state !== options.state || !code) {
44638
+ const detail = error ? "OAuth authorization was refused." : !state || state !== options.state ? "Invalid state parameter. Possible CSRF attack." : "No authorization code received.";
44639
+ res.writeHead(400, { "Content-Type": "text/html" });
44640
+ res.end(`<html><body><h1>Authentication Failed</h1><p>${detail}</p><p>You can close this window.</p></body></html>`);
44641
+ finish(new Error(error ? "OAuth authorization was refused" : !state || state !== options.state ? "Invalid OAuth state parameter (CSRF protection)" : "No authorization code received"));
44642
+ return;
44643
+ }
44644
+ res.writeHead(200, { "Content-Type": "text/html" });
44645
+ res.end("<html><body><h1>Authentication Successful!</h1><p>You can now close this window and return to your terminal.</p></body></html>");
44646
+ finish(undefined, { authCode: code, state, redirectUri });
44647
+ });
44648
+ server.on("error", (err) => finish(new Error(`Failed to start callback server: ${err.message}`)));
44649
+ server.listen(options.port, () => {
44650
+ const address = server.address();
44651
+ if (!address || typeof address === "string") {
44652
+ finish(new Error("Failed to get server port"));
44653
+ return;
44654
+ }
44655
+ redirectUri = `http://localhost:${address.port}/auth/callback`;
44656
+ log(`[CodexOAuth] Callback server started on http://localhost:${address.port}`);
44657
+ options.openBrowser(options.authorize(redirectUri)).catch(() => finish(new Error("Could not open OAuth browser")));
44658
+ });
44659
+ const timer = schedule(() => finish(new Error("OAuth login timed out after 5 minutes")), options.timeoutMs);
44660
+ });
44661
+ }
44662
+ async function loginCodexPkce(deps = {}) {
44663
+ log("[CodexOAuth] Starting OAuth login flow");
44664
+ const verifier = randomBytes7(64).toString("base64url");
44665
+ const challenge = createHash12("sha256").update(verifier).digest("base64url");
44666
+ const state = randomBytes7(32).toString("base64url");
44667
+ const options = {
44668
+ port: 1455,
44669
+ timeoutMs: 5 * 60 * 1000,
44670
+ state,
44671
+ authorize: (redirectUri) => buildAuthUrl(challenge, state, redirectUri),
44672
+ openBrowser: deps.browser ?? openBrowser
44673
+ };
44674
+ const callback = await (deps.callback ? deps.callback(options) : waitForCallback(options, deps));
44675
+ if (!callback.state || callback.state !== state)
44676
+ throw new Error("Invalid OAuth state parameter (CSRF protection)");
44677
+ if (!callback.authCode)
44678
+ throw new Error("No authorization code received");
44679
+ if (callback.redirectUri !== "http://localhost:1455/auth/callback")
44680
+ throw new Error("Invalid OAuth redirect URI");
44681
+ log("[CodexOAuth] Exchanging auth code for tokens");
44682
+ let tokens;
44683
+ try {
44684
+ const response = await (deps.fetch ?? globalThis.fetch)(resolveUpstreamUrl(CODEX_OAUTH_CONFIG.tokenUrl), {
44685
+ method: "POST",
44686
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
44687
+ body: new URLSearchParams({
44688
+ grant_type: "authorization_code",
44689
+ code: callback.authCode,
44690
+ redirect_uri: callback.redirectUri,
44691
+ client_id: CODEX_OAUTH_CONFIG.clientId,
44692
+ code_verifier: verifier
44693
+ })
44694
+ });
44695
+ if (!response.ok) {
44696
+ throw new Error(`Token exchange failed: ${response.status}`);
44697
+ }
44698
+ try {
44699
+ tokens = await response.json();
44700
+ } catch {
44701
+ throw new Error("Invalid OAuth token response");
44702
+ }
44703
+ if (!tokens.access_token || !tokens.refresh_token)
44704
+ throw new Error("Token response missing access_token or refresh_token");
44705
+ } catch (error) {
44706
+ const message = error instanceof Error ? error.message : "Token exchange failed";
44707
+ const safe = message.split(callback.authCode).join("[redacted]").split(verifier).join("[redacted]");
44708
+ throw new Error(`Failed to authenticate with OpenAI OAuth: ${safe}`);
44709
+ }
44710
+ const claims = loginClaims(tokens.id_token);
44711
+ return {
44712
+ credentials: {
44713
+ access_token: tokens.access_token,
44714
+ refresh_token: tokens.refresh_token,
44715
+ expires_at: (deps.now ?? Date.now)() + tokens.expires_in * 1000,
44716
+ account_id: claims.accountId
44717
+ },
44718
+ identity: claims.identity
44719
+ };
44720
+ }
44721
+ var CODEX_OAUTH_CONFIG;
44722
+ var init_codex_pkce = __esm(() => {
44723
+ init_logger();
44724
+ init_test_upstream();
44725
+ CODEX_OAUTH_CONFIG = {
44726
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
44727
+ authUrl: "https://auth.openai.com/oauth/authorize",
44728
+ tokenUrl: "https://auth.openai.com/oauth/token",
44729
+ scopes: ["openid", "profile", "email", "offline_access"]
44730
+ };
44731
+ });
44732
+
44733
+ // src/auth/codex-oauth.ts
44734
+ var exports_codex_oauth = {};
44735
+ __export(exports_codex_oauth, {
44736
+ CodexOAuth: () => CodexOAuth,
44737
+ CodexOAuthLocalError: () => CodexOAuthLocalError,
44738
+ getCodexOAuth: () => getCodexOAuth
44739
+ });
44740
+ import { existsSync as existsSync18, readFileSync as readFileSync21, unlinkSync as unlinkSync6 } from "fs";
44741
+ function isRecord2(value) {
44742
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44743
+ }
44744
+ function isAbsenceError(e) {
44745
+ const code = e?.code;
44746
+ return code === "ENOENT" || code === "ENOTDIR";
44747
+ }
44748
+ function errorMessage(e) {
44749
+ return e instanceof Error ? e.message : String(e);
44750
+ }
44751
+ function credentialFileUnreadable(credPath, detail) {
44752
+ return new CodexOAuthLocalError(`Could not read the OpenAI Codex credential file (${credPath}): ${detail}. ` + `Fix its permissions or contents, or run \`${LOGIN_COMMAND}\` to replace it; ` + "claudish does not fall back to OPENAI_CODEX_API_KEY while this file exists.");
44753
+ }
44754
+ function asCredentialFileError(e, credPath) {
44755
+ return e instanceof CodexOAuthLocalError ? e : credentialFileUnreadable(credPath, errorMessage(e));
44756
+ }
44757
+ function describeTokenEndpointError(body, presentedRefreshToken) {
44758
+ let text = "";
44759
+ try {
44760
+ const parsed = JSON.parse(body);
44761
+ const err = parsed?.error;
44762
+ const nested = typeof err === "object" && err !== null ? err : undefined;
44763
+ const code = typeof err === "string" ? err : typeof nested?.code === "string" ? nested.code : undefined;
44764
+ const message = typeof parsed?.error_description === "string" ? parsed.error_description : typeof nested?.message === "string" ? nested.message : undefined;
44765
+ text = [code, message].filter(Boolean).join(": ");
44766
+ } catch {
44767
+ text = body;
44768
+ }
44769
+ if (presentedRefreshToken)
44770
+ text = text.split(presentedRefreshToken).join("[redacted]");
44771
+ text = text.replace(/\s+/g, " ").trim();
44772
+ if (text.length > 200)
44773
+ text = `${text.slice(0, 200)}\u2026`;
44774
+ return text ? ` ${text}` : "";
44775
+ }
44776
+ function tokenHostUnreachable(e, tokenUrl) {
44777
+ log(`[CodexOAuth] Could not reach ${tokenUrl}: ${errorMessage(e)}`);
44778
+ return Object.assign(new Error(`Could not reach ${tokenUrl} to refresh the Codex token: ${errorMessage(e)}`, {
44779
+ cause: markOwnTimeout(e)
44780
+ }), { claudishEndpoint: tokenUrl });
44781
+ }
44782
+
44783
+ class CodexOAuth {
44784
+ static instance = null;
44785
+ credentials = null;
44786
+ credentialFileFailure = null;
44787
+ readCredentialText = (path) => readFileSync21(path, "utf-8");
44788
+ refreshPromise = null;
44789
+ tokenRefreshMargin = 5 * 60 * 1000;
44790
+ gateOverrides = {};
44791
+ static _resetInstanceForTests() {
44792
+ const instance = CodexOAuth.instance;
44793
+ if (!instance)
44794
+ return;
44795
+ instance.credentials = null;
44796
+ instance.credentialFileFailure = null;
44797
+ instance.readCredentialText = (path) => readFileSync21(path, "utf-8");
44798
+ instance.refreshPromise = null;
44799
+ instance.tokenRefreshMargin = 5 * 60 * 1000;
44800
+ instance.gateOverrides = {};
44801
+ instance.loadCredentials();
44802
+ }
44803
+ _setRefreshGateOptionsForTests(opts) {
44804
+ this.gateOverrides = { ...opts };
44805
+ }
44806
+ _setCredentialFileReaderForTests(read) {
44807
+ this.readCredentialText = read ?? ((path) => readFileSync21(path, "utf-8"));
44808
+ }
44809
+ static getInstance() {
44810
+ if (!CodexOAuth.instance) {
44811
+ CodexOAuth.instance = new CodexOAuth;
44812
+ }
44813
+ return CodexOAuth.instance;
44814
+ }
44815
+ constructor() {
44816
+ this.loadCredentials();
44817
+ }
44818
+ reloadCredentials() {
44819
+ this.loadCredentials();
44820
+ this.refreshPromise = null;
44821
+ }
44822
+ hasCredentials() {
44823
+ return this.credentials !== null && !!this.credentials.refresh_token || this.credentialFileFailure !== null;
44824
+ }
44825
+ getCredentialsPath() {
44826
+ return legacyCodexCredentialsPath();
44827
+ }
44828
+ getRefreshGatePath() {
44829
+ return this.gateOverrides.path ?? accountLockPath("openai-codex", LEGACY_CODEX_ACCOUNT_ID);
44830
+ }
44831
+ async login(deps = {}) {
44832
+ await Promise.resolve().then(() => init_management());
44833
+ if (codexLoginUsesKeychain()) {
44834
+ const lines = await loginCodexAccount({ pkce: () => loginCodexPkce(deps) });
44835
+ for (const line of lines)
44836
+ console.log(line);
44837
+ this.reloadCredentials();
44838
+ log("[CodexOAuth] Login saved to the Keychain account store");
44839
+ return;
44840
+ }
44841
+ assertLegacyCodexLoginPermitted();
44842
+ const { credentials } = await loginCodexPkce(deps);
44843
+ const accountId = credentials.account_id;
44844
+ await saveLegacyCodexLogin(() => {
44845
+ this.saveCredentials(credentials);
44846
+ this.setCredentials(credentials);
44847
+ });
44848
+ log("[CodexOAuth] Login successful");
44849
+ if (accountId) {
44850
+ log(`[CodexOAuth] Account ID: ${accountId}`);
44851
+ }
44852
+ }
44853
+ async logout() {
44854
+ await Promise.resolve().then(() => init_management());
44855
+ assertLegacyCodexLogoutPermitted();
44856
+ const credPath = this.getCredentialsPath();
44857
+ if (existsSync18(credPath)) {
44858
+ unlinkSync6(credPath);
44859
+ log("[CodexOAuth] Credentials deleted");
44860
+ }
44861
+ this.setCredentials(null);
44862
+ }
44863
+ async getAccessToken() {
44864
+ if (this.refreshPromise) {
44865
+ log("[CodexOAuth] Waiting for in-progress refresh");
44866
+ return this.refreshPromise;
44867
+ }
44868
+ if (!this.credentials && this.credentialFileFailure) {
44869
+ this.loadCredentials();
44870
+ if (this.credentialFileFailure)
44871
+ throw this.credentialFileFailure;
44872
+ }
44873
+ if (!this.credentials)
44874
+ throw await this.credentialFileGoneError();
44875
+ if (this.isTokenValid()) {
44876
+ return this.credentials.access_token;
44877
+ }
44878
+ return this.startRefresh({ kind: "expiry" });
44879
+ }
44880
+ getAccountId() {
44881
+ return this.credentials?.account_id;
44882
+ }
44883
+ async forceRefresh() {
44884
+ if (this.refreshPromise) {
44885
+ log("[CodexOAuth] Forced refresh joined the in-progress refresh");
44886
+ return this.refreshPromise;
44887
+ }
44888
+ return this.startRefresh({ kind: "forced", rejected: this.credentials?.access_token });
44889
+ }
44890
+ async refreshToken() {
44891
+ await this.forceRefresh();
44892
+ }
44893
+ isTokenValid() {
44894
+ if (!this.credentials)
44895
+ return false;
44896
+ return this.isFresh(this.credentials);
44897
+ }
44898
+ isFresh(credentials) {
44899
+ return Date.now() < credentials.expires_at - this.tokenRefreshMargin;
44900
+ }
44901
+ isAdoptable(onDisk, trigger) {
44902
+ if (!this.isFresh(onDisk))
44903
+ return false;
44904
+ return trigger.kind === "expiry" || onDisk.access_token !== trigger.rejected;
44905
+ }
44906
+ startRefresh(trigger) {
44907
+ const flight = this.refreshUnderGate(trigger).finally(() => {
44908
+ if (this.refreshPromise === flight)
44909
+ this.refreshPromise = null;
44910
+ });
44911
+ this.refreshPromise = flight;
44912
+ return flight;
44913
+ }
44914
+ async refreshUnderGate(trigger) {
44915
+ const before = this.readCredentialFile();
44916
+ try {
44917
+ return await withRefreshGate("openai-codex", LEGACY_CODEX_ACCOUNT_ID, (handle) => this.refreshHoldingGate(trigger, handle), {
44918
+ path: this.getRefreshGatePath(),
44919
+ gen: before?.generation ?? 0,
44920
+ ...this.gateOverrides.deps ? { deps: this.gateOverrides.deps } : {}
44921
+ });
44922
+ } catch (err) {
44923
+ if (!isGateUnavailable(err))
44924
+ throw err;
44925
+ return this.refreshWithoutGate(trigger, err);
44926
+ }
44927
+ }
44928
+ async refreshWithoutGate(trigger, err) {
44929
+ const latest = this.readCredentialFile();
44930
+ const ownership = latest ? await this.legacyFileOwnership() : undefined;
44931
+ if (latest && ownership === "facade" && this.isAdoptable(latest.credentials, trigger)) {
44932
+ log(`[CodexOAuth] Refresh gate unavailable (${err.reason}); the holder already rotated \u2014 using its token`);
44933
+ this.setCredentials(latest.credentials);
44934
+ return latest.credentials.access_token;
44935
+ }
44936
+ if (ownership === "elsewhere")
44937
+ this.setCredentials(null);
44938
+ else if (!latest)
44939
+ await this.credentialFileGoneError({ dropCredentials: true });
44940
+ log(`[CodexOAuth] Refresh gate unavailable: ${err.message}`);
44941
+ if (err.reason !== "unwritable") {
44942
+ throw credentialHttpError(`Another claudish process is refreshing the OpenAI Codex login (${err.reason}); retrying will usually work.`, 503);
44943
+ }
44944
+ throw new CodexOAuthLocalError(`Could not refresh the OpenAI Codex login: the refresh lock could not be created (${err.reason}). Retry the request; if this persists, run \`${LOGIN_COMMAND}\`.`);
44945
+ }
44946
+ async refreshHoldingGate(trigger, handle) {
44947
+ const onDisk = this.readCredentialFile();
44948
+ if (!onDisk)
44949
+ throw await this.credentialFileGoneError({ dropCredentials: true });
44950
+ const ownership = await this.legacyFileOwnership();
44951
+ if (ownership !== "facade") {
44952
+ if (ownership === "elsewhere")
44953
+ this.setCredentials(null);
44954
+ throw this.facadeYieldError(ownership);
44955
+ }
44956
+ if (this.isAdoptable(onDisk.credentials, trigger)) {
44957
+ log("[CodexOAuth] Another process already rotated the token \u2014 using it, no exchange");
44958
+ this.setCredentials(onDisk.credentials);
44959
+ return onDisk.credentials.access_token;
44960
+ }
44961
+ const updated = await this.exchangeRefreshToken(onDisk.credentials);
44962
+ if (!handle.holdsLock()) {
44963
+ throw new CodexOAuthLocalError(`Could not refresh the OpenAI Codex login: the refresh lock was taken over before the new token could be saved. Retry the request; if this persists, run \`${LOGIN_COMMAND}\`.`);
44964
+ }
44965
+ const current = this.readCredentialFile();
44966
+ if (!current) {
44967
+ log("[CodexOAuth] The credential file was removed during the exchange \u2014 not recreating it");
44968
+ this.setCredentials(null);
44969
+ throw await this.credentialFileGoneError();
44970
+ }
44971
+ if (current.credentials.refresh_token !== onDisk.credentials.refresh_token) {
44972
+ log("[CodexOAuth] The credential file changed during the exchange \u2014 keeping it");
44973
+ this.setCredentials(current.credentials);
44974
+ return current.credentials.access_token;
44975
+ }
44976
+ if (await this.legacyFileOwnership() === "elsewhere") {
44977
+ log("[CodexOAuth] The account list no longer names the credential file \u2014 not saving to it");
44978
+ this.setCredentials(null);
44979
+ throw this.facadeYieldError("elsewhere");
44980
+ }
44981
+ try {
44982
+ this.saveCredentials(updated);
44983
+ } catch (e) {
44984
+ log(`[CodexOAuth] Could not save the refreshed token: ${errorMessage(e)}`);
44985
+ throw new CodexOAuthLocalError(`Could not save the refreshed OpenAI Codex login (${errorMessage(e)}). Run \`${LOGIN_COMMAND}\` to sign in again.`);
44986
+ }
44987
+ this.setCredentials(updated);
44988
+ log(`[CodexOAuth] Token refreshed, valid until ${new Date(updated.expires_at).toISOString()}`);
44989
+ return updated.access_token;
44990
+ }
44991
+ async legacyFileOwnership() {
44992
+ await Promise.resolve().then(() => init_registry());
44993
+ const registry = getAccountRegistry();
44994
+ registry.invalidate();
44995
+ let snapshot;
44996
+ try {
44997
+ snapshot = registry.snapshot("openai-codex");
44998
+ } catch (err) {
44999
+ log(`[CodexOAuth] Could not read the Codex account list: ${errorMessage(err)}`);
45000
+ return "unknown";
45001
+ }
45002
+ const failed = codexSourceFailed(snapshot);
45003
+ if (failed) {
45004
+ log(`[CodexOAuth] Could not read the Codex account list (${failed.path}); not trusting the last-known-good copy`);
45005
+ return "unknown";
45006
+ }
45007
+ if (isSynthesizedLegacyOnlySnapshot(snapshot))
45008
+ return "facade";
45009
+ const suppressed = snapshot.settings["openai-codex"]?.legacyCodexSuppressed === true;
45010
+ const named = snapshot.accounts["openai-codex"].some((row) => row.store === "legacy-codex-file");
45011
+ return !suppressed && named ? "file-row" : "elsewhere";
45012
+ }
45013
+ async missingLoginError() {
45014
+ return this.credentialFileGoneError();
45015
+ }
45016
+ facadeYieldError(ownership) {
45017
+ if (ownership === "unknown") {
45018
+ return new CodexOAuthLocalError("The Codex account list could not be read, so the OpenAI Codex login was not refreshed. Retry the request.");
45019
+ }
45020
+ if (ownership === "file-row") {
45021
+ return new CodexOAuthLocalError("The Codex login is now a configured account; retry the request.");
45022
+ }
45023
+ return new CodexOAuthLocalError("The Codex login moved to the macOS Keychain; retry the request.");
45024
+ }
45025
+ async credentialFileGoneError(opts = {}) {
45026
+ await Promise.resolve().then(() => init_registry());
45027
+ const registry = getAccountRegistry();
45028
+ registry.invalidate();
45029
+ const unreadable = (detail) => {
45030
+ log(`[CodexOAuth] Credential file absent and the account list is unreadable: ${detail}`);
45031
+ return new CodexOAuthLocalError("The OpenAI Codex credential file is gone and the Codex account list could not be read. Retry the request.");
45032
+ };
45033
+ let configured;
45034
+ try {
45035
+ const snapshot = registry.snapshot("openai-codex");
45036
+ const failed = codexSourceFailed(snapshot);
45037
+ if (failed)
45038
+ return unreadable(`${failed.path}: ${failed.error ?? "read failed"}`);
45039
+ configured = snapshot.accounts["openai-codex"].length > 0;
45040
+ } catch (err) {
45041
+ return unreadable(errorMessage(err));
45042
+ }
45043
+ if (opts.dropCredentials)
45044
+ this.setCredentials(null);
45045
+ if (configured) {
45046
+ log("[CodexOAuth] Credential file absent; Codex accounts are configured \u2014 answering transient");
45047
+ return new CodexOAuthLocalError("The Codex login moved to the macOS Keychain; retry the request.");
45048
+ }
45049
+ return new CodexOAuthRefreshError(`No OpenAI Codex OAuth credentials found. Please run \`${LOGIN_COMMAND}\` first.`);
45050
+ }
45051
+ async exchangeRefreshToken(from) {
45052
+ log("[CodexOAuth] Refreshing access token");
45053
+ const tokenUrl = resolveUpstreamUrl(CODEX_OAUTH_CONFIG.tokenUrl);
45054
+ let response;
45055
+ try {
45056
+ response = await fetch(tokenUrl, {
45057
+ method: "POST",
45058
+ headers: {
45059
+ "Content-Type": "application/json"
45060
+ },
45061
+ body: JSON.stringify({
45062
+ grant_type: "refresh_token",
45063
+ refresh_token: from.refresh_token,
45064
+ client_id: CODEX_OAUTH_CONFIG.clientId
45065
+ }),
45066
+ signal: AbortSignal.timeout(CODEX_EXCHANGE_TIMEOUT_MS)
45067
+ });
45068
+ } catch (e) {
45069
+ throw tokenHostUnreachable(e, tokenUrl);
45070
+ }
45071
+ if (!response.ok) {
45072
+ const errorText = await response.text().catch(() => "");
45073
+ const detail = describeTokenEndpointError(errorText, from.refresh_token);
45074
+ log(`[CodexOAuth] Refresh failed: HTTP ${response.status}${detail}`);
45075
+ if (response.status === 429 || response.status >= 500 && response.status <= 599) {
45076
+ throw credentialHttpError(`The Codex token host is busy or unavailable (HTTP ${response.status}); retrying will usually work.${detail}`, response.status);
45077
+ }
45078
+ throw new CodexOAuthRefreshError(`OpenAI Codex login expired or was revoked \u2014 run \`${LOGIN_COMMAND}\` to sign in again. (token refresh: HTTP ${response.status}${detail})`);
45079
+ }
45080
+ let tokens;
45081
+ try {
45082
+ tokens = await response.json();
45083
+ } catch (e) {
45084
+ throw new CodexOAuthRefreshError(`Run \`${LOGIN_COMMAND}\` to sign in again: the Codex token endpoint returned an unreadable body (${errorMessage(e)}). A retry can succeed only if another claudish process already saved a newer token.`);
45085
+ }
45086
+ if (typeof tokens?.access_token !== "string" || tokens.access_token.length === 0) {
45087
+ throw new CodexOAuthRefreshError(`Could not refresh the OpenAI Codex login: the token endpoint returned no access token. Retry; if this persists, run \`${LOGIN_COMMAND}\`.`);
45088
+ }
45089
+ const accountId = (tokens.id_token ? this.extractAccountId(tokens.id_token) : undefined) ?? from.account_id;
45090
+ const expiresIn = typeof tokens.expires_in === "number" ? tokens.expires_in : 3600;
45091
+ return {
45092
+ access_token: tokens.access_token,
45093
+ refresh_token: tokens.refresh_token || from.refresh_token,
45094
+ expires_at: Date.now() + expiresIn * 1000,
45095
+ account_id: accountId
45096
+ };
45097
+ }
45098
+ setCredentials(credentials) {
45099
+ this.credentials = credentials;
45100
+ this.credentialFileFailure = null;
45101
+ }
45102
+ loadCredentials() {
45103
+ const outcome = this.classifyCredentialFile();
45104
+ switch (outcome.kind) {
45105
+ case "present":
45106
+ this.setCredentials(outcome.read.credentials);
45107
+ log("[CodexOAuth] Loaded credentials from file");
45108
+ return;
45109
+ case "absent":
45110
+ this.setCredentials(null);
45111
+ return;
45112
+ case "failed":
45113
+ this.credentials = null;
45114
+ this.credentialFileFailure = outcome.error;
45115
+ log(`[CodexOAuth] Credential file present but unusable \u2014 Codex requests fail until it is fixed: ${outcome.error.message}`);
45116
+ return;
45117
+ }
45118
+ }
45119
+ classifyCredentialFile() {
45120
+ let raw;
45121
+ try {
45122
+ raw = this.readRawCredentialFile();
45123
+ } catch (e) {
45124
+ return { kind: "failed", error: asCredentialFileError(e, this.getCredentialsPath()) };
45125
+ }
45126
+ if (raw === null)
45127
+ return { kind: "absent" };
45128
+ if (typeof raw.refresh_token !== "string" || !raw.refresh_token) {
45129
+ log("[CodexOAuth] Credential file names no refresh token \u2014 treating it as no login");
45130
+ return { kind: "absent" };
45131
+ }
45132
+ try {
45133
+ return { kind: "present", read: this.projectCredentialFile(raw) };
45134
+ } catch (e) {
45135
+ return { kind: "failed", error: asCredentialFileError(e, this.getCredentialsPath()) };
45136
+ }
45137
+ }
45138
+ readCredentialFile() {
45139
+ const raw = this.readRawCredentialFile();
45140
+ return raw === null ? null : this.projectCredentialFile(raw);
45141
+ }
45142
+ readRawCredentialFile() {
45143
+ const credPath = this.getCredentialsPath();
45144
+ let text;
45145
+ try {
45146
+ text = this.readCredentialText(credPath);
45147
+ } catch (e) {
45148
+ if (isAbsenceError(e))
45149
+ return null;
45150
+ throw credentialFileUnreadable(credPath, errorMessage(e));
45151
+ }
45152
+ let parsed;
45153
+ try {
45154
+ parsed = JSON.parse(text);
45155
+ } catch {
45156
+ throw credentialFileUnreadable(credPath, `it is not valid JSON (${text.length === 0 ? "empty" : "truncated or corrupted"})`);
45157
+ }
45158
+ if (!isRecord2(parsed)) {
45159
+ throw credentialFileUnreadable(credPath, "it is not a JSON object");
45160
+ }
45161
+ return parsed;
45162
+ }
45163
+ projectCredentialFile(raw) {
45164
+ const credPath = this.getCredentialsPath();
45165
+ const accessToken = raw.access_token;
45166
+ const refreshToken = raw.refresh_token;
45167
+ const expiresAt = raw.expires_at;
45168
+ if (typeof accessToken !== "string" || !accessToken || typeof refreshToken !== "string" || !refreshToken || typeof expiresAt !== "number" || !expiresAt) {
45169
+ log("[CodexOAuth] Invalid credentials file structure");
45170
+ throw new CodexOAuthLocalError(`The OpenAI Codex credential file (${credPath}) has no usable token pair. Run \`${LOGIN_COMMAND}\` to replace it.`);
45171
+ }
45172
+ const accountId = raw.account_id;
45173
+ const generation = raw.claudish_generation;
45174
+ return {
45175
+ credentials: {
45176
+ access_token: accessToken,
45177
+ refresh_token: refreshToken,
45178
+ expires_at: expiresAt,
45179
+ ...typeof accountId === "string" && accountId ? { account_id: accountId } : {}
45180
+ },
45181
+ generation: typeof generation === "number" ? generation : 0
45182
+ };
45183
+ }
45184
+ saveCredentials(credentials) {
45185
+ const credPath = this.getCredentialsPath();
45186
+ let existing = {};
45187
+ try {
45188
+ if (existsSync18(credPath)) {
45189
+ const parsed = JSON.parse(readFileSync21(credPath, "utf-8"));
45190
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
45191
+ existing = parsed;
45192
+ }
45193
+ } catch {
45194
+ existing = {};
45195
+ }
45196
+ const previousGeneration = typeof existing.claudish_generation === "number" ? existing.claudish_generation : 0;
45197
+ const document = {
45198
+ ...existing,
45199
+ ...credentials,
45200
+ claudish_generation: previousGeneration + 1
45201
+ };
45202
+ atomicWriteFileSync(credPath, JSON.stringify(document, null, 2), { mode: 384 });
45203
+ log(`[CodexOAuth] Credentials saved to ${credPath}`);
45204
+ }
45205
+ extractAccountId(idToken) {
45206
+ try {
45207
+ const parts = idToken.split(".");
45208
+ if (parts.length !== 3)
45209
+ return;
45210
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
45211
+ const authClaim = payload["https://api.openai.com/auth"];
45212
+ const accountId = authClaim?.chatgpt_account_id || payload.chatgpt_account_id || authClaim?.user_id;
45213
+ if (accountId) {
45214
+ log(`[CodexOAuth] Extracted account ID from id_token: ${accountId}`);
45215
+ return accountId;
45216
+ }
45217
+ return;
45218
+ } catch (e) {
45219
+ log(`[CodexOAuth] Failed to extract account ID from id_token: ${e.message}`);
45220
+ return;
45221
+ }
45222
+ }
45223
+ }
45224
+ function getCodexOAuth() {
45225
+ return CodexOAuth.getInstance();
45226
+ }
45227
+ var LOGIN_COMMAND = "claudish login codex", CodexOAuthRefreshError, CodexOAuthLocalError, CODEX_EXCHANGE_TIMEOUT_MS;
45228
+ var init_codex_oauth = __esm(() => {
45229
+ init_connection_error();
45230
+ init_transient_retry();
45231
+ init_logger();
45232
+ init_test_upstream();
45233
+ init_atomic_write();
45234
+ init_exchange();
45235
+ init_paths();
45236
+ init_refresh_lock();
45237
+ init_types();
45238
+ init_codex_pkce();
45239
+ CodexOAuthRefreshError = class CodexOAuthRefreshError extends Error {
45240
+ terminal = true;
45241
+ upstreamStatus = 401;
45242
+ constructor(message) {
45243
+ super(message);
45244
+ this.name = "CodexOAuthRefreshError";
45245
+ }
45246
+ };
45247
+ CodexOAuthLocalError = class CodexOAuthLocalError extends Error {
45248
+ transient = true;
45249
+ constructor(message) {
45250
+ super(message);
45251
+ this.name = "CodexOAuthLocalError";
45252
+ }
45253
+ };
45254
+ CODEX_EXCHANGE_TIMEOUT_MS = Math.min(TOKEN_REFRESH_TIMEOUT_MS, EXCHANGE_TIMEOUT_MS);
45255
+ });
45256
+
44528
45257
  // src/auth/accounts/management.ts
44529
45258
  import { randomBytes as randomBytes8 } from "crypto";
44530
- import { readFileSync as readFileSync21 } from "fs";
45259
+ import { accessSync, constants as fsConstants, lstatSync as lstatSync3, readFileSync as readFileSync22 } from "fs";
45260
+ function legacyMoveDeferredMessage(outcome, env = process.env) {
45261
+ return `Could not move the existing Codex login into the macOS Keychain (${outcome.reason}: ` + `${redactSecrets(outcome.detail)}). The login was not moved; ${legacyCodexCredentialsPath(env)} was kept. ` + MOVE_HINTS[outcome.reason];
45262
+ }
45263
+ function moveLegacyCodexLogin(deps = {}, opts = {}) {
45264
+ return new CodexAccountManagement(deps).moveLegacyLogin(opts);
45265
+ }
45266
+ function codexLoginUsesKeychain(env = process.env) {
45267
+ return !legacyMoveKeychainBlocked(env) && defaultAccountStore(env).kind === "keychain";
45268
+ }
45269
+ function loginCodexAccount(deps = {}) {
45270
+ return new CodexAccountManagement(deps).loginCodex();
45271
+ }
44531
45272
  function codexEntry(raw) {
44532
45273
  const block = raw.subscriptionAccounts;
44533
45274
  return block?.[PROVIDER];
@@ -44649,7 +45390,7 @@ class CodexAccountManagement {
44649
45390
  configPath: () => config,
44650
45391
  legacyCodexPath: () => legacyCodexCredentialsPath(this.env),
44651
45392
  env: this.env,
44652
- readFile: (path) => path === config ? "{}" : readFileSync21(path, "utf-8")
45393
+ readFile: (path) => path === config ? "{}" : readFileSync22(path, "utf-8")
44653
45394
  });
44654
45395
  return registry.snapshot(PROVIDER).accounts[PROVIDER].find((row) => row.legacy);
44655
45396
  }
@@ -44793,7 +45534,8 @@ class CodexAccountManagement {
44793
45534
  if (command.verb === "add") {
44794
45535
  if (command.label !== undefined)
44795
45536
  validLabel(command.label);
44796
- return this.add(command.label);
45537
+ await this.moveBeforeMutation();
45538
+ return this.add({ label: command.label });
44797
45539
  }
44798
45540
  if (command.verb === "rename")
44799
45541
  validLabel(command.newLabel);
@@ -44805,6 +45547,8 @@ class CodexAccountManagement {
44805
45547
  }
44806
45548
  if (command.selector !== undefined)
44807
45549
  validSelector(command.selector);
45550
+ if (command.verb !== "remove")
45551
+ await this.moveBeforeMutation();
44808
45552
  const target = this.inspect((entry) => this.select(entry, command));
44809
45553
  if (command.verb === "login")
44810
45554
  return this.login(target);
@@ -44814,10 +45558,14 @@ class CodexAccountManagement {
44814
45558
  return this.remove(target, command);
44815
45559
  return this.setEnabled(target, command.verb === "enable");
44816
45560
  }
44817
- async add(requestedLabel) {
45561
+ async add(opts = {}) {
45562
+ const requestedLabel = opts.label;
45563
+ const askNotice = opts.notice !== false;
45564
+ const enrollLegacy = opts.enrollLegacy !== false;
45565
+ const rowsFor = (entry) => enrollLegacy ? this.rowsOf(entry) : entry.accounts.map(asRecord);
44818
45566
  const initial = this.inspect((entry) => {
44819
- const legacy = this.synthesizedLegacy(entry);
44820
- if (requestedLabel !== undefined && labelTaken(this.rowsOf(entry).map((r) => r.label), requestedLabel)) {
45567
+ const legacy = enrollLegacy ? this.synthesizedLegacy(entry) : undefined;
45568
+ if (requestedLabel !== undefined && labelTaken(rowsFor(entry).map((r) => r.label), requestedLabel)) {
44821
45569
  throw new Error(`A Codex account labelled ${requestedLabel} already exists.`);
44822
45570
  }
44823
45571
  if (legacy && !legacy.identity.chatgptAccountId) {
@@ -44825,7 +45573,7 @@ class CodexAccountManagement {
44825
45573
  }
44826
45574
  return { notice: entry.noticeAcceptedAt, legacy, id: this.newId(entry) };
44827
45575
  });
44828
- if (!initial.notice && !await this.ask(OWNED_ACCOUNTS_NOTICE)) {
45576
+ if (askNotice && !initial.notice && !await this.ask(OWNED_ACCOUNTS_NOTICE)) {
44829
45577
  return ["Cancelled; no account was added."];
44830
45578
  }
44831
45579
  const acceptedAt = new Date(this.now()).toISOString();
@@ -44850,7 +45598,7 @@ class CodexAccountManagement {
44850
45598
  this.inspect((entry) => {
44851
45599
  if (entry.accounts.some((a) => a.id === row.id))
44852
45600
  throw new Error("The generated account id is taken; retry the add.");
44853
- const rows = this.rowsOf(entry);
45601
+ const rows = rowsFor(entry);
44854
45602
  this.refuseIdentity(rows, result.identity);
44855
45603
  this.chooseLabel(rows, result.identity, requestedLabel);
44856
45604
  });
@@ -44865,10 +45613,10 @@ class CodexAccountManagement {
44865
45613
  this.fence(holds);
44866
45614
  if (entry.accounts.some((a) => a.id === row.id))
44867
45615
  throw new Error("The generated account id is taken; retry the add.");
44868
- const rows = this.rowsOf(entry);
45616
+ const rows = rowsFor(entry);
44869
45617
  this.refuseIdentity(rows, result.identity);
44870
45618
  const label = this.chooseLabel(rows, result.identity, requestedLabel);
44871
- const legacy = rows.find((r) => r.legacy);
45619
+ const legacy = enrollLegacy ? rows.find((r) => r.legacy) : undefined;
44872
45620
  if (legacy) {
44873
45621
  if (legacy.identity.chatgptAccountId !== initial.legacy?.identity.chatgptAccountId) {
44874
45622
  throw new Error("The legacy Codex login changed during the add; retry to confirm its enrollment.");
@@ -44883,7 +45631,8 @@ class CodexAccountManagement {
44883
45631
  });
44884
45632
  }
44885
45633
  entry.accounts.push({ ...row, label });
44886
- entry.noticeAcceptedAt ??= acceptedAt;
45634
+ if (askNotice)
45635
+ entry.noticeAcceptedAt ??= acceptedAt;
44887
45636
  finalLabel = label;
44888
45637
  });
44889
45638
  } catch (error) {
@@ -44912,8 +45661,10 @@ class CodexAccountManagement {
44912
45661
  }
44913
45662
  throw new Error(`The add's outcome is unresolved: its token item (${store.kind}, id ${record.id}) was kept because the account list could not confirm the row is absent. Run \`claudish account list\`. Cause: ${errorText7(error)}`);
44914
45663
  }
44915
- async login(target) {
45664
+ async login(target, opts = {}) {
44916
45665
  if (target.legacy) {
45666
+ if (codexLoginUsesKeychain(this.env))
45667
+ return this.loginCodex();
44917
45668
  if (this.deps.legacyLogin)
44918
45669
  await this.deps.legacyLogin();
44919
45670
  else
@@ -44925,7 +45676,7 @@ class CodexAccountManagement {
44925
45676
  const result = await (this.deps.pkce ?? loginCodexPkce)();
44926
45677
  const stable = stableIdentity(result.identity);
44927
45678
  if (target.identity.chatgptAccountId && stable !== target.identity.chatgptAccountId) {
44928
- throw new Error(`This login is a different Codex identity than ${target.label}; use \`claudish account add codex\` for a different account.`);
45679
+ throw new Error(opts.differentIdentity ?? `This login is a different Codex identity than ${target.label}; use \`claudish account add codex\` for a different account.`);
44929
45680
  }
44930
45681
  await this.gate(target, async (holds) => {
44931
45682
  const validate = (entry) => {
@@ -44982,575 +45733,530 @@ class CodexAccountManagement {
44982
45733
  if (changed)
44983
45734
  await this.invalidate();
44984
45735
  const lines = [
44985
- `Codex account ${named(target)} ${changed ? "" : "is already "}${enabled ? "enabled" : "disabled"}.`
44986
- ];
44987
- if (!enabled) {
44988
- lines.push("Configured Codex accounts still reserve the subscription: with no enabled account, Codex requests fail visibly instead of using OPENAI_CODEX_API_KEY.");
44989
- }
44990
- if (enabled && poolDisabled) {
44991
- lines.push("The Codex pool itself is disabled in config.json (`enabled: false`); no account is selected.");
44992
- }
44993
- return lines;
44994
- }
44995
- async rename(target, newLabel) {
44996
- if (target.legacy) {
44997
- if (target.label === newLabel) {
44998
- return [`Codex account ${named(target)} is already labelled ${newLabel}.`];
44999
- }
45000
- if (!target.identity.chatgptAccountId) {
45001
- 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.");
45002
- }
45003
- }
45004
- let changed = false;
45005
- await this.gate(target, async (holds) => {
45006
- changed = mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
45007
- this.fence(holds);
45008
- const current = this.resolve(entry, target.legacy ? { label: target.label } : { id: target.id });
45009
- sameTarget(current, target);
45010
- if (current.label === newLabel)
45011
- return;
45012
- const others = this.rowsOf(entry).filter((row) => row.id !== current.id).map((row) => row.label);
45013
- if (labelTaken(others, newLabel)) {
45014
- throw new Error(`A Codex account labelled ${newLabel} already exists.`);
45015
- }
45016
- if (current.legacy) {
45017
- entry.accounts.push({
45018
- id: this.newId(entry),
45019
- label: newLabel,
45020
- identity: current.identity,
45021
- store: "legacy-codex-file",
45022
- enabled: true,
45023
- addedAt: current.addedAt
45024
- });
45025
- return;
45026
- }
45027
- const row = entry.accounts.find((a) => a.id === target.id);
45028
- if (row)
45029
- row.label = newLabel;
45030
- });
45031
- });
45032
- if (changed)
45033
- await this.invalidate();
45034
- if (changed && target.legacy) {
45035
- 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.`);
45036
- }
45037
- return [
45038
- changed ? `Renamed Codex account ${named(target)} to ${newLabel}.` : `Codex account ${named(target)} is already labelled ${newLabel}.`
45039
- ];
45040
- }
45041
- async remove(target, command) {
45042
- const legacyFile = target.store === "legacy-codex-file";
45043
- if (legacyFile && !command.deleteFile) {
45044
- throw new Error(`${target.label} is the legacy Codex login; removing it deletes its credential file, which needs an explicit --delete-file. A label alone never authorizes that.`);
45045
- }
45046
- if (!legacyFile && command.deleteFile) {
45047
- throw new Error(`--delete-file applies only to the legacy Codex login, not ${target.label}.`);
45048
- }
45049
- 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.`)) {
45050
- return ["Cancelled; no account was removed."];
45051
- }
45052
- const warnings = [];
45053
- await this.gate(target, async (holds) => {
45054
- mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
45055
- this.fence(holds);
45056
- const current = this.resolve(entry, target.legacy ? { label: target.label } : { id: target.id });
45057
- sameTarget(current, target);
45058
- entry.accounts = entry.accounts.filter((row) => row.id !== target.id);
45059
- if (legacyFile)
45060
- entry.legacyCodexSuppressed = true;
45061
- });
45062
- await this.invalidate();
45063
- const state = this.deps.state ?? getStateStore();
45064
- try {
45065
- if (!await state.setActive(PROVIDER, target.id, target.id)) {
45066
- warnings.push("Clearing the active-account pointer is pending; state.json was not written.");
45067
- }
45068
- } catch {
45069
- warnings.push("Clearing the active-account pointer is pending; state.json was not written.");
45070
- }
45071
- try {
45072
- this.fence(holds);
45073
- await this.store(target.store).delete(target);
45074
- } catch {
45075
- warnings.push(`The removal is committed, but deleting ${target.label}'s token item (${target.store}) failed; the account was not restored${legacyFile ? " and the legacy login stays suppressed" : ""}.`);
45076
- }
45077
- });
45078
- const next = this.inspect((entry) => {
45079
- if (entry.accounts.length > 0) {
45080
- return entry.enabled === false || entry.accounts.every((row) => row.enabled === false) ? "pool-disabled" : "pool";
45081
- }
45082
- return this.synthesizedLegacy(entry) ? "legacy" : "key";
45083
- });
45084
- const nextText = {
45085
- pool: "the remaining Codex accounts",
45086
- "pool-disabled": "none: Codex accounts remain configured but all are disabled, so requests fail visibly",
45087
- legacy: "the legacy Codex login",
45088
- key: "OPENAI_CODEX_API_KEY if one is configured, otherwise none"
45089
- }[next];
45090
- return [
45091
- `Removed Codex account ${named(target)}${note(target.identity, target.label)}. Next Codex credential for new requests: ${nextText}.`,
45092
- ...next === "key" ? ["Warning: new Codex requests may now use OPENAI_CODEX_API_KEY, billed per token."] : [],
45093
- ...warnings
45094
- ];
45095
- }
45096
- }
45097
- function errorText7(err) {
45098
- return err instanceof Error ? err.message : String(err);
45099
- }
45100
- var PROVIDER = "openai-codex", OWNED_ACCOUNTS_NOTICE, LEGACY_LOGIN_REFUSED, LEGACY_SAVE_WAIT_MS = 15000;
45101
- var init_management = __esm(() => {
45102
- init_dist12();
45103
- init_profile_config();
45104
- init_codex_pkce();
45105
- init_account_label();
45106
- init_config_lock();
45107
- init_file_lock();
45108
- init_paths();
45109
- init_refresh_lock();
45110
- init_registry();
45111
- init_state_store();
45112
- init_token_store();
45113
- init_types();
45114
- OWNED_ACCOUNTS_NOTICE = "Add only Codex accounts you own and pay for. Switching between subscriptions may be " + "restricted by OpenAI's terms; this tool is not permission to bypass them. Accept and continue?";
45115
- LEGACY_LOGIN_REFUSED = "Codex accounts are managed by `claudish account`, or the legacy login was removed. " + "Run `claudish account add codex` for a new account, or " + "`claudish account login codex <label>` to log an existing one in again.";
45116
- });
45117
-
45118
- // src/auth/codex-oauth.ts
45119
- var exports_codex_oauth = {};
45120
- __export(exports_codex_oauth, {
45121
- CodexOAuth: () => CodexOAuth,
45122
- getCodexOAuth: () => getCodexOAuth
45123
- });
45124
- import { existsSync as existsSync18, readFileSync as readFileSync22, unlinkSync as unlinkSync6 } from "fs";
45125
- function isRecord2(value) {
45126
- return typeof value === "object" && value !== null && !Array.isArray(value);
45127
- }
45128
- function isAbsenceError(e) {
45129
- const code = e?.code;
45130
- return code === "ENOENT" || code === "ENOTDIR";
45131
- }
45132
- function errorMessage(e) {
45133
- return e instanceof Error ? e.message : String(e);
45134
- }
45135
- function credentialFileUnreadable(credPath, detail) {
45136
- return new CodexOAuthLocalError(`Could not read the OpenAI Codex credential file (${credPath}): ${detail}. ` + `Fix its permissions or contents, or run \`${LOGIN_COMMAND}\` to replace it; ` + "claudish does not fall back to OPENAI_CODEX_API_KEY while this file exists.");
45137
- }
45138
- function asCredentialFileError(e, credPath) {
45139
- return e instanceof CodexOAuthLocalError ? e : credentialFileUnreadable(credPath, errorMessage(e));
45140
- }
45141
- function describeTokenEndpointError(body, presentedRefreshToken) {
45142
- let text = "";
45143
- try {
45144
- const parsed = JSON.parse(body);
45145
- const err = parsed?.error;
45146
- const nested = typeof err === "object" && err !== null ? err : undefined;
45147
- const code = typeof err === "string" ? err : typeof nested?.code === "string" ? nested.code : undefined;
45148
- const message = typeof parsed?.error_description === "string" ? parsed.error_description : typeof nested?.message === "string" ? nested.message : undefined;
45149
- text = [code, message].filter(Boolean).join(": ");
45150
- } catch {
45151
- text = body;
45152
- }
45153
- if (presentedRefreshToken)
45154
- text = text.split(presentedRefreshToken).join("[redacted]");
45155
- text = text.replace(/\s+/g, " ").trim();
45156
- if (text.length > 200)
45157
- text = `${text.slice(0, 200)}\u2026`;
45158
- return text ? ` ${text}` : "";
45159
- }
45160
- function tokenHostUnreachable(e, tokenUrl) {
45161
- log(`[CodexOAuth] Could not reach ${tokenUrl}: ${errorMessage(e)}`);
45162
- return Object.assign(new Error(`Could not reach ${tokenUrl} to refresh the Codex token: ${errorMessage(e)}`, {
45163
- cause: markOwnTimeout(e)
45164
- }), { claudishEndpoint: tokenUrl });
45165
- }
45166
-
45167
- class CodexOAuth {
45168
- static instance = null;
45169
- credentials = null;
45170
- credentialFileFailure = null;
45171
- readCredentialText = (path) => readFileSync22(path, "utf-8");
45172
- refreshPromise = null;
45173
- tokenRefreshMargin = 5 * 60 * 1000;
45174
- gateOverrides = {};
45175
- static _resetInstanceForTests() {
45176
- const instance = CodexOAuth.instance;
45177
- if (!instance)
45178
- return;
45179
- instance.credentials = null;
45180
- instance.credentialFileFailure = null;
45181
- instance.readCredentialText = (path) => readFileSync22(path, "utf-8");
45182
- instance.refreshPromise = null;
45183
- instance.tokenRefreshMargin = 5 * 60 * 1000;
45184
- instance.gateOverrides = {};
45185
- instance.loadCredentials();
45186
- }
45187
- _setRefreshGateOptionsForTests(opts) {
45188
- this.gateOverrides = { ...opts };
45189
- }
45190
- _setCredentialFileReaderForTests(read) {
45191
- this.readCredentialText = read ?? ((path) => readFileSync22(path, "utf-8"));
45192
- }
45193
- static getInstance() {
45194
- if (!CodexOAuth.instance) {
45195
- CodexOAuth.instance = new CodexOAuth;
45196
- }
45197
- return CodexOAuth.instance;
45198
- }
45199
- constructor() {
45200
- this.loadCredentials();
45201
- }
45202
- reloadCredentials() {
45203
- this.loadCredentials();
45204
- this.refreshPromise = null;
45205
- }
45206
- hasCredentials() {
45207
- return this.credentials !== null && !!this.credentials.refresh_token || this.credentialFileFailure !== null;
45208
- }
45209
- getCredentialsPath() {
45210
- return legacyCodexCredentialsPath();
45211
- }
45212
- getRefreshGatePath() {
45213
- return this.gateOverrides.path ?? accountLockPath("openai-codex", LEGACY_CODEX_ACCOUNT_ID);
45214
- }
45215
- async login(deps = {}) {
45216
- await Promise.resolve().then(() => init_management());
45217
- assertLegacyCodexLoginPermitted();
45218
- const { credentials } = await loginCodexPkce(deps);
45219
- const accountId = credentials.account_id;
45220
- await saveLegacyCodexLogin(() => {
45221
- this.saveCredentials(credentials);
45222
- this.setCredentials(credentials);
45223
- });
45224
- log("[CodexOAuth] Login successful");
45225
- if (accountId) {
45226
- log(`[CodexOAuth] Account ID: ${accountId}`);
45736
+ `Codex account ${named(target)} ${changed ? "" : "is already "}${enabled ? "enabled" : "disabled"}.`
45737
+ ];
45738
+ if (!enabled) {
45739
+ lines.push("Configured Codex accounts still reserve the subscription: with no enabled account, Codex requests fail visibly instead of using OPENAI_CODEX_API_KEY.");
45227
45740
  }
45228
- }
45229
- async logout() {
45230
- await Promise.resolve().then(() => init_management());
45231
- assertLegacyCodexLogoutPermitted();
45232
- const credPath = this.getCredentialsPath();
45233
- if (existsSync18(credPath)) {
45234
- unlinkSync6(credPath);
45235
- log("[CodexOAuth] Credentials deleted");
45741
+ if (enabled && poolDisabled) {
45742
+ lines.push("The Codex pool itself is disabled in config.json (`enabled: false`); no account is selected.");
45236
45743
  }
45237
- this.setCredentials(null);
45744
+ return lines;
45238
45745
  }
45239
- async getAccessToken() {
45240
- if (this.refreshPromise) {
45241
- log("[CodexOAuth] Waiting for in-progress refresh");
45242
- return this.refreshPromise;
45746
+ async rename(target, newLabel) {
45747
+ if (target.legacy) {
45748
+ if (target.label === newLabel) {
45749
+ return [`Codex account ${named(target)} is already labelled ${newLabel}.`];
45750
+ }
45751
+ if (!target.identity.chatgptAccountId) {
45752
+ 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.");
45753
+ }
45243
45754
  }
45244
- if (!this.credentials && this.credentialFileFailure) {
45245
- this.loadCredentials();
45246
- if (this.credentialFileFailure)
45247
- throw this.credentialFileFailure;
45755
+ let changed = false;
45756
+ await this.gate(target, async (holds) => {
45757
+ changed = mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
45758
+ this.fence(holds);
45759
+ const current = this.resolve(entry, target.legacy ? { label: target.label } : { id: target.id });
45760
+ sameTarget(current, target);
45761
+ if (current.label === newLabel)
45762
+ return;
45763
+ const others = this.rowsOf(entry).filter((row) => row.id !== current.id).map((row) => row.label);
45764
+ if (labelTaken(others, newLabel)) {
45765
+ throw new Error(`A Codex account labelled ${newLabel} already exists.`);
45766
+ }
45767
+ if (current.legacy) {
45768
+ entry.accounts.push({
45769
+ id: this.newId(entry),
45770
+ label: newLabel,
45771
+ identity: current.identity,
45772
+ store: "legacy-codex-file",
45773
+ enabled: true,
45774
+ addedAt: current.addedAt
45775
+ });
45776
+ return;
45777
+ }
45778
+ const row = entry.accounts.find((a) => a.id === target.id);
45779
+ if (row)
45780
+ row.label = newLabel;
45781
+ });
45782
+ });
45783
+ if (changed)
45784
+ await this.invalidate();
45785
+ if (changed && target.legacy) {
45786
+ 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.`);
45248
45787
  }
45249
- if (!this.credentials) {
45250
- throw new CodexOAuthRefreshError(`No OpenAI Codex OAuth credentials found. Please run \`${LOGIN_COMMAND}\` first.`);
45788
+ return [
45789
+ changed ? `Renamed Codex account ${named(target)} to ${newLabel}.` : `Codex account ${named(target)} is already labelled ${newLabel}.`
45790
+ ];
45791
+ }
45792
+ async remove(target, command) {
45793
+ const legacyFile = target.store === "legacy-codex-file";
45794
+ if (legacyFile && !command.deleteFile) {
45795
+ throw new Error(`${target.label} is the legacy Codex login; removing it deletes its credential file, which needs an explicit --delete-file. A label alone never authorizes that.`);
45251
45796
  }
45252
- if (this.isTokenValid()) {
45253
- return this.credentials.access_token;
45797
+ if (!legacyFile && command.deleteFile) {
45798
+ throw new Error(`--delete-file applies only to the legacy Codex login, not ${target.label}.`);
45254
45799
  }
45255
- return this.startRefresh({ kind: "expiry" });
45256
- }
45257
- getAccountId() {
45258
- return this.credentials?.account_id;
45259
- }
45260
- async forceRefresh() {
45261
- if (this.refreshPromise) {
45262
- log("[CodexOAuth] Forced refresh joined the in-progress refresh");
45263
- return this.refreshPromise;
45800
+ 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.`)) {
45801
+ return ["Cancelled; no account was removed."];
45264
45802
  }
45265
- return this.startRefresh({ kind: "forced", rejected: this.credentials?.access_token });
45266
- }
45267
- async refreshToken() {
45268
- await this.forceRefresh();
45269
- }
45270
- isTokenValid() {
45271
- if (!this.credentials)
45272
- return false;
45273
- return this.isFresh(this.credentials);
45274
- }
45275
- isFresh(credentials) {
45276
- return Date.now() < credentials.expires_at - this.tokenRefreshMargin;
45277
- }
45278
- isAdoptable(onDisk, trigger) {
45279
- if (!this.isFresh(onDisk))
45280
- return false;
45281
- return trigger.kind === "expiry" || onDisk.access_token !== trigger.rejected;
45282
- }
45283
- startRefresh(trigger) {
45284
- const flight = this.refreshUnderGate(trigger).finally(() => {
45285
- if (this.refreshPromise === flight)
45286
- this.refreshPromise = null;
45287
- });
45288
- this.refreshPromise = flight;
45289
- return flight;
45290
- }
45291
- async refreshUnderGate(trigger) {
45292
- const before = this.readCredentialFile();
45293
- try {
45294
- return await withRefreshGate("openai-codex", LEGACY_CODEX_ACCOUNT_ID, (handle) => this.refreshHoldingGate(trigger, handle), {
45295
- path: this.getRefreshGatePath(),
45296
- gen: before?.generation ?? 0,
45297
- ...this.gateOverrides.deps ? { deps: this.gateOverrides.deps } : {}
45803
+ const warnings = [];
45804
+ await this.gate(target, async (holds) => {
45805
+ mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
45806
+ this.fence(holds);
45807
+ const current = this.resolve(entry, target.legacy ? { label: target.label } : { id: target.id });
45808
+ sameTarget(current, target);
45809
+ entry.accounts = entry.accounts.filter((row) => row.id !== target.id);
45810
+ if (legacyFile)
45811
+ entry.legacyCodexSuppressed = true;
45298
45812
  });
45299
- } catch (err) {
45300
- if (!isGateUnavailable(err))
45301
- throw err;
45302
- const latest = this.readCredentialFile();
45303
- if (latest && this.isAdoptable(latest.credentials, trigger)) {
45304
- log(`[CodexOAuth] Refresh gate unavailable (${err.reason}); the holder already rotated \u2014 using its token`);
45305
- this.setCredentials(latest.credentials);
45306
- return latest.credentials.access_token;
45813
+ await this.invalidate();
45814
+ const state = this.deps.state ?? getStateStore();
45815
+ try {
45816
+ if (!await state.setActive(PROVIDER, target.id, target.id)) {
45817
+ warnings.push("Clearing the active-account pointer is pending; state.json was not written.");
45818
+ }
45819
+ } catch {
45820
+ warnings.push("Clearing the active-account pointer is pending; state.json was not written.");
45821
+ }
45822
+ try {
45823
+ this.fence(holds);
45824
+ await this.store(target.store).delete(target);
45825
+ } catch {
45826
+ warnings.push(`The removal is committed, but deleting ${target.label}'s token item (${target.store}) failed; the account was not restored${legacyFile ? " and the legacy login stays suppressed" : ""}.`);
45307
45827
  }
45308
- log(`[CodexOAuth] Refresh gate unavailable: ${err.message}`);
45309
- if (err.reason !== "unwritable") {
45310
- throw credentialHttpError(`Another claudish process is refreshing the OpenAI Codex login (${err.reason}); retrying will usually work.`, 503);
45828
+ });
45829
+ const next = this.inspect((entry) => {
45830
+ if (entry.accounts.length > 0) {
45831
+ return entry.enabled === false || entry.accounts.every((row) => row.enabled === false) ? "pool-disabled" : "pool";
45311
45832
  }
45312
- throw new CodexOAuthLocalError(`Could not refresh the OpenAI Codex login: the refresh lock could not be created (${err.reason}). Retry the request; if this persists, run \`${LOGIN_COMMAND}\`.`);
45313
- }
45833
+ return this.synthesizedLegacy(entry) ? "legacy" : "key";
45834
+ });
45835
+ const nextText = {
45836
+ pool: "the remaining Codex accounts",
45837
+ "pool-disabled": "none: Codex accounts remain configured but all are disabled, so requests fail visibly",
45838
+ legacy: "the legacy Codex login",
45839
+ key: "OPENAI_CODEX_API_KEY if one is configured, otherwise none"
45840
+ }[next];
45841
+ return [
45842
+ `Removed Codex account ${named(target)}${note(target.identity, target.label)}. Next Codex credential for new requests: ${nextText}.`,
45843
+ ...next === "key" ? ["Warning: new Codex requests may now use OPENAI_CODEX_API_KEY, billed per token."] : [],
45844
+ ...warnings
45845
+ ];
45314
45846
  }
45315
- async refreshHoldingGate(trigger, handle) {
45316
- const onDisk = this.readCredentialFile();
45317
- if (!onDisk) {
45318
- throw new CodexOAuthRefreshError(`No OpenAI Codex OAuth credentials found. Please run \`${LOGIN_COMMAND}\` first.`);
45319
- }
45320
- if (this.isAdoptable(onDisk.credentials, trigger)) {
45321
- log("[CodexOAuth] Another process already rotated the token \u2014 using it, no exchange");
45322
- this.setCredentials(onDisk.credentials);
45323
- return onDisk.credentials.access_token;
45847
+ async moveBeforeMutation() {
45848
+ const moved = await this.moveLegacyLogin();
45849
+ if (moved.kind === "deferred")
45850
+ throw new Error(legacyMoveDeferredMessage(moved, this.env));
45851
+ }
45852
+ async loginCodex() {
45853
+ await this.moveBeforeMutation();
45854
+ const rows = this.inspect((entry) => entry.accounts.map(asRecord));
45855
+ if (rows.length === 0) {
45856
+ return this.add({ label: LEGACY_CODEX_LABEL, notice: false, enrollLegacy: false });
45857
+ }
45858
+ if (rows.length === 1) {
45859
+ const [row] = rows;
45860
+ const remove = `claudish account remove codex ${shellWord(row.label)}` + (row.store === "legacy-codex-file" ? " --delete-file" : "");
45861
+ return this.login(row, {
45862
+ differentIdentity: `This login is a different Codex identity than ${row.label}. Run \`claudish account add codex\` to add it, or \`${remove}\` and then \`claudish login codex\` to replace it.`
45863
+ });
45324
45864
  }
45325
- const updated = await this.exchangeRefreshToken(onDisk.credentials);
45326
- if (!handle.holdsLock()) {
45327
- throw new CodexOAuthLocalError(`Could not refresh the OpenAI Codex login: the refresh lock was taken over before the new token could be saved. Retry the request; if this persists, run \`${LOGIN_COMMAND}\`.`);
45865
+ throw new Error(`Codex has ${rows.length} accounts; log one in with \`claudish account login codex <label>\`.`);
45866
+ }
45867
+ async moveLegacyLogin(opts = {}) {
45868
+ if (legacyMoveKeychainBlocked(this.env))
45869
+ return notApplicable("no-keychain");
45870
+ const dest = this.deps.newStore?.() ?? defaultAccountStore(this.env);
45871
+ if (dest.kind !== "keychain")
45872
+ return notApplicable("no-keychain");
45873
+ if (!isDefaultConfigPath(this.configPath(), this.env))
45874
+ return notApplicable("alternate-config");
45875
+ let target;
45876
+ try {
45877
+ target = this.inspect((entry) => this.moveTargetOf(entry));
45878
+ } catch (err) {
45879
+ if (this.legacyFileDenied())
45880
+ return this.moveDeferred(err, "file-unreadable");
45881
+ return this.moveDeferred(err, "config-unavailable");
45882
+ }
45883
+ if (target.kind === "none")
45884
+ return notApplicable("no-legacy-account");
45885
+ if (target.kind === "leftover")
45886
+ return this.deleteLeftover();
45887
+ const found = target;
45888
+ let outcome;
45889
+ try {
45890
+ outcome = await this.gate(found.row, (holds) => this.moveHoldingGate(found, dest, holds, opts));
45891
+ } catch (err) {
45892
+ return this.moveDeferred(err, "gate-busy");
45328
45893
  }
45329
- const current = this.readCredentialFile();
45330
- if (!current) {
45331
- log("[CodexOAuth] The credential file was removed during the exchange \u2014 not recreating it");
45332
- this.setCredentials(null);
45333
- throw new CodexOAuthRefreshError(`No OpenAI Codex OAuth credentials found. Please run \`${LOGIN_COMMAND}\` first.`);
45894
+ if (outcome.kind === "moved") {
45895
+ log(`[accounts] moved the legacy Codex login into the Keychain as ${outcome.label} (id ${outcome.id})${outcome.fileDeleted ? "" : "; the credential file was kept"}`);
45334
45896
  }
45335
- if (current.credentials.refresh_token !== onDisk.credentials.refresh_token) {
45336
- log("[CodexOAuth] The credential file changed during the exchange \u2014 keeping it");
45337
- this.setCredentials(current.credentials);
45338
- return current.credentials.access_token;
45897
+ return outcome;
45898
+ }
45899
+ moveTargetOf(entry) {
45900
+ const legacyRow = entry.accounts.find((row) => row.store === "legacy-codex-file");
45901
+ if (legacyRow)
45902
+ return { kind: "row", row: asRecord(legacyRow) };
45903
+ if (entry.accounts.length === 0 && !entry.legacyCodexSuppressed) {
45904
+ const synthesized = this.synthesizedLegacy(entry);
45905
+ return synthesized ? { kind: "synthesized", row: synthesized } : { kind: "none" };
45339
45906
  }
45907
+ if (entry.legacyCodexSuppressed && this.legacyFilePresent())
45908
+ return { kind: "leftover" };
45909
+ return { kind: "none" };
45910
+ }
45911
+ legacyFilePresent() {
45340
45912
  try {
45341
- this.saveCredentials(updated);
45342
- } catch (e) {
45343
- log(`[CodexOAuth] Could not save the refreshed token: ${errorMessage(e)}`);
45344
- throw new CodexOAuthLocalError(`Could not save the refreshed OpenAI Codex login (${errorMessage(e)}). Run \`${LOGIN_COMMAND}\` to sign in again.`);
45913
+ lstatSync3(legacyCodexCredentialsPath(this.env));
45914
+ return true;
45915
+ } catch (err) {
45916
+ const code = err?.code;
45917
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
45918
+ log(`[accounts] legacy Codex file check failed: ${redactSecrets(errorText7(err))}`);
45919
+ }
45920
+ return false;
45345
45921
  }
45346
- this.setCredentials(updated);
45347
- log(`[CodexOAuth] Token refreshed, valid until ${new Date(updated.expires_at).toISOString()}`);
45348
- return updated.access_token;
45349
45922
  }
45350
- async exchangeRefreshToken(from) {
45351
- log("[CodexOAuth] Refreshing access token");
45352
- const tokenUrl = resolveUpstreamUrl(CODEX_OAUTH_CONFIG.tokenUrl);
45353
- let response;
45923
+ legacyFileDenied() {
45354
45924
  try {
45355
- response = await fetch(tokenUrl, {
45356
- method: "POST",
45357
- headers: {
45358
- "Content-Type": "application/json"
45359
- },
45360
- body: JSON.stringify({
45361
- grant_type: "refresh_token",
45362
- refresh_token: from.refresh_token,
45363
- client_id: CODEX_OAUTH_CONFIG.clientId
45364
- }),
45365
- signal: AbortSignal.timeout(CODEX_EXCHANGE_TIMEOUT_MS)
45366
- });
45367
- } catch (e) {
45368
- throw tokenHostUnreachable(e, tokenUrl);
45925
+ accessSync(legacyCodexCredentialsPath(this.env), fsConstants.R_OK);
45926
+ return false;
45927
+ } catch (err) {
45928
+ const code = err?.code;
45929
+ return code === "EACCES" || code === "EPERM";
45369
45930
  }
45370
- if (!response.ok) {
45371
- const errorText = await response.text().catch(() => "");
45372
- const detail = describeTokenEndpointError(errorText, from.refresh_token);
45373
- log(`[CodexOAuth] Refresh failed: HTTP ${response.status}${detail}`);
45374
- if (response.status === 429 || response.status >= 500 && response.status <= 599) {
45375
- throw credentialHttpError(`The Codex token host is busy or unavailable (HTTP ${response.status}); retrying will usually work.${detail}`, response.status);
45931
+ }
45932
+ async moveHoldingGate(target, dest, holds, opts) {
45933
+ const hooks = this.deps.moveHooks;
45934
+ const source = this.store("legacy-codex-file");
45935
+ let phase = "config-unavailable";
45936
+ let row = target.row;
45937
+ let material;
45938
+ try {
45939
+ if (target.kind === "synthesized") {
45940
+ const enrolled = this.enrolSynthesizedLegacy(target.row, holds);
45941
+ if (enrolled === "already-moved")
45942
+ return { kind: "already-moved" };
45943
+ if (enrolled === "gone")
45944
+ return notApplicable("no-legacy-account");
45945
+ row = enrolled;
45946
+ await hooks?.afterEnroll?.();
45947
+ }
45948
+ phase = "file-unreadable";
45949
+ const read = await source.read(row, { uncached: true });
45950
+ phase = "config-unavailable";
45951
+ if (!read) {
45952
+ const state = this.inspect((entry) => moveTargetState(entry, row));
45953
+ return state === "moved" ? { kind: "already-moved" } : notApplicable("no-legacy-account");
45954
+ }
45955
+ material = read;
45956
+ const state = this.inspect((entry) => moveTargetState(entry, row));
45957
+ if (state === "moved")
45958
+ return { kind: "already-moved" };
45959
+ if (state === "removed")
45960
+ return notApplicable("no-legacy-account");
45961
+ if (state === "changed") {
45962
+ throw new LegacyMoveDeferredError("target-changed", "the Codex account row changed while the move was pending");
45963
+ }
45964
+ phase = "keychain-unreadable";
45965
+ const existing = await dest.read(row, { uncached: true });
45966
+ const record = {
45967
+ ...material,
45968
+ writeId: randomBytes8(16).toString("hex"),
45969
+ updatedAt: this.now()
45970
+ };
45971
+ const resumed = existing !== null && sameDurableRecord(existing, material);
45972
+ if (!resumed) {
45973
+ phase = "keychain-write-failed";
45974
+ this.fence(holds);
45975
+ await this.persistMovedRecord(dest, row, record);
45376
45976
  }
45377
- throw new CodexOAuthRefreshError(`OpenAI Codex login expired or was revoked \u2014 run \`${LOGIN_COMMAND}\` to sign in again. (token refresh: HTTP ${response.status}${detail})`);
45977
+ phase = "config-unavailable";
45978
+ await hooks?.beforePublish?.();
45979
+ this.fence(holds);
45980
+ const published = row;
45981
+ mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
45982
+ this.fence(holds);
45983
+ if (moveTargetState(entry, published) !== "same") {
45984
+ throw new LegacyMoveDeferredError("target-changed", "the Codex account row changed while the move was pending");
45985
+ }
45986
+ const current = entry.accounts.find((a) => a.id === published.id);
45987
+ if (current)
45988
+ current.store = dest.kind;
45989
+ entry.legacyCodexSuppressed = true;
45990
+ });
45991
+ } catch (err) {
45992
+ return this.moveDeferred(err, phase);
45378
45993
  }
45379
- let tokens;
45380
45994
  try {
45381
- tokens = await response.json();
45382
- } catch (e) {
45383
- throw new CodexOAuthRefreshError(`Run \`${LOGIN_COMMAND}\` to sign in again: the Codex token endpoint returned an unreadable body (${errorMessage(e)}). A retry can succeed only if another claudish process already saved a newer token.`);
45995
+ if (opts.trigger === "request")
45996
+ getAccountRegistry().invalidate();
45997
+ else
45998
+ await this.invalidate();
45999
+ } catch (err) {
46000
+ log(`[accounts] legacy Codex move: invalidation failed: ${redactSecrets(errorText7(err))}`);
45384
46001
  }
45385
- if (typeof tokens?.access_token !== "string" || tokens.access_token.length === 0) {
45386
- throw new CodexOAuthRefreshError(`Could not refresh the OpenAI Codex login: the token endpoint returned no access token. Retry; if this persists, run \`${LOGIN_COMMAND}\`.`);
46002
+ try {
46003
+ await hooks?.beforeDelete?.();
46004
+ } catch (err) {
46005
+ log(`[accounts] legacy Codex move: stopped before the file delete: ${errorText7(err)}`);
46006
+ return { kind: "moved", id: row.id, label: row.label, fileDeleted: false };
45387
46007
  }
45388
- const accountId = (tokens.id_token ? this.extractAccountId(tokens.id_token) : undefined) ?? from.account_id;
45389
- const expiresIn = typeof tokens.expires_in === "number" ? tokens.expires_in : 3600;
45390
- return {
45391
- access_token: tokens.access_token,
45392
- refresh_token: tokens.refresh_token || from.refresh_token,
45393
- expires_at: Date.now() + expiresIn * 1000,
45394
- account_id: accountId
45395
- };
45396
- }
45397
- setCredentials(credentials) {
45398
- this.credentials = credentials;
45399
- this.credentialFileFailure = null;
46008
+ const fileDeleted = await this.deleteMovedLegacyFile(source, row, material);
46009
+ return { kind: "moved", id: row.id, label: row.label, fileDeleted };
45400
46010
  }
45401
- loadCredentials() {
45402
- const outcome = this.classifyCredentialFile();
45403
- switch (outcome.kind) {
45404
- case "present":
45405
- this.setCredentials(outcome.read.credentials);
45406
- log("[CodexOAuth] Loaded credentials from file");
46011
+ enrolSynthesizedLegacy(expected, holds) {
46012
+ const out = { value: "gone" };
46013
+ mutateAccountsConfig(this.configPath(), PROVIDER, (entry) => {
46014
+ this.fence(holds);
46015
+ const legacyRow = entry.accounts.find((a) => a.store === "legacy-codex-file");
46016
+ if (legacyRow) {
46017
+ out.value = asRecord(legacyRow);
45407
46018
  return;
45408
- case "absent":
45409
- this.setCredentials(null);
46019
+ }
46020
+ if (entry.accounts.length > 0 || entry.legacyCodexSuppressed) {
46021
+ out.value = "already-moved";
45410
46022
  return;
45411
- case "failed":
45412
- this.credentials = null;
45413
- this.credentialFileFailure = outcome.error;
45414
- log(`[CodexOAuth] Credential file present but unusable \u2014 Codex requests fail until it is fixed: ${outcome.error.message}`);
46023
+ }
46024
+ const current = this.synthesizedLegacy(entry);
46025
+ if (!current)
45415
46026
  return;
45416
- }
45417
- }
45418
- classifyCredentialFile() {
45419
- let raw;
45420
- try {
45421
- raw = this.readRawCredentialFile();
45422
- } catch (e) {
45423
- return { kind: "failed", error: asCredentialFileError(e, this.getCredentialsPath()) };
45424
- }
45425
- if (raw === null)
45426
- return { kind: "absent" };
45427
- if (typeof raw.refresh_token !== "string" || !raw.refresh_token) {
45428
- log("[CodexOAuth] Credential file names no refresh token \u2014 treating it as no login");
45429
- return { kind: "absent" };
45430
- }
45431
- try {
45432
- return { kind: "present", read: this.projectCredentialFile(raw) };
45433
- } catch (e) {
45434
- return { kind: "failed", error: asCredentialFileError(e, this.getCredentialsPath()) };
45435
- }
45436
- }
45437
- readCredentialFile() {
45438
- const raw = this.readRawCredentialFile();
45439
- return raw === null ? null : this.projectCredentialFile(raw);
46027
+ if (current.identity.chatgptAccountId !== expected.identity.chatgptAccountId) {
46028
+ throw new LegacyMoveDeferredError("target-changed", "the legacy Codex login changed while the move was pending");
46029
+ }
46030
+ const row = {
46031
+ id: this.newId(entry),
46032
+ label: current.label,
46033
+ identity: current.identity,
46034
+ store: "legacy-codex-file",
46035
+ enabled: true,
46036
+ addedAt: current.addedAt
46037
+ };
46038
+ entry.accounts.push(row);
46039
+ out.value = asRecord(row);
46040
+ });
46041
+ return out.value;
45440
46042
  }
45441
- readRawCredentialFile() {
45442
- const credPath = this.getCredentialsPath();
45443
- let text;
46043
+ async persistMovedRecord(dest, ref, record) {
46044
+ let ambiguous = false;
45444
46045
  try {
45445
- text = this.readCredentialText(credPath);
45446
- } catch (e) {
45447
- if (isAbsenceError(e))
45448
- return null;
45449
- throw credentialFileUnreadable(credPath, errorMessage(e));
46046
+ await dest.write(ref, record);
46047
+ } catch (err) {
46048
+ if (!(err instanceof TokenStoreAmbiguousWriteError)) {
46049
+ throw new LegacyMoveDeferredError("keychain-write-failed", errorText7(err));
46050
+ }
46051
+ ambiguous = true;
46052
+ log("[accounts] legacy Codex move: ambiguous Keychain write, reading it back");
45450
46053
  }
45451
- let parsed;
46054
+ let proven;
45452
46055
  try {
45453
- parsed = JSON.parse(text);
45454
- } catch {
45455
- throw credentialFileUnreadable(credPath, `it is not valid JSON (${text.length === 0 ? "empty" : "truncated or corrupted"})`);
45456
- }
45457
- if (!isRecord2(parsed)) {
45458
- throw credentialFileUnreadable(credPath, "it is not a JSON object");
46056
+ proven = await dest.read(ref, { uncached: true });
46057
+ } catch (err) {
46058
+ throw new LegacyMoveDeferredError("keychain-unverified", `the Keychain item could not be read back: ${errorText7(err)}`);
45459
46059
  }
45460
- return parsed;
45461
- }
45462
- projectCredentialFile(raw) {
45463
- const credPath = this.getCredentialsPath();
45464
- const accessToken = raw.access_token;
45465
- const refreshToken = raw.refresh_token;
45466
- const expiresAt = raw.expires_at;
45467
- if (typeof accessToken !== "string" || !accessToken || typeof refreshToken !== "string" || !refreshToken || typeof expiresAt !== "number" || !expiresAt) {
45468
- log("[CodexOAuth] Invalid credentials file structure");
45469
- throw new CodexOAuthLocalError(`The OpenAI Codex credential file (${credPath}) has no usable token pair. Run \`${LOGIN_COMMAND}\` to replace it.`);
46060
+ if (!proven || proven.writeId !== record.writeId || proven.generation !== record.generation || proven.fingerprint !== record.fingerprint) {
46061
+ throw new LegacyMoveDeferredError("keychain-unverified", ambiguous ? "the Keychain write timed out and its read-back does not show it" : "the Keychain read-back does not show this write");
45470
46062
  }
45471
- const accountId = raw.account_id;
45472
- const generation = raw.claudish_generation;
45473
- return {
45474
- credentials: {
45475
- access_token: accessToken,
45476
- refresh_token: refreshToken,
45477
- expires_at: expiresAt,
45478
- ...typeof accountId === "string" && accountId ? { account_id: accountId } : {}
45479
- },
45480
- generation: typeof generation === "number" ? generation : 0
45481
- };
45482
46063
  }
45483
- saveCredentials(credentials) {
45484
- const credPath = this.getCredentialsPath();
45485
- let existing = {};
46064
+ async deleteMovedLegacyFile(source, ref, copied) {
45486
46065
  try {
45487
- if (existsSync18(credPath)) {
45488
- const parsed = JSON.parse(readFileSync22(credPath, "utf-8"));
45489
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
45490
- existing = parsed;
46066
+ const now = await source.read(ref, { uncached: true });
46067
+ if (!now)
46068
+ return true;
46069
+ if (!sameDurableRecord(now, copied)) {
46070
+ log("[accounts] legacy Codex move: the credential file changed after the copy; kept it (the account list no longer names it)");
46071
+ return false;
45491
46072
  }
45492
- } catch {
45493
- existing = {};
46073
+ await source.delete(ref);
46074
+ return true;
46075
+ } catch (err) {
46076
+ log("[accounts] legacy Codex move: could not delete the credential file: " + redactSecrets(errorText7(err)));
46077
+ return false;
45494
46078
  }
45495
- const previousGeneration = typeof existing.claudish_generation === "number" ? existing.claudish_generation : 0;
45496
- const document = {
45497
- ...existing,
45498
- ...credentials,
45499
- claudish_generation: previousGeneration + 1
45500
- };
45501
- atomicWriteFileSync(credPath, JSON.stringify(document, null, 2), { mode: 384 });
45502
- log(`[CodexOAuth] Credentials saved to ${credPath}`);
45503
46079
  }
45504
- extractAccountId(idToken) {
46080
+ async deleteLeftover() {
46081
+ const legacy = {
46082
+ id: LEGACY_CODEX_ACCOUNT_ID,
46083
+ label: LEGACY_CODEX_LABEL,
46084
+ provider: PROVIDER,
46085
+ identity: {},
46086
+ store: "legacy-codex-file",
46087
+ enabled: true,
46088
+ addedAt: "",
46089
+ legacy: true
46090
+ };
45505
46091
  try {
45506
- const parts = idToken.split(".");
45507
- if (parts.length !== 3)
45508
- return;
45509
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
45510
- const authClaim = payload["https://api.openai.com/auth"];
45511
- const accountId = authClaim?.chatgpt_account_id || payload.chatgpt_account_id || authClaim?.user_id;
45512
- if (accountId) {
45513
- log(`[CodexOAuth] Extracted account ID from id_token: ${accountId}`);
45514
- return accountId;
45515
- }
45516
- return;
45517
- } catch (e) {
45518
- log(`[CodexOAuth] Failed to extract account ID from id_token: ${e.message}`);
45519
- return;
46092
+ return await this.gate(legacy, async () => {
46093
+ const still = this.inspect((entry) => entry.legacyCodexSuppressed === true && !entry.accounts.some((row) => row.store === "legacy-codex-file") && this.legacyFilePresent());
46094
+ if (!still)
46095
+ return notApplicable("no-legacy-account");
46096
+ await this.store("legacy-codex-file").delete(legacy);
46097
+ log("[accounts] removed the leftover legacy Codex credential file; nothing reads it");
46098
+ return { kind: "leftover-deleted" };
46099
+ });
46100
+ } catch (err) {
46101
+ log(`[accounts] leftover legacy Codex file not removed: ${redactSecrets(errorText7(err))}`);
46102
+ return notApplicable("no-legacy-account");
45520
46103
  }
45521
46104
  }
46105
+ moveDeferred(err, fallback) {
46106
+ const reason = moveDeferralOf(err, fallback);
46107
+ const detail = redactSecrets(errorText7(err));
46108
+ log(`[accounts] legacy Codex move deferred (${reason}): ${detail}`);
46109
+ return { kind: "deferred", reason, detail };
46110
+ }
45522
46111
  }
45523
- function getCodexOAuth() {
45524
- return CodexOAuth.getInstance();
46112
+ function notApplicable(reason) {
46113
+ return { kind: "not-applicable", reason };
45525
46114
  }
45526
- var LOGIN_COMMAND = "claudish login codex", CodexOAuthRefreshError, CodexOAuthLocalError, CODEX_EXCHANGE_TIMEOUT_MS;
45527
- var init_codex_oauth = __esm(() => {
45528
- init_connection_error();
45529
- init_transient_retry();
46115
+ function moveTargetState(entry, row) {
46116
+ const current = entry.accounts.find((a) => a.id === row.id);
46117
+ if (!current)
46118
+ return "removed";
46119
+ if (current.identity?.chatgptAccountId !== row.identity.chatgptAccountId)
46120
+ return "changed";
46121
+ if (current.store !== "legacy-codex-file")
46122
+ return "moved";
46123
+ return "same";
46124
+ }
46125
+ function moveDeferralOf(err, fallback) {
46126
+ if (err instanceof LegacyMoveDeferredError)
46127
+ return err.reason;
46128
+ if (err instanceof AccountListUnavailableError && err.failure?.source === "legacy") {
46129
+ return "file-unreadable";
46130
+ }
46131
+ if (err instanceof RefreshUnavailableError)
46132
+ return "gate-busy";
46133
+ return fallback;
46134
+ }
46135
+ function errorText7(err) {
46136
+ return err instanceof Error ? err.message : String(err);
46137
+ }
46138
+ var PROVIDER = "openai-codex", OWNED_ACCOUNTS_NOTICE, LEGACY_LOGIN_REFUSED, LEGACY_SAVE_WAIT_MS = 15000, LegacyMoveDeferredError, MOVE_HINTS;
46139
+ var init_management = __esm(() => {
46140
+ init_dist12();
45530
46141
  init_logger();
45531
- init_test_upstream();
45532
- init_atomic_write();
45533
- init_exchange();
46142
+ init_profile_config();
46143
+ init_redact();
46144
+ init_codex_pkce();
46145
+ init_account_label();
46146
+ init_config_lock();
46147
+ init_file_lock();
46148
+ init_legacy_move();
45534
46149
  init_paths();
45535
46150
  init_refresh_lock();
46151
+ init_registry();
46152
+ init_state_store();
46153
+ init_token_store();
45536
46154
  init_types();
45537
- init_codex_pkce();
45538
- CodexOAuthRefreshError = class CodexOAuthRefreshError extends Error {
45539
- terminal = true;
45540
- upstreamStatus = 401;
45541
- constructor(message) {
45542
- super(message);
45543
- this.name = "CodexOAuthRefreshError";
46155
+ OWNED_ACCOUNTS_NOTICE = "Add only Codex accounts you own and pay for. Switching between subscriptions may be " + "restricted by OpenAI's terms; this tool is not permission to bypass them. Accept and continue?";
46156
+ LEGACY_LOGIN_REFUSED = "Codex accounts are managed by `claudish account`, or the legacy login was removed. " + "Run `claudish account add codex` for a new account, or " + "`claudish account login codex <label>` to log an existing one in again.";
46157
+ LegacyMoveDeferredError = class LegacyMoveDeferredError extends Error {
46158
+ reason;
46159
+ constructor(reason, detail) {
46160
+ super(detail);
46161
+ this.reason = reason;
46162
+ this.name = "LegacyMoveDeferredError";
45544
46163
  }
45545
46164
  };
45546
- CodexOAuthLocalError = class CodexOAuthLocalError extends Error {
45547
- transient = true;
45548
- constructor(message) {
45549
- super(message);
45550
- this.name = "CodexOAuthLocalError";
45551
- }
46165
+ MOVE_HINTS = {
46166
+ "gate-busy": "Retry in a moment.",
46167
+ "config-unavailable": "Retry in a moment; if this persists, repair config.json.",
46168
+ "target-changed": "Retry in a moment.",
46169
+ "file-unreadable": "Repair or remove the file, then retry.",
46170
+ "keychain-unreadable": "Unlock the login keychain and retry.",
46171
+ "keychain-write-failed": "Unlock the login keychain and retry.",
46172
+ "keychain-unverified": "Unlock the login keychain and retry."
45552
46173
  };
45553
- CODEX_EXCHANGE_TIMEOUT_MS = Math.min(TOKEN_REFRESH_TIMEOUT_MS, EXCHANGE_TIMEOUT_MS);
46174
+ });
46175
+
46176
+ // src/auth/accounts/legacy-move.ts
46177
+ import { lstatSync as lstatSync4, realpathSync as realpathSync3 } from "fs";
46178
+ import { join as join22, resolve as resolve2 } from "path";
46179
+ function legacyMoveKeychainBlocked(env = process.env) {
46180
+ return env.CLAUDISH_DISABLE_KEYCHAIN === "1" || !isKeychainSupported();
46181
+ }
46182
+ function canonical(path) {
46183
+ try {
46184
+ return realpathSync3(path);
46185
+ } catch {
46186
+ return resolve2(path);
46187
+ }
46188
+ }
46189
+ function isDefaultConfigPath(configPath, env = process.env) {
46190
+ return canonical(configPath) === canonical(join22(claudishHome(env), "config.json"));
46191
+ }
46192
+ function legacyMoveApplies(env, configPath, registry) {
46193
+ if (legacyMoveKeychainBlocked(env))
46194
+ return false;
46195
+ let due;
46196
+ try {
46197
+ const snapshot = registry.snapshot("openai-codex");
46198
+ const rows = snapshot.accounts["openai-codex"];
46199
+ const legacyRow = rows.some((row) => row.legacy || row.store === "legacy-codex-file");
46200
+ due = legacyRow || snapshot.settings["openai-codex"]?.legacyCodexSuppressed === true && legacyFileExists(env);
46201
+ } catch {
46202
+ return false;
46203
+ }
46204
+ if (!due || !isDefaultConfigPath(configPath, env))
46205
+ return false;
46206
+ return defaultAccountStore(env).kind === "keychain";
46207
+ }
46208
+ function legacyFileExists(env) {
46209
+ try {
46210
+ lstatSync4(legacyCodexCredentialsPath(env));
46211
+ return true;
46212
+ } catch {
46213
+ return false;
46214
+ }
46215
+ }
46216
+ function startLegacyCodexMove(opts = {}) {
46217
+ try {
46218
+ if (inFlight2)
46219
+ return;
46220
+ const now = opts.now ?? Date.now;
46221
+ if (now() < cooldownUntil)
46222
+ return;
46223
+ const env = opts.env ?? process.env;
46224
+ const configPath = (opts.configPath ?? activeConfigFile)();
46225
+ if (!legacyMoveApplies(env, configPath, opts.registry ?? getAccountRegistry()))
46226
+ return;
46227
+ const move = (async () => {
46228
+ await Promise.resolve().then(() => init_management());
46229
+ return moveLegacyCodexLogin({ env, configPath: () => configPath, ...opts.management }, { trigger: "request" });
46230
+ })().catch((err) => ({
46231
+ kind: "deferred",
46232
+ reason: "config-unavailable",
46233
+ detail: redactSecrets(err instanceof Error ? err.message : String(err))
46234
+ })).then((outcome) => {
46235
+ log(`[accounts] legacy Codex move (request path): ${outcome.kind}`);
46236
+ if (outcome.kind === "deferred" || outcome.kind === "not-applicable") {
46237
+ cooldownUntil = now() + LEGACY_MOVE_RETRY_MS;
46238
+ }
46239
+ return outcome;
46240
+ }).finally(() => {
46241
+ if (inFlight2 === move)
46242
+ inFlight2 = null;
46243
+ });
46244
+ inFlight2 = move;
46245
+ lastMove = move;
46246
+ } catch (err) {
46247
+ log(`[accounts] legacy Codex move not started: ${err instanceof Error ? err.message : String(err)}`);
46248
+ }
46249
+ }
46250
+ var LEGACY_MOVE_RETRY_MS, inFlight2 = null, lastMove = null, cooldownUntil = 0;
46251
+ var init_legacy_move = __esm(() => {
46252
+ init_logger();
46253
+ init_profile_config();
46254
+ init_keychain();
46255
+ init_redact();
46256
+ init_paths();
46257
+ init_registry();
46258
+ init_token_store();
46259
+ LEGACY_MOVE_RETRY_MS = 10 * 60 * 1000;
45554
46260
  });
45555
46261
 
45556
46262
  // src/auth/credentials/composite-credential.ts
@@ -45654,6 +46360,9 @@ class CodexOAuthHalf {
45654
46360
  }
45655
46361
  async getRequestAuth(ctx) {
45656
46362
  if (ctx.requireOAuth && !this.oauth.hasCredentials()) {
46363
+ const why = await this.oauth.missingLoginError();
46364
+ if (why instanceof CodexOAuthLocalError)
46365
+ throw why;
45657
46366
  throw new MeteredEscapeError("Codex OAuth login disappeared during this request");
45658
46367
  }
45659
46368
  const token = ctx.forceRefresh ? await this.oauth.forceRefresh() : await this.oauth.getAccessToken();
@@ -45693,6 +46402,7 @@ class CodexPoolHalf {
45693
46402
  return this.registry.hasConfiguredAccounts("openai-codex");
45694
46403
  }
45695
46404
  async getRequestAuth(ctx) {
46405
+ startLegacyCodexMove();
45696
46406
  if (!ctx.pin && this.registry.isSynthesizedLegacyOnly()) {
45697
46407
  return this.legacy.getRequestAuth(ctx);
45698
46408
  }
@@ -45733,6 +46443,7 @@ var CODEX_RESPONSES_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses"
45733
46443
  var init_codex_credential = __esm(() => {
45734
46444
  init_test_upstream();
45735
46445
  init_errors3();
46446
+ init_legacy_move();
45736
46447
  init_pool();
45737
46448
  init_registry();
45738
46449
  init_codex_oauth();
@@ -45743,9 +46454,9 @@ var init_codex_credential = __esm(() => {
45743
46454
  // src/providers/devin/devin-credentials.ts
45744
46455
  import { readFileSync as readFileSync23 } from "fs";
45745
46456
  import { homedir as homedir22 } from "os";
45746
- import { join as join22 } from "path";
46457
+ import { join as join23 } from "path";
45747
46458
  function devinCredentialsPath() {
45748
- return credentialsPathOverride ?? join22(homedir22(), ".local", "share", "devin", "credentials.toml");
46459
+ return credentialsPathOverride ?? join23(homedir22(), ".local", "share", "devin", "credentials.toml");
45749
46460
  }
45750
46461
  function readTomlString(source, key) {
45751
46462
  const match = source.match(new RegExp(`^\\s*${key}\\s*=\\s*"([^"]*)"`, "m"));
@@ -45885,13 +46596,13 @@ import {
45885
46596
  writeSync as writeSync6
45886
46597
  } from "fs";
45887
46598
  import { homedir as homedir23, hostname as hostname3, platform, release as release2 } from "os";
45888
- import { dirname as dirname12, join as join23 } from "path";
46599
+ import { dirname as dirname12, join as join24 } from "path";
45889
46600
  import { promisify as promisify4 } from "util";
45890
46601
  function kimiOAuthPath() {
45891
- return kimiOAuthPathOverride ?? join23(homedir23(), ".claudish", "kimi-oauth.json");
46602
+ return kimiOAuthPathOverride ?? join24(homedir23(), ".claudish", "kimi-oauth.json");
45892
46603
  }
45893
46604
  function kimiDeviceIdPath() {
45894
- return kimiDeviceIdPathOverride ?? join23(homedir23(), ".claudish", "kimi-device-id");
46605
+ return kimiDeviceIdPathOverride ?? join24(homedir23(), ".claudish", "kimi-device-id");
45895
46606
  }
45896
46607
  function isKimiCredentials(data) {
45897
46608
  if (!data || typeof data !== "object")
@@ -46236,7 +46947,7 @@ var init_kimi_oauth = __esm(() => {
46236
46947
  // src/auth/oauth-registry.ts
46237
46948
  import { existsSync as existsSync20, readFileSync as readFileSync25 } from "fs";
46238
46949
  import { homedir as homedir24 } from "os";
46239
- import { join as join24 } from "path";
46950
+ import { join as join25 } from "path";
46240
46951
  function credentialSatisfies(descriptor, data) {
46241
46952
  if (!data?.access_token)
46242
46953
  return false;
@@ -46249,7 +46960,7 @@ function credentialSatisfies(descriptor, data) {
46249
46960
  return true;
46250
46961
  }
46251
46962
  function hasValidOAuthCredentials(descriptor) {
46252
- const credPath = join24(homedir24(), ".claudish", descriptor.credentialFile);
46963
+ const credPath = join25(homedir24(), ".claudish", descriptor.credentialFile);
46253
46964
  if (!existsSync20(credPath))
46254
46965
  return false;
46255
46966
  if (descriptor.validationMode === "file-exists") {
@@ -46265,10 +46976,18 @@ function hasOAuthCredentials(providerName) {
46265
46976
  const descriptor = OAUTH_PROVIDERS[providerName];
46266
46977
  if (!descriptor)
46267
46978
  return false;
46979
+ if (providerName === "openai-codex") {
46980
+ try {
46981
+ return getAccountRegistry().hasConfiguredAccounts("openai-codex");
46982
+ } catch {
46983
+ return false;
46984
+ }
46985
+ }
46268
46986
  return hasValidOAuthCredentials(descriptor);
46269
46987
  }
46270
46988
  var OAUTH_PROVIDERS;
46271
46989
  var init_oauth_registry = __esm(() => {
46990
+ init_registry();
46272
46991
  OAUTH_PROVIDERS = {
46273
46992
  "kimi-coding": {
46274
46993
  credentialFile: "kimi-oauth.json",
@@ -46442,12 +47161,12 @@ import {
46442
47161
  writeFileSync as writeFileSync9
46443
47162
  } from "fs";
46444
47163
  import { homedir as homedir25 } from "os";
46445
- import { join as join25 } from "path";
47164
+ import { join as join26 } from "path";
46446
47165
  function defaultMuseStoreDir() {
46447
- return join25(homedir25(), ".claudish");
47166
+ return join26(homedir25(), ".claudish");
46448
47167
  }
46449
47168
  function museStorePath(kind, dir = defaultMuseStoreDir()) {
46450
- return join25(dir, FILE_NAMES[kind]);
47169
+ return join26(dir, FILE_NAMES[kind]);
46451
47170
  }
46452
47171
  function isValidMuseStore(value) {
46453
47172
  if (!value || typeof value !== "object")
@@ -46502,7 +47221,7 @@ var init_muse_store = __esm(() => {
46502
47221
 
46503
47222
  // src/providers/meta-muse/muse-keychain.ts
46504
47223
  import { mkdirSync as mkdirSync14, readFileSync as readFileSync27, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync10 } from "fs";
46505
- import { join as join26 } from "path";
47224
+ import { join as join27 } from "path";
46506
47225
  function processExists(pid) {
46507
47226
  try {
46508
47227
  process.kill(pid, 0);
@@ -46515,7 +47234,7 @@ function resetMuseKeychainState() {
46515
47234
  keychainEpoch++;
46516
47235
  retainedToken = null;
46517
47236
  lateResult = null;
46518
- inFlight2 = null;
47237
+ inFlight3 = null;
46519
47238
  }
46520
47239
  function disabled() {
46521
47240
  return deps2.env().CLAUDISH_DISABLE_KEYCHAIN === "1";
@@ -46552,7 +47271,7 @@ function parseMuseKeychainValue(value) {
46552
47271
  }
46553
47272
  }
46554
47273
  function lockPath2() {
46555
- return join26(deps2.dir(), LOCK_FILE);
47274
+ return join27(deps2.dir(), LOCK_FILE);
46556
47275
  }
46557
47276
  function readLock() {
46558
47277
  let mtime;
@@ -46639,8 +47358,8 @@ async function withMuseKeychainLogin(use, deadlineMs = MUSE_KEYCHAIN_READ_DEADLI
46639
47358
  lateResult = null;
46640
47359
  return settle(result, use);
46641
47360
  }
46642
- if (inFlight2)
46643
- return raceJoin(inFlight2, deadline, use);
47361
+ if (inFlight3)
47362
+ return raceJoin(inFlight3, deadline, use);
46644
47363
  if (deps2.now() >= deadline)
46645
47364
  return { kind: "timeout" };
46646
47365
  const lock = readLock();
@@ -46679,7 +47398,7 @@ async function withMuseKeychainLogin(use, deadlineMs = MUSE_KEYCHAIN_READ_DEADLI
46679
47398
  const done = spawn.done.finally(() => {
46680
47399
  rewriteLock(deps2.pid(), "claudish");
46681
47400
  });
46682
- inFlight2 = done;
47401
+ inFlight3 = done;
46683
47402
  inFlightEpoch = keychainEpoch;
46684
47403
  return raceJoin(done, deadline, use);
46685
47404
  }
@@ -46696,16 +47415,16 @@ async function raceJoin(done, deadline, use) {
46696
47415
  if (winner === "timeout") {
46697
47416
  const startedIn = inFlightEpoch;
46698
47417
  done.then((result) => {
46699
- if (inFlight2 === done)
46700
- inFlight2 = null;
47418
+ if (inFlight3 === done)
47419
+ inFlight3 = null;
46701
47420
  if (startedIn === keychainEpoch)
46702
47421
  lateResult = result;
46703
47422
  releaseLock();
46704
47423
  });
46705
47424
  return { kind: "timeout" };
46706
47425
  }
46707
- if (inFlight2 === done)
46708
- inFlight2 = null;
47426
+ if (inFlight3 === done)
47427
+ inFlight3 = null;
46709
47428
  try {
46710
47429
  return await settle(winner, use);
46711
47430
  } finally {
@@ -46750,7 +47469,7 @@ async function waitForLockThenCopy(deadline, sinceMtime) {
46750
47469
  await deps2.sleep(Math.min(LOCK_POLL_MS, Math.max(0, deadline - deps2.now())));
46751
47470
  }
46752
47471
  }
46753
- var MUSE_KEYCHAIN_SERVICE = "ai.meta.dev.credentials", MUSE_KEYCHAIN_ACCOUNT = "meta", LOCK_FILE = "muse-keychain-read.lock", PRESENCE_TTL_MS = 3000, LOCK_POLL_MS = 500, LOCK_MAX_AGE_MS, UNREADABLE_LOCK_GRACE_MS = 5000, MUSE_KEYCHAIN_READ_DEADLINE_MS = 120000, defaultDeps4, deps2, presenceMemo = null, inFlight2 = null, lateResult = null, keychainEpoch = 0, inFlightEpoch = 0, holdsLock = false, retainedToken = null;
47472
+ var MUSE_KEYCHAIN_SERVICE = "ai.meta.dev.credentials", MUSE_KEYCHAIN_ACCOUNT = "meta", LOCK_FILE = "muse-keychain-read.lock", PRESENCE_TTL_MS = 3000, LOCK_POLL_MS = 500, LOCK_MAX_AGE_MS, UNREADABLE_LOCK_GRACE_MS = 5000, MUSE_KEYCHAIN_READ_DEADLINE_MS = 120000, defaultDeps4, deps2, presenceMemo = null, inFlight3 = null, lateResult = null, keychainEpoch = 0, inFlightEpoch = 0, holdsLock = false, retainedToken = null;
46754
47473
  var init_muse_keychain = __esm(() => {
46755
47474
  init_prompt_role();
46756
47475
  init_keychain();
@@ -50314,7 +51033,7 @@ var init_vision_proxy = __esm(() => {
50314
51033
 
50315
51034
  // src/mcp-session/home-dir.ts
50316
51035
  import { homedir as homedir27 } from "os";
50317
- import { join as join27 } from "path";
51036
+ import { join as join28 } from "path";
50318
51037
  function userHomeFrom(env, homedirFn = homedir27) {
50319
51038
  const home = env.HOME;
50320
51039
  return home && home.length > 0 ? home : homedirFn();
@@ -50323,7 +51042,7 @@ function sessionsDirFrom(env, homedirFn = homedir27) {
50323
51042
  const explicit = env.CLAUDISH_SESSIONS_DIR;
50324
51043
  if (explicit && explicit.length > 0)
50325
51044
  return explicit;
50326
- return join27(userHomeFrom(env, homedirFn), ".claudish", "sessions");
51045
+ return join28(userHomeFrom(env, homedirFn), ".claudish", "sessions");
50327
51046
  }
50328
51047
  function claudeCodeStringHash(s) {
50329
51048
  let h = 0;
@@ -50498,7 +51217,7 @@ var init_transcript_tailer = () => {};
50498
51217
  // src/session-events/index.ts
50499
51218
  import { existsSync as existsSync22, readFileSync as readFileSync28, readdirSync as readdirSync3 } from "fs";
50500
51219
  import { homedir as homedir28 } from "os";
50501
- import { join as join28 } from "path";
51220
+ import { join as join29 } from "path";
50502
51221
  function extractSessionId2(metadata) {
50503
51222
  const userId = metadata?.user_id;
50504
51223
  if (typeof userId !== "string")
@@ -50523,7 +51242,7 @@ class SessionEventRegistry {
50523
51242
  claudeHome;
50524
51243
  pollIntervalMs;
50525
51244
  constructor(opts = {}) {
50526
- this.claudeHome = opts.claudeHome ?? join28(homedir28(), ".claude");
51245
+ this.claudeHome = opts.claudeHome ?? join29(homedir28(), ".claude");
50527
51246
  this.pollIntervalMs = opts.pollIntervalMs;
50528
51247
  }
50529
51248
  ensureSession(sessionId) {
@@ -50609,13 +51328,13 @@ class SessionEventRegistry {
50609
51328
  }
50610
51329
  }
50611
51330
  locateTranscript(sessionId) {
50612
- const projectsDir = join28(this.claudeHome, "projects");
50613
- const primary = join28(projectsDir, slugFromCwd(process.cwd()), `${sessionId}.jsonl`);
51331
+ const projectsDir = join29(this.claudeHome, "projects");
51332
+ const primary = join29(projectsDir, slugFromCwd(process.cwd()), `${sessionId}.jsonl`);
50614
51333
  if (existsSync22(primary))
50615
51334
  return primary;
50616
51335
  try {
50617
51336
  for (const dir of readdirSync3(projectsDir)) {
50618
- const candidate = join28(projectsDir, dir, `${sessionId}.jsonl`);
51337
+ const candidate = join29(projectsDir, dir, `${sessionId}.jsonl`);
50619
51338
  if (existsSync22(candidate))
50620
51339
  return candidate;
50621
51340
  }
@@ -50624,7 +51343,7 @@ class SessionEventRegistry {
50624
51343
  }
50625
51344
  readSettingsEffortLevel() {
50626
51345
  try {
50627
- const settings = JSON.parse(readFileSync28(join28(this.claudeHome, "settings.json"), "utf-8"));
51346
+ const settings = JSON.parse(readFileSync28(join29(this.claudeHome, "settings.json"), "utf-8"));
50628
51347
  return typeof settings.effortLevel === "string" ? settings.effortLevel : undefined;
50629
51348
  } catch {
50630
51349
  return;
@@ -50735,7 +51454,7 @@ import {
50735
51454
  writeFileSync as writeFileSync11
50736
51455
  } from "fs";
50737
51456
  import { homedir as homedir29 } from "os";
50738
- import { join as join29 } from "path";
51457
+ import { join as join30 } from "path";
50739
51458
  function ensureDir() {
50740
51459
  if (!existsSync23(CLAUDISH_DIR)) {
50741
51460
  mkdirSync15(CLAUDISH_DIR, { recursive: true });
@@ -50770,7 +51489,7 @@ function writeToDisk(events) {
50770
51489
  ensureDir();
50771
51490
  const trimmed = enforceSizeCap([...events]);
50772
51491
  const payload = { version: 1, events: trimmed };
50773
- const tmpFile = join29(CLAUDISH_DIR, `stats-buffer.tmp.${process.pid}.json`);
51492
+ const tmpFile = join30(CLAUDISH_DIR, `stats-buffer.tmp.${process.pid}.json`);
50774
51493
  writeFileSync11(tmpFile, JSON.stringify(payload, null, 2), "utf-8");
50775
51494
  renameSync8(tmpFile, BUFFER_FILE);
50776
51495
  memoryCache = trimmed;
@@ -50843,8 +51562,8 @@ function syncFlushOnExit() {
50843
51562
  var BUFFER_MAX_BYTES, CLAUDISH_DIR, BUFFER_FILE, memoryCache = null, eventsSinceLastFlush = 0, flushScheduled = false, SIGNAL_EXIT_CODE;
50844
51563
  var init_stats_buffer = __esm(() => {
50845
51564
  BUFFER_MAX_BYTES = 64 * 1024;
50846
- CLAUDISH_DIR = join29(homedir29(), ".claudish");
50847
- BUFFER_FILE = join29(CLAUDISH_DIR, "stats-buffer.json");
51565
+ CLAUDISH_DIR = join30(homedir29(), ".claudish");
51566
+ BUFFER_FILE = join30(CLAUDISH_DIR, "stats-buffer.json");
50848
51567
  process.on("exit", syncFlushOnExit);
50849
51568
  SIGNAL_EXIT_CODE = { SIGTERM: 143, SIGINT: 130 };
50850
51569
  for (const signal of ["SIGTERM", "SIGINT"]) {
@@ -54891,7 +55610,7 @@ var init_openai_responses_sse = __esm(() => {
54891
55610
  });
54892
55611
 
54893
55612
  // src/session/token-file.ts
54894
- import { join as join30 } from "path";
55613
+ import { join as join31 } from "path";
54895
55614
  function assignedTokenFile(env) {
54896
55615
  const f = env.CLAUDISH_TOKEN_FILE;
54897
55616
  if (!f || f === env.CLAUDISH_PUBLISHED_TOKEN_FILE)
@@ -54899,7 +55618,7 @@ function assignedTokenFile(env) {
54899
55618
  return f;
54900
55619
  }
54901
55620
  function resolveTokenFilePath(port, env = process.env) {
54902
- return assignedTokenFile(env) ?? join30(userHomeFrom(env), ".claudish", `tokens-${port}.json`);
55621
+ return assignedTokenFile(env) ?? join31(userHomeFrom(env), ".claudish", `tokens-${port}.json`);
54903
55622
  }
54904
55623
  var init_token_file = __esm(() => {
54905
55624
  init_home_dir();
@@ -57340,9 +58059,9 @@ var init_composed_handler = __esm(() => {
57340
58059
  // src/providers/api-key-provenance.ts
57341
58060
  import { existsSync as existsSync24, readFileSync as readFileSync30 } from "fs";
57342
58061
  import { homedir as homedir30 } from "os";
57343
- import { join as join31, resolve as resolve2 } from "path";
58062
+ import { join as join32, resolve as resolve3 } from "path";
57344
58063
  function activeConfigPath() {
57345
- return activeGlobalConfigFile(join31(homedir30(), ".claudish", "config.json"));
58064
+ return activeGlobalConfigFile(join32(homedir30(), ".claudish", "config.json"));
57346
58065
  }
57347
58066
  function configLayerLabel() {
57348
58067
  return getConfigFileOverride() ? activeConfigPath() : "~/.claudish/config.json";
@@ -57360,7 +58079,7 @@ function resolveApiKeyProvenance(envVar, aliases) {
57360
58079
  const allVars = [envVar, ...aliases || []];
57361
58080
  const dotenvValue = readDotenvKey(allVars);
57362
58081
  layers.push({
57363
- source: `.env (${resolve2(".env")})`,
58082
+ source: `.env (${resolve3(".env")})`,
57364
58083
  maskedValue: maskKey(dotenvValue),
57365
58084
  isActive: false
57366
58085
  });
@@ -57466,7 +58185,7 @@ function formatProvenanceLog(p) {
57466
58185
  }
57467
58186
  function readDotenvKey(envVars) {
57468
58187
  try {
57469
- const dotenvPath = resolve2(".env");
58188
+ const dotenvPath = resolve3(".env");
57470
58189
  if (!existsSync24(dotenvPath))
57471
58190
  return null;
57472
58191
  const parsed = readAllowedDotenv(readFileSync30(dotenvPath, "utf-8"));
@@ -71181,7 +71900,7 @@ function compareByConnectionPrice(a, b) {
71181
71900
  // src/providers/probe-catalog.ts
71182
71901
  import { existsSync as existsSync25, mkdirSync as mkdirSync17, readFileSync as readFileSync31, writeFileSync as writeFileSync13 } from "fs";
71183
71902
  import { homedir as homedir31 } from "os";
71184
- import { dirname as dirname14, join as join32 } from "path";
71903
+ import { dirname as dirname14, join as join33 } from "path";
71185
71904
  function describeProbeCatalogFailure(outcome) {
71186
71905
  switch (outcome.kind) {
71187
71906
  case "incompatible":
@@ -71352,7 +72071,7 @@ var PROBE_MODELS_URL = "https://us-central1-claudish-6da10.cloudfunctions.net/pr
71352
72071
  var init_probe_catalog = __esm(() => {
71353
72072
  init_catalog_route_bindings();
71354
72073
  CACHE_TTL_MS4 = 60 * 60 * 1000;
71355
- PROBE_MODELS_CACHE_PATH = join32(homedir31(), ".claudish", "probe-models.json");
72074
+ PROBE_MODELS_CACHE_PATH = join33(homedir31(), ".claudish", "probe-models.json");
71356
72075
  });
71357
72076
 
71358
72077
  // src/providers/route-candidates.ts
@@ -73369,7 +74088,7 @@ var init_slot_row = __esm(() => {
73369
74088
 
73370
74089
  // src/pane/prompt-delivery.ts
73371
74090
  import { writeFileSync as writeFileSync14 } from "fs";
73372
- import { join as join33 } from "path";
74091
+ import { join as join34 } from "path";
73373
74092
  function stripControl(s) {
73374
74093
  let out = "";
73375
74094
  for (const ch of s) {
@@ -73436,7 +74155,7 @@ function renderTurnFile(text, limit = READ_LINE_LIMIT) {
73436
74155
  `), lines: out.length, marker };
73437
74156
  }
73438
74157
  function turnFilePath(turnDir, turnIndex) {
73439
- return join33(turnDir, `turn-${turnIndex}.md`);
74158
+ return join34(turnDir, `turn-${turnIndex}.md`);
73440
74159
  }
73441
74160
  function fileTemplate(file, marker) {
73442
74161
  const base = `Your task is in the file \`${file}\`. Read all of it with the Read tool (in parts if it is long), then do exactly what it says, treating its content as the user's message.`;
@@ -73517,12 +74236,12 @@ var init_prompt_delivery = __esm(() => {
73517
74236
 
73518
74237
  // src/team-stats.ts
73519
74238
  import { existsSync as existsSync26, readFileSync as readFileSync32, writeFileSync as writeFileSync15 } from "fs";
73520
- import { join as join34 } from "path";
74239
+ import { join as join35 } from "path";
73521
74240
  function statsDir(sessionPath) {
73522
- return join34(sessionPath, "stats");
74241
+ return join35(sessionPath, "stats");
73523
74242
  }
73524
74243
  function tokenFileFor(sessionPath, anonId) {
73525
- return join34(statsDir(sessionPath), `${anonId}.json`);
74244
+ return join35(statsDir(sessionPath), `${anonId}.json`);
73526
74245
  }
73527
74246
  function readTokenStatsAt(path) {
73528
74247
  if (!existsSync26(path))
@@ -73685,7 +74404,7 @@ ${segs.join(" \xB7 ")}`;
73685
74404
  }
73686
74405
  function writeStatusFile(sessionPath, manifest, status, opts) {
73687
74406
  try {
73688
- writeFileSync15(join34(sessionPath, "status.txt"), `${renderTeamStats(sessionPath, manifest, status, opts)}
74407
+ writeFileSync15(join35(sessionPath, "status.txt"), `${renderTeamStats(sessionPath, manifest, status, opts)}
73689
74408
  `, "utf-8");
73690
74409
  } catch {}
73691
74410
  }
@@ -73928,7 +74647,7 @@ var init_effort_recommendations = __esm(() => {
73928
74647
  // src/model-loader.ts
73929
74648
  import { existsSync as existsSync27, readFileSync as readFileSync34 } from "fs";
73930
74649
  import { homedir as homedir32 } from "os";
73931
- import { join as join35 } from "path";
74650
+ import { join as join36 } from "path";
73932
74651
  function isRecommendationTier(value) {
73933
74652
  return typeof value === "string" && RECOMMENDATION_TIER_INDEX.has(value);
73934
74653
  }
@@ -74509,7 +75228,7 @@ var init_model_loader = __esm(() => {
74509
75228
  ];
74510
75229
  RECOMMENDATION_TIER_INDEX = new Map(RECOMMENDATION_SECTIONS.map((section, index) => [section.recommendationTier, index]));
74511
75230
  FIREBASE_RECOMMENDED_URL = `${FIREBASE_BASE_URL}?catalog=recommended`;
74512
- RECOMMENDED_MODELS_CACHE_PATH = join35(homedir32(), ".claudish", "recommended-models-cache-v2.json");
75231
+ RECOMMENDED_MODELS_CACHE_PATH = join36(homedir32(), ".claudish", "recommended-models-cache-v2.json");
74513
75232
  RECOMMENDED_ROUTE_TIER_ORDER = {
74514
75233
  native: 0,
74515
75234
  general: 1,
@@ -75807,7 +76526,7 @@ var init_DiscoveryNotice = __esm(() => {
75807
76526
  // src/providers/model-descriptions.ts
75808
76527
  import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync35, writeFileSync as writeFileSync16 } from "fs";
75809
76528
  import { homedir as homedir33 } from "os";
75810
- import { dirname as dirname15, join as join36 } from "path";
76529
+ import { dirname as dirname15, join as join37 } from "path";
75811
76530
  function emptyDescriptionIndex() {
75812
76531
  return EMPTY_INDEX;
75813
76532
  }
@@ -75900,7 +76619,7 @@ var init_model_descriptions = __esm(() => {
75900
76619
  init_logger();
75901
76620
  init_model_loader();
75902
76621
  init_cache_ttl();
75903
- MODEL_DESCRIPTIONS_CACHE_PATH = join36(homedir33(), ".claudish", "model-descriptions.json");
76622
+ MODEL_DESCRIPTIONS_CACHE_PATH = join37(homedir33(), ".claudish", "model-descriptions.json");
75904
76623
  EMPTY_INDEX = { get: () => {
75905
76624
  return;
75906
76625
  }, size: 0 };
@@ -82233,22 +82952,22 @@ var init_branding = __esm(() => {
82233
82952
  // src/update-checker.ts
82234
82953
  import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync36, unlinkSync as unlinkSync9, writeFileSync as writeFileSync17 } from "fs";
82235
82954
  import { homedir as homedir34, platform as platform2, tmpdir } from "os";
82236
- import { join as join37 } from "path";
82955
+ import { join as join38 } from "path";
82237
82956
  function getCacheFilePath() {
82238
82957
  let cacheDir;
82239
82958
  if (isWindows) {
82240
- const localAppData = process.env.LOCALAPPDATA || join37(homedir34(), "AppData", "Local");
82241
- cacheDir = join37(localAppData, "claudish");
82959
+ const localAppData = process.env.LOCALAPPDATA || join38(homedir34(), "AppData", "Local");
82960
+ cacheDir = join38(localAppData, "claudish");
82242
82961
  } else {
82243
- cacheDir = join37(homedir34(), ".cache", "claudish");
82962
+ cacheDir = join38(homedir34(), ".cache", "claudish");
82244
82963
  }
82245
82964
  try {
82246
82965
  if (!existsSync29(cacheDir)) {
82247
82966
  mkdirSync19(cacheDir, { recursive: true });
82248
82967
  }
82249
- return join37(cacheDir, "update-check.json");
82968
+ return join38(cacheDir, "update-check.json");
82250
82969
  } catch {
82251
- return join37(tmpdir(), "claudish-update-check.json");
82970
+ return join38(tmpdir(), "claudish-update-check.json");
82252
82971
  }
82253
82972
  }
82254
82973
  function readCache2() {
@@ -88575,7 +89294,7 @@ var init_response_seam = __esm(() => {
88575
89294
  // src/services/pricing-cache.ts
88576
89295
  import { existsSync as existsSync30, readFileSync as readFileSync37, statSync as statSync13 } from "fs";
88577
89296
  import { homedir as homedir35 } from "os";
88578
- import { join as join38 } from "path";
89297
+ import { join as join39 } from "path";
88579
89298
  function prefixMatch(modelName) {
88580
89299
  for (const [key, pricing] of pricingMap) {
88581
89300
  if (modelName.startsWith(key))
@@ -88635,14 +89354,14 @@ var init_pricing_cache = __esm(() => {
88635
89354
  init_logger();
88636
89355
  init_catalog_query();
88637
89356
  pricingMap = new Map;
88638
- CACHE_DIR = join38(homedir35(), ".claudish");
88639
- CACHE_FILE = join38(CACHE_DIR, "pricing-cache.json");
89357
+ CACHE_DIR = join39(homedir35(), ".claudish");
89358
+ CACHE_FILE = join39(CACHE_DIR, "pricing-cache.json");
88640
89359
  CACHE_TTL_MS5 = 24 * 60 * 60 * 1000;
88641
89360
  });
88642
89361
 
88643
89362
  // src/proxy-server.ts
88644
89363
  import { appendFileSync as appendFileSync6, mkdirSync as mkdirSync20 } from "fs";
88645
- import { join as join39 } from "path";
89364
+ import { join as join40 } from "path";
88646
89365
  function isTerminalRoutingFailure(e) {
88647
89366
  return e instanceof RoutingError;
88648
89367
  }
@@ -88650,7 +89369,7 @@ function maybeCaptureClassifierRequest(c, body) {
88650
89369
  if (!process.env.CLAUDISH_CLASSIFIER_DEBUG)
88651
89370
  return;
88652
89371
  try {
88653
- const dir = join39(process.cwd(), "logs");
89372
+ const dir = join40(process.cwd(), "logs");
88654
89373
  if (!classifierCaptureDirReady) {
88655
89374
  mkdirSync20(dir, { recursive: true });
88656
89375
  classifierCaptureDirReady = true;
@@ -88675,7 +89394,7 @@ function maybeCaptureClassifierRequest(c, body) {
88675
89394
  "x-api-key": c.req.header("x-api-key") ? "<present>" : null
88676
89395
  }
88677
89396
  };
88678
- appendFileSync6(join39(dir, "classifier-capture.jsonl"), `${JSON.stringify(record)}
89397
+ appendFileSync6(join40(dir, "classifier-capture.jsonl"), `${JSON.stringify(record)}
88679
89398
  `);
88680
89399
  } catch {}
88681
89400
  }
@@ -89210,13 +89929,13 @@ import {
89210
89929
  unlinkSync as unlinkSync10
89211
89930
  } from "fs";
89212
89931
  import { homedir as homedir36 } from "os";
89213
- import { dirname as dirname16, join as join40 } from "path";
89932
+ import { dirname as dirname16, join as join41 } from "path";
89214
89933
  import { fileURLToPath } from "url";
89215
89934
  function getVersion3() {
89216
89935
  return VERSION;
89217
89936
  }
89218
89937
  function clearAllModelCaches() {
89219
- const cacheDir = join40(homedir36(), ".claudish");
89938
+ const cacheDir = join41(homedir36(), ".claudish");
89220
89939
  if (!existsSync31(cacheDir))
89221
89940
  return;
89222
89941
  const cachePatterns = ["pricing-cache.json", "recommended-models-cache-v2.json"];
@@ -89225,7 +89944,7 @@ function clearAllModelCaches() {
89225
89944
  const files = readdirSync4(cacheDir);
89226
89945
  for (const file of files) {
89227
89946
  if (cachePatterns.includes(file)) {
89228
- unlinkSync10(join40(cacheDir, file));
89947
+ unlinkSync10(join41(cacheDir, file));
89229
89948
  cleared++;
89230
89949
  }
89231
89950
  }
@@ -90745,7 +91464,7 @@ ${h("MORE INFO")}
90745
91464
  }
90746
91465
  function printAIAgentGuide() {
90747
91466
  try {
90748
- const guidePath = join40(__dirname2, "../AI_AGENT_GUIDE.md");
91467
+ const guidePath = join41(__dirname2, "../AI_AGENT_GUIDE.md");
90749
91468
  const guideContent = readFileSync38(guidePath, "utf-8");
90750
91469
  console.log(guideContent);
90751
91470
  } catch (error) {
@@ -90762,10 +91481,10 @@ async function initializeClaudishSkill() {
90762
91481
  console.log(`\uD83D\uDD27 Initializing Claudish skill in current project...
90763
91482
  `);
90764
91483
  const cwd = process.cwd();
90765
- const claudeDir = join40(cwd, ".claude");
90766
- const skillsDir = join40(claudeDir, "skills");
90767
- const claudishSkillDir = join40(skillsDir, "claudish-usage");
90768
- const skillFile = join40(claudishSkillDir, "SKILL.md");
91484
+ const claudeDir = join41(cwd, ".claude");
91485
+ const skillsDir = join41(claudeDir, "skills");
91486
+ const claudishSkillDir = join41(skillsDir, "claudish-usage");
91487
+ const skillFile = join41(claudishSkillDir, "SKILL.md");
90769
91488
  if (existsSync31(skillFile)) {
90770
91489
  console.log("\u2705 Claudish skill already installed at:");
90771
91490
  console.log(` ${skillFile}
@@ -90773,7 +91492,7 @@ async function initializeClaudishSkill() {
90773
91492
  console.log("\uD83D\uDCA1 To reinstall, delete the file and run 'claudish --init' again.");
90774
91493
  return;
90775
91494
  }
90776
- const sourceSkillPath = join40(__dirname2, "../skills/claudish-usage/SKILL.md");
91495
+ const sourceSkillPath = join41(__dirname2, "../skills/claudish-usage/SKILL.md");
90777
91496
  if (!existsSync31(sourceSkillPath)) {
90778
91497
  console.error("\u274C Error: Claudish skill file not found in installation.");
90779
91498
  console.error(` Expected at: ${sourceSkillPath}`);
@@ -90969,16 +91688,16 @@ var init_cli = __esm(() => {
90969
91688
  // src/launcher/magmux-binary.ts
90970
91689
  import { execSync } from "child_process";
90971
91690
  import { existsSync as existsSync32 } from "fs";
90972
- import { dirname as dirname17, join as join41 } from "path";
91691
+ import { dirname as dirname17, join as join42 } from "path";
90973
91692
  import { fileURLToPath as fileURLToPath2 } from "url";
90974
91693
  function findMagmuxBinaryOrNull() {
90975
91694
  const thisFile = fileURLToPath2(import.meta.url);
90976
91695
  const thisDir = dirname17(thisFile);
90977
- const pkgRoots = [join41(thisDir, ".."), join41(thisDir, "..", "..")];
91696
+ const pkgRoots = [join42(thisDir, ".."), join42(thisDir, "..", "..")];
90978
91697
  const platform = process.platform;
90979
91698
  const arch = process.arch;
90980
91699
  for (const pkgRoot of pkgRoots) {
90981
- const bundled = join41(pkgRoot, "native", `magmux-${platform}-${arch}`);
91700
+ const bundled = join42(pkgRoot, "native", `magmux-${platform}-${arch}`);
90982
91701
  if (existsSync32(bundled))
90983
91702
  return bundled;
90984
91703
  }
@@ -90986,7 +91705,7 @@ function findMagmuxBinaryOrNull() {
90986
91705
  const pkgName = `@claudish/magmux-${platform}-${arch}`;
90987
91706
  let searchDir = pkgRoots[0];
90988
91707
  for (let i = 0;i < 5; i++) {
90989
- const candidate = join41(searchDir, "node_modules", pkgName, "bin", "magmux");
91708
+ const candidate = join42(searchDir, "node_modules", pkgName, "bin", "magmux");
90990
91709
  if (existsSync32(candidate))
90991
91710
  return candidate;
90992
91711
  const parent = dirname17(searchDir);
@@ -91023,7 +91742,7 @@ import {
91023
91742
  writeFileSync as writeFileSync18
91024
91743
  } from "fs";
91025
91744
  import { connect as netConnect } from "net";
91026
- import { join as join42 } from "path";
91745
+ import { join as join43 } from "path";
91027
91746
  function envDeltas(parentEnv, childEnv) {
91028
91747
  const set = [];
91029
91748
  const unset = [];
@@ -91089,9 +91808,9 @@ function planMagmuxWrap(input) {
91089
91808
  const controlSocket = `/tmp/magmux-${id}.sock`;
91090
91809
  const tmpRoot = input.tmpRoot ?? "/tmp";
91091
91810
  mkdirSync22(tmpRoot, { recursive: true });
91092
- const scriptDir = mkdtempSync(join42(tmpRoot, "claudish-launch-"));
91811
+ const scriptDir = mkdtempSync(join43(tmpRoot, "claudish-launch-"));
91093
91812
  chmodSync3(scriptDir, 448);
91094
- const scriptPath = join42(scriptDir, "claude-launch.sh");
91813
+ const scriptPath = join43(scriptDir, "claude-launch.sh");
91095
91814
  const parentEnv = input.parentEnv ?? process.env;
91096
91815
  const deltas = envDeltas(parentEnv, input.childEnv);
91097
91816
  const fd = openSync10(scriptPath, "wx", 384);
@@ -91236,8 +91955,8 @@ var DEFAULT_PANE_COLS = 160, DEFAULT_PANE_ROWS = 50, MIN_PANE_COLS = 40, MAX_PAN
91236
91955
 
91237
91956
  // src/pane/process-identity.ts
91238
91957
  import { execFile as execFile2, execFileSync as execFileSync2 } from "child_process";
91239
- import { lstatSync as lstatSync3 } from "fs";
91240
- import { basename as basename2, dirname as dirname18, join as join43 } from "path";
91958
+ import { lstatSync as lstatSync5 } from "fs";
91959
+ import { basename as basename2, dirname as dirname18, join as join44 } from "path";
91241
91960
  function collapse(s) {
91242
91961
  return s.trim().replace(/\s+/g, " ");
91243
91962
  }
@@ -91447,7 +92166,7 @@ function isLaunchDirPath(root, p) {
91447
92166
  if (dirname18(p) !== root || !LAUNCH_DIR_RE.test(basename2(p)))
91448
92167
  return false;
91449
92168
  try {
91450
- const st = lstatSync3(p);
92169
+ const st = lstatSync5(p);
91451
92170
  return st.isDirectory() && !st.isSymbolicLink();
91452
92171
  } catch {
91453
92172
  return false;
@@ -91457,16 +92176,16 @@ function isValidPaneId(id) {
91457
92176
  return PANE_ID_RE.test(id) && !/^\d+$/.test(id);
91458
92177
  }
91459
92178
  function sockPathOf(root, paneId) {
91460
- return join43(root, `magmux-${paneId}.sock`);
92179
+ return join44(root, `magmux-${paneId}.sock`);
91461
92180
  }
91462
92181
  function recordPathOf(root, paneId) {
91463
- return join43(root, "panes", `${paneId}.json`);
92182
+ return join44(root, "panes", `${paneId}.json`);
91464
92183
  }
91465
92184
  function isPaneSockPath(root, p, paneId) {
91466
92185
  return isValidPaneId(paneId) && dirname18(p) === root && basename2(p) === `magmux-${paneId}.sock`;
91467
92186
  }
91468
92187
  function isPaneRecordPath(root, p, paneId) {
91469
- return isValidPaneId(paneId) && dirname18(p) === join43(root, "panes") && basename2(p) === `${paneId}.json`;
92188
+ return isValidPaneId(paneId) && dirname18(p) === join44(root, "panes") && basename2(p) === `${paneId}.json`;
91470
92189
  }
91471
92190
  function watcherArgv(a) {
91472
92191
  return [
@@ -91572,17 +92291,17 @@ var init_process_identity = __esm(() => {
91572
92291
  // src/pane/pane-launch.ts
91573
92292
  import { execFile as execFile3, spawn } from "child_process";
91574
92293
  import { randomBytes as randomBytes12 } from "crypto";
91575
- import { chmodSync as chmodSync4, lstatSync as lstatSync4, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync19 } from "fs";
91576
- import { join as join44, resolve as resolve3 } from "path";
92294
+ import { chmodSync as chmodSync4, lstatSync as lstatSync6, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync2, writeFileSync as writeFileSync19 } from "fs";
92295
+ import { join as join45, resolve as resolve4 } from "path";
91577
92296
  function uid() {
91578
92297
  return typeof process.getuid === "function" ? process.getuid() : 0;
91579
92298
  }
91580
92299
  function sockRootFor(env = process.env) {
91581
92300
  const override = env[ENV.CLAUDISH_PANE_ROOT]?.trim();
91582
- return override ? resolve3(override) : `/tmp/claudish-mux-${uid()}`;
92301
+ return override ? resolve4(override) : `/tmp/claudish-mux-${uid()}`;
91583
92302
  }
91584
92303
  function assertPrivateDir(p) {
91585
- const st = lstatSync4(p);
92304
+ const st = lstatSync6(p);
91586
92305
  if (st.isSymbolicLink() || !st.isDirectory())
91587
92306
  throw new PaneRootError(`pane socket root ${p} is not a real directory`);
91588
92307
  if (st.uid !== uid())
@@ -91591,7 +92310,7 @@ function assertPrivateDir(p) {
91591
92310
  throw new PaneRootError(`pane socket root ${p} has mode ${(st.mode & 511).toString(8)}; it must be 0700`);
91592
92311
  }
91593
92312
  function ensureSockRoot(given = sockRootFor()) {
91594
- const root = resolve3(given);
92313
+ const root = resolve4(given);
91595
92314
  try {
91596
92315
  mkdirSync23(root, { mode: 448 });
91597
92316
  } catch (e) {
@@ -91601,7 +92320,7 @@ function ensureSockRoot(given = sockRootFor()) {
91601
92320
  throw e;
91602
92321
  }
91603
92322
  assertPrivateDir(root);
91604
- const panes = join44(root, "panes");
92323
+ const panes = join45(root, "panes");
91605
92324
  try {
91606
92325
  mkdirSync23(panes, { mode: 448 });
91607
92326
  } catch (e) {
@@ -91655,7 +92374,7 @@ function buildPaneEnv(input) {
91655
92374
  if ((input.platform ?? process.platform) === "linux" && entryBytes > LINUX_ENV_STRING_LIMIT)
91656
92375
  throw new PaneEnvTooLargeError(`pane environment snapshot is ${entryBytes} bytes, over Linux's 131072-byte limit per variable`);
91657
92376
  env[ENV.CLAUDISH_PANE_ENV] = json;
91658
- env.SHELL = join44(input.ctlDir, "sh-shim");
92377
+ env.SHELL = join45(input.ctlDir, "sh-shim");
91659
92378
  return { magmuxEnv: env };
91660
92379
  }
91661
92380
  function buildClaudishPaneArgv(spawnModel, sessionUuid, turnDir, callerFlags) {
@@ -91865,21 +92584,21 @@ exec ${words}
91865
92584
  `;
91866
92585
  }
91867
92586
  function paneCommand(ctlDir) {
91868
- const cmd = `. ${shq2(join44(ctlDir, "pane-launch.sh"))}`;
92587
+ const cmd = `. ${shq2(join45(ctlDir, "pane-launch.sh"))}`;
91869
92588
  if (/claude /.test(cmd) || /claude$/.test(cmd) || /(^|\/)claude['"]?$/.test(cmd))
91870
92589
  throw new Error(`pane command would attach magmux's Claude Code controller: ${cmd}`);
91871
92590
  return cmd;
91872
92591
  }
91873
92592
  function createLaunchDirs(root) {
91874
- const ctlDir = mkdtempSync2(join44(root, "launch-"));
92593
+ const ctlDir = mkdtempSync2(join45(root, "launch-"));
91875
92594
  chmodSync4(ctlDir, 448);
91876
- const turnDir = mkdtempSync2(join44(root, "launch-"));
92595
+ const turnDir = mkdtempSync2(join45(root, "launch-"));
91877
92596
  chmodSync4(turnDir, 448);
91878
92597
  return { ctlDir, turnDir };
91879
92598
  }
91880
92599
  function writeLauncherFiles(ctlDir, script) {
91881
- writeFileSync19(join44(ctlDir, "pane-launch.sh"), script, { mode: 384, flag: "wx" });
91882
- writeFileSync19(join44(ctlDir, "sh-shim"), SHELL_SHIM, { mode: 448, flag: "wx" });
92600
+ writeFileSync19(join45(ctlDir, "pane-launch.sh"), script, { mode: 384, flag: "wx" });
92601
+ writeFileSync19(join45(ctlDir, "sh-shim"), SHELL_SHIM, { mode: 448, flag: "wx" });
91883
92602
  }
91884
92603
  function spawnPaneWatcher(args) {
91885
92604
  const proc = spawn("/bin/sh", watcherArgv(args), {
@@ -91902,7 +92621,7 @@ function paneMagmuxArgv(input) {
91902
92621
  "--session-id",
91903
92622
  input.sessionUuid,
91904
92623
  "--transcript",
91905
- resolve3(input.transcriptPath),
92624
+ resolve4(input.transcriptPath),
91906
92625
  "--id",
91907
92626
  input.paneId,
91908
92627
  "--sock-dir",
@@ -92281,9 +93000,9 @@ var init_magmux_client = __esm(() => {
92281
93000
  // src/pane/pane-registry.ts
92282
93001
  import {
92283
93002
  constants as constants3,
92284
- accessSync,
93003
+ accessSync as accessSync2,
92285
93004
  existsSync as existsSync34,
92286
- lstatSync as lstatSync5,
93005
+ lstatSync as lstatSync7,
92287
93006
  readFileSync as readFileSync39,
92288
93007
  readdirSync as readdirSync5,
92289
93008
  renameSync as renameSync9,
@@ -92291,7 +93010,7 @@ import {
92291
93010
  unlinkSync as unlinkSync11,
92292
93011
  writeFileSync as writeFileSync20
92293
93012
  } from "fs";
92294
- import { join as join45 } from "path";
93013
+ import { join as join46 } from "path";
92295
93014
  function ownerStartOfSelf() {
92296
93015
  if (ownStart)
92297
93016
  return ownStart;
@@ -92312,7 +93031,7 @@ function ownerAlive(owner, table) {
92312
93031
  return !minted || startedAt(row, minted.startMs);
92313
93032
  }
92314
93033
  function recordsDir(root) {
92315
- return join45(root, "panes");
93034
+ return join46(root, "panes");
92316
93035
  }
92317
93036
  function writeRecord(root, rec) {
92318
93037
  const p = recordPathOf(root, rec.paneId);
@@ -92382,13 +93101,13 @@ function readRecords(root) {
92382
93101
  }
92383
93102
  function readGroupFile(ctlDir) {
92384
93103
  try {
92385
- return parseGroupFile(readFileSync39(join45(ctlDir, "group"), "utf8"));
93104
+ return parseGroupFile(readFileSync39(join46(ctlDir, "group"), "utf8"));
92386
93105
  } catch {
92387
93106
  return null;
92388
93107
  }
92389
93108
  }
92390
93109
  function writeGroupFile(ctlDir, snap) {
92391
- const p = join45(ctlDir, "group");
93110
+ const p = join46(ctlDir, "group");
92392
93111
  const tmp = `${p}.tmp`;
92393
93112
  try {
92394
93113
  writeFileSync20(tmp, formatGroupFile(snap), { mode: 384 });
@@ -92620,14 +93339,14 @@ function dialErrno(reported, sockPath) {
92620
93339
  return reported;
92621
93340
  let isSocket;
92622
93341
  try {
92623
- isSocket = lstatSync5(sockPath).isSocket();
93342
+ isSocket = lstatSync7(sockPath).isSocket();
92624
93343
  } catch (e) {
92625
93344
  return e.code ?? "unknown";
92626
93345
  }
92627
93346
  if (!isSocket)
92628
93347
  return "ENOTSOCK";
92629
93348
  try {
92630
- accessSync(sockPath, constants3.W_OK);
93349
+ accessSync2(sockPath, constants3.W_OK);
92631
93350
  } catch {
92632
93351
  return "EACCES";
92633
93352
  }
@@ -92665,7 +93384,7 @@ async function sweepRecordlessSocket(root, name, out) {
92665
93384
  const owner = ownerOfSocketId(id);
92666
93385
  if (!owner || ownerAlive(owner, table))
92667
93386
  return;
92668
- const sockPath = join45(root, name);
93387
+ const sockPath = join46(root, name);
92669
93388
  const magmuxMayLive = !table || table.some((r) => isPaneMagmuxCommand(r.command, id));
92670
93389
  const probe = await probeSocket(sockPath, magmuxMayLive);
92671
93390
  if (probe.state === "stale") {
@@ -92835,7 +93554,7 @@ var init_queue = __esm(() => {
92835
93554
 
92836
93555
  // src/mcp-session/parent-session.ts
92837
93556
  import { readFile as readFile7 } from "fs/promises";
92838
- import { join as join46 } from "path";
93557
+ import { join as join47 } from "path";
92839
93558
  function positiveInt(value) {
92840
93559
  if (value === undefined || !/^[1-9][0-9]*$/.test(value))
92841
93560
  return;
@@ -92859,11 +93578,11 @@ function parentSessionIdFrom(env) {
92859
93578
  }
92860
93579
  function claudeConfigDir(env) {
92861
93580
  const fromEnv = env.CLAUDE_CONFIG_DIR;
92862
- return fromEnv && fromEnv.length > 0 ? fromEnv : join46(userHomeFrom(env), ".claude");
93581
+ return fromEnv && fromEnv.length > 0 ? fromEnv : join47(userHomeFrom(env), ".claude");
92863
93582
  }
92864
93583
  async function readHostSessionRecord(opts) {
92865
93584
  try {
92866
- const raw = await readFile7(join46(opts.configDir, "sessions", `${opts.hostPid}.json`), "utf-8");
93585
+ const raw = await readFile7(join47(opts.configDir, "sessions", `${opts.hostPid}.json`), "utf-8");
92867
93586
  const parsed = JSON.parse(raw);
92868
93587
  if (!parsed || typeof parsed !== "object")
92869
93588
  return;
@@ -93213,15 +93932,15 @@ var init_input_gate = __esm(() => {
93213
93932
  });
93214
93933
 
93215
93934
  // src/pane/gate-binding.ts
93216
- import { realpathSync as realpathSync3 } from "fs";
93217
- import { basename as basename3, dirname as dirname19, join as join47, resolve as resolve4 } from "path";
93935
+ import { realpathSync as realpathSync4 } from "fs";
93936
+ import { basename as basename3, dirname as dirname19, join as join48, resolve as resolve5 } from "path";
93218
93937
  function normaliseTranscriptPath(p) {
93219
- const abs = resolve4(p);
93938
+ const abs = resolve5(p);
93220
93939
  const tail = [];
93221
93940
  let cur = abs;
93222
93941
  for (;; ) {
93223
93942
  try {
93224
- return tail.length ? join47(realpathSync3(cur), ...tail.reverse()) : realpathSync3(cur);
93943
+ return tail.length ? join48(realpathSync4(cur), ...tail.reverse()) : realpathSync4(cur);
93225
93944
  } catch {
93226
93945
  const parent = dirname19(cur);
93227
93946
  if (parent === cur)
@@ -93635,7 +94354,7 @@ var init_scroll_input = __esm(() => {
93635
94354
 
93636
94355
  // src/pane/session-registry.ts
93637
94356
  import { readFileSync as readFileSync40, readdirSync as readdirSync6 } from "fs";
93638
- import { join as join48 } from "path";
94357
+ import { join as join49 } from "path";
93639
94358
  function parseRegistryRecord(raw) {
93640
94359
  let parsed;
93641
94360
  try {
@@ -93680,7 +94399,7 @@ class SessionRegistry {
93680
94399
  isAlive;
93681
94400
  constructor(o) {
93682
94401
  this.o = o;
93683
- this.dir = join48(o.configDir, "sessions");
94402
+ this.dir = join49(o.configDir, "sessions");
93684
94403
  this.now = o.now ?? Date.now;
93685
94404
  this.isAlive = o.isAlive ?? pidAlive;
93686
94405
  }
@@ -93716,7 +94435,7 @@ class SessionRegistry {
93716
94435
  readPid(pid) {
93717
94436
  let raw;
93718
94437
  try {
93719
- raw = readFileSync40(join48(this.dir, `${pid}.json`), "utf8");
94438
+ raw = readFileSync40(join49(this.dir, `${pid}.json`), "utf8");
93720
94439
  } catch {
93721
94440
  return null;
93722
94441
  }
@@ -94018,10 +94737,10 @@ import {
94018
94737
  openSync as openSync11,
94019
94738
  readSync as readSync4,
94020
94739
  readdirSync as readdirSync7,
94021
- realpathSync as realpathSync4,
94740
+ realpathSync as realpathSync5,
94022
94741
  statSync as statSync14
94023
94742
  } from "fs";
94024
- import { join as join49 } from "path";
94743
+ import { join as join50 } from "path";
94025
94744
  function initialFollowerState(resolvePath = safeRealpath) {
94026
94745
  return {
94027
94746
  turns: [],
@@ -94037,7 +94756,7 @@ function initialFollowerState(resolvePath = safeRealpath) {
94037
94756
  }
94038
94757
  function safeRealpath(p) {
94039
94758
  try {
94040
- return realpathSync4(p);
94759
+ return realpathSync5(p);
94041
94760
  } catch {
94042
94761
  return p;
94043
94762
  }
@@ -94595,7 +95314,7 @@ class TranscriptFollower {
94595
95314
  for (const f of this.subagentFiles()) {
94596
95315
  let tail = this.subs.get(f);
94597
95316
  if (!tail) {
94598
- tail = new Tail(join49(this.subagentsDir, f));
95317
+ tail = new Tail(join50(this.subagentsDir, f));
94599
95318
  this.subs.set(f, tail);
94600
95319
  }
94601
95320
  for (const { line } of tail.read()) {
@@ -94694,8 +95413,8 @@ var init_transcript_follower = __esm(() => {
94694
95413
  });
94695
95414
 
94696
95415
  // src/pane/pane-session.ts
94697
- import { readFileSync as readFileSync41, realpathSync as realpathSync5, rmSync as rmSync11, statSync as statSync15, writeFileSync as writeFileSync21 } from "fs";
94698
- import { join as join50 } from "path";
95416
+ import { readFileSync as readFileSync41, realpathSync as realpathSync6, rmSync as rmSync11, statSync as statSync15, writeFileSync as writeFileSync21 } from "fs";
95417
+ import { join as join51 } from "path";
94699
95418
  function iso3(ms) {
94700
95419
  return new Date(ms).toISOString();
94701
95420
  }
@@ -94847,7 +95566,7 @@ class PaneSessionImpl {
94847
95566
  });
94848
95567
  this.secondaryQuietMs = o.timings?.secondaryQuietMs ?? SECONDARY_QUIET_MS;
94849
95568
  this.corroborationMs = o.timings?.corroborationMs ?? CORROBORATION_MS;
94850
- this.follower = new TranscriptFollower(o.transcriptPath, join50(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
95569
+ this.follower = new TranscriptFollower(o.transcriptPath, join51(o.transcriptPath.replace(/\.jsonl$/, ""), "subagents"));
94851
95570
  this.registry = new SessionRegistry({
94852
95571
  configDir: claudeConfigDir(o.parentEnv ?? process.env),
94853
95572
  sessionUuid: o.sessionUuid
@@ -94894,7 +95613,7 @@ class PaneSessionImpl {
94894
95613
  return this.spawnFailed(new Error("magmux did not start"));
94895
95614
  this.magmux = proc;
94896
95615
  try {
94897
- writeFileSync21(join50(this.ctlDir, "magmux.pid"), `${proc.pid}
95616
+ writeFileSync21(join51(this.ctlDir, "magmux.pid"), `${proc.pid}
94898
95617
  `, { mode: 384 });
94899
95618
  } catch {}
94900
95619
  updateRecord(this.root, this.paneId, { magmuxPid: proc.pid });
@@ -97345,7 +98064,7 @@ function preparePane(o, root, parentEnv) {
97345
98064
  try {
97346
98065
  const paneId = mintPaneId(o.kind, o.label);
97347
98066
  const sockPath = sockPathOf(root, paneId);
97348
- const cwd = realpathSync5(o.cwd);
98067
+ const cwd = realpathSync6(o.cwd);
97349
98068
  const launch = o.claudishSpawn ?? resolveClaudishLaunch(parentEnv);
97350
98069
  dirs = createLaunchDirs(root);
97351
98070
  const argv = buildClaudishPaneArgv(o.spawnModel, o.sessionUuid, dirs.turnDir, o.callerFlags);
@@ -97893,15 +98612,15 @@ import {
97893
98612
  constants as constants4,
97894
98613
  closeSync as closeSync12,
97895
98614
  fstatSync as fstatSync5,
97896
- lstatSync as lstatSync6,
98615
+ lstatSync as lstatSync8,
97897
98616
  openSync as openSync12,
97898
98617
  readSync as readSync5,
97899
98618
  readdirSync as readdirSync8,
97900
98619
  readlinkSync,
97901
- realpathSync as realpathSync6,
98620
+ realpathSync as realpathSync7,
97902
98621
  statSync as statSync16
97903
98622
  } from "fs";
97904
- import { basename as basename4, dirname as dirname20, join as join51, sep } from "path";
98623
+ import { basename as basename4, dirname as dirname20, join as join52, sep } from "path";
97905
98624
  function isUuid(v) {
97906
98625
  return typeof v === "string" && UUID_RE.test(v);
97907
98626
  }
@@ -98051,7 +98770,7 @@ function locateTranscriptByUuid(projectsRoot, uuid) {
98051
98770
  }
98052
98771
  let hit = null;
98053
98772
  for (const d of dirs) {
98054
- const p = join51(projectsRoot, d, `${uuid}.jsonl`);
98773
+ const p = join52(projectsRoot, d, `${uuid}.jsonl`);
98055
98774
  try {
98056
98775
  if (!statSync16(p).isFile())
98057
98776
  continue;
@@ -98113,8 +98832,8 @@ function openContainedTranscript(path, uuid, projectsRoot) {
98113
98832
  let real;
98114
98833
  let root;
98115
98834
  try {
98116
- real = realpathSync6(path);
98117
- root = realpathSync6(projectsRoot);
98835
+ real = realpathSync7(path);
98836
+ root = realpathSync7(projectsRoot);
98118
98837
  } catch (e) {
98119
98838
  if (!isEnoent(e))
98120
98839
  logRejection(path, errText(e));
@@ -98142,8 +98861,8 @@ function openContainedTranscript(path, uuid, projectsRoot) {
98142
98861
  return null;
98143
98862
  }
98144
98863
  const parent = dirname20(real);
98145
- const named = lstatSync6(real);
98146
- if (realpathSync6(parent) !== parent || !named.isFile() || named.dev !== st.dev || named.ino !== st.ino || !kernelPathMatches(fd, real)) {
98864
+ const named = lstatSync8(real);
98865
+ if (realpathSync7(parent) !== parent || !named.isFile() || named.dev !== st.dev || named.ino !== st.ino || !kernelPathMatches(fd, real)) {
98147
98866
  logRejection(path, "the opened file is not the one under the projects directory");
98148
98867
  closeQuietly(fd);
98149
98868
  return null;
@@ -98275,10 +98994,10 @@ __export(exports_session_discovery, {
98275
98994
  transcriptPathFor: () => transcriptPathFor
98276
98995
  });
98277
98996
  import { execFile as execFile4, execFileSync as execFileSync3 } from "child_process";
98278
- import { closeSync as closeSync13, openSync as openSync13, readSync as readSync6, readdirSync as readdirSync9, realpathSync as realpathSync7, statSync as statSync17 } from "fs";
98279
- import { basename as basename5, join as join52 } from "path";
98997
+ import { closeSync as closeSync13, openSync as openSync13, readSync as readSync6, readdirSync as readdirSync9, realpathSync as realpathSync8, statSync as statSync17 } from "fs";
98998
+ import { basename as basename5, join as join53 } from "path";
98280
98999
  function projectsDir(env = process.env) {
98281
- return join52(claudeConfigDir(env), "projects");
99000
+ return join53(claudeConfigDir(env), "projects");
98282
99001
  }
98283
99002
  function slugForPath(absPath) {
98284
99003
  return projectDirNameFor(absPath);
@@ -98286,9 +99005,9 @@ function slugForPath(absPath) {
98286
99005
  function transcriptPathFor(cwd, sessionUuid, projectsRoot = projectsDir()) {
98287
99006
  let real = cwd;
98288
99007
  try {
98289
- real = realpathSync7(cwd);
99008
+ real = realpathSync8(cwd);
98290
99009
  } catch {}
98291
- return join52(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
99010
+ return join53(projectsRoot, slugForPath(real), `${sessionUuid}.jsonl`);
98292
99011
  }
98293
99012
  function isAgentSession(row) {
98294
99013
  return row.entrypoint !== undefined && row.entrypoint !== "cli";
@@ -98335,7 +99054,7 @@ function projectDirs() {
98335
99054
  }
98336
99055
  }
98337
99056
  function sessionsIn2(dirName) {
98338
- const dir = join52(projectsDir(), dirName);
99057
+ const dir = join53(projectsDir(), dirName);
98339
99058
  let names;
98340
99059
  try {
98341
99060
  names = readdirSync9(dir).filter((n) => n.endsWith(".jsonl"));
@@ -98344,7 +99063,7 @@ function sessionsIn2(dirName) {
98344
99063
  }
98345
99064
  const rows = [];
98346
99065
  for (const n of names) {
98347
- const file = join52(dir, n);
99066
+ const file = join53(dir, n);
98348
99067
  try {
98349
99068
  const st = statSync17(file);
98350
99069
  if (st.size === 0)
@@ -98723,16 +99442,16 @@ var init_session_discovery = __esm(() => {
98723
99442
  import { randomBytes as randomBytes13, randomUUID as randomUUID8 } from "crypto";
98724
99443
  import {
98725
99444
  existsSync as existsSync36,
98726
- lstatSync as lstatSync7,
99445
+ lstatSync as lstatSync9,
98727
99446
  mkdirSync as mkdirSync24,
98728
99447
  readFileSync as readFileSync42,
98729
99448
  readdirSync as readdirSync10,
98730
- realpathSync as realpathSync8,
99449
+ realpathSync as realpathSync9,
98731
99450
  rmSync as rmSync12,
98732
99451
  statSync as statSync18,
98733
99452
  writeFileSync as writeFileSync22
98734
99453
  } from "fs";
98735
- import { basename as basename6, dirname as dirname21, join as join53, resolve as resolve5, sep as sep2 } from "path";
99454
+ import { basename as basename6, dirname as dirname21, join as join54, resolve as resolve6, sep as sep2 } from "path";
98736
99455
  function slotTerminal(e) {
98737
99456
  if (!e.session)
98738
99457
  return true;
@@ -98769,13 +99488,13 @@ function addressRun(path, runId) {
98769
99488
  pruneRuns();
98770
99489
  if (runId) {
98771
99490
  const run = teamRuns.get(runId) ?? null;
98772
- if (run && path !== undefined && resolve5(path) !== run.path)
99491
+ if (run && path !== undefined && resolve6(path) !== run.path)
98773
99492
  return null;
98774
99493
  return run;
98775
99494
  }
98776
99495
  if (path === undefined)
98777
99496
  return null;
98778
- const id = newestRunByPath.get(resolve5(path));
99497
+ const id = newestRunByPath.get(resolve6(path));
98779
99498
  return id ? teamRuns.get(id) ?? null : null;
98780
99499
  }
98781
99500
  function activeRunAt(path) {
@@ -98801,7 +99520,7 @@ function notableAnomalies(snap) {
98801
99520
  return snap.anomalies.filter((a) => a.startsWith("background_shell_open") || a.startsWith("turn_end_record_missing") || a.startsWith("agent_count_mismatch") || a.startsWith("prompt_not_read"));
98802
99521
  }
98803
99522
  function upstreamLogPath(path, id) {
98804
- return join53(path, "errors", `${id}-upstream.jsonl`);
99523
+ return join54(path, "errors", `${id}-upstream.jsonl`);
98805
99524
  }
98806
99525
  function neverSpawnedRow(e, m) {
98807
99526
  return {
@@ -98910,14 +99629,14 @@ function teamRunRowFromDisk(path, status) {
98910
99629
  }
98911
99630
  function statusMtimeMs(path) {
98912
99631
  try {
98913
- return statSync18(join53(path, "status.json")).mtimeMs;
99632
+ return statSync18(join54(path, "status.json")).mtimeMs;
98914
99633
  } catch {
98915
99634
  return null;
98916
99635
  }
98917
99636
  }
98918
99637
  function readManifestOrNull(path) {
98919
99638
  try {
98920
- return JSON.parse(readFileSync42(join53(path, "manifest.json"), "utf-8"));
99639
+ return JSON.parse(readFileSync42(join54(path, "manifest.json"), "utf-8"));
98921
99640
  } catch {
98922
99641
  return null;
98923
99642
  }
@@ -99016,7 +99735,7 @@ function transcriptTeamSlot(path, slot, since, runId, opts = {}) {
99016
99735
  }
99017
99736
  if (path === undefined)
99018
99737
  throw unknownRun(path, runId);
99019
- const resolved = resolve5(path);
99738
+ const resolved = resolve6(path);
99020
99739
  let status;
99021
99740
  try {
99022
99741
  status = getStatus(resolved);
@@ -99177,13 +99896,13 @@ function answerFailureDetail(v, answer, requirePattern, outcomes) {
99177
99896
  function readWrittenFile(path, root) {
99178
99897
  let realRoot;
99179
99898
  try {
99180
- realRoot = realpathSync8(root);
99899
+ realRoot = realpathSync9(root);
99181
99900
  } catch {
99182
- realRoot = resolve5(root);
99901
+ realRoot = resolve6(root);
99183
99902
  }
99184
99903
  let real;
99185
99904
  try {
99186
- real = realpathSync8(resolve5(root, path));
99905
+ real = realpathSync9(resolve6(root, path));
99187
99906
  } catch {
99188
99907
  return { skip: "missing" };
99189
99908
  }
@@ -99237,13 +99956,13 @@ function persistErrorLog(errorLogPath, header, sections) {
99237
99956
  } catch {}
99238
99957
  }
99239
99958
  function validateSessionPath(sessionPath) {
99240
- const resolved = resolve5(sessionPath);
99959
+ const resolved = resolve6(sessionPath);
99241
99960
  const outside = () => new Error(`Session path must be within current directory: ${sessionPath}`);
99242
99961
  let existing = resolved;
99243
99962
  const tail = [];
99244
99963
  for (;; ) {
99245
99964
  try {
99246
- lstatSync7(existing);
99965
+ lstatSync9(existing);
99247
99966
  break;
99248
99967
  } catch {
99249
99968
  const parent = dirname21(existing);
@@ -99255,18 +99974,18 @@ function validateSessionPath(sessionPath) {
99255
99974
  }
99256
99975
  let real;
99257
99976
  try {
99258
- real = join53(realpathSync8(existing), ...tail);
99977
+ real = join54(realpathSync9(existing), ...tail);
99259
99978
  } catch {
99260
99979
  throw outside();
99261
99980
  }
99262
- const cwd = realpathSync8(process.cwd());
99981
+ const cwd = realpathSync9(process.cwd());
99263
99982
  if (real !== cwd && !real.startsWith(cwd + sep2)) {
99264
99983
  throw outside();
99265
99984
  }
99266
99985
  return resolved;
99267
99986
  }
99268
99987
  function readTeamInputFile(inputPath) {
99269
- const resolved = resolve5(inputPath);
99988
+ const resolved = resolve6(inputPath);
99270
99989
  const cwd = process.cwd();
99271
99990
  if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
99272
99991
  throw new Error(`Input file must be within current directory: ${inputPath}`);
@@ -99284,14 +100003,14 @@ function setupSession(sessionPath, models, input) {
99284
100003
  if (models.length === 0) {
99285
100004
  throw new Error("At least one model is required");
99286
100005
  }
99287
- if (existsSync36(join53(sessionPath, "manifest.json"))) {
100006
+ if (existsSync36(join54(sessionPath, "manifest.json"))) {
99288
100007
  throw new Error(`Session already exists at ${sessionPath}. Use a new directory path or delete the existing session first.`);
99289
100008
  }
99290
- mkdirSync24(join53(sessionPath, "work"), { recursive: true });
99291
- mkdirSync24(join53(sessionPath, "errors"), { recursive: true });
100009
+ mkdirSync24(join54(sessionPath, "work"), { recursive: true });
100010
+ mkdirSync24(join54(sessionPath, "errors"), { recursive: true });
99292
100011
  if (input !== undefined) {
99293
- writeFileSync22(join53(sessionPath, "input.md"), input, "utf-8");
99294
- } else if (!existsSync36(join53(sessionPath, "input.md"))) {
100012
+ writeFileSync22(join54(sessionPath, "input.md"), input, "utf-8");
100013
+ } else if (!existsSync36(join54(sessionPath, "input.md"))) {
99295
100014
  throw new Error(`No input.md found at ${sessionPath} and no input provided`);
99296
100015
  }
99297
100016
  const ids = models.map((_, i) => String(i + 1).padStart(2, "0"));
@@ -99308,9 +100027,9 @@ function setupSession(sessionPath, models, input) {
99308
100027
  model: models[i],
99309
100028
  assignedAt: now
99310
100029
  };
99311
- mkdirSync24(join53(sessionPath, "work", anonId), { recursive: true });
100030
+ mkdirSync24(join54(sessionPath, "work", anonId), { recursive: true });
99312
100031
  }
99313
- writeFileSync22(join53(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
100032
+ writeFileSync22(join54(sessionPath, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
99314
100033
  const status = {
99315
100034
  startedAt: now,
99316
100035
  models: Object.fromEntries(Object.keys(manifest.models).map((id) => [
@@ -99325,7 +100044,7 @@ function setupSession(sessionPath, models, input) {
99325
100044
  }
99326
100045
  ]))
99327
100046
  };
99328
- writeFileAtomic(join53(sessionPath, "status.json"), JSON.stringify(status, null, 2));
100047
+ writeFileAtomic(join54(sessionPath, "status.json"), JSON.stringify(status, null, 2));
99329
100048
  return manifest;
99330
100049
  }
99331
100050
  function assertValidRequirePattern(pattern) {
@@ -99353,7 +100072,7 @@ async function preflightTeamRun(opts) {
99353
100072
  throw new Error(`invalid_args: ${refusal}`);
99354
100073
  }
99355
100074
  await assertMagmuxAvailable();
99356
- assertNoActiveRun(resolve5(opts.path));
100075
+ assertNoActiveRun(resolve6(opts.path));
99357
100076
  const root = sockRootFor(opts.parentEnv ?? process.env);
99358
100077
  const live = livePaneCount(root);
99359
100078
  if (live + opts.slots > MAX_LIVE_PANES)
@@ -99385,10 +100104,10 @@ async function startModelsIn(flight, sessionPath, opts) {
99385
100104
  if (!flagCheck.ok)
99386
100105
  throw new Error(`invalid_args: ${flagCheck.message}`);
99387
100106
  const agentRequested = requestedAgentOf(claudeFlags);
99388
- const path = resolve5(sessionPath);
99389
- const manifest = JSON.parse(readFileSync42(join53(path, "manifest.json"), "utf-8"));
99390
- const statusPath = join53(path, "status.json");
99391
- const inputContent = readFileSync42(join53(path, "input.md"), "utf-8");
100107
+ const path = resolve6(sessionPath);
100108
+ const manifest = JSON.parse(readFileSync42(join54(path, "manifest.json"), "utf-8"));
100109
+ const statusPath = join54(path, "status.json");
100110
+ const inputContent = readFileSync42(join54(path, "input.md"), "utf-8");
99392
100111
  const readAvailable = !flagsRemoveRead(claudeFlags);
99393
100112
  const refusal = deliveryRefusal(inputContent, readAvailable);
99394
100113
  if (refusal)
@@ -99428,7 +100147,7 @@ async function startModelsIn(flight, sessionPath, opts) {
99428
100147
  mkdirSync24(statsDir(path), { recursive: true });
99429
100148
  const entries = new Map;
99430
100149
  let run = null;
99431
- const errorLogPathOf = (id) => join53(path, "errors", `${id}.log`);
100150
+ const errorLogPathOf = (id) => join54(path, "errors", `${id}.log`);
99432
100151
  const upstreamLogOf = (id) => upstreamLogPath(path, id);
99433
100152
  const flagsOf = (e) => {
99434
100153
  const effort = opts.efforts?.[e.model];
@@ -99438,7 +100157,7 @@ async function startModelsIn(flight, sessionPath, opts) {
99438
100157
  function writeResponse(e, text) {
99439
100158
  e.answer = text;
99440
100159
  try {
99441
- writeFileSync22(join53(path, `response-${e.id}.md`), text, "utf-8");
100160
+ writeFileSync22(join54(path, `response-${e.id}.md`), text, "utf-8");
99442
100161
  } catch (err) {
99443
100162
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
99444
100163
  }
@@ -99448,7 +100167,7 @@ async function startModelsIn(flight, sessionPath, opts) {
99448
100167
  if (e.answer !== null || !e.session)
99449
100168
  return;
99450
100169
  try {
99451
- writeFileSync22(join53(path, `response-${e.id}.md`), e.session.chainText(), "utf-8");
100170
+ writeFileSync22(join54(path, `response-${e.id}.md`), e.session.chainText(), "utf-8");
99452
100171
  } catch (err) {
99453
100172
  persistErrorLog(errorLogPathOf(e.id), `response file not written: ${String(err)}`, []);
99454
100173
  }
@@ -99465,7 +100184,7 @@ async function startModelsIn(flight, sessionPath, opts) {
99465
100184
  return { file: null, outcomes: turn.writtenFiles ?? [] };
99466
100185
  const outcomes = [];
99467
100186
  for (const written of turn.writtenFiles ?? []) {
99468
- const path = resolve5(cwd, written);
100187
+ const path = resolve6(cwd, written);
99469
100188
  const r = readWrittenFile(path, cwd);
99470
100189
  if ("skip" in r) {
99471
100190
  outcomes.push(`${path} (skipped: ${r.skip})`);
@@ -99645,7 +100364,7 @@ async function startModelsIn(flight, sessionPath, opts) {
99645
100364
  }
99646
100365
  async function startSlot(e, uuid, transcriptPath) {
99647
100366
  let session;
99648
- e.inboxPath = createHookInbox(join53(path, "work"), `${e.id}.hook-inbox.jsonl`);
100367
+ e.inboxPath = createHookInbox(join54(path, "work"), `${e.id}.hook-inbox.jsonl`);
99649
100368
  const binding = bindingFor(e, uuid);
99650
100369
  e.binding = binding;
99651
100370
  try {
@@ -99875,9 +100594,9 @@ async function runModels(sessionPath, opts = {}) {
99875
100594
  return handle.done;
99876
100595
  }
99877
100596
  async function judgeResponses(sessionPath, opts = {}) {
99878
- const active = activeRunAt(resolve5(sessionPath));
100597
+ const active = activeRunAt(resolve6(sessionPath));
99879
100598
  if (active)
99880
- throw new Error(`invalid_args: the team run at ${resolve5(sessionPath)} is still ACTIVE (run_id ${active.runId}); judge it once it has settled`);
100599
+ throw new Error(`invalid_args: the team run at ${resolve6(sessionPath)} is still ACTIVE (run_id ${active.runId}); judge it once it has settled`);
99881
100600
  const responseFiles = readdirSync10(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
99882
100601
  if (responseFiles.length < 2) {
99883
100602
  throw new Error(`Need at least 2 responses to judge, found ${responseFiles.length}`);
@@ -99885,13 +100604,13 @@ async function judgeResponses(sessionPath, opts = {}) {
99885
100604
  const responses = {};
99886
100605
  for (const file of responseFiles) {
99887
100606
  const id = file.replace(/^response-/, "").replace(/\.md$/, "");
99888
- responses[id] = readFileSync42(join53(sessionPath, file), "utf-8");
100607
+ responses[id] = readFileSync42(join54(sessionPath, file), "utf-8");
99889
100608
  }
99890
- const input = readFileSync42(join53(sessionPath, "input.md"), "utf-8");
100609
+ const input = readFileSync42(join54(sessionPath, "input.md"), "utf-8");
99891
100610
  const judgePrompt = buildJudgePrompt(input, responses);
99892
- writeFileSync22(join53(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
100611
+ writeFileSync22(join54(sessionPath, "judge-prompt.md"), judgePrompt, "utf-8");
99893
100612
  const judgeModels = opts.judges ?? getDefaultJudgeModels(sessionPath);
99894
- const judgePath = join53(sessionPath, "judging");
100613
+ const judgePath = join54(sessionPath, "judging");
99895
100614
  mkdirSync24(judgePath, { recursive: true });
99896
100615
  setupSession(judgePath, judgeModels, judgePrompt);
99897
100616
  await runModels(judgePath, {
@@ -99903,11 +100622,11 @@ async function judgeResponses(sessionPath, opts = {}) {
99903
100622
  });
99904
100623
  const votes = parseJudgeVotes(judgePath, Object.keys(responses));
99905
100624
  const verdict = aggregateVerdict(votes, Object.keys(responses));
99906
- writeFileSync22(join53(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
100625
+ writeFileSync22(join54(sessionPath, "verdict.md"), formatVerdict(verdict, sessionPath), "utf-8");
99907
100626
  return verdict;
99908
100627
  }
99909
100628
  function getStatus(sessionPath) {
99910
- return JSON.parse(readFileSync42(join53(sessionPath, "status.json"), "utf-8"));
100629
+ return JSON.parse(readFileSync42(join54(sessionPath, "status.json"), "utf-8"));
99911
100630
  }
99912
100631
  function readTeamStatus(sessionPath) {
99913
100632
  try {
@@ -99947,7 +100666,7 @@ function fisherYatesShuffle(arr) {
99947
100666
  return arr;
99948
100667
  }
99949
100668
  function getDefaultJudgeModels(sessionPath) {
99950
- const manifest = JSON.parse(readFileSync42(join53(sessionPath, "manifest.json"), "utf-8"));
100669
+ const manifest = JSON.parse(readFileSync42(join54(sessionPath, "manifest.json"), "utf-8"));
99951
100670
  return Object.values(manifest.models).map((e) => e.model);
99952
100671
  }
99953
100672
  function buildJudgePrompt(input, responses) {
@@ -100010,7 +100729,7 @@ function parseJudgeVotes(judgePath, responseIds) {
100010
100729
  const judgeId = file.replace(/^response-/, "").replace(/\.md$/, "");
100011
100730
  let content;
100012
100731
  try {
100013
- content = readFileSync42(join53(judgePath, file), "utf-8");
100732
+ content = readFileSync42(join54(judgePath, file), "utf-8");
100014
100733
  } catch (err) {
100015
100734
  log(`[team] judge answer ${file} unreadable: ${err instanceof Error ? err.message : String(err)}`);
100016
100735
  continue;
@@ -100063,7 +100782,7 @@ function aggregateVerdict(votes, responseIds) {
100063
100782
  function formatVerdict(verdict, sessionPath) {
100064
100783
  let manifest = null;
100065
100784
  try {
100066
- manifest = JSON.parse(readFileSync42(join53(sessionPath, "manifest.json"), "utf-8"));
100785
+ manifest = JSON.parse(readFileSync42(join54(sessionPath, "manifest.json"), "utf-8"));
100067
100786
  } catch {}
100068
100787
  let output = `# Team Verdict
100069
100788
 
@@ -100120,7 +100839,7 @@ var init_team_orchestrator = __esm(() => {
100120
100839
 
100121
100840
  // src/mcp-session/inherited-settings.ts
100122
100841
  import { existsSync as existsSync37, readFileSync as readFileSync43, statSync as statSync19 } from "fs";
100123
- import { join as join54, resolve as resolve6 } from "path";
100842
+ import { join as join55, resolve as resolve7 } from "path";
100124
100843
  function isPlainObject7(value) {
100125
100844
  return typeof value === "object" && value !== null && !Array.isArray(value);
100126
100845
  }
@@ -100174,7 +100893,7 @@ function readSettingsValue(value) {
100174
100893
  function buildInheritedSettingsLayer(setupFrom, callerFlags) {
100175
100894
  const layers = [];
100176
100895
  for (const name of ["settings.json", "settings.local.json"]) {
100177
- const file = join54(setupFrom, ".claude", name);
100896
+ const file = join55(setupFrom, ".claude", name);
100178
100897
  if (!existsSync37(file))
100179
100898
  continue;
100180
100899
  layers.push(parseLayer(readFileSync43(file, "utf-8"), file));
@@ -100206,14 +100925,14 @@ function resolveSetupFrom(arg, workDir, serverCwd) {
100206
100925
  return;
100207
100926
  let setupFrom = serverCwd;
100208
100927
  if (typeof arg === "string") {
100209
- setupFrom = resolve6(arg);
100928
+ setupFrom = resolve7(arg);
100210
100929
  if (!statSync19(setupFrom, { throwIfNoEntry: false })?.isDirectory()) {
100211
100930
  throw new Error(`invalid_args: setup_from ${setupFrom} is not an existing directory`);
100212
100931
  }
100213
100932
  } else if (arg !== undefined) {
100214
100933
  throw new Error("invalid_args: setup_from must be a directory path or null");
100215
100934
  }
100216
- return setupFrom === resolve6(workDir ?? serverCwd) ? undefined : setupFrom;
100935
+ return setupFrom === resolve7(workDir ?? serverCwd) ? undefined : setupFrom;
100217
100936
  }
100218
100937
  var init_inherited_settings = () => {};
100219
100938
 
@@ -100233,7 +100952,7 @@ import {
100233
100952
  statSync as statSync20,
100234
100953
  writeFileSync as writeFileSync23
100235
100954
  } from "fs";
100236
- import { join as join55, resolve as resolve7, sep as sep3 } from "path";
100955
+ import { join as join56, resolve as resolve8, sep as sep3 } from "path";
100237
100956
  function normaliseTimeoutSeconds(raw) {
100238
100957
  const n = typeof raw === "string" && raw.trim() !== "" ? Number(raw) : raw;
100239
100958
  if (typeof n !== "number" || !Number.isFinite(n))
@@ -100403,7 +101122,7 @@ function dropLeadingFragment(tail) {
100403
101122
  return firstBreak === -1 ? tail.text : tail.text.slice(firstBreak + 1);
100404
101123
  }
100405
101124
  function diskAccounting(sessionDir) {
100406
- const stats = readTokenStatsAt(join55(sessionDir, "tokens.json"));
101125
+ const stats = readTokenStatsAt(join56(sessionDir, "tokens.json"));
100407
101126
  return {
100408
101127
  tokensIn: stats?.billed_input_tokens ?? null,
100409
101128
  tokensOut: stats?.output_tokens ?? null,
@@ -100488,7 +101207,7 @@ class SessionManager {
100488
101207
  let dir = "";
100489
101208
  for (let attempt = 0;; attempt++) {
100490
101209
  id = `team-${randomUUID9().replace(/-/g, "").slice(0, 8)}`;
100491
- dir = join55(this.sessionsDir, id);
101210
+ dir = join56(this.sessionsDir, id);
100492
101211
  try {
100493
101212
  mkdirSync25(dir);
100494
101213
  break;
@@ -100503,7 +101222,7 @@ class SessionManager {
100503
101222
  sessionId: id,
100504
101223
  parentClaudeSessionId: opts.parentClaudeSessionId,
100505
101224
  startedAt,
100506
- teamPath: resolve7(opts.teamPath),
101225
+ teamPath: resolve8(opts.teamPath),
100507
101226
  slots: opts.slots
100508
101227
  });
100509
101228
  this.teamRunStarts.set(id, startedAt);
@@ -100520,9 +101239,9 @@ class SessionManager {
100520
101239
  if (dir === null)
100521
101240
  throw new Error("not a record id");
100522
101241
  const completedAt = new Date().toISOString();
100523
- const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join55(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
101242
+ const startedAt = this.teamRunStarts.get(record) ?? metaString(readJsonObject(join56(dir, "spawn.json"), META_READ_LIMIT)?.startedAt) ?? completedAt;
100524
101243
  const elapsedMs = Date.parse(completedAt) - Date.parse(startedAt);
100525
- writeJsonAtomic(join55(dir, "meta.json"), {
101244
+ writeJsonAtomic(join56(dir, "meta.json"), {
100526
101245
  kind: "team",
100527
101246
  status: outcome.status,
100528
101247
  startedAt,
@@ -100554,7 +101273,7 @@ class SessionManager {
100554
101273
  startedAt,
100555
101274
  ...rest
100556
101275
  };
100557
- writeJsonAtomic(join55(dir, "spawn.json"), body);
101276
+ writeJsonAtomic(join56(dir, "spawn.json"), body);
100558
101277
  }
100559
101278
  async createSession(opts) {
100560
101279
  if (this.activeSessions + this.starting >= this.maxSessions) {
@@ -100594,7 +101313,7 @@ class SessionManager {
100594
101313
  throw err;
100595
101314
  }
100596
101315
  if (inherited.layer) {
100597
- flags = [...inherited.restFlags, "--settings", join55(entry.sessionDir, INHERITED_SETTINGS)];
101316
+ flags = [...inherited.restFlags, "--settings", join56(entry.sessionDir, INHERITED_SETTINGS)];
100598
101317
  }
100599
101318
  this.sessions.set(entry.info.sessionId, entry);
100600
101319
  uncount();
@@ -100619,10 +101338,10 @@ class SessionManager {
100619
101338
  const timeout = normaliseTimeoutSeconds(opts.timeoutSeconds);
100620
101339
  const agentRequested = requestedAgentOf(opts.claudishFlags ?? []);
100621
101340
  const startedAt = new Date().toISOString();
100622
- const sessionDir = opts.sessionDir ?? join55(this.sessionsDir, sessionId);
101341
+ const sessionDir = opts.sessionDir ?? join56(this.sessionsDir, sessionId);
100623
101342
  mkdirSync25(sessionDir, { recursive: true });
100624
101343
  if (opts.prompt)
100625
- writeFileSync23(join55(sessionDir, "prompt.md"), opts.prompt, "utf-8");
101344
+ writeFileSync23(join56(sessionDir, "prompt.md"), opts.prompt, "utf-8");
100626
101345
  this.writeSpawnRecord(sessionDir, {
100627
101346
  kind: "session",
100628
101347
  sessionId,
@@ -100635,13 +101354,13 @@ class SessionManager {
100635
101354
  ...agentRequested ? { agentRequested } : {}
100636
101355
  });
100637
101356
  if (inheritedLayer) {
100638
- writeFileSync23(join55(sessionDir, INHERITED_SETTINGS), `${JSON.stringify(inheritedLayer, null, 2)}
101357
+ writeFileSync23(join56(sessionDir, INHERITED_SETTINGS), `${JSON.stringify(inheritedLayer, null, 2)}
100639
101358
  `, { encoding: "utf-8", mode: 384 });
100640
101359
  }
100641
101360
  const cwd = opts.cwd ?? process.cwd();
100642
101361
  const parentEnv = opts.parentEnv ?? this.parentEnv;
100643
101362
  const spawnModel = opts.spawnModel ?? null;
100644
- const tokenFile = opts.tokenFile ?? join55(sessionDir, "tokens.json");
101363
+ const tokenFile = opts.tokenFile ?? join56(sessionDir, "tokens.json");
100645
101364
  return {
100646
101365
  info: {
100647
101366
  sessionId,
@@ -100679,18 +101398,18 @@ class SessionManager {
100679
101398
  },
100680
101399
  session: null,
100681
101400
  scrollback: new ScrollbackBuffer(this.scrollbackCapacity),
100682
- outputLogStream: createWriteStream(join55(sessionDir, "output.log")),
101401
+ outputLogStream: createWriteStream(join56(sessionDir, "output.log")),
100683
101402
  sessionDir,
100684
101403
  cwd,
100685
101404
  tokenFile,
100686
- eventLogPath: join55(sessionDir, "events.jsonl"),
101405
+ eventLogPath: join56(sessionDir, "events.jsonl"),
100687
101406
  eventLogBytes: 0,
100688
- upstreamErrorLogPath: join55(sessionDir, "upstream-errors.jsonl"),
101407
+ upstreamErrorLogPath: join56(sessionDir, "upstream-errors.jsonl"),
100689
101408
  eventRing: [],
100690
101409
  proseBytes: 0,
100691
101410
  answeredTurns: new Map,
100692
101411
  inboxPath: null,
100693
- waitLogPath: join55(sessionDir, "waits.jsonl"),
101412
+ waitLogPath: join56(sessionDir, "waits.jsonl"),
100694
101413
  waitLogBytes: 0,
100695
101414
  waitSince: null,
100696
101415
  ended: false,
@@ -100885,7 +101604,7 @@ class SessionManager {
100885
101604
  try {
100886
101605
  const text = screenLines.join(`
100887
101606
  `).replace(/\s+$/, "");
100888
- writeFileSync23(join55(entry.sessionDir, "screen.txt"), `${redactSecrets(text)}
101607
+ writeFileSync23(join56(entry.sessionDir, "screen.txt"), `${redactSecrets(text)}
100889
101608
  `, "utf-8");
100890
101609
  } catch {}
100891
101610
  }
@@ -100899,7 +101618,7 @@ class SessionManager {
100899
101618
  entry.waitSince = null;
100900
101619
  }
100901
101620
  try {
100902
- writeJsonAtomic(join55(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
101621
+ writeJsonAtomic(join56(entry.sessionDir, "meta.json"), toMetaRecord(entry.info, entry.cwd));
100903
101622
  } catch (err) {
100904
101623
  process.stderr.write(`[claudish] session ${entry.info.sessionId}: could not write meta.json: ` + `${err instanceof Error ? err.message : String(err)}
100905
101624
  `);
@@ -101140,8 +101859,8 @@ class SessionManager {
101140
101859
  } catch {
101141
101860
  return null;
101142
101861
  }
101143
- const spawnPath = join55(sessionDir, "spawn.json");
101144
- const metaPath = join55(sessionDir, "meta.json");
101862
+ const spawnPath = join56(sessionDir, "spawn.json");
101863
+ const metaPath = join56(sessionDir, "meta.json");
101145
101864
  const spawnRecord = readJsonObject(spawnPath, META_READ_LIMIT);
101146
101865
  if (spawnRecord?.kind === "team")
101147
101866
  return null;
@@ -101233,9 +101952,9 @@ class SessionManager {
101233
101952
  diskSessionDir(sessionId) {
101234
101953
  if (!SESSION_ID_RE.test(sessionId))
101235
101954
  return null;
101236
- const root = resolve7(this.sessionsDir);
101237
- const dir = resolve7(root, sessionId);
101238
- if (dir !== join55(root, sessionId))
101955
+ const root = resolve8(this.sessionsDir);
101956
+ const dir = resolve8(root, sessionId);
101957
+ if (dir !== join56(root, sessionId))
101239
101958
  return null;
101240
101959
  if (!dir.startsWith(root + sep3))
101241
101960
  return null;
@@ -101254,10 +101973,10 @@ class SessionManager {
101254
101973
  } catch {
101255
101974
  return null;
101256
101975
  }
101257
- const spawnRecord = readJsonObject(join55(sessionDir, "spawn.json"), META_READ_LIMIT);
101976
+ const spawnRecord = readJsonObject(join56(sessionDir, "spawn.json"), META_READ_LIMIT);
101258
101977
  if (spawnRecord?.kind === "team")
101259
101978
  return null;
101260
- const meta = readJsonObject(join55(sessionDir, "meta.json"), META_READ_LIMIT);
101979
+ const meta = readJsonObject(join56(sessionDir, "meta.json"), META_READ_LIMIT);
101261
101980
  if (meta?.kind === "team")
101262
101981
  return null;
101263
101982
  const partial = meta === null;
@@ -101279,7 +101998,7 @@ class SessionManager {
101279
101998
  return stored;
101280
101999
  }
101281
102000
  diskOutput(record, tailLines) {
101282
- const tail = readTailText(join55(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
102001
+ const tail = readTailText(join56(record.sessionDir, "output.log"), OUTPUT_TAIL_BYTES);
101283
102002
  const buffer = new ScrollbackBuffer(this.scrollbackCapacity);
101284
102003
  if (tail?.text)
101285
102004
  buffer.append(dropLeadingFragment(tail));
@@ -101299,12 +102018,12 @@ class SessionManager {
101299
102018
  }
101300
102019
  diskDiagnostics(record, limit) {
101301
102020
  const { sessionDir, info } = record;
101302
- const eventLogPath = join55(sessionDir, "events.jsonl");
101303
- const upstreamErrorLogPath = join55(sessionDir, "upstream-errors.jsonl");
101304
- const outputLogPath = join55(sessionDir, "output.log");
102021
+ const eventLogPath = join56(sessionDir, "events.jsonl");
102022
+ const upstreamErrorLogPath = join56(sessionDir, "upstream-errors.jsonl");
102023
+ const outputLogPath = join56(sessionDir, "output.log");
101305
102024
  const events = readTailLines2(eventLogPath, EVENT_TAIL_BYTES);
101306
102025
  const outputTail = readTailText(outputLogPath, OUTPUT_TAIL_BYTES);
101307
- const screen = readTailText(join55(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
102026
+ const screen = readTailText(join56(sessionDir, "screen.txt"), SCREEN_READ_BYTES);
101308
102027
  return {
101309
102028
  sessionId: info.sessionId,
101310
102029
  state: info.state,
@@ -101710,7 +102429,7 @@ var init_progress_heartbeat = __esm(() => {
101710
102429
  import { spawn as spawn2 } from "child_process";
101711
102430
  import { mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, rmSync as rmSync14, writeFileSync as writeFileSync24 } from "fs";
101712
102431
  import { tmpdir as tmpdir2 } from "os";
101713
- import { join as join56 } from "path";
102432
+ import { join as join57 } from "path";
101714
102433
  function claudeCodePrintArgv(model, systemPromptFile) {
101715
102434
  const head = [PRINT_FLAGS[0], "--model", model, ...PRINT_FLAGS.slice(1)];
101716
102435
  return systemPromptFile !== undefined ? [...head, "--system-prompt-file", systemPromptFile] : [...head, "--system-prompt", ""];
@@ -101811,7 +102530,7 @@ function killGroup(proc) {
101811
102530
  timer.unref?.();
101812
102531
  }
101813
102532
  function stopAllClaudeCodePrintChildren() {
101814
- for (const proc of inFlight3)
102533
+ for (const proc of inFlight4)
101815
102534
  killGroup(proc);
101816
102535
  }
101817
102536
  function stderrTail(stderr) {
@@ -101828,13 +102547,13 @@ async function runPromptViaClaudeCode(model, prompt, systemPrompt, opts = {}) {
101828
102547
  } catch (e) {
101829
102548
  throw new Error(`cannot start claudish (${parentEnv.CLAUDISH_BIN?.trim() || "claudish"}): ${e instanceof Error ? e.message : String(e)}`);
101830
102549
  }
101831
- const root = mkdtempSync3(join56(tmpdir2(), "claudish-run-prompt-"));
102550
+ const root = mkdtempSync3(join57(tmpdir2(), "claudish-run-prompt-"));
101832
102551
  try {
101833
- const cwd = join56(root, "cwd");
102552
+ const cwd = join57(root, "cwd");
101834
102553
  mkdirSync26(cwd, { mode: 448 });
101835
102554
  let systemPromptFile;
101836
102555
  if (typeof systemPrompt === "string" && systemPrompt.length > 0) {
101837
- systemPromptFile = join56(root, "system-prompt.txt");
102556
+ systemPromptFile = join57(root, "system-prompt.txt");
101838
102557
  writeFileSync24(systemPromptFile, systemPrompt, { mode: 384 });
101839
102558
  }
101840
102559
  const argv = claudeCodePrintArgv(model, systemPromptFile);
@@ -101887,7 +102606,7 @@ function runChild(launch, argv, cwd, env, prompt, signal) {
101887
102606
  if (settled)
101888
102607
  return;
101889
102608
  settled = true;
101890
- inFlight3.delete(proc);
102609
+ inFlight4.delete(proc);
101891
102610
  signal?.removeEventListener("abort", onAbort);
101892
102611
  fn();
101893
102612
  };
@@ -101902,7 +102621,7 @@ function runChild(launch, argv, cwd, env, prompt, signal) {
101902
102621
  proc.stdin?.on("error", () => {});
101903
102622
  proc.on("error", (e) => finish(() => reject(startError(launch.command, e))));
101904
102623
  proc.on("close", (code, sig) => finish(() => resolvePromise({ code, signal: sig, stdout, stderr })));
101905
- inFlight3.add(proc);
102624
+ inFlight4.add(proc);
101906
102625
  signal?.addEventListener("abort", onAbort, { once: true });
101907
102626
  if (signal?.aborted)
101908
102627
  onAbort();
@@ -101914,7 +102633,7 @@ function startError(command, e) {
101914
102633
  const detail = typeof code === "string" ? code : e instanceof Error ? e.message : String(e ?? "unknown");
101915
102634
  return new Error(`cannot start claudish (${command}): ${detail}`);
101916
102635
  }
101917
- var PRINT_FLAGS, PRINT_CHILD_DROPPED_MARKERS, inFlight3, KILL_GRACE_MS = 5000, STDERR_TAIL_CHARS = 2000;
102636
+ var PRINT_FLAGS, PRINT_CHILD_DROPPED_MARKERS, inFlight4, KILL_GRACE_MS = 5000, STDERR_TAIL_CHARS = 2000;
101918
102637
  var init_run_prompt_claude_code = __esm(() => {
101919
102638
  init_config();
101920
102639
  init_child_env();
@@ -101936,7 +102655,7 @@ var init_run_prompt_claude_code = __esm(() => {
101936
102655
  ...PANE_LAUNCH_VARS,
101937
102656
  ENV.CLAUDISH_PANE_CHILD
101938
102657
  ];
101939
- inFlight3 = new Set;
102658
+ inFlight4 = new Set;
101940
102659
  });
101941
102660
 
101942
102661
  // src/providers/preflight-chain.ts
@@ -102136,7 +102855,7 @@ var init_preflight_chain = __esm(() => {
102136
102855
  // src/mcp-server.ts
102137
102856
  import { existsSync as existsSync39, readFileSync as readFileSync45, readdirSync as readdirSync11 } from "fs";
102138
102857
  import { homedir as homedir37 } from "os";
102139
- import { dirname as dirname22, join as join57, resolve as resolve8 } from "path";
102858
+ import { dirname as dirname22, join as join58, resolve as resolve9 } from "path";
102140
102859
  import { fileURLToPath as fileURLToPath3 } from "url";
102141
102860
  async function loadAllModels(forceRefresh = false) {
102142
102861
  if (!forceRefresh && existsSync39(OPENROUTER_MODELS_CACHE_PATH)) {
@@ -103328,7 +104047,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
103328
104047
  const promptText = () => {
103329
104048
  if (input !== undefined)
103330
104049
  return input;
103331
- const existing = join57(resolved, "input.md");
104050
+ const existing = join58(resolved, "input.md");
103332
104051
  return existsSync39(existing) ? readFileSync45(existing, "utf-8") : undefined;
103333
104052
  };
103334
104053
  const choice = modelChoiceStop([
@@ -103511,16 +104230,16 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
103511
104230
  const sp = session_path;
103512
104231
  for (const file of ["status.json", "manifest.json", "input.md"]) {
103513
104232
  try {
103514
- sessionData[file] = readFileSync45(join57(sp, file), "utf-8");
104233
+ sessionData[file] = readFileSync45(join58(sp, file), "utf-8");
103515
104234
  } catch {}
103516
104235
  }
103517
104236
  try {
103518
- const errorDir = join57(sp, "errors");
104237
+ const errorDir = join58(sp, "errors");
103519
104238
  if (existsSync39(errorDir)) {
103520
104239
  for (const f of readdirSync11(errorDir)) {
103521
104240
  if (f.endsWith(".log")) {
103522
104241
  try {
103523
- sessionData[`errors/${f}`] = readFileSync45(join57(errorDir, f), "utf-8");
104242
+ sessionData[`errors/${f}`] = readFileSync45(join58(errorDir, f), "utf-8");
103524
104243
  } catch {}
103525
104244
  }
103526
104245
  }
@@ -103530,7 +104249,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
103530
104249
  for (const f of readdirSync11(sp)) {
103531
104250
  if (f.startsWith("response-") && f.endsWith(".md")) {
103532
104251
  try {
103533
- const content = readFileSync45(join57(sp, f), "utf-8");
104252
+ const content = readFileSync45(join58(sp, f), "utf-8");
103534
104253
  sessionData[f] = content.slice(0, 200) + (content.length > 200 ? "... (truncated)" : "");
103535
104254
  } catch {}
103536
104255
  }
@@ -103539,7 +104258,7 @@ This searched OpenRouter's listing only. Subscription wire ids and catalog alias
103539
104258
  }
103540
104259
  let version = "unknown";
103541
104260
  try {
103542
- const pkgPath = join57(__dirname3, "../package.json");
104261
+ const pkgPath = join58(__dirname3, "../package.json");
103543
104262
  if (existsSync39(pkgPath)) {
103544
104263
  version = JSON.parse(readFileSync45(pkgPath, "utf-8")).version;
103545
104264
  }
@@ -103722,7 +104441,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
103722
104441
  const { specs, stripped } = strippedProviders([args.model], new Set(args.user_required_provider === true ? [args.model] : []));
103723
104442
  const requestedModel = specs[0];
103724
104443
  const choice = modelChoiceStop([{ given: args.model, spec: requestedModel }], new Set(args.user_required_model === true ? [args.model] : []), "with user_required_model: true", {
103725
- projectRulesReadable: typeof args.work_dir !== "string" || resolve8(args.work_dir) === process.cwd()
104444
+ projectRulesReadable: typeof args.work_dir !== "string" || resolve9(args.work_dir) === process.cwd()
103726
104445
  });
103727
104446
  if (choice)
103728
104447
  return choice;
@@ -103732,7 +104451,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
103732
104451
  }
103733
104452
  const workDir = args.work_dir;
103734
104453
  const plan = await prehydrateCredentialsForSpawn([requestedModel], {
103735
- pin: workDir === undefined || resolve8(workDir) === process.cwd()
104454
+ pin: workDir === undefined || resolve9(workDir) === process.cwd()
103736
104455
  });
103737
104456
  const parentClaudeSessionId = await parentSessionForCall({
103738
104457
  hostPid: sessionManager.hostPid
@@ -104221,8 +104940,8 @@ var init_mcp_server = __esm(() => {
104221
104940
  init_preflight_chain();
104222
104941
  __filename3 = fileURLToPath3(import.meta.url);
104223
104942
  __dirname3 = dirname22(__filename3);
104224
- CLAUDISH_CACHE_DIR = join57(homedir37(), ".claudish");
104225
- OPENROUTER_MODELS_CACHE_PATH = join57(CLAUDISH_CACHE_DIR, "openrouter-models.json");
104943
+ CLAUDISH_CACHE_DIR = join58(homedir37(), ".claudish");
104944
+ OPENROUTER_MODELS_CACHE_PATH = join58(CLAUDISH_CACHE_DIR, "openrouter-models.json");
104226
104945
  NEXT_STEP = {
104227
104946
  cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
104228
104947
  timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
@@ -104353,7 +105072,7 @@ var HOOKS_AMBIENT_DTS = '// claudish hook types \u2014 written by `claudish hook
104353
105072
  // src/hooks/observer/corpus.ts
104354
105073
  import { appendFileSync as appendFileSync8, readFileSync as readFileSync47, readdirSync as readdirSync12, statSync as statSync21 } from "fs";
104355
105074
  import { homedir as homedir38 } from "os";
104356
- import { join as join58 } from "path";
105075
+ import { join as join59 } from "path";
104357
105076
  function directoryOf2(filePath) {
104358
105077
  const slash = filePath.lastIndexOf("/");
104359
105078
  return slash > 0 ? filePath.slice(0, slash) : undefined;
@@ -104432,26 +105151,26 @@ function listTranscripts(root) {
104432
105151
  return files;
104433
105152
  }
104434
105153
  for (const project of projects) {
104435
- const dir = join58(root, project);
105154
+ const dir = join59(root, project);
104436
105155
  try {
104437
105156
  if (!statSync21(dir).isDirectory())
104438
105157
  continue;
104439
105158
  for (const f of readdirSync12(dir)) {
104440
105159
  if (f.endsWith(".jsonl"))
104441
- files.push(join58(dir, f));
105160
+ files.push(join59(dir, f));
104442
105161
  }
104443
105162
  } catch {}
104444
105163
  }
104445
105164
  return files;
104446
105165
  }
104447
105166
  function buildCorpus(options = {}) {
104448
- const root = options.projectsRoot ?? join58(homedir38(), ".claude", "projects");
105167
+ const root = options.projectsRoot ?? join59(homedir38(), ".claude", "projects");
104449
105168
  const files = listTranscripts(root);
104450
105169
  const records = [];
104451
105170
  for (const f of files)
104452
105171
  records.push(...replayTranscript(f));
104453
105172
  if (options.write && records.length > 0) {
104454
- const outputPath = options.outputPath ?? join58(homedir38(), ".claudish", "behavior-divergences.jsonl");
105173
+ const outputPath = options.outputPath ?? join59(homedir38(), ".claudish", "behavior-divergences.jsonl");
104455
105174
  try {
104456
105175
  appendFileSync8(outputPath, `${records.map((r) => JSON.stringify(r)).join(`
104457
105176
  `)}
@@ -104469,7 +105188,7 @@ var init_corpus = __esm(() => {
104469
105188
  // src/hooks-command.ts
104470
105189
  import { existsSync as existsSync41, mkdirSync as mkdirSync27, readFileSync as readFileSync48, writeFileSync as writeFileSync25 } from "fs";
104471
105190
  import { homedir as homedir39 } from "os";
104472
- import { join as join59, resolve as resolve9 } from "path";
105191
+ import { join as join60, resolve as resolve10 } from "path";
104473
105192
  function severityColor(sev) {
104474
105193
  if (sev === "fix")
104475
105194
  return green(sev);
@@ -104559,7 +105278,7 @@ async function check(file, json) {
104559
105278
  console.error("Usage: claudish hooks check <file> [--json]");
104560
105279
  return 1;
104561
105280
  }
104562
- const path = resolve9(process.cwd(), file);
105281
+ const path = resolve10(process.cwd(), file);
104563
105282
  const notes = [];
104564
105283
  const loaded = await loadUserHooks({
104565
105284
  modules: [path],
@@ -104592,10 +105311,10 @@ async function check(file, json) {
104592
105311
  return valid ? 0 : 1;
104593
105312
  }
104594
105313
  function init(dirArg) {
104595
- const dir = dirArg ? resolve9(process.cwd(), dirArg) : join59(homedir39(), ".claudish", "hooks");
105314
+ const dir = dirArg ? resolve10(process.cwd(), dirArg) : join60(homedir39(), ".claudish", "hooks");
104596
105315
  const files = [
104597
- [join59(dir, "example-hook.ts"), EXAMPLE_HOOK],
104598
- [join59(dir, "claudish-hooks.d.ts"), HOOKS_AMBIENT_DTS]
105316
+ [join60(dir, "example-hook.ts"), EXAMPLE_HOOK],
105317
+ [join60(dir, "claudish-hooks.d.ts"), HOOKS_AMBIENT_DTS]
104599
105318
  ];
104600
105319
  const existing = files.filter(([path]) => existsSync41(path)).map(([path]) => path);
104601
105320
  if (existing.length > 0) {
@@ -104798,7 +105517,7 @@ var init_hooks_command = __esm(() => {
104798
105517
  import { spawn as spawn3 } from "child_process";
104799
105518
  import { existsSync as existsSync42, readFileSync as readFileSync49, writeFileSync as writeFileSync26 } from "fs";
104800
105519
  import { connect as netConnect2 } from "net";
104801
- import { join as join60 } from "path";
105520
+ import { join as join61 } from "path";
104802
105521
  import { setTimeout as wait } from "timers/promises";
104803
105522
  function paneRouteLine(explanation) {
104804
105523
  const origin = describeRouteExplanation(explanation);
@@ -104948,9 +105667,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
104948
105667
  const keep = opts?.keep ?? false;
104949
105668
  const manifest = setupSession(sessionPath, models, input);
104950
105669
  const startedAt = new Date().toISOString();
104951
- const gridfilePath = join60(sessionPath, "gridfile.txt");
104952
- const prompt = readFileSync49(join60(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
104953
- const rawPrompt = readFileSync49(join60(sessionPath, "input.md"), "utf-8");
105670
+ const gridfilePath = join61(sessionPath, "gridfile.txt");
105671
+ const prompt = readFileSync49(join61(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
105672
+ const rawPrompt = readFileSync49(join61(sessionPath, "input.md"), "utf-8");
104954
105673
  const usedBannerColors = new Set;
104955
105674
  const routeLines = mode === "interactive" ? new Map : await resolvePaneRouteLines(Object.values(manifest.models).map((entry) => entry.model));
104956
105675
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
@@ -104982,7 +105701,7 @@ async function runWithGrid(sessionPath, models, input, opts) {
104982
105701
  });
104983
105702
  const [{ results }] = await Promise.all([subscription, procExit]);
104984
105703
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
104985
- const statusPath = join60(sessionPath, "status.json");
105704
+ const statusPath = join61(sessionPath, "status.json");
104986
105705
  writeFileSync26(statusPath, JSON.stringify(status, null, 2), "utf-8");
104987
105706
  return status;
104988
105707
  }
@@ -105003,7 +105722,7 @@ var init_team_grid = __esm(() => {
105003
105722
 
105004
105723
  // src/team-cli.ts
105005
105724
  import { readFileSync as readFileSync50 } from "fs";
105006
- import { join as join61 } from "path";
105725
+ import { join as join62 } from "path";
105007
105726
  async function runPaneTeam(sessionPath, models, input, opts) {
105008
105727
  installPaneShutdownHooks({ exitAfter: true });
105009
105728
  try {
@@ -105148,7 +105867,7 @@ async function teamCommand(args) {
105148
105867
  }
105149
105868
  case "judge": {
105150
105869
  await judgeResponses(sessionPath, { judges });
105151
- console.log(readFileSync50(join61(sessionPath, "verdict.md"), "utf-8"));
105870
+ console.log(readFileSync50(join62(sessionPath, "verdict.md"), "utf-8"));
105152
105871
  break;
105153
105872
  }
105154
105873
  case "run-and-judge": {
@@ -105164,7 +105883,7 @@ async function teamCommand(args) {
105164
105883
  });
105165
105884
  printStatus(status);
105166
105885
  await judgeResponses(sessionPath, { judges });
105167
- console.log(readFileSync50(join61(sessionPath, "verdict.md"), "utf-8"));
105886
+ console.log(readFileSync50(join62(sessionPath, "verdict.md"), "utf-8"));
105168
105887
  break;
105169
105888
  }
105170
105889
  case "status": {
@@ -107068,8 +107787,10 @@ A command that takes <label> also takes the account's email.
107068
107787
  --id <generated-id> may replace or confirm the label; labels are unique per claudish provider.
107069
107788
  Removing the legacy login (default, legacy-codex-file) requires --delete-file.
107070
107789
  New accounts use the macOS Keychain, or protected plaintext files where there is none.
107790
+ On macOS the legacy login (~/.claudish/codex-oauth.json) moves into the Keychain on first use.
107071
107791
  Add only accounts you own; vendor terms may restrict switching between subscriptions.
107072
- With Codex accounts configured, use account add/login instead of \`claudish login codex\`.
107792
+ On macOS, \`claudish login codex\` adds or logs in your one Codex account; with several, use account login.
107793
+ Elsewhere, with Codex accounts configured, use account add/login instead of \`claudish login codex\`.
107073
107794
  Claude account mutations, import and use are not available. No token input or export.`, MUTATION_VERBS, HOUR_MS19 = 3600000, FORECAST_HELP = `Usage:
107074
107795
  claudish account forecast [--json] [--horizon <h|d>] [--provider codex]
107075
107796
 
@@ -107092,7 +107813,7 @@ var init_account_command = __esm(() => {
107092
107813
  import { spawnSync as spawnSync2 } from "child_process";
107093
107814
  import { existsSync as existsSync43, unlinkSync as unlinkSync12 } from "fs";
107094
107815
  import { homedir as homedir40 } from "os";
107095
- import { join as join62 } from "path";
107816
+ import { join as join63 } from "path";
107096
107817
  async function defaultSuggestModel() {
107097
107818
  try {
107098
107819
  const token = await getValidAntigravityAccessToken();
@@ -107211,7 +107932,7 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
107211
107932
  async logout(deps) {
107212
107933
  deleteSharedAntigravityToken(deps);
107213
107934
  try {
107214
- const tokenFile = join62(homedir40(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
107935
+ const tokenFile = join63(homedir40(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
107215
107936
  if (existsSync43(tokenFile))
107216
107937
  unlinkSync12(tokenFile);
107217
107938
  } catch {}
@@ -115403,9 +116124,9 @@ var init_tui = __esm(() => {
115403
116124
  // src/auth/claude-code-facts.ts
115404
116125
  import { createHash as createHash14 } from "crypto";
115405
116126
  import { homedir as homedir41 } from "os";
115406
- import { isAbsolute as isAbsolute2, resolve as resolve10 } from "path";
116127
+ import { isAbsolute as isAbsolute2, resolve as resolve11 } from "path";
115407
116128
  function defaultConfigDir(env) {
115408
- return resolve10(env.HOME || homedir41(), ".claude");
116129
+ return resolve11(env.HOME || homedir41(), ".claude");
115409
116130
  }
115410
116131
  function configDirHash(configDir) {
115411
116132
  return createHash14("sha256").update(configDir).digest("hex").slice(0, 8);
@@ -115414,7 +116135,7 @@ function claudeCodeKeychainService(env = process.env) {
115414
116135
  const raw = env.CLAUDE_CONFIG_DIR?.trim();
115415
116136
  if (!raw)
115416
116137
  return `${BASE_KEYCHAIN_SERVICE}${KEYCHAIN_SERVICE_SUFFIX}`;
115417
- const resolved = isAbsolute2(raw) ? resolve10(raw) : resolve10(process.cwd(), raw);
116138
+ const resolved = isAbsolute2(raw) ? resolve11(raw) : resolve11(process.cwd(), raw);
115418
116139
  if (resolved === defaultConfigDir(env)) {
115419
116140
  return `${BASE_KEYCHAIN_SERVICE}${KEYCHAIN_SERVICE_SUFFIX}`;
115420
116141
  }
@@ -115490,7 +116211,7 @@ var init_own_pane = __esm(() => {
115490
116211
 
115491
116212
  // src/hooks/session/interactive-driver.ts
115492
116213
  import { existsSync as existsSync44, statSync as statSync22 } from "fs";
115493
- import { join as join63 } from "path";
116214
+ import { join as join64 } from "path";
115494
116215
  function nextDriverPhase(phase, ev) {
115495
116216
  const row = EDGES[phase];
115496
116217
  return row[ev] ?? null;
@@ -115708,7 +116429,7 @@ class InteractiveDriver {
115708
116429
  }
115709
116430
  follow(sid, offset) {
115710
116431
  const path = transcriptPathFor(this.o.cwd, sid, this.o.projectsRoot);
115711
- this.follower = new TranscriptFollower(path, join63(path.replace(/\.jsonl$/, ""), "subagents"));
116432
+ this.follower = new TranscriptFollower(path, join64(path.replace(/\.jsonl$/, ""), "subagents"));
115712
116433
  this.sessionId = sid;
115713
116434
  this.settledTurn = null;
115714
116435
  this.lastPollAt = Date.now();
@@ -116823,7 +117544,7 @@ import {
116823
117544
  writeFileSync as writeFileSync27
116824
117545
  } from "fs";
116825
117546
  import { homedir as homedir42, tmpdir as tmpdir3 } from "os";
116826
- import { dirname as dirname23, join as join64 } from "path";
117547
+ import { dirname as dirname23, join as join65 } from "path";
116827
117548
  import { isatty } from "tty";
116828
117549
  function releaseTerminalIsolation() {
116829
117550
  if (!restoreTerminal)
@@ -116895,7 +117616,7 @@ function hasResolvableAnthropicAuth(deps = {}) {
116895
117616
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
116896
117617
  return true;
116897
117618
  }
116898
- if (fileExists(join64(homedir42(), ".claude", ".credentials.json")))
117619
+ if (fileExists(join65(homedir42(), ".claude", ".credentials.json")))
116899
117620
  return true;
116900
117621
  return keychainProbe();
116901
117622
  }
@@ -116995,10 +117716,10 @@ function readableClaudeSettingsLayers(config, env, deps) {
116995
117716
  const cwd = deps.cwd ?? process.cwd();
116996
117717
  const files = [
116997
117718
  managedSettingsPath(),
116998
- join64(home, ".claude", "settings.json"),
116999
- ...env.CLAUDE_CONFIG_DIR ? [join64(env.CLAUDE_CONFIG_DIR, "settings.json")] : [],
117000
- join64(cwd, ".claude", "settings.json"),
117001
- join64(cwd, ".claude", "settings.local.json")
117719
+ join65(home, ".claude", "settings.json"),
117720
+ ...env.CLAUDE_CONFIG_DIR ? [join65(env.CLAUDE_CONFIG_DIR, "settings.json")] : [],
117721
+ join65(cwd, ".claude", "settings.json"),
117722
+ join65(cwd, ".claude", "settings.local.json")
117002
117723
  ];
117003
117724
  const layers = [];
117004
117725
  for (const file of files) {
@@ -117030,7 +117751,7 @@ function readSettingsSource(source, readFile) {
117030
117751
  }
117031
117752
  function managedSettingsPath() {
117032
117753
  if (isWindows2()) {
117033
- return join64(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
117754
+ return join65(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
117034
117755
  }
117035
117756
  if (process.platform === "darwin") {
117036
117757
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -117051,8 +117772,8 @@ function isWindows2() {
117051
117772
  }
117052
117773
  function createStatusLineScript(tokenFilePath) {
117053
117774
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
117054
- const claudishDir = join64(homeDir, ".claudish");
117055
- const scriptPath = join64(claudishDir, `status-${process.pid}-${Date.now()}.js`);
117775
+ const claudishDir = join65(homeDir, ".claudish");
117776
+ const scriptPath = join65(claudishDir, `status-${process.pid}-${Date.now()}.js`);
117056
117777
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
117057
117778
  const light = getThemeMode() === "light";
117058
117779
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -117244,7 +117965,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
117244
117965
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
117245
117966
  continue;
117246
117967
  scanned++;
117247
- const full = join64(dir, name);
117968
+ const full = join65(dir, name);
117248
117969
  try {
117249
117970
  if (statSync23(full).mtimeMs >= cutoff)
117250
117971
  continue;
@@ -117273,9 +117994,9 @@ function parseSettingsArgSafe(value) {
117273
117994
  }
117274
117995
  function userSettingsFileCandidates(cwd) {
117275
117996
  return [
117276
- join64(homedir42(), ".claude", "settings.json"),
117277
- join64(cwd, ".claude", "settings.json"),
117278
- join64(cwd, ".claude", "settings.local.json")
117997
+ join65(homedir42(), ".claude", "settings.json"),
117998
+ join65(cwd, ".claude", "settings.json"),
117999
+ join65(cwd, ".claude", "settings.local.json")
117279
118000
  ];
117280
118001
  }
117281
118002
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
@@ -117316,11 +118037,11 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
117316
118037
  }
117317
118038
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
117318
118039
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir3();
117319
- const claudishDir = join64(homeDir, ".claudish");
118040
+ const claudishDir = join65(homeDir, ".claudish");
117320
118041
  try {
117321
118042
  mkdirSync28(claudishDir, { recursive: true });
117322
118043
  } catch {}
117323
- const tempPath = join64(claudishDir, `settings-${process.pid}-${Date.now()}.json`);
118044
+ const tempPath = join65(claudishDir, `settings-${process.pid}-${Date.now()}.json`);
117324
118045
  const assignedFile = assignedTokenFile(process.env) !== null;
117325
118046
  const tokenFilePath = resolveTokenFilePath(port);
117326
118047
  cleanupStaleTokenFiles(claudishDir);
@@ -117711,7 +118432,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
117711
118432
  console.error(`
117712
118433
  Or set CLAUDE_PATH to your custom installation:`);
117713
118434
  const home = homedir42();
117714
- const localPath = isWindows2() ? join64(home, ".claude", "local", "claude.exe") : join64(home, ".claude", "local", "claude");
118435
+ const localPath = isWindows2() ? join65(home, ".claude", "local", "claude.exe") : join65(home, ".claude", "local", "claude");
117715
118436
  console.error(` export CLAUDE_PATH=${localPath}`);
117716
118437
  process.exit(1);
117717
118438
  }
@@ -117746,7 +118467,7 @@ Or set CLAUDE_PATH to your custom installation:`);
117746
118467
  let driverLaunch = null;
117747
118468
  if (surfaces.driver) {
117748
118469
  const minted = sessionFlags.sessionId || sessionFlags.resumes ? null : randomUUID11();
117749
- const turnDir = mkdtempSync4(join64(tmpdir3(), "claudish-turns-"));
118470
+ const turnDir = mkdtempSync4(join65(tmpdir3(), "claudish-turns-"));
117750
118471
  chmodSync5(turnDir, 448);
117751
118472
  claudeArgs.unshift(...driverLeadingArgs({ mintedSessionId: minted, turnDir }));
117752
118473
  const sink = new InProcessSink;
@@ -117895,15 +118616,15 @@ async function findClaudeBinary() {
117895
118616
  }
117896
118617
  }
117897
118618
  const home = homedir42();
117898
- const localPath = isWindows ? join64(home, ".claude", "local", "claude.exe") : join64(home, ".claude", "local", "claude");
118619
+ const localPath = isWindows ? join65(home, ".claude", "local", "claude.exe") : join65(home, ".claude", "local", "claude");
117899
118620
  if (existsSync46(localPath)) {
117900
118621
  return localPath;
117901
118622
  }
117902
118623
  if (isWindows) {
117903
118624
  const windowsPaths = [
117904
- join64(home, "AppData", "Roaming", "npm", "claude.cmd"),
117905
- join64(home, ".npm-global", "claude.cmd"),
117906
- join64(home, "node_modules", ".bin", "claude.cmd")
118625
+ join65(home, "AppData", "Roaming", "npm", "claude.cmd"),
118626
+ join65(home, ".npm-global", "claude.cmd"),
118627
+ join65(home, "node_modules", ".bin", "claude.cmd")
117907
118628
  ];
117908
118629
  for (const path of windowsPaths) {
117909
118630
  if (existsSync46(path)) {
@@ -117914,11 +118635,11 @@ async function findClaudeBinary() {
117914
118635
  const commonPaths = [
117915
118636
  "/usr/local/bin/claude",
117916
118637
  "/opt/homebrew/bin/claude",
117917
- join64(home, ".npm-global/bin/claude"),
117918
- join64(home, ".local/bin/claude"),
117919
- join64(home, "node_modules/.bin/claude"),
118638
+ join65(home, ".npm-global/bin/claude"),
118639
+ join65(home, ".local/bin/claude"),
118640
+ join65(home, "node_modules/.bin/claude"),
117920
118641
  "/data/data/com.termux/files/usr/bin/claude",
117921
- join64(home, "../usr/bin/claude")
118642
+ join65(home, "../usr/bin/claude")
117922
118643
  ];
117923
118644
  for (const path of commonPaths) {
117924
118645
  if (existsSync46(path)) {
@@ -118027,16 +118748,16 @@ var init_claude_runner = __esm(() => {
118027
118748
  // src/diag-output.ts
118028
118749
  import { createWriteStream as createWriteStream2, mkdirSync as mkdirSync29, unlinkSync as unlinkSync14, writeFileSync as writeFileSync28 } from "fs";
118029
118750
  import { homedir as homedir43 } from "os";
118030
- import { join as join65 } from "path";
118751
+ import { join as join66 } from "path";
118031
118752
  function getClaudishDir() {
118032
- const dir = join65(homedir43(), ".claudish");
118753
+ const dir = join66(homedir43(), ".claudish");
118033
118754
  try {
118034
118755
  mkdirSync29(dir, { recursive: true });
118035
118756
  } catch {}
118036
118757
  return dir;
118037
118758
  }
118038
118759
  function getDiagLogPath() {
118039
- return join65(getClaudishDir(), `diag-${process.pid}.log`);
118760
+ return join66(getClaudishDir(), `diag-${process.pid}.log`);
118040
118761
  }
118041
118762
 
118042
118763
  class LogFileDiagOutput {
@@ -120698,7 +121419,7 @@ init_op_source();
120698
121419
  init_startup_trace();
120699
121420
  init_picker_cancelled();
120700
121421
  import { existsSync as existsSync47, readFileSync as readFileSync53 } from "fs";
120701
- import { join as join66, resolve as resolve11 } from "path";
121422
+ import { join as join67, resolve as resolve12 } from "path";
120702
121423
 
120703
121424
  // src/tui/runtime/should-open-picker.ts
120704
121425
  function noModelConfigured(gate) {
@@ -120715,7 +121436,7 @@ function requiresExplicitModel(gate, tty) {
120715
121436
  var autoloadDropped = [...droppedAtLaunch, ...takeLauncherDropped(process.env)];
120716
121437
  var dotenvText;
120717
121438
  try {
120718
- dotenvText = readFileSync53(join66(process.cwd(), ".env"));
121439
+ dotenvText = readFileSync53(join67(process.cwd(), ".env"));
120719
121440
  } catch {}
120720
121441
  if (dotenvText !== undefined) {
120721
121442
  await Promise.resolve().then(() => init_dotenv_allowlist());
@@ -120830,7 +121551,7 @@ async function applyOpImport() {
120830
121551
  async function applyConfigOverride() {
120831
121552
  await Promise.resolve();
120832
121553
  const plan = planConfigOverride(process.argv.slice(2), process.env, {
120833
- resolve: resolve11,
121554
+ resolve: resolve12,
120834
121555
  exists: existsSync47
120835
121556
  });
120836
121557
  if (plan.kind === "none")
@@ -121063,7 +121784,7 @@ async function runCli() {
121063
121784
  process.exit(1);
121064
121785
  }
121065
121786
  const mode = cliConfig.teamMode ?? "default";
121066
- const sessionPath = join66(process.cwd(), `.claudish-team-${Date.now()}`);
121787
+ const sessionPath = join67(process.cwd(), `.claudish-team-${Date.now()}`);
121067
121788
  if (mode === "json") {
121068
121789
  await Promise.resolve().then(() => init_pane());
121069
121790
  await Promise.resolve().then(() => init_team_orchestrator());
@@ -121083,7 +121804,7 @@ async function runCli() {
121083
121804
  const status = await runModels(sessionPath);
121084
121805
  const result = { ...status, responses: {} };
121085
121806
  for (const anonId of Object.keys(status.models)) {
121086
- const responsePath = join66(sessionPath, `response-${anonId}.md`);
121807
+ const responsePath = join67(sessionPath, `response-${anonId}.md`);
121087
121808
  try {
121088
121809
  const raw = readFileSync53(responsePath, "utf-8").trim();
121089
121810
  try {