@mostok/codexes 0.1.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.
Files changed (96) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +88 -0
  3. package/dist/accounts/account-registry.d.ts +30 -0
  4. package/dist/accounts/account-registry.js +263 -0
  5. package/dist/accounts/account-registry.js.map +1 -0
  6. package/dist/accounts/account-resolution.d.ts +16 -0
  7. package/dist/accounts/account-resolution.js +71 -0
  8. package/dist/accounts/account-resolution.js.map +1 -0
  9. package/dist/accounts/resolve-active-account.d.ts +6 -0
  10. package/dist/accounts/resolve-active-account.js +32 -0
  11. package/dist/accounts/resolve-active-account.js.map +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +3426 -0
  14. package/dist/cli.js.map +7 -0
  15. package/dist/commands/account-add/run-account-add-command.d.ts +2 -0
  16. package/dist/commands/account-add/run-account-add-command.js +296 -0
  17. package/dist/commands/account-add/run-account-add-command.js.map +1 -0
  18. package/dist/commands/account-list/run-account-list-command.d.ts +2 -0
  19. package/dist/commands/account-list/run-account-list-command.js +48 -0
  20. package/dist/commands/account-list/run-account-list-command.js.map +1 -0
  21. package/dist/commands/account-remove/run-account-remove-command.d.ts +2 -0
  22. package/dist/commands/account-remove/run-account-remove-command.js +52 -0
  23. package/dist/commands/account-remove/run-account-remove-command.js.map +1 -0
  24. package/dist/commands/account-use/run-account-use-command.d.ts +2 -0
  25. package/dist/commands/account-use/run-account-use-command.js +78 -0
  26. package/dist/commands/account-use/run-account-use-command.js.map +1 -0
  27. package/dist/commands/root/run-root-command.d.ts +2 -0
  28. package/dist/commands/root/run-root-command.js +175 -0
  29. package/dist/commands/root/run-root-command.js.map +1 -0
  30. package/dist/config/wrapper-config.d.ts +24 -0
  31. package/dist/config/wrapper-config.js +145 -0
  32. package/dist/config/wrapper-config.js.map +1 -0
  33. package/dist/core/bootstrap.d.ts +8 -0
  34. package/dist/core/bootstrap.js +32 -0
  35. package/dist/core/bootstrap.js.map +1 -0
  36. package/dist/core/context.d.ts +33 -0
  37. package/dist/core/context.js +72 -0
  38. package/dist/core/context.js.map +1 -0
  39. package/dist/core/paths.d.ts +12 -0
  40. package/dist/core/paths.js +30 -0
  41. package/dist/core/paths.js.map +1 -0
  42. package/dist/logging/logger.d.ts +18 -0
  43. package/dist/logging/logger.js +56 -0
  44. package/dist/logging/logger.js.map +1 -0
  45. package/dist/process/codex-launch-spec.d.ts +5 -0
  46. package/dist/process/codex-launch-spec.js +80 -0
  47. package/dist/process/codex-launch-spec.js.map +1 -0
  48. package/dist/process/find-codex-binary.d.ts +14 -0
  49. package/dist/process/find-codex-binary.js +73 -0
  50. package/dist/process/find-codex-binary.js.map +1 -0
  51. package/dist/process/run-codex-login.d.ts +14 -0
  52. package/dist/process/run-codex-login.js +97 -0
  53. package/dist/process/run-codex-login.js.map +1 -0
  54. package/dist/process/spawn-codex-command.d.ts +7 -0
  55. package/dist/process/spawn-codex-command.js +69 -0
  56. package/dist/process/spawn-codex-command.js.map +1 -0
  57. package/dist/runtime/activate-account/activate-account.d.ts +27 -0
  58. package/dist/runtime/activate-account/activate-account.js +298 -0
  59. package/dist/runtime/activate-account/activate-account.js.map +1 -0
  60. package/dist/runtime/auth-state-probe.d.ts +57 -0
  61. package/dist/runtime/auth-state-probe.js +394 -0
  62. package/dist/runtime/auth-state-probe.js.map +1 -0
  63. package/dist/runtime/init/initialize-runtime.d.ts +19 -0
  64. package/dist/runtime/init/initialize-runtime.js +275 -0
  65. package/dist/runtime/init/initialize-runtime.js.map +1 -0
  66. package/dist/runtime/lock/runtime-lock.d.ts +11 -0
  67. package/dist/runtime/lock/runtime-lock.js +99 -0
  68. package/dist/runtime/lock/runtime-lock.js.map +1 -0
  69. package/dist/runtime/login-workspace.d.ts +18 -0
  70. package/dist/runtime/login-workspace.js +171 -0
  71. package/dist/runtime/login-workspace.js.map +1 -0
  72. package/dist/runtime/runtime-contract.d.ts +44 -0
  73. package/dist/runtime/runtime-contract.js +79 -0
  74. package/dist/runtime/runtime-contract.js.map +1 -0
  75. package/dist/selection/account-auth-state.d.ts +23 -0
  76. package/dist/selection/account-auth-state.js +132 -0
  77. package/dist/selection/account-auth-state.js.map +1 -0
  78. package/dist/selection/select-account.d.ts +11 -0
  79. package/dist/selection/select-account.js +168 -0
  80. package/dist/selection/select-account.js.map +1 -0
  81. package/dist/selection/usage-cache.d.ts +24 -0
  82. package/dist/selection/usage-cache.js +106 -0
  83. package/dist/selection/usage-cache.js.map +1 -0
  84. package/dist/selection/usage-client.d.ts +23 -0
  85. package/dist/selection/usage-client.js +143 -0
  86. package/dist/selection/usage-client.js.map +1 -0
  87. package/dist/selection/usage-normalize.d.ts +7 -0
  88. package/dist/selection/usage-normalize.js +209 -0
  89. package/dist/selection/usage-normalize.js.map +1 -0
  90. package/dist/selection/usage-probe-coordinator.d.ts +18 -0
  91. package/dist/selection/usage-probe-coordinator.js +69 -0
  92. package/dist/selection/usage-probe-coordinator.js.map +1 -0
  93. package/dist/selection/usage-types.d.ts +59 -0
  94. package/dist/selection/usage-types.js +2 -0
  95. package/dist/selection/usage-types.js.map +1 -0
  96. package/package.json +59 -0
package/dist/cli.js ADDED
@@ -0,0 +1,3426 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/logging/logger.ts
4
+ var LOG_LEVEL_ORDER = {
5
+ DEBUG: 10,
6
+ INFO: 20,
7
+ WARN: 30,
8
+ ERROR: 40
9
+ };
10
+ function resolveLogLevel(value) {
11
+ switch (value?.toUpperCase()) {
12
+ case "ERROR":
13
+ return "ERROR";
14
+ case "WARN":
15
+ return "WARN";
16
+ case "INFO":
17
+ return "INFO";
18
+ case "DEBUG":
19
+ return "DEBUG";
20
+ default:
21
+ return "ERROR";
22
+ }
23
+ }
24
+ function createLogSink(stream) {
25
+ return {
26
+ write(level, event, details = {}) {
27
+ stream.write(
28
+ `${JSON.stringify({
29
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
30
+ level,
31
+ event,
32
+ details
33
+ })}
34
+ `
35
+ );
36
+ }
37
+ };
38
+ }
39
+ function createLogger(input) {
40
+ const configuredLevel = resolveLogLevel(input.level);
41
+ return {
42
+ debug(event, details) {
43
+ logWithLevel("DEBUG", input.name, configuredLevel, input.sink, event, details);
44
+ },
45
+ info(event, details) {
46
+ logWithLevel("INFO", input.name, configuredLevel, input.sink, event, details);
47
+ },
48
+ warn(event, details) {
49
+ logWithLevel("WARN", input.name, configuredLevel, input.sink, event, details);
50
+ },
51
+ error(event, details) {
52
+ logWithLevel("ERROR", input.name, configuredLevel, input.sink, event, details);
53
+ }
54
+ };
55
+ }
56
+ function logWithLevel(level, name, configuredLevel, sink, event, details) {
57
+ if (LOG_LEVEL_ORDER[level] < LOG_LEVEL_ORDER[configuredLevel]) {
58
+ return;
59
+ }
60
+ sink.write(level, `${name}.${event}`, details);
61
+ }
62
+
63
+ // src/config/wrapper-config.ts
64
+ import { mkdir, readFile } from "node:fs/promises";
65
+ var DEFAULT_EXPERIMENTAL_PROBE_TIMEOUT_MS = 3500;
66
+ var DEFAULT_EXPERIMENTAL_CACHE_TTL_MS = 6e4;
67
+ async function resolveWrapperConfig(input) {
68
+ await mkdir(input.paths.dataRoot, { recursive: true });
69
+ const credentialStoreMode = await detectCredentialStoreMode(
70
+ input.paths.codexConfigFile,
71
+ input.logger
72
+ );
73
+ const resolved = {
74
+ configFilePath: input.paths.wrapperConfigFile,
75
+ codexConfigFilePath: input.paths.codexConfigFile,
76
+ selectionCacheFilePath: input.paths.selectionCacheFile,
77
+ credentialStoreMode,
78
+ credentialStorePolicyReason: credentialStoreMode === "file" ? "file mode detected in Codex config" : "codexes currently supports only file-backed auth storage",
79
+ accountSelectionStrategy: resolveAccountSelectionStrategy(input.env),
80
+ experimentalSelection: resolveExperimentalSelectionConfig(input.env, input.logger)
81
+ };
82
+ input.logger.info("wrapper_config.resolved", {
83
+ configFilePath: resolved.configFilePath,
84
+ codexConfigFilePath: resolved.codexConfigFilePath,
85
+ selectionCacheFilePath: resolved.selectionCacheFilePath,
86
+ credentialStoreMode: resolved.credentialStoreMode,
87
+ accountSelectionStrategy: resolved.accountSelectionStrategy,
88
+ experimentalSelection: resolved.experimentalSelection
89
+ });
90
+ return resolved;
91
+ }
92
+ function resolveExperimentalSelectionConfig(env, logger) {
93
+ const probeTimeoutMs = resolvePositiveIntegerEnv({
94
+ defaultValue: DEFAULT_EXPERIMENTAL_PROBE_TIMEOUT_MS,
95
+ env,
96
+ envKey: "CODEXES_EXPERIMENTAL_SELECTION_TIMEOUT_MS",
97
+ logger
98
+ });
99
+ const cacheTtlMs = resolvePositiveIntegerEnv({
100
+ defaultValue: DEFAULT_EXPERIMENTAL_CACHE_TTL_MS,
101
+ env,
102
+ envKey: "CODEXES_EXPERIMENTAL_SELECTION_CACHE_TTL_MS",
103
+ logger
104
+ });
105
+ const useAccountIdHeader = resolveBooleanEnv(
106
+ env.CODEXES_EXPERIMENTAL_SELECTION_USE_ACCOUNT_ID_HEADER
107
+ );
108
+ const enabled = resolveAccountSelectionStrategy(env) === "remaining-limit-experimental";
109
+ logger.debug("wrapper_config.experimental_selection_resolved", {
110
+ enabled,
111
+ probeTimeoutMs,
112
+ cacheTtlMs,
113
+ useAccountIdHeader
114
+ });
115
+ return {
116
+ enabled,
117
+ probeTimeoutMs,
118
+ cacheTtlMs,
119
+ useAccountIdHeader
120
+ };
121
+ }
122
+ function resolveAccountSelectionStrategy(env) {
123
+ switch (env.CODEXES_ACCOUNT_SELECTION_STRATEGY?.trim().toLowerCase()) {
124
+ case "single-account":
125
+ return "single-account";
126
+ case "remaining-limit-experimental":
127
+ return "remaining-limit-experimental";
128
+ case "manual-default":
129
+ case void 0:
130
+ case "":
131
+ return "manual-default";
132
+ default:
133
+ return "manual-default";
134
+ }
135
+ }
136
+ async function detectCredentialStoreMode(configFile, logger) {
137
+ try {
138
+ const rawConfig = await readFile(configFile, "utf8");
139
+ const mode = parseCredentialStoreMode(rawConfig);
140
+ logger.debug("credential_store.detected", {
141
+ configFile,
142
+ credentialStoreMode: mode
143
+ });
144
+ return mode;
145
+ } catch (error) {
146
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
147
+ logger.warn("credential_store.config_missing", {
148
+ configFile,
149
+ fallbackMode: "missing"
150
+ });
151
+ return "missing";
152
+ }
153
+ logger.error("credential_store.read_failed", {
154
+ configFile,
155
+ message: error instanceof Error ? error.message : String(error)
156
+ });
157
+ return "unknown";
158
+ }
159
+ }
160
+ function parseCredentialStoreMode(rawConfig) {
161
+ const match = rawConfig.match(/^\s*cli_auth_credentials_store\s*=\s*"([^"]+)"/m);
162
+ if (!match) {
163
+ return "missing";
164
+ }
165
+ const configuredValue = match[1];
166
+ if (!configuredValue) {
167
+ return "unknown";
168
+ }
169
+ switch (configuredValue.trim().toLowerCase()) {
170
+ case "file":
171
+ return "file";
172
+ case "keyring":
173
+ return "keyring";
174
+ case "auto":
175
+ return "auto";
176
+ default:
177
+ return "unknown";
178
+ }
179
+ }
180
+ function resolvePositiveIntegerEnv(input) {
181
+ const raw = input.env[input.envKey]?.trim();
182
+ if (!raw) {
183
+ return input.defaultValue;
184
+ }
185
+ const parsed = Number.parseInt(raw, 10);
186
+ if (!Number.isFinite(parsed) || parsed <= 0) {
187
+ input.logger.warn("wrapper_config.invalid_env_override", {
188
+ envKey: input.envKey,
189
+ rawValue: raw,
190
+ fallbackValue: input.defaultValue
191
+ });
192
+ return input.defaultValue;
193
+ }
194
+ return parsed;
195
+ }
196
+ function resolveBooleanEnv(value) {
197
+ switch (value?.trim().toLowerCase()) {
198
+ case "1":
199
+ case "true":
200
+ case "yes":
201
+ case "on":
202
+ return true;
203
+ default:
204
+ return false;
205
+ }
206
+ }
207
+
208
+ // src/core/paths.ts
209
+ import path from "node:path";
210
+ import os from "node:os";
211
+ function resolvePaths(cwd, env) {
212
+ const baseDataDir = resolveBaseDataDir(env);
213
+ return {
214
+ projectRoot: cwd,
215
+ dataRoot: baseDataDir,
216
+ sharedCodexHome: env.CODEX_HOME ?? path.join(baseDataDir, "shared-home"),
217
+ accountRoot: path.join(baseDataDir, "accounts"),
218
+ runtimeRoot: path.join(baseDataDir, "runtime"),
219
+ registryFile: path.join(baseDataDir, "registry.json"),
220
+ wrapperConfigFile: path.join(baseDataDir, "codexes.json"),
221
+ codexConfigFile: path.join(env.CODEX_HOME ?? path.join(baseDataDir, "shared-home"), "config.toml"),
222
+ selectionCacheFile: path.join(baseDataDir, "selection-cache.json")
223
+ };
224
+ }
225
+ function resolveBaseDataDir(env) {
226
+ if (process.platform === "win32") {
227
+ return path.join(
228
+ env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local"),
229
+ "codexes"
230
+ );
231
+ }
232
+ if (process.platform === "darwin") {
233
+ return path.join(
234
+ env.HOME ?? os.homedir(),
235
+ "Library",
236
+ "Application Support",
237
+ "codexes"
238
+ );
239
+ }
240
+ const xdgStateHome = env.XDG_STATE_HOME;
241
+ if (xdgStateHome) {
242
+ return path.join(xdgStateHome, "codexes");
243
+ }
244
+ return path.join(env.HOME ?? os.homedir(), ".local", "state", "codexes");
245
+ }
246
+
247
+ // src/process/find-codex-binary.ts
248
+ import { access, stat } from "node:fs/promises";
249
+ import path2 from "node:path";
250
+ async function findCodexBinary(input) {
251
+ const candidates = buildCandidates(input.env);
252
+ const rejectedCandidates = [];
253
+ input.logger.debug("binary_resolution.start", {
254
+ wrapperExecutablePath: input.wrapperExecutablePath,
255
+ candidateCount: candidates.length
256
+ });
257
+ for (const candidate of candidates) {
258
+ const reason = await getRejectionReason(candidate, input.wrapperExecutablePath);
259
+ if (reason) {
260
+ rejectedCandidates.push({ candidate, reason });
261
+ input.logger.debug("binary_resolution.rejected", { candidate, reason });
262
+ continue;
263
+ }
264
+ input.logger.info("binary_resolution.selected", { candidate });
265
+ return {
266
+ path: candidate,
267
+ candidates,
268
+ rejectedCandidates
269
+ };
270
+ }
271
+ input.logger.warn("binary_resolution.missing", {
272
+ wrapperExecutablePath: input.wrapperExecutablePath,
273
+ rejectedCandidates
274
+ });
275
+ return {
276
+ path: null,
277
+ candidates,
278
+ rejectedCandidates
279
+ };
280
+ }
281
+ function buildCandidates(env) {
282
+ const pathValue = env.PATH ?? "";
283
+ const pathEntries = pathValue.split(path2.delimiter).map((entry) => entry.trim()).filter(Boolean);
284
+ const executableNames = process.platform === "win32" ? ["codex.cmd", "codex.exe", "codex.bat"] : ["codex"];
285
+ return Array.from(
286
+ new Set(
287
+ pathEntries.flatMap(
288
+ (entry) => executableNames.map((executableName) => path2.join(entry, executableName))
289
+ )
290
+ )
291
+ );
292
+ }
293
+ async function getRejectionReason(candidate, wrapperExecutablePath) {
294
+ try {
295
+ await access(candidate);
296
+ } catch {
297
+ return "not_accessible";
298
+ }
299
+ const [candidateStat, wrapperStat] = await Promise.all([
300
+ stat(candidate).catch(() => null),
301
+ stat(wrapperExecutablePath).catch(() => null)
302
+ ]);
303
+ if (!candidateStat || !candidateStat.isFile()) {
304
+ return "not_a_file";
305
+ }
306
+ if (wrapperStat && isSameFile(candidate, wrapperExecutablePath, candidateStat, wrapperStat)) {
307
+ return "self_recursive_wrapper_path";
308
+ }
309
+ if (path2.basename(candidate).toLowerCase().startsWith("codexes")) {
310
+ return "wrapper_named_binary";
311
+ }
312
+ return null;
313
+ }
314
+ function isSameFile(candidate, wrapperExecutablePath, candidateStat, wrapperStat) {
315
+ if (path2.resolve(candidate) === path2.resolve(wrapperExecutablePath)) {
316
+ return true;
317
+ }
318
+ return candidateStat.ino === wrapperStat.ino && candidateStat.dev === wrapperStat.dev;
319
+ }
320
+
321
+ // src/runtime/init/initialize-runtime.ts
322
+ import os2 from "node:os";
323
+ import path4 from "node:path";
324
+ import {
325
+ copyFile,
326
+ cp,
327
+ mkdir as mkdir3,
328
+ readFile as readFile3,
329
+ stat as stat3,
330
+ writeFile as writeFile2
331
+ } from "node:fs/promises";
332
+
333
+ // src/accounts/account-registry.ts
334
+ import { randomUUID } from "node:crypto";
335
+ import { mkdir as mkdir2, readFile as readFile2, rename, rm, stat as stat2, writeFile } from "node:fs/promises";
336
+ import path3 from "node:path";
337
+ var REGISTRY_SCHEMA_VERSION = 1;
338
+ function createAccountRegistry(input) {
339
+ return {
340
+ addAccount(details) {
341
+ return withRegistryMutation(input, "registry.add", async (document, now) => {
342
+ const normalizedLabel = normalizeLabel(details.label);
343
+ const duplicate = document.accounts.find(
344
+ (account) => account.label.toLowerCase() === normalizedLabel.toLowerCase()
345
+ );
346
+ if (duplicate) {
347
+ input.logger.warn("registry.duplicate_label", {
348
+ label: normalizedLabel,
349
+ existingAccountId: duplicate.id
350
+ });
351
+ throw new Error(`An account named "${normalizedLabel}" already exists.`);
352
+ }
353
+ const accountId = randomUUID();
354
+ const record = {
355
+ id: accountId,
356
+ label: normalizedLabel,
357
+ authDirectory: details.authDirectory ?? path3.join(input.accountRoot, accountId),
358
+ createdAt: now,
359
+ updatedAt: now,
360
+ lastUsedAt: null
361
+ };
362
+ document.accounts.push(record);
363
+ if (!document.defaultAccountId) {
364
+ document.defaultAccountId = record.id;
365
+ }
366
+ input.logger.info("registry.account_added", {
367
+ accountId: record.id,
368
+ label: record.label,
369
+ authDirectory: record.authDirectory,
370
+ defaultAccountId: document.defaultAccountId
371
+ });
372
+ return record;
373
+ });
374
+ },
375
+ async getDefaultAccount() {
376
+ const document = await readRegistryDocument(input);
377
+ const account = document.defaultAccountId ? document.accounts.find((entry) => entry.id === document.defaultAccountId) ?? null : null;
378
+ input.logger.debug("registry.default_loaded", {
379
+ defaultAccountId: document.defaultAccountId,
380
+ resolvedAccountId: account?.id ?? null
381
+ });
382
+ return account;
383
+ },
384
+ async listAccounts() {
385
+ const document = await readRegistryDocument(input);
386
+ input.logger.debug("registry.list_loaded", {
387
+ accountCount: document.accounts.length,
388
+ defaultAccountId: document.defaultAccountId
389
+ });
390
+ return [...document.accounts];
391
+ },
392
+ removeAccount(accountId) {
393
+ return withRegistryMutation(input, "registry.remove", async (document, now) => {
394
+ const record = document.accounts.find((account) => account.id === accountId);
395
+ if (!record) {
396
+ input.logger.warn("registry.remove_missing", { accountId });
397
+ throw new Error(`Account "${accountId}" was not found.`);
398
+ }
399
+ document.accounts = document.accounts.filter((account) => account.id !== accountId);
400
+ if (document.defaultAccountId === accountId) {
401
+ document.defaultAccountId = document.accounts[0]?.id ?? null;
402
+ }
403
+ record.updatedAt = now;
404
+ input.logger.info("registry.account_removed", {
405
+ accountId,
406
+ nextDefaultAccountId: document.defaultAccountId
407
+ });
408
+ return record;
409
+ });
410
+ },
411
+ selectAccount(accountId) {
412
+ return withRegistryMutation(input, "registry.select", async (document, now) => {
413
+ const record = document.accounts.find((account) => account.id === accountId);
414
+ if (!record) {
415
+ input.logger.warn("registry.select_missing", { accountId });
416
+ throw new Error(`Account "${accountId}" was not found.`);
417
+ }
418
+ document.defaultAccountId = record.id;
419
+ record.updatedAt = now;
420
+ record.lastUsedAt = now;
421
+ input.logger.info("registry.account_selected", {
422
+ accountId,
423
+ label: record.label
424
+ });
425
+ return record;
426
+ });
427
+ }
428
+ };
429
+ }
430
+ async function withRegistryMutation(input, operation, mutate) {
431
+ await mkdir2(input.accountRoot, { recursive: true });
432
+ await mkdir2(path3.dirname(input.registryFile), { recursive: true });
433
+ const document = await readRegistryDocument(input);
434
+ const now = (/* @__PURE__ */ new Date()).toISOString();
435
+ input.logger.debug(`${operation}.start`, {
436
+ registryFile: input.registryFile,
437
+ accountRoot: input.accountRoot,
438
+ accountCount: document.accounts.length
439
+ });
440
+ const result = await mutate(document, now);
441
+ await persistRegistryDocument(input, document);
442
+ input.logger.debug(`${operation}.complete`, {
443
+ registryFile: input.registryFile,
444
+ accountCount: document.accounts.length,
445
+ defaultAccountId: document.defaultAccountId
446
+ });
447
+ return result;
448
+ }
449
+ async function readRegistryDocument(input) {
450
+ await mkdir2(input.accountRoot, { recursive: true });
451
+ await mkdir2(path3.dirname(input.registryFile), { recursive: true });
452
+ try {
453
+ const raw = await readFile2(input.registryFile, "utf8");
454
+ const parsed = JSON.parse(raw);
455
+ const migrated = migrateRegistryDocument(parsed, input.logger, input.registryFile);
456
+ input.logger.debug("registry.read_success", {
457
+ registryFile: input.registryFile,
458
+ schemaVersion: migrated.schemaVersion,
459
+ accountCount: migrated.accounts.length
460
+ });
461
+ return migrated;
462
+ } catch (error) {
463
+ if (isFileMissing(error)) {
464
+ const emptyDocument2 = createEmptyRegistryDocument();
465
+ input.logger.info("registry.read_missing", {
466
+ registryFile: input.registryFile,
467
+ action: "create_empty_registry"
468
+ });
469
+ await persistRegistryDocument(input, emptyDocument2);
470
+ return emptyDocument2;
471
+ }
472
+ const normalized = normalizeUnknownError(error);
473
+ const corruptionBackupPath = `${input.registryFile}.corrupt-${Date.now()}`;
474
+ input.logger.warn("registry.read_corrupt", {
475
+ registryFile: input.registryFile,
476
+ corruptionBackupPath,
477
+ message: normalized.message
478
+ });
479
+ await rename(input.registryFile, corruptionBackupPath).catch(() => void 0);
480
+ const emptyDocument = createEmptyRegistryDocument();
481
+ await persistRegistryDocument(input, emptyDocument);
482
+ return emptyDocument;
483
+ }
484
+ }
485
+ async function persistRegistryDocument(input, document) {
486
+ const tempFile = `${input.registryFile}.tmp`;
487
+ const serialized = JSON.stringify(document, null, 2);
488
+ await writeFile(tempFile, serialized, "utf8");
489
+ await rename(tempFile, input.registryFile);
490
+ input.logger.debug("registry.write_success", {
491
+ registryFile: input.registryFile,
492
+ bytes: Buffer.byteLength(serialized, "utf8"),
493
+ schemaVersion: document.schemaVersion,
494
+ defaultAccountId: document.defaultAccountId
495
+ });
496
+ }
497
+ function migrateRegistryDocument(value, logger, registryFile) {
498
+ if (!isObject(value)) {
499
+ throw new Error("Registry document is not a JSON object.");
500
+ }
501
+ const schemaVersion = typeof value.schemaVersion === "number" ? value.schemaVersion : 0;
502
+ logger.debug("registry.migration_check", {
503
+ registryFile,
504
+ schemaVersion,
505
+ targetSchemaVersion: REGISTRY_SCHEMA_VERSION
506
+ });
507
+ if (schemaVersion > REGISTRY_SCHEMA_VERSION) {
508
+ throw new Error(
509
+ `Registry schema ${schemaVersion} is newer than supported schema ${REGISTRY_SCHEMA_VERSION}.`
510
+ );
511
+ }
512
+ if (schemaVersion === REGISTRY_SCHEMA_VERSION) {
513
+ return normalizeRegistryDocument(value);
514
+ }
515
+ if (schemaVersion === 0) {
516
+ const migrated = normalizeRegistryDocument({
517
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
518
+ defaultAccountId: value.defaultAccountId ?? null,
519
+ accounts: value.accounts ?? []
520
+ });
521
+ logger.info("registry.migration_applied", {
522
+ registryFile,
523
+ fromSchemaVersion: 0,
524
+ toSchemaVersion: REGISTRY_SCHEMA_VERSION
525
+ });
526
+ return migrated;
527
+ }
528
+ throw new Error(`Unsupported registry schema version ${schemaVersion}.`);
529
+ }
530
+ function normalizeRegistryDocument(value) {
531
+ const accounts = Array.isArray(value.accounts) ? value.accounts.map(normalizeAccountRecord) : [];
532
+ const defaultAccountId = typeof value.defaultAccountId === "string" ? value.defaultAccountId : null;
533
+ return {
534
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
535
+ defaultAccountId: defaultAccountId && accounts.some((account) => account.id === defaultAccountId) ? defaultAccountId : accounts[0]?.id ?? null,
536
+ accounts
537
+ };
538
+ }
539
+ function normalizeAccountRecord(value) {
540
+ if (!isObject(value)) {
541
+ throw new Error("Account record is not an object.");
542
+ }
543
+ const id = typeof value.id === "string" ? value.id : randomUUID();
544
+ const createdAt = typeof value.createdAt === "string" ? value.createdAt : (/* @__PURE__ */ new Date(0)).toISOString();
545
+ const updatedAt = typeof value.updatedAt === "string" ? value.updatedAt : createdAt;
546
+ return {
547
+ id,
548
+ label: normalizeLabel(typeof value.label === "string" ? value.label : id),
549
+ authDirectory: typeof value.authDirectory === "string" ? value.authDirectory : path3.join("accounts", id),
550
+ createdAt,
551
+ updatedAt,
552
+ lastUsedAt: typeof value.lastUsedAt === "string" ? value.lastUsedAt : null
553
+ };
554
+ }
555
+ function createEmptyRegistryDocument() {
556
+ return {
557
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
558
+ defaultAccountId: null,
559
+ accounts: []
560
+ };
561
+ }
562
+ function normalizeLabel(label) {
563
+ const normalized = label.trim();
564
+ if (!normalized) {
565
+ throw new Error("Account label cannot be empty.");
566
+ }
567
+ return normalized;
568
+ }
569
+ function isObject(value) {
570
+ return typeof value === "object" && value !== null;
571
+ }
572
+ function isFileMissing(error) {
573
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
574
+ }
575
+ function normalizeUnknownError(error) {
576
+ if (error instanceof Error) {
577
+ return { message: error.message };
578
+ }
579
+ return { message: String(error) };
580
+ }
581
+
582
+ // src/runtime/init/initialize-runtime.ts
583
+ async function initializeRuntimeEnvironment(input) {
584
+ const legacyCodexHome = path4.join(os2.homedir(), ".codex");
585
+ const firstRun = !await pathExists(input.paths.sharedCodexHome);
586
+ const createdDirectories = [];
587
+ const createdFiles = [];
588
+ const copiedSharedArtifacts = [];
589
+ const skippedArtifacts = [];
590
+ input.logger.info("runtime_init.start", {
591
+ dataRoot: input.paths.dataRoot,
592
+ sharedCodexHome: input.paths.sharedCodexHome,
593
+ legacyCodexHome,
594
+ firstRun
595
+ });
596
+ for (const directory of [
597
+ input.paths.dataRoot,
598
+ input.paths.sharedCodexHome,
599
+ input.paths.accountRoot,
600
+ input.paths.runtimeRoot,
601
+ path4.join(input.paths.runtimeRoot, "backups"),
602
+ path4.join(input.paths.runtimeRoot, "tmp"),
603
+ path4.dirname(input.paths.registryFile)
604
+ ]) {
605
+ if (!await pathExists(directory)) {
606
+ createdDirectories.push(directory);
607
+ input.logger.info("runtime_init.directory_create", { directory });
608
+ } else {
609
+ input.logger.debug("runtime_init.directory_exists", { directory });
610
+ }
611
+ await mkdir3(directory, { recursive: true });
612
+ }
613
+ const configResult = await ensureSharedConfigToml({
614
+ legacyCodexHome,
615
+ logger: input.logger,
616
+ sharedCodexHome: input.paths.sharedCodexHome
617
+ });
618
+ createdFiles.push(...configResult.createdFiles);
619
+ copiedSharedArtifacts.push(...configResult.copiedArtifacts);
620
+ skippedArtifacts.push(...configResult.skippedArtifacts);
621
+ const mcpResult = await ensureSharedFileCopy({
622
+ artifactName: "mcp.json",
623
+ logger: input.logger,
624
+ sharedCodexHome: input.paths.sharedCodexHome,
625
+ sourceCodexHome: legacyCodexHome
626
+ });
627
+ createdFiles.push(...mcpResult.createdFiles);
628
+ copiedSharedArtifacts.push(...mcpResult.copiedArtifacts);
629
+ skippedArtifacts.push(...mcpResult.skippedArtifacts);
630
+ const trustResult = await ensureSharedDirectoryCopy({
631
+ artifactName: "trust",
632
+ logger: input.logger,
633
+ sharedCodexHome: input.paths.sharedCodexHome,
634
+ sourceCodexHome: legacyCodexHome
635
+ });
636
+ copiedSharedArtifacts.push(...trustResult.copiedArtifacts);
637
+ skippedArtifacts.push(...trustResult.skippedArtifacts);
638
+ const registry = createAccountRegistry({
639
+ accountRoot: input.paths.accountRoot,
640
+ logger: input.logger,
641
+ registryFile: input.paths.registryFile
642
+ });
643
+ if (!await pathExists(input.paths.registryFile)) {
644
+ createdFiles.push(input.paths.registryFile);
645
+ input.logger.info("runtime_init.registry_bootstrap", {
646
+ registryFile: input.paths.registryFile
647
+ });
648
+ } else {
649
+ input.logger.debug("runtime_init.registry_exists", {
650
+ registryFile: input.paths.registryFile
651
+ });
652
+ }
653
+ await registry.listAccounts();
654
+ const result = {
655
+ firstRun,
656
+ legacyCodexHome,
657
+ sharedCodexHome: input.paths.sharedCodexHome,
658
+ createdDirectories,
659
+ createdFiles,
660
+ copiedSharedArtifacts,
661
+ skippedArtifacts
662
+ };
663
+ input.logger.info("runtime_init.complete", {
664
+ firstRun: result.firstRun,
665
+ createdDirectories: result.createdDirectories,
666
+ createdFiles: result.createdFiles,
667
+ copiedSharedArtifacts: result.copiedSharedArtifacts,
668
+ skippedArtifacts: result.skippedArtifacts
669
+ });
670
+ return result;
671
+ }
672
+ async function ensureSharedConfigToml(input) {
673
+ const targetPath = path4.join(input.sharedCodexHome, "config.toml");
674
+ const sourcePath = path4.join(input.legacyCodexHome, "config.toml");
675
+ if (await pathExists(targetPath)) {
676
+ const existingConfig = await readFile3(targetPath, "utf8");
677
+ const existingMode = detectCredentialStoreMode2(existingConfig);
678
+ input.logger.debug("runtime_init.config_exists", {
679
+ targetPath,
680
+ credentialStoreMode: existingMode
681
+ });
682
+ if (existingMode === "missing") {
683
+ await writeFile2(targetPath, pinFileCredentialStore(existingConfig), "utf8");
684
+ input.logger.info("runtime_init.config_file_mode_pinned", {
685
+ targetPath
686
+ });
687
+ } else if (existingMode !== "file") {
688
+ input.logger.warn("runtime_init.config_mode_unsupported", {
689
+ targetPath,
690
+ credentialStoreMode: existingMode
691
+ });
692
+ }
693
+ return emptyArtifactResult();
694
+ }
695
+ if (await pathExists(sourcePath) && !samePath(sourcePath, targetPath)) {
696
+ const sourceConfig = await readFile3(sourcePath, "utf8");
697
+ const pinnedConfig = pinFileCredentialStore(sourceConfig);
698
+ await writeFile2(targetPath, pinnedConfig, "utf8");
699
+ input.logger.info("runtime_init.config_imported", {
700
+ sourcePath,
701
+ targetPath,
702
+ sourceCredentialStoreMode: detectCredentialStoreMode2(sourceConfig),
703
+ targetCredentialStoreMode: detectCredentialStoreMode2(pinnedConfig)
704
+ });
705
+ return {
706
+ copiedArtifacts: ["config.toml"],
707
+ createdFiles: [targetPath],
708
+ skippedArtifacts: []
709
+ };
710
+ }
711
+ await writeFile2(
712
+ targetPath,
713
+ [
714
+ 'cli_auth_credentials_store = "file"',
715
+ "",
716
+ "# Wrapper-owned shared Codex home for codexes.",
717
+ "# Add MCP and trust configuration here as needed.",
718
+ ""
719
+ ].join("\n"),
720
+ "utf8"
721
+ );
722
+ input.logger.info("runtime_init.config_created", {
723
+ targetPath
724
+ });
725
+ return {
726
+ copiedArtifacts: [],
727
+ createdFiles: [targetPath],
728
+ skippedArtifacts: []
729
+ };
730
+ }
731
+ async function ensureSharedFileCopy(input) {
732
+ const targetPath = path4.join(input.sharedCodexHome, input.artifactName);
733
+ const sourcePath = path4.join(input.sourceCodexHome, input.artifactName);
734
+ if (await pathExists(targetPath)) {
735
+ input.logger.debug("runtime_init.file_exists", {
736
+ artifactName: input.artifactName,
737
+ targetPath
738
+ });
739
+ return emptyArtifactResult();
740
+ }
741
+ if (!await pathExists(sourcePath) || samePath(sourcePath, targetPath)) {
742
+ input.logger.debug("runtime_init.file_skip", {
743
+ artifactName: input.artifactName,
744
+ sourcePath,
745
+ targetPath,
746
+ reason: "source_missing_or_same_path"
747
+ });
748
+ return {
749
+ copiedArtifacts: [],
750
+ createdFiles: [],
751
+ skippedArtifacts: [
752
+ {
753
+ path: input.artifactName,
754
+ reason: "source_missing_or_same_path"
755
+ }
756
+ ]
757
+ };
758
+ }
759
+ await copyFile(sourcePath, targetPath);
760
+ input.logger.info("runtime_init.file_copied", {
761
+ artifactName: input.artifactName,
762
+ sourcePath,
763
+ targetPath
764
+ });
765
+ return {
766
+ copiedArtifacts: [input.artifactName],
767
+ createdFiles: [targetPath],
768
+ skippedArtifacts: []
769
+ };
770
+ }
771
+ async function ensureSharedDirectoryCopy(input) {
772
+ const targetPath = path4.join(input.sharedCodexHome, input.artifactName);
773
+ const sourcePath = path4.join(input.sourceCodexHome, input.artifactName);
774
+ if (await pathExists(targetPath)) {
775
+ input.logger.debug("runtime_init.directory_artifact_exists", {
776
+ artifactName: input.artifactName,
777
+ targetPath
778
+ });
779
+ return emptyArtifactResult();
780
+ }
781
+ if (!await pathExists(sourcePath) || samePath(sourcePath, targetPath)) {
782
+ input.logger.debug("runtime_init.directory_artifact_skip", {
783
+ artifactName: input.artifactName,
784
+ sourcePath,
785
+ targetPath,
786
+ reason: "source_missing_or_same_path"
787
+ });
788
+ return {
789
+ copiedArtifacts: [],
790
+ createdFiles: [],
791
+ skippedArtifacts: [
792
+ {
793
+ path: input.artifactName,
794
+ reason: "source_missing_or_same_path"
795
+ }
796
+ ]
797
+ };
798
+ }
799
+ await cp(sourcePath, targetPath, { recursive: true });
800
+ input.logger.info("runtime_init.directory_artifact_copied", {
801
+ artifactName: input.artifactName,
802
+ sourcePath,
803
+ targetPath
804
+ });
805
+ return {
806
+ copiedArtifacts: [input.artifactName],
807
+ createdFiles: [],
808
+ skippedArtifacts: []
809
+ };
810
+ }
811
+ function detectCredentialStoreMode2(rawConfig) {
812
+ const match = rawConfig.match(/^\s*cli_auth_credentials_store\s*=\s*"([^"]+)"/m);
813
+ return match?.[1]?.trim().toLowerCase() ?? "missing";
814
+ }
815
+ function pinFileCredentialStore(rawConfig) {
816
+ if (/^\s*cli_auth_credentials_store\s*=\s*"file"/m.test(rawConfig)) {
817
+ return rawConfig;
818
+ }
819
+ if (/^\s*cli_auth_credentials_store\s*=\s*"[^"]+"/m.test(rawConfig)) {
820
+ return rawConfig.replace(
821
+ /^\s*cli_auth_credentials_store\s*=\s*"[^"]+"/m,
822
+ 'cli_auth_credentials_store = "file"'
823
+ );
824
+ }
825
+ const trimmed = rawConfig.trim();
826
+ if (!trimmed) {
827
+ return 'cli_auth_credentials_store = "file"\n';
828
+ }
829
+ return ['cli_auth_credentials_store = "file"', "", trimmed, ""].join("\n");
830
+ }
831
+ function samePath(left, right) {
832
+ return path4.resolve(left) === path4.resolve(right);
833
+ }
834
+ async function pathExists(targetPath) {
835
+ try {
836
+ await stat3(targetPath);
837
+ return true;
838
+ } catch (error) {
839
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
840
+ return false;
841
+ }
842
+ throw error;
843
+ }
844
+ }
845
+ function emptyArtifactResult() {
846
+ return {
847
+ copiedArtifacts: [],
848
+ createdFiles: [],
849
+ skippedArtifacts: []
850
+ };
851
+ }
852
+
853
+ // src/core/context.ts
854
+ async function buildAppContext(argv, io) {
855
+ const logLevel = resolveLogLevel(io.env.LOG_LEVEL);
856
+ const sink = createLogSink(io.stderr);
857
+ const paths = resolvePaths(io.cwd, io.env);
858
+ const runtimeInitialization = await initializeRuntimeEnvironment({
859
+ env: io.env,
860
+ logger: createLogger({
861
+ level: logLevel,
862
+ name: "runtime",
863
+ sink
864
+ }),
865
+ paths
866
+ });
867
+ const wrapperConfig = await resolveWrapperConfig({
868
+ env: io.env,
869
+ logger: createLogger({
870
+ level: logLevel,
871
+ name: "config",
872
+ sink
873
+ }),
874
+ paths
875
+ });
876
+ const codexBinary = await findCodexBinary({
877
+ env: io.env,
878
+ logger: createLogger({
879
+ level: logLevel,
880
+ name: "process",
881
+ sink
882
+ }),
883
+ wrapperExecutablePath: io.executablePath
884
+ });
885
+ createLogger({
886
+ level: logLevel,
887
+ name: "context",
888
+ sink
889
+ }).debug("initialized", {
890
+ argv,
891
+ cwd: io.cwd,
892
+ logLevel,
893
+ paths,
894
+ runtimeInitialization,
895
+ wrapperConfig,
896
+ codexBinary
897
+ });
898
+ return {
899
+ argv,
900
+ executablePath: io.executablePath,
901
+ environment: {
902
+ cwd: io.cwd,
903
+ platform: process.platform,
904
+ runtime: process.version
905
+ },
906
+ io: {
907
+ stdout: io.stdout,
908
+ stderr: io.stderr
909
+ },
910
+ logging: {
911
+ level: logLevel,
912
+ sink
913
+ },
914
+ paths,
915
+ runtimeInitialization,
916
+ wrapperConfig,
917
+ codexBinary
918
+ };
919
+ }
920
+
921
+ // src/runtime/runtime-contract.ts
922
+ import path5 from "node:path";
923
+ function createRuntimeContract(input) {
924
+ const contract = {
925
+ credentialStoreMode: input.credentialStoreMode,
926
+ sharedCodexHome: input.sharedCodexHome,
927
+ runtimeRoot: input.runtimeRoot,
928
+ perAccountRoot: input.accountRoot,
929
+ supported: input.credentialStoreMode === "file",
930
+ fileRules: [
931
+ createRule("config.toml", "shared", "never", "Shared CLI behavior and MCP config should remain common."),
932
+ createRule("mcp.json", "shared", "never", "MCP topology is shared across accounts."),
933
+ createRule("trust/**", "shared", "never", "Trust metadata should not be overwritten per account."),
934
+ createRule(
935
+ "auth.json",
936
+ "account",
937
+ "if-changed",
938
+ "Windows probe evidence shows auth.json is sufficient for `codex login status`, so it belongs to the active account profile."
939
+ ),
940
+ createRule(
941
+ "sessions/**",
942
+ "account",
943
+ "if-changed",
944
+ "Session refresh artifacts are still treated as account-scoped until a real token-refresh probe proves otherwise."
945
+ ),
946
+ createRule("cache/**", "ephemeral", "never", "Transient caches should be recreated instead of copied."),
947
+ createRule("logs/**", "ephemeral", "never", "Runtime logs are diagnostic and should not sync back."),
948
+ createRule("history.jsonl", "ephemeral", "never", "Conversation history should stay local to each runtime session."),
949
+ createRule("models_cache.json", "ephemeral", "never", "Model cache data can be rebuilt and should not drive account switching."),
950
+ createRule("tmp/**", "ephemeral", "never", "Temporary files should be discarded after each run."),
951
+ createRule(
952
+ "state_*.sqlite*",
953
+ "protected",
954
+ "never",
955
+ "Observed SQLite runtime state exists in the live Codex home and remains unproven for cross-account merge or sync-back."
956
+ ),
957
+ createRule(
958
+ "logs_*.sqlite*",
959
+ "protected",
960
+ "never",
961
+ "SQLite-backed log databases should stay isolated until a dedicated write-heavy probe proves they are safe to discard or share."
962
+ ),
963
+ createRule("keyring/**", "protected", "never", "External credential stores are explicitly unsupported for MVP.")
964
+ ],
965
+ syncBackStrategy: {
966
+ whenChildProcessSucceeds: "Compare allowed account-classified files and sync only changed files back to the owning account profile.",
967
+ whenChildProcessFails: "Restore pre-launch runtime state and avoid syncing ambiguous mutations unless the failure is known-safe.",
968
+ compareStrategy: "Use file existence, modified time, and content hash checks before any sync-back write."
969
+ }
970
+ };
971
+ input.logger.info("runtime.contract_created", {
972
+ sharedCodexHome: contract.sharedCodexHome,
973
+ runtimeRoot: contract.runtimeRoot,
974
+ perAccountRoot: contract.perAccountRoot,
975
+ credentialStoreMode: contract.credentialStoreMode,
976
+ supported: contract.supported
977
+ });
978
+ input.logger.debug("runtime.file_rules", {
979
+ fileRules: contract.fileRules,
980
+ syncBackStrategy: contract.syncBackStrategy
981
+ });
982
+ return contract;
983
+ }
984
+ function resolveAccountRuntimePaths(contract, accountId) {
985
+ const accountDirectory = path5.join(contract.perAccountRoot, accountId);
986
+ return {
987
+ accountDirectory,
988
+ accountStateDirectory: path5.join(accountDirectory, "state"),
989
+ accountMetadataFile: path5.join(accountDirectory, "account.json"),
990
+ runtimeBackupDirectory: path5.join(contract.runtimeRoot, "backups", accountId),
991
+ runtimeTempDirectory: path5.join(contract.runtimeRoot, "tmp", accountId)
992
+ };
993
+ }
994
+ function summarizeRuntimeContract(contract) {
995
+ return {
996
+ supported: contract.supported,
997
+ credentialStoreMode: contract.credentialStoreMode,
998
+ sharedCodexHome: contract.sharedCodexHome,
999
+ runtimeRoot: contract.runtimeRoot,
1000
+ perAccountRoot: contract.perAccountRoot,
1001
+ classifications: contract.fileRules.reduce(
1002
+ (accumulator, rule) => {
1003
+ accumulator[rule.classification] += 1;
1004
+ return accumulator;
1005
+ },
1006
+ {
1007
+ shared: 0,
1008
+ account: 0,
1009
+ ephemeral: 0,
1010
+ protected: 0
1011
+ }
1012
+ )
1013
+ };
1014
+ }
1015
+ function createRule(pathPattern, classification, syncBack, reason) {
1016
+ return {
1017
+ pathPattern,
1018
+ classification,
1019
+ syncBack,
1020
+ reason
1021
+ };
1022
+ }
1023
+
1024
+ // src/commands/account-add/run-account-add-command.ts
1025
+ import { mkdir as mkdir5, readFile as readFile5, rm as rm2, writeFile as writeFile4 } from "node:fs/promises";
1026
+ import path8 from "node:path";
1027
+
1028
+ // src/runtime/login-workspace.ts
1029
+ import { copyFile as copyFile2, cp as cp2, mkdir as mkdir4, mkdtemp, readFile as readFile4, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
1030
+ import path6 from "node:path";
1031
+ async function prepareLoginWorkspace(input) {
1032
+ const workspaceParent = path6.join(input.runtimeRoot, "tmp");
1033
+ await mkdir4(workspaceParent, { recursive: true });
1034
+ const workspaceRoot = await mkdtemp(path6.join(workspaceParent, "account-add-"));
1035
+ const codexHome = path6.join(workspaceRoot, "codex-home");
1036
+ await mkdir4(codexHome, { recursive: true });
1037
+ input.logger.info("login_workspace.created", {
1038
+ workspaceRoot,
1039
+ codexHome,
1040
+ sharedCodexHome: input.sharedCodexHome
1041
+ });
1042
+ await ensurePinnedFileConfig({
1043
+ codexHome,
1044
+ logger: input.logger,
1045
+ sharedCodexHome: input.sharedCodexHome
1046
+ });
1047
+ await copySharedArtifactIfPresent({
1048
+ artifactName: "mcp.json",
1049
+ codexHome,
1050
+ logger: input.logger,
1051
+ sharedCodexHome: input.sharedCodexHome
1052
+ });
1053
+ await copySharedDirectoryIfPresent({
1054
+ artifactName: "trust",
1055
+ codexHome,
1056
+ logger: input.logger,
1057
+ sharedCodexHome: input.sharedCodexHome
1058
+ });
1059
+ await mkdir4(path6.join(codexHome, "sessions"), { recursive: true });
1060
+ return { workspaceRoot, codexHome };
1061
+ }
1062
+ async function copyAccountScopedAuthArtifacts(input) {
1063
+ const accountRules = input.runtimeContract.fileRules.filter(
1064
+ (rule) => rule.classification === "account"
1065
+ );
1066
+ input.logger.info("login_workspace.copy_account_artifacts.start", {
1067
+ accountId: input.accountId,
1068
+ destinationRoot: input.destinationRoot,
1069
+ sourceCodexHome: input.sourceCodexHome,
1070
+ allowedPatterns: accountRules.map((rule) => rule.pathPattern)
1071
+ });
1072
+ await mkdir4(input.destinationRoot, { recursive: true });
1073
+ for (const rule of accountRules) {
1074
+ await copyRuleArtifacts({
1075
+ destinationRoot: input.destinationRoot,
1076
+ logger: input.logger,
1077
+ rule,
1078
+ sourceCodexHome: input.sourceCodexHome
1079
+ });
1080
+ }
1081
+ input.logger.info("login_workspace.copy_account_artifacts.complete", {
1082
+ accountId: input.accountId,
1083
+ destinationRoot: input.destinationRoot
1084
+ });
1085
+ }
1086
+ async function ensurePinnedFileConfig(input) {
1087
+ const sourceConfigPath = path6.join(input.sharedCodexHome, "config.toml");
1088
+ const targetConfigPath = path6.join(input.codexHome, "config.toml");
1089
+ const configContents = await readFile4(sourceConfigPath, "utf8").catch(() => "");
1090
+ const pinnedConfig = pinFileCredentialStore2(configContents);
1091
+ await writeFile3(targetConfigPath, pinnedConfig, "utf8");
1092
+ input.logger.info("login_workspace.config_prepared", {
1093
+ sourceConfigPath,
1094
+ targetConfigPath,
1095
+ credentialStoreMode: "file"
1096
+ });
1097
+ }
1098
+ async function copySharedArtifactIfPresent(input) {
1099
+ const sourcePath = path6.join(input.sharedCodexHome, input.artifactName);
1100
+ const targetPath = path6.join(input.codexHome, input.artifactName);
1101
+ if (!await pathExists2(sourcePath)) {
1102
+ input.logger.debug("login_workspace.shared_file_skipped", {
1103
+ artifactName: input.artifactName,
1104
+ sourcePath,
1105
+ reason: "missing"
1106
+ });
1107
+ return;
1108
+ }
1109
+ await copyFile2(sourcePath, targetPath);
1110
+ input.logger.debug("login_workspace.shared_file_copied", {
1111
+ artifactName: input.artifactName,
1112
+ sourcePath,
1113
+ targetPath
1114
+ });
1115
+ }
1116
+ async function copySharedDirectoryIfPresent(input) {
1117
+ const sourcePath = path6.join(input.sharedCodexHome, input.artifactName);
1118
+ const targetPath = path6.join(input.codexHome, input.artifactName);
1119
+ if (!await pathExists2(sourcePath)) {
1120
+ input.logger.debug("login_workspace.shared_directory_skipped", {
1121
+ artifactName: input.artifactName,
1122
+ sourcePath,
1123
+ reason: "missing"
1124
+ });
1125
+ return;
1126
+ }
1127
+ await cp2(sourcePath, targetPath, { recursive: true });
1128
+ input.logger.debug("login_workspace.shared_directory_copied", {
1129
+ artifactName: input.artifactName,
1130
+ sourcePath,
1131
+ targetPath
1132
+ });
1133
+ }
1134
+ async function copyRuleArtifacts(input) {
1135
+ if (input.rule.pathPattern.endsWith("/**")) {
1136
+ const relativeDirectory = input.rule.pathPattern.slice(0, -3);
1137
+ const sourceDirectory = path6.join(input.sourceCodexHome, relativeDirectory);
1138
+ const targetDirectory = path6.join(input.destinationRoot, relativeDirectory);
1139
+ if (!await pathExists2(sourceDirectory)) {
1140
+ input.logger.debug("login_workspace.account_directory_skipped", {
1141
+ pathPattern: input.rule.pathPattern,
1142
+ sourceDirectory,
1143
+ reason: "missing"
1144
+ });
1145
+ return;
1146
+ }
1147
+ await cp2(sourceDirectory, targetDirectory, { recursive: true });
1148
+ input.logger.debug("login_workspace.account_directory_copied", {
1149
+ pathPattern: input.rule.pathPattern,
1150
+ sourceDirectory,
1151
+ targetDirectory
1152
+ });
1153
+ return;
1154
+ }
1155
+ const sourceFile = path6.join(input.sourceCodexHome, input.rule.pathPattern);
1156
+ const targetFile = path6.join(input.destinationRoot, input.rule.pathPattern);
1157
+ if (!await pathExists2(sourceFile)) {
1158
+ input.logger.debug("login_workspace.account_file_skipped", {
1159
+ pathPattern: input.rule.pathPattern,
1160
+ sourceFile,
1161
+ reason: "missing"
1162
+ });
1163
+ return;
1164
+ }
1165
+ await mkdir4(path6.dirname(targetFile), { recursive: true });
1166
+ await copyFile2(sourceFile, targetFile);
1167
+ input.logger.debug("login_workspace.account_file_copied", {
1168
+ pathPattern: input.rule.pathPattern,
1169
+ sourceFile,
1170
+ targetFile
1171
+ });
1172
+ }
1173
+ function pinFileCredentialStore2(rawConfig) {
1174
+ if (/^\s*cli_auth_credentials_store\s*=\s*"file"/m.test(rawConfig)) {
1175
+ return rawConfig;
1176
+ }
1177
+ if (/^\s*cli_auth_credentials_store\s*=\s*"[^"]+"/m.test(rawConfig)) {
1178
+ return rawConfig.replace(
1179
+ /^\s*cli_auth_credentials_store\s*=\s*"[^"]+"/m,
1180
+ 'cli_auth_credentials_store = "file"'
1181
+ );
1182
+ }
1183
+ const trimmed = rawConfig.trim();
1184
+ if (!trimmed) {
1185
+ return 'cli_auth_credentials_store = "file"\n';
1186
+ }
1187
+ return ['cli_auth_credentials_store = "file"', "", trimmed, ""].join("\n");
1188
+ }
1189
+ async function pathExists2(targetPath) {
1190
+ try {
1191
+ await stat4(targetPath);
1192
+ return true;
1193
+ } catch (error) {
1194
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
1195
+ return false;
1196
+ }
1197
+ throw error;
1198
+ }
1199
+ }
1200
+
1201
+ // src/process/run-codex-login.ts
1202
+ import { spawn } from "node:child_process";
1203
+
1204
+ // src/process/codex-launch-spec.ts
1205
+ import { access as access2 } from "node:fs/promises";
1206
+ import path7 from "node:path";
1207
+ async function resolveCodexLaunchSpec(codexBinaryPath, args) {
1208
+ if (process.platform !== "win32") {
1209
+ return {
1210
+ command: codexBinaryPath,
1211
+ args
1212
+ };
1213
+ }
1214
+ const npmShim = await resolveNpmCodexShim(codexBinaryPath);
1215
+ if (npmShim) {
1216
+ return {
1217
+ command: npmShim.nodeBinary,
1218
+ args: [npmShim.codexScript, ...args]
1219
+ };
1220
+ }
1221
+ if (/\.(cmd|bat)$/i.test(codexBinaryPath)) {
1222
+ return {
1223
+ command: process.env.ComSpec ?? "cmd.exe",
1224
+ args: ["/d", "/s", "/c", buildCmdInvocation(codexBinaryPath, args)]
1225
+ };
1226
+ }
1227
+ if (/\.ps1$/i.test(codexBinaryPath)) {
1228
+ return {
1229
+ command: process.env.ComSpec ? "powershell.exe" : "pwsh",
1230
+ args: [
1231
+ "-NoProfile",
1232
+ "-ExecutionPolicy",
1233
+ "Bypass",
1234
+ "-File",
1235
+ codexBinaryPath,
1236
+ ...args
1237
+ ]
1238
+ };
1239
+ }
1240
+ return {
1241
+ command: codexBinaryPath,
1242
+ args
1243
+ };
1244
+ }
1245
+ function buildCmdInvocation(binaryPath, args) {
1246
+ return [quoteForCmd(binaryPath), ...args.map(quoteForCmd)].join(" ");
1247
+ }
1248
+ function quoteForCmd(value) {
1249
+ if (value.length === 0) {
1250
+ return '""';
1251
+ }
1252
+ if (!/[\s"]/u.test(value)) {
1253
+ return value;
1254
+ }
1255
+ return `"${value.replace(/"/g, '""')}"`;
1256
+ }
1257
+ async function resolveNpmCodexShim(codexBinaryPath) {
1258
+ const basename = path7.basename(codexBinaryPath).toLowerCase();
1259
+ if (!["codex", "codex.cmd", "codex.bat", "codex.ps1"].includes(basename)) {
1260
+ return null;
1261
+ }
1262
+ const directory = path7.dirname(codexBinaryPath);
1263
+ const codexScript = path7.join(directory, "node_modules", "@openai", "codex", "bin", "codex.js");
1264
+ if (!await pathExists3(codexScript)) {
1265
+ return null;
1266
+ }
1267
+ const bundledNode = path7.join(directory, "node.exe");
1268
+ return {
1269
+ codexScript,
1270
+ nodeBinary: await pathExists3(bundledNode) ? bundledNode : "node"
1271
+ };
1272
+ }
1273
+ async function pathExists3(filePath) {
1274
+ try {
1275
+ await access2(filePath);
1276
+ return true;
1277
+ } catch {
1278
+ return false;
1279
+ }
1280
+ }
1281
+
1282
+ // src/process/run-codex-login.ts
1283
+ async function runInteractiveCodexLogin(input) {
1284
+ const launchSpec = await resolveCodexLaunchSpec(input.codexBinaryPath, ["login"]);
1285
+ input.logger.info("login.spawn.start", {
1286
+ codexBinaryPath: input.codexBinaryPath,
1287
+ resolvedCommand: launchSpec.command,
1288
+ codexHome: input.codexHome,
1289
+ argv: launchSpec.args,
1290
+ timeoutMs: input.timeoutMs
1291
+ });
1292
+ return new Promise((resolve, reject) => {
1293
+ const child = spawn(launchSpec.command, launchSpec.args, {
1294
+ env: {
1295
+ ...process.env,
1296
+ CODEX_HOME: input.codexHome
1297
+ },
1298
+ shell: false,
1299
+ stdio: "inherit",
1300
+ windowsHide: false
1301
+ });
1302
+ let timedOut = false;
1303
+ let cancelledBySignal = false;
1304
+ let settled = false;
1305
+ const timeoutHandle = setTimeout(() => {
1306
+ timedOut = true;
1307
+ input.logger.warn("login.spawn.timeout", {
1308
+ codexBinaryPath: input.codexBinaryPath,
1309
+ codexHome: input.codexHome,
1310
+ timeoutMs: input.timeoutMs
1311
+ });
1312
+ terminateChild(child);
1313
+ }, input.timeoutMs);
1314
+ const forwardSignal = (signal) => {
1315
+ cancelledBySignal = true;
1316
+ input.logger.warn("login.spawn.parent_signal", {
1317
+ signal,
1318
+ pid: child.pid ?? null
1319
+ });
1320
+ child.kill(signal);
1321
+ };
1322
+ const signalHandlers = {
1323
+ SIGINT: () => forwardSignal("SIGINT"),
1324
+ SIGTERM: () => forwardSignal("SIGTERM")
1325
+ };
1326
+ process.on("SIGINT", signalHandlers.SIGINT);
1327
+ process.on("SIGTERM", signalHandlers.SIGTERM);
1328
+ const cleanup = () => {
1329
+ clearTimeout(timeoutHandle);
1330
+ process.off("SIGINT", signalHandlers.SIGINT);
1331
+ process.off("SIGTERM", signalHandlers.SIGTERM);
1332
+ };
1333
+ child.on("error", (error) => {
1334
+ if (settled) {
1335
+ return;
1336
+ }
1337
+ settled = true;
1338
+ cleanup();
1339
+ input.logger.error("login.spawn.error", {
1340
+ codexBinaryPath: input.codexBinaryPath,
1341
+ codexHome: input.codexHome,
1342
+ message: error.message
1343
+ });
1344
+ reject(error);
1345
+ });
1346
+ child.on("exit", (exitCode2, signal) => {
1347
+ if (settled) {
1348
+ return;
1349
+ }
1350
+ settled = true;
1351
+ cleanup();
1352
+ input.logger.info("login.spawn.complete", {
1353
+ codexBinaryPath: input.codexBinaryPath,
1354
+ codexHome: input.codexHome,
1355
+ exitCode: exitCode2,
1356
+ signal,
1357
+ timedOut,
1358
+ cancelledBySignal
1359
+ });
1360
+ resolve({
1361
+ exitCode: exitCode2,
1362
+ signal,
1363
+ timedOut,
1364
+ timeoutMs: input.timeoutMs,
1365
+ cancelledBySignal
1366
+ });
1367
+ });
1368
+ });
1369
+ }
1370
+ function terminateChild(child) {
1371
+ if (process.platform === "win32") {
1372
+ child.kill();
1373
+ return;
1374
+ }
1375
+ child.kill("SIGTERM");
1376
+ }
1377
+
1378
+ // src/commands/account-add/run-account-add-command.ts
1379
+ var DEFAULT_LOGIN_TIMEOUT_MS = 10 * 60 * 1e3;
1380
+ var ACCOUNT_METADATA_SCHEMA_VERSION = 1;
1381
+ async function runAccountAddCommand(context, argv) {
1382
+ const logger = createLogger({
1383
+ level: context.logging.level,
1384
+ name: "account_add",
1385
+ sink: context.logging.sink
1386
+ });
1387
+ if (argv.includes("--help")) {
1388
+ context.io.stdout.write(`${buildAccountAddHelpText()}
1389
+ `);
1390
+ logger.info("help.rendered");
1391
+ return 0;
1392
+ }
1393
+ const parsed = parseAccountAddArgs(argv);
1394
+ logger.info("command.start", {
1395
+ requestedLabel: parsed.label,
1396
+ timeoutMs: parsed.timeoutMs,
1397
+ codexBinaryPath: context.codexBinary.path,
1398
+ sharedCodexHome: context.paths.sharedCodexHome,
1399
+ accountRoot: context.paths.accountRoot,
1400
+ runtimeRoot: context.paths.runtimeRoot
1401
+ });
1402
+ if (!context.codexBinary.path) {
1403
+ logger.error("command.binary_missing", {
1404
+ candidates: context.codexBinary.candidates,
1405
+ rejectedCandidates: context.codexBinary.rejectedCandidates
1406
+ });
1407
+ throw new Error("Could not find the real `codex` binary on PATH.");
1408
+ }
1409
+ if (context.wrapperConfig.credentialStoreMode !== "file") {
1410
+ logger.error("command.unsupported_credential_store", {
1411
+ credentialStoreMode: context.wrapperConfig.credentialStoreMode,
1412
+ configFilePath: context.wrapperConfig.codexConfigFilePath,
1413
+ reason: context.wrapperConfig.credentialStorePolicyReason
1414
+ });
1415
+ throw new Error(
1416
+ `codexes account add requires cli_auth_credentials_store = "file"; detected ${context.wrapperConfig.credentialStoreMode}.`
1417
+ );
1418
+ }
1419
+ const registry = createAccountRegistry({
1420
+ accountRoot: context.paths.accountRoot,
1421
+ logger,
1422
+ registryFile: context.paths.registryFile
1423
+ });
1424
+ const existingAccounts = await registry.listAccounts();
1425
+ const duplicate = existingAccounts.find(
1426
+ (account) => account.label.toLowerCase() === parsed.label.toLowerCase()
1427
+ );
1428
+ if (duplicate) {
1429
+ logger.warn("command.duplicate_label", {
1430
+ requestedLabel: parsed.label,
1431
+ existingAccountId: duplicate.id
1432
+ });
1433
+ throw new Error(`An account named "${parsed.label}" already exists.`);
1434
+ }
1435
+ const runtimeContract = createRuntimeContract({
1436
+ accountRoot: context.paths.accountRoot,
1437
+ credentialStoreMode: context.wrapperConfig.credentialStoreMode,
1438
+ logger,
1439
+ runtimeRoot: context.paths.runtimeRoot,
1440
+ sharedCodexHome: context.paths.sharedCodexHome
1441
+ });
1442
+ const workspace = await prepareLoginWorkspace({
1443
+ logger,
1444
+ runtimeRoot: context.paths.runtimeRoot,
1445
+ sharedCodexHome: context.paths.sharedCodexHome
1446
+ });
1447
+ let loginResult = null;
1448
+ try {
1449
+ loginResult = await runInteractiveCodexLogin({
1450
+ codexBinaryPath: context.codexBinary.path,
1451
+ codexHome: workspace.codexHome,
1452
+ logger,
1453
+ timeoutMs: parsed.timeoutMs
1454
+ });
1455
+ if (loginResult.exitCode !== 0) {
1456
+ logger.warn("command.login_failed", {
1457
+ exitCode: loginResult.exitCode,
1458
+ signal: loginResult.signal,
1459
+ timedOut: loginResult.timedOut,
1460
+ timeoutMs: loginResult.timeoutMs,
1461
+ cancelledBySignal: loginResult.cancelledBySignal
1462
+ });
1463
+ context.io.stderr.write(buildLoginFailureMessage(loginResult));
1464
+ return loginResult.exitCode ?? 1;
1465
+ }
1466
+ const authSummary = await readAuthSummary(workspace.codexHome, logger);
1467
+ if (!authSummary.present) {
1468
+ logger.error("command.auth_missing_after_login", {
1469
+ codexHome: workspace.codexHome
1470
+ });
1471
+ throw new Error(
1472
+ "codex login completed without creating auth.json in the isolated workspace."
1473
+ );
1474
+ }
1475
+ const account = await registry.addAccount({ label: parsed.label });
1476
+ try {
1477
+ const runtimePaths = resolveAccountRuntimePaths(runtimeContract, account.id);
1478
+ await mkdir5(runtimePaths.accountDirectory, { recursive: true });
1479
+ await copyAccountScopedAuthArtifacts({
1480
+ accountId: account.id,
1481
+ destinationRoot: runtimePaths.accountStateDirectory,
1482
+ logger,
1483
+ runtimeContract,
1484
+ sourceCodexHome: workspace.codexHome
1485
+ });
1486
+ await writeAccountMetadata({
1487
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1488
+ destinationFile: runtimePaths.accountMetadataFile,
1489
+ label: account.label,
1490
+ logger,
1491
+ recordId: account.id,
1492
+ summary: authSummary
1493
+ });
1494
+ logger.info("command.complete", {
1495
+ accountId: account.id,
1496
+ label: account.label,
1497
+ authDirectory: account.authDirectory,
1498
+ authAccountId: authSummary.accountId
1499
+ });
1500
+ context.io.stdout.write(
1501
+ [
1502
+ `Added account "${account.label}"`,
1503
+ ` id: ${account.id}`,
1504
+ ` auth state: ${runtimePaths.accountStateDirectory}`,
1505
+ authSummary.accountId ? ` auth account id: ${authSummary.accountId}` : null
1506
+ ].filter((line) => Boolean(line)).join("\n") + "\n"
1507
+ );
1508
+ return 0;
1509
+ } catch (error) {
1510
+ logger.error("command.persist_failed", {
1511
+ message: error instanceof Error ? error.message : String(error),
1512
+ accountLabel: parsed.label
1513
+ });
1514
+ await cleanupFailedAccountRecord(registry, logger, parsed.label);
1515
+ throw error;
1516
+ }
1517
+ } finally {
1518
+ await cleanupWorkspace(workspace, logger, loginResult);
1519
+ }
1520
+ }
1521
+ function parseAccountAddArgs(argv) {
1522
+ let label = null;
1523
+ let timeoutMs = DEFAULT_LOGIN_TIMEOUT_MS;
1524
+ for (let index = 0; index < argv.length; index += 1) {
1525
+ const token = argv[index];
1526
+ if (!token) {
1527
+ continue;
1528
+ }
1529
+ if (token === "--timeout-ms") {
1530
+ const next = argv[index + 1];
1531
+ if (!next) {
1532
+ throw new Error("Expected a number after --timeout-ms.");
1533
+ }
1534
+ const parsedTimeout = Number.parseInt(next, 10);
1535
+ if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
1536
+ throw new Error(`Invalid timeout: ${next}`);
1537
+ }
1538
+ timeoutMs = parsedTimeout;
1539
+ index += 1;
1540
+ continue;
1541
+ }
1542
+ if (token.startsWith("--timeout-ms=")) {
1543
+ const raw = token.slice("--timeout-ms=".length);
1544
+ const parsedTimeout = Number.parseInt(raw, 10);
1545
+ if (!Number.isFinite(parsedTimeout) || parsedTimeout <= 0) {
1546
+ throw new Error(`Invalid timeout: ${raw}`);
1547
+ }
1548
+ timeoutMs = parsedTimeout;
1549
+ continue;
1550
+ }
1551
+ if (token.startsWith("--")) {
1552
+ throw new Error(`Unknown option for account add: ${token}`);
1553
+ }
1554
+ if (label) {
1555
+ throw new Error(`Unexpected argument for account add: ${token}`);
1556
+ }
1557
+ label = token.trim();
1558
+ }
1559
+ if (!label) {
1560
+ throw new Error(buildAccountAddHelpText());
1561
+ }
1562
+ return { label, timeoutMs };
1563
+ }
1564
+ function buildAccountAddHelpText() {
1565
+ return [
1566
+ "Usage:",
1567
+ " codexes account add <label> [--timeout-ms <milliseconds>]",
1568
+ "",
1569
+ "Examples:",
1570
+ " codexes account add work",
1571
+ " codexes account add personal --timeout-ms 900000"
1572
+ ].join("\n");
1573
+ }
1574
+ async function readAuthSummary(codexHome, logger) {
1575
+ const authFile = path8.join(codexHome, "auth.json");
1576
+ try {
1577
+ const raw = await readFile5(authFile, "utf8");
1578
+ const parsed = JSON.parse(raw);
1579
+ const tokens = typeof parsed.tokens === "object" && parsed.tokens !== null ? parsed.tokens : null;
1580
+ const summary = {
1581
+ present: true,
1582
+ authMode: typeof parsed.auth_mode === "string" ? parsed.auth_mode : null,
1583
+ accountId: typeof tokens?.account_id === "string" ? tokens.account_id : null,
1584
+ lastRefresh: typeof parsed.last_refresh === "string" ? parsed.last_refresh : null
1585
+ };
1586
+ logger.debug("auth_summary.loaded", {
1587
+ authFile,
1588
+ authMode: summary.authMode,
1589
+ accountId: summary.accountId,
1590
+ lastRefresh: summary.lastRefresh
1591
+ });
1592
+ return summary;
1593
+ } catch (error) {
1594
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
1595
+ logger.warn("auth_summary.missing", { authFile });
1596
+ return {
1597
+ present: false,
1598
+ authMode: null,
1599
+ accountId: null,
1600
+ lastRefresh: null
1601
+ };
1602
+ }
1603
+ logger.error("auth_summary.failed", {
1604
+ authFile,
1605
+ message: error instanceof Error ? error.message : String(error)
1606
+ });
1607
+ throw error;
1608
+ }
1609
+ }
1610
+ async function writeAccountMetadata(input) {
1611
+ const metadata = {
1612
+ schemaVersion: ACCOUNT_METADATA_SCHEMA_VERSION,
1613
+ accountId: input.recordId,
1614
+ label: input.label,
1615
+ capturedAt: input.capturedAt,
1616
+ authMode: input.summary.authMode,
1617
+ authAccountId: input.summary.accountId,
1618
+ lastRefresh: input.summary.lastRefresh,
1619
+ loginStatus: "succeeded"
1620
+ };
1621
+ await writeFile4(input.destinationFile, JSON.stringify(metadata, null, 2), "utf8");
1622
+ input.logger.info("account_metadata.written", {
1623
+ destinationFile: input.destinationFile,
1624
+ accountId: metadata.accountId,
1625
+ authAccountId: metadata.authAccountId
1626
+ });
1627
+ }
1628
+ async function cleanupFailedAccountRecord(registry, logger, label) {
1629
+ const accounts = await registry.listAccounts();
1630
+ const account = [...accounts].reverse().find((entry) => entry.label.toLowerCase() === label.toLowerCase());
1631
+ if (!account) {
1632
+ return;
1633
+ }
1634
+ await registry.removeAccount(account.id).catch(() => void 0);
1635
+ await rm2(account.authDirectory, { force: true, recursive: true }).catch(() => void 0);
1636
+ logger.warn("command.rollback_account_record", {
1637
+ accountId: account.id,
1638
+ label: account.label
1639
+ });
1640
+ }
1641
+ async function cleanupWorkspace(workspace, logger, loginResult) {
1642
+ logger.debug("workspace.cleanup.start", {
1643
+ workspaceRoot: workspace.workspaceRoot,
1644
+ codexHome: workspace.codexHome,
1645
+ loginExitCode: loginResult?.exitCode ?? null
1646
+ });
1647
+ await rm2(workspace.workspaceRoot, {
1648
+ force: true,
1649
+ recursive: true
1650
+ }).catch(() => void 0);
1651
+ logger.debug("workspace.cleanup.complete", {
1652
+ workspaceRoot: workspace.workspaceRoot
1653
+ });
1654
+ }
1655
+ function buildLoginFailureMessage(loginResult) {
1656
+ if (loginResult.timedOut) {
1657
+ return `codexes: account login timed out after ${loginResult.timeoutMs}ms.
1658
+ `;
1659
+ }
1660
+ if (loginResult.cancelledBySignal) {
1661
+ return "codexes: account login was cancelled.\n";
1662
+ }
1663
+ return `codexes: account login failed with exit code ${loginResult.exitCode ?? "unknown"}.
1664
+ `;
1665
+ }
1666
+
1667
+ // src/accounts/account-resolution.ts
1668
+ import { readFile as readFile6 } from "node:fs/promises";
1669
+ import path9 from "node:path";
1670
+ function resolveAccountBySelector(input) {
1671
+ const normalizedSelector = input.selector.trim();
1672
+ const matches = input.accounts.filter(
1673
+ (account) => account.id === normalizedSelector || account.label.toLowerCase() === normalizedSelector.toLowerCase()
1674
+ );
1675
+ input.logger.debug("account_resolution.lookup", {
1676
+ selector: normalizedSelector,
1677
+ accountCount: input.accounts.length,
1678
+ matchCount: matches.length,
1679
+ matchedAccountIds: matches.map((account) => account.id)
1680
+ });
1681
+ if (matches.length === 0) {
1682
+ throw new Error(`No account matches "${normalizedSelector}".`);
1683
+ }
1684
+ if (matches.length > 1) {
1685
+ throw new Error(
1686
+ `Selector "${normalizedSelector}" matched multiple accounts; use the account id instead.`
1687
+ );
1688
+ }
1689
+ const [match] = matches;
1690
+ if (!match) {
1691
+ throw new Error(`No account matches "${normalizedSelector}".`);
1692
+ }
1693
+ return match;
1694
+ }
1695
+ async function buildAccountPresentations(input) {
1696
+ const presentations = [];
1697
+ for (const account of input.accounts) {
1698
+ presentations.push({
1699
+ account,
1700
+ ...await readAccountMetadataSummary(account, input.logger)
1701
+ });
1702
+ }
1703
+ return presentations;
1704
+ }
1705
+ async function readAccountMetadataSummary(account, logger) {
1706
+ const metadataFile = path9.join(account.authDirectory, "account.json");
1707
+ try {
1708
+ const raw = await readFile6(metadataFile, "utf8");
1709
+ const parsed = JSON.parse(raw);
1710
+ const summary = {
1711
+ authAccountId: typeof parsed.authAccountId === "string" ? parsed.authAccountId : null,
1712
+ authMode: typeof parsed.authMode === "string" ? parsed.authMode : null
1713
+ };
1714
+ logger.debug("account_resolution.metadata_loaded", {
1715
+ accountId: account.id,
1716
+ metadataFile,
1717
+ authAccountId: summary.authAccountId,
1718
+ authMode: summary.authMode
1719
+ });
1720
+ return summary;
1721
+ } catch (error) {
1722
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
1723
+ logger.debug("account_resolution.metadata_missing", {
1724
+ accountId: account.id,
1725
+ metadataFile
1726
+ });
1727
+ return { authAccountId: null, authMode: null };
1728
+ }
1729
+ logger.warn("account_resolution.metadata_failed", {
1730
+ accountId: account.id,
1731
+ metadataFile,
1732
+ message: error instanceof Error ? error.message : String(error)
1733
+ });
1734
+ return { authAccountId: null, authMode: null };
1735
+ }
1736
+ }
1737
+
1738
+ // src/commands/account-list/run-account-list-command.ts
1739
+ async function runAccountListCommand(context) {
1740
+ const logger = createLogger({
1741
+ level: context.logging.level,
1742
+ name: "account_list",
1743
+ sink: context.logging.sink
1744
+ });
1745
+ const registry = createAccountRegistry({
1746
+ accountRoot: context.paths.accountRoot,
1747
+ logger,
1748
+ registryFile: context.paths.registryFile
1749
+ });
1750
+ const [accounts, defaultAccount] = await Promise.all([
1751
+ registry.listAccounts(),
1752
+ registry.getDefaultAccount()
1753
+ ]);
1754
+ logger.info("command.start", {
1755
+ accountCount: accounts.length,
1756
+ defaultAccountId: defaultAccount?.id ?? null
1757
+ });
1758
+ if (accounts.length === 0) {
1759
+ context.io.stdout.write(
1760
+ [
1761
+ "No accounts configured.",
1762
+ "Add one with: codexes account add <label>"
1763
+ ].join("\n") + "\n"
1764
+ );
1765
+ logger.info("command.empty");
1766
+ return 0;
1767
+ }
1768
+ const presentations = await buildAccountPresentations({ accounts, logger });
1769
+ const lines = presentations.map(({ account, authAccountId, authMode }) => {
1770
+ const markers = [
1771
+ defaultAccount?.id === account.id ? "default" : null,
1772
+ authMode ? `auth=${authMode}` : null,
1773
+ authAccountId ? `authAccountId=${authAccountId}` : null
1774
+ ].filter((value) => Boolean(value)).join(", ");
1775
+ return `${defaultAccount?.id === account.id ? "*" : " "} ${account.label} (${account.id})${markers ? ` [${markers}]` : ""}`;
1776
+ });
1777
+ context.io.stdout.write(`${lines.join("\n")}
1778
+ `);
1779
+ logger.info("command.complete", {
1780
+ accountIds: presentations.map(({ account }) => account.id)
1781
+ });
1782
+ return 0;
1783
+ }
1784
+
1785
+ // src/commands/account-remove/run-account-remove-command.ts
1786
+ import { rm as rm3 } from "node:fs/promises";
1787
+ async function runAccountRemoveCommand(context, argv) {
1788
+ const logger = createLogger({
1789
+ level: context.logging.level,
1790
+ name: "account_remove",
1791
+ sink: context.logging.sink
1792
+ });
1793
+ if (argv.includes("--help")) {
1794
+ context.io.stdout.write(`${buildAccountRemoveHelpText()}
1795
+ `);
1796
+ logger.info("help.rendered");
1797
+ return 0;
1798
+ }
1799
+ const selector = argv[0]?.trim();
1800
+ if (!selector || argv.length > 1) {
1801
+ throw new Error(buildAccountRemoveHelpText());
1802
+ }
1803
+ const registry = createAccountRegistry({
1804
+ accountRoot: context.paths.accountRoot,
1805
+ logger,
1806
+ registryFile: context.paths.registryFile
1807
+ });
1808
+ const accounts = await registry.listAccounts();
1809
+ if (accounts.length === 0) {
1810
+ context.io.stdout.write("No accounts configured.\n");
1811
+ logger.info("command.empty");
1812
+ return 0;
1813
+ }
1814
+ const account = resolveAccountBySelector({ accounts, logger, selector });
1815
+ logger.info("command.start", {
1816
+ requestedSelector: selector,
1817
+ resolvedAccountId: account.id,
1818
+ label: account.label
1819
+ });
1820
+ await registry.removeAccount(account.id);
1821
+ await rm3(account.authDirectory, { force: true, recursive: true });
1822
+ context.io.stdout.write(`Removed account "${account.label}" (${account.id}).
1823
+ `);
1824
+ logger.info("command.complete", {
1825
+ requestedSelector: selector,
1826
+ resolvedAccountId: account.id
1827
+ });
1828
+ return 0;
1829
+ }
1830
+ function buildAccountRemoveHelpText() {
1831
+ return [
1832
+ "Usage:",
1833
+ " codexes account remove <account-id-or-label>"
1834
+ ].join("\n");
1835
+ }
1836
+
1837
+ // src/commands/account-use/run-account-use-command.ts
1838
+ async function runAccountUseCommand(context, argv) {
1839
+ const logger = createLogger({
1840
+ level: context.logging.level,
1841
+ name: "account_use",
1842
+ sink: context.logging.sink
1843
+ });
1844
+ if (argv.includes("--help")) {
1845
+ context.io.stdout.write(`${buildAccountUseHelpText()}
1846
+ `);
1847
+ logger.info("help.rendered");
1848
+ return 0;
1849
+ }
1850
+ const registry = createAccountRegistry({
1851
+ accountRoot: context.paths.accountRoot,
1852
+ logger,
1853
+ registryFile: context.paths.registryFile
1854
+ });
1855
+ const accounts = await registry.listAccounts();
1856
+ if (accounts.length === 0) {
1857
+ context.io.stdout.write(
1858
+ [
1859
+ "No accounts configured.",
1860
+ "Add one with: codexes account add <label>"
1861
+ ].join("\n") + "\n"
1862
+ );
1863
+ logger.info("command.empty");
1864
+ return 0;
1865
+ }
1866
+ const selector = argv[0]?.trim() ?? null;
1867
+ if (argv.length > 1) {
1868
+ throw new Error(buildAccountUseHelpText());
1869
+ }
1870
+ let targetAccount = null;
1871
+ if (!selector) {
1872
+ if (accounts.length === 1) {
1873
+ const [singleAccount] = accounts;
1874
+ if (!singleAccount) {
1875
+ throw new Error("No accounts configured.");
1876
+ }
1877
+ targetAccount = singleAccount;
1878
+ logger.info("command.single_account_default", {
1879
+ resolvedAccountId: targetAccount.id,
1880
+ label: targetAccount.label
1881
+ });
1882
+ } else {
1883
+ throw new Error(
1884
+ "Multiple accounts exist. Specify which one to use: codexes account use <account-id-or-label>"
1885
+ );
1886
+ }
1887
+ } else {
1888
+ targetAccount = resolveAccountBySelector({ accounts, logger, selector });
1889
+ }
1890
+ if (!targetAccount) {
1891
+ throw new Error("Could not resolve the account to use.");
1892
+ }
1893
+ logger.info("command.start", {
1894
+ requestedSelector: selector,
1895
+ resolvedAccountId: targetAccount.id,
1896
+ label: targetAccount.label
1897
+ });
1898
+ const selectedAccount = await registry.selectAccount(targetAccount.id);
1899
+ context.io.stdout.write(
1900
+ `Using account "${selectedAccount.label}" (${selectedAccount.id}) as the default.
1901
+ `
1902
+ );
1903
+ logger.info("command.complete", {
1904
+ requestedSelector: selector,
1905
+ resolvedAccountId: selectedAccount.id
1906
+ });
1907
+ return 0;
1908
+ }
1909
+ function buildAccountUseHelpText() {
1910
+ return [
1911
+ "Usage:",
1912
+ " codexes account use <account-id-or-label>",
1913
+ " codexes account use",
1914
+ "",
1915
+ "When only one account exists, `codexes account use` selects it automatically."
1916
+ ].join("\n");
1917
+ }
1918
+
1919
+ // src/runtime/lock/runtime-lock.ts
1920
+ import os3 from "node:os";
1921
+ import path10 from "node:path";
1922
+ import { mkdir as mkdir6, readFile as readFile7, rm as rm4, stat as stat5, writeFile as writeFile5 } from "node:fs/promises";
1923
+ var DEFAULT_WAIT_TIMEOUT_MS = 15e3;
1924
+ var DEFAULT_STALE_LOCK_MS = 5 * 60 * 1e3;
1925
+ var DEFAULT_POLL_INTERVAL_MS = 250;
1926
+ async function acquireRuntimeLock(input) {
1927
+ const lockRoot = path10.join(input.runtimeRoot, "lock");
1928
+ const ownerFile = path10.join(lockRoot, "owner.json");
1929
+ const waitTimeoutMs = input.waitTimeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
1930
+ const staleLockMs = input.staleLockMs ?? DEFAULT_STALE_LOCK_MS;
1931
+ const pollIntervalMs = input.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
1932
+ const startedAt = Date.now();
1933
+ input.logger.info("runtime_lock.acquire.start", {
1934
+ lockRoot,
1935
+ waitTimeoutMs,
1936
+ staleLockMs,
1937
+ pollIntervalMs
1938
+ });
1939
+ while (true) {
1940
+ try {
1941
+ await mkdir6(lockRoot);
1942
+ const owner = {
1943
+ pid: process.pid,
1944
+ host: os3.hostname(),
1945
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1946
+ };
1947
+ await writeFile5(ownerFile, JSON.stringify(owner, null, 2), "utf8");
1948
+ input.logger.info("runtime_lock.acquire.complete", {
1949
+ lockRoot,
1950
+ waitedMs: Date.now() - startedAt,
1951
+ owner
1952
+ });
1953
+ return {
1954
+ async release() {
1955
+ input.logger.info("runtime_lock.release.start", { lockRoot });
1956
+ await rm4(lockRoot, { force: true, recursive: true }).catch(() => void 0);
1957
+ input.logger.info("runtime_lock.release.complete", { lockRoot });
1958
+ }
1959
+ };
1960
+ } catch (error) {
1961
+ if (!isAlreadyExistsError(error)) {
1962
+ input.logger.error("runtime_lock.acquire.failed", {
1963
+ lockRoot,
1964
+ message: error instanceof Error ? error.message : String(error)
1965
+ });
1966
+ throw error;
1967
+ }
1968
+ }
1969
+ const lockAgeMs = await readLockAgeMs(lockRoot, ownerFile);
1970
+ if (lockAgeMs !== null && lockAgeMs > staleLockMs) {
1971
+ input.logger.warn("runtime_lock.stale_detected", {
1972
+ lockRoot,
1973
+ lockAgeMs
1974
+ });
1975
+ await rm4(lockRoot, { force: true, recursive: true }).catch(() => void 0);
1976
+ continue;
1977
+ }
1978
+ const waitedMs = Date.now() - startedAt;
1979
+ input.logger.debug("runtime_lock.acquire.waiting", {
1980
+ lockRoot,
1981
+ waitedMs,
1982
+ lockAgeMs
1983
+ });
1984
+ if (waitedMs >= waitTimeoutMs) {
1985
+ input.logger.error("runtime_lock.acquire.timeout", {
1986
+ lockRoot,
1987
+ waitedMs,
1988
+ lockAgeMs
1989
+ });
1990
+ throw new Error(
1991
+ `Timed out waiting for the shared runtime lock after ${waitTimeoutMs}ms.`
1992
+ );
1993
+ }
1994
+ await sleep(pollIntervalMs);
1995
+ }
1996
+ }
1997
+ async function readLockAgeMs(lockRoot, ownerFile) {
1998
+ const ownerContents = await readFile7(ownerFile, "utf8").catch(() => null);
1999
+ if (ownerContents) {
2000
+ const parsed = JSON.parse(ownerContents);
2001
+ if (typeof parsed.createdAt === "string") {
2002
+ return Date.now() - new Date(parsed.createdAt).getTime();
2003
+ }
2004
+ }
2005
+ const lockStats = await stat5(lockRoot).catch(() => null);
2006
+ return lockStats ? Date.now() - lockStats.mtimeMs : null;
2007
+ }
2008
+ function isAlreadyExistsError(error) {
2009
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
2010
+ }
2011
+ function sleep(durationMs) {
2012
+ return new Promise((resolve) => {
2013
+ setTimeout(resolve, durationMs);
2014
+ });
2015
+ }
2016
+
2017
+ // src/runtime/activate-account/activate-account.ts
2018
+ import { copyFile as copyFile3, cp as cp3, mkdir as mkdir7, readFile as readFile8, rm as rm5, stat as stat6 } from "node:fs/promises";
2019
+ import path11 from "node:path";
2020
+ import { createHash } from "node:crypto";
2021
+ async function activateAccountIntoSharedRuntime(input) {
2022
+ const runtimePaths = resolveAccountRuntimePaths(input.runtimeContract, input.account.id);
2023
+ const accountStateRoot = runtimePaths.accountStateDirectory;
2024
+ const backupRoot = path11.join(runtimePaths.runtimeBackupDirectory, "active");
2025
+ input.logger.info("account_activation.start", {
2026
+ accountId: input.account.id,
2027
+ label: input.account.label,
2028
+ accountStateRoot,
2029
+ sharedCodexHome: input.sharedCodexHome,
2030
+ backupRoot
2031
+ });
2032
+ await rm5(backupRoot, { force: true, recursive: true }).catch(() => void 0);
2033
+ await mkdir7(backupRoot, { recursive: true });
2034
+ const accountRules = input.runtimeContract.fileRules.filter(
2035
+ (rule) => rule.classification === "account"
2036
+ );
2037
+ const authSourcePath = path11.join(accountStateRoot, "auth.json");
2038
+ if (!await pathExists4(authSourcePath)) {
2039
+ input.logger.error("account_activation.missing_auth", {
2040
+ accountId: input.account.id,
2041
+ authSourcePath
2042
+ });
2043
+ throw new Error(
2044
+ `Account "${input.account.label}" has no stored auth.json; add the account again.`
2045
+ );
2046
+ }
2047
+ try {
2048
+ for (const rule of accountRules) {
2049
+ await backupRuntimeArtifact({
2050
+ backupRoot,
2051
+ logger: input.logger,
2052
+ rule,
2053
+ sharedCodexHome: input.sharedCodexHome
2054
+ });
2055
+ await replaceRuntimeArtifact({
2056
+ accountStateRoot,
2057
+ logger: input.logger,
2058
+ rule,
2059
+ sharedCodexHome: input.sharedCodexHome
2060
+ });
2061
+ }
2062
+ } catch (error) {
2063
+ input.logger.error("account_activation.failed", {
2064
+ accountId: input.account.id,
2065
+ message: error instanceof Error ? error.message : String(error)
2066
+ });
2067
+ await restoreSharedRuntimeFromBackup({
2068
+ account: input.account,
2069
+ backupRoot,
2070
+ logger: input.logger,
2071
+ runtimeContract: input.runtimeContract,
2072
+ sharedCodexHome: input.sharedCodexHome
2073
+ });
2074
+ throw error;
2075
+ }
2076
+ input.logger.info("account_activation.complete", {
2077
+ accountId: input.account.id,
2078
+ sharedCodexHome: input.sharedCodexHome
2079
+ });
2080
+ return {
2081
+ account: input.account,
2082
+ backupRoot,
2083
+ runtimeContract: input.runtimeContract,
2084
+ sharedCodexHome: input.sharedCodexHome,
2085
+ sourceAccountStateRoot: accountStateRoot
2086
+ };
2087
+ }
2088
+ async function syncSharedRuntimeBackToAccount(input) {
2089
+ const accountRules = input.session.runtimeContract.fileRules.filter(
2090
+ (rule) => rule.classification === "account"
2091
+ );
2092
+ input.logger.info("account_sync.start", {
2093
+ accountId: input.session.account.id,
2094
+ sharedCodexHome: input.session.sharedCodexHome,
2095
+ accountStateRoot: input.session.sourceAccountStateRoot
2096
+ });
2097
+ for (const rule of accountRules) {
2098
+ await syncRuntimeArtifact({
2099
+ accountStateRoot: input.session.sourceAccountStateRoot,
2100
+ logger: input.logger,
2101
+ rule,
2102
+ sharedCodexHome: input.session.sharedCodexHome
2103
+ });
2104
+ }
2105
+ input.logger.info("account_sync.complete", {
2106
+ accountId: input.session.account.id
2107
+ });
2108
+ }
2109
+ async function restoreSharedRuntimeFromBackup(input) {
2110
+ const accountRules = input.runtimeContract.fileRules.filter(
2111
+ (rule) => rule.classification === "account"
2112
+ );
2113
+ input.logger.warn("account_activation.restore.start", {
2114
+ accountId: input.account.id,
2115
+ backupRoot: input.backupRoot,
2116
+ sharedCodexHome: input.sharedCodexHome
2117
+ });
2118
+ for (const rule of accountRules) {
2119
+ await restoreRuntimeArtifact({
2120
+ backupRoot: input.backupRoot,
2121
+ logger: input.logger,
2122
+ rule,
2123
+ sharedCodexHome: input.sharedCodexHome
2124
+ });
2125
+ }
2126
+ input.logger.warn("account_activation.restore.complete", {
2127
+ accountId: input.account.id
2128
+ });
2129
+ }
2130
+ async function backupRuntimeArtifact(input) {
2131
+ const sourcePath = path11.join(input.sharedCodexHome, normalizedPattern(input.rule.pathPattern));
2132
+ const backupPath = path11.join(input.backupRoot, normalizedPattern(input.rule.pathPattern));
2133
+ if (!await pathExists4(sourcePath)) {
2134
+ input.logger.debug("account_activation.backup.skip", {
2135
+ pathPattern: input.rule.pathPattern,
2136
+ sourcePath,
2137
+ reason: "missing"
2138
+ });
2139
+ return;
2140
+ }
2141
+ await mkdir7(path11.dirname(backupPath), { recursive: true });
2142
+ if (isDirectoryPattern(input.rule)) {
2143
+ await cp3(sourcePath, backupPath, { recursive: true });
2144
+ } else {
2145
+ await copyFile3(sourcePath, backupPath);
2146
+ }
2147
+ input.logger.debug("account_activation.backup.complete", {
2148
+ pathPattern: input.rule.pathPattern,
2149
+ sourcePath,
2150
+ backupPath
2151
+ });
2152
+ }
2153
+ async function replaceRuntimeArtifact(input) {
2154
+ const sourcePath = path11.join(input.accountStateRoot, normalizedPattern(input.rule.pathPattern));
2155
+ const targetPath = path11.join(input.sharedCodexHome, normalizedPattern(input.rule.pathPattern));
2156
+ await rm5(targetPath, { force: true, recursive: true }).catch(() => void 0);
2157
+ if (!await pathExists4(sourcePath)) {
2158
+ input.logger.debug("account_activation.replace.skip", {
2159
+ pathPattern: input.rule.pathPattern,
2160
+ sourcePath,
2161
+ reason: "missing"
2162
+ });
2163
+ return;
2164
+ }
2165
+ await mkdir7(path11.dirname(targetPath), { recursive: true });
2166
+ if (isDirectoryPattern(input.rule)) {
2167
+ await cp3(sourcePath, targetPath, { recursive: true });
2168
+ } else {
2169
+ await copyFile3(sourcePath, targetPath);
2170
+ }
2171
+ input.logger.debug("account_activation.replace.complete", {
2172
+ pathPattern: input.rule.pathPattern,
2173
+ sourcePath,
2174
+ targetPath
2175
+ });
2176
+ }
2177
+ async function syncRuntimeArtifact(input) {
2178
+ const sourcePath = path11.join(input.sharedCodexHome, normalizedPattern(input.rule.pathPattern));
2179
+ const targetPath = path11.join(input.accountStateRoot, normalizedPattern(input.rule.pathPattern));
2180
+ if (!await pathExists4(sourcePath)) {
2181
+ input.logger.debug("account_sync.skip", {
2182
+ pathPattern: input.rule.pathPattern,
2183
+ sourcePath,
2184
+ reason: "missing"
2185
+ });
2186
+ return;
2187
+ }
2188
+ const changed = await hasArtifactChanged(sourcePath, targetPath, isDirectoryPattern(input.rule));
2189
+ if (!changed) {
2190
+ input.logger.debug("account_sync.no_change", {
2191
+ pathPattern: input.rule.pathPattern,
2192
+ sourcePath,
2193
+ targetPath
2194
+ });
2195
+ return;
2196
+ }
2197
+ await rm5(targetPath, { force: true, recursive: true }).catch(() => void 0);
2198
+ await mkdir7(path11.dirname(targetPath), { recursive: true });
2199
+ if (isDirectoryPattern(input.rule)) {
2200
+ await cp3(sourcePath, targetPath, { recursive: true });
2201
+ } else {
2202
+ await copyFile3(sourcePath, targetPath);
2203
+ }
2204
+ input.logger.info("account_sync.updated", {
2205
+ pathPattern: input.rule.pathPattern,
2206
+ sourcePath,
2207
+ targetPath
2208
+ });
2209
+ }
2210
+ async function restoreRuntimeArtifact(input) {
2211
+ const backupPath = path11.join(input.backupRoot, normalizedPattern(input.rule.pathPattern));
2212
+ const targetPath = path11.join(input.sharedCodexHome, normalizedPattern(input.rule.pathPattern));
2213
+ await rm5(targetPath, { force: true, recursive: true }).catch(() => void 0);
2214
+ if (!await pathExists4(backupPath)) {
2215
+ input.logger.debug("account_activation.restore.skip", {
2216
+ pathPattern: input.rule.pathPattern,
2217
+ backupPath,
2218
+ reason: "missing"
2219
+ });
2220
+ return;
2221
+ }
2222
+ await mkdir7(path11.dirname(targetPath), { recursive: true });
2223
+ if (isDirectoryPattern(input.rule)) {
2224
+ await cp3(backupPath, targetPath, { recursive: true });
2225
+ } else {
2226
+ await copyFile3(backupPath, targetPath);
2227
+ }
2228
+ input.logger.debug("account_activation.restore.complete_artifact", {
2229
+ pathPattern: input.rule.pathPattern,
2230
+ backupPath,
2231
+ targetPath
2232
+ });
2233
+ }
2234
+ function isDirectoryPattern(rule) {
2235
+ return rule.pathPattern.endsWith("/**");
2236
+ }
2237
+ function normalizedPattern(pattern) {
2238
+ return pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern;
2239
+ }
2240
+ async function hasArtifactChanged(sourcePath, targetPath, isDirectory) {
2241
+ if (!await pathExists4(targetPath)) {
2242
+ return true;
2243
+ }
2244
+ if (isDirectory) {
2245
+ const [sourceHash2, targetHash2] = await Promise.all([
2246
+ hashDirectory(sourcePath),
2247
+ hashDirectory(targetPath)
2248
+ ]);
2249
+ return sourceHash2 !== targetHash2;
2250
+ }
2251
+ const [sourceHash, targetHash] = await Promise.all([
2252
+ hashFile(sourcePath),
2253
+ hashFile(targetPath)
2254
+ ]);
2255
+ return sourceHash !== targetHash;
2256
+ }
2257
+ async function hashFile(filePath) {
2258
+ const content = await readFile8(filePath);
2259
+ return createHash("sha256").update(content).digest("hex");
2260
+ }
2261
+ async function hashDirectory(directoryPath) {
2262
+ const entries = await collectFiles(directoryPath);
2263
+ const hash = createHash("sha256");
2264
+ for (const entry of entries.sort()) {
2265
+ hash.update(entry.relativePath);
2266
+ hash.update(await readFile8(entry.absolutePath));
2267
+ }
2268
+ return hash.digest("hex");
2269
+ }
2270
+ async function collectFiles(root) {
2271
+ const rootStats = await stat6(root).catch(() => null);
2272
+ if (!rootStats) {
2273
+ return [];
2274
+ }
2275
+ if (!rootStats.isDirectory()) {
2276
+ return [{ absolutePath: root, relativePath: path11.basename(root) }];
2277
+ }
2278
+ const results = [];
2279
+ const stack = [root];
2280
+ while (stack.length > 0) {
2281
+ const current = stack.pop();
2282
+ if (!current) {
2283
+ continue;
2284
+ }
2285
+ const entries = await import("node:fs/promises").then(
2286
+ (fs) => fs.readdir(current, { withFileTypes: true })
2287
+ );
2288
+ for (const entry of entries) {
2289
+ const absolutePath = path11.join(current, entry.name);
2290
+ if (entry.isDirectory()) {
2291
+ stack.push(absolutePath);
2292
+ continue;
2293
+ }
2294
+ if (entry.isFile()) {
2295
+ results.push({
2296
+ absolutePath,
2297
+ relativePath: path11.relative(root, absolutePath).split(path11.sep).join("/")
2298
+ });
2299
+ }
2300
+ }
2301
+ }
2302
+ return results;
2303
+ }
2304
+ async function pathExists4(targetPath) {
2305
+ try {
2306
+ await stat6(targetPath);
2307
+ return true;
2308
+ } catch (error) {
2309
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
2310
+ return false;
2311
+ }
2312
+ throw error;
2313
+ }
2314
+ }
2315
+
2316
+ // src/process/spawn-codex-command.ts
2317
+ import { spawn as spawn2 } from "node:child_process";
2318
+ async function spawnCodexCommand(input) {
2319
+ const launchSpec = await resolveCodexLaunchSpec(input.codexBinaryPath, input.argv);
2320
+ input.logger.info("spawn_codex.start", {
2321
+ codexBinaryPath: input.codexBinaryPath,
2322
+ resolvedCommand: launchSpec.command,
2323
+ codexHome: input.codexHome,
2324
+ argv: launchSpec.args,
2325
+ stdinIsTTY: process.stdin.isTTY ?? false,
2326
+ stdoutIsTTY: process.stdout.isTTY ?? false,
2327
+ stderrIsTTY: process.stderr.isTTY ?? false
2328
+ });
2329
+ return new Promise((resolve, reject) => {
2330
+ const child = spawn2(launchSpec.command, launchSpec.args, {
2331
+ env: {
2332
+ ...process.env,
2333
+ CODEX_HOME: input.codexHome
2334
+ },
2335
+ shell: false,
2336
+ stdio: "inherit",
2337
+ windowsHide: false
2338
+ });
2339
+ let settled = false;
2340
+ const forwardSignal = (signal) => {
2341
+ input.logger.warn("spawn_codex.parent_signal", {
2342
+ signal,
2343
+ pid: child.pid ?? null
2344
+ });
2345
+ child.kill(signal);
2346
+ };
2347
+ const signalHandlers = {
2348
+ SIGINT: () => forwardSignal("SIGINT"),
2349
+ SIGTERM: () => forwardSignal("SIGTERM")
2350
+ };
2351
+ process.on("SIGINT", signalHandlers.SIGINT);
2352
+ process.on("SIGTERM", signalHandlers.SIGTERM);
2353
+ const cleanup = () => {
2354
+ process.off("SIGINT", signalHandlers.SIGINT);
2355
+ process.off("SIGTERM", signalHandlers.SIGTERM);
2356
+ };
2357
+ child.on("error", (error) => {
2358
+ if (settled) {
2359
+ return;
2360
+ }
2361
+ settled = true;
2362
+ cleanup();
2363
+ input.logger.error("spawn_codex.error", {
2364
+ codexBinaryPath: input.codexBinaryPath,
2365
+ message: error.message
2366
+ });
2367
+ reject(error);
2368
+ });
2369
+ child.on("exit", (exitCode2, signal) => {
2370
+ if (settled) {
2371
+ return;
2372
+ }
2373
+ settled = true;
2374
+ cleanup();
2375
+ input.logger.info("spawn_codex.complete", {
2376
+ codexBinaryPath: input.codexBinaryPath,
2377
+ exitCode: exitCode2,
2378
+ signal
2379
+ });
2380
+ resolve(exitCode2 ?? 1);
2381
+ });
2382
+ });
2383
+ }
2384
+
2385
+ // src/selection/account-auth-state.ts
2386
+ import { readFile as readFile9 } from "node:fs/promises";
2387
+ import path12 from "node:path";
2388
+ async function readAccountAuthState(input) {
2389
+ const filePath = path12.join(input.account.authDirectory, "state", "auth.json");
2390
+ input.logger.debug("selection.account_auth_state.read_start", {
2391
+ accountId: input.account.id,
2392
+ label: input.account.label,
2393
+ filePath
2394
+ });
2395
+ try {
2396
+ const raw = await readFile9(filePath, "utf8");
2397
+ const parsed = JSON.parse(raw);
2398
+ if (!isRecord(parsed)) {
2399
+ input.logger.warn("selection.account_auth_state.unsupported_shape", {
2400
+ accountId: input.account.id,
2401
+ label: input.account.label,
2402
+ filePath,
2403
+ topLevelType: typeof parsed
2404
+ });
2405
+ return {
2406
+ ok: false,
2407
+ category: "unsupported-auth-shape",
2408
+ filePath,
2409
+ message: "auth.json is not a JSON object."
2410
+ };
2411
+ }
2412
+ const accessToken = resolveString(
2413
+ parsed.access_token,
2414
+ getNestedString(parsed, ["tokens", "access_token"]),
2415
+ getNestedString(parsed, ["tokens", "accessToken"])
2416
+ );
2417
+ if (!accessToken) {
2418
+ input.logger.warn("selection.account_auth_state.access_token_missing", {
2419
+ accountId: input.account.id,
2420
+ label: input.account.label,
2421
+ filePath,
2422
+ hasTokensObject: isRecord(parsed.tokens)
2423
+ });
2424
+ return {
2425
+ ok: false,
2426
+ category: "missing-access-token",
2427
+ filePath,
2428
+ message: "auth.json does not contain an access_token."
2429
+ };
2430
+ }
2431
+ const result = {
2432
+ ok: true,
2433
+ filePath,
2434
+ state: {
2435
+ accessToken,
2436
+ accountId: resolveString(
2437
+ parsed.account_id,
2438
+ parsed.accountId,
2439
+ getNestedString(parsed, ["tokens", "account_id"]),
2440
+ getNestedString(parsed, ["tokens", "accountId"])
2441
+ ),
2442
+ authMode: resolveString(
2443
+ parsed.auth_mode,
2444
+ parsed.authMode,
2445
+ getNestedString(parsed, ["tokens", "auth_mode"]),
2446
+ getNestedString(parsed, ["tokens", "authMode"])
2447
+ ),
2448
+ lastRefresh: resolveString(
2449
+ parsed.last_refresh,
2450
+ parsed.lastRefresh,
2451
+ parsed.refresh_at,
2452
+ parsed.refreshAt
2453
+ )
2454
+ }
2455
+ };
2456
+ input.logger.debug("selection.account_auth_state.read_complete", {
2457
+ accountId: input.account.id,
2458
+ label: input.account.label,
2459
+ filePath,
2460
+ hasAccessToken: true,
2461
+ authAccountId: result.state.accountId,
2462
+ authMode: result.state.authMode,
2463
+ hasRefreshMetadata: result.state.lastRefresh !== null
2464
+ });
2465
+ return result;
2466
+ } catch (error) {
2467
+ if (isNodeErrorWithCode(error, "ENOENT")) {
2468
+ input.logger.warn("selection.account_auth_state.missing_file", {
2469
+ accountId: input.account.id,
2470
+ label: input.account.label,
2471
+ filePath
2472
+ });
2473
+ return {
2474
+ ok: false,
2475
+ category: "missing-file",
2476
+ filePath,
2477
+ message: "auth.json was not found for the account profile."
2478
+ };
2479
+ }
2480
+ if (error instanceof SyntaxError) {
2481
+ input.logger.warn("selection.account_auth_state.malformed_json", {
2482
+ accountId: input.account.id,
2483
+ label: input.account.label,
2484
+ filePath,
2485
+ message: error.message
2486
+ });
2487
+ return {
2488
+ ok: false,
2489
+ category: "malformed-json",
2490
+ filePath,
2491
+ message: error.message
2492
+ };
2493
+ }
2494
+ input.logger.warn("selection.account_auth_state.unsupported_shape", {
2495
+ accountId: input.account.id,
2496
+ label: input.account.label,
2497
+ filePath,
2498
+ message: error instanceof Error ? error.message : String(error)
2499
+ });
2500
+ return {
2501
+ ok: false,
2502
+ category: "unsupported-auth-shape",
2503
+ filePath,
2504
+ message: error instanceof Error ? error.message : String(error)
2505
+ };
2506
+ }
2507
+ }
2508
+ function getNestedString(value, pathParts) {
2509
+ let current = value;
2510
+ for (const part of pathParts) {
2511
+ if (!isRecord(current) || typeof current[part] === "undefined") {
2512
+ return null;
2513
+ }
2514
+ current = current[part];
2515
+ }
2516
+ return typeof current === "string" && current.trim().length > 0 ? current : null;
2517
+ }
2518
+ function resolveString(...values) {
2519
+ for (const value of values) {
2520
+ if (typeof value === "string" && value.trim().length > 0) {
2521
+ return value;
2522
+ }
2523
+ }
2524
+ return null;
2525
+ }
2526
+ function isRecord(value) {
2527
+ return typeof value === "object" && value !== null;
2528
+ }
2529
+ function isNodeErrorWithCode(error, code) {
2530
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
2531
+ }
2532
+
2533
+ // src/selection/usage-normalize.ts
2534
+ function normalizeWhamUsageResponse(input) {
2535
+ input.logger.debug("selection.usage_normalize.start", {
2536
+ accountIdHint: input.accountIdHint ?? null,
2537
+ topLevelKeys: Object.keys(input.raw).sort()
2538
+ });
2539
+ const daily = normalizeUsageWindow({
2540
+ accountIdHint: input.accountIdHint,
2541
+ logger: input.logger,
2542
+ raw: resolveUsageWindow(input.raw, "daily"),
2543
+ window: "daily"
2544
+ });
2545
+ const weekly = normalizeUsageWindow({
2546
+ accountIdHint: input.accountIdHint,
2547
+ logger: input.logger,
2548
+ raw: resolveUsageWindow(input.raw, "weekly"),
2549
+ window: "weekly"
2550
+ });
2551
+ const accountId = pickString(input.raw.account_id, input.raw.accountId, input.accountIdHint);
2552
+ const allowed = typeof input.raw.allowed === "boolean" ? input.raw.allowed : true;
2553
+ const limitReached = typeof input.raw.limit_reached === "boolean" ? input.raw.limit_reached : daily.limitReached || weekly.limitReached;
2554
+ const status = classifyUsageStatus({
2555
+ allowed,
2556
+ dailyRemaining: daily.remaining,
2557
+ limitReached,
2558
+ weeklyRemaining: weekly.remaining
2559
+ });
2560
+ const snapshot = {
2561
+ accountId,
2562
+ allowed,
2563
+ limitReached,
2564
+ dailyRemaining: daily.remaining,
2565
+ weeklyRemaining: weekly.remaining,
2566
+ dailyResetsAt: daily.resetsAt,
2567
+ weeklyResetsAt: weekly.resetsAt,
2568
+ dailyPercentUsed: daily.percentUsed,
2569
+ weeklyPercentUsed: weekly.percentUsed,
2570
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
2571
+ status,
2572
+ statusReason: describeUsageStatus(status),
2573
+ windows: {
2574
+ daily,
2575
+ weekly
2576
+ }
2577
+ };
2578
+ input.logger.debug("selection.usage_normalize.complete", {
2579
+ accountId: snapshot.accountId,
2580
+ allowed: snapshot.allowed,
2581
+ limitReached: snapshot.limitReached,
2582
+ dailyRemaining: snapshot.dailyRemaining,
2583
+ weeklyRemaining: snapshot.weeklyRemaining,
2584
+ dailyResetsAt: snapshot.dailyResetsAt,
2585
+ weeklyResetsAt: snapshot.weeklyResetsAt,
2586
+ status: snapshot.status,
2587
+ statusReason: snapshot.statusReason
2588
+ });
2589
+ return snapshot;
2590
+ }
2591
+ function normalizeUsageWindow(input) {
2592
+ if (!input.raw) {
2593
+ input.logger.debug("selection.usage_normalize.window_missing", {
2594
+ accountIdHint: input.accountIdHint ?? null,
2595
+ window: input.window
2596
+ });
2597
+ return {
2598
+ limit: null,
2599
+ used: null,
2600
+ remaining: null,
2601
+ limitReached: false,
2602
+ resetsAt: null,
2603
+ percentUsed: null,
2604
+ source: null
2605
+ };
2606
+ }
2607
+ const limit = pickNumber(input.raw.limit);
2608
+ const used = pickNumber(input.raw.used, calculateUsed(limit, pickNumber(input.raw.remaining)));
2609
+ const remaining = pickNumber(
2610
+ input.raw.remaining,
2611
+ calculateRemaining(limit, used)
2612
+ );
2613
+ const limitReached = typeof input.raw.limit_reached === "boolean" ? input.raw.limit_reached : remaining !== null ? remaining <= 0 : false;
2614
+ const percentUsed = pickNumber(
2615
+ input.raw.percent_used,
2616
+ input.raw.percentage_used,
2617
+ calculatePercentUsed(limit, used, remaining)
2618
+ );
2619
+ const resetsAt = normalizeTimestamp(
2620
+ input.raw.reset_at,
2621
+ input.raw.resets_at,
2622
+ input.raw.next_reset_at
2623
+ );
2624
+ const source = resolveWindowSource(input.raw);
2625
+ input.logger.debug("selection.usage_normalize.window_complete", {
2626
+ accountIdHint: input.accountIdHint ?? null,
2627
+ window: input.window,
2628
+ limit,
2629
+ used,
2630
+ remaining,
2631
+ limitReached,
2632
+ percentUsed,
2633
+ resetsAt,
2634
+ source
2635
+ });
2636
+ return {
2637
+ limit,
2638
+ used,
2639
+ remaining,
2640
+ limitReached,
2641
+ resetsAt,
2642
+ percentUsed,
2643
+ source
2644
+ };
2645
+ }
2646
+ function resolveUsageWindow(raw, window) {
2647
+ const candidates = [raw[window], raw.usage?.[window], raw.quotas?.[window]];
2648
+ for (const candidate of candidates) {
2649
+ if (isRecord2(candidate)) {
2650
+ return candidate;
2651
+ }
2652
+ }
2653
+ return null;
2654
+ }
2655
+ function resolveWindowSource(raw) {
2656
+ if (typeof raw.source === "string") {
2657
+ return raw.source;
2658
+ }
2659
+ if (typeof raw.kind === "string") {
2660
+ return raw.kind;
2661
+ }
2662
+ return null;
2663
+ }
2664
+ function classifyUsageStatus(input) {
2665
+ if (!input.allowed) {
2666
+ return "not-allowed";
2667
+ }
2668
+ if (input.limitReached) {
2669
+ return "limit-reached";
2670
+ }
2671
+ if (input.dailyRemaining === null && input.weeklyRemaining === null) {
2672
+ return "missing-usage-data";
2673
+ }
2674
+ return "usable";
2675
+ }
2676
+ function describeUsageStatus(status) {
2677
+ switch (status) {
2678
+ case "not-allowed":
2679
+ return "usage endpoint reported that the account is not allowed to launch";
2680
+ case "limit-reached":
2681
+ return "usage endpoint reported an exhausted limit window";
2682
+ case "missing-usage-data":
2683
+ return "usage endpoint did not expose enough quota fields to rank this account";
2684
+ case "usable":
2685
+ return "usage endpoint exposed enough quota fields to rank this account";
2686
+ }
2687
+ }
2688
+ function normalizeTimestamp(...values) {
2689
+ for (const value of values) {
2690
+ if (typeof value === "string") {
2691
+ const parsed = new Date(value);
2692
+ if (!Number.isNaN(parsed.valueOf())) {
2693
+ return parsed.toISOString();
2694
+ }
2695
+ }
2696
+ if (typeof value === "number" && Number.isFinite(value)) {
2697
+ const normalizedValue = value > 1e10 ? value : value * 1e3;
2698
+ const parsed = new Date(normalizedValue);
2699
+ if (!Number.isNaN(parsed.valueOf())) {
2700
+ return parsed.toISOString();
2701
+ }
2702
+ }
2703
+ }
2704
+ return null;
2705
+ }
2706
+ function calculateRemaining(limit, used) {
2707
+ if (limit === null || used === null) {
2708
+ return null;
2709
+ }
2710
+ return limit - used;
2711
+ }
2712
+ function calculateUsed(limit, remaining) {
2713
+ if (limit === null || remaining === null) {
2714
+ return null;
2715
+ }
2716
+ return limit - remaining;
2717
+ }
2718
+ function calculatePercentUsed(limit, used, remaining) {
2719
+ if (limit === null || limit <= 0) {
2720
+ return null;
2721
+ }
2722
+ const numerator = used ?? calculateUsed(limit, remaining);
2723
+ if (numerator === null) {
2724
+ return null;
2725
+ }
2726
+ return Number((numerator / limit * 100).toFixed(2));
2727
+ }
2728
+ function pickString(...values) {
2729
+ for (const value of values) {
2730
+ if (typeof value === "string" && value.trim().length > 0) {
2731
+ return value;
2732
+ }
2733
+ }
2734
+ return null;
2735
+ }
2736
+ function pickNumber(...values) {
2737
+ for (const value of values) {
2738
+ if (typeof value === "number" && Number.isFinite(value)) {
2739
+ return value;
2740
+ }
2741
+ }
2742
+ return null;
2743
+ }
2744
+ function isRecord2(value) {
2745
+ return typeof value === "object" && value !== null;
2746
+ }
2747
+
2748
+ // src/selection/usage-client.ts
2749
+ var WHAM_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
2750
+ async function probeAccountUsage(input) {
2751
+ const fetchImpl = input.fetchImpl ?? fetch;
2752
+ input.logger.info("selection.usage_probe.start", {
2753
+ accountId: input.account.id,
2754
+ label: input.account.label,
2755
+ timeoutMs: input.probeConfig.probeTimeoutMs,
2756
+ useAccountIdHeader: input.probeConfig.useAccountIdHeader
2757
+ });
2758
+ const authState = await readAccountAuthState({
2759
+ account: input.account,
2760
+ logger: input.logger
2761
+ });
2762
+ if (!authState.ok) {
2763
+ input.logger.warn("selection.usage_probe.auth_missing", {
2764
+ accountId: input.account.id,
2765
+ label: input.account.label,
2766
+ category: authState.category,
2767
+ filePath: authState.filePath
2768
+ });
2769
+ return {
2770
+ ok: false,
2771
+ account: input.account,
2772
+ category: "auth-missing",
2773
+ message: authState.message,
2774
+ source: "fresh"
2775
+ };
2776
+ }
2777
+ try {
2778
+ const response = await fetchImpl(WHAM_USAGE_URL, {
2779
+ method: "GET",
2780
+ headers: buildUsageHeaders({
2781
+ accessToken: authState.state.accessToken,
2782
+ accountId: authState.state.accountId,
2783
+ useAccountIdHeader: input.probeConfig.useAccountIdHeader
2784
+ }),
2785
+ signal: AbortSignal.timeout(input.probeConfig.probeTimeoutMs)
2786
+ });
2787
+ input.logger.debug("selection.usage_probe.http_complete", {
2788
+ accountId: input.account.id,
2789
+ label: input.account.label,
2790
+ status: response.status,
2791
+ ok: response.ok
2792
+ });
2793
+ if (!response.ok) {
2794
+ input.logger.warn("selection.usage_probe.http_error", {
2795
+ accountId: input.account.id,
2796
+ label: input.account.label,
2797
+ status: response.status
2798
+ });
2799
+ return {
2800
+ ok: false,
2801
+ account: input.account,
2802
+ category: "http-error",
2803
+ message: `Usage probe returned HTTP ${response.status}.`,
2804
+ source: "fresh"
2805
+ };
2806
+ }
2807
+ const body = await response.json();
2808
+ if (!isRecord3(body)) {
2809
+ input.logger.warn("selection.usage_probe.invalid_response", {
2810
+ accountId: input.account.id,
2811
+ label: input.account.label,
2812
+ bodyType: typeof body
2813
+ });
2814
+ return {
2815
+ ok: false,
2816
+ account: input.account,
2817
+ category: "invalid-response",
2818
+ message: "Usage probe returned a non-object JSON payload.",
2819
+ source: "fresh"
2820
+ };
2821
+ }
2822
+ const snapshot = normalizeWhamUsageResponse({
2823
+ accountIdHint: authState.state.accountId ?? input.account.id,
2824
+ logger: input.logger,
2825
+ raw: body
2826
+ });
2827
+ input.logger.info("selection.usage_probe.success", {
2828
+ accountId: input.account.id,
2829
+ label: input.account.label,
2830
+ snapshotStatus: snapshot.status,
2831
+ dailyRemaining: snapshot.dailyRemaining,
2832
+ weeklyRemaining: snapshot.weeklyRemaining,
2833
+ limitReached: snapshot.limitReached
2834
+ });
2835
+ return {
2836
+ ok: true,
2837
+ account: input.account,
2838
+ snapshot,
2839
+ source: "fresh"
2840
+ };
2841
+ } catch (error) {
2842
+ if (isAbortError(error)) {
2843
+ input.logger.warn("selection.usage_probe.timeout", {
2844
+ accountId: input.account.id,
2845
+ label: input.account.label,
2846
+ timeoutMs: input.probeConfig.probeTimeoutMs
2847
+ });
2848
+ return {
2849
+ ok: false,
2850
+ account: input.account,
2851
+ category: "timeout",
2852
+ message: `Usage probe timed out after ${input.probeConfig.probeTimeoutMs}ms.`,
2853
+ source: "fresh"
2854
+ };
2855
+ }
2856
+ input.logger.error("selection.usage_probe.request_failed", {
2857
+ accountId: input.account.id,
2858
+ label: input.account.label,
2859
+ message: error instanceof Error ? error.message : String(error)
2860
+ });
2861
+ return {
2862
+ ok: false,
2863
+ account: input.account,
2864
+ category: "invalid-response",
2865
+ message: error instanceof Error ? error.message : String(error),
2866
+ source: "fresh"
2867
+ };
2868
+ }
2869
+ }
2870
+ function buildUsageHeaders(input) {
2871
+ const headers = new Headers({
2872
+ accept: "application/json",
2873
+ authorization: `Bearer ${input.accessToken}`,
2874
+ "user-agent": "codexes/0.1 experimental-usage-probe"
2875
+ });
2876
+ if (input.useAccountIdHeader && input.accountId) {
2877
+ headers.set("OpenAI-Account-ID", input.accountId);
2878
+ }
2879
+ return headers;
2880
+ }
2881
+ function isAbortError(error) {
2882
+ return error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
2883
+ }
2884
+ function isRecord3(value) {
2885
+ return typeof value === "object" && value !== null;
2886
+ }
2887
+
2888
+ // src/selection/usage-cache.ts
2889
+ import { mkdir as mkdir8, readFile as readFile10, rename as rename2, writeFile as writeFile6 } from "node:fs/promises";
2890
+ import path13 from "node:path";
2891
+ var USAGE_CACHE_SCHEMA_VERSION = 1;
2892
+ async function loadUsageCache(input) {
2893
+ try {
2894
+ const raw = await readFile10(input.cacheFilePath, "utf8");
2895
+ const parsed = JSON.parse(raw);
2896
+ const normalized = normalizeUsageCacheDocument(parsed);
2897
+ input.logger.debug("selection.usage_cache.load_success", {
2898
+ cacheFilePath: input.cacheFilePath,
2899
+ entryCount: normalized.entries.length
2900
+ });
2901
+ return normalized.entries;
2902
+ } catch (error) {
2903
+ if (isNodeErrorWithCode2(error, "ENOENT")) {
2904
+ input.logger.debug("selection.usage_cache.missing", {
2905
+ cacheFilePath: input.cacheFilePath
2906
+ });
2907
+ return [];
2908
+ }
2909
+ const backupPath = `${input.cacheFilePath}.corrupt-${Date.now()}`;
2910
+ await rename2(input.cacheFilePath, backupPath).catch(() => void 0);
2911
+ input.logger.warn("selection.usage_cache.corrupt", {
2912
+ cacheFilePath: input.cacheFilePath,
2913
+ backupPath,
2914
+ message: error instanceof Error ? error.message : String(error)
2915
+ });
2916
+ return [];
2917
+ }
2918
+ }
2919
+ async function persistUsageCache(input) {
2920
+ await mkdir8(path13.dirname(input.cacheFilePath), { recursive: true });
2921
+ const document = {
2922
+ schemaVersion: USAGE_CACHE_SCHEMA_VERSION,
2923
+ entries: input.entries
2924
+ };
2925
+ const tempFile = `${input.cacheFilePath}.tmp`;
2926
+ const serialized = JSON.stringify(document, null, 2);
2927
+ await writeFile6(tempFile, serialized, "utf8");
2928
+ await rename2(tempFile, input.cacheFilePath);
2929
+ input.logger.debug("selection.usage_cache.persisted", {
2930
+ cacheFilePath: input.cacheFilePath,
2931
+ entryCount: input.entries.length
2932
+ });
2933
+ }
2934
+ function resolveFreshUsageCacheEntry(input) {
2935
+ const entry = input.entries.find((candidate) => candidate.accountId === input.accountId) ?? null;
2936
+ if (!entry) {
2937
+ input.logger.debug("selection.usage_cache.miss", {
2938
+ accountId: input.accountId,
2939
+ ttlMs: input.ttlMs
2940
+ });
2941
+ return null;
2942
+ }
2943
+ const ageMs = input.now - new Date(entry.cachedAt).valueOf();
2944
+ if (!Number.isFinite(ageMs) || ageMs > input.ttlMs) {
2945
+ input.logger.debug("selection.usage_cache.expired", {
2946
+ accountId: input.accountId,
2947
+ cachedAt: entry.cachedAt,
2948
+ ageMs: Number.isFinite(ageMs) ? ageMs : null,
2949
+ ttlMs: input.ttlMs
2950
+ });
2951
+ return null;
2952
+ }
2953
+ input.logger.debug("selection.usage_cache.hit", {
2954
+ accountId: input.accountId,
2955
+ cachedAt: entry.cachedAt,
2956
+ ageMs,
2957
+ ttlMs: input.ttlMs
2958
+ });
2959
+ return entry;
2960
+ }
2961
+ function normalizeUsageCacheDocument(value) {
2962
+ if (!isRecord4(value)) {
2963
+ throw new Error("Usage cache document is not an object.");
2964
+ }
2965
+ const schemaVersion = typeof value.schemaVersion === "number" ? value.schemaVersion : USAGE_CACHE_SCHEMA_VERSION;
2966
+ if (schemaVersion !== USAGE_CACHE_SCHEMA_VERSION) {
2967
+ throw new Error(`Unsupported usage cache schema version ${schemaVersion}.`);
2968
+ }
2969
+ const entries = Array.isArray(value.entries) ? value.entries.filter(isUsageCacheEntry) : [];
2970
+ return {
2971
+ schemaVersion: USAGE_CACHE_SCHEMA_VERSION,
2972
+ entries
2973
+ };
2974
+ }
2975
+ function isUsageCacheEntry(value) {
2976
+ return isRecord4(value) && typeof value.accountId === "string" && typeof value.accountLabel === "string" && typeof value.cachedAt === "string" && isRecord4(value.snapshot);
2977
+ }
2978
+ function isRecord4(value) {
2979
+ return typeof value === "object" && value !== null;
2980
+ }
2981
+ function isNodeErrorWithCode2(error, code) {
2982
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
2983
+ }
2984
+
2985
+ // src/selection/usage-probe-coordinator.ts
2986
+ async function resolveAccountUsageSnapshots(input) {
2987
+ const now = Date.now();
2988
+ input.logger.info("selection.usage_probe_coordinator.start", {
2989
+ accountCount: input.accounts.length,
2990
+ cacheFilePath: input.cacheFilePath,
2991
+ cacheTtlMs: input.probeConfig.cacheTtlMs,
2992
+ timeoutMs: input.probeConfig.probeTimeoutMs
2993
+ });
2994
+ const cacheEntries = await loadUsageCache({
2995
+ cacheFilePath: input.cacheFilePath,
2996
+ logger: input.logger
2997
+ });
2998
+ const freshCacheEntries = [...cacheEntries];
2999
+ const resolutions = await Promise.all(
3000
+ input.accounts.map(async (account) => {
3001
+ const cached = resolveFreshUsageCacheEntry({
3002
+ accountId: account.id,
3003
+ entries: freshCacheEntries,
3004
+ logger: input.logger,
3005
+ now,
3006
+ ttlMs: input.probeConfig.cacheTtlMs
3007
+ });
3008
+ if (cached) {
3009
+ return {
3010
+ ok: true,
3011
+ account,
3012
+ snapshot: cached.snapshot,
3013
+ source: "cache"
3014
+ };
3015
+ }
3016
+ const fresh = await probeAccountUsage({
3017
+ account,
3018
+ fetchImpl: input.fetchImpl,
3019
+ logger: input.logger,
3020
+ probeConfig: input.probeConfig
3021
+ });
3022
+ if (fresh.ok) {
3023
+ upsertCacheEntry(freshCacheEntries, {
3024
+ accountId: account.id,
3025
+ accountLabel: account.label,
3026
+ cachedAt: new Date(now).toISOString(),
3027
+ snapshot: fresh.snapshot
3028
+ });
3029
+ }
3030
+ return fresh;
3031
+ })
3032
+ );
3033
+ await persistUsageCache({
3034
+ cacheFilePath: input.cacheFilePath,
3035
+ entries: freshCacheEntries,
3036
+ logger: input.logger
3037
+ });
3038
+ input.logger.info("selection.usage_probe_coordinator.complete", {
3039
+ accountCount: input.accounts.length,
3040
+ cacheHitCount: resolutions.filter((entry) => entry.ok && entry.source === "cache").length,
3041
+ freshSuccessCount: resolutions.filter((entry) => entry.ok && entry.source === "fresh").length,
3042
+ failureCount: resolutions.filter((entry) => !entry.ok).length
3043
+ });
3044
+ return resolutions;
3045
+ }
3046
+ function upsertCacheEntry(entries, nextEntry) {
3047
+ const existingIndex = entries.findIndex((entry) => entry.accountId === nextEntry.accountId);
3048
+ if (existingIndex >= 0) {
3049
+ entries.splice(existingIndex, 1, nextEntry);
3050
+ return;
3051
+ }
3052
+ entries.push(nextEntry);
3053
+ }
3054
+
3055
+ // src/selection/select-account.ts
3056
+ async function selectAccountForExecution(input) {
3057
+ const accounts = await input.registry.listAccounts();
3058
+ input.logger.info("selection.start", {
3059
+ strategy: input.strategy,
3060
+ accountCount: accounts.length
3061
+ });
3062
+ if (accounts.length === 0) {
3063
+ input.logger.warn("selection.none");
3064
+ throw new Error("No accounts configured. Add one with `codexes account add <label>`.");
3065
+ }
3066
+ switch (input.strategy) {
3067
+ case "manual-default":
3068
+ return selectManualDefaultAccount(input.registry, input.logger, accounts);
3069
+ case "single-account":
3070
+ return selectSingleAccountOnly(input.registry, input.logger, accounts);
3071
+ case "remaining-limit-experimental":
3072
+ return selectExperimentalRemainingLimitAccount({
3073
+ accounts,
3074
+ experimentalSelection: input.experimentalSelection,
3075
+ fetchImpl: input.fetchImpl,
3076
+ logger: input.logger,
3077
+ registry: input.registry,
3078
+ selectionCacheFilePath: input.selectionCacheFilePath
3079
+ });
3080
+ }
3081
+ }
3082
+ async function selectManualDefaultAccount(registry, logger, accounts) {
3083
+ const defaultAccount = await registry.getDefaultAccount();
3084
+ if (defaultAccount) {
3085
+ logger.info("selection.manual_default", {
3086
+ accountId: defaultAccount.id,
3087
+ label: defaultAccount.label
3088
+ });
3089
+ return defaultAccount;
3090
+ }
3091
+ if (accounts.length === 1) {
3092
+ const [singleAccount] = accounts;
3093
+ if (!singleAccount) {
3094
+ throw new Error("No accounts configured.");
3095
+ }
3096
+ logger.info("selection.manual_default_fallback_single", {
3097
+ accountId: singleAccount.id,
3098
+ label: singleAccount.label
3099
+ });
3100
+ return registry.selectAccount(singleAccount.id);
3101
+ }
3102
+ logger.warn("selection.manual_default_missing", {
3103
+ accountCount: accounts.length
3104
+ });
3105
+ throw new Error(
3106
+ "Multiple accounts are configured but no default account is selected. Use `codexes account use <account-id-or-label>` first."
3107
+ );
3108
+ }
3109
+ async function selectSingleAccountOnly(registry, logger, accounts) {
3110
+ if (accounts.length !== 1) {
3111
+ logger.warn("selection.single_account_invalid", {
3112
+ accountCount: accounts.length
3113
+ });
3114
+ throw new Error(
3115
+ "The single-account strategy requires exactly one configured account."
3116
+ );
3117
+ }
3118
+ const [singleAccount] = accounts;
3119
+ if (!singleAccount) {
3120
+ throw new Error("No accounts configured.");
3121
+ }
3122
+ logger.info("selection.single_account", {
3123
+ accountId: singleAccount.id,
3124
+ label: singleAccount.label
3125
+ });
3126
+ const defaultAccount = await registry.getDefaultAccount();
3127
+ if (defaultAccount?.id === singleAccount.id) {
3128
+ return singleAccount;
3129
+ }
3130
+ return registry.selectAccount(singleAccount.id);
3131
+ }
3132
+ async function selectExperimentalRemainingLimitAccount(input) {
3133
+ if (!input.experimentalSelection?.enabled || !input.selectionCacheFilePath) {
3134
+ input.logger.warn("selection.experimental_config_missing", {
3135
+ enabled: input.experimentalSelection?.enabled ?? false,
3136
+ hasSelectionCacheFilePath: Boolean(input.selectionCacheFilePath)
3137
+ });
3138
+ return selectManualDefaultAccount(input.registry, input.logger, input.accounts);
3139
+ }
3140
+ const defaultAccount = await input.registry.getDefaultAccount();
3141
+ const probeResults = await resolveAccountUsageSnapshots({
3142
+ accounts: input.accounts,
3143
+ cacheFilePath: input.selectionCacheFilePath,
3144
+ fetchImpl: input.fetchImpl,
3145
+ logger: input.logger,
3146
+ probeConfig: input.experimentalSelection
3147
+ });
3148
+ const failedProbes = probeResults.filter((entry) => !entry.ok);
3149
+ if (failedProbes.length > 0) {
3150
+ const eventName = failedProbes.length === probeResults.length ? "selection.experimental_fallback_all_probes_failed" : "selection.experimental_fallback_mixed_probe_outcomes";
3151
+ input.logger.warn(eventName, {
3152
+ failedAccountIds: failedProbes.map((entry) => entry.account.id),
3153
+ failureCategories: failedProbes.map((entry) => entry.category),
3154
+ successfulAccountIds: probeResults.filter((entry) => entry.ok).map((entry) => entry.account.id)
3155
+ });
3156
+ return selectManualDefaultAccount(input.registry, input.logger, input.accounts);
3157
+ }
3158
+ const successfulProbes = probeResults.filter((entry) => entry.ok);
3159
+ const candidates = successfulProbes.filter((entry) => entry.snapshot.status === "usable").sort(
3160
+ (left, right) => compareExperimentalCandidates({
3161
+ defaultAccountId: defaultAccount?.id ?? null,
3162
+ left,
3163
+ right,
3164
+ registryOrder: input.accounts
3165
+ })
3166
+ );
3167
+ input.logger.info("selection.experimental_ranked", {
3168
+ candidateOrder: candidates.map((entry) => ({
3169
+ accountId: entry.account.id,
3170
+ label: entry.account.label,
3171
+ dailyRemaining: entry.snapshot.dailyRemaining,
3172
+ weeklyRemaining: entry.snapshot.weeklyRemaining,
3173
+ source: entry.source
3174
+ })),
3175
+ defaultAccountId: defaultAccount?.id ?? null
3176
+ });
3177
+ const selected = candidates[0];
3178
+ if (!selected) {
3179
+ const allExhausted = successfulProbes.every(
3180
+ (entry) => entry.snapshot.limitReached || entry.snapshot.status === "limit-reached"
3181
+ );
3182
+ input.logger.warn(
3183
+ allExhausted ? "selection.experimental_fallback_all_accounts_exhausted" : "selection.experimental_fallback_ambiguous_usage",
3184
+ {
3185
+ usableProbeCount: candidates.length,
3186
+ probeStatuses: successfulProbes.map((entry) => ({
3187
+ accountId: entry.account.id,
3188
+ snapshotStatus: entry.snapshot.status,
3189
+ limitReached: entry.snapshot.limitReached,
3190
+ dailyRemaining: entry.snapshot.dailyRemaining,
3191
+ weeklyRemaining: entry.snapshot.weeklyRemaining
3192
+ }))
3193
+ }
3194
+ );
3195
+ return selectManualDefaultAccount(input.registry, input.logger, input.accounts);
3196
+ }
3197
+ input.logger.info("selection.experimental_selected", {
3198
+ accountId: selected.account.id,
3199
+ label: selected.account.label,
3200
+ dailyRemaining: selected.snapshot.dailyRemaining,
3201
+ weeklyRemaining: selected.snapshot.weeklyRemaining,
3202
+ source: selected.source
3203
+ });
3204
+ return selected.account;
3205
+ }
3206
+ function compareExperimentalCandidates(input) {
3207
+ const dailyDelta = (input.right.snapshot.dailyRemaining ?? Number.NEGATIVE_INFINITY) - (input.left.snapshot.dailyRemaining ?? Number.NEGATIVE_INFINITY);
3208
+ if (dailyDelta !== 0) {
3209
+ return dailyDelta;
3210
+ }
3211
+ const weeklyDelta = (input.right.snapshot.weeklyRemaining ?? Number.NEGATIVE_INFINITY) - (input.left.snapshot.weeklyRemaining ?? Number.NEGATIVE_INFINITY);
3212
+ if (weeklyDelta !== 0) {
3213
+ return weeklyDelta;
3214
+ }
3215
+ const leftIsDefault = input.left.account.id === input.defaultAccountId;
3216
+ const rightIsDefault = input.right.account.id === input.defaultAccountId;
3217
+ if (leftIsDefault !== rightIsDefault) {
3218
+ return leftIsDefault ? -1 : 1;
3219
+ }
3220
+ return input.registryOrder.findIndex((account) => account.id === input.left.account.id) - input.registryOrder.findIndex((account) => account.id === input.right.account.id);
3221
+ }
3222
+
3223
+ // src/commands/root/run-root-command.ts
3224
+ async function runRootCommand(context) {
3225
+ const logger = createLogger({
3226
+ level: context.logging.level,
3227
+ name: "root",
3228
+ sink: context.logging.sink
3229
+ });
3230
+ logger.debug("argv.received", { argv: context.argv });
3231
+ logger.debug("runtime.detected", {
3232
+ sharedCodexHome: context.paths.sharedCodexHome,
3233
+ accountRoot: context.paths.accountRoot,
3234
+ runtimeRoot: context.paths.runtimeRoot,
3235
+ registryFile: context.paths.registryFile,
3236
+ wrapperConfigFile: context.paths.wrapperConfigFile,
3237
+ selectionCacheFile: context.paths.selectionCacheFile,
3238
+ firstRun: context.runtimeInitialization.firstRun,
3239
+ copiedSharedArtifacts: context.runtimeInitialization.copiedSharedArtifacts,
3240
+ createdRuntimeFiles: context.runtimeInitialization.createdFiles,
3241
+ credentialStoreMode: context.wrapperConfig.credentialStoreMode,
3242
+ accountSelectionStrategy: context.wrapperConfig.accountSelectionStrategy,
3243
+ experimentalSelection: context.wrapperConfig.experimentalSelection,
3244
+ codexBinaryPath: context.codexBinary.path,
3245
+ recursionGuardSource: context.executablePath
3246
+ });
3247
+ const runtimeContract = createRuntimeContract({
3248
+ accountRoot: context.paths.accountRoot,
3249
+ credentialStoreMode: context.wrapperConfig.credentialStoreMode,
3250
+ logger,
3251
+ runtimeRoot: context.paths.runtimeRoot,
3252
+ sharedCodexHome: context.paths.sharedCodexHome
3253
+ });
3254
+ const runtimeSummary = summarizeRuntimeContract(runtimeContract);
3255
+ logger.debug("runtime.contract_ready", runtimeSummary);
3256
+ if (context.wrapperConfig.credentialStoreMode !== "file") {
3257
+ logger.warn("credential_store.unsupported", {
3258
+ credentialStoreMode: context.wrapperConfig.credentialStoreMode,
3259
+ codexConfigFile: context.paths.codexConfigFile,
3260
+ reason: context.wrapperConfig.credentialStorePolicyReason
3261
+ });
3262
+ }
3263
+ if (context.argv[0] === "account" && context.argv[1] === "add") {
3264
+ logger.info("command.dispatch", {
3265
+ command: "account add",
3266
+ argv: context.argv.slice(2)
3267
+ });
3268
+ return runAccountAddCommand(context, context.argv.slice(2));
3269
+ }
3270
+ if (context.argv[0] === "account" && context.argv[1] === "list") {
3271
+ logger.info("command.dispatch", {
3272
+ command: "account list",
3273
+ argv: context.argv.slice(2)
3274
+ });
3275
+ return runAccountListCommand(context);
3276
+ }
3277
+ if (context.argv[0] === "account" && context.argv[1] === "remove") {
3278
+ logger.info("command.dispatch", {
3279
+ command: "account remove",
3280
+ argv: context.argv.slice(2)
3281
+ });
3282
+ return runAccountRemoveCommand(context, context.argv.slice(2));
3283
+ }
3284
+ if (context.argv[0] === "account" && context.argv[1] === "use") {
3285
+ logger.info("command.dispatch", {
3286
+ command: "account use",
3287
+ argv: context.argv.slice(2)
3288
+ });
3289
+ return runAccountUseCommand(context, context.argv.slice(2));
3290
+ }
3291
+ if (context.argv.includes("--help")) {
3292
+ context.io.stdout.write(`${buildHelpText()}
3293
+ `);
3294
+ logger.info("help.rendered");
3295
+ return 0;
3296
+ }
3297
+ if (!context.codexBinary.path) {
3298
+ logger.error("command.binary_missing", {
3299
+ candidates: context.codexBinary.candidates,
3300
+ rejectedCandidates: context.codexBinary.rejectedCandidates
3301
+ });
3302
+ throw new Error("Could not find the real `codex` binary on PATH.");
3303
+ }
3304
+ const registry = createAccountRegistry({
3305
+ accountRoot: context.paths.accountRoot,
3306
+ logger,
3307
+ registryFile: context.paths.registryFile
3308
+ });
3309
+ if (context.wrapperConfig.accountSelectionStrategy === "remaining-limit-experimental") {
3310
+ logger.warn("selection.experimental_enabled", {
3311
+ endpoint: "https://chatgpt.com/backend-api/wham/usage",
3312
+ fallbackStrategy: "manual-default",
3313
+ timeoutMs: context.wrapperConfig.experimentalSelection.probeTimeoutMs,
3314
+ cacheTtlMs: context.wrapperConfig.experimentalSelection.cacheTtlMs,
3315
+ useAccountIdHeader: context.wrapperConfig.experimentalSelection.useAccountIdHeader
3316
+ });
3317
+ }
3318
+ const activeAccount = await selectAccountForExecution({
3319
+ experimentalSelection: context.wrapperConfig.experimentalSelection,
3320
+ fetchImpl: fetch,
3321
+ logger,
3322
+ registry,
3323
+ selectionCacheFilePath: context.paths.selectionCacheFile,
3324
+ strategy: context.wrapperConfig.accountSelectionStrategy
3325
+ });
3326
+ const lock = await acquireRuntimeLock({
3327
+ logger,
3328
+ runtimeRoot: context.paths.runtimeRoot
3329
+ });
3330
+ try {
3331
+ const activation = await activateAccountIntoSharedRuntime({
3332
+ account: activeAccount,
3333
+ logger,
3334
+ runtimeContract,
3335
+ sharedCodexHome: context.paths.sharedCodexHome
3336
+ });
3337
+ try {
3338
+ const exitCode2 = await spawnCodexCommand({
3339
+ argv: context.argv,
3340
+ codexBinaryPath: context.codexBinary.path,
3341
+ codexHome: context.paths.sharedCodexHome,
3342
+ logger
3343
+ });
3344
+ await syncSharedRuntimeBackToAccount({
3345
+ logger,
3346
+ session: activation
3347
+ });
3348
+ return exitCode2;
3349
+ } catch (error) {
3350
+ await restoreSharedRuntimeFromBackup({
3351
+ account: activeAccount,
3352
+ backupRoot: activation.backupRoot,
3353
+ logger,
3354
+ runtimeContract,
3355
+ sharedCodexHome: context.paths.sharedCodexHome
3356
+ });
3357
+ throw error;
3358
+ }
3359
+ } finally {
3360
+ await lock.release();
3361
+ }
3362
+ }
3363
+ function buildHelpText() {
3364
+ return [
3365
+ "codexes",
3366
+ "",
3367
+ "Transparent multi-account wrapper around the Codex CLI.",
3368
+ "",
3369
+ "Usage:",
3370
+ " codexes [args...]",
3371
+ " codexes account add <label> [--timeout-ms <milliseconds>]",
3372
+ " codexes account list",
3373
+ " codexes account use <account-id-or-label>",
3374
+ " codexes account remove <account-id-or-label>",
3375
+ "",
3376
+ "Runtime model:",
3377
+ " Shared CODEX_HOME is preserved in a wrapper-owned runtime root.",
3378
+ " Account auth state is stored per account and synced back selectively.",
3379
+ "",
3380
+ "Current status:",
3381
+ " Account management and default Codex passthrough are implemented.",
3382
+ " Selection strategies: manual-default, single-account, remaining-limit-experimental.",
3383
+ " Experimental mode probes https://chatgpt.com/backend-api/wham/usage and falls back to manual-default when ranking is unreliable."
3384
+ ].join("\n");
3385
+ }
3386
+
3387
+ // src/core/bootstrap.ts
3388
+ async function runCli(argv, io) {
3389
+ const context = await buildAppContext(argv, io);
3390
+ const logger = createLogger({
3391
+ level: context.logging.level,
3392
+ name: "bootstrap",
3393
+ sink: context.logging.sink
3394
+ });
3395
+ logger.debug("bootstrap.start", {
3396
+ argv,
3397
+ cwd: context.environment.cwd,
3398
+ platform: context.environment.platform,
3399
+ runtime: context.environment.runtime
3400
+ });
3401
+ try {
3402
+ const exitCode2 = await runRootCommand(context);
3403
+ logger.debug("bootstrap.exit", { exitCode: exitCode2 });
3404
+ return exitCode2;
3405
+ } catch (error) {
3406
+ const normalized = error instanceof Error ? error : new Error(String(error));
3407
+ logger.error("bootstrap.fatal", {
3408
+ message: normalized.message,
3409
+ stack: normalized.stack
3410
+ });
3411
+ context.io.stderr.write(`codexes: ${normalized.message}
3412
+ `);
3413
+ return 1;
3414
+ }
3415
+ }
3416
+
3417
+ // src/cli.ts
3418
+ var exitCode = await runCli(process.argv.slice(2), {
3419
+ cwd: process.cwd(),
3420
+ env: process.env,
3421
+ executablePath: process.argv[1] ?? process.execPath,
3422
+ stderr: process.stderr,
3423
+ stdout: process.stdout
3424
+ });
3425
+ process.exit(exitCode);
3426
+ //# sourceMappingURL=cli.js.map