codex-multi-auth 1.2.1 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -21
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +11 -0
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +320 -70
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +4 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +33 -1
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts +2 -1
- package/dist/lib/codex-manager/commands/forecast.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/forecast.js +92 -2
- package/dist/lib/codex-manager/commands/forecast.js.map +1 -1
- package/dist/lib/codex-manager/commands/report.d.ts +3 -1
- package/dist/lib/codex-manager/commands/report.d.ts.map +1 -1
- package/dist/lib/codex-manager/commands/report.js +102 -12
- package/dist/lib/codex-manager/commands/report.js.map +1 -1
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +50 -40
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +172 -17
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/forecast.d.ts.map +1 -1
- package/dist/lib/forecast.js +1 -19
- package/dist/lib/forecast.js.map +1 -1
- package/dist/lib/health.d.ts +1 -0
- package/dist/lib/health.d.ts.map +1 -1
- package/dist/lib/health.js +6 -2
- package/dist/lib/health.js.map +1 -1
- package/dist/lib/parallel-probe.d.ts +1 -1
- package/dist/lib/parallel-probe.d.ts.map +1 -1
- package/dist/lib/parallel-probe.js +5 -2
- package/dist/lib/parallel-probe.js.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -1
- package/dist/lib/preemptive-quota-scheduler.js +6 -3
- package/dist/lib/preemptive-quota-scheduler.js.map +1 -1
- package/dist/lib/proactive-refresh.d.ts +1 -1
- package/dist/lib/proactive-refresh.d.ts.map +1 -1
- package/dist/lib/proactive-refresh.js +14 -5
- package/dist/lib/proactive-refresh.js.map +1 -1
- package/dist/lib/refresh-guardian.d.ts +3 -0
- package/dist/lib/refresh-guardian.d.ts.map +1 -1
- package/dist/lib/refresh-guardian.js +142 -52
- package/dist/lib/refresh-guardian.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +180 -43
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/rotation.d.ts +14 -11
- package/dist/lib/rotation.d.ts.map +1 -1
- package/dist/lib/rotation.js +36 -30
- package/dist/lib/rotation.js.map +1 -1
- package/dist/lib/runtime/account-check.d.ts +4 -0
- package/dist/lib/runtime/account-check.d.ts.map +1 -1
- package/dist/lib/runtime/account-check.js +15 -10
- package/dist/lib/runtime/account-check.js.map +1 -1
- package/dist/lib/runtime/account-state.d.ts +1 -12
- package/dist/lib/runtime/account-state.d.ts.map +1 -1
- package/dist/lib/runtime/account-state.js +1 -37
- package/dist/lib/runtime/account-state.js.map +1 -1
- package/dist/lib/runtime/verify-flagged.d.ts +4 -0
- package/dist/lib/runtime/verify-flagged.d.ts.map +1 -1
- package/dist/lib/runtime/verify-flagged.js +32 -23
- package/dist/lib/runtime/verify-flagged.js.map +1 -1
- package/dist/lib/storage/flagged-load-entry.d.ts +2 -0
- package/dist/lib/storage/flagged-load-entry.d.ts.map +1 -1
- package/dist/lib/storage/flagged-load-entry.js +1 -0
- package/dist/lib/storage/flagged-load-entry.js.map +1 -1
- package/dist/lib/storage/flagged-storage-file.d.ts +4 -0
- package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage-file.js +1 -1
- package/dist/lib/storage/flagged-storage-file.js.map +1 -1
- package/dist/lib/storage/flagged-storage-io.d.ts +1 -0
- package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -1
- package/dist/lib/storage/flagged-storage-io.js +130 -5
- package/dist/lib/storage/flagged-storage-io.js.map +1 -1
- package/dist/lib/storage/identity.d.ts +6 -0
- package/dist/lib/storage/identity.d.ts.map +1 -1
- package/dist/lib/storage/identity.js +14 -3
- package/dist/lib/storage/identity.js.map +1 -1
- package/dist/lib/storage/path-state.d.ts +1 -0
- package/dist/lib/storage/path-state.d.ts.map +1 -1
- package/dist/lib/storage/path-state.js +3 -0
- package/dist/lib/storage/path-state.js.map +1 -1
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +3 -2
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +7 -0
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/unified-settings.d.ts.map +1 -1
- package/dist/lib/unified-settings.js +226 -28
- package/dist/lib/unified-settings.js.map +1 -1
- package/package.json +8 -3
- package/scripts/codex-bin-resolver.js +99 -0
- package/scripts/codex.js +709 -81
- package/dist/lib/codex-manager/best.d.ts +0 -26
- package/dist/lib/codex-manager/best.d.ts.map +0 -1
- package/dist/lib/codex-manager/best.js +0 -249
- package/dist/lib/codex-manager/best.js.map +0 -1
- package/dist/lib/codex-manager/config-explain.d.ts +0 -2
- package/dist/lib/codex-manager/config-explain.d.ts.map +0 -1
- package/dist/lib/codex-manager/config-explain.js +0 -62
- package/dist/lib/codex-manager/config-explain.js.map +0 -1
- package/dist/lib/codex-manager/config-template.d.ts +0 -2
- package/dist/lib/codex-manager/config-template.d.ts.map +0 -1
- package/dist/lib/codex-manager/config-template.js +0 -112
- package/dist/lib/codex-manager/config-template.js.map +0 -1
- package/dist/lib/codex-manager/debug-bundle.d.ts +0 -2
- package/dist/lib/codex-manager/debug-bundle.d.ts.map +0 -1
- package/dist/lib/codex-manager/debug-bundle.js +0 -128
- package/dist/lib/codex-manager/debug-bundle.js.map +0 -1
- package/dist/lib/codex-manager/doctor.d.ts +0 -20
- package/dist/lib/codex-manager/doctor.d.ts.map +0 -1
- package/dist/lib/codex-manager/doctor.js +0 -546
- package/dist/lib/codex-manager/doctor.js.map +0 -1
- package/dist/lib/codex-manager/fix.d.ts +0 -26
- package/dist/lib/codex-manager/fix.d.ts.map +0 -1
- package/dist/lib/codex-manager/fix.js +0 -366
- package/dist/lib/codex-manager/fix.js.map +0 -1
- package/dist/lib/codex-manager/forecast.d.ts +0 -26
- package/dist/lib/codex-manager/forecast.d.ts.map +0 -1
- package/dist/lib/codex-manager/forecast.js +0 -218
- package/dist/lib/codex-manager/forecast.js.map +0 -1
- package/dist/lib/codex-manager/info.d.ts +0 -11
- package/dist/lib/codex-manager/info.d.ts.map +0 -1
- package/dist/lib/codex-manager/info.js +0 -91
- package/dist/lib/codex-manager/info.js.map +0 -1
- package/dist/lib/codex-manager/login.d.ts +0 -40
- package/dist/lib/codex-manager/login.d.ts.map +0 -1
- package/dist/lib/codex-manager/login.js +0 -1041
- package/dist/lib/codex-manager/login.js.map +0 -1
- package/dist/lib/codex-manager/report.d.ts +0 -39
- package/dist/lib/codex-manager/report.d.ts.map +0 -1
- package/dist/lib/codex-manager/report.js +0 -243
- package/dist/lib/codex-manager/report.js.map +0 -1
- package/dist/lib/codex-manager/switch.d.ts +0 -31
- package/dist/lib/codex-manager/switch.d.ts.map +0 -1
- package/dist/lib/codex-manager/switch.js +0 -103
- package/dist/lib/codex-manager/switch.js.map +0 -1
- package/dist/lib/codex-manager/verify-flagged.d.ts +0 -20
- package/dist/lib/codex-manager/verify-flagged.d.ts.map +0 -1
- package/dist/lib/codex-manager/verify-flagged.js +0 -316
- package/dist/lib/codex-manager/verify-flagged.js.map +0 -1
- package/dist/lib/request/trace-mode.d.ts +0 -9
- package/dist/lib/request/trace-mode.d.ts.map +0 -1
- package/dist/lib/request/trace-mode.js +0 -14
- 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
|