codex-multi-auth 2.1.13-beta.1 → 2.2.0
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/LICENSE +21 -21
- package/README.md +415 -415
- package/assets/codex-multi-auth-icon.svg +39 -39
- package/assets/readme-hero.svg +31 -31
- package/config/README.md +29 -29
- package/config/codex-legacy.json +571 -571
- package/config/codex-modern.json +258 -258
- package/config/minimal-codex.json +15 -15
- package/config/schema/config.schema.json +20 -20
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +30 -1
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/org-override.d.ts +19 -0
- package/dist/lib/auth/org-override.d.ts.map +1 -0
- package/dist/lib/auth/org-override.js +23 -0
- package/dist/lib/auth/org-override.js.map +1 -0
- package/dist/lib/circuit-breaker.d.ts +6 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +8 -0
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/codex-manager/account-manager-commands.d.ts +14 -0
- package/dist/lib/codex-manager/account-manager-commands.d.ts.map +1 -0
- package/dist/lib/codex-manager/account-manager-commands.js +43 -0
- package/dist/lib/codex-manager/account-manager-commands.js.map +1 -0
- package/dist/lib/codex-manager/commands/best.d.ts +1 -1
- package/dist/lib/codex-manager/commands/best.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/best.js +2 -1
- package/dist/lib/codex-manager/commands/best.js.map +1 -1
- package/dist/lib/codex-manager/commands/debug-bundle.d.ts +16 -0
- package/dist/lib/codex-manager/commands/debug-bundle.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/debug-bundle.js +80 -5
- package/dist/lib/codex-manager/commands/debug-bundle.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +2 -1
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +2 -2
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager/commands/status.d.ts +2 -0
- package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/status.js +84 -25
- package/dist/lib/codex-manager/commands/status.js.map +1 -1
- package/dist/lib/codex-manager/commands/switch.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/switch.js +7 -0
- package/dist/lib/codex-manager/commands/switch.js.map +1 -1
- package/dist/lib/codex-manager/experimental-settings-prompt.d.ts.map +1 -1
- package/dist/lib/codex-manager/experimental-settings-prompt.js +13 -3
- package/dist/lib/codex-manager/experimental-settings-prompt.js.map +1 -1
- package/dist/lib/codex-manager/forecast-report-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/forecast-report-commands.js +3 -3
- package/dist/lib/codex-manager/forecast-report-commands.js.map +1 -1
- package/dist/lib/codex-manager/repair-commands.d.ts.map +1 -1
- package/dist/lib/codex-manager/repair-commands.js +11 -1
- package/dist/lib/codex-manager/repair-commands.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub/experimental.d.ts.map +1 -1
- package/dist/lib/codex-manager/settings-hub/experimental.js +8 -0
- package/dist/lib/codex-manager/settings-hub/experimental.js.map +1 -1
- package/dist/lib/codex-manager.d.ts +3 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +53 -65
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +152 -5
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/context-overflow.d.ts +7 -2
- package/dist/lib/context-overflow.d.ts.map +1 -1
- package/dist/lib/context-overflow.js +65 -47
- package/dist/lib/context-overflow.js.map +1 -1
- package/dist/lib/errors.d.ts +16 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +23 -0
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/local-bridge.d.ts +8 -0
- package/dist/lib/local-bridge.d.ts.map +1 -1
- package/dist/lib/local-bridge.js +81 -7
- package/dist/lib/local-bridge.js.map +1 -1
- package/dist/lib/logger.d.ts +9 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +104 -19
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/oauth-success.html +125 -125
- package/dist/lib/oc-chatgpt-orchestrator.d.ts +7 -1
- package/dist/lib/oc-chatgpt-orchestrator.d.ts.map +1 -1
- package/dist/lib/oc-chatgpt-orchestrator.js +68 -9
- package/dist/lib/oc-chatgpt-orchestrator.js.map +1 -1
- package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
- package/dist/lib/policy/runtime-policy.js +15 -1
- package/dist/lib/policy/runtime-policy.js.map +1 -1
- package/dist/lib/prompts/codex-host-bridge.js +138 -138
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +203 -108
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/prompts/fetch-utils.d.ts +72 -0
- package/dist/lib/prompts/fetch-utils.d.ts.map +1 -0
- package/dist/lib/prompts/fetch-utils.js +165 -0
- package/dist/lib/prompts/fetch-utils.js.map +1 -0
- package/dist/lib/prompts/host-codex-prompt.d.ts.map +1 -1
- package/dist/lib/prompts/host-codex-prompt.js +24 -10
- package/dist/lib/prompts/host-codex-prompt.js.map +1 -1
- package/dist/lib/quota-cache.d.ts.map +1 -1
- package/dist/lib/quota-cache.js +10 -1
- package/dist/lib/quota-cache.js.map +1 -1
- package/dist/lib/quota-probe.d.ts +21 -0
- package/dist/lib/quota-probe.d.ts.map +1 -1
- package/dist/lib/quota-probe.js +39 -0
- package/dist/lib/quota-probe.js.map +1 -1
- package/dist/lib/quota-readiness.d.ts +3 -1
- package/dist/lib/quota-readiness.d.ts.map +1 -1
- package/dist/lib/quota-readiness.js +16 -3
- package/dist/lib/quota-readiness.js.map +1 -1
- package/dist/lib/recovery/storage.d.ts +7 -0
- package/dist/lib/recovery/storage.d.ts.map +1 -1
- package/dist/lib/recovery/storage.js +178 -10
- package/dist/lib/recovery/storage.js.map +1 -1
- package/dist/lib/refresh-lease.d.ts +1 -1
- package/dist/lib/refresh-lease.d.ts.map +1 -1
- package/dist/lib/refresh-lease.js +25 -3
- package/dist/lib/refresh-lease.js.map +1 -1
- package/dist/lib/refresh-queue.d.ts.map +1 -1
- package/dist/lib/refresh-queue.js +31 -17
- package/dist/lib/refresh-queue.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +0 -5
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +36 -7
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/rotation.d.ts +12 -0
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +36 -0
- package/dist/lib/rotation.js.map +1 -1
- package/dist/lib/runtime/account-check-types.d.ts +1 -0
- package/dist/lib/runtime/account-check-types.d.ts.map +1 -1
- package/dist/lib/runtime/account-check-types.js +1 -0
- package/dist/lib/runtime/account-check-types.js.map +1 -1
- package/dist/lib/runtime/account-check.d.ts.map +1 -1
- package/dist/lib/runtime/account-check.js +15 -4
- package/dist/lib/runtime/account-check.js.map +1 -1
- package/dist/lib/runtime/runtime-observability.d.ts.map +1 -1
- package/dist/lib/runtime/runtime-observability.js +24 -2
- package/dist/lib/runtime/runtime-observability.js.map +1 -1
- package/dist/lib/runtime-rotation-proxy.d.ts +3 -0
- package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
- package/dist/lib/runtime-rotation-proxy.js +296 -21
- package/dist/lib/runtime-rotation-proxy.js.map +1 -1
- package/dist/lib/schemas.d.ts +2 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +2 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/account-clear.d.ts.map +1 -1
- package/dist/lib/storage/account-clear.js +4 -2
- package/dist/lib/storage/account-clear.js.map +1 -1
- package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage-io.js +4 -1
- package/dist/lib/storage/flagged-storage-io.js.map +1 -1
- package/dist/lib/storage/import-export.d.ts.map +1 -1
- package/dist/lib/storage/import-export.js +29 -9
- package/dist/lib/storage/import-export.js.map +1 -1
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +93 -0
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage/storage-parser.d.ts.map +1 -1
- package/dist/lib/storage/storage-parser.js +7 -1
- package/dist/lib/storage/storage-parser.js.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +13 -9
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/table-formatter.d.ts.map +1 -1
- package/dist/lib/table-formatter.js +20 -2
- package/dist/lib/table-formatter.js.map +1 -1
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.d.ts.map +1 -1
- package/dist/lib/ui/auth-menu.js +8 -2
- package/dist/lib/ui/auth-menu.js.map +1 -1
- package/dist/lib/ui/display-width.d.ts +27 -0
- package/dist/lib/ui/display-width.d.ts.map +1 -0
- package/dist/lib/ui/display-width.js +194 -0
- package/dist/lib/ui/display-width.js.map +1 -0
- package/dist/lib/ui/select.d.ts +23 -0
- package/dist/lib/ui/select.d.ts.map +1 -1
- package/dist/lib/ui/select.js +64 -16
- package/dist/lib/ui/select.js.map +1 -1
- package/dist/lib/ui/theme.d.ts +13 -0
- package/dist/lib/ui/theme.d.ts.map +1 -1
- package/dist/lib/ui/theme.js +34 -1
- package/dist/lib/ui/theme.js.map +1 -1
- package/node_modules/@codex-ai/plugin/dist/index.d.ts +2 -2
- package/node_modules/@codex-ai/plugin/dist/index.js +2 -2
- package/node_modules/@codex-ai/plugin/dist/tool.d.ts +42 -42
- package/node_modules/@codex-ai/plugin/dist/tool.js +29 -29
- package/node_modules/@codex-ai/plugin/package.json +9 -9
- package/node_modules/@codex-ai/sdk/dist/index.d.ts +4 -0
- package/node_modules/@codex-ai/sdk/dist/index.js +2 -0
- package/node_modules/@codex-ai/sdk/package.json +8 -0
- package/package.json +196 -193
- package/scripts/codex-app-launcher.js +653 -653
- package/scripts/codex-app-router.js +322 -322
- package/scripts/codex-bin-resolver.js +254 -254
- package/scripts/codex-multi-auth.js +59 -59
- package/scripts/codex-routing.js +59 -56
- package/scripts/codex.js +4782 -4275
- package/scripts/install-codex-auth-utils.js +206 -206
- package/scripts/mcodex +117 -0
- package/scripts/postinstall.js +343 -343
- package/scripts/preuninstall.js +254 -254
- package/vendor/codex-ai-plugin/dist/index.d.ts +2 -2
- package/vendor/codex-ai-plugin/dist/index.js +2 -2
- package/vendor/codex-ai-plugin/dist/tool.d.ts +42 -42
- package/vendor/codex-ai-plugin/dist/tool.js +29 -29
- package/vendor/codex-ai-plugin/package.json +9 -9
- package/vendor/codex-ai-sdk/dist/index.d.ts +4 -4
- package/vendor/codex-ai-sdk/dist/index.js +2 -2
- package/vendor/codex-ai-sdk/package.json +8 -8
|
@@ -1,206 +1,206 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import { rename as fsRename } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import process from "node:process";
|
|
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
|
-
}
|
|
47
|
-
export const FILE_RETRY_CODES = new Set([
|
|
48
|
-
"EBUSY",
|
|
49
|
-
"EPERM",
|
|
50
|
-
"EAGAIN",
|
|
51
|
-
"ENOTEMPTY",
|
|
52
|
-
"EACCES",
|
|
53
|
-
]);
|
|
54
|
-
export const FILE_RETRY_MAX_ATTEMPTS = 6;
|
|
55
|
-
export const FILE_RETRY_BASE_DELAY_MS = 25;
|
|
56
|
-
export const FILE_RETRY_JITTER_MS = 20;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* @param {NodeJS.Platform} [platform]
|
|
60
|
-
* @param {NodeJS.ProcessEnv} [env]
|
|
61
|
-
* @param {string} [home]
|
|
62
|
-
*/
|
|
63
|
-
export function resolveInstallPaths(
|
|
64
|
-
platform = process.platform,
|
|
65
|
-
env = process.env,
|
|
66
|
-
home = homedir(),
|
|
67
|
-
) {
|
|
68
|
-
const isWindows = platform === "win32";
|
|
69
|
-
const appData = (env.APPDATA ?? "").trim();
|
|
70
|
-
const localAppData = (env.LOCALAPPDATA ?? appData).trim();
|
|
71
|
-
const configBase = isWindows
|
|
72
|
-
? appData || join(home, "AppData", "Roaming")
|
|
73
|
-
: join(home, ".config");
|
|
74
|
-
const cacheBase = isWindows
|
|
75
|
-
? localAppData || join(home, "AppData", "Local")
|
|
76
|
-
: join(home, ".cache");
|
|
77
|
-
const configDir = join(configBase, "Codex");
|
|
78
|
-
const configPath = join(configDir, "Codex.json");
|
|
79
|
-
const cacheDir = join(cacheBase, "Codex");
|
|
80
|
-
return {
|
|
81
|
-
configDir,
|
|
82
|
-
configPath,
|
|
83
|
-
cacheDir,
|
|
84
|
-
cacheNodeModules: join(cacheDir, "node_modules", PLUGIN_NAME),
|
|
85
|
-
cacheBunLock: join(cacheDir, "bun.lock"),
|
|
86
|
-
cachePackageJson: join(cacheDir, "package.json"),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
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
|
-
|
|
99
|
-
/** @param {unknown} list */
|
|
100
|
-
export function normalizePluginList(list) {
|
|
101
|
-
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
102
|
-
const filtered = entries.filter((entry) => {
|
|
103
|
-
if (typeof entry !== "string") return true;
|
|
104
|
-
return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
|
|
105
|
-
});
|
|
106
|
-
const deduped = [];
|
|
107
|
-
const seen = new Set();
|
|
108
|
-
for (const entry of filtered) {
|
|
109
|
-
const key =
|
|
110
|
-
typeof entry === "string" ? `s:${entry}` : `j:${JSON.stringify(entry)}`;
|
|
111
|
-
if (seen.has(key)) continue;
|
|
112
|
-
seen.add(key);
|
|
113
|
-
deduped.push(entry);
|
|
114
|
-
}
|
|
115
|
-
return [...deduped, PLUGIN_NAME];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** @param {number} ms */
|
|
119
|
-
function sleep(ms) {
|
|
120
|
-
// Keep this helper local so installer scripts remain standalone and do not depend on lib/.
|
|
121
|
-
return new Promise((resolve) => {
|
|
122
|
-
setTimeout(resolve, ms);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** @param {unknown} error */
|
|
127
|
-
function shouldRetryFileOperation(error) {
|
|
128
|
-
const fileError = /** @type {NodeJS.ErrnoException | undefined} */ (error);
|
|
129
|
-
return (
|
|
130
|
-
fileError instanceof Error &&
|
|
131
|
-
typeof fileError.code === "string" &&
|
|
132
|
-
FILE_RETRY_CODES.has(fileError.code)
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** @template T @param {() => Promise<T>} operation */
|
|
137
|
-
export async function withFileOperationRetry(operation) {
|
|
138
|
-
for (let attempt = 1; ; attempt += 1) {
|
|
139
|
-
try {
|
|
140
|
-
return await operation();
|
|
141
|
-
} catch (error) {
|
|
142
|
-
if (
|
|
143
|
-
!shouldRetryFileOperation(error) ||
|
|
144
|
-
attempt >= FILE_RETRY_MAX_ATTEMPTS
|
|
145
|
-
) {
|
|
146
|
-
throw error;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const jitter = Math.floor(Math.random() * FILE_RETRY_JITTER_MS);
|
|
150
|
-
const delayMs = FILE_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1) + jitter;
|
|
151
|
-
await sleep(delayMs);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* @param {string} sourcePath
|
|
158
|
-
* @param {string} targetPath
|
|
159
|
-
* @param {{
|
|
160
|
-
* rename?: (sourcePath: string, targetPath: string) => Promise<void>,
|
|
161
|
-
* log?: (message: string) => void,
|
|
162
|
-
* maxRetries?: number,
|
|
163
|
-
* baseDelayMs?: number,
|
|
164
|
-
* jitterMs?: number,
|
|
165
|
-
* random?: () => number,
|
|
166
|
-
* sleep?: (ms: number) => Promise<void>,
|
|
167
|
-
* }} [options]
|
|
168
|
-
*/
|
|
169
|
-
export async function renameWithRetry(sourcePath, targetPath, options = {}) {
|
|
170
|
-
const {
|
|
171
|
-
rename = fsRename,
|
|
172
|
-
log = () => {},
|
|
173
|
-
maxRetries = FILE_RETRY_MAX_ATTEMPTS,
|
|
174
|
-
baseDelayMs = FILE_RETRY_BASE_DELAY_MS,
|
|
175
|
-
jitterMs = FILE_RETRY_JITTER_MS,
|
|
176
|
-
random = Math.random,
|
|
177
|
-
sleep: sleepImpl = sleep,
|
|
178
|
-
} = options;
|
|
179
|
-
|
|
180
|
-
if (!Number.isInteger(maxRetries) || maxRetries < 1) {
|
|
181
|
-
throw new RangeError("maxRetries must be an integer >= 1");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
185
|
-
try {
|
|
186
|
-
await rename(sourcePath, targetPath);
|
|
187
|
-
return;
|
|
188
|
-
} catch (error) {
|
|
189
|
-
const code =
|
|
190
|
-
error && typeof error === "object" && "code" in error
|
|
191
|
-
? error.code
|
|
192
|
-
: undefined;
|
|
193
|
-
const isRetryable =
|
|
194
|
-
typeof code === "string" && FILE_RETRY_CODES.has(code);
|
|
195
|
-
if (!isRetryable || attempt === maxRetries - 1) {
|
|
196
|
-
throw error;
|
|
197
|
-
}
|
|
198
|
-
const delayMs =
|
|
199
|
-
baseDelayMs * 2 ** attempt + Math.floor(random() * jitterMs);
|
|
200
|
-
log(
|
|
201
|
-
`Retrying atomic rename (${attempt + 1}/${maxRetries}) code=${code ?? "unknown"} source=${sourcePath} target=${targetPath} delayMs=${delayMs}`,
|
|
202
|
-
);
|
|
203
|
-
await sleepImpl(delayMs);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { rename as fsRename } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
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
|
+
}
|
|
47
|
+
export const FILE_RETRY_CODES = new Set([
|
|
48
|
+
"EBUSY",
|
|
49
|
+
"EPERM",
|
|
50
|
+
"EAGAIN",
|
|
51
|
+
"ENOTEMPTY",
|
|
52
|
+
"EACCES",
|
|
53
|
+
]);
|
|
54
|
+
export const FILE_RETRY_MAX_ATTEMPTS = 6;
|
|
55
|
+
export const FILE_RETRY_BASE_DELAY_MS = 25;
|
|
56
|
+
export const FILE_RETRY_JITTER_MS = 20;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {NodeJS.Platform} [platform]
|
|
60
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
61
|
+
* @param {string} [home]
|
|
62
|
+
*/
|
|
63
|
+
export function resolveInstallPaths(
|
|
64
|
+
platform = process.platform,
|
|
65
|
+
env = process.env,
|
|
66
|
+
home = homedir(),
|
|
67
|
+
) {
|
|
68
|
+
const isWindows = platform === "win32";
|
|
69
|
+
const appData = (env.APPDATA ?? "").trim();
|
|
70
|
+
const localAppData = (env.LOCALAPPDATA ?? appData).trim();
|
|
71
|
+
const configBase = isWindows
|
|
72
|
+
? appData || join(home, "AppData", "Roaming")
|
|
73
|
+
: join(home, ".config");
|
|
74
|
+
const cacheBase = isWindows
|
|
75
|
+
? localAppData || join(home, "AppData", "Local")
|
|
76
|
+
: join(home, ".cache");
|
|
77
|
+
const configDir = join(configBase, "Codex");
|
|
78
|
+
const configPath = join(configDir, "Codex.json");
|
|
79
|
+
const cacheDir = join(cacheBase, "Codex");
|
|
80
|
+
return {
|
|
81
|
+
configDir,
|
|
82
|
+
configPath,
|
|
83
|
+
cacheDir,
|
|
84
|
+
cacheNodeModules: join(cacheDir, "node_modules", PLUGIN_NAME),
|
|
85
|
+
cacheBunLock: join(cacheDir, "bun.lock"),
|
|
86
|
+
cachePackageJson: join(cacheDir, "package.json"),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
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
|
+
|
|
99
|
+
/** @param {unknown} list */
|
|
100
|
+
export function normalizePluginList(list) {
|
|
101
|
+
const entries = Array.isArray(list) ? list.filter(Boolean) : [];
|
|
102
|
+
const filtered = entries.filter((entry) => {
|
|
103
|
+
if (typeof entry !== "string") return true;
|
|
104
|
+
return entry !== PLUGIN_NAME && !entry.startsWith(`${PLUGIN_NAME}@`);
|
|
105
|
+
});
|
|
106
|
+
const deduped = [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const entry of filtered) {
|
|
109
|
+
const key =
|
|
110
|
+
typeof entry === "string" ? `s:${entry}` : `j:${JSON.stringify(entry)}`;
|
|
111
|
+
if (seen.has(key)) continue;
|
|
112
|
+
seen.add(key);
|
|
113
|
+
deduped.push(entry);
|
|
114
|
+
}
|
|
115
|
+
return [...deduped, PLUGIN_NAME];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @param {number} ms */
|
|
119
|
+
function sleep(ms) {
|
|
120
|
+
// Keep this helper local so installer scripts remain standalone and do not depend on lib/.
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
setTimeout(resolve, ms);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @param {unknown} error */
|
|
127
|
+
function shouldRetryFileOperation(error) {
|
|
128
|
+
const fileError = /** @type {NodeJS.ErrnoException | undefined} */ (error);
|
|
129
|
+
return (
|
|
130
|
+
fileError instanceof Error &&
|
|
131
|
+
typeof fileError.code === "string" &&
|
|
132
|
+
FILE_RETRY_CODES.has(fileError.code)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** @template T @param {() => Promise<T>} operation */
|
|
137
|
+
export async function withFileOperationRetry(operation) {
|
|
138
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
139
|
+
try {
|
|
140
|
+
return await operation();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (
|
|
143
|
+
!shouldRetryFileOperation(error) ||
|
|
144
|
+
attempt >= FILE_RETRY_MAX_ATTEMPTS
|
|
145
|
+
) {
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const jitter = Math.floor(Math.random() * FILE_RETRY_JITTER_MS);
|
|
150
|
+
const delayMs = FILE_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1) + jitter;
|
|
151
|
+
await sleep(delayMs);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @param {string} sourcePath
|
|
158
|
+
* @param {string} targetPath
|
|
159
|
+
* @param {{
|
|
160
|
+
* rename?: (sourcePath: string, targetPath: string) => Promise<void>,
|
|
161
|
+
* log?: (message: string) => void,
|
|
162
|
+
* maxRetries?: number,
|
|
163
|
+
* baseDelayMs?: number,
|
|
164
|
+
* jitterMs?: number,
|
|
165
|
+
* random?: () => number,
|
|
166
|
+
* sleep?: (ms: number) => Promise<void>,
|
|
167
|
+
* }} [options]
|
|
168
|
+
*/
|
|
169
|
+
export async function renameWithRetry(sourcePath, targetPath, options = {}) {
|
|
170
|
+
const {
|
|
171
|
+
rename = fsRename,
|
|
172
|
+
log = () => {},
|
|
173
|
+
maxRetries = FILE_RETRY_MAX_ATTEMPTS,
|
|
174
|
+
baseDelayMs = FILE_RETRY_BASE_DELAY_MS,
|
|
175
|
+
jitterMs = FILE_RETRY_JITTER_MS,
|
|
176
|
+
random = Math.random,
|
|
177
|
+
sleep: sleepImpl = sleep,
|
|
178
|
+
} = options;
|
|
179
|
+
|
|
180
|
+
if (!Number.isInteger(maxRetries) || maxRetries < 1) {
|
|
181
|
+
throw new RangeError("maxRetries must be an integer >= 1");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (let attempt = 0; attempt < maxRetries; attempt += 1) {
|
|
185
|
+
try {
|
|
186
|
+
await rename(sourcePath, targetPath);
|
|
187
|
+
return;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const code =
|
|
190
|
+
error && typeof error === "object" && "code" in error
|
|
191
|
+
? error.code
|
|
192
|
+
: undefined;
|
|
193
|
+
const isRetryable =
|
|
194
|
+
typeof code === "string" && FILE_RETRY_CODES.has(code);
|
|
195
|
+
if (!isRetryable || attempt === maxRetries - 1) {
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
const delayMs =
|
|
199
|
+
baseDelayMs * 2 ** attempt + Math.floor(random() * jitterMs);
|
|
200
|
+
log(
|
|
201
|
+
`Retrying atomic rename (${attempt + 1}/${maxRetries}) code=${code ?? "unknown"} source=${sourcePath} target=${targetPath} delayMs=${delayMs}`,
|
|
202
|
+
);
|
|
203
|
+
await sleepImpl(delayMs);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
package/scripts/mcodex
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
monitor_interval="${MCODEX_MONITOR_INTERVAL:-5}"
|
|
5
|
+
live_accounts=0
|
|
6
|
+
tmux_history_limit="${MCODEX_TMUX_HISTORY_LIMIT:-50000}"
|
|
7
|
+
|
|
8
|
+
# Security: monitor_interval is embedded into `watch -n <n> ...` command strings
|
|
9
|
+
# that tmux hands to a shell. An attacker-controlled MCODEX_MONITOR_INTERVAL like
|
|
10
|
+
# "5; rm -rf ~" would otherwise execute. Require a positive integer (optionally
|
|
11
|
+
# with a fractional part for sub-second intervals) and fall back to the default
|
|
12
|
+
# otherwise, so no shell metacharacters can ever reach the command string.
|
|
13
|
+
if ! [[ "$monitor_interval" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
|
14
|
+
echo "mcodex: invalid MCODEX_MONITOR_INTERVAL '$monitor_interval'; using 5" >&2
|
|
15
|
+
monitor_interval=5
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Same hardening for the tmux scrollback history limit (used as a tmux option
|
|
19
|
+
# argument): must be a plain integer.
|
|
20
|
+
if ! [[ "$tmux_history_limit" =~ ^[0-9]+$ ]]; then
|
|
21
|
+
echo "mcodex: invalid MCODEX_TMUX_HISTORY_LIMIT '$tmux_history_limit'; using 50000" >&2
|
|
22
|
+
tmux_history_limit=50000
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
quote_args() {
|
|
26
|
+
local quoted=()
|
|
27
|
+
local arg
|
|
28
|
+
for arg in "$@"; do
|
|
29
|
+
printf -v arg '%q' "$arg"
|
|
30
|
+
quoted+=("$arg")
|
|
31
|
+
done
|
|
32
|
+
# Join with single spaces and NO trailing space (a trailing space would become
|
|
33
|
+
# part of the interpolated tmux command string).
|
|
34
|
+
local IFS=' '
|
|
35
|
+
printf '%s' "${quoted[*]}"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
require_watch() {
|
|
39
|
+
if ! command -v watch >/dev/null 2>&1; then
|
|
40
|
+
echo "mcodex: 'watch' is not installed; the live account monitor requires it (install procps / procps-ng)." >&2
|
|
41
|
+
return 1
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
run_monitor() {
|
|
46
|
+
require_watch || return 1
|
|
47
|
+
watch -n "$monitor_interval" 'codex-multi-auth list'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
configure_tmux_scrollback() {
|
|
51
|
+
local target="${1:-}"
|
|
52
|
+
local target_args=()
|
|
53
|
+
if [[ -n "$target" ]]; then
|
|
54
|
+
target_args=(-t "$target")
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
tmux set-option "${target_args[@]}" mouse on >/dev/null 2>&1
|
|
58
|
+
tmux set-option "${target_args[@]}" history-limit "$tmux_history_limit" >/dev/null 2>&1
|
|
59
|
+
tmux bind-key -T root WheelUpPane copy-mode -e >/dev/null 2>&1
|
|
60
|
+
tmux bind-key -T copy-mode WheelUpPane send-keys -X scroll-up >/dev/null 2>&1
|
|
61
|
+
tmux bind-key -T copy-mode WheelDownPane send-keys -X scroll-down >/dev/null 2>&1
|
|
62
|
+
tmux bind-key -T copy-mode-vi WheelUpPane send-keys -X scroll-up >/dev/null 2>&1
|
|
63
|
+
tmux bind-key -T copy-mode-vi WheelDownPane send-keys -X scroll-down >/dev/null 2>&1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if [[ "${1:-}" == "--monitor" ]]; then
|
|
67
|
+
# --monitor takes no extra args; the live account list is fixed. Propagate the
|
|
68
|
+
# watch-availability check's exit code instead of masking it with `exit 0`.
|
|
69
|
+
run_monitor
|
|
70
|
+
exit $?
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if [[ "${1:-}" == "--tmux" || "${1:-}" == "-t" ]]; then
|
|
74
|
+
shift
|
|
75
|
+
if [[ "${1:-}" == "--live-accounts" ]]; then
|
|
76
|
+
live_accounts=1
|
|
77
|
+
shift
|
|
78
|
+
fi
|
|
79
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
80
|
+
echo "mcodex: tmux is not installed; launching without tmux" >&2
|
|
81
|
+
exec codex-multi-auth-codex "$@"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
if [[ -n "${TMUX:-}" ]]; then
|
|
85
|
+
configure_tmux_scrollback
|
|
86
|
+
if [[ "$live_accounts" == "1" ]] && require_watch; then
|
|
87
|
+
tmux split-window -h "watch -n $monitor_interval 'codex-multi-auth list'"
|
|
88
|
+
fi
|
|
89
|
+
exec codex-multi-auth-codex "$@"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
session="${MCODEX_TMUX_SESSION:-mcodex}"
|
|
93
|
+
suffix="$(date +%H%M%S)"
|
|
94
|
+
if tmux has-session -t "$session" 2>/dev/null; then
|
|
95
|
+
session="${session}-${suffix}"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# Build the inner shell-command as a single string. quote_args runs each arg
|
|
99
|
+
# through printf %q, so every token is already shell-safe (embedded quotes,
|
|
100
|
+
# spaces, $, backticks are escaped); the surrounding expansion is not
|
|
101
|
+
# re-evaluated, so the command tmux receives is exactly this string.
|
|
102
|
+
cmd="codex-multi-auth-codex"
|
|
103
|
+
args="$(quote_args "$@")"
|
|
104
|
+
if [[ -n "$args" ]]; then
|
|
105
|
+
cmd="$cmd $args"
|
|
106
|
+
fi
|
|
107
|
+
tmux new-session -d -s "$session" -n codex "$cmd"
|
|
108
|
+
configure_tmux_scrollback "$session"
|
|
109
|
+
if [[ "$live_accounts" == "1" ]] && require_watch; then
|
|
110
|
+
tmux split-window -h -t "$session:0" "watch -n $monitor_interval 'codex-multi-auth list'"
|
|
111
|
+
fi
|
|
112
|
+
tmux select-pane -t "$session:0.0"
|
|
113
|
+
tmux attach-session -t "$session"
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
exec codex-multi-auth-codex "$@"
|