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
package/dist/lib/storage.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { existsSync, promises as fs } from "node:fs";
|
|
4
|
-
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative } from "node:path";
|
|
5
5
|
import { ACCOUNT_LIMITS } from "./constants.js";
|
|
6
6
|
import { createLogger } from "./logger.js";
|
|
7
|
-
import { exportNamedBackupFile, resolveNamedBackupPath, } from "./named-backup-export.js";
|
|
7
|
+
import { exportNamedBackupFile, getNamedBackupRoot, resolveNamedBackupPath, } from "./named-backup-export.js";
|
|
8
8
|
import { MODEL_FAMILIES } from "./prompts/codex.js";
|
|
9
9
|
import { AnyAccountStorageSchema, getValidationErrors } from "./schemas.js";
|
|
10
10
|
import { migrateV1ToV3, } from "./storage/migrations.js";
|
|
@@ -21,6 +21,71 @@ const BACKUP_COPY_BASE_DELAY_MS = 10;
|
|
|
21
21
|
const RESET_MARKER_SUFFIX = ".reset-intent";
|
|
22
22
|
let storageBackupEnabled = true;
|
|
23
23
|
let lastAccountsSaveTimestamp = 0;
|
|
24
|
+
async function collectNamedBackups(storagePath) {
|
|
25
|
+
const backupRoot = getNamedBackupRoot(storagePath);
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = await fs.readdir(backupRoot, {
|
|
29
|
+
withFileTypes: true,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const code = error.code;
|
|
35
|
+
if (code === "ENOENT")
|
|
36
|
+
return [];
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
const candidates = [];
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (!entry.isFile())
|
|
42
|
+
continue;
|
|
43
|
+
if (!entry.name.toLowerCase().endsWith(".json"))
|
|
44
|
+
continue;
|
|
45
|
+
const candidatePath = join(backupRoot, entry.name);
|
|
46
|
+
try {
|
|
47
|
+
const statsBefore = await fs.stat(candidatePath);
|
|
48
|
+
const { normalized } = await loadAccountsFromPath(candidatePath);
|
|
49
|
+
if (!normalized || normalized.accounts.length === 0)
|
|
50
|
+
continue;
|
|
51
|
+
const statsAfter = await fs.stat(candidatePath).catch(() => null);
|
|
52
|
+
if (statsAfter && statsAfter.mtimeMs !== statsBefore.mtimeMs) {
|
|
53
|
+
log.debug("backup file changed between stat and load, mtime may be stale", {
|
|
54
|
+
candidatePath,
|
|
55
|
+
fileName: entry.name,
|
|
56
|
+
beforeMtimeMs: statsBefore.mtimeMs,
|
|
57
|
+
afterMtimeMs: statsAfter.mtimeMs,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
candidates.push({
|
|
61
|
+
path: candidatePath,
|
|
62
|
+
fileName: entry.name,
|
|
63
|
+
accountCount: normalized.accounts.length,
|
|
64
|
+
mtimeMs: statsBefore.mtimeMs,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
log.debug("Skipping named backup candidate after loadAccountsFromPath/fs.stat failure", {
|
|
69
|
+
candidatePath,
|
|
70
|
+
fileName: entry.name,
|
|
71
|
+
error: error instanceof Error
|
|
72
|
+
? {
|
|
73
|
+
message: error.message,
|
|
74
|
+
stack: error.stack,
|
|
75
|
+
}
|
|
76
|
+
: String(error),
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
candidates.sort((left, right) => {
|
|
82
|
+
const mtimeDelta = right.mtimeMs - left.mtimeMs;
|
|
83
|
+
if (mtimeDelta !== 0)
|
|
84
|
+
return mtimeDelta;
|
|
85
|
+
return left.fileName.localeCompare(right.fileName);
|
|
86
|
+
});
|
|
87
|
+
return candidates;
|
|
88
|
+
}
|
|
24
89
|
/**
|
|
25
90
|
* Custom error class for storage operations with platform-aware hints.
|
|
26
91
|
*/
|
|
@@ -98,11 +163,12 @@ function looksLikeSyntheticFixtureStorage(storage) {
|
|
|
98
163
|
return storage.accounts.every((account) => looksLikeSyntheticFixtureAccount(account));
|
|
99
164
|
}
|
|
100
165
|
async function ensureGitignore(storagePath) {
|
|
101
|
-
|
|
166
|
+
const state = getStoragePathState();
|
|
167
|
+
if (!state.currentStoragePath)
|
|
102
168
|
return;
|
|
103
169
|
const configDir = dirname(storagePath);
|
|
104
170
|
const inferredProjectRoot = dirname(configDir);
|
|
105
|
-
const candidateRoots = [currentProjectRoot, inferredProjectRoot].filter((root) => typeof root === "string" && root.length > 0);
|
|
171
|
+
const candidateRoots = [state.currentProjectRoot, inferredProjectRoot].filter((root) => typeof root === "string" && root.length > 0);
|
|
106
172
|
const projectRoot = candidateRoots.find((root) => existsSync(join(root, ".git")));
|
|
107
173
|
if (!projectRoot)
|
|
108
174
|
return;
|
|
@@ -127,10 +193,20 @@ async function ensureGitignore(storagePath) {
|
|
|
127
193
|
log.warn("Failed to update .gitignore", { error: String(error) });
|
|
128
194
|
}
|
|
129
195
|
}
|
|
130
|
-
let
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
196
|
+
let currentStorageState = {
|
|
197
|
+
currentStoragePath: null,
|
|
198
|
+
currentLegacyProjectStoragePath: null,
|
|
199
|
+
currentLegacyWorktreeStoragePath: null,
|
|
200
|
+
currentProjectRoot: null,
|
|
201
|
+
};
|
|
202
|
+
const storagePathStateContext = new AsyncLocalStorage();
|
|
203
|
+
function getStoragePathState() {
|
|
204
|
+
return storagePathStateContext.getStore() ?? currentStorageState;
|
|
205
|
+
}
|
|
206
|
+
function setStoragePathState(state) {
|
|
207
|
+
currentStorageState = state;
|
|
208
|
+
storagePathStateContext.enterWith(state);
|
|
209
|
+
}
|
|
134
210
|
export function setStorageBackupEnabled(enabled) {
|
|
135
211
|
storageBackupEnabled = enabled;
|
|
136
212
|
}
|
|
@@ -536,50 +612,115 @@ export function getLastAccountsSaveTimestamp() {
|
|
|
536
612
|
}
|
|
537
613
|
export function setStoragePath(projectPath) {
|
|
538
614
|
if (!projectPath) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
615
|
+
setStoragePathState({
|
|
616
|
+
currentStoragePath: null,
|
|
617
|
+
currentLegacyProjectStoragePath: null,
|
|
618
|
+
currentLegacyWorktreeStoragePath: null,
|
|
619
|
+
currentProjectRoot: null,
|
|
620
|
+
});
|
|
543
621
|
return;
|
|
544
622
|
}
|
|
545
623
|
const projectRoot = findProjectRoot(projectPath);
|
|
546
624
|
if (projectRoot) {
|
|
547
|
-
currentProjectRoot = projectRoot;
|
|
548
625
|
const identityRoot = resolveProjectStorageIdentityRoot(projectRoot);
|
|
549
|
-
currentStoragePath = join(getProjectGlobalConfigDir(identityRoot), ACCOUNTS_FILE_NAME);
|
|
550
|
-
currentLegacyProjectStoragePath = join(getProjectConfigDir(projectRoot), ACCOUNTS_FILE_NAME);
|
|
626
|
+
const currentStoragePath = join(getProjectGlobalConfigDir(identityRoot), ACCOUNTS_FILE_NAME);
|
|
627
|
+
const currentLegacyProjectStoragePath = join(getProjectConfigDir(projectRoot), ACCOUNTS_FILE_NAME);
|
|
551
628
|
const previousWorktreeScopedPath = join(getProjectGlobalConfigDir(projectRoot), ACCOUNTS_FILE_NAME);
|
|
552
|
-
currentLegacyWorktreeStoragePath =
|
|
553
|
-
previousWorktreeScopedPath
|
|
554
|
-
|
|
555
|
-
|
|
629
|
+
const currentLegacyWorktreeStoragePath = previousWorktreeScopedPath !== currentStoragePath
|
|
630
|
+
? previousWorktreeScopedPath
|
|
631
|
+
: null;
|
|
632
|
+
setStoragePathState({
|
|
633
|
+
currentStoragePath,
|
|
634
|
+
currentLegacyProjectStoragePath,
|
|
635
|
+
currentLegacyWorktreeStoragePath,
|
|
636
|
+
currentProjectRoot: projectRoot,
|
|
637
|
+
});
|
|
556
638
|
}
|
|
557
639
|
else {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
640
|
+
setStoragePathState({
|
|
641
|
+
currentStoragePath: null,
|
|
642
|
+
currentLegacyProjectStoragePath: null,
|
|
643
|
+
currentLegacyWorktreeStoragePath: null,
|
|
644
|
+
currentProjectRoot: null,
|
|
645
|
+
});
|
|
562
646
|
}
|
|
563
647
|
}
|
|
564
648
|
export function setStoragePathDirect(path) {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
649
|
+
setStoragePathState({
|
|
650
|
+
currentStoragePath: path,
|
|
651
|
+
currentLegacyProjectStoragePath: null,
|
|
652
|
+
currentLegacyWorktreeStoragePath: null,
|
|
653
|
+
currentProjectRoot: null,
|
|
654
|
+
});
|
|
569
655
|
}
|
|
570
656
|
/**
|
|
571
657
|
* Returns the file path for the account storage JSON file.
|
|
572
658
|
* @returns Absolute path to the accounts.json file
|
|
573
659
|
*/
|
|
574
660
|
export function getStoragePath() {
|
|
575
|
-
|
|
576
|
-
|
|
661
|
+
const state = getStoragePathState();
|
|
662
|
+
if (state.currentStoragePath) {
|
|
663
|
+
return state.currentStoragePath;
|
|
577
664
|
}
|
|
578
665
|
return join(getConfigDir(), ACCOUNTS_FILE_NAME);
|
|
579
666
|
}
|
|
580
667
|
export function buildNamedBackupPath(name) {
|
|
581
668
|
return resolveNamedBackupPath(name, getStoragePath());
|
|
582
669
|
}
|
|
670
|
+
export async function getNamedBackups() {
|
|
671
|
+
return collectNamedBackups(getStoragePath());
|
|
672
|
+
}
|
|
673
|
+
export async function restoreAccountsFromBackup(path, options) {
|
|
674
|
+
const backupRoot = getNamedBackupRoot(getStoragePath());
|
|
675
|
+
let resolvedBackupRoot;
|
|
676
|
+
try {
|
|
677
|
+
resolvedBackupRoot = await fs.realpath(backupRoot);
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
const code = error.code;
|
|
681
|
+
if (code === "ENOENT") {
|
|
682
|
+
throw new Error(`Backup root does not exist: ${backupRoot}`);
|
|
683
|
+
}
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
let resolvedBackupPath;
|
|
687
|
+
try {
|
|
688
|
+
resolvedBackupPath = await fs.realpath(path);
|
|
689
|
+
}
|
|
690
|
+
catch (error) {
|
|
691
|
+
const code = error.code;
|
|
692
|
+
if (code === "ENOENT") {
|
|
693
|
+
throw new Error(`Backup file no longer exists: ${path}`);
|
|
694
|
+
}
|
|
695
|
+
throw error;
|
|
696
|
+
}
|
|
697
|
+
const relativePath = relative(resolvedBackupRoot, resolvedBackupPath);
|
|
698
|
+
const isInsideBackupRoot = relativePath.length > 0 &&
|
|
699
|
+
!relativePath.startsWith("..") &&
|
|
700
|
+
!isAbsolute(relativePath);
|
|
701
|
+
if (!isInsideBackupRoot) {
|
|
702
|
+
throw new Error(`Backup path must stay inside ${resolvedBackupRoot}: ${path}`);
|
|
703
|
+
}
|
|
704
|
+
const { normalized } = await (async () => {
|
|
705
|
+
try {
|
|
706
|
+
return await loadAccountsFromPath(resolvedBackupPath);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
const code = error.code;
|
|
710
|
+
if (code === "ENOENT") {
|
|
711
|
+
throw new Error(`Backup file no longer exists: ${path}`);
|
|
712
|
+
}
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
})();
|
|
716
|
+
if (!normalized || normalized.accounts.length === 0) {
|
|
717
|
+
throw new Error(`Backup does not contain any accounts: ${resolvedBackupPath}`);
|
|
718
|
+
}
|
|
719
|
+
if (options?.persist !== false) {
|
|
720
|
+
await saveAccounts(normalized);
|
|
721
|
+
}
|
|
722
|
+
return normalized;
|
|
723
|
+
}
|
|
583
724
|
export async function exportNamedBackup(name, options) {
|
|
584
725
|
return exportNamedBackupFile(name, {
|
|
585
726
|
getStoragePath,
|
|
@@ -593,16 +734,17 @@ function getLegacyFlaggedAccountsPath() {
|
|
|
593
734
|
return join(dirname(getStoragePath()), LEGACY_FLAGGED_ACCOUNTS_FILE_NAME);
|
|
594
735
|
}
|
|
595
736
|
async function migrateLegacyProjectStorageIfNeeded(persist = saveAccounts) {
|
|
596
|
-
|
|
737
|
+
const state = getStoragePathState();
|
|
738
|
+
if (!state.currentStoragePath) {
|
|
597
739
|
return null;
|
|
598
740
|
}
|
|
599
741
|
const candidatePaths = [
|
|
600
|
-
currentLegacyWorktreeStoragePath,
|
|
601
|
-
currentLegacyProjectStoragePath,
|
|
742
|
+
state.currentLegacyWorktreeStoragePath,
|
|
743
|
+
state.currentLegacyProjectStoragePath,
|
|
602
744
|
]
|
|
603
745
|
.filter((path) => typeof path === "string" &&
|
|
604
746
|
path.length > 0 &&
|
|
605
|
-
path !== currentStoragePath)
|
|
747
|
+
path !== state.currentStoragePath)
|
|
606
748
|
.filter((path, index, all) => all.indexOf(path) === index);
|
|
607
749
|
if (candidatePaths.length === 0) {
|
|
608
750
|
return null;
|
|
@@ -611,7 +753,7 @@ async function migrateLegacyProjectStorageIfNeeded(persist = saveAccounts) {
|
|
|
611
753
|
if (existingCandidatePaths.length === 0) {
|
|
612
754
|
return null;
|
|
613
755
|
}
|
|
614
|
-
let targetStorage = await loadNormalizedStorageFromPath(currentStoragePath, "current account storage");
|
|
756
|
+
let targetStorage = await loadNormalizedStorageFromPath(state.currentStoragePath, "current account storage");
|
|
615
757
|
let migrated = false;
|
|
616
758
|
for (const legacyPath of existingCandidatePaths) {
|
|
617
759
|
const legacyStorage = await loadNormalizedStorageFromPath(legacyPath, "legacy account storage");
|
|
@@ -629,7 +771,7 @@ async function migrateLegacyProjectStorageIfNeeded(persist = saveAccounts) {
|
|
|
629
771
|
targetStorage = fallbackStorage;
|
|
630
772
|
log.warn("Failed to persist migrated account storage", {
|
|
631
773
|
from: legacyPath,
|
|
632
|
-
to: currentStoragePath,
|
|
774
|
+
to: state.currentStoragePath,
|
|
633
775
|
error: String(error),
|
|
634
776
|
});
|
|
635
777
|
continue;
|
|
@@ -651,14 +793,14 @@ async function migrateLegacyProjectStorageIfNeeded(persist = saveAccounts) {
|
|
|
651
793
|
}
|
|
652
794
|
log.info("Migrated legacy project account storage", {
|
|
653
795
|
from: legacyPath,
|
|
654
|
-
to: currentStoragePath,
|
|
796
|
+
to: state.currentStoragePath,
|
|
655
797
|
accounts: mergedStorage.accounts.length,
|
|
656
798
|
});
|
|
657
799
|
}
|
|
658
800
|
if (migrated) {
|
|
659
801
|
return targetStorage;
|
|
660
802
|
}
|
|
661
|
-
if (targetStorage && !existsSync(currentStoragePath)) {
|
|
803
|
+
if (targetStorage && !existsSync(state.currentStoragePath)) {
|
|
662
804
|
return targetStorage;
|
|
663
805
|
}
|
|
664
806
|
return null;
|
|
@@ -1437,8 +1579,10 @@ function cloneAccountStorageForPersistence(storage) {
|
|
|
1437
1579
|
}
|
|
1438
1580
|
export async function withAccountStorageTransaction(handler) {
|
|
1439
1581
|
return withStorageLock(async () => {
|
|
1582
|
+
const storagePath = getStoragePath();
|
|
1440
1583
|
const state = {
|
|
1441
1584
|
snapshot: await loadAccountsInternal(saveAccountsUnlocked),
|
|
1585
|
+
storagePath,
|
|
1442
1586
|
active: true,
|
|
1443
1587
|
};
|
|
1444
1588
|
const current = state.snapshot;
|
|
@@ -1451,8 +1595,10 @@ export async function withAccountStorageTransaction(handler) {
|
|
|
1451
1595
|
}
|
|
1452
1596
|
export async function withAccountAndFlaggedStorageTransaction(handler) {
|
|
1453
1597
|
return withStorageLock(async () => {
|
|
1598
|
+
const storagePath = getStoragePath();
|
|
1454
1599
|
const state = {
|
|
1455
1600
|
snapshot: await loadAccountsInternal(saveAccountsUnlocked),
|
|
1601
|
+
storagePath,
|
|
1456
1602
|
active: true,
|
|
1457
1603
|
};
|
|
1458
1604
|
const current = state.snapshot;
|
|
@@ -1555,7 +1701,8 @@ function normalizeFlaggedStorage(data) {
|
|
|
1555
1701
|
const isSwitchReason = (value) => value === "rate-limit" ||
|
|
1556
1702
|
value === "initial" ||
|
|
1557
1703
|
value === "rotation" ||
|
|
1558
|
-
value === "best"
|
|
1704
|
+
value === "best" ||
|
|
1705
|
+
value === "restore";
|
|
1559
1706
|
const isCooldownReason = (value) => value === "auth-failure" ||
|
|
1560
1707
|
value === "network-error" ||
|
|
1561
1708
|
value === "rate-limit";
|
|
@@ -1769,13 +1916,17 @@ export async function clearFlaggedAccounts() {
|
|
|
1769
1916
|
*/
|
|
1770
1917
|
export async function exportAccounts(filePath, force = true, beforeCommit) {
|
|
1771
1918
|
const resolvedPath = resolvePath(filePath);
|
|
1919
|
+
const currentStoragePath = getStoragePath();
|
|
1772
1920
|
if (!force && existsSync(resolvedPath)) {
|
|
1773
1921
|
throw new Error(`File already exists: ${resolvedPath}`);
|
|
1774
1922
|
}
|
|
1775
1923
|
const transactionState = transactionSnapshotContext.getStore();
|
|
1776
|
-
const storage = transactionState?.active
|
|
1924
|
+
const storage = transactionState?.active &&
|
|
1925
|
+
transactionState.storagePath === currentStoragePath
|
|
1777
1926
|
? transactionState.snapshot
|
|
1778
|
-
:
|
|
1927
|
+
: transactionState?.active
|
|
1928
|
+
? await loadAccountsInternal(saveAccountsUnlocked)
|
|
1929
|
+
: await withAccountStorageTransaction((current) => Promise.resolve(current));
|
|
1779
1930
|
if (!storage || storage.accounts.length === 0) {
|
|
1780
1931
|
throw new Error("No accounts to export");
|
|
1781
1932
|
}
|