codexuse-cli 5.6.0 → 5.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6228,13 +6228,20 @@ async function openLockDatabase(databasePath) {
6228
6228
  throw error;
6229
6229
  }
6230
6230
  }
6231
- async function acquireCrossProcessFileLock(filePath) {
6231
+ async function acquireCrossProcessFileLockTransaction(filePath, mode) {
6232
6232
  const databasePath = `${import_node_path8.default.resolve(filePath)}.codexuse-lock.sqlite`;
6233
6233
  let database = null;
6234
6234
  try {
6235
6235
  database = await openLockDatabase(databasePath);
6236
+ database.exec("PRAGMA busy_timeout = 1000;");
6237
+ database.exec("CREATE TABLE IF NOT EXISTS codexuse_file_lock (id INTEGER PRIMARY KEY);");
6236
6238
  database.exec("PRAGMA busy_timeout = 0;");
6237
- database.exec("BEGIN IMMEDIATE;");
6239
+ if (mode === "write") {
6240
+ database.exec("BEGIN EXCLUSIVE;");
6241
+ } else {
6242
+ database.exec("BEGIN;");
6243
+ database.exec("SELECT COUNT(*) FROM codexuse_file_lock;");
6244
+ }
6238
6245
  } catch (error) {
6239
6246
  database?.close();
6240
6247
  if (isSqliteBusyError2(error)) {
@@ -6256,6 +6263,12 @@ async function acquireCrossProcessFileLock(filePath) {
6256
6263
  }
6257
6264
  };
6258
6265
  }
6266
+ async function acquireCrossProcessFileLock(filePath) {
6267
+ return acquireCrossProcessFileLockTransaction(filePath, "write");
6268
+ }
6269
+ async function acquireCrossProcessFileReadLock(filePath) {
6270
+ return acquireCrossProcessFileLockTransaction(filePath, "read");
6271
+ }
6259
6272
 
6260
6273
  // ../../packages/shared/src/core/logger.ts
6261
6274
  var isTestEnv = process.env.NODE_ENV === "test" || process.env.VITEST === "true" || process.env.VITEST === "1";
@@ -32534,6 +32547,21 @@ async function acquireCodexAuthFileLock(filePath) {
32534
32547
  throw error;
32535
32548
  }
32536
32549
  }
32550
+ async function acquireCodexAuthFileReadLock(filePath) {
32551
+ const queueKey = import_node_path9.default.resolve(filePath);
32552
+ await (authFileLockQueues.get(queueKey) ?? Promise.resolve());
32553
+ const releaseReadLock = await acquireCrossProcessFileReadLock(filePath);
32554
+ if (!releaseReadLock) {
32555
+ return null;
32556
+ }
32557
+ const needsRecovery = await readOptionalFile(authWritebackPaths(filePath).manifest) !== null;
32558
+ if (!needsRecovery) {
32559
+ return { mode: "read", release: releaseReadLock };
32560
+ }
32561
+ await releaseReadLock();
32562
+ const releaseWriteLock = await acquireCodexAuthFileLock(filePath);
32563
+ return releaseWriteLock ? { mode: "write", release: releaseWriteLock } : null;
32564
+ }
32537
32565
  async function readAuthFileCached(filePath) {
32538
32566
  let stats;
32539
32567
  try {
@@ -32708,15 +32736,15 @@ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
32708
32736
  const tempAuthPath = import_node_path9.default.join(tempHome, "auth.json");
32709
32737
  let initialSourceAuth;
32710
32738
  try {
32711
- const releaseLock = await acquireCodexAuthFileLock(sourceAuthPath);
32712
- if (!releaseLock) {
32739
+ const authLock = await acquireCodexAuthFileReadLock(sourceAuthPath);
32740
+ if (!authLock) {
32713
32741
  await import_node_fs4.promises.rm(tempHome, { recursive: true, force: true });
32714
32742
  return null;
32715
32743
  }
32716
32744
  try {
32717
32745
  initialSourceAuth = await readAuthFileCached(sourceAuthPath);
32718
32746
  } finally {
32719
- await releaseLock();
32747
+ await authLock.release();
32720
32748
  }
32721
32749
  if (!initialSourceAuth) {
32722
32750
  await import_node_fs4.promises.rm(tempHome, { recursive: true, force: true });
@@ -32843,15 +32871,15 @@ async function activateResetWindowViaRpcIsolated(envOverride, options, sourceAut
32843
32871
  const tempAuthPath = import_node_path9.default.join(tempHome, "auth.json");
32844
32872
  let initialSourceAuth;
32845
32873
  try {
32846
- const releaseLock = await acquireCodexAuthFileLock(sourceAuthPath);
32847
- if (!releaseLock) {
32874
+ const authLock = await acquireCodexAuthFileReadLock(sourceAuthPath);
32875
+ if (!authLock) {
32848
32876
  await import_node_fs4.promises.rm(tempHome, { recursive: true, force: true });
32849
32877
  return { status: "skipped", reason: "auth-busy" };
32850
32878
  }
32851
32879
  try {
32852
32880
  initialSourceAuth = await readAuthFileCached(sourceAuthPath);
32853
32881
  } finally {
32854
- await releaseLock();
32882
+ await authLock.release();
32855
32883
  }
32856
32884
  if (!initialSourceAuth) {
32857
32885
  await import_node_fs4.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
@@ -43081,6 +43109,7 @@ var ProfileManager = class {
43081
43109
  this.jwtPayloadCache = /* @__PURE__ */ new Map();
43082
43110
  this.profileRecordIndexCache = null;
43083
43111
  this.credentialsMigrationPromise = null;
43112
+ this.blockedProfileMigrationErrors = /* @__PURE__ */ new Map();
43084
43113
  this.profileHomeAuthSyncCache = /* @__PURE__ */ new Map();
43085
43114
  }
43086
43115
  computeExpiryIso(data) {
@@ -43133,6 +43162,16 @@ var ProfileManager = class {
43133
43162
  normalizeProfileHomeId(value3, fallbackName) {
43134
43163
  return normalizeProfileHomeId(value3) ?? this.normalizeProfileName(fallbackName);
43135
43164
  }
43165
+ assertProfileHomeUsable(profileName) {
43166
+ const migrationError = this.blockedProfileMigrationErrors.get(profileName);
43167
+ if (!migrationError) {
43168
+ return;
43169
+ }
43170
+ throw errorWithCause(
43171
+ `Profile '${profileName}' cannot be used because another profile references the same private auth store. Delete one conflicting profile, then restart CodexUse.`,
43172
+ migrationError
43173
+ );
43174
+ }
43136
43175
  isNotFoundError(error) {
43137
43176
  return Boolean(
43138
43177
  error && typeof error === "object" && "code" in error && error.code === "ENOENT"
@@ -43814,8 +43853,7 @@ var ProfileManager = class {
43814
43853
  }
43815
43854
  if (!this.credentialsMigrationPromise) {
43816
43855
  this.credentialsMigrationPromise = this.migrateCredentialsToProfileHomes().catch((error) => {
43817
- this.credentialsMigrationPromise = null;
43818
- throw error;
43856
+ logError("Deferred profile credential migration because the migration pass failed:", error);
43819
43857
  });
43820
43858
  }
43821
43859
  await this.credentialsMigrationPromise;
@@ -43832,9 +43870,12 @@ var ProfileManager = class {
43832
43870
  const collisionKey = homeId.toLowerCase();
43833
43871
  const existingProfileName = profileNamesByHomeId.get(collisionKey);
43834
43872
  if (existingProfileName && existingProfileName !== profileName) {
43835
- throw new Error(
43873
+ const collisionError = new Error(
43836
43874
  `Profiles '${existingProfileName}' and '${profileName}' use the same private auth store. Refresh one profile before retrying.`
43837
43875
  );
43876
+ this.blockedProfileMigrationErrors.set(existingProfileName, collisionError);
43877
+ this.blockedProfileMigrationErrors.set(profileName, collisionError);
43878
+ logWarn("Deferred profiles that reference the same private auth store:", collisionError);
43838
43879
  }
43839
43880
  homeIdsByProfileName.set(profileName, homeId);
43840
43881
  profileNamesByHomeId.set(collisionKey, profileName);
@@ -43855,62 +43896,94 @@ var ProfileManager = class {
43855
43896
  let changed = false;
43856
43897
  const nextMap = {};
43857
43898
  for (const [profileName, raw] of Object.entries(source)) {
43858
- const record3 = this.fromStateRecord(profileName, raw);
43859
- if (!record3) {
43899
+ if (this.blockedProfileMigrationErrors.has(profileName)) {
43860
43900
  nextMap[profileName] = raw;
43861
43901
  continue;
43862
43902
  }
43863
- const nextHomeId = homeIdsByProfileName.get(profileName) ?? record3.homeId;
43864
- const hasStateCredentials = Boolean(
43865
- record3.data.id_token || record3.data.access_token || record3.data.refresh_token
43866
- );
43867
- const needsHomeId = normalizeProfileHomeId(raw.homeId) === null;
43868
- if (needsHomeId && record3.homeId !== nextHomeId) {
43869
- const sourceHome = this.getProfileHomePath(record3.homeId);
43870
- const targetHome = this.getProfileHomePath(nextHomeId);
43871
- let sourceExists = true;
43872
- try {
43873
- await import_fs.promises.access(sourceHome);
43874
- } catch (error) {
43875
- if (!this.isNotFoundError(error)) {
43876
- throw error;
43877
- }
43878
- sourceExists = false;
43903
+ try {
43904
+ let record3 = this.fromStateRecord(profileName, raw);
43905
+ if (!record3) {
43906
+ nextMap[profileName] = raw;
43907
+ continue;
43879
43908
  }
43880
- if (sourceExists) {
43909
+ const nextHomeId = homeIdsByProfileName.get(profileName) ?? record3.homeId;
43910
+ const hasStateCredentials = Boolean(
43911
+ record3.data.id_token || record3.data.access_token || record3.data.refresh_token
43912
+ );
43913
+ const needsHomeId = normalizeProfileHomeId(raw.homeId) === null;
43914
+ if (needsHomeId && record3.homeId !== nextHomeId) {
43915
+ const sourceHome = this.getProfileHomePath(record3.homeId);
43916
+ const targetHome = this.getProfileHomePath(nextHomeId);
43917
+ let sourceExists = true;
43881
43918
  try {
43882
- await import_fs.promises.access(targetHome);
43883
- throw new Error(
43884
- `Profile '${profileName}' cannot migrate its private auth store because the target already exists.`
43885
- );
43919
+ await import_fs.promises.access(sourceHome);
43886
43920
  } catch (error) {
43887
43921
  if (!this.isNotFoundError(error)) {
43888
43922
  throw error;
43889
43923
  }
43924
+ sourceExists = false;
43890
43925
  }
43891
- const releaseGuard = await this.acquireProfileHomeMutationGuard(record3);
43892
- try {
43893
- await import_fs.promises.rename(sourceHome, targetHome);
43894
- } finally {
43895
- await releaseGuard();
43926
+ if (sourceExists) {
43927
+ let targetExists = true;
43928
+ try {
43929
+ await import_fs.promises.access(targetHome);
43930
+ } catch (error) {
43931
+ if (!this.isNotFoundError(error)) {
43932
+ throw error;
43933
+ }
43934
+ targetExists = false;
43935
+ }
43936
+ if (targetExists) {
43937
+ const sourceHydration = await this.hydrateProfileRecordWithStatus(record3);
43938
+ let targetAuth = null;
43939
+ try {
43940
+ const targetRaw = await this.readProfileAuthFile(
43941
+ (0, import_path2.join)(targetHome, "auth.json")
43942
+ );
43943
+ targetAuth = targetRaw ? JSON.parse(targetRaw) : null;
43944
+ } catch (error) {
43945
+ logWarn(
43946
+ `Deferred private auth migration for '${profileName}' because the existing target could not be verified:`,
43947
+ error
43948
+ );
43949
+ }
43950
+ if (!sourceHydration.cacheable || !targetAuth || !this.activeAuthMatchesRecord(targetAuth, sourceHydration.record)) {
43951
+ logWarn(
43952
+ `Deferred private auth migration for '${profileName}' because the existing target belongs to unverified credentials.`
43953
+ );
43954
+ nextMap[profileName] = raw;
43955
+ continue;
43956
+ }
43957
+ record3 = sourceHydration.record;
43958
+ } else {
43959
+ const releaseGuard = await this.acquireProfileHomeMutationGuard(record3);
43960
+ try {
43961
+ await import_fs.promises.rename(sourceHome, targetHome);
43962
+ } finally {
43963
+ await releaseGuard();
43964
+ }
43965
+ }
43896
43966
  }
43897
43967
  }
43898
- }
43899
- record3.homeId = nextHomeId;
43900
- if (!hasStateCredentials && !needsHomeId && raw.credentialsSource === "profile-home") {
43901
- nextMap[profileName] = raw;
43902
- continue;
43903
- }
43904
- const hydration = await this.hydrateProfileRecordWithStatus(record3);
43905
- if (!hasStateCredentials && !hydration.cacheable) {
43906
- nextMap[profileName] = this.toStateRecord(record3);
43968
+ record3.homeId = nextHomeId;
43969
+ if (!hasStateCredentials && !needsHomeId && raw.credentialsSource === "profile-home") {
43970
+ nextMap[profileName] = raw;
43971
+ continue;
43972
+ }
43973
+ const hydration = await this.hydrateProfileRecordWithStatus(record3);
43974
+ if (!hasStateCredentials && !hydration.cacheable) {
43975
+ nextMap[profileName] = this.toStateRecord(record3);
43976
+ changed = true;
43977
+ continue;
43978
+ }
43979
+ nextMap[profileName] = this.toStateRecord(
43980
+ await this.hydrateProfileRecordStrict(hydration.record)
43981
+ );
43907
43982
  changed = true;
43908
- continue;
43983
+ } catch (error) {
43984
+ nextMap[profileName] = raw;
43985
+ logWarn(`Deferred private auth migration for '${profileName}':`, error);
43909
43986
  }
43910
- nextMap[profileName] = this.toStateRecord(
43911
- await this.hydrateProfileRecordStrict(hydration.record)
43912
- );
43913
- changed = true;
43914
43987
  }
43915
43988
  if (changed) {
43916
43989
  this.profileRecordIndexCache = null;
@@ -44183,6 +44256,48 @@ var ProfileManager = class {
44183
44256
  }
44184
44257
  return result2;
44185
44258
  }
44259
+ async readProfileAuthFile(authPath) {
44260
+ const authLock = await acquireCodexAuthFileReadLock(authPath);
44261
+ if (!authLock) {
44262
+ throw profileMutationError(
44263
+ "PROFILE_HOME_BUSY",
44264
+ "Another CodexUse process is updating this profile auth. Retry after it finishes."
44265
+ );
44266
+ }
44267
+ let result2 = null;
44268
+ let readError = null;
44269
+ let needsWriteRecovery = false;
44270
+ try {
44271
+ const recoveryManifest = await this.readOptionalFile(
44272
+ this.profileAuthCasPaths(authPath).manifest
44273
+ );
44274
+ if (recoveryManifest !== null && authLock.mode === "read") {
44275
+ needsWriteRecovery = true;
44276
+ } else {
44277
+ if (recoveryManifest !== null) {
44278
+ await this.recoverProfileAuthCasIfNeeded(authPath);
44279
+ }
44280
+ result2 = await this.readOptionalFile(authPath);
44281
+ }
44282
+ } catch (error) {
44283
+ readError = error;
44284
+ }
44285
+ try {
44286
+ await authLock.release();
44287
+ } catch (releaseError) {
44288
+ throw errorWithCause("Profile auth read lock could not be released.", {
44289
+ readError,
44290
+ releaseError
44291
+ });
44292
+ }
44293
+ if (readError) {
44294
+ throw readError;
44295
+ }
44296
+ if (needsWriteRecovery) {
44297
+ return this.withProfileAuthFileLock(authPath, async (currentRaw) => currentRaw);
44298
+ }
44299
+ return result2;
44300
+ }
44186
44301
  async replaceProfileAuthIfCurrent(authPath, expectedRaw, replacementRaw) {
44187
44302
  await this.withProfileAuthFileLock(authPath, async (currentRaw) => {
44188
44303
  if (currentRaw !== expectedRaw) {
@@ -44518,13 +44633,14 @@ var ProfileManager = class {
44518
44633
  return (await this.hydrateProfileRecordWithStatus(record3)).record;
44519
44634
  }
44520
44635
  async hydrateProfileRecordWithStatus(record3) {
44636
+ const migrationError = this.blockedProfileMigrationErrors.get(record3.name);
44637
+ if (migrationError) {
44638
+ return { record: record3, cacheable: false };
44639
+ }
44521
44640
  const authPath = import_path.default.join(this.getProfileHomePath(record3.homeId), "auth.json");
44522
44641
  let authRaw;
44523
44642
  try {
44524
- const recovered = await this.withProfileAuthFileLock(
44525
- authPath,
44526
- async (currentRaw) => currentRaw
44527
- );
44643
+ const recovered = await this.readProfileAuthFile(authPath);
44528
44644
  if (recovered === null) {
44529
44645
  const missing = new Error("Profile auth is missing.");
44530
44646
  missing.code = "ENOENT";
@@ -44627,18 +44743,14 @@ var ProfileManager = class {
44627
44743
  };
44628
44744
  }
44629
44745
  async hydrateProfileRecordStrict(record3) {
44746
+ this.assertProfileHomeUsable(record3.name);
44630
44747
  const authPath = import_path.default.join(this.getProfileHomePath(record3.homeId), "auth.json");
44631
44748
  let activeAuth;
44632
44749
  try {
44633
- const raw = await this.withProfileAuthFileLock(
44634
- authPath,
44635
- async (currentRaw) => {
44636
- if (currentRaw === null) {
44637
- throw new Error("Profile auth is missing.");
44638
- }
44639
- return currentRaw;
44640
- }
44641
- );
44750
+ const raw = await this.readProfileAuthFile(authPath);
44751
+ if (raw === null) {
44752
+ throw new Error("Profile auth is missing.");
44753
+ }
44642
44754
  activeAuth = JSON.parse(raw);
44643
44755
  } catch (error) {
44644
44756
  throw errorWithCause(
@@ -44730,6 +44842,7 @@ var ProfileManager = class {
44730
44842
  return activeDescription.identityKey === targetIdentity && this.normalizeWorkspaceId(activeDescription.workspaceId) === targetWorkspaceId;
44731
44843
  }
44732
44844
  async writeActiveAuthForRecordLocked(record3, expectedSourceFingerprint) {
44845
+ this.assertProfileHomeUsable(record3.name);
44733
44846
  const releaseIntent = await this.acquireProfileHomeWriteIntent(record3);
44734
44847
  try {
44735
44848
  const latest = await this.readStoredProfileRecord(record3.name);
@@ -45026,6 +45139,7 @@ var ProfileManager = class {
45026
45139
  }
45027
45140
  async persistProfileRecord(name, data, metadata, options) {
45028
45141
  const resolvedName = this.normalizeProfileName(name);
45142
+ this.assertProfileHomeUsable(resolvedName);
45029
45143
  const initialMap = await this.readProfilesStateMap();
45030
45144
  const initialState = initialMap[resolvedName] ?? null;
45031
45145
  const storedExisting = initialState ? this.fromStateRecord(resolvedName, initialState) : null;
@@ -46651,6 +46765,7 @@ var ProfileManager = class {
46651
46765
  await this.initialize();
46652
46766
  const sourceName = this.normalizeProfileName(oldName);
46653
46767
  const targetName = this.normalizeProfileName(newName);
46768
+ this.assertProfileHomeUsable(sourceName);
46654
46769
  const initialProfiles = await this.readProfilesStateMap();
46655
46770
  const expectedSourceState = initialProfiles[sourceName] ?? null;
46656
46771
  const existing = expectedSourceState ? this.fromStateRecord(sourceName, expectedSourceState) : null;
@@ -51673,7 +51788,7 @@ async function ensureCliStorageReady() {
51673
51788
  }
51674
51789
 
51675
51790
  // src/app/main.ts
51676
- var VERSION = true ? "5.6.0" : "0.0.0";
51791
+ var VERSION = true ? "5.6.2" : "0.0.0";
51677
51792
  async function runCli() {
51678
51793
  const args2 = process.argv.slice(2);
51679
51794
  if (args2.length === 0) {