codex-multi-auth 1.2.1 → 1.2.2

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 (102) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +19 -8
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/accounts.d.ts +10 -0
  5. package/dist/lib/accounts.d.ts.map +1 -1
  6. package/dist/lib/accounts.js +312 -69
  7. package/dist/lib/accounts.js.map +1 -1
  8. package/dist/lib/circuit-breaker.d.ts +4 -0
  9. package/dist/lib/circuit-breaker.d.ts.map +1 -1
  10. package/dist/lib/circuit-breaker.js +33 -1
  11. package/dist/lib/circuit-breaker.js.map +1 -1
  12. package/dist/lib/codex-manager/commands/forecast.d.ts +2 -1
  13. package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
  14. package/dist/lib/codex-manager/commands/forecast.js +92 -2
  15. package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
  16. package/dist/lib/codex-manager/commands/report.d.ts +2 -1
  17. package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
  18. package/dist/lib/codex-manager/commands/report.js +87 -2
  19. package/dist/lib/codex-manager/commands/report.js.map +1 -1
  20. package/dist/lib/codex-manager.d.ts.map +1 -1
  21. package/dist/lib/codex-manager.js +2 -0
  22. package/dist/lib/codex-manager.js.map +1 -1
  23. package/dist/lib/health.d.ts +1 -0
  24. package/dist/lib/health.d.ts.map +1 -1
  25. package/dist/lib/health.js +6 -2
  26. package/dist/lib/health.js.map +1 -1
  27. package/dist/lib/parallel-probe.d.ts +1 -1
  28. package/dist/lib/parallel-probe.d.ts.map +1 -1
  29. package/dist/lib/parallel-probe.js +5 -2
  30. package/dist/lib/parallel-probe.js.map +1 -1
  31. package/dist/lib/proactive-refresh.d.ts +1 -1
  32. package/dist/lib/proactive-refresh.d.ts.map +1 -1
  33. package/dist/lib/proactive-refresh.js +14 -5
  34. package/dist/lib/proactive-refresh.js.map +1 -1
  35. package/dist/lib/refresh-guardian.d.ts +3 -0
  36. package/dist/lib/refresh-guardian.d.ts.map +1 -1
  37. package/dist/lib/refresh-guardian.js +142 -52
  38. package/dist/lib/refresh-guardian.js.map +1 -1
  39. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  40. package/dist/lib/request/fetch-helpers.js +64 -13
  41. package/dist/lib/request/fetch-helpers.js.map +1 -1
  42. package/dist/lib/rotation.d.ts +14 -11
  43. package/dist/lib/rotation.d.ts.map +1 -1
  44. package/dist/lib/rotation.js +36 -30
  45. package/dist/lib/rotation.js.map +1 -1
  46. package/dist/lib/storage/identity.d.ts +6 -0
  47. package/dist/lib/storage/identity.d.ts.map +1 -1
  48. package/dist/lib/storage/identity.js +14 -3
  49. package/dist/lib/storage/identity.js.map +1 -1
  50. package/package.json +1 -1
  51. package/dist/lib/codex-manager/best.d.ts +0 -26
  52. package/dist/lib/codex-manager/best.d.ts.map +0 -1
  53. package/dist/lib/codex-manager/best.js +0 -249
  54. package/dist/lib/codex-manager/best.js.map +0 -1
  55. package/dist/lib/codex-manager/config-explain.d.ts +0 -2
  56. package/dist/lib/codex-manager/config-explain.d.ts.map +0 -1
  57. package/dist/lib/codex-manager/config-explain.js +0 -62
  58. package/dist/lib/codex-manager/config-explain.js.map +0 -1
  59. package/dist/lib/codex-manager/config-template.d.ts +0 -2
  60. package/dist/lib/codex-manager/config-template.d.ts.map +0 -1
  61. package/dist/lib/codex-manager/config-template.js +0 -112
  62. package/dist/lib/codex-manager/config-template.js.map +0 -1
  63. package/dist/lib/codex-manager/debug-bundle.d.ts +0 -2
  64. package/dist/lib/codex-manager/debug-bundle.d.ts.map +0 -1
  65. package/dist/lib/codex-manager/debug-bundle.js +0 -128
  66. package/dist/lib/codex-manager/debug-bundle.js.map +0 -1
  67. package/dist/lib/codex-manager/doctor.d.ts +0 -20
  68. package/dist/lib/codex-manager/doctor.d.ts.map +0 -1
  69. package/dist/lib/codex-manager/doctor.js +0 -546
  70. package/dist/lib/codex-manager/doctor.js.map +0 -1
  71. package/dist/lib/codex-manager/fix.d.ts +0 -26
  72. package/dist/lib/codex-manager/fix.d.ts.map +0 -1
  73. package/dist/lib/codex-manager/fix.js +0 -366
  74. package/dist/lib/codex-manager/fix.js.map +0 -1
  75. package/dist/lib/codex-manager/forecast.d.ts +0 -26
  76. package/dist/lib/codex-manager/forecast.d.ts.map +0 -1
  77. package/dist/lib/codex-manager/forecast.js +0 -218
  78. package/dist/lib/codex-manager/forecast.js.map +0 -1
  79. package/dist/lib/codex-manager/info.d.ts +0 -11
  80. package/dist/lib/codex-manager/info.d.ts.map +0 -1
  81. package/dist/lib/codex-manager/info.js +0 -91
  82. package/dist/lib/codex-manager/info.js.map +0 -1
  83. package/dist/lib/codex-manager/login.d.ts +0 -40
  84. package/dist/lib/codex-manager/login.d.ts.map +0 -1
  85. package/dist/lib/codex-manager/login.js +0 -1041
  86. package/dist/lib/codex-manager/login.js.map +0 -1
  87. package/dist/lib/codex-manager/report.d.ts +0 -39
  88. package/dist/lib/codex-manager/report.d.ts.map +0 -1
  89. package/dist/lib/codex-manager/report.js +0 -243
  90. package/dist/lib/codex-manager/report.js.map +0 -1
  91. package/dist/lib/codex-manager/switch.d.ts +0 -31
  92. package/dist/lib/codex-manager/switch.d.ts.map +0 -1
  93. package/dist/lib/codex-manager/switch.js +0 -103
  94. package/dist/lib/codex-manager/switch.js.map +0 -1
  95. package/dist/lib/codex-manager/verify-flagged.d.ts +0 -20
  96. package/dist/lib/codex-manager/verify-flagged.d.ts.map +0 -1
  97. package/dist/lib/codex-manager/verify-flagged.js +0 -316
  98. package/dist/lib/codex-manager/verify-flagged.js.map +0 -1
  99. package/dist/lib/request/trace-mode.d.ts +0 -9
  100. package/dist/lib/request/trace-mode.d.ts.map +0 -1
  101. package/dist/lib/request/trace-mode.js +0 -14
  102. package/dist/lib/request/trace-mode.js.map +0 -1
@@ -1,1041 +0,0 @@
1
- import { stdin as input, stdout as output } from "node:process";
2
- import { createInterface } from "node:readline/promises";
3
- import { extractAccountEmail, extractAccountId, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, } from "../accounts.js";
4
- import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, REDIRECT_URI, } from "../auth/auth.js";
5
- import { copyTextToClipboard, isBrowserLaunchSuppressed, openBrowserUrl, } from "../auth/browser.js";
6
- import { startLocalOAuthServer } from "../auth/server.js";
7
- import { promptAddAnotherAccount, promptLoginMode, } from "../cli.js";
8
- import { setCodexCliActiveSelection } from "../codex-cli/writer.js";
9
- import { ACCOUNT_LIMITS } from "../constants.js";
10
- import { loadDashboardDisplaySettings, } from "../dashboard-settings.js";
11
- import { createLogger } from "../logger.js";
12
- import { MODEL_FAMILIES } from "../prompts/codex.js";
13
- import { loadQuotaCache } from "../quota-cache.js";
14
- import { clearAccounts, findMatchingAccountIndex, formatStorageErrorHint, getNamedBackups, loadAccounts, loadFlaggedAccounts, restoreAccountsFromBackup, StorageError, saveAccounts, setStoragePath, withAccountStorageTransaction, } from "../storage.js";
15
- import { ANSI } from "../ui/ansi.js";
16
- import { confirm } from "../ui/confirm.js";
17
- import { UI_COPY } from "../ui/copy.js";
18
- import { getUiRuntimeOptions } from "../ui/runtime.js";
19
- import { select } from "../ui/select.js";
20
- import { printUsage } from "./help.js";
21
- import { applyUiThemeFromDashboardSettings } from "./settings-hub.js";
22
- const log = createLogger("codex-manager-login");
23
- function parseAuthLoginArgs(args) {
24
- const options = {
25
- manual: false,
26
- };
27
- for (const arg of args) {
28
- if (arg === "--manual" || arg === "--no-browser") {
29
- options.manual = true;
30
- continue;
31
- }
32
- if (arg === "--help" || arg === "-h") {
33
- printUsage();
34
- return { ok: false, message: "" };
35
- }
36
- return {
37
- ok: false,
38
- message: `Unknown login option: ${arg}`,
39
- };
40
- }
41
- return { ok: true, options };
42
- }
43
- function isAbortError(error) {
44
- if (!(error instanceof Error))
45
- return false;
46
- const maybe = error;
47
- return maybe.name === "AbortError" || maybe.code === "ABORT_ERR";
48
- }
49
- function isReadlineClosedError(error) {
50
- if (!(error instanceof Error)) {
51
- return false;
52
- }
53
- const errorCode = typeof error === "object" && error !== null && "code" in error
54
- ? String(error.code)
55
- : "";
56
- return (errorCode === "ERR_USE_AFTER_CLOSE" ||
57
- /readline was closed/i.test(error.message));
58
- }
59
- export function formatBackupSavedAt(mtimeMs) {
60
- return new Date(mtimeMs).toLocaleString(undefined, {
61
- month: "short",
62
- day: "numeric",
63
- year: "numeric",
64
- hour: "numeric",
65
- minute: "2-digit",
66
- });
67
- }
68
- async function waitForMenuReturn(stylePromptText, options = {}) {
69
- if (!input.isTTY || !output.isTTY) {
70
- return;
71
- }
72
- const promptText = options.promptText ?? UI_COPY.returnFlow.continuePrompt;
73
- const autoReturnMs = options.autoReturnMs ?? 0;
74
- const pauseOnAnyKey = options.pauseOnAnyKey ?? true;
75
- try {
76
- let chunk;
77
- do {
78
- chunk = input.read();
79
- } while (chunk !== null);
80
- }
81
- catch {
82
- // best effort buffer drain
83
- }
84
- const writeInlineStatus = (message) => {
85
- output.write(`\r${ANSI.clearLine}${stylePromptText(message, "muted")}`);
86
- };
87
- const clearInlineStatus = () => {
88
- output.write(`\r${ANSI.clearLine}`);
89
- };
90
- if (autoReturnMs > 0) {
91
- if (!pauseOnAnyKey) {
92
- await new Promise((resolve) => setTimeout(resolve, autoReturnMs));
93
- return;
94
- }
95
- const wasRaw = input.isRaw ?? false;
96
- const endAt = Date.now() + autoReturnMs;
97
- let lastShownSeconds = null;
98
- const renderCountdown = () => {
99
- const remainingMs = Math.max(0, endAt - Date.now());
100
- const remainingSeconds = Math.max(1, Math.ceil(remainingMs / 1000));
101
- if (lastShownSeconds === remainingSeconds)
102
- return;
103
- lastShownSeconds = remainingSeconds;
104
- writeInlineStatus(UI_COPY.returnFlow.autoReturn(remainingSeconds));
105
- };
106
- renderCountdown();
107
- const pinned = await new Promise((resolve) => {
108
- let done = false;
109
- const interval = setInterval(renderCountdown, 80);
110
- let timeout = setTimeout(() => {
111
- timeout = null;
112
- if (!done) {
113
- done = true;
114
- cleanup();
115
- resolve(false);
116
- }
117
- }, autoReturnMs);
118
- const onData = () => {
119
- if (done)
120
- return;
121
- done = true;
122
- cleanup();
123
- resolve(true);
124
- };
125
- const cleanup = () => {
126
- clearInterval(interval);
127
- if (timeout) {
128
- clearTimeout(timeout);
129
- timeout = null;
130
- }
131
- input.removeListener("data", onData);
132
- try {
133
- input.setRawMode(wasRaw);
134
- }
135
- catch {
136
- // best effort restore
137
- }
138
- };
139
- try {
140
- input.setRawMode(true);
141
- }
142
- catch {
143
- // best effort fallback
144
- }
145
- input.on("data", onData);
146
- input.resume();
147
- });
148
- clearInlineStatus();
149
- if (!pinned) {
150
- return;
151
- }
152
- writeInlineStatus(stylePromptText(UI_COPY.returnFlow.paused, "muted"));
153
- await new Promise((resolve) => {
154
- const wasRaw = input.isRaw ?? false;
155
- const onData = () => {
156
- cleanup();
157
- resolve();
158
- };
159
- const cleanup = () => {
160
- input.removeListener("data", onData);
161
- try {
162
- input.setRawMode(wasRaw);
163
- }
164
- catch {
165
- // best effort restore
166
- }
167
- };
168
- try {
169
- input.setRawMode(true);
170
- }
171
- catch {
172
- // best effort fallback
173
- }
174
- input.on("data", onData);
175
- input.resume();
176
- });
177
- clearInlineStatus();
178
- return;
179
- }
180
- const rl = createInterface({ input, output });
181
- try {
182
- const question = promptText.length > 0 ? `${stylePromptText(promptText, "muted")} ` : "";
183
- output.write(`\r${ANSI.clearLine}`);
184
- await rl.question(question);
185
- }
186
- catch (error) {
187
- if (!isAbortError(error)) {
188
- throw error;
189
- }
190
- }
191
- finally {
192
- rl.close();
193
- clearInlineStatus();
194
- }
195
- }
196
- function stringifyLogArgs(args) {
197
- return args
198
- .map((value) => {
199
- if (typeof value === "string")
200
- return value;
201
- try {
202
- return JSON.stringify(value);
203
- }
204
- catch {
205
- return String(value);
206
- }
207
- })
208
- .join(" ");
209
- }
210
- async function runActionPanel(stylePromptText, title, stage, action, settings) {
211
- if (!input.isTTY || !output.isTTY) {
212
- await action();
213
- return;
214
- }
215
- const spinnerFrames = ["-", "\\", "|", "/"];
216
- let frame = 0;
217
- let running = true;
218
- let failed = null;
219
- const captured = [];
220
- const maxVisibleLines = Math.max(8, (output.rows ?? 24) - 8);
221
- const previousLog = console.log;
222
- const previousWarn = console.warn;
223
- const previousError = console.error;
224
- const capture = (prefix, args) => {
225
- const line = stringifyLogArgs(args).trim();
226
- if (!line)
227
- return;
228
- captured.push(prefix ? `${prefix}${line}` : line);
229
- if (captured.length > 400) {
230
- captured.splice(0, captured.length - 400);
231
- }
232
- };
233
- const render = () => {
234
- output.write(ANSI.clearScreen + ANSI.moveTo(1, 1));
235
- const spinner = running
236
- ? `${spinnerFrames[frame % spinnerFrames.length] ?? "-"} `
237
- : failed
238
- ? "x "
239
- : "+ ";
240
- const stageText = running
241
- ? `${spinner}${stage}`
242
- : failed
243
- ? UI_COPY.returnFlow.failed
244
- : UI_COPY.returnFlow.done;
245
- previousLog(stylePromptText(title, "accent"));
246
- previousLog(stylePromptText(stageText, failed ? "danger" : running ? "accent" : "success"));
247
- previousLog("");
248
- const lines = captured.slice(-maxVisibleLines);
249
- for (const line of lines) {
250
- previousLog(line);
251
- }
252
- const remainingLines = Math.max(0, maxVisibleLines - lines.length);
253
- for (let i = 0; i < remainingLines; i += 1) {
254
- previousLog("");
255
- }
256
- previousLog("");
257
- if (running)
258
- previousLog(stylePromptText(UI_COPY.returnFlow.working, "muted"));
259
- frame += 1;
260
- };
261
- console.log = (...args) => {
262
- capture("", args);
263
- };
264
- console.warn = (...args) => {
265
- capture("! ", args);
266
- };
267
- console.error = (...args) => {
268
- capture("x ", args);
269
- };
270
- output.write(ANSI.altScreenOn + ANSI.hide);
271
- let timer = null;
272
- try {
273
- render();
274
- timer = setInterval(() => {
275
- if (!running)
276
- return;
277
- render();
278
- }, 120);
279
- await action();
280
- }
281
- catch (error) {
282
- failed = error;
283
- capture("x ", [error instanceof Error ? error.message : String(error)]);
284
- }
285
- finally {
286
- running = false;
287
- if (timer) {
288
- clearInterval(timer);
289
- timer = null;
290
- }
291
- render();
292
- console.log = previousLog;
293
- console.warn = previousWarn;
294
- console.error = previousError;
295
- }
296
- if (failed) {
297
- await waitForMenuReturn(stylePromptText, {
298
- promptText: UI_COPY.returnFlow.actionFailedPrompt,
299
- });
300
- }
301
- else {
302
- await waitForMenuReturn(stylePromptText, {
303
- autoReturnMs: settings?.actionAutoReturnMs ?? 2_000,
304
- pauseOnAnyKey: settings?.actionPauseOnKey ?? true,
305
- });
306
- }
307
- output.write(ANSI.altScreenOff + ANSI.show + ANSI.clearScreen + ANSI.moveTo(1, 1));
308
- if (failed) {
309
- throw failed;
310
- }
311
- }
312
- async function promptManualCallback(state, stylePromptText, options = {}) {
313
- const useInteractivePrompt = input.isTTY && output.isTTY;
314
- if (!useInteractivePrompt && !options.allowNonTty) {
315
- return null;
316
- }
317
- const rl = createInterface({ input, output });
318
- try {
319
- if (useInteractivePrompt) {
320
- console.log("");
321
- console.log(stylePromptText(UI_COPY.oauth.pastePrompt, "accent"));
322
- }
323
- const answer = useInteractivePrompt
324
- ? await rl.question("◆ ")
325
- : await new Promise((resolve, reject) => {
326
- if (input.readableEnded || input.destroyed) {
327
- resolve(null);
328
- return;
329
- }
330
- let settled = false;
331
- const handleInputClosed = () => {
332
- if (settled)
333
- return;
334
- settled = true;
335
- input.off("end", handleInputClosed);
336
- input.off("close", handleInputClosed);
337
- resolve(null);
338
- };
339
- const finish = (value) => {
340
- if (settled)
341
- return;
342
- settled = true;
343
- input.off("end", handleInputClosed);
344
- input.off("close", handleInputClosed);
345
- resolve(value);
346
- };
347
- const fail = (error) => {
348
- if (settled)
349
- return;
350
- settled = true;
351
- input.off("end", handleInputClosed);
352
- input.off("close", handleInputClosed);
353
- reject(error);
354
- };
355
- rl.question("")
356
- .then((value) => finish(value))
357
- .catch((error) => {
358
- if (isAbortError(error) || isReadlineClosedError(error)) {
359
- handleInputClosed();
360
- return;
361
- }
362
- fail(error);
363
- });
364
- input.once("end", handleInputClosed);
365
- input.once("close", handleInputClosed);
366
- });
367
- if (answer === null) {
368
- return null;
369
- }
370
- if (answer.includes("\u001b")) {
371
- return null;
372
- }
373
- const normalized = answer.trim().toLowerCase();
374
- if (normalized.length === 0 ||
375
- normalized === "q" ||
376
- normalized === "quit" ||
377
- normalized === "cancel" ||
378
- normalized === "back") {
379
- return null;
380
- }
381
- const parsed = parseAuthorizationInput(answer);
382
- if (!parsed.code)
383
- return null;
384
- if (parsed.state && parsed.state !== state)
385
- return null;
386
- return parsed.code;
387
- }
388
- catch (error) {
389
- if (isAbortError(error) || isReadlineClosedError(error)) {
390
- return null;
391
- }
392
- throw error;
393
- }
394
- finally {
395
- rl.close();
396
- }
397
- }
398
- async function promptOAuthSignInMode(backupOption, backupDiscoveryWarning = null) {
399
- if (!input.isTTY || !output.isTTY) {
400
- return "browser";
401
- }
402
- const ui = getUiRuntimeOptions();
403
- const items = [
404
- {
405
- label: UI_COPY.oauth.signInHeading,
406
- value: "cancel",
407
- kind: "heading",
408
- },
409
- { label: UI_COPY.oauth.openBrowser, value: "browser", color: "green" },
410
- { label: UI_COPY.oauth.manualMode, value: "manual", color: "yellow" },
411
- ...(backupOption
412
- ? [
413
- { separator: true, label: "", value: "cancel" },
414
- {
415
- label: UI_COPY.oauth.restoreHeading,
416
- value: "cancel",
417
- kind: "heading",
418
- },
419
- {
420
- label: UI_COPY.oauth.restoreSavedBackup,
421
- value: "restore-backup",
422
- hint: UI_COPY.oauth.loadLastBackupHint(backupOption.fileName, backupOption.accountCount, formatBackupSavedAt(backupOption.mtimeMs)),
423
- color: "cyan",
424
- },
425
- ]
426
- : []),
427
- { separator: true, label: "", value: "cancel" },
428
- { label: UI_COPY.oauth.back, value: "cancel", color: "red" },
429
- ];
430
- const selected = await select(items, {
431
- message: UI_COPY.oauth.chooseModeTitle,
432
- subtitle: backupDiscoveryWarning
433
- ? `${UI_COPY.oauth.chooseModeSubtitle} ${backupDiscoveryWarning}`
434
- : UI_COPY.oauth.chooseModeSubtitle,
435
- help: backupOption
436
- ? UI_COPY.oauth.chooseModeHelpWithBackup
437
- : UI_COPY.oauth.chooseModeHelp,
438
- clearScreen: true,
439
- theme: ui.theme,
440
- selectedEmphasis: "minimal",
441
- allowEscape: false,
442
- onInput: (raw) => {
443
- const lower = raw.toLowerCase();
444
- if (lower === "q")
445
- return "cancel";
446
- if (lower === "1")
447
- return "browser";
448
- if (lower === "2")
449
- return "manual";
450
- if (lower === "3" && backupOption)
451
- return "restore-backup";
452
- return undefined;
453
- },
454
- });
455
- return selected ?? "cancel";
456
- }
457
- async function promptBackupRestoreMode(latestBackup) {
458
- if (!input.isTTY || !output.isTTY) {
459
- return "latest";
460
- }
461
- const ui = getUiRuntimeOptions();
462
- const items = [
463
- {
464
- label: UI_COPY.oauth.loadLastBackup,
465
- value: "latest",
466
- hint: `${UI_COPY.oauth.restoreBackupLatestHint}\n${UI_COPY.oauth.manualBackupHint(latestBackup.accountCount, formatBackupSavedAt(latestBackup.mtimeMs))}`,
467
- color: "cyan",
468
- },
469
- {
470
- label: UI_COPY.oauth.chooseBackupManually,
471
- value: "manual",
472
- color: "yellow",
473
- },
474
- { label: UI_COPY.oauth.back, value: "back", color: "red" },
475
- ];
476
- const selected = await select(items, {
477
- message: UI_COPY.oauth.restoreBackupTitle,
478
- subtitle: UI_COPY.oauth.restoreBackupSubtitle,
479
- help: UI_COPY.oauth.restoreBackupHelp,
480
- clearScreen: true,
481
- theme: ui.theme,
482
- selectedEmphasis: "minimal",
483
- allowEscape: false,
484
- onInput: (raw) => {
485
- const lower = raw.toLowerCase();
486
- if (lower === "q")
487
- return "back";
488
- if (lower === "1")
489
- return "latest";
490
- if (lower === "2")
491
- return "manual";
492
- return undefined;
493
- },
494
- });
495
- return selected ?? "back";
496
- }
497
- async function promptManualBackupSelection(backups) {
498
- if (!input.isTTY || !output.isTTY) {
499
- return backups[0] ?? null;
500
- }
501
- const ui = getUiRuntimeOptions();
502
- const items = backups.map((backup) => ({
503
- label: backup.fileName,
504
- value: backup,
505
- hint: UI_COPY.oauth.manualBackupHint(backup.accountCount, formatBackupSavedAt(backup.mtimeMs)),
506
- color: "cyan",
507
- }));
508
- items.push({ label: UI_COPY.oauth.back, value: null, color: "red" });
509
- const selected = await select(items, {
510
- message: UI_COPY.oauth.manualBackupTitle,
511
- subtitle: UI_COPY.oauth.manualBackupSubtitle,
512
- help: UI_COPY.oauth.manualBackupHelp,
513
- clearScreen: true,
514
- theme: ui.theme,
515
- selectedEmphasis: "minimal",
516
- allowEscape: false,
517
- onInput: (raw) => {
518
- if (raw.toLowerCase() === "q")
519
- return null;
520
- return undefined;
521
- },
522
- });
523
- return selected;
524
- }
525
- async function runOAuthFlow(forceNewLogin, signInMode, stylePromptText) {
526
- const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
527
- let code = null;
528
- let oauthServer = null;
529
- try {
530
- if (signInMode === "browser") {
531
- try {
532
- oauthServer = await startLocalOAuthServer({ state });
533
- }
534
- catch {
535
- oauthServer = null;
536
- }
537
- }
538
- if (signInMode === "browser") {
539
- const opened = openBrowserUrl(url);
540
- if (opened) {
541
- console.log(stylePromptText(UI_COPY.oauth.browserOpened, "success"));
542
- }
543
- else {
544
- console.log(stylePromptText(UI_COPY.oauth.browserOpenFail, "warning"));
545
- console.log(`${stylePromptText(UI_COPY.oauth.goTo, "accent")} ${url}`);
546
- const copied = copyTextToClipboard(url);
547
- console.log(stylePromptText(copied ? UI_COPY.oauth.copyOk : UI_COPY.oauth.copyFail, copied ? "success" : "warning"));
548
- }
549
- }
550
- else {
551
- console.log(`${stylePromptText(UI_COPY.oauth.goTo, "accent")} ${url}`);
552
- const copied = copyTextToClipboard(url);
553
- console.log(stylePromptText(copied ? UI_COPY.oauth.copyOk : UI_COPY.oauth.copyFail, copied ? "success" : "warning"));
554
- }
555
- const waitingForCallback = signInMode === "browser" && oauthServer?.ready === true;
556
- if (waitingForCallback && oauthServer) {
557
- console.log(stylePromptText(UI_COPY.oauth.waitingCallback, "muted"));
558
- const callbackResult = await oauthServer.waitForCode(state);
559
- code = callbackResult?.code ?? null;
560
- }
561
- if (!code) {
562
- console.log(stylePromptText(waitingForCallback
563
- ? UI_COPY.oauth.callbackMissed
564
- : signInMode === "manual"
565
- ? UI_COPY.oauth.callbackBypassed
566
- : UI_COPY.oauth.callbackUnavailable, "warning"));
567
- code = await promptManualCallback(state, stylePromptText, {
568
- allowNonTty: signInMode === "manual",
569
- });
570
- }
571
- }
572
- finally {
573
- oauthServer?.close();
574
- }
575
- if (!code) {
576
- return {
577
- type: "failed",
578
- reason: "unknown",
579
- message: UI_COPY.oauth.cancelled,
580
- };
581
- }
582
- return exchangeAuthorizationCode(code, pkce.verifier, REDIRECT_URI);
583
- }
584
- function isUserCancelledOAuth(result) {
585
- if (result.type !== "failed")
586
- return false;
587
- const message = (result.message ?? "").toLowerCase();
588
- return message.includes("cancelled");
589
- }
590
- function resolveAccountSelection(tokens) {
591
- const override = (process.env.CODEX_AUTH_ACCOUNT_ID ?? "").trim();
592
- if (override) {
593
- return {
594
- ...tokens,
595
- accountIdOverride: override,
596
- accountIdSource: "manual",
597
- };
598
- }
599
- const candidates = getAccountIdCandidates(tokens.access, tokens.idToken);
600
- if (candidates.length === 0) {
601
- return tokens;
602
- }
603
- if (candidates.length === 1) {
604
- const [candidate] = candidates;
605
- if (candidate) {
606
- return {
607
- ...tokens,
608
- accountIdOverride: candidate.accountId,
609
- accountIdSource: candidate.source,
610
- accountLabel: candidate.label,
611
- };
612
- }
613
- }
614
- const best = selectBestAccountCandidate(candidates);
615
- if (!best) {
616
- return tokens;
617
- }
618
- return {
619
- ...tokens,
620
- accountIdOverride: best.accountId,
621
- accountIdSource: best.source ?? "token",
622
- accountLabel: best.label,
623
- };
624
- }
625
- async function persistAccountPool(results, replaceAll) {
626
- if (results.length === 0)
627
- return;
628
- await withAccountStorageTransaction(async (loadedStorage, persist) => {
629
- const stored = replaceAll ? null : loadedStorage;
630
- const now = Date.now();
631
- const accounts = stored?.accounts ? [...stored.accounts] : [];
632
- let selectedAccountIndex = null;
633
- for (const result of results) {
634
- const tokenAccountId = extractAccountId(result.access);
635
- const accountId = resolveRequestAccountId(result.accountIdOverride, result.accountIdSource, tokenAccountId);
636
- const accountIdSource = accountId
637
- ? (result.accountIdSource ??
638
- (result.accountIdOverride ? "manual" : "token"))
639
- : undefined;
640
- const accountLabel = result.accountLabel;
641
- const accountEmail = sanitizeEmail(extractAccountEmail(result.access, result.idToken));
642
- const existingIndex = findMatchingAccountIndex(accounts, {
643
- accountId,
644
- email: accountEmail,
645
- refreshToken: result.refresh,
646
- }, {
647
- allowUniqueAccountIdFallbackWithoutEmail: true,
648
- });
649
- if (existingIndex === undefined) {
650
- const newIndex = accounts.length;
651
- accounts.push({
652
- accountId,
653
- accountIdSource,
654
- accountLabel,
655
- email: accountEmail,
656
- refreshToken: result.refresh,
657
- accessToken: result.access,
658
- expiresAt: result.expires,
659
- enabled: true,
660
- addedAt: now,
661
- lastUsed: now,
662
- });
663
- selectedAccountIndex = newIndex;
664
- continue;
665
- }
666
- const existing = accounts[existingIndex];
667
- if (!existing)
668
- continue;
669
- const nextEmail = accountEmail ?? existing.email;
670
- const nextAccountId = accountId ?? existing.accountId;
671
- const nextAccountIdSource = accountId
672
- ? (accountIdSource ?? existing.accountIdSource)
673
- : existing.accountIdSource;
674
- accounts[existingIndex] = {
675
- ...existing,
676
- accountId: nextAccountId,
677
- accountIdSource: nextAccountIdSource,
678
- accountLabel: accountLabel ?? existing.accountLabel,
679
- email: nextEmail,
680
- refreshToken: result.refresh,
681
- accessToken: result.access,
682
- expiresAt: result.expires,
683
- enabled: true,
684
- lastUsed: now,
685
- };
686
- selectedAccountIndex = existingIndex;
687
- }
688
- const fallbackActiveIndex = accounts.length === 0
689
- ? 0
690
- : Math.max(0, Math.min(stored?.activeIndex ?? 0, accounts.length - 1));
691
- const nextActiveIndex = accounts.length === 0
692
- ? 0
693
- : selectedAccountIndex === null
694
- ? fallbackActiveIndex
695
- : Math.max(0, Math.min(selectedAccountIndex, accounts.length - 1));
696
- const activeIndexByFamily = {};
697
- for (const family of MODEL_FAMILIES) {
698
- activeIndexByFamily[family] = nextActiveIndex;
699
- }
700
- await persist({
701
- version: 3,
702
- accounts,
703
- activeIndex: nextActiveIndex,
704
- activeIndexByFamily,
705
- });
706
- });
707
- }
708
- async function syncSelectionToCodex(tokens) {
709
- const tokenAccountId = extractAccountId(tokens.access);
710
- const accountId = resolveRequestAccountId(tokens.accountIdOverride, tokens.accountIdSource, tokenAccountId);
711
- const email = sanitizeEmail(extractAccountEmail(tokens.access, tokens.idToken));
712
- await setCodexCliActiveSelection({
713
- accountId,
714
- email,
715
- accessToken: tokens.access,
716
- refreshToken: tokens.refresh,
717
- expiresAt: tokens.expires,
718
- idToken: tokens.idToken,
719
- });
720
- }
721
- async function clearAccountsAndReset() {
722
- await clearAccounts();
723
- }
724
- async function handleManageAction(storage, menuResult, deps) {
725
- if (typeof menuResult.switchAccountIndex === "number") {
726
- const index = menuResult.switchAccountIndex;
727
- await deps.runSwitch([String(index + 1)]);
728
- return;
729
- }
730
- if (typeof menuResult.deleteAccountIndex === "number") {
731
- const idx = menuResult.deleteAccountIndex;
732
- if (idx >= 0 && idx < storage.accounts.length) {
733
- storage.accounts.splice(idx, 1);
734
- storage.activeIndex = 0;
735
- storage.activeIndexByFamily = {};
736
- for (const family of MODEL_FAMILIES) {
737
- storage.activeIndexByFamily[family] = 0;
738
- }
739
- await saveAccounts(storage);
740
- console.log(`Deleted account ${idx + 1}.`);
741
- }
742
- return;
743
- }
744
- if (typeof menuResult.toggleAccountIndex === "number") {
745
- const idx = menuResult.toggleAccountIndex;
746
- const account = storage.accounts[idx];
747
- if (account) {
748
- account.enabled = account.enabled === false;
749
- await saveAccounts(storage);
750
- console.log(`${account.enabled === false ? "Disabled" : "Enabled"} account ${idx + 1}.`);
751
- }
752
- return;
753
- }
754
- if (typeof menuResult.refreshAccountIndex === "number") {
755
- const idx = menuResult.refreshAccountIndex;
756
- const existing = storage.accounts[idx];
757
- if (!existing)
758
- return;
759
- const signInMode = await promptOAuthSignInMode(null);
760
- if (signInMode === "cancel") {
761
- console.log(deps.stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
762
- return;
763
- }
764
- if (signInMode !== "browser" && signInMode !== "manual") {
765
- return;
766
- }
767
- const tokenResult = await runOAuthFlow(true, signInMode, deps.stylePromptText);
768
- if (tokenResult.type !== "success") {
769
- console.error(`Refresh failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
770
- return;
771
- }
772
- const resolved = resolveAccountSelection(tokenResult);
773
- await persistAccountPool([resolved], false);
774
- await syncSelectionToCodex(resolved);
775
- console.log(`Refreshed account ${idx + 1}.`);
776
- }
777
- }
778
- export async function runAuthLogin(args, deps) {
779
- const parsedArgs = parseAuthLoginArgs(args);
780
- if (!parsedArgs.ok) {
781
- if (parsedArgs.message) {
782
- console.error(parsedArgs.message);
783
- printUsage();
784
- return 1;
785
- }
786
- return 0;
787
- }
788
- const loginOptions = parsedArgs.options;
789
- setStoragePath(null);
790
- let pendingMenuQuotaRefresh = null;
791
- let menuQuotaRefreshStatus;
792
- loginFlow: while (true) {
793
- let existingStorage = await loadAccounts();
794
- if (existingStorage && existingStorage.accounts.length > 0) {
795
- while (true) {
796
- existingStorage = await loadAccounts();
797
- if (!existingStorage || existingStorage.accounts.length === 0) {
798
- break;
799
- }
800
- const currentStorage = existingStorage;
801
- const displaySettings = await loadDashboardDisplaySettings();
802
- applyUiThemeFromDashboardSettings(displaySettings);
803
- const quotaCache = await loadQuotaCache();
804
- const shouldAutoFetchLimits = displaySettings.menuAutoFetchLimits ?? true;
805
- const showFetchStatus = displaySettings.menuShowFetchStatus ?? true;
806
- const quotaTtlMs = displaySettings.menuQuotaTtlMs ?? 5 * 60_000;
807
- if (shouldAutoFetchLimits && !pendingMenuQuotaRefresh) {
808
- const staleCount = deps.countMenuQuotaRefreshTargets(currentStorage, quotaCache, quotaTtlMs);
809
- if (staleCount > 0) {
810
- if (showFetchStatus) {
811
- menuQuotaRefreshStatus = `${UI_COPY.mainMenu.loadingLimits} [0/${staleCount}]`;
812
- }
813
- pendingMenuQuotaRefresh = deps
814
- .refreshQuotaCacheForMenu(currentStorage, quotaCache, quotaTtlMs, (current, total) => {
815
- if (!showFetchStatus)
816
- return;
817
- menuQuotaRefreshStatus = `${UI_COPY.mainMenu.loadingLimits} [${current}/${total}]`;
818
- })
819
- .then(() => undefined)
820
- .catch(() => undefined)
821
- .finally(() => {
822
- menuQuotaRefreshStatus = undefined;
823
- pendingMenuQuotaRefresh = null;
824
- });
825
- }
826
- }
827
- const flaggedStorage = await loadFlaggedAccounts();
828
- const menuResult = await promptLoginMode(deps.toExistingAccountInfo(currentStorage, quotaCache, displaySettings), {
829
- flaggedCount: flaggedStorage.accounts.length,
830
- statusMessage: showFetchStatus
831
- ? () => menuQuotaRefreshStatus
832
- : undefined,
833
- });
834
- if (menuResult.mode === "cancel") {
835
- console.log("Cancelled.");
836
- return 0;
837
- }
838
- if (menuResult.mode === "check") {
839
- await runActionPanel(deps.stylePromptText, "Quick Check", "Checking local session + live status", async () => {
840
- await deps.runHealthCheck({
841
- forceRefresh: false,
842
- liveProbe: true,
843
- });
844
- }, displaySettings);
845
- continue;
846
- }
847
- if (menuResult.mode === "deep-check") {
848
- await runActionPanel(deps.stylePromptText, "Deep Check", "Refreshing and testing all accounts", async () => {
849
- await deps.runHealthCheck({
850
- forceRefresh: true,
851
- liveProbe: true,
852
- });
853
- }, displaySettings);
854
- continue;
855
- }
856
- if (menuResult.mode === "forecast") {
857
- await runActionPanel(deps.stylePromptText, "Best Account", "Comparing accounts", async () => {
858
- await deps.runForecast(["--live"]);
859
- }, displaySettings);
860
- continue;
861
- }
862
- if (menuResult.mode === "fix") {
863
- await runActionPanel(deps.stylePromptText, "Auto-Fix", "Checking and fixing common issues", async () => {
864
- await deps.runFix(["--live"]);
865
- }, displaySettings);
866
- continue;
867
- }
868
- if (menuResult.mode === "settings") {
869
- await deps.configureUnifiedSettings(displaySettings);
870
- continue;
871
- }
872
- if (menuResult.mode === "verify-flagged") {
873
- await runActionPanel(deps.stylePromptText, "Problem Account Check", "Checking problem accounts", async () => {
874
- await deps.runVerifyFlagged([]);
875
- }, displaySettings);
876
- continue;
877
- }
878
- if (menuResult.mode === "fresh" && menuResult.deleteAll) {
879
- await runActionPanel(deps.stylePromptText, "Reset Accounts", "Deleting all saved accounts", async () => {
880
- await clearAccountsAndReset();
881
- console.log("Cleared saved accounts from active storage. Recovery snapshots remain available.");
882
- }, displaySettings);
883
- continue;
884
- }
885
- if (menuResult.mode === "manage") {
886
- const requiresInteractiveOAuth = typeof menuResult.refreshAccountIndex === "number";
887
- if (requiresInteractiveOAuth) {
888
- await handleManageAction(currentStorage, menuResult, deps);
889
- continue;
890
- }
891
- await runActionPanel(deps.stylePromptText, "Applying Change", "Updating selected account", async () => {
892
- await handleManageAction(currentStorage, menuResult, deps);
893
- }, displaySettings);
894
- continue;
895
- }
896
- if (menuResult.mode === "add") {
897
- break;
898
- }
899
- }
900
- }
901
- const refreshedStorage = await loadAccounts();
902
- let existingCount = refreshedStorage?.accounts.length ?? 0;
903
- let forceNewLogin = existingCount > 0;
904
- let onboardingBackupDiscoveryWarning = null;
905
- const loadNamedBackupsForOnboarding = async () => {
906
- if (existingCount > 0) {
907
- onboardingBackupDiscoveryWarning = null;
908
- return [];
909
- }
910
- try {
911
- onboardingBackupDiscoveryWarning = null;
912
- return await getNamedBackups();
913
- }
914
- catch (error) {
915
- const code = error.code;
916
- log.debug("getNamedBackups failed, skipping restore option", {
917
- code,
918
- error: error instanceof Error ? error.message : String(error),
919
- });
920
- if (code && code !== "ENOENT") {
921
- onboardingBackupDiscoveryWarning =
922
- "Named backup discovery failed. Continuing with browser or manual sign-in only.";
923
- console.warn(onboardingBackupDiscoveryWarning);
924
- }
925
- else {
926
- onboardingBackupDiscoveryWarning = null;
927
- }
928
- return [];
929
- }
930
- };
931
- let namedBackups = await loadNamedBackupsForOnboarding();
932
- while (true) {
933
- const latestNamedBackup = namedBackups[0] ?? null;
934
- const preferManualMode = loginOptions.manual || isBrowserLaunchSuppressed();
935
- const signInMode = preferManualMode
936
- ? "manual"
937
- : await promptOAuthSignInMode(latestNamedBackup, onboardingBackupDiscoveryWarning);
938
- if (signInMode === "cancel") {
939
- if (existingCount > 0) {
940
- console.log(deps.stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
941
- continue loginFlow;
942
- }
943
- console.log("Cancelled.");
944
- return 0;
945
- }
946
- if (signInMode === "restore-backup") {
947
- const latestAvailableBackup = namedBackups[0] ?? null;
948
- if (!latestAvailableBackup) {
949
- namedBackups = await loadNamedBackupsForOnboarding();
950
- continue;
951
- }
952
- const restoreMode = await promptBackupRestoreMode(latestAvailableBackup);
953
- if (restoreMode === "back") {
954
- namedBackups = await loadNamedBackupsForOnboarding();
955
- continue;
956
- }
957
- const selectedBackup = restoreMode === "manual"
958
- ? await promptManualBackupSelection(namedBackups)
959
- : latestAvailableBackup;
960
- if (!selectedBackup) {
961
- namedBackups = await loadNamedBackupsForOnboarding();
962
- continue;
963
- }
964
- const confirmed = await confirm(UI_COPY.oauth.restoreBackupConfirm(selectedBackup.fileName, selectedBackup.accountCount));
965
- if (!confirmed) {
966
- namedBackups = await loadNamedBackupsForOnboarding();
967
- continue;
968
- }
969
- const displaySettings = await loadDashboardDisplaySettings();
970
- applyUiThemeFromDashboardSettings(displaySettings);
971
- try {
972
- await runActionPanel(deps.stylePromptText, "Load Backup", `Loading ${selectedBackup.fileName}`, async () => {
973
- const restoredStorage = await restoreAccountsFromBackup(selectedBackup.path, { persist: false });
974
- const targetIndex = deps.resolveActiveIndex(restoredStorage);
975
- const { synced } = await deps.persistAndSyncSelectedAccount({
976
- storage: restoredStorage,
977
- targetIndex,
978
- parsed: targetIndex + 1,
979
- switchReason: "restore",
980
- preserveActiveIndexByFamily: true,
981
- });
982
- console.log(UI_COPY.oauth.restoreBackupLoaded(selectedBackup.fileName, restoredStorage.accounts.length));
983
- if (!synced) {
984
- console.warn(UI_COPY.oauth.restoreBackupSyncWarning);
985
- }
986
- }, displaySettings);
987
- }
988
- catch (error) {
989
- const message = error instanceof Error ? error.message : String(error);
990
- if (error instanceof StorageError) {
991
- console.error(formatStorageErrorHint(error, selectedBackup.path));
992
- }
993
- else {
994
- console.error(`Backup restore failed: ${message}`);
995
- }
996
- const storageAfterRestoreAttempt = await loadAccounts().catch(() => null);
997
- if ((storageAfterRestoreAttempt?.accounts.length ?? 0) > 0) {
998
- continue loginFlow;
999
- }
1000
- namedBackups = await loadNamedBackupsForOnboarding();
1001
- continue;
1002
- }
1003
- continue loginFlow;
1004
- }
1005
- if (signInMode !== "browser" && signInMode !== "manual") {
1006
- continue;
1007
- }
1008
- const tokenResult = await runOAuthFlow(forceNewLogin, signInMode, deps.stylePromptText);
1009
- if (tokenResult.type !== "success") {
1010
- if (isUserCancelledOAuth(tokenResult)) {
1011
- if (existingCount > 0) {
1012
- console.log(deps.stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
1013
- continue loginFlow;
1014
- }
1015
- console.log("Cancelled.");
1016
- return 0;
1017
- }
1018
- console.error(`Login failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
1019
- return 1;
1020
- }
1021
- const resolved = resolveAccountSelection(tokenResult);
1022
- await persistAccountPool([resolved], false);
1023
- await syncSelectionToCodex(resolved);
1024
- const latestStorage = await loadAccounts();
1025
- const count = latestStorage?.accounts.length ?? 1;
1026
- existingCount = count;
1027
- namedBackups = [];
1028
- onboardingBackupDiscoveryWarning = null;
1029
- console.log(`Added account. Total: ${count}`);
1030
- if (count >= ACCOUNT_LIMITS.MAX_ACCOUNTS) {
1031
- console.log(`Reached maximum account limit (${ACCOUNT_LIMITS.MAX_ACCOUNTS}).`);
1032
- break;
1033
- }
1034
- const addAnother = await promptAddAnotherAccount(count);
1035
- if (!addAnother)
1036
- break;
1037
- forceNewLogin = true;
1038
- }
1039
- }
1040
- }
1041
- //# sourceMappingURL=login.js.map