@tokensrc/codex 1.14.7 → 1.14.9
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 +4 -5
- package/assets/shortcut-bootstrap.cjs +453 -0
- package/dist/command-directory.js +2 -2
- package/dist/command-directory.js.map +1 -1
- package/dist/commands.js +67 -16
- package/dist/commands.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/managed-credential-v3-flow.d.ts +8 -0
- package/dist/managed-credential-v3-flow.js +72 -27
- package/dist/managed-credential-v3-flow.js.map +1 -1
- package/dist/managed-credential-v3-journal.d.ts +6 -43
- package/dist/managed-credential-v3-journal.js +166 -20
- package/dist/managed-credential-v3-journal.js.map +1 -1
- package/dist/shortcuts.d.ts +11 -1
- package/dist/shortcuts.js +243 -35
- package/dist/shortcuts.js.map +1 -1
- package/dist/types.d.ts +0 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -127,8 +127,7 @@ const codex = await createCodexModule({
|
|
|
127
127
|
},
|
|
128
128
|
persistence: { configDirectory: applicationConfigDirectory },
|
|
129
129
|
desktopApp: {
|
|
130
|
-
inspect: inspectHostDesktopApp
|
|
131
|
-
prepare: prepareHostDesktopApp
|
|
130
|
+
inspect: inspectHostDesktopApp
|
|
132
131
|
}
|
|
133
132
|
});
|
|
134
133
|
|
|
@@ -138,9 +137,9 @@ const remote = await codex.status.loadRemote();
|
|
|
138
137
|
```
|
|
139
138
|
|
|
140
139
|
The optional `desktopApp.inspect` hook adds host-provided desktop version details
|
|
141
|
-
to local and `status` output.
|
|
142
|
-
|
|
143
|
-
|
|
140
|
+
to local and `status` output. The package never downloads, installs, replaces,
|
|
141
|
+
upgrades, or downgrades a desktop application; `codex app` only prepares the
|
|
142
|
+
selected official credentials and launches the existing official application.
|
|
144
143
|
|
|
145
144
|
Hosts may instead inject a static access token or standalone OIDC settings, and
|
|
146
145
|
can customize branding and launcher behavior. Injected access tokens and provider
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// NUMA_CODEX_SHORTCUT_BOOTSTRAP
|
|
5
|
+
// This file is copied beside managed shortcuts. Keep it dependency-free.
|
|
6
|
+
|
|
7
|
+
const fs = require("node:fs");
|
|
8
|
+
const path = require("node:path");
|
|
9
|
+
const { spawnSync } = require("node:child_process");
|
|
10
|
+
|
|
11
|
+
const STATE_SCHEMA_VERSION = 1;
|
|
12
|
+
const SUCCESS_CHECK_INTERVAL_MS = 0;
|
|
13
|
+
const FAILURE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
|
|
14
|
+
const METADATA_TIMEOUT_MS = 8_000;
|
|
15
|
+
const INSTALL_TIMEOUT_MS = 120_000;
|
|
16
|
+
const LOCK_STALE_MS = 5 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
function fail(message) {
|
|
19
|
+
process.stderr.write(`Numa shortcut: ${message}\n`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function warn(message) {
|
|
24
|
+
process.stderr.write(`Numa shortcut update skipped: ${message}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function plainObject(value) {
|
|
28
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function absoluteFile(value) {
|
|
32
|
+
return typeof value === "string" && value.length > 0 && path.isAbsolute(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function safeRegistry(value) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = new URL(value);
|
|
38
|
+
if (parsed.username || parsed.password || parsed.hash || parsed.search) return false;
|
|
39
|
+
if (parsed.protocol === "https:") return true;
|
|
40
|
+
return parsed.protocol === "http:" && ["localhost", "127.0.0.1", "::1"].includes(parsed.hostname);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safePackageName(value) {
|
|
47
|
+
return typeof value === "string"
|
|
48
|
+
&& /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u.test(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function safeCommandName(value) {
|
|
52
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseVersion(value) {
|
|
56
|
+
if (typeof value !== "string") return undefined;
|
|
57
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u.exec(value);
|
|
58
|
+
if (!match) return undefined;
|
|
59
|
+
return {
|
|
60
|
+
raw: value,
|
|
61
|
+
major: Number(match[1]),
|
|
62
|
+
minor: Number(match[2]),
|
|
63
|
+
patch: Number(match[3]),
|
|
64
|
+
prerelease: match[4] ? match[4].split(".") : []
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function comparePrerelease(left, right) {
|
|
69
|
+
if (left.length === 0 || right.length === 0) return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
|
|
70
|
+
const length = Math.max(left.length, right.length);
|
|
71
|
+
for (let index = 0; index < length; index += 1) {
|
|
72
|
+
if (left[index] === undefined) return -1;
|
|
73
|
+
if (right[index] === undefined) return 1;
|
|
74
|
+
const leftNumber = /^\d+$/u.test(left[index]) ? Number(left[index]) : undefined;
|
|
75
|
+
const rightNumber = /^\d+$/u.test(right[index]) ? Number(right[index]) : undefined;
|
|
76
|
+
if (leftNumber !== undefined && rightNumber !== undefined && leftNumber !== rightNumber) {
|
|
77
|
+
return leftNumber < rightNumber ? -1 : 1;
|
|
78
|
+
}
|
|
79
|
+
if (leftNumber !== undefined && rightNumber === undefined) return -1;
|
|
80
|
+
if (leftNumber === undefined && rightNumber !== undefined) return 1;
|
|
81
|
+
if (left[index] !== right[index]) return left[index] < right[index] ? -1 : 1;
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function compareVersions(left, right) {
|
|
87
|
+
for (const field of ["major", "minor", "patch"]) {
|
|
88
|
+
if (left[field] !== right[field]) return left[field] < right[field] ? -1 : 1;
|
|
89
|
+
}
|
|
90
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readJson(file) {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeJsonAtomic(file, value) {
|
|
102
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
103
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
104
|
+
try {
|
|
105
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
106
|
+
if (process.platform === "win32") fs.rmSync(file, { force: true });
|
|
107
|
+
fs.renameSync(temporary, file);
|
|
108
|
+
} finally {
|
|
109
|
+
fs.rmSync(temporary, { force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function loadConfig(file) {
|
|
114
|
+
const value = readJson(file);
|
|
115
|
+
if (!plainObject(value)
|
|
116
|
+
|| value.schemaVersion !== 1
|
|
117
|
+
|| typeof value.marker !== "string"
|
|
118
|
+
|| !safePackageName(value.packageName)
|
|
119
|
+
|| !safeCommandName(value.commandName)
|
|
120
|
+
|| !safeRegistry(value.registryUrl)
|
|
121
|
+
|| !parseVersion(value.installedVersion)
|
|
122
|
+
|| !absoluteFile(value.nodeExecutable)
|
|
123
|
+
|| (value.npmExecutable !== null && !absoluteFile(value.npmExecutable))
|
|
124
|
+
|| (value.fallbackEntrypoint !== null && !absoluteFile(value.fallbackEntrypoint))
|
|
125
|
+
|| !absoluteFile(value.cacheDirectory)) {
|
|
126
|
+
throw new Error(`invalid managed launcher configuration: ${file}`);
|
|
127
|
+
}
|
|
128
|
+
if (!fs.existsSync(value.nodeExecutable)) {
|
|
129
|
+
throw new Error("trusted Node.js runtime captured during shortcut installation is unavailable; reinstall the shortcut");
|
|
130
|
+
}
|
|
131
|
+
const configuredNode = fs.realpathSync(value.nodeExecutable);
|
|
132
|
+
const launchedNode = fs.realpathSync(process.execPath);
|
|
133
|
+
if (launchedNode !== configuredNode) {
|
|
134
|
+
throw new Error("managed shortcut was invoked by an unexpected Node.js runtime; reinstall the shortcut from a trusted terminal");
|
|
135
|
+
}
|
|
136
|
+
value.nodeExecutable = configuredNode;
|
|
137
|
+
value.npmExecutable = verifiedNpmCli(value.npmExecutable);
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function verifiedNpmCli(file) {
|
|
142
|
+
if (!file || !fs.existsSync(file)) return null;
|
|
143
|
+
let actual;
|
|
144
|
+
try {
|
|
145
|
+
actual = fs.realpathSync(file);
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
if (path.basename(actual).toLowerCase() !== "npm-cli.js") return null;
|
|
150
|
+
let current = path.dirname(actual);
|
|
151
|
+
const filesystemRoot = path.parse(current).root;
|
|
152
|
+
while (current !== filesystemRoot) {
|
|
153
|
+
const manifest = readJson(path.join(current, "package.json"));
|
|
154
|
+
if (manifest !== undefined) {
|
|
155
|
+
if (!plainObject(manifest) || manifest.name !== "npm") return null;
|
|
156
|
+
const declared = typeof manifest.bin === "string"
|
|
157
|
+
? manifest.bin
|
|
158
|
+
: plainObject(manifest.bin) && typeof manifest.bin.npm === "string"
|
|
159
|
+
? manifest.bin.npm
|
|
160
|
+
: undefined;
|
|
161
|
+
if (!declared || path.isAbsolute(declared)) return null;
|
|
162
|
+
try {
|
|
163
|
+
return fs.realpathSync(path.resolve(current, declared)) === actual ? actual : null;
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
current = path.dirname(current);
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function packageRoot(cacheDirectory, packageName, version) {
|
|
174
|
+
return path.join(cacheDirectory, "versions", version, "node_modules", ...packageName.split("/"));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function inside(parent, child) {
|
|
178
|
+
const relative = path.relative(parent, child);
|
|
179
|
+
return relative !== "" && !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function packageEntrypoint(root, packageName, version, commandName) {
|
|
183
|
+
const manifest = readJson(path.join(root, "package.json"));
|
|
184
|
+
if (!plainObject(manifest) || manifest.name !== packageName || manifest.version !== version) return undefined;
|
|
185
|
+
const declared = typeof manifest.bin === "string"
|
|
186
|
+
? manifest.bin
|
|
187
|
+
: plainObject(manifest.bin) && typeof manifest.bin[commandName] === "string"
|
|
188
|
+
? manifest.bin[commandName]
|
|
189
|
+
: undefined;
|
|
190
|
+
if (!declared || path.isAbsolute(declared)) return undefined;
|
|
191
|
+
const entrypoint = path.resolve(root, declared);
|
|
192
|
+
if (!inside(root, entrypoint) || !fs.existsSync(entrypoint)) return undefined;
|
|
193
|
+
try {
|
|
194
|
+
const realRoot = fs.realpathSync(root);
|
|
195
|
+
const realEntrypoint = fs.realpathSync(entrypoint);
|
|
196
|
+
return inside(realRoot, realEntrypoint) ? realEntrypoint : undefined;
|
|
197
|
+
} catch {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function cachedEntrypoint(config, version) {
|
|
203
|
+
return packageEntrypoint(
|
|
204
|
+
packageRoot(config.cacheDirectory, config.packageName, version),
|
|
205
|
+
config.packageName,
|
|
206
|
+
version,
|
|
207
|
+
config.commandName
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function fallbackEntrypoint(config) {
|
|
212
|
+
if (!config.fallbackEntrypoint || !fs.existsSync(config.fallbackEntrypoint)) return undefined;
|
|
213
|
+
let current = path.dirname(config.fallbackEntrypoint);
|
|
214
|
+
const filesystemRoot = path.parse(current).root;
|
|
215
|
+
while (current !== filesystemRoot) {
|
|
216
|
+
const manifest = readJson(path.join(current, "package.json"));
|
|
217
|
+
if (plainObject(manifest) && manifest.name === config.packageName && manifest.version === config.installedVersion) {
|
|
218
|
+
const declared = typeof manifest.bin === "string"
|
|
219
|
+
? manifest.bin
|
|
220
|
+
: plainObject(manifest.bin) && typeof manifest.bin[config.commandName] === "string"
|
|
221
|
+
? manifest.bin[config.commandName]
|
|
222
|
+
: undefined;
|
|
223
|
+
if (!declared) return undefined;
|
|
224
|
+
try {
|
|
225
|
+
return fs.realpathSync(path.resolve(current, declared)) === fs.realpathSync(config.fallbackEntrypoint)
|
|
226
|
+
? fs.realpathSync(config.fallbackEntrypoint)
|
|
227
|
+
: undefined;
|
|
228
|
+
} catch {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
current = path.dirname(current);
|
|
233
|
+
}
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function npmInvocation(config, args, timeout) {
|
|
238
|
+
if (!config.npmExecutable || !fs.existsSync(config.npmExecutable)) {
|
|
239
|
+
return { status: null, error: new Error("npm executable captured during shortcut installation is unavailable") };
|
|
240
|
+
}
|
|
241
|
+
const javascriptCli = /\.(?:c?js|mjs)$/iu.test(config.npmExecutable);
|
|
242
|
+
const command = javascriptCli ? config.nodeExecutable : config.npmExecutable;
|
|
243
|
+
const commandArgs = javascriptCli ? [config.npmExecutable, ...args] : args;
|
|
244
|
+
return spawnSync(command, commandArgs, {
|
|
245
|
+
encoding: "utf8",
|
|
246
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
247
|
+
timeout,
|
|
248
|
+
windowsHide: true,
|
|
249
|
+
env: {
|
|
250
|
+
...process.env,
|
|
251
|
+
PATH: `${path.dirname(config.nodeExecutable)}${path.delimiter}${process.env.PATH ?? ""}`,
|
|
252
|
+
npm_config_audit: "false",
|
|
253
|
+
npm_config_fund: "false",
|
|
254
|
+
npm_config_update_notifier: "false"
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function latestVersion(config) {
|
|
260
|
+
const result = npmInvocation(config, [
|
|
261
|
+
"view",
|
|
262
|
+
`${config.packageName}@latest`,
|
|
263
|
+
"version",
|
|
264
|
+
"--json",
|
|
265
|
+
`--registry=${config.registryUrl}`
|
|
266
|
+
], METADATA_TIMEOUT_MS);
|
|
267
|
+
if (result.error || result.status !== 0) {
|
|
268
|
+
throw result.error ?? new Error((result.stderr || "npm view failed").trim());
|
|
269
|
+
}
|
|
270
|
+
const value = JSON.parse(result.stdout);
|
|
271
|
+
const parsed = parseVersion(value);
|
|
272
|
+
if (!parsed) throw new Error("registry latest tag did not resolve to an exact semantic version");
|
|
273
|
+
return parsed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function installExact(config, version) {
|
|
277
|
+
const finalDirectory = path.join(config.cacheDirectory, "versions", version);
|
|
278
|
+
const existing = cachedEntrypoint(config, version);
|
|
279
|
+
if (existing) return existing;
|
|
280
|
+
const staging = path.join(config.cacheDirectory, "staging", `${version}-${process.pid}-${Date.now()}`);
|
|
281
|
+
fs.mkdirSync(staging, { recursive: true, mode: 0o700 });
|
|
282
|
+
try {
|
|
283
|
+
const result = npmInvocation(config, [
|
|
284
|
+
"install",
|
|
285
|
+
"--ignore-scripts",
|
|
286
|
+
"--no-audit",
|
|
287
|
+
"--no-fund",
|
|
288
|
+
"--omit=dev",
|
|
289
|
+
"--no-package-lock",
|
|
290
|
+
"--no-save",
|
|
291
|
+
`--prefix=${staging}`,
|
|
292
|
+
`--registry=${config.registryUrl}`,
|
|
293
|
+
`${config.packageName}@${version}`
|
|
294
|
+
], INSTALL_TIMEOUT_MS);
|
|
295
|
+
if (result.error || result.status !== 0) {
|
|
296
|
+
throw result.error ?? new Error((result.stderr || "npm install failed").trim());
|
|
297
|
+
}
|
|
298
|
+
const stagedRoot = path.join(staging, "node_modules", ...config.packageName.split("/"));
|
|
299
|
+
if (!packageEntrypoint(stagedRoot, config.packageName, version, config.commandName)) {
|
|
300
|
+
throw new Error("downloaded package identity, version, or executable does not match the requested release");
|
|
301
|
+
}
|
|
302
|
+
fs.mkdirSync(path.dirname(finalDirectory), { recursive: true, mode: 0o700 });
|
|
303
|
+
if (fs.existsSync(finalDirectory)) fs.rmSync(finalDirectory, { recursive: true, force: true });
|
|
304
|
+
fs.renameSync(staging, finalDirectory);
|
|
305
|
+
const verified = cachedEntrypoint(config, version);
|
|
306
|
+
if (!verified) throw new Error("installed release failed exact readback verification");
|
|
307
|
+
return verified;
|
|
308
|
+
} finally {
|
|
309
|
+
fs.rmSync(staging, { recursive: true, force: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function readState(config) {
|
|
314
|
+
const value = readJson(path.join(config.cacheDirectory, "state.json"));
|
|
315
|
+
if (!plainObject(value)
|
|
316
|
+
|| value.schemaVersion !== STATE_SCHEMA_VERSION
|
|
317
|
+
|| value.packageName !== config.packageName
|
|
318
|
+
|| value.registryUrl !== config.registryUrl
|
|
319
|
+
|| (value.knownGoodVersion !== null && !parseVersion(value.knownGoodVersion))
|
|
320
|
+
|| !Number.isSafeInteger(value.nextCheckAt)
|
|
321
|
+
|| value.nextCheckAt < 0) {
|
|
322
|
+
return {
|
|
323
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
324
|
+
packageName: config.packageName,
|
|
325
|
+
registryUrl: config.registryUrl,
|
|
326
|
+
knownGoodVersion: null,
|
|
327
|
+
nextCheckAt: 0
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function writeState(config, state) {
|
|
334
|
+
writeJsonAtomic(path.join(config.cacheDirectory, "state.json"), state);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function acquireLock(config) {
|
|
338
|
+
const lock = path.join(config.cacheDirectory, "update.lock");
|
|
339
|
+
fs.mkdirSync(config.cacheDirectory, { recursive: true, mode: 0o700 });
|
|
340
|
+
try {
|
|
341
|
+
const descriptor = fs.openSync(lock, "wx", 0o600);
|
|
342
|
+
fs.writeFileSync(descriptor, `${process.pid}\n`);
|
|
343
|
+
return () => {
|
|
344
|
+
try { fs.closeSync(descriptor); } catch { /* already closed */ }
|
|
345
|
+
fs.rmSync(lock, { force: true });
|
|
346
|
+
};
|
|
347
|
+
} catch (error) {
|
|
348
|
+
if (error && error.code === "EEXIST") {
|
|
349
|
+
try {
|
|
350
|
+
if (Date.now() - fs.statSync(lock).mtimeMs > LOCK_STALE_MS) {
|
|
351
|
+
fs.rmSync(lock, { force: true });
|
|
352
|
+
return acquireLock(config);
|
|
353
|
+
}
|
|
354
|
+
} catch { /* another launcher owns or removed the lock */ }
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function refreshKnownGood(config, state) {
|
|
362
|
+
const release = acquireLock(config);
|
|
363
|
+
if (!release) return state;
|
|
364
|
+
try {
|
|
365
|
+
const current = readState(config);
|
|
366
|
+
if (Date.now() < current.nextCheckAt) return current;
|
|
367
|
+
const installed = parseVersion(config.installedVersion);
|
|
368
|
+
const known = current.knownGoodVersion ? parseVersion(current.knownGoodVersion) : undefined;
|
|
369
|
+
const floor = known && compareVersions(known, installed) > 0 ? known : installed;
|
|
370
|
+
try {
|
|
371
|
+
const latest = latestVersion(config);
|
|
372
|
+
if (compareVersions(latest, floor) < 0) {
|
|
373
|
+
warn(`registry latest ${latest.raw} is older than trusted ${floor.raw}; keeping the trusted release`);
|
|
374
|
+
const next = { ...current, nextCheckAt: Date.now() + SUCCESS_CHECK_INTERVAL_MS };
|
|
375
|
+
writeState(config, next);
|
|
376
|
+
return next;
|
|
377
|
+
}
|
|
378
|
+
installExact(config, latest.raw);
|
|
379
|
+
const next = {
|
|
380
|
+
...current,
|
|
381
|
+
knownGoodVersion: latest.raw,
|
|
382
|
+
nextCheckAt: Date.now() + SUCCESS_CHECK_INTERVAL_MS
|
|
383
|
+
};
|
|
384
|
+
writeState(config, next);
|
|
385
|
+
return next;
|
|
386
|
+
} catch (error) {
|
|
387
|
+
warn(`${error instanceof Error ? error.message : String(error)}; using the last known-good local release`);
|
|
388
|
+
const next = { ...current, nextCheckAt: Date.now() + FAILURE_CHECK_INTERVAL_MS };
|
|
389
|
+
writeState(config, next);
|
|
390
|
+
return next;
|
|
391
|
+
}
|
|
392
|
+
} finally {
|
|
393
|
+
release();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function run(config, args) {
|
|
398
|
+
const state = refreshKnownGood(config, readState(config));
|
|
399
|
+
const installed = parseVersion(config.installedVersion);
|
|
400
|
+
const known = state.knownGoodVersion ? parseVersion(state.knownGoodVersion) : undefined;
|
|
401
|
+
const entrypoint = known && compareVersions(known, installed) >= 0
|
|
402
|
+
? cachedEntrypoint(config, state.knownGoodVersion)
|
|
403
|
+
: undefined;
|
|
404
|
+
const fallbackAllowed = !known || compareVersions(known, installed) <= 0;
|
|
405
|
+
const selected = entrypoint ?? (fallbackAllowed ? fallbackEntrypoint(config) : undefined);
|
|
406
|
+
if (!selected) {
|
|
407
|
+
fail(known && compareVersions(known, installed) > 0
|
|
408
|
+
? `trusted Numa ${known.raw} is unavailable or corrupt; refusing to downgrade to ${installed.raw}. Restore network access or reinstall the shortcut from a trusted terminal.`
|
|
409
|
+
: "no verified local Numa release is available. Re-run `npx -y --prefer-online @numa-tech/numa@latest codex shortcut install` from a terminal with Node and npm.");
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const child = spawnSync(config.nodeExecutable, [selected, ...args], {
|
|
413
|
+
stdio: "inherit",
|
|
414
|
+
windowsHide: false,
|
|
415
|
+
env: {
|
|
416
|
+
...process.env,
|
|
417
|
+
PATH: `${path.dirname(config.nodeExecutable)}${path.delimiter}${process.env.PATH ?? ""}`
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
if (child.error) {
|
|
421
|
+
fail(`failed to start verified Numa release: ${child.error.message}`);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
process.exitCode = child.status === null ? 1 : child.status;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function main(argv = process.argv.slice(2)) {
|
|
428
|
+
const nodeVersion = /^(\d+)\.(\d+)\./u.exec(process.versions.node);
|
|
429
|
+
if (!nodeVersion || Number(nodeVersion[1]) < 20
|
|
430
|
+
|| (Number(nodeVersion[1]) === 20 && Number(nodeVersion[2]) < 12)) {
|
|
431
|
+
fail(`Node.js 20.12 or newer is required; detected ${process.versions.node}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (argv.length < 1 || !absoluteFile(argv[0])) {
|
|
435
|
+
fail("managed launcher configuration path is missing or not absolute");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
try {
|
|
439
|
+
run(loadConfig(argv[0]), argv.slice(1));
|
|
440
|
+
} catch (error) {
|
|
441
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (require.main === module) main();
|
|
446
|
+
|
|
447
|
+
module.exports = {
|
|
448
|
+
compareVersions,
|
|
449
|
+
loadConfig,
|
|
450
|
+
main,
|
|
451
|
+
parseVersion,
|
|
452
|
+
safeRegistry
|
|
453
|
+
};
|
|
@@ -6,8 +6,8 @@ const definitions = [
|
|
|
6
6
|
{ name: "upload", description: "Upload the legacy v1 local account pool", command: "codex upload [-u|--server <url>]", summary: "仅用于 v1 兼容账号批量导入;新账号统一运行 numa codex login 进入 v4 RT 池", access: "authenticated", examples: ["numa codex upload --storage ~/.codex/multi-auth/openai-codex-accounts.json"] },
|
|
7
7
|
{ name: "use", description: "Select an account and install official Codex credentials", command: "codex use [account]", summary: "选择服务端账号并写入官方 ChatGPT auth.json", access: "authenticated", examples: ["numa codex use acct_xxx"] },
|
|
8
8
|
{ name: "run", description: "Launch the official Codex CLI", command: "codex run [-s|--select-account|-a|--account <id>] [-u|--server <url>] [--] [codexArgs...]", summary: "选择或复用 ChatGPT 账号后启动官方 Codex;模型流量不经过宿主", access: "authenticated", examples: ["numa codex run -s", "numa codex run --account acct_xxx", "numa codex run -- exec \"完成长任务\""] },
|
|
9
|
-
{ name: "app", description: "Launch the official Codex desktop app", command: "codex app [workspace] [-s|--select-account|-a|--account <id>] [-u|--server <url>]", summary: "
|
|
10
|
-
{ name: "shortcut", description: "Install or remove launcher shortcuts", command: "codex shortcut [install|remove] [-t|--target run|app|both] [-s|--select-account] [--no-cli-wrapper]", summary: "安装或删除
|
|
9
|
+
{ name: "app", description: "Launch the official Codex desktop app", command: "codex app [workspace] [-s|--select-account|-a|--account <id>] [-u|--server <url>]", summary: "准备官方 ChatGPT 登录并启动现有 macOS/Windows Codex 桌面 App;不安装或改动App版本", access: "authenticated", examples: ["numa codex app . -s", "numa codex app . --account acct_xxx"] },
|
|
10
|
+
{ name: "shortcut", description: "Install or remove launcher shortcuts", command: "codex shortcut [install|remove] [-t|--target run|app|both] [-s|--select-account] [--no-cli-wrapper]", summary: "安装或删除macOS/Windows/Linux的原子自更新Numa launcher、Codex快捷命令与桌面入口", access: "public", examples: ["numa codex shortcut install", "numa codex shortcut install --target app --workspace .", "numa codex shortcut remove"] },
|
|
11
11
|
{ name: "auth-agent", description: "Continuously renew official Codex credentials", command: "codex auth-agent [account]", summary: "前台刷新短效 token 到官方 Codex 登录并输出完整状态日志", access: "authenticated", examples: ["numa codex auth-agent acct_xxx --replace-login", "numa codex auth-agent --replace-login --once"] },
|
|
12
12
|
{ name: "admin", description: "Manage server-side Codex accounts and assignments", command: "codex admin <accounts|assignments>", summary: "管理员查看账号与凭据版本、触发刷新,并管理 Keycloak 用户账号绑定", access: "authenticated", examples: ["numa codex admin accounts list", "numa codex admin accounts credential-versions acct_xxx", "numa codex admin accounts refresh acct_xxx", "numa codex admin assignments list", "numa codex admin assignments bind acct_xxx user-subject --default", "numa codex admin assignments unbind assignment_xxx"] },
|
|
13
13
|
{ name: "status", description: "Show selection, desktop app, and official credential status", command: "codex status", summary: "查看当前账号选择、ChatGPT Desktop 版本与官方 auth.json 状态", access: "public", examples: ["numa codex status --json"] },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-directory.js","sourceRoot":"","sources":["../src/command-directory.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,GAAsC;IACrD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,wCAAwC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,sBAAsB,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE;IACjM,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,oEAAoE,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,2DAA2D,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,kBAAkB,EAAE,+CAA+C,EAAE,iFAAiF,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE;IACjZ,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,wCAAwC,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,uBAAuB,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE;IACpM,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,+BAA+B,EAAE,OAAO,EAAE,gCAAgC,EAAE,OAAO,EAAE,qCAAqC,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,uDAAuD,CAAC,EAAE;IACvP,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE,OAAO,EAAE,kCAAkC,EAAE,OAAO,EAAE,qDAAqD,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,4EAA4E,CAAC,EAAE;IAC1S,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,0DAA0D,EAAE,OAAO,EAAE,qBAAqB,EAAE,OAAO,EAAE,gCAAgC,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,yBAAyB,CAAC,EAAE;IACnO,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,+BAA+B,EAAE,OAAO,EAAE,2FAA2F,EAAE,OAAO,EAAE,uCAAuC,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,mBAAmB,EAAE,mCAAmC,EAAE,kCAAkC,CAAC,EAAE;IACxV,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,uCAAuC,EAAE,OAAO,EAAE,mFAAmF,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"command-directory.js","sourceRoot":"","sources":["../src/command-directory.ts"],"names":[],"mappings":"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"}
|
package/dist/commands.js
CHANGED
|
@@ -12,7 +12,7 @@ import { cleanupLegacyCodexProvider } from "./legacy-cleanup.js";
|
|
|
12
12
|
import { executeCodexEnrollmentLogin } from "./enrollment-flow.js";
|
|
13
13
|
import { executeCodexMigrationLoginBatch } from "./migration-login-batch.js";
|
|
14
14
|
import { executeCodexShortLivedCredentialDelivery } from "./short-lived-delivery-flow.js";
|
|
15
|
-
import { executeCodexTokenPoolV4Delivery, reportManagedCodexV3Credentials } from "./managed-credential-v3-flow.js";
|
|
15
|
+
import { executeCodexTokenPoolV4Delivery, reconcilePendingManagedCodexV3Operation, reportManagedCodexV3Credentials } from "./managed-credential-v3-flow.js";
|
|
16
16
|
import { resolveManagedCodexV3AssignmentTarget } from "./managed-credential-v3-assignment.js";
|
|
17
17
|
import { executeCodexTokenPoolV4Login } from "./token-pool-v4-upload-flow.js";
|
|
18
18
|
import { readManagedCodexV3State } from "./managed-credential-v3.js";
|
|
@@ -168,7 +168,7 @@ async function loadAccountData(deps, options = {}) {
|
|
|
168
168
|
selectedAccountId: previous?.account.id ?? response.defaultAccountId ?? undefined
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
-
async function chooseAccount(deps, requested, options = {}) {
|
|
171
|
+
async function chooseAccount(deps, requested, options = {}, persistSelection = true) {
|
|
172
172
|
const data = await loadAccountData(deps, options);
|
|
173
173
|
const { response, selectedAccountId } = data;
|
|
174
174
|
const previous = await loadCodexSelection(deps.config.persistence.configDirectory);
|
|
@@ -193,7 +193,7 @@ async function chooseAccount(deps, requested, options = {}) {
|
|
|
193
193
|
? previous.account.providerAccountId
|
|
194
194
|
: undefined;
|
|
195
195
|
const v3Assignment = data.v3Assignments?.items.find((item) => item.managedAccountId === account.id && item.assignmentId === account.accountId);
|
|
196
|
-
const
|
|
196
|
+
const selectionInput = {
|
|
197
197
|
serviceUrl: options.server ?? deps.config.connection.serverUrl,
|
|
198
198
|
allowInsecurePoc: Boolean(options.insecurePoc || options.insecureHttp || deps.config.connection.allowInsecureHttp),
|
|
199
199
|
account: v3Assignment ? {
|
|
@@ -220,9 +220,24 @@ async function chooseAccount(deps, requested, options = {}) {
|
|
|
220
220
|
label: accountLabel,
|
|
221
221
|
distributionMode: "SCHEMA_V1"
|
|
222
222
|
}
|
|
223
|
-
}
|
|
223
|
+
};
|
|
224
|
+
// V4 callers stage the selection in memory and persist it only after the
|
|
225
|
+
// official auth.json has been installed and the delivery has been ACKed.
|
|
226
|
+
// This keeps a failed account switch from pointing selection at credentials
|
|
227
|
+
// that were never installed.
|
|
228
|
+
const selection = persistSelection
|
|
229
|
+
? await saveCodexSelection(deps.config.persistence.configDirectory, selectionInput)
|
|
230
|
+
: { schemaVersion: 1, ...selectionInput, updatedAt: new Date().toISOString() };
|
|
224
231
|
return { account, selection };
|
|
225
232
|
}
|
|
233
|
+
async function persistInstalledSelection(deps, installed) {
|
|
234
|
+
const selection = await saveCodexSelection(deps.config.persistence.configDirectory, {
|
|
235
|
+
serviceUrl: installed.selection.serviceUrl,
|
|
236
|
+
allowInsecurePoc: installed.selection.allowInsecurePoc,
|
|
237
|
+
account: installed.selection.account
|
|
238
|
+
});
|
|
239
|
+
return { ...installed, selection };
|
|
240
|
+
}
|
|
226
241
|
async function installCredentials(deps, selection, service) {
|
|
227
242
|
const client = deps.clientFor(service);
|
|
228
243
|
await cleanupLegacyCodexProvider(codexConfigPathForEnv(deps.env));
|
|
@@ -232,6 +247,13 @@ async function installCredentials(deps, selection, service) {
|
|
|
232
247
|
|| !selection.account.providerAccountId) {
|
|
233
248
|
throw new CodexServiceError("The selected account is not a v4 token-pool assignment.", "codex_assignment_not_found", 404);
|
|
234
249
|
}
|
|
250
|
+
await reconcilePendingManagedCodexV3Operation({
|
|
251
|
+
client,
|
|
252
|
+
tenantId: deps.config.connection.tenantId,
|
|
253
|
+
env: deps.env
|
|
254
|
+
});
|
|
255
|
+
// Recovery may ACK or renew a previous delivery and therefore advance the
|
|
256
|
+
// Server assignment tuple. Always fetch a fresh snapshot afterwards.
|
|
235
257
|
const assignments = await client.listV3CredentialAssignments();
|
|
236
258
|
const assignment = assignments.items.find((item) => item.assignmentId === selection.account.assignmentId
|
|
237
259
|
&& item.managedAccountId === selection.account.managedAccountId
|
|
@@ -301,10 +323,38 @@ async function installCredentials(deps, selection, service) {
|
|
|
301
323
|
}
|
|
302
324
|
async function selectedCredentials(deps, requested, options = {}) {
|
|
303
325
|
if (accountTokenPoolV4Enabled(deps.config)) {
|
|
326
|
+
const client = deps.clientFor(serviceConnection(options));
|
|
327
|
+
const recovered = await reconcilePendingManagedCodexV3Operation({
|
|
328
|
+
client,
|
|
329
|
+
tenantId: deps.config.connection.tenantId,
|
|
330
|
+
env: deps.env
|
|
331
|
+
});
|
|
332
|
+
if (!requested && recovered) {
|
|
333
|
+
const assignments = await client.listV3CredentialAssignments();
|
|
334
|
+
const assignment = findV3Assignment({ assignments, ...recovered });
|
|
335
|
+
if (!assignment || assignment.currentGenerationVersion !== recovered.generationVersion
|
|
336
|
+
|| assignment.authorityMode !== "CLIENT_MANAGED") {
|
|
337
|
+
throw new CodexServiceError("The recovered local v4 lineage no longer matches the server assignment.", "lineage_version_conflict", 409);
|
|
338
|
+
}
|
|
339
|
+
const selection = await saveCodexSelection(deps.config.persistence.configDirectory, {
|
|
340
|
+
serviceUrl: options.server ?? deps.config.connection.serverUrl,
|
|
341
|
+
allowInsecurePoc: Boolean(options.insecurePoc || options.insecureHttp || deps.config.connection.allowInsecureHttp),
|
|
342
|
+
account: {
|
|
343
|
+
id: recovered.managedAccountId,
|
|
344
|
+
accountId: recovered.providerAccountId,
|
|
345
|
+
label: assignment.label,
|
|
346
|
+
distributionMode: "CLIENT_MANAGED_V4",
|
|
347
|
+
assignmentId: recovered.assignmentId,
|
|
348
|
+
managedAccountId: recovered.managedAccountId,
|
|
349
|
+
providerAccountId: recovered.providerAccountId
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
return { selection, expiresAt: Date.parse(recovered.credentialExpiresAt) };
|
|
353
|
+
}
|
|
304
354
|
const state = await readManagedCodexV3State(deps.env);
|
|
305
355
|
if (!state || state.phase !== "INSTALLED" || state.authorityMode !== "CLIENT_MANAGED") {
|
|
306
|
-
const selection = (await chooseAccount(deps, requested, options)).selection;
|
|
307
|
-
return installCredentials(deps, selection, serviceConnection(options));
|
|
356
|
+
const selection = (await chooseAccount(deps, requested, options, false)).selection;
|
|
357
|
+
return persistInstalledSelection(deps, await installCredentials(deps, selection, serviceConnection(options)));
|
|
308
358
|
}
|
|
309
359
|
if (requested && ![
|
|
310
360
|
state.assignmentId,
|
|
@@ -312,10 +362,10 @@ async function selectedCredentials(deps, requested, options = {}) {
|
|
|
312
362
|
state.providerAccountId,
|
|
313
363
|
state.lineageId
|
|
314
364
|
].includes(requested)) {
|
|
315
|
-
const selection = (await chooseAccount(deps, requested, options)).selection;
|
|
316
|
-
return installCredentials(deps, selection, serviceConnection(options));
|
|
365
|
+
const selection = (await chooseAccount(deps, requested, options, false)).selection;
|
|
366
|
+
return persistInstalledSelection(deps, await installCredentials(deps, selection, serviceConnection(options)));
|
|
317
367
|
}
|
|
318
|
-
const assignments = await
|
|
368
|
+
const assignments = await client.listV3CredentialAssignments();
|
|
319
369
|
const assignment = findV3Assignment({ assignments, ...state });
|
|
320
370
|
if (!assignment || assignment.assignmentId !== state.assignmentId
|
|
321
371
|
|| assignment.managedAccountId !== state.managedAccountId
|
|
@@ -524,8 +574,11 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
524
574
|
});
|
|
525
575
|
serviceOptions(codex.command("use [account]").description("Select an account and update the official Codex auth.json"))
|
|
526
576
|
.action(async (account, opts, command) => {
|
|
527
|
-
const
|
|
528
|
-
await
|
|
577
|
+
const v4 = accountTokenPoolV4Enabled(deps.config);
|
|
578
|
+
const result = await chooseAccount(deps, account, opts, !v4);
|
|
579
|
+
const installed = await installCredentials(deps, result.selection, serviceConnection(opts));
|
|
580
|
+
if (v4)
|
|
581
|
+
await persistInstalledSelection(deps, installed);
|
|
529
582
|
if (await json(command, deps))
|
|
530
583
|
print(deps.output, success({ account: result.account, auth_mode: "chatgpt" }));
|
|
531
584
|
else
|
|
@@ -542,7 +595,7 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
542
595
|
deps.errorOutput.write(`Using ChatGPT account: ${selected.selection.account.label} (${selected.selection.account.id})\n`);
|
|
543
596
|
process.exitCode = await launchWithSelectedCredentials(deps, selected, (signal) => deps.run(args, { signal }));
|
|
544
597
|
});
|
|
545
|
-
serviceOptions(codex.command("app [workspace]").description("
|
|
598
|
+
serviceOptions(codex.command("app [workspace]").description("Launch the existing official Codex desktop app without modifying its version"))
|
|
546
599
|
.option("-a, --account <id>", "account id")
|
|
547
600
|
.option("-s, --select-account", "select an account before launch")
|
|
548
601
|
.action(async (workspace, opts) => {
|
|
@@ -576,9 +629,7 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
576
629
|
path: local.desktopApp.path ?? null,
|
|
577
630
|
bundle_id: local.desktopApp.bundleId ?? null,
|
|
578
631
|
version: local.desktopApp.version ?? null,
|
|
579
|
-
build: local.desktopApp.build ?? null
|
|
580
|
-
target_version: local.desktopApp.targetVersion ?? null,
|
|
581
|
-
remediation_required: local.desktopApp.remediationRequired ?? false
|
|
632
|
+
build: local.desktopApp.build ?? null
|
|
582
633
|
} : null,
|
|
583
634
|
selection: local.selection ? {
|
|
584
635
|
account: local.selection.account,
|
|
@@ -592,7 +643,7 @@ export function registerCodexCommands(program, deps, options = {}) {
|
|
|
592
643
|
deps.output.write(`Official ChatGPT login: ${local.officialAuthMatchesSelection ? "matched" : "not matched"}\n`);
|
|
593
644
|
if (local.desktopApp) {
|
|
594
645
|
const desktop = !local.desktopApp.supported
|
|
595
|
-
? `not
|
|
646
|
+
? `not available on ${local.desktopApp.platform}`
|
|
596
647
|
: local.desktopApp.installed
|
|
597
648
|
? `${local.desktopApp.version ?? "unknown version"}${local.desktopApp.build ? ` (${local.desktopApp.build})` : ""}`
|
|
598
649
|
: "not installed";
|