codex-multi-auth 1.1.11 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -29
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts.d.ts +22 -2
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +84 -1
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/browser.d.ts +1 -0
- package/dist/lib/auth/browser.d.ts.map +1 -1
- package/dist/lib/auth/browser.js +13 -0
- package/dist/lib/auth/browser.js.map +1 -1
- package/dist/lib/auth/token-utils.d.ts +16 -0
- package/dist/lib/auth/token-utils.d.ts.map +1 -1
- package/dist/lib/auth/token-utils.js +13 -0
- package/dist/lib/auth/token-utils.js.map +1 -1
- package/dist/lib/codex-manager/settings-hub.d.ts +21 -0
- package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
- package/dist/lib/codex-manager/settings-hub.js +48 -65
- package/dist/lib/codex-manager/settings-hub.js.map +1 -1
- package/dist/lib/codex-manager.d.ts +1 -0
- package/dist/lib/codex-manager.d.ts.map +1 -1
- package/dist/lib/codex-manager.js +365 -33
- package/dist/lib/codex-manager.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +18 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +157 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/schemas.d.ts +7 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +7 -1
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/migrations.d.ts +5 -2
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage.d.ts +10 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +191 -40
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/ui/copy.d.ts +24 -4
- package/dist/lib/ui/copy.d.ts.map +1 -1
- package/dist/lib/ui/copy.js +24 -4
- package/dist/lib/ui/copy.js.map +1 -1
- package/package.json +4 -3
|
@@ -4,25 +4,28 @@ import { promises as fs, existsSync } from "node:fs";
|
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, REDIRECT_URI, } from "./auth/auth.js";
|
|
6
6
|
import { startLocalOAuthServer } from "./auth/server.js";
|
|
7
|
-
import { copyTextToClipboard, openBrowserUrl } from "./auth/browser.js";
|
|
7
|
+
import { copyTextToClipboard, isBrowserLaunchSuppressed, openBrowserUrl } from "./auth/browser.js";
|
|
8
8
|
import { promptAddAnotherAccount, promptLoginMode } from "./cli.js";
|
|
9
9
|
import { extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, getAccountIdCandidates, resolveRequestAccountId, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./accounts.js";
|
|
10
10
|
import { ACCOUNT_LIMITS } from "./constants.js";
|
|
11
11
|
import { loadDashboardDisplaySettings, DEFAULT_DASHBOARD_DISPLAY_SETTINGS, } from "./dashboard-settings.js";
|
|
12
12
|
import { evaluateForecastAccounts, isHardRefreshFailure, recommendForecastAccount, summarizeForecast, } from "./forecast.js";
|
|
13
|
+
import { createLogger } from "./logger.js";
|
|
13
14
|
import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
14
15
|
import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./quota-probe.js";
|
|
15
16
|
import { queuedRefresh } from "./refresh-queue.js";
|
|
16
17
|
import { loadQuotaCache, saveQuotaCache, } from "./quota-cache.js";
|
|
17
|
-
import { clearAccounts, findMatchingAccountIndex, getStoragePath, loadFlaggedAccounts, loadAccounts, saveFlaggedAccounts, saveAccounts, setStoragePath, withAccountAndFlaggedStorageTransaction, withAccountStorageTransaction, } from "./storage.js";
|
|
18
|
+
import { clearAccounts, findMatchingAccountIndex, formatStorageErrorHint, getNamedBackups, getStoragePath, loadFlaggedAccounts, loadAccounts, StorageError, restoreAccountsFromBackup, saveFlaggedAccounts, saveAccounts, setStoragePath, withAccountAndFlaggedStorageTransaction, withAccountStorageTransaction, } from "./storage.js";
|
|
18
19
|
import { getCodexCliAuthPath, getCodexCliConfigPath, loadCodexCliState, } from "./codex-cli/state.js";
|
|
19
20
|
import { setCodexCliActiveSelection } from "./codex-cli/writer.js";
|
|
20
21
|
import { ANSI } from "./ui/ansi.js";
|
|
21
22
|
import { UI_COPY } from "./ui/copy.js";
|
|
23
|
+
import { confirm } from "./ui/confirm.js";
|
|
22
24
|
import { paintUiText, quotaToneFromLeftPercent } from "./ui/format.js";
|
|
23
25
|
import { getUiRuntimeOptions } from "./ui/runtime.js";
|
|
24
26
|
import { select } from "./ui/select.js";
|
|
25
27
|
import { applyUiThemeFromDashboardSettings, configureUnifiedSettings, resolveMenuLayoutMode } from "./codex-manager/settings-hub.js";
|
|
28
|
+
const log = createLogger("codex-manager");
|
|
26
29
|
function stylePromptText(text, tone) {
|
|
27
30
|
if (!output.isTTY)
|
|
28
31
|
return text;
|
|
@@ -215,7 +218,7 @@ function printUsage() {
|
|
|
215
218
|
"Codex Multi-Auth CLI",
|
|
216
219
|
"",
|
|
217
220
|
"Usage:",
|
|
218
|
-
" codex auth login",
|
|
221
|
+
" codex auth login [--manual|--no-browser]",
|
|
219
222
|
" codex auth list",
|
|
220
223
|
" codex auth status",
|
|
221
224
|
" codex auth switch <index>",
|
|
@@ -233,6 +236,26 @@ function printUsage() {
|
|
|
233
236
|
" - Syncs active account into Codex CLI auth state",
|
|
234
237
|
].join("\n"));
|
|
235
238
|
}
|
|
239
|
+
function parseAuthLoginArgs(args) {
|
|
240
|
+
const options = {
|
|
241
|
+
manual: false,
|
|
242
|
+
};
|
|
243
|
+
for (const arg of args) {
|
|
244
|
+
if (arg === "--manual" || arg === "--no-browser") {
|
|
245
|
+
options.manual = true;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (arg === "--help" || arg === "-h") {
|
|
249
|
+
printUsage();
|
|
250
|
+
return { ok: false, message: "" };
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
message: `Unknown login option: ${arg}`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return { ok: true, options };
|
|
258
|
+
}
|
|
236
259
|
const IMPLEMENTED_FEATURES = [
|
|
237
260
|
{ id: 1, name: "Multi-account OAuth login dashboard" },
|
|
238
261
|
{ id: 2, name: "Account add/update dedupe by token/id/email" },
|
|
@@ -829,15 +852,64 @@ function applyTokenAccountIdentity(account, tokenAccountId) {
|
|
|
829
852
|
account.accountIdSource = nextIdentity.accountIdSource;
|
|
830
853
|
return true;
|
|
831
854
|
}
|
|
832
|
-
async function promptManualCallback(state) {
|
|
833
|
-
|
|
855
|
+
async function promptManualCallback(state, options = {}) {
|
|
856
|
+
const useInteractivePrompt = input.isTTY && output.isTTY;
|
|
857
|
+
if (!useInteractivePrompt && !options.allowNonTty) {
|
|
834
858
|
return null;
|
|
835
859
|
}
|
|
836
860
|
const rl = createInterface({ input, output });
|
|
837
861
|
try {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
862
|
+
if (useInteractivePrompt) {
|
|
863
|
+
console.log("");
|
|
864
|
+
console.log(stylePromptText(UI_COPY.oauth.pastePrompt, "accent"));
|
|
865
|
+
}
|
|
866
|
+
const answer = useInteractivePrompt
|
|
867
|
+
? await rl.question("◆ ")
|
|
868
|
+
: await new Promise((resolve, reject) => {
|
|
869
|
+
if (input.readableEnded || input.destroyed) {
|
|
870
|
+
resolve(null);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
let settled = false;
|
|
874
|
+
const handleInputClosed = () => {
|
|
875
|
+
if (settled)
|
|
876
|
+
return;
|
|
877
|
+
settled = true;
|
|
878
|
+
input.off("end", handleInputClosed);
|
|
879
|
+
input.off("close", handleInputClosed);
|
|
880
|
+
resolve(null);
|
|
881
|
+
};
|
|
882
|
+
const finish = (value) => {
|
|
883
|
+
if (settled)
|
|
884
|
+
return;
|
|
885
|
+
settled = true;
|
|
886
|
+
input.off("end", handleInputClosed);
|
|
887
|
+
input.off("close", handleInputClosed);
|
|
888
|
+
resolve(value);
|
|
889
|
+
};
|
|
890
|
+
const fail = (error) => {
|
|
891
|
+
if (settled)
|
|
892
|
+
return;
|
|
893
|
+
settled = true;
|
|
894
|
+
input.off("end", handleInputClosed);
|
|
895
|
+
input.off("close", handleInputClosed);
|
|
896
|
+
reject(error);
|
|
897
|
+
};
|
|
898
|
+
rl.question("")
|
|
899
|
+
.then((value) => finish(value))
|
|
900
|
+
.catch((error) => {
|
|
901
|
+
if (isAbortError(error) || isReadlineClosedError(error)) {
|
|
902
|
+
handleInputClosed();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
fail(error);
|
|
906
|
+
});
|
|
907
|
+
input.once("end", handleInputClosed);
|
|
908
|
+
input.once("close", handleInputClosed);
|
|
909
|
+
});
|
|
910
|
+
if (answer === null) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
841
913
|
if (answer.includes("\u001b")) {
|
|
842
914
|
return null;
|
|
843
915
|
}
|
|
@@ -857,7 +929,7 @@ async function promptManualCallback(state) {
|
|
|
857
929
|
return parsed.code;
|
|
858
930
|
}
|
|
859
931
|
catch (error) {
|
|
860
|
-
if (isAbortError(error)) {
|
|
932
|
+
if (isAbortError(error) || isReadlineClosedError(error)) {
|
|
861
933
|
return null;
|
|
862
934
|
}
|
|
863
935
|
throw error;
|
|
@@ -866,20 +938,56 @@ async function promptManualCallback(state) {
|
|
|
866
938
|
rl.close();
|
|
867
939
|
}
|
|
868
940
|
}
|
|
869
|
-
|
|
941
|
+
function isReadlineClosedError(error) {
|
|
942
|
+
if (!(error instanceof Error)) {
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
const errorCode = typeof error === "object" && error !== null && "code" in error
|
|
946
|
+
? String(error.code)
|
|
947
|
+
: "";
|
|
948
|
+
return errorCode === "ERR_USE_AFTER_CLOSE" || /readline was closed/i.test(error.message);
|
|
949
|
+
}
|
|
950
|
+
export function formatBackupSavedAt(mtimeMs) {
|
|
951
|
+
return new Date(mtimeMs).toLocaleString(undefined, {
|
|
952
|
+
month: "short",
|
|
953
|
+
day: "numeric",
|
|
954
|
+
year: "numeric",
|
|
955
|
+
hour: "numeric",
|
|
956
|
+
minute: "2-digit",
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
async function promptOAuthSignInMode(backupOption, backupDiscoveryWarning = null) {
|
|
870
960
|
if (!input.isTTY || !output.isTTY) {
|
|
871
961
|
return "browser";
|
|
872
962
|
}
|
|
873
963
|
const ui = getUiRuntimeOptions();
|
|
874
964
|
const items = [
|
|
965
|
+
{ label: UI_COPY.oauth.signInHeading, value: "cancel", kind: "heading" },
|
|
875
966
|
{ label: UI_COPY.oauth.openBrowser, value: "browser", color: "green" },
|
|
876
967
|
{ label: UI_COPY.oauth.manualMode, value: "manual", color: "yellow" },
|
|
968
|
+
...(backupOption
|
|
969
|
+
? [
|
|
970
|
+
{ separator: true, label: "", value: "cancel" },
|
|
971
|
+
{ label: UI_COPY.oauth.restoreHeading, value: "cancel", kind: "heading" },
|
|
972
|
+
{
|
|
973
|
+
label: UI_COPY.oauth.restoreSavedBackup,
|
|
974
|
+
value: "restore-backup",
|
|
975
|
+
hint: UI_COPY.oauth.loadLastBackupHint(backupOption.fileName, backupOption.accountCount, formatBackupSavedAt(backupOption.mtimeMs)),
|
|
976
|
+
color: "cyan",
|
|
977
|
+
},
|
|
978
|
+
]
|
|
979
|
+
: []),
|
|
980
|
+
{ separator: true, label: "", value: "cancel" },
|
|
877
981
|
{ label: UI_COPY.oauth.back, value: "cancel", color: "red" },
|
|
878
982
|
];
|
|
879
983
|
const selected = await select(items, {
|
|
880
984
|
message: UI_COPY.oauth.chooseModeTitle,
|
|
881
|
-
subtitle:
|
|
882
|
-
|
|
985
|
+
subtitle: backupDiscoveryWarning
|
|
986
|
+
? `${UI_COPY.oauth.chooseModeSubtitle} ${backupDiscoveryWarning}`
|
|
987
|
+
: UI_COPY.oauth.chooseModeSubtitle,
|
|
988
|
+
help: backupOption
|
|
989
|
+
? UI_COPY.oauth.chooseModeHelpWithBackup
|
|
990
|
+
: UI_COPY.oauth.chooseModeHelp,
|
|
883
991
|
clearScreen: true,
|
|
884
992
|
theme: ui.theme,
|
|
885
993
|
selectedEmphasis: "minimal",
|
|
@@ -892,11 +1000,81 @@ async function promptOAuthSignInMode() {
|
|
|
892
1000
|
return "browser";
|
|
893
1001
|
if (lower === "2")
|
|
894
1002
|
return "manual";
|
|
1003
|
+
if (lower === "3" && backupOption)
|
|
1004
|
+
return "restore-backup";
|
|
895
1005
|
return undefined;
|
|
896
1006
|
},
|
|
897
1007
|
});
|
|
898
1008
|
return selected ?? "cancel";
|
|
899
1009
|
}
|
|
1010
|
+
async function promptBackupRestoreMode(latestBackup) {
|
|
1011
|
+
if (!input.isTTY || !output.isTTY) {
|
|
1012
|
+
return "latest";
|
|
1013
|
+
}
|
|
1014
|
+
const ui = getUiRuntimeOptions();
|
|
1015
|
+
const items = [
|
|
1016
|
+
{
|
|
1017
|
+
label: UI_COPY.oauth.loadLastBackup,
|
|
1018
|
+
value: "latest",
|
|
1019
|
+
hint: `${UI_COPY.oauth.restoreBackupLatestHint}\n${UI_COPY.oauth.manualBackupHint(latestBackup.accountCount, formatBackupSavedAt(latestBackup.mtimeMs))}`,
|
|
1020
|
+
color: "cyan",
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
label: UI_COPY.oauth.chooseBackupManually,
|
|
1024
|
+
value: "manual",
|
|
1025
|
+
color: "yellow",
|
|
1026
|
+
},
|
|
1027
|
+
{ label: UI_COPY.oauth.back, value: "back", color: "red" },
|
|
1028
|
+
];
|
|
1029
|
+
const selected = await select(items, {
|
|
1030
|
+
message: UI_COPY.oauth.restoreBackupTitle,
|
|
1031
|
+
subtitle: UI_COPY.oauth.restoreBackupSubtitle,
|
|
1032
|
+
help: UI_COPY.oauth.restoreBackupHelp,
|
|
1033
|
+
clearScreen: true,
|
|
1034
|
+
theme: ui.theme,
|
|
1035
|
+
selectedEmphasis: "minimal",
|
|
1036
|
+
allowEscape: false,
|
|
1037
|
+
onInput: (raw) => {
|
|
1038
|
+
const lower = raw.toLowerCase();
|
|
1039
|
+
if (lower === "q")
|
|
1040
|
+
return "back";
|
|
1041
|
+
if (lower === "1")
|
|
1042
|
+
return "latest";
|
|
1043
|
+
if (lower === "2")
|
|
1044
|
+
return "manual";
|
|
1045
|
+
return undefined;
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1048
|
+
return selected ?? "back";
|
|
1049
|
+
}
|
|
1050
|
+
async function promptManualBackupSelection(backups) {
|
|
1051
|
+
if (!input.isTTY || !output.isTTY) {
|
|
1052
|
+
return backups[0] ?? null;
|
|
1053
|
+
}
|
|
1054
|
+
const ui = getUiRuntimeOptions();
|
|
1055
|
+
const items = backups.map((backup) => ({
|
|
1056
|
+
label: backup.fileName,
|
|
1057
|
+
value: backup,
|
|
1058
|
+
hint: UI_COPY.oauth.manualBackupHint(backup.accountCount, formatBackupSavedAt(backup.mtimeMs)),
|
|
1059
|
+
color: "cyan",
|
|
1060
|
+
}));
|
|
1061
|
+
items.push({ label: UI_COPY.oauth.back, value: null, color: "red" });
|
|
1062
|
+
const selected = await select(items, {
|
|
1063
|
+
message: UI_COPY.oauth.manualBackupTitle,
|
|
1064
|
+
subtitle: UI_COPY.oauth.manualBackupSubtitle,
|
|
1065
|
+
help: UI_COPY.oauth.manualBackupHelp,
|
|
1066
|
+
clearScreen: true,
|
|
1067
|
+
theme: ui.theme,
|
|
1068
|
+
selectedEmphasis: "minimal",
|
|
1069
|
+
allowEscape: false,
|
|
1070
|
+
onInput: (raw) => {
|
|
1071
|
+
if (raw.toLowerCase() === "q")
|
|
1072
|
+
return null;
|
|
1073
|
+
return undefined;
|
|
1074
|
+
},
|
|
1075
|
+
});
|
|
1076
|
+
return selected;
|
|
1077
|
+
}
|
|
900
1078
|
async function waitForMenuReturn(options = {}) {
|
|
901
1079
|
if (!input.isTTY || !output.isTTY) {
|
|
902
1080
|
return;
|
|
@@ -1142,18 +1320,29 @@ async function runActionPanel(title, stage, action, settings) {
|
|
|
1142
1320
|
throw failed;
|
|
1143
1321
|
}
|
|
1144
1322
|
}
|
|
1145
|
-
async function runOAuthFlow(forceNewLogin) {
|
|
1323
|
+
async function runOAuthFlow(forceNewLogin, signInMode) {
|
|
1146
1324
|
const { pkce, state, url } = await createAuthorizationFlow({ forceNewLogin });
|
|
1147
|
-
const oauthServer = await startLocalOAuthServer({ state });
|
|
1148
1325
|
let code = null;
|
|
1326
|
+
let oauthServer = null;
|
|
1149
1327
|
try {
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1328
|
+
if (signInMode === "browser") {
|
|
1329
|
+
try {
|
|
1330
|
+
oauthServer = await startLocalOAuthServer({ state });
|
|
1331
|
+
}
|
|
1332
|
+
catch (serverError) {
|
|
1333
|
+
log.warn("Local OAuth callback server unavailable; falling back to manual callback entry.", serverError instanceof Error
|
|
1334
|
+
? {
|
|
1335
|
+
message: serverError.message,
|
|
1336
|
+
stack: serverError.stack,
|
|
1337
|
+
code: typeof serverError === "object" &&
|
|
1338
|
+
serverError !== null &&
|
|
1339
|
+
"code" in serverError
|
|
1340
|
+
? String(serverError.code)
|
|
1341
|
+
: undefined,
|
|
1342
|
+
}
|
|
1343
|
+
: { error: String(serverError) });
|
|
1344
|
+
oauthServer = null;
|
|
1345
|
+
}
|
|
1157
1346
|
}
|
|
1158
1347
|
if (signInMode === "browser") {
|
|
1159
1348
|
const opened = openBrowserUrl(url);
|
|
@@ -1172,18 +1361,23 @@ async function runOAuthFlow(forceNewLogin) {
|
|
|
1172
1361
|
const copied = copyTextToClipboard(url);
|
|
1173
1362
|
console.log(stylePromptText(copied ? UI_COPY.oauth.copyOk : UI_COPY.oauth.copyFail, copied ? "success" : "warning"));
|
|
1174
1363
|
}
|
|
1175
|
-
|
|
1364
|
+
const waitingForCallback = signInMode === "browser" && oauthServer?.ready === true;
|
|
1365
|
+
if (waitingForCallback && oauthServer) {
|
|
1176
1366
|
console.log(stylePromptText(UI_COPY.oauth.waitingCallback, "muted"));
|
|
1177
1367
|
const callbackResult = await oauthServer.waitForCode(state);
|
|
1178
1368
|
code = callbackResult?.code ?? null;
|
|
1179
1369
|
}
|
|
1180
1370
|
if (!code) {
|
|
1181
|
-
console.log(stylePromptText(
|
|
1182
|
-
|
|
1371
|
+
console.log(stylePromptText(waitingForCallback
|
|
1372
|
+
? UI_COPY.oauth.callbackMissed
|
|
1373
|
+
: signInMode === "manual"
|
|
1374
|
+
? UI_COPY.oauth.callbackBypassed
|
|
1375
|
+
: UI_COPY.oauth.callbackUnavailable, "warning"));
|
|
1376
|
+
code = await promptManualCallback(state, { allowNonTty: signInMode === "manual" });
|
|
1183
1377
|
}
|
|
1184
1378
|
}
|
|
1185
1379
|
finally {
|
|
1186
|
-
oauthServer
|
|
1380
|
+
oauthServer?.close();
|
|
1187
1381
|
}
|
|
1188
1382
|
if (!code) {
|
|
1189
1383
|
return {
|
|
@@ -3369,7 +3563,15 @@ async function handleManageAction(storage, menuResult) {
|
|
|
3369
3563
|
const existing = storage.accounts[idx];
|
|
3370
3564
|
if (!existing)
|
|
3371
3565
|
return;
|
|
3372
|
-
const
|
|
3566
|
+
const signInMode = await promptOAuthSignInMode(null);
|
|
3567
|
+
if (signInMode === "cancel") {
|
|
3568
|
+
console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
if (signInMode !== "browser" && signInMode !== "manual") {
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
const tokenResult = await runOAuthFlow(true, signInMode);
|
|
3373
3575
|
if (tokenResult.type !== "success") {
|
|
3374
3576
|
console.error(`Refresh failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
|
|
3375
3577
|
return;
|
|
@@ -3380,7 +3582,17 @@ async function handleManageAction(storage, menuResult) {
|
|
|
3380
3582
|
console.log(`Refreshed account ${idx + 1}.`);
|
|
3381
3583
|
}
|
|
3382
3584
|
}
|
|
3383
|
-
async function runAuthLogin() {
|
|
3585
|
+
async function runAuthLogin(args) {
|
|
3586
|
+
const parsedArgs = parseAuthLoginArgs(args);
|
|
3587
|
+
if (!parsedArgs.ok) {
|
|
3588
|
+
if (parsedArgs.message) {
|
|
3589
|
+
console.error(parsedArgs.message);
|
|
3590
|
+
printUsage();
|
|
3591
|
+
return 1;
|
|
3592
|
+
}
|
|
3593
|
+
return 0;
|
|
3594
|
+
}
|
|
3595
|
+
const loginOptions = parsedArgs.options;
|
|
3384
3596
|
setStoragePath(null);
|
|
3385
3597
|
let pendingMenuQuotaRefresh = null;
|
|
3386
3598
|
let menuQuotaRefreshStatus;
|
|
@@ -3485,10 +3697,113 @@ async function runAuthLogin() {
|
|
|
3485
3697
|
}
|
|
3486
3698
|
}
|
|
3487
3699
|
const refreshedStorage = await loadAccounts();
|
|
3488
|
-
|
|
3700
|
+
let existingCount = refreshedStorage?.accounts.length ?? 0;
|
|
3489
3701
|
let forceNewLogin = existingCount > 0;
|
|
3702
|
+
let onboardingBackupDiscoveryWarning = null;
|
|
3703
|
+
const loadNamedBackupsForOnboarding = async () => {
|
|
3704
|
+
if (existingCount > 0) {
|
|
3705
|
+
onboardingBackupDiscoveryWarning = null;
|
|
3706
|
+
return [];
|
|
3707
|
+
}
|
|
3708
|
+
try {
|
|
3709
|
+
onboardingBackupDiscoveryWarning = null;
|
|
3710
|
+
return await getNamedBackups();
|
|
3711
|
+
}
|
|
3712
|
+
catch (error) {
|
|
3713
|
+
const code = error.code;
|
|
3714
|
+
log.debug("getNamedBackups failed, skipping restore option", {
|
|
3715
|
+
code,
|
|
3716
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3717
|
+
});
|
|
3718
|
+
if (code && code !== "ENOENT") {
|
|
3719
|
+
onboardingBackupDiscoveryWarning =
|
|
3720
|
+
"Named backup discovery failed. Continuing with browser or manual sign-in only.";
|
|
3721
|
+
console.warn(onboardingBackupDiscoveryWarning);
|
|
3722
|
+
}
|
|
3723
|
+
else {
|
|
3724
|
+
onboardingBackupDiscoveryWarning = null;
|
|
3725
|
+
}
|
|
3726
|
+
return [];
|
|
3727
|
+
}
|
|
3728
|
+
};
|
|
3729
|
+
let namedBackups = await loadNamedBackupsForOnboarding();
|
|
3490
3730
|
while (true) {
|
|
3491
|
-
const
|
|
3731
|
+
const latestNamedBackup = namedBackups[0] ?? null;
|
|
3732
|
+
const preferManualMode = loginOptions.manual || isBrowserLaunchSuppressed();
|
|
3733
|
+
const signInMode = preferManualMode
|
|
3734
|
+
? "manual"
|
|
3735
|
+
: await promptOAuthSignInMode(latestNamedBackup, onboardingBackupDiscoveryWarning);
|
|
3736
|
+
if (signInMode === "cancel") {
|
|
3737
|
+
if (existingCount > 0) {
|
|
3738
|
+
console.log(stylePromptText(UI_COPY.oauth.cancelledBackToMenu, "muted"));
|
|
3739
|
+
continue loginFlow;
|
|
3740
|
+
}
|
|
3741
|
+
console.log("Cancelled.");
|
|
3742
|
+
return 0;
|
|
3743
|
+
}
|
|
3744
|
+
if (signInMode === "restore-backup") {
|
|
3745
|
+
const latestAvailableBackup = namedBackups[0] ?? null;
|
|
3746
|
+
if (!latestAvailableBackup) {
|
|
3747
|
+
namedBackups = await loadNamedBackupsForOnboarding();
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
const restoreMode = await promptBackupRestoreMode(latestAvailableBackup);
|
|
3751
|
+
if (restoreMode === "back") {
|
|
3752
|
+
namedBackups = await loadNamedBackupsForOnboarding();
|
|
3753
|
+
continue;
|
|
3754
|
+
}
|
|
3755
|
+
const selectedBackup = restoreMode === "manual"
|
|
3756
|
+
? await promptManualBackupSelection(namedBackups)
|
|
3757
|
+
: latestAvailableBackup;
|
|
3758
|
+
if (!selectedBackup) {
|
|
3759
|
+
namedBackups = await loadNamedBackupsForOnboarding();
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
const confirmed = await confirm(UI_COPY.oauth.restoreBackupConfirm(selectedBackup.fileName, selectedBackup.accountCount));
|
|
3763
|
+
if (!confirmed) {
|
|
3764
|
+
namedBackups = await loadNamedBackupsForOnboarding();
|
|
3765
|
+
continue;
|
|
3766
|
+
}
|
|
3767
|
+
const displaySettings = await loadDashboardDisplaySettings();
|
|
3768
|
+
applyUiThemeFromDashboardSettings(displaySettings);
|
|
3769
|
+
try {
|
|
3770
|
+
await runActionPanel("Load Backup", `Loading ${selectedBackup.fileName}`, async () => {
|
|
3771
|
+
const restoredStorage = await restoreAccountsFromBackup(selectedBackup.path, { persist: false });
|
|
3772
|
+
const targetIndex = resolveActiveIndex(restoredStorage);
|
|
3773
|
+
const { synced } = await persistAndSyncSelectedAccount({
|
|
3774
|
+
storage: restoredStorage,
|
|
3775
|
+
targetIndex,
|
|
3776
|
+
parsed: targetIndex + 1,
|
|
3777
|
+
switchReason: "restore",
|
|
3778
|
+
preserveActiveIndexByFamily: true,
|
|
3779
|
+
});
|
|
3780
|
+
console.log(UI_COPY.oauth.restoreBackupLoaded(selectedBackup.fileName, restoredStorage.accounts.length));
|
|
3781
|
+
if (!synced) {
|
|
3782
|
+
console.warn(UI_COPY.oauth.restoreBackupSyncWarning);
|
|
3783
|
+
}
|
|
3784
|
+
}, displaySettings);
|
|
3785
|
+
}
|
|
3786
|
+
catch (error) {
|
|
3787
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3788
|
+
if (error instanceof StorageError) {
|
|
3789
|
+
console.error(formatStorageErrorHint(error, selectedBackup.path));
|
|
3790
|
+
}
|
|
3791
|
+
else {
|
|
3792
|
+
console.error(`Backup restore failed: ${message}`);
|
|
3793
|
+
}
|
|
3794
|
+
const storageAfterRestoreAttempt = await loadAccounts().catch(() => null);
|
|
3795
|
+
if ((storageAfterRestoreAttempt?.accounts.length ?? 0) > 0) {
|
|
3796
|
+
continue loginFlow;
|
|
3797
|
+
}
|
|
3798
|
+
namedBackups = await loadNamedBackupsForOnboarding();
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
continue loginFlow;
|
|
3802
|
+
}
|
|
3803
|
+
if (signInMode !== "browser" && signInMode !== "manual") {
|
|
3804
|
+
continue;
|
|
3805
|
+
}
|
|
3806
|
+
const tokenResult = await runOAuthFlow(forceNewLogin, signInMode);
|
|
3492
3807
|
if (tokenResult.type !== "success") {
|
|
3493
3808
|
if (isUserCancelledOAuth(tokenResult)) {
|
|
3494
3809
|
if (existingCount > 0) {
|
|
@@ -3506,6 +3821,9 @@ async function runAuthLogin() {
|
|
|
3506
3821
|
await syncSelectionToCodex(resolved);
|
|
3507
3822
|
const latestStorage = await loadAccounts();
|
|
3508
3823
|
const count = latestStorage?.accounts.length ?? 1;
|
|
3824
|
+
existingCount = count;
|
|
3825
|
+
namedBackups = [];
|
|
3826
|
+
onboardingBackupDiscoveryWarning = null;
|
|
3509
3827
|
console.log(`Added account. Total: ${count}`);
|
|
3510
3828
|
if (count >= ACCOUNT_LIMITS.MAX_ACCOUNTS) {
|
|
3511
3829
|
console.log(`Reached maximum account limit (${ACCOUNT_LIMITS.MAX_ACCOUNTS}).`);
|
|
@@ -3558,15 +3876,29 @@ async function runSwitch(args) {
|
|
|
3558
3876
|
console.log(`Switched to account ${parsed}: ${formatAccountLabel(account, targetIndex)}${wasDisabled ? " (re-enabled)" : ""}`);
|
|
3559
3877
|
return 0;
|
|
3560
3878
|
}
|
|
3561
|
-
async function persistAndSyncSelectedAccount({ storage, targetIndex, parsed, switchReason, initialSyncIdToken, }) {
|
|
3879
|
+
async function persistAndSyncSelectedAccount({ storage, targetIndex, parsed, switchReason, initialSyncIdToken, preserveActiveIndexByFamily = false, }) {
|
|
3562
3880
|
const account = storage.accounts[targetIndex];
|
|
3563
3881
|
if (!account) {
|
|
3564
3882
|
throw new Error(`Account ${parsed} not found.`);
|
|
3565
3883
|
}
|
|
3884
|
+
const shouldPreserveActiveIndexByFamily = preserveActiveIndexByFamily &&
|
|
3885
|
+
!!storage.activeIndexByFamily &&
|
|
3886
|
+
targetIndex === storage.activeIndex;
|
|
3566
3887
|
storage.activeIndex = targetIndex;
|
|
3567
3888
|
storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
|
|
3568
|
-
|
|
3569
|
-
storage.
|
|
3889
|
+
if (shouldPreserveActiveIndexByFamily) {
|
|
3890
|
+
const maxIndex = Math.max(0, storage.accounts.length - 1);
|
|
3891
|
+
for (const family of MODEL_FAMILIES) {
|
|
3892
|
+
const raw = storage.activeIndexByFamily[family];
|
|
3893
|
+
const candidate = typeof raw === "number" && Number.isFinite(raw) ? raw : targetIndex;
|
|
3894
|
+
storage.activeIndexByFamily[family] = Math.max(0, Math.min(candidate, maxIndex));
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3897
|
+
else {
|
|
3898
|
+
storage.activeIndexByFamily = storage.activeIndexByFamily ?? {};
|
|
3899
|
+
for (const family of MODEL_FAMILIES) {
|
|
3900
|
+
storage.activeIndexByFamily[family] = targetIndex;
|
|
3901
|
+
}
|
|
3570
3902
|
}
|
|
3571
3903
|
const wasDisabled = account.enabled === false;
|
|
3572
3904
|
if (wasDisabled) {
|
|
@@ -3920,7 +4252,7 @@ export async function runCodexMultiAuthCli(rawArgs) {
|
|
|
3920
4252
|
return 0;
|
|
3921
4253
|
}
|
|
3922
4254
|
if (command === "login") {
|
|
3923
|
-
return runAuthLogin();
|
|
4255
|
+
return runAuthLogin(rest);
|
|
3924
4256
|
}
|
|
3925
4257
|
if (command === "list" || command === "status") {
|
|
3926
4258
|
await showAccountStatus();
|