codex-multi-auth 0.1.3 → 0.1.5

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 (86) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +7 -10
  3. package/assets/readme-hero.svg +31 -31
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +51 -13
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/auth/auth.d.ts +6 -2
  8. package/dist/lib/auth/auth.d.ts.map +1 -1
  9. package/dist/lib/auth/auth.js +34 -11
  10. package/dist/lib/auth/auth.js.map +1 -1
  11. package/dist/lib/auth/server.d.ts +1 -1
  12. package/dist/lib/auth/server.js +3 -3
  13. package/dist/lib/codex-cli/state.d.ts +9 -0
  14. package/dist/lib/codex-cli/state.d.ts.map +1 -1
  15. package/dist/lib/codex-cli/state.js +14 -0
  16. package/dist/lib/codex-cli/state.js.map +1 -1
  17. package/dist/lib/codex-cli/writer.d.ts.map +1 -1
  18. package/dist/lib/codex-cli/writer.js +106 -14
  19. package/dist/lib/codex-cli/writer.js.map +1 -1
  20. package/dist/lib/codex-manager/settings-hub.d.ts +16 -1
  21. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  22. package/dist/lib/codex-manager/settings-hub.js +28 -2
  23. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  24. package/dist/lib/codex-manager.d.ts +1 -0
  25. package/dist/lib/codex-manager.d.ts.map +1 -1
  26. package/dist/lib/codex-manager.js +319 -16
  27. package/dist/lib/codex-manager.js.map +1 -1
  28. package/dist/lib/context-overflow.js +10 -10
  29. package/dist/lib/oauth-success.html +192 -260
  30. package/dist/lib/parallel-probe.d.ts +7 -0
  31. package/dist/lib/parallel-probe.d.ts.map +1 -1
  32. package/dist/lib/parallel-probe.js +37 -9
  33. package/dist/lib/parallel-probe.js.map +1 -1
  34. package/dist/lib/prompts/codex.js +2 -2
  35. package/dist/lib/refresh-queue.d.ts +4 -5
  36. package/dist/lib/refresh-queue.d.ts.map +1 -1
  37. package/dist/lib/refresh-queue.js +99 -21
  38. package/dist/lib/refresh-queue.js.map +1 -1
  39. package/dist/lib/request/fetch-helpers.d.ts +12 -4
  40. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  41. package/dist/lib/request/fetch-helpers.js +53 -25
  42. package/dist/lib/request/fetch-helpers.js.map +1 -1
  43. package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
  44. package/dist/lib/request/helpers/tool-utils.js +36 -6
  45. package/dist/lib/request/helpers/tool-utils.js.map +1 -1
  46. package/dist/lib/request/rate-limit-backoff.d.ts +7 -0
  47. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -1
  48. package/dist/lib/request/rate-limit-backoff.js +24 -4
  49. package/dist/lib/request/rate-limit-backoff.js.map +1 -1
  50. package/dist/lib/request/request-transformer.d.ts +10 -0
  51. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  52. package/dist/lib/request/request-transformer.js +61 -37
  53. package/dist/lib/request/request-transformer.js.map +1 -1
  54. package/dist/lib/rotation.d.ts +19 -0
  55. package/dist/lib/rotation.d.ts.map +1 -1
  56. package/dist/lib/rotation.js +60 -39
  57. package/dist/lib/rotation.js.map +1 -1
  58. package/dist/lib/ui/copy.d.ts +1 -1
  59. package/dist/lib/ui/copy.js +1 -1
  60. package/dist/lib/utils.d.ts +6 -0
  61. package/dist/lib/utils.d.ts.map +1 -1
  62. package/dist/lib/utils.js +11 -0
  63. package/dist/lib/utils.js.map +1 -1
  64. package/node_modules/@codex-ai/plugin/dist/index.d.ts +1 -1
  65. package/node_modules/@codex-ai/plugin/dist/tool.d.ts +41 -41
  66. package/node_modules/@codex-ai/plugin/dist/tool.js +28 -28
  67. package/package.json +9 -5
  68. package/scripts/audit-dev-allowlist.js +186 -94
  69. package/scripts/bench-format/hashline-v2.mjs +642 -642
  70. package/scripts/bench-format/render.mjs +496 -496
  71. package/scripts/bench-format/stats.mjs +54 -54
  72. package/scripts/bench-format/tasks.mjs +151 -151
  73. package/scripts/benchmark-render-dashboard.mjs +49 -49
  74. package/scripts/benchmark-runtime-path.mjs +166 -0
  75. package/scripts/codex-multi-auth.js +1 -1
  76. package/scripts/codex.js +86 -26
  77. package/scripts/copy-oauth-success.js +42 -3
  78. package/scripts/install-codex-auth-utils.js +69 -0
  79. package/scripts/install-codex-auth.js +23 -14
  80. package/scripts/test-all-models.sh +7 -7
  81. package/scripts/test-model-matrix.js +108 -80
  82. package/scripts/validate-model-map.sh +7 -7
  83. package/vendor/codex-ai-plugin/dist/index.d.ts +1 -1
  84. package/vendor/codex-ai-plugin/dist/tool.d.ts +41 -41
  85. package/vendor/codex-ai-plugin/dist/tool.js +28 -28
  86. package/vendor/codex-ai-sdk/dist/index.d.ts +3 -3
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { performance } from "node:perf_hooks";
5
+ import process from "node:process";
6
+ import { dirname, resolve } from "node:path";
7
+ import { filterInput } from "../dist/lib/request/request-transformer.js";
8
+ import { cleanupToolDefinitions } from "../dist/lib/request/helpers/tool-utils.js";
9
+ import { AccountManager } from "../dist/lib/accounts.js";
10
+
11
+ function argValue(args, name) {
12
+ const prefix = `${name}=`;
13
+ const match = args.find((arg) => arg.startsWith(prefix));
14
+ return match ? match.slice(prefix.length) : undefined;
15
+ }
16
+
17
+ function parsePositiveInt(value, fallback) {
18
+ if (!value) return fallback;
19
+ const parsed = Number.parseInt(value, 10);
20
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
21
+ return parsed;
22
+ }
23
+
24
+ function benchmarkCase(name, iterations, fn) {
25
+ for (let i = 0; i < 5; i += 1) {
26
+ fn();
27
+ }
28
+ const start = performance.now();
29
+ for (let i = 0; i < iterations; i += 1) {
30
+ fn();
31
+ }
32
+ const end = performance.now();
33
+ return {
34
+ name,
35
+ iterations,
36
+ avgMs: Number(((end - start) / iterations).toFixed(6)),
37
+ };
38
+ }
39
+
40
+ function buildInputItems(size) {
41
+ const items = [];
42
+ for (let i = 0; i < size; i += 1) {
43
+ items.push({
44
+ type: "message",
45
+ role: i % 2 === 0 ? "user" : "assistant",
46
+ id: `msg_${i}`,
47
+ content: [{ type: "input_text", text: `payload-${i}` }],
48
+ });
49
+ if (i % 40 === 0) {
50
+ items.push({ type: "item_reference", id: `ref_${i}` });
51
+ }
52
+ }
53
+ return items;
54
+ }
55
+
56
+ function buildTools(toolCount, propertyCount) {
57
+ const tools = [];
58
+ for (let i = 0; i < toolCount; i += 1) {
59
+ const properties = {};
60
+ const required = [];
61
+ for (let j = 0; j < propertyCount; j += 1) {
62
+ const key = `field_${j}`;
63
+ properties[key] = { type: ["string", "null"], description: `property-${j}` };
64
+ required.push(key);
65
+ }
66
+ required.push("ghost_field");
67
+ tools.push({
68
+ type: "function",
69
+ function: {
70
+ name: `tool_${i}`,
71
+ parameters: {
72
+ type: "object",
73
+ properties,
74
+ required,
75
+ additionalProperties: false,
76
+ },
77
+ },
78
+ });
79
+ }
80
+ return tools;
81
+ }
82
+
83
+ function buildManager(accountCount) {
84
+ const now = Date.now();
85
+ const accounts = [];
86
+ for (let i = 0; i < accountCount; i += 1) {
87
+ accounts.push({
88
+ refreshToken: `rt_${i}`,
89
+ accessToken: `at_${i}`,
90
+ expiresAt: now + 3_600_000,
91
+ accountId: `acct_${i}`,
92
+ email: `user${i}@example.com`,
93
+ enabled: true,
94
+ addedAt: now,
95
+ lastUsed: 0,
96
+ rateLimitResetTimes: {},
97
+ });
98
+ }
99
+ return new AccountManager(undefined, {
100
+ version: 3,
101
+ accounts,
102
+ activeIndex: 0,
103
+ activeIndexByFamily: {},
104
+ });
105
+ }
106
+
107
+ function run() {
108
+ const args = process.argv.slice(2);
109
+ const iterations = parsePositiveInt(argValue(args, "--iterations"), 30);
110
+ const outputPath = argValue(args, "--output");
111
+
112
+ const inputSmall = buildInputItems(400);
113
+ const inputLarge = buildInputItems(2000);
114
+ const toolsMedium = buildTools(40, 12);
115
+ const toolsLarge = buildTools(140, 25);
116
+
117
+ const results = [
118
+ benchmarkCase("filterInput_small", iterations, () => {
119
+ const out = filterInput(inputSmall);
120
+ if (!Array.isArray(out)) throw new Error("filterInput_small failed");
121
+ }),
122
+ benchmarkCase("filterInput_large", iterations, () => {
123
+ const out = filterInput(inputLarge);
124
+ if (!Array.isArray(out)) throw new Error("filterInput_large failed");
125
+ }),
126
+ benchmarkCase("cleanupToolDefinitions_medium", iterations, () => {
127
+ const out = cleanupToolDefinitions(toolsMedium);
128
+ if (!Array.isArray(out)) throw new Error("cleanupToolDefinitions_medium failed");
129
+ }),
130
+ benchmarkCase("cleanupToolDefinitions_large", iterations, () => {
131
+ const out = cleanupToolDefinitions(toolsLarge);
132
+ if (!Array.isArray(out)) throw new Error("cleanupToolDefinitions_large failed");
133
+ }),
134
+ benchmarkCase("accountHybridSelection_200", iterations, () => {
135
+ const manager = buildManager(200);
136
+ for (let i = 0; i < 200; i += 1) {
137
+ manager.getCurrentOrNextForFamilyHybrid("codex", "gpt-5-codex", { pidOffsetEnabled: false });
138
+ }
139
+ }),
140
+ ];
141
+
142
+ const payload = {
143
+ generatedAt: new Date().toISOString(),
144
+ node: process.version,
145
+ iterations,
146
+ results,
147
+ };
148
+
149
+ if (outputPath) {
150
+ const resolved = resolve(outputPath);
151
+ return mkdir(dirname(resolved), { recursive: true }).then(() =>
152
+ writeFile(resolved, `${JSON.stringify(payload, null, 2)}\n`, "utf8"),
153
+ ).then(() => {
154
+ console.log(`Runtime benchmark written: ${resolved}`);
155
+ console.log(JSON.stringify(payload, null, 2));
156
+ });
157
+ }
158
+
159
+ console.log(JSON.stringify(payload, null, 2));
160
+ return Promise.resolve();
161
+ }
162
+
163
+ run().catch((error) => {
164
+ console.error(`Runtime benchmark failed: ${error instanceof Error ? error.message : String(error)}`);
165
+ process.exit(1);
166
+ });
@@ -3,4 +3,4 @@
3
3
  import { runCodexMultiAuthCli } from "../dist/lib/codex-manager.js";
4
4
 
5
5
  const exitCode = await runCodexMultiAuthCli(process.argv.slice(2));
6
- process.exit(exitCode);
6
+ process.exitCode = Number.isInteger(exitCode) ? exitCode : 1;
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;
@@ -124,37 +178,43 @@ function normalizeExitCode(value) {
124
178
  return 1;
125
179
  }
126
180
 
127
- const rawArgs = process.argv.slice(2);
128
- const normalizedArgs = normalizeAuthAlias(rawArgs);
129
- const bypass = (process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1";
181
+ async function main() {
182
+ const rawArgs = process.argv.slice(2);
183
+ const normalizedArgs = normalizeAuthAlias(rawArgs);
184
+ const bypass = (process.env.CODEX_MULTI_AUTH_BYPASS ?? "").trim() === "1";
130
185
 
131
- if (!bypass && shouldHandleMultiAuthAuth(normalizedArgs)) {
132
- try {
133
- const runCodexMultiAuthCli = await loadRunCodexMultiAuthCli();
134
- if (!runCodexMultiAuthCli) {
135
- process.exit(1);
186
+ if (!bypass && shouldHandleMultiAuthAuth(normalizedArgs)) {
187
+ try {
188
+ const runCodexMultiAuthCli = await loadRunCodexMultiAuthCli();
189
+ if (!runCodexMultiAuthCli) {
190
+ return 1;
191
+ }
192
+ const exitCode = await runCodexMultiAuthCli(normalizedArgs);
193
+ return normalizeExitCode(exitCode);
194
+ } catch (error) {
195
+ console.error(
196
+ `codex-multi-auth runner failed: ${error instanceof Error ? error.message : String(error)}`,
197
+ );
198
+ return 1;
136
199
  }
137
- const exitCode = await runCodexMultiAuthCli(normalizedArgs);
138
- process.exit(normalizeExitCode(exitCode));
139
- } catch (error) {
200
+ }
201
+
202
+ const realCodexBin = resolveRealCodexBin();
203
+ if (!realCodexBin) {
140
204
  console.error(
141
- `codex-multi-auth runner failed: ${error instanceof Error ? error.message : String(error)}`,
205
+ [
206
+ "Could not locate the official Codex CLI binary (@openai/codex).",
207
+ "Install it globally: npm install -g @openai/codex",
208
+ "Or set CODEX_MULTI_AUTH_REAL_CODEX_BIN to a full bin/codex.js path.",
209
+ ].join("\n"),
142
210
  );
143
- process.exit(1);
211
+ return 1;
144
212
  }
145
- }
146
213
 
147
- const realCodexBin = resolveRealCodexBin();
148
- if (!realCodexBin) {
149
- console.error(
150
- [
151
- "Could not locate the official Codex CLI binary (@openai/codex).",
152
- "Install it globally: npm install -g @openai/codex",
153
- "Or set CODEX_MULTI_AUTH_REAL_CODEX_BIN to a full bin/codex.js path.",
154
- ].join("\n"),
155
- );
156
- process.exit(1);
214
+ await autoSyncManagerActiveSelectionIfEnabled();
215
+ const forwardArgs = buildForwardArgs(rawArgs);
216
+ return forwardToRealCodex(realCodexBin, forwardArgs);
157
217
  }
158
218
 
159
- const forwardExitCode = await forwardToRealCodex(realCodexBin, rawArgs);
160
- process.exit(forwardExitCode);
219
+ const exitCode = await main();
220
+ process.exitCode = normalizeExitCode(exitCode);
@@ -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
  }
@@ -134,15 +143,15 @@ async function clearCache() {
134
143
  if (dryRun) {
135
144
  log(`[dry-run] Would remove ${cacheNodeModules}`);
136
145
  log(`[dry-run] Would remove ${cacheBunLock}`);
137
- } else {
138
- try {
139
- await rm(cacheNodeModules, { recursive: true, force: true });
140
- await rm(cacheBunLock, { force: true });
141
- } catch (error) {
142
- log(
143
- `Warning: Could not fully clear cache (${error instanceof Error ? error.message : String(error)}). You may need to restart Codex.`,
144
- );
145
- }
146
+ } else {
147
+ try {
148
+ await withFileOperationRetry(() => rm(cacheNodeModules, { recursive: true, force: true }));
149
+ await withFileOperationRetry(() => rm(cacheBunLock, { force: true }));
150
+ } catch (error) {
151
+ log(
152
+ `Warning: Could not fully clear cache (${error instanceof Error ? error.message : String(error)}). You may need to restart Codex.`,
153
+ );
154
+ }
146
155
  }
147
156
 
148
157
  await removePluginFromCachePackage();
@@ -1,7 +1,7 @@
1
- #!/bin/bash
2
-
3
- set -euo pipefail
4
-
5
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
- node "${SCRIPT_DIR}/test-model-matrix.js" "$@"
7
-
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ node "${SCRIPT_DIR}/test-model-matrix.js" "$@"
7
+