codex-multi-auth 2.1.4 → 2.1.6

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 (95) hide show
  1. package/README.md +50 -25
  2. package/config/README.md +2 -3
  3. package/dist/index.js +23 -23
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/auth/device-auth.d.ts.map +1 -1
  6. package/dist/lib/auth/device-auth.js +18 -1
  7. package/dist/lib/auth/device-auth.js.map +1 -1
  8. package/dist/lib/codex-manager/commands/account.js +6 -6
  9. package/dist/lib/codex-manager/commands/account.js.map +1 -1
  10. package/dist/lib/codex-manager/commands/best.js +1 -1
  11. package/dist/lib/codex-manager/commands/best.js.map +1 -1
  12. package/dist/lib/codex-manager/commands/bridge.js +5 -5
  13. package/dist/lib/codex-manager/commands/bridge.js.map +1 -1
  14. package/dist/lib/codex-manager/commands/budget.js +3 -3
  15. package/dist/lib/codex-manager/commands/budget.js.map +1 -1
  16. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
  17. package/dist/lib/codex-manager/commands/forecast.js +18 -4
  18. package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
  19. package/dist/lib/codex-manager/commands/integrations.js +1 -1
  20. package/dist/lib/codex-manager/commands/integrations.js.map +1 -1
  21. package/dist/lib/codex-manager/commands/models.js +1 -1
  22. package/dist/lib/codex-manager/commands/models.js.map +1 -1
  23. package/dist/lib/codex-manager/commands/monitor.js +1 -1
  24. package/dist/lib/codex-manager/commands/monitor.js.map +1 -1
  25. package/dist/lib/codex-manager/commands/report.js +1 -1
  26. package/dist/lib/codex-manager/commands/report.js.map +1 -1
  27. package/dist/lib/codex-manager/commands/rotation.d.ts.map +1 -1
  28. package/dist/lib/codex-manager/commands/rotation.js +23 -14
  29. package/dist/lib/codex-manager/commands/rotation.js.map +1 -1
  30. package/dist/lib/codex-manager/commands/switch.js +1 -1
  31. package/dist/lib/codex-manager/commands/switch.js.map +1 -1
  32. package/dist/lib/codex-manager/commands/uninstall.d.ts +36 -0
  33. package/dist/lib/codex-manager/commands/uninstall.d.ts.map +1 -0
  34. package/dist/lib/codex-manager/commands/uninstall.js +284 -0
  35. package/dist/lib/codex-manager/commands/uninstall.js.map +1 -0
  36. package/dist/lib/codex-manager/commands/usage.js +2 -2
  37. package/dist/lib/codex-manager/commands/usage.js.map +1 -1
  38. package/dist/lib/codex-manager/commands/verify.js +4 -4
  39. package/dist/lib/codex-manager/commands/verify.js.map +1 -1
  40. package/dist/lib/codex-manager/commands/why-selected.js +3 -3
  41. package/dist/lib/codex-manager/commands/why-selected.js.map +1 -1
  42. package/dist/lib/codex-manager/forecast-report-commands.js +3 -3
  43. package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
  44. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  45. package/dist/lib/codex-manager/help.js +27 -26
  46. package/dist/lib/codex-manager/help.js.map +1 -1
  47. package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
  48. package/dist/lib/codex-manager/repair-commands.js +23 -11
  49. package/dist/lib/codex-manager/repair-commands.js.map +1 -1
  50. package/dist/lib/codex-manager.d.ts.map +1 -1
  51. package/dist/lib/codex-manager.js +47 -6
  52. package/dist/lib/codex-manager.js.map +1 -1
  53. package/dist/lib/forecast.js +1 -1
  54. package/dist/lib/forecast.js.map +1 -1
  55. package/dist/lib/request/fetch-helpers.d.ts +1 -1
  56. package/dist/lib/request/fetch-helpers.js +8 -8
  57. package/dist/lib/request/fetch-helpers.js.map +1 -1
  58. package/dist/lib/request/stream-failover.d.ts.map +1 -1
  59. package/dist/lib/request/stream-failover.js +12 -1
  60. package/dist/lib/request/stream-failover.js.map +1 -1
  61. package/dist/lib/rotation.d.ts +1 -1
  62. package/dist/lib/rotation.js +1 -1
  63. package/dist/lib/runtime/app-bind.d.ts.map +1 -1
  64. package/dist/lib/runtime/app-bind.js +28 -3
  65. package/dist/lib/runtime/app-bind.js.map +1 -1
  66. package/dist/lib/runtime/config-toml.d.ts.map +1 -1
  67. package/dist/lib/runtime/config-toml.js +29 -3
  68. package/dist/lib/runtime/config-toml.js.map +1 -1
  69. package/dist/lib/runtime/hydrate-emails.d.ts.map +1 -1
  70. package/dist/lib/runtime/hydrate-emails.js +6 -1
  71. package/dist/lib/runtime/hydrate-emails.js.map +1 -1
  72. package/dist/lib/runtime/runtime-current-account.d.ts.map +1 -1
  73. package/dist/lib/runtime/runtime-current-account.js +10 -1
  74. package/dist/lib/runtime/runtime-current-account.js.map +1 -1
  75. package/dist/lib/runtime/storage-scope.js +2 -2
  76. package/dist/lib/runtime/storage-scope.js.map +1 -1
  77. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  78. package/dist/lib/runtime-rotation-proxy.js +44 -9
  79. package/dist/lib/runtime-rotation-proxy.js.map +1 -1
  80. package/dist/lib/storage/transactions.d.ts +2 -1
  81. package/dist/lib/storage/transactions.d.ts.map +1 -1
  82. package/dist/lib/storage/transactions.js +9 -3
  83. package/dist/lib/storage/transactions.js.map +1 -1
  84. package/dist/lib/update-notice.d.ts +12 -0
  85. package/dist/lib/update-notice.d.ts.map +1 -0
  86. package/dist/lib/update-notice.js +315 -0
  87. package/dist/lib/update-notice.js.map +1 -0
  88. package/package.json +26 -17
  89. package/scripts/codex.js +269 -65
  90. package/scripts/install-codex-auth-utils.js +48 -0
  91. package/scripts/preuninstall.js +254 -0
  92. package/dist/lib/auto-update-checker.d.ts +0 -32
  93. package/dist/lib/auto-update-checker.d.ts.map +0 -1
  94. package/dist/lib/auto-update-checker.js +0 -730
  95. package/dist/lib/auto-update-checker.js.map +0 -1
package/scripts/codex.js CHANGED
@@ -41,6 +41,23 @@ const RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES = new Set([
41
41
  "auth.json",
42
42
  "accounts.json",
43
43
  ]);
44
+ const RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS = new Set(["multi-auth"]);
45
+ const RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS = new Set([
46
+ ".sandbox",
47
+ ".sandbox-bin",
48
+ ".sandbox-secrets",
49
+ ".tmp",
50
+ "ambient-suggestions",
51
+ "archived_sessions",
52
+ "backups",
53
+ "cache",
54
+ "generated_images",
55
+ "log",
56
+ "sqlite",
57
+ "tmp",
58
+ "understand-anything",
59
+ "vendor_imports",
60
+ ]);
44
61
  const SHADOW_HOME_STATE_FILE_SET = new Set(SHADOW_HOME_STATE_FILES);
45
62
  const SHADOW_HOME_CONFIG_FILE = "config.toml";
46
63
  const SHADOW_HOME_SYNC_LOCK_DIR = ".codex-multi-auth-shadow-sync.lock";
@@ -63,8 +80,8 @@ const DEFAULT_APP_RUNTIME_HELPER_DETACH_GRACE_MS = 5_000;
63
80
  const APP_RUNTIME_HELPER_LAUNCH_TIMEOUT_MS = 15_000;
64
81
  const APP_SERVER_SHIM_DIR_NAME = "app-server-shims";
65
82
  const APP_SERVER_SHIM_HELPER_PREFIX = "helper-";
66
- const DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS = 3_000;
67
- const STARTUP_AUTO_UPDATE_TIMED_OUT = Symbol("startup-auto-update-timed-out");
83
+ const DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS = 3_000;
84
+ const STARTUP_UPDATE_NOTICE_TIMED_OUT = Symbol("startup-update-notice-timed-out");
68
85
  let shadowHomeCleanupBusyFailuresRemaining = Number.parseInt(
69
86
  process.env.CODEX_MULTI_AUTH_TEST_SHADOW_CLEANUP_BUSY_FAILURES ?? "0",
70
87
  10,
@@ -90,6 +107,8 @@ const shadowHomeCleanupRetryMarkerDir =
90
107
  let warnedInvalidRuntimeRotationProxyEnv = false;
91
108
  let warnedPendingAccountReadIdOverflow = false;
92
109
  let warnedShadowHomeSqliteLinkFailure = false;
110
+ const warnedShadowHomeLinkOnlyDirectoryFailures = new Set();
111
+ const warnedShadowHomeSqliteSidecarPlaceholderFailures = new Set();
93
112
 
94
113
  async function loadRuntimeConstants() {
95
114
  const fallback = {
@@ -261,31 +280,27 @@ function readBooleanEnvFlag(name) {
261
280
  return null;
262
281
  }
263
282
 
264
- function isStartupAutoUpdateDebugEnabled() {
283
+ function isStartupUpdateNoticeDebugEnabled() {
265
284
  return readBooleanEnvFlag("CODEX_MULTI_AUTH_DEBUG") === true;
266
285
  }
267
286
 
268
- function shouldLogStartupAutoUpdateProgress() {
269
- return process.stderr.isTTY === true || isStartupAutoUpdateDebugEnabled();
287
+ function shouldLogStartupUpdateNotice() {
288
+ return process.stderr.isTTY === true || isStartupUpdateNoticeDebugEnabled();
270
289
  }
271
290
 
272
- function readStartupAutoUpdateBudgetMs() {
291
+ function readStartupUpdateNoticeBudgetMs() {
273
292
  const raw =
274
- process.env.CODEX_MULTI_AUTH_AUTO_UPDATE_STARTUP_BUDGET_MS ??
275
- process.env.CODEX_MULTI_AUTH_TEST_STARTUP_AUTO_UPDATE_BUDGET_MS;
276
- if (!raw) return DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
293
+ process.env.CODEX_MULTI_AUTH_UPDATE_NOTICE_STARTUP_BUDGET_MS ??
294
+ process.env.CODEX_MULTI_AUTH_TEST_STARTUP_UPDATE_NOTICE_BUDGET_MS;
295
+ if (!raw) return DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS;
277
296
  const parsed = Number.parseInt(raw, 10);
278
297
  return Number.isFinite(parsed) && parsed > 0
279
298
  ? parsed
280
- : DEFAULT_STARTUP_AUTO_UPDATE_BUDGET_MS;
299
+ : DEFAULT_STARTUP_UPDATE_NOTICE_BUDGET_MS;
281
300
  }
282
301
 
283
- function resolveStartupAutoUpdateTimeouts(budgetMs) {
284
- const fetchTimeoutMs = Math.max(1, Math.floor(budgetMs * 0.4));
285
- return {
286
- fetchTimeoutMs,
287
- updateTimeoutMs: Math.max(1, budgetMs - fetchTimeoutMs),
288
- };
302
+ function resolveStartupUpdateNoticeTimeoutMs(budgetMs) {
303
+ return Math.max(1, Math.floor(budgetMs * 0.8));
289
304
  }
290
305
 
291
306
  function isModuleNotFoundError(error) {
@@ -297,7 +312,7 @@ function isModuleNotFoundError(error) {
297
312
  );
298
313
  }
299
314
 
300
- function shouldRunStartupAutoUpdate(rawArgs, normalizedArgs) {
315
+ function shouldRunStartupUpdateNotice(rawArgs, normalizedArgs) {
301
316
  if ((process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1") {
302
317
  return false;
303
318
  }
@@ -310,7 +325,7 @@ function shouldRunStartupAutoUpdate(rawArgs, normalizedArgs) {
310
325
  return true;
311
326
  }
312
327
 
313
- async function withStartupAutoUpdateBudget(promise, budgetMs) {
328
+ async function withStartupUpdateNoticeBudget(promise, budgetMs) {
314
329
  let timeout = null;
315
330
  try {
316
331
  return await Promise.race([
@@ -319,7 +334,7 @@ async function withStartupAutoUpdateBudget(promise, budgetMs) {
319
334
  timeout = setTimeout(
320
335
  () => {
321
336
  timeout?.unref?.();
322
- resolve(STARTUP_AUTO_UPDATE_TIMED_OUT);
337
+ resolve(STARTUP_UPDATE_NOTICE_TIMED_OUT);
323
338
  },
324
339
  budgetMs,
325
340
  );
@@ -330,51 +345,42 @@ async function withStartupAutoUpdateBudget(promise, budgetMs) {
330
345
  }
331
346
  }
332
347
 
333
- function logStartupAutoUpdateDebug(message) {
334
- if (isStartupAutoUpdateDebugEnabled()) {
348
+ function logStartupUpdateNoticeDebug(message) {
349
+ if (isStartupUpdateNoticeDebugEnabled()) {
335
350
  console.error(`codex-multi-auth: ${message}`);
336
351
  }
337
352
  }
338
353
 
339
- async function autoUpdatePackageIfEnabled(rawArgs, normalizedArgs) {
340
- if (!shouldRunStartupAutoUpdate(rawArgs, normalizedArgs)) return;
341
- const budgetMs = readStartupAutoUpdateBudgetMs();
342
- const { fetchTimeoutMs, updateTimeoutMs } =
343
- resolveStartupAutoUpdateTimeouts(budgetMs);
354
+ async function showUpdateNoticeIfAvailable(rawArgs, normalizedArgs) {
355
+ if (!shouldRunStartupUpdateNotice(rawArgs, normalizedArgs)) return;
356
+ const budgetMs = readStartupUpdateNoticeBudgetMs();
357
+ const fetchTimeoutMs = resolveStartupUpdateNoticeTimeoutMs(budgetMs);
344
358
  try {
345
- const mod = await import("../dist/lib/auto-update-checker.js");
346
- if (typeof mod.autoUpdateIfAvailable !== "function") {
359
+ const mod = await import("../dist/lib/update-notice.js");
360
+ if (typeof mod.checkForUpdates !== "function") {
347
361
  return;
348
362
  }
349
- const updatePromise = mod.autoUpdateIfAvailable({
350
- fetchTimeoutMs,
351
- timeoutMs: updateTimeoutMs,
352
- onUpdateStart: (update) => {
353
- if (!update?.latestVersion) return;
354
- if (!shouldLogStartupAutoUpdateProgress()) return;
355
- console.error(
356
- `codex-multi-auth: auto-update found ${update.latestVersion}; starting npm update -g codex-multi-auth. Startup will continue if it exceeds ${budgetMs}ms.`,
357
- );
358
- },
359
- });
360
- const result = await withStartupAutoUpdateBudget(updatePromise, budgetMs);
361
- if (result === STARTUP_AUTO_UPDATE_TIMED_OUT) {
362
- logStartupAutoUpdateDebug(
363
- `auto-update skipped: startup budget exceeded after ${budgetMs}ms`,
363
+ const checkPromise = mod.checkForUpdates(false, fetchTimeoutMs);
364
+ const result = await withStartupUpdateNoticeBudget(checkPromise, budgetMs);
365
+ if (result === STARTUP_UPDATE_NOTICE_TIMED_OUT) {
366
+ logStartupUpdateNoticeDebug(
367
+ `update notice skipped: startup budget exceeded after ${budgetMs}ms`,
364
368
  );
365
369
  return;
366
370
  }
367
- if (result?.updated && result.latestVersion) {
368
- if (shouldLogStartupAutoUpdateProgress()) {
371
+ if (result?.hasUpdate && result.latestVersion && shouldLogStartupUpdateNotice()) {
372
+ if (typeof mod.formatManualUpdateNotice === "function") {
373
+ console.error(mod.formatManualUpdateNotice(result));
374
+ } else {
369
375
  console.error(
370
- `codex-multi-auth: auto-updated to ${result.latestVersion}. New sessions will use the latest package.`,
376
+ `codex-multi-auth update available: v${result.latestVersion}; current: v${result.currentVersion}; run: ${result.updateCommand}`,
371
377
  );
372
378
  }
373
379
  }
374
380
  } catch (error) {
375
381
  if (isModuleNotFoundError(error)) return;
376
- logStartupAutoUpdateDebug(
377
- `auto-update skipped: ${error instanceof Error ? error.message : String(error)}`,
382
+ logStartupUpdateNoticeDebug(
383
+ `update notice skipped: ${error instanceof Error ? error.message : String(error)}`,
378
384
  );
379
385
  }
380
386
  }
@@ -1879,6 +1885,41 @@ function mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) {
1879
1885
  return "copied";
1880
1886
  }
1881
1887
 
1888
+ function linkDirectoryIntoShadowHome(sourcePath, destinationPath) {
1889
+ try {
1890
+ if ((process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_DIR_COPY ?? "").trim() === "1") {
1891
+ throw new Error("simulated directory link failure");
1892
+ }
1893
+ symlinkSync(
1894
+ sourcePath,
1895
+ destinationPath,
1896
+ process.platform === "win32" ? "junction" : "dir",
1897
+ );
1898
+ return true;
1899
+ } catch {
1900
+ return false;
1901
+ }
1902
+ }
1903
+
1904
+ function warnSkippedLinkOnlyShadowHomeDirectory(name) {
1905
+ if (warnedShadowHomeLinkOnlyDirectoryFailures.has(name)) {
1906
+ return;
1907
+ }
1908
+ warnedShadowHomeLinkOnlyDirectoryFailures.add(name);
1909
+ console.error(
1910
+ `codex-multi-auth: skipped optional shadow-home directory ${name} because linking failed; refusing to copy generated runtime data.`,
1911
+ );
1912
+ }
1913
+
1914
+ function shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback() {
1915
+ const normalized = (
1916
+ process.env.CODEX_MULTI_AUTH_RUNTIME_SHADOW_COPY_GENERATED_DIRS ?? ""
1917
+ )
1918
+ .trim()
1919
+ .toLowerCase();
1920
+ return normalized === "1" || normalized === "true" || normalized === "yes";
1921
+ }
1922
+
1882
1923
  function linkFileIntoShadowHome(sourcePath, destinationPath) {
1883
1924
  try {
1884
1925
  symlinkSync(sourcePath, destinationPath, "file");
@@ -1903,25 +1944,148 @@ function mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile) {
1903
1944
  tightenFile(destinationPath);
1904
1945
  }
1905
1946
 
1947
+ function isSqliteMainFile(name) {
1948
+ return /\.sqlite$/i.test(name);
1949
+ }
1950
+
1951
+ function isSqliteSidecarFile(name) {
1952
+ return /\.sqlite-(?:shm|wal)$/i.test(name);
1953
+ }
1954
+
1955
+ function normalizeRuntimeShadowHomeEntryName(name) {
1956
+ return process.platform === "win32" || process.platform === "darwin"
1957
+ ? name.toLowerCase()
1958
+ : name;
1959
+ }
1960
+
1961
+ function isCodexRuntimeLocalSqliteFile(name) {
1962
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1963
+ return /^(?:state|logs)_\d+\.sqlite(?:-(?:shm|wal))?$/.test(normalizedName);
1964
+ }
1965
+
1966
+ function isCodexRuntimeTransientStateFile(name) {
1967
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1968
+ return (
1969
+ /^(?:auth|accounts)\.json\.\d+\.[a-z0-9]+\.tmp$/.test(
1970
+ normalizedName,
1971
+ ) ||
1972
+ /^\.codex-global-state\.json\.tmp-[a-z0-9-]+$/.test(normalizedName)
1973
+ );
1974
+ }
1975
+
1976
+ function isRuntimeRotationShadowHomeOmittedEntry(name) {
1977
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1978
+ return (
1979
+ RUNTIME_ROTATION_SHADOW_HOME_OMIT_ROOT_DIRS.has(normalizedName) ||
1980
+ isCodexRuntimeLocalSqliteFile(name) ||
1981
+ isCodexRuntimeTransientStateFile(name)
1982
+ );
1983
+ }
1984
+
1985
+ function isRuntimeRotationShadowHomeLinkOnlyDirectory(name) {
1986
+ const normalizedName = normalizeRuntimeShadowHomeEntryName(name);
1987
+ return RUNTIME_ROTATION_SHADOW_HOME_LINK_ONLY_ROOT_DIRS.has(normalizedName);
1988
+ }
1989
+
1906
1990
  function shouldMaterializeFileIntoShadowHome(name) {
1907
- return /\.sqlite(?:-(?:shm|wal))?$/i.test(name);
1991
+ return isSqliteMainFile(name) || isSqliteSidecarFile(name);
1908
1992
  }
1909
1993
 
1910
- function materializeFileIntoShadowHome(sourcePath, destinationPath) {
1994
+ function warnSkippedSqliteShadowHomeMaterialization() {
1995
+ if (!warnedShadowHomeSqliteLinkFailure) {
1996
+ warnedShadowHomeSqliteLinkFailure = true;
1997
+ console.error(
1998
+ "codex-multi-auth: skipped SQLite shadow-home materialization because linking failed; refusing to copy active SQLite state.",
1999
+ );
2000
+ }
2001
+ }
2002
+
2003
+ function warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationPath) {
2004
+ if (!warnedShadowHomeSqliteSidecarPlaceholderFailures.has(destinationPath)) {
2005
+ warnedShadowHomeSqliteSidecarPlaceholderFailures.add(destinationPath);
2006
+ console.error(
2007
+ `codex-multi-auth: skipped SQLite shadow-home sidecar placeholder for ${destinationPath} because linking failed: ${error instanceof Error ? error.message : String(error)}`,
2008
+ );
2009
+ }
2010
+ }
2011
+
2012
+ function materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath) {
1911
2013
  try {
1912
- linkSync(sourcePath, destinationPath);
2014
+ if (
2015
+ (process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SIDECAR_PLACEHOLDER_FAILURE ??
2016
+ ""
2017
+ ).trim() === "1"
2018
+ ) {
2019
+ const error = new Error("simulated SQLite sidecar placeholder failure");
2020
+ error.code = "EPERM";
2021
+ throw error;
2022
+ }
2023
+ symlinkSync(sourceSidecarPath, destinationSidecarPath, "file");
1913
2024
  return true;
1914
- } catch {
1915
- if (!warnedShadowHomeSqliteLinkFailure) {
1916
- warnedShadowHomeSqliteLinkFailure = true;
1917
- console.error(
1918
- "codex-multi-auth: skipped SQLite shadow-home materialization because hard-linking failed; refusing to copy active SQLite state.",
1919
- );
2025
+ } catch (error) {
2026
+ if (error?.code === "EEXIST") {
2027
+ return true;
2028
+ }
2029
+ warnSkippedSqliteShadowHomeSidecarPlaceholder(error, destinationSidecarPath);
2030
+ return false;
2031
+ }
2032
+ }
2033
+
2034
+ function materializeFileIntoShadowHome(sourcePath, destinationPath) {
2035
+ try {
2036
+ if (
2037
+ (process.env.CODEX_MULTI_AUTH_TEST_FORCE_SHADOW_SQLITE_SIDECAR_LINK_FAILURE ??
2038
+ ""
2039
+ ).trim() === "1" &&
2040
+ isSqliteSidecarFile(basename(sourcePath))
2041
+ ) {
2042
+ throw new Error("simulated SQLite sidecar link failure");
2043
+ }
2044
+ if (linkFileIntoShadowHome(sourcePath, destinationPath)) {
2045
+ return true;
1920
2046
  }
2047
+ warnSkippedSqliteShadowHomeMaterialization();
2048
+ } catch {
2049
+ warnSkippedSqliteShadowHomeMaterialization();
1921
2050
  }
1922
2051
  return false;
1923
2052
  }
1924
2053
 
2054
+ function materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath) {
2055
+ for (const suffix of ["-wal", "-shm"]) {
2056
+ const sourceSidecarPath = `${sourcePath}${suffix}`;
2057
+ const destinationSidecarPath = `${destinationPath}${suffix}`;
2058
+ if (existsSync(destinationSidecarPath)) {
2059
+ continue;
2060
+ }
2061
+ if (existsSync(sourceSidecarPath)) {
2062
+ if (!materializeFileIntoShadowHome(sourceSidecarPath, destinationSidecarPath)) {
2063
+ if (!materializeSqliteSidecarPlaceholder(
2064
+ sourceSidecarPath,
2065
+ destinationSidecarPath,
2066
+ )) {
2067
+ return false;
2068
+ }
2069
+ }
2070
+ continue;
2071
+ }
2072
+ if (!materializeSqliteSidecarPlaceholder(sourceSidecarPath, destinationSidecarPath)) {
2073
+ return false;
2074
+ }
2075
+ }
2076
+ return true;
2077
+ }
2078
+
2079
+ function removeSqliteShadowHomeMaterialization(destinationPath) {
2080
+ for (const path of [destinationPath, `${destinationPath}-wal`, `${destinationPath}-shm`]) {
2081
+ try {
2082
+ rmSync(path, { force: true });
2083
+ } catch {
2084
+ // Best-effort cleanup; keep removing the remaining SQLite siblings.
2085
+ }
2086
+ }
2087
+ }
2088
+
1925
2089
  function collectShadowHomeSyncFileNames(shadowCodexHome, syncFileNames) {
1926
2090
  try {
1927
2091
  for (const entry of readdirSync(shadowCodexHome, { withFileTypes: true })) {
@@ -2031,9 +2195,14 @@ function syncAdditionalShadowHomeFiles(
2031
2195
  originalFileStates,
2032
2196
  tightenFile,
2033
2197
  skipSyncBackNames = new Set(),
2198
+ skipSyncBackPredicate = () => false,
2034
2199
  ) {
2035
2200
  for (const name of names) {
2036
- if (SHADOW_HOME_STATE_FILE_SET.has(name) || skipSyncBackNames.has(name)) {
2201
+ if (
2202
+ SHADOW_HOME_STATE_FILE_SET.has(name) ||
2203
+ skipSyncBackNames.has(name) ||
2204
+ skipSyncBackPredicate(name)
2205
+ ) {
2037
2206
  continue;
2038
2207
  }
2039
2208
  const shadowPath = join(shadowCodexHome, name);
@@ -2069,6 +2238,10 @@ function createShadowHomeMirror(
2069
2238
  ) {
2070
2239
  const syncFileNames = new Set(SHADOW_HOME_STATE_FILES);
2071
2240
  const skipSyncBackNames = new Set(options.skipSyncBackNames ?? []);
2241
+ const skipMirrorPredicate = options.skipMirrorPredicate ?? (() => false);
2242
+ const skipSyncBackPredicate = options.skipSyncBackPredicate ?? (() => false);
2243
+ const linkOnlyDirectoryPredicate =
2244
+ options.linkOnlyDirectoryPredicate ?? (() => false);
2072
2245
  const originalFileStates = new Map();
2073
2246
  const copiedDirectoryNames = new Set();
2074
2247
  const rememberSyncFile = (name) => {
@@ -2090,7 +2263,8 @@ function createShadowHomeMirror(
2090
2263
  if (
2091
2264
  name === SHADOW_HOME_CONFIG_FILE ||
2092
2265
  name === SHADOW_HOME_SYNC_STATE_FILE ||
2093
- name === SHADOW_HOME_SYNC_LOCK_DIR
2266
+ name === SHADOW_HOME_SYNC_LOCK_DIR ||
2267
+ skipMirrorPredicate(name)
2094
2268
  ) {
2095
2269
  continue;
2096
2270
  }
@@ -2114,6 +2288,15 @@ function createShadowHomeMirror(
2114
2288
  throw new Error(`Expected ${name} to be a file`);
2115
2289
  }
2116
2290
  if (directoryLike) {
2291
+ if (
2292
+ linkOnlyDirectoryPredicate(name) &&
2293
+ !shouldCopyRuntimeGeneratedShadowHomeDirectoryFallback()
2294
+ ) {
2295
+ if (!linkDirectoryIntoShadowHome(sourcePath, destinationPath)) {
2296
+ warnSkippedLinkOnlyShadowHomeDirectory(name);
2297
+ }
2298
+ continue;
2299
+ }
2117
2300
  if (mirrorDirectoryIntoShadowHome(sourcePath, destinationPath) === "copied") {
2118
2301
  copiedDirectoryNames.add(name);
2119
2302
  }
@@ -2125,7 +2308,14 @@ function createShadowHomeMirror(
2125
2308
  copyFileSync(sourcePath, destinationPath);
2126
2309
  tightenFile(destinationPath);
2127
2310
  } else if (shouldMaterializeFile) {
2128
- materializeFileIntoShadowHome(sourcePath, destinationPath);
2311
+ if (isSqliteSidecarFile(name)) {
2312
+ continue;
2313
+ }
2314
+ if (materializeFileIntoShadowHome(sourcePath, destinationPath) && isSqliteMainFile(name)) {
2315
+ if (!materializeSqliteSidecarsIntoShadowHome(sourcePath, destinationPath)) {
2316
+ removeSqliteShadowHomeMaterialization(destinationPath);
2317
+ }
2318
+ }
2129
2319
  } else {
2130
2320
  mirrorFileIntoShadowHome(sourcePath, destinationPath, tightenFile);
2131
2321
  }
@@ -2164,6 +2354,7 @@ function createShadowHomeMirror(
2164
2354
  originalFileStates,
2165
2355
  tightenFile,
2166
2356
  skipSyncBackNames,
2357
+ skipSyncBackPredicate,
2167
2358
  );
2168
2359
  } catch {
2169
2360
  // Best-effort only; runtime auth refreshes should not fail cleanup.
@@ -2220,6 +2411,10 @@ function resolveOriginalMultiAuthDir(env) {
2220
2411
  return undefined;
2221
2412
  }
2222
2413
 
2414
+ function resolveRuntimeRotationOriginalMultiAuthDir(originalCodexHome, env) {
2415
+ return resolveOriginalMultiAuthDir(env) ?? join(originalCodexHome, "multi-auth");
2416
+ }
2417
+
2223
2418
  function parseRuntimeRotationProxyEnv(value) {
2224
2419
  if (value === undefined) return undefined;
2225
2420
  const normalized = value.trim().toLowerCase();
@@ -2329,7 +2524,10 @@ function createRuntimeRotationProxyCodexHome(
2329
2524
  shadowCodexHome,
2330
2525
  tightenShadowHomePermissions,
2331
2526
  {
2527
+ skipMirrorPredicate: isRuntimeRotationShadowHomeOmittedEntry,
2332
2528
  skipSyncBackNames: RUNTIME_ROTATION_SHADOW_HOME_OMIT_STATE_FILES,
2529
+ skipSyncBackPredicate: isRuntimeRotationShadowHomeOmittedEntry,
2530
+ linkOnlyDirectoryPredicate: isRuntimeRotationShadowHomeLinkOnlyDirectory,
2333
2531
  },
2334
2532
  );
2335
2533
  omitRuntimeRotationShadowHomeStateFiles(shadowCodexHome);
@@ -2354,11 +2552,11 @@ function createRuntimeRotationProxyCodexHome(
2354
2552
  ...baseEnv,
2355
2553
  CODEX_HOME: shadowCodexHome,
2356
2554
  OPENAI_API_KEY: clientApiKey,
2555
+ CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
2556
+ originalCodexHome,
2557
+ baseEnv,
2558
+ ),
2357
2559
  };
2358
- const originalMultiAuthDir = resolveOriginalMultiAuthDir(baseEnv);
2359
- if (originalMultiAuthDir) {
2360
- forwardedEnv.CODEX_MULTI_AUTH_DIR = originalMultiAuthDir;
2361
- }
2362
2560
 
2363
2561
  return {
2364
2562
  env: forwardedEnv,
@@ -2813,6 +3011,10 @@ function startRuntimeRotationAppHelper(baseContext) {
2813
3011
  {
2814
3012
  env: {
2815
3013
  ...baseContext.env,
3014
+ CODEX_MULTI_AUTH_DIR: resolveRuntimeRotationOriginalMultiAuthDir(
3015
+ realCodexHome,
3016
+ baseContext.env,
3017
+ ),
2816
3018
  [APP_RUNTIME_HELPER_OWNER_PID_ENV]: String(process.pid),
2817
3019
  [APP_RUNTIME_HELPER_REAL_CODEX_HOME_ENV]: realCodexHome,
2818
3020
  },
@@ -3541,6 +3743,8 @@ function repairCodexSessionIndex(codexHome) {
3541
3743
 
3542
3744
  const additions = [];
3543
3745
  for (const rolloutPath of collectRolloutFiles(sessionsDir)) {
3746
+ const idFromName = extractRolloutIdFromFilename(basename(rolloutPath));
3747
+ if (idFromName && seen.has(idFromName)) continue;
3544
3748
  const entry = parseRolloutIndexEntry(rolloutPath);
3545
3749
  if (!entry || seen.has(entry.id)) continue;
3546
3750
  seen.add(entry.id);
@@ -3871,7 +4075,7 @@ async function main() {
3871
4075
  }
3872
4076
 
3873
4077
  const normalizedArgs = normalizeAuthAlias(rawArgs);
3874
- await autoUpdatePackageIfEnabled(rawArgs, normalizedArgs);
4078
+ await showUpdateNoticeIfAvailable(rawArgs, normalizedArgs);
3875
4079
  ensureWindowsShellShimGuards();
3876
4080
 
3877
4081
  const bypass = (process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1";
@@ -3,8 +3,47 @@
3
3
  import { rename as fsRename } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
6
+ import process from "node:process";
6
7
 
7
8
  const PLUGIN_NAME = "codex-multi-auth";
9
+
10
+ const TRUE_VALUES = new Set(["1", "true", "yes"]);
11
+ const FALSE_VALUES = new Set(["0", "false", "no"]);
12
+ const CI_ENV_KEYS = [
13
+ "CI",
14
+ "GITHUB_ACTIONS",
15
+ "GITLAB_CI",
16
+ "CIRCLECI",
17
+ "BUILDKITE",
18
+ "TF_BUILD",
19
+ "TEAMCITY_VERSION",
20
+ "JENKINS_URL",
21
+ "TRAVIS",
22
+ "APPVEYOR",
23
+ "BITBUCKET_BUILD_NUMBER",
24
+ ];
25
+
26
+ /** @param {string | undefined} value */
27
+ function readOptionalBooleanValue(value) {
28
+ if (value === undefined || value.trim().length === 0) return null;
29
+ const normalized = value.trim().toLowerCase();
30
+ if (TRUE_VALUES.has(normalized)) return true;
31
+ if (FALSE_VALUES.has(normalized)) return false;
32
+ return null;
33
+ }
34
+
35
+ /** @param {NodeJS.ProcessEnv} env @param {string} key */
36
+ function isEnabledEnvFlag(env, key) {
37
+ const value = env[key];
38
+ if (value === undefined || value.trim().length === 0) return false;
39
+ return readOptionalBooleanValue(value) !== false;
40
+ }
41
+
42
+ /** @param {NodeJS.ProcessEnv} [env] */
43
+ export function isCiEnvironment(env = process.env) {
44
+ if (readOptionalBooleanValue(env.npm_config_ignore_scripts) === true) return true;
45
+ return CI_ENV_KEYS.some((key) => isEnabledEnvFlag(env, key));
46
+ }
8
47
  export const FILE_RETRY_CODES = new Set([
9
48
  "EBUSY",
10
49
  "EPERM",
@@ -48,6 +87,15 @@ export function resolveInstallPaths(
48
87
  };
49
88
  }
50
89
 
90
+ /** @param {unknown} list */
91
+ export function removePluginFromList(list) {
92
+ const entries = Array.isArray(list) ? list.filter(Boolean) : [];
93
+ return entries.filter((entry) => {
94
+ if (typeof entry !== "string") return true;
95
+ return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
96
+ });
97
+ }
98
+
51
99
  /** @param {unknown} list */
52
100
  export function normalizePluginList(list) {
53
101
  const entries = Array.isArray(list) ? list.filter(Boolean) : [];