codex-multi-auth 0.1.3 → 0.1.4

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 (66) hide show
  1. package/README.md +8 -11
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +51 -13
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/auth/auth.d.ts +6 -2
  6. package/dist/lib/auth/auth.d.ts.map +1 -1
  7. package/dist/lib/auth/auth.js +34 -11
  8. package/dist/lib/auth/auth.js.map +1 -1
  9. package/dist/lib/auth/server.d.ts +1 -1
  10. package/dist/lib/auth/server.js +3 -3
  11. package/dist/lib/codex-cli/state.d.ts +9 -0
  12. package/dist/lib/codex-cli/state.d.ts.map +1 -1
  13. package/dist/lib/codex-cli/state.js +14 -0
  14. package/dist/lib/codex-cli/state.js.map +1 -1
  15. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  16. package/dist/lib/codex-cli/writer.js +106 -14
  17. package/dist/lib/codex-cli/writer.js.map +1 -1
  18. package/dist/lib/codex-manager/settings-hub.d.ts +16 -1
  19. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  20. package/dist/lib/codex-manager/settings-hub.js +28 -2
  21. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  22. package/dist/lib/codex-manager.d.ts +1 -0
  23. package/dist/lib/codex-manager.d.ts.map +1 -1
  24. package/dist/lib/codex-manager.js +319 -16
  25. package/dist/lib/codex-manager.js.map +1 -1
  26. package/dist/lib/oauth-success.html +192 -260
  27. package/dist/lib/parallel-probe.d.ts +7 -0
  28. package/dist/lib/parallel-probe.d.ts.map +1 -1
  29. package/dist/lib/parallel-probe.js +37 -9
  30. package/dist/lib/parallel-probe.js.map +1 -1
  31. package/dist/lib/refresh-queue.d.ts +4 -5
  32. package/dist/lib/refresh-queue.d.ts.map +1 -1
  33. package/dist/lib/refresh-queue.js +99 -21
  34. package/dist/lib/refresh-queue.js.map +1 -1
  35. package/dist/lib/request/fetch-helpers.d.ts +12 -4
  36. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  37. package/dist/lib/request/fetch-helpers.js +53 -25
  38. package/dist/lib/request/fetch-helpers.js.map +1 -1
  39. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
  40. package/dist/lib/request/helpers/tool-utils.js +36 -6
  41. package/dist/lib/request/helpers/tool-utils.js.map +1 -1
  42. package/dist/lib/request/rate-limit-backoff.d.ts +7 -0
  43. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -1
  44. package/dist/lib/request/rate-limit-backoff.js +24 -4
  45. package/dist/lib/request/rate-limit-backoff.js.map +1 -1
  46. package/dist/lib/request/request-transformer.d.ts +10 -0
  47. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  48. package/dist/lib/request/request-transformer.js +61 -37
  49. package/dist/lib/request/request-transformer.js.map +1 -1
  50. package/dist/lib/rotation.d.ts +19 -0
  51. package/dist/lib/rotation.d.ts.map +1 -1
  52. package/dist/lib/rotation.js +60 -39
  53. package/dist/lib/rotation.js.map +1 -1
  54. package/dist/lib/ui/copy.d.ts +1 -1
  55. package/dist/lib/ui/copy.js +1 -1
  56. package/dist/lib/utils.d.ts +6 -0
  57. package/dist/lib/utils.d.ts.map +1 -1
  58. package/dist/lib/utils.js +11 -0
  59. package/dist/lib/utils.js.map +1 -1
  60. package/package.json +9 -5
  61. package/scripts/audit-dev-allowlist.js +186 -94
  62. package/scripts/benchmark-runtime-path.mjs +166 -0
  63. package/scripts/codex.js +57 -1
  64. package/scripts/copy-oauth-success.js +42 -3
  65. package/scripts/install-codex-auth-utils.js +69 -0
  66. package/scripts/install-codex-auth.js +16 -7
package/scripts/codex.js CHANGED
@@ -32,6 +32,25 @@ async function loadRunCodexMultiAuthCli() {
32
32
  }
33
33
  }
34
34
 
35
+ async function autoSyncManagerActiveSelectionIfEnabled() {
36
+ const enabled = (process.env.CODEX_MULTI_AUTH_AUTO_SYNC_ON_STARTUP ?? "1").trim() !== "0";
37
+ if (!enabled) return;
38
+
39
+ try {
40
+ const mod = await import("../dist/lib/codex-manager.js");
41
+ if (typeof mod.autoSyncActiveAccountToCodex !== "function") {
42
+ return;
43
+ }
44
+ await mod.autoSyncActiveAccountToCodex();
45
+ } catch (error) {
46
+ if (error && typeof error === "object" && "code" in error && error.code === "ERR_MODULE_NOT_FOUND") {
47
+ // Non-auth command path should keep forwarding even if dist is missing.
48
+ return;
49
+ }
50
+ // Best effort only: never block official Codex startup on sync failure.
51
+ }
52
+ }
53
+
35
54
  function resolveRealCodexBin() {
36
55
  const override = (process.env.CODEX_MULTI_AUTH_REAL_CODEX_BIN ?? "").trim();
37
56
  if (override.length > 0) {
@@ -113,6 +132,41 @@ function forwardToRealCodex(codexBin, args) {
113
132
  });
114
133
  }
115
134
 
135
+ function hasCliAuthCredentialsStoreOverride(args) {
136
+ for (let i = 0; i < args.length; i += 1) {
137
+ const arg = args[i];
138
+ if (arg === "-c" || arg === "--config") {
139
+ const next = args[i + 1];
140
+ if (!next || !next.includes("=")) continue;
141
+ const [key] = next.split("=", 1);
142
+ if ((key ?? "").trim() === "cli_auth_credentials_store") {
143
+ return true;
144
+ }
145
+ continue;
146
+ }
147
+ if (typeof arg === "string" && arg.startsWith("--config=")) {
148
+ const assignment = arg.slice("--config=".length);
149
+ const [key] = assignment.split("=", 1);
150
+ if ((key ?? "").trim() === "cli_auth_credentials_store") {
151
+ return true;
152
+ }
153
+ }
154
+ }
155
+ return false;
156
+ }
157
+
158
+ function buildForwardArgs(rawArgs) {
159
+ const forceFileAuthStore = (process.env.CODEX_MULTI_AUTH_FORCE_FILE_AUTH_STORE ?? "1").trim() !== "0";
160
+ if (!forceFileAuthStore) return [...rawArgs];
161
+ if (hasCliAuthCredentialsStoreOverride(rawArgs)) return [...rawArgs];
162
+
163
+ return [
164
+ ...rawArgs,
165
+ "-c",
166
+ 'cli_auth_credentials_store="file"',
167
+ ];
168
+ }
169
+
116
170
  function normalizeExitCode(value) {
117
171
  if (typeof value === "number" && Number.isInteger(value)) {
118
172
  return value;
@@ -156,5 +210,7 @@ if (!realCodexBin) {
156
210
  process.exit(1);
157
211
  }
158
212
 
159
- const forwardExitCode = await forwardToRealCodex(realCodexBin, rawArgs);
213
+ await autoSyncManagerActiveSelectionIfEnabled();
214
+ const forwardArgs = buildForwardArgs(rawArgs);
215
+ const forwardExitCode = await forwardToRealCodex(realCodexBin, forwardArgs);
160
216
  process.exit(forwardExitCode);
@@ -1,4 +1,4 @@
1
- import { promises as fs } from "node:fs";
1
+ import * as fs from "node:fs/promises";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
@@ -16,20 +16,59 @@ function getDefaultPaths() {
16
16
  return { src, dest };
17
17
  }
18
18
 
19
+ /**
20
+ * Copy a file and retry automatically while the destination is temporarily locked.
21
+ * @param {string} src absolute path to the source HTML file
22
+ * @param {string} dest absolute path to the destination HTML file
23
+ * @param {{ maxAttempts?: number, backoffMs?: number }} options retry configuration
24
+ */
25
+ async function copyWithRetry(
26
+ src,
27
+ dest,
28
+ { maxAttempts = 3, backoffMs = 50 } = {},
29
+ ) {
30
+ const retryableCodes = new Set(["EBUSY", "EPERM", "EACCES"]);
31
+ let attempt = 0;
32
+ for (;;) {
33
+ try {
34
+ await fs.copyFile(src, dest);
35
+ return;
36
+ } catch (err) {
37
+ const code = err && typeof err === "object" && "code" in err ? err.code : undefined;
38
+ const isRetryable = typeof code === "string" && retryableCodes.has(code);
39
+ if (!isRetryable || attempt >= maxAttempts - 1) {
40
+ throw err;
41
+ }
42
+ attempt += 1;
43
+ const delayMs = backoffMs * (2 ** (attempt - 1));
44
+ await new Promise((resolve) => {
45
+ setTimeout(resolve, delayMs);
46
+ });
47
+ }
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Copy the OAuth success HTML into dist/, ensuring safe directory creation and retries.
53
+ * @param {{ src?: string, dest?: string }} options optional override paths for testing
54
+ */
19
55
  export async function copyOAuthSuccessHtml(options = {}) {
20
56
  const defaults = getDefaultPaths();
21
57
  const src = options.src ?? defaults.src;
22
58
  const dest = options.dest ?? defaults.dest;
23
59
 
24
60
  await fs.mkdir(dirname(dest), { recursive: true });
25
- await fs.copyFile(src, dest);
61
+ await copyWithRetry(src, dest);
26
62
 
27
63
  return { src, dest };
28
64
  }
29
65
 
30
66
  const isDirectRun = (() => {
31
67
  if (!process.argv[1]) return false;
32
- return normalizePathForCompare(process.argv[1]) === normalizePathForCompare(__filename);
68
+ return (
69
+ normalizePathForCompare(process.argv[1]) ===
70
+ normalizePathForCompare(__filename)
71
+ );
33
72
  })();
34
73
 
35
74
  if (isDirectRun) {
@@ -1,7 +1,12 @@
1
1
  import { join } from "node:path";
2
2
  import { homedir } from "node:os";
3
+ import { rename as fsRename } from "node:fs/promises";
3
4
 
4
5
  const PLUGIN_NAME = "codex-multi-auth";
6
+ export const FILE_RETRY_CODES = new Set(["EBUSY", "EPERM", "EAGAIN", "ENOTEMPTY", "EACCES"]);
7
+ export const FILE_RETRY_MAX_ATTEMPTS = 6;
8
+ export const FILE_RETRY_BASE_DELAY_MS = 25;
9
+ export const FILE_RETRY_JITTER_MS = 20;
5
10
 
6
11
  export function resolveInstallPaths(
7
12
  platform = process.platform,
@@ -47,3 +52,67 @@ export function normalizePluginList(list) {
47
52
  return [...deduped, PLUGIN_NAME];
48
53
  }
49
54
 
55
+ function sleep(ms) {
56
+ // Keep this helper local so installer scripts remain standalone and do not depend on lib/.
57
+ return new Promise((resolve) => {
58
+ setTimeout(resolve, ms);
59
+ });
60
+ }
61
+
62
+ function shouldRetryFileOperation(error) {
63
+ return error instanceof Error &&
64
+ typeof error.code === "string" &&
65
+ FILE_RETRY_CODES.has(error.code);
66
+ }
67
+
68
+ export async function withFileOperationRetry(operation) {
69
+ for (let attempt = 1; ; attempt += 1) {
70
+ try {
71
+ return await operation();
72
+ } catch (error) {
73
+ if (!shouldRetryFileOperation(error) || attempt >= FILE_RETRY_MAX_ATTEMPTS) {
74
+ throw error;
75
+ }
76
+
77
+ const jitter = Math.floor(Math.random() * FILE_RETRY_JITTER_MS);
78
+ const delayMs = (FILE_RETRY_BASE_DELAY_MS * (2 ** (attempt - 1))) + jitter;
79
+ await sleep(delayMs);
80
+ }
81
+ }
82
+ }
83
+
84
+ export async function renameWithRetry(sourcePath, targetPath, options = {}) {
85
+ const {
86
+ rename = fsRename,
87
+ log = () => {},
88
+ maxRetries = FILE_RETRY_MAX_ATTEMPTS,
89
+ baseDelayMs = FILE_RETRY_BASE_DELAY_MS,
90
+ jitterMs = FILE_RETRY_JITTER_MS,
91
+ random = Math.random,
92
+ sleep: sleepImpl = sleep,
93
+ } = options;
94
+
95
+ if (!Number.isInteger(maxRetries) || maxRetries < 1) {
96
+ throw new RangeError("maxRetries must be an integer >= 1");
97
+ }
98
+
99
+ for (let attempt = 0; attempt < maxRetries; attempt += 1) {
100
+ try {
101
+ await rename(sourcePath, targetPath);
102
+ return;
103
+ } catch (error) {
104
+ const code = error && typeof error === "object" && "code" in error
105
+ ? error.code
106
+ : undefined;
107
+ const isRetryable = typeof code === "string" && FILE_RETRY_CODES.has(code);
108
+ if (!isRetryable || attempt === maxRetries - 1) {
109
+ throw error;
110
+ }
111
+ const delayMs = baseDelayMs * 2 ** attempt + Math.floor(random() * jitterMs);
112
+ log(
113
+ `Retrying atomic rename (${attempt + 1}/${maxRetries}) code=${code ?? "unknown"} source=${sourcePath} target=${targetPath} delayMs=${delayMs}`,
114
+ );
115
+ await sleepImpl(delayMs);
116
+ }
117
+ }
118
+ }
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { existsSync } from "node:fs";
4
- import { readFile, writeFile, mkdir, copyFile, rm, rename } from "node:fs/promises";
4
+ import { readFile, writeFile, mkdir, copyFile, rm } from "node:fs/promises";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { dirname, join, resolve } from "node:path";
7
- import { normalizePluginList, resolveInstallPaths } from "./install-codex-auth-utils.js";
7
+ import {
8
+ normalizePluginList,
9
+ renameWithRetry,
10
+ resolveInstallPaths,
11
+ withFileOperationRetry,
12
+ } from "./install-codex-auth-utils.js";
8
13
 
9
14
  const PLUGIN_NAME = "codex-multi-auth";
10
15
 
@@ -61,11 +66,15 @@ async function writeJsonAtomic(filePath, value) {
61
66
  .slice(2, 8)}`;
62
67
  const content = formatJson(value);
63
68
  try {
64
- await writeFile(tempPath, content, "utf-8");
65
- await rename(tempPath, filePath);
69
+ await withFileOperationRetry(() => writeFile(tempPath, content, "utf-8"));
70
+ await renameWithRetry(tempPath, filePath, { log });
66
71
  } finally {
67
72
  if (existsSync(tempPath)) {
68
- await rm(tempPath, { force: true });
73
+ try {
74
+ await withFileOperationRetry(() => rm(tempPath, { force: true }));
75
+ } catch (error) {
76
+ log(`Warning: Could not remove temporary file ${tempPath} (${error}).`);
77
+ }
69
78
  }
70
79
  }
71
80
  }
@@ -136,8 +145,8 @@ async function clearCache() {
136
145
  log(`[dry-run] Would remove ${cacheBunLock}`);
137
146
  } else {
138
147
  try {
139
- await rm(cacheNodeModules, { recursive: true, force: true });
140
- await rm(cacheBunLock, { force: true });
148
+ await withFileOperationRetry(() => rm(cacheNodeModules, { recursive: true, force: true }));
149
+ await withFileOperationRetry(() => rm(cacheBunLock, { force: true }));
141
150
  } catch (error) {
142
151
  log(
143
152
  `Warning: Could not fully clear cache (${error instanceof Error ? error.message : String(error)}). You may need to restart Codex.`,