codex-multi-auth 0.1.2 → 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.
- package/README.md +6 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -13
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +6 -2
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +34 -11
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/auth/server.d.ts +1 -1
- package/dist/lib/auth/server.js +3 -3
- package/dist/lib/codex-cli/state.d.ts +9 -0
- package/dist/lib/codex-cli/state.d.ts.map +1 -1
- package/dist/lib/codex-cli/state.js +14 -0
- package/dist/lib/codex-cli/state.js.map +1 -1
- package/dist/lib/codex-cli/writer.d.ts.map +1 -1
- package/dist/lib/codex-cli/writer.js +106 -14
- package/dist/lib/codex-cli/writer.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +16 -1
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
- package/dist/lib/codex-manager/settings-hub.js +28 -2
- package/dist/lib/codex-manager/settings-hub.js.map +1 -1
- package/dist/lib/codex-manager.d.ts +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +320 -18
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/oauth-success.html +192 -260
- package/dist/lib/parallel-probe.d.ts +7 -0
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +37 -9
- package/dist/lib/parallel-probe.js.map +1 -1
- package/dist/lib/refresh-queue.d.ts +4 -5
- package/dist/lib/refresh-queue.d.ts.map +1 -1
- package/dist/lib/refresh-queue.js +99 -21
- package/dist/lib/refresh-queue.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +12 -4
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +53 -25
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -1
- package/dist/lib/request/helpers/tool-utils.js +36 -6
- package/dist/lib/request/helpers/tool-utils.js.map +1 -1
- package/dist/lib/request/rate-limit-backoff.d.ts +7 -0
- package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -1
- package/dist/lib/request/rate-limit-backoff.js +24 -4
- package/dist/lib/request/rate-limit-backoff.js.map +1 -1
- package/dist/lib/request/request-transformer.d.ts +10 -0
- package/dist/lib/request/request-transformer.d.ts.map +1 -1
- package/dist/lib/request/request-transformer.js +61 -37
- package/dist/lib/request/request-transformer.js.map +1 -1
- package/dist/lib/rotation.d.ts +19 -0
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +60 -39
- package/dist/lib/rotation.js.map +1 -1
- package/dist/lib/ui/copy.d.ts +1 -1
- package/dist/lib/ui/copy.js +1 -1
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -1
- package/package.json +9 -5
- package/scripts/audit-dev-allowlist.js +186 -94
- package/scripts/benchmark-runtime-path.mjs +166 -0
- package/scripts/codex.js +57 -1
- package/scripts/copy-oauth-success.js +42 -3
- package/scripts/install-codex-auth-utils.js +69 -0
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
69
|
+
await withFileOperationRetry(() => writeFile(tempPath, content, "utf-8"));
|
|
70
|
+
await renameWithRetry(tempPath, filePath, { log });
|
|
66
71
|
} finally {
|
|
67
72
|
if (existsSync(tempPath)) {
|
|
68
|
-
|
|
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.`,
|