oc-codex-multi-auth 6.0.0 → 6.1.1
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/LICENSE +1 -1
- package/README.md +56 -18
- package/assets/readme-hero.svg +10 -10
- package/config/README.md +1 -1
- package/config/minimal-opencode.json +1 -1
- package/config/opencode-legacy.json +1 -1
- package/config/opencode-modern.json +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -2525
- package/dist/index.js.map +1 -1
- package/dist/lib/accounts/persistence.d.ts +34 -0
- package/dist/lib/accounts/persistence.d.ts.map +1 -0
- package/dist/lib/accounts/persistence.js +153 -0
- package/dist/lib/accounts/persistence.js.map +1 -0
- package/dist/lib/accounts/recovery.d.ts +59 -0
- package/dist/lib/accounts/recovery.d.ts.map +1 -0
- package/dist/lib/accounts/recovery.js +229 -0
- package/dist/lib/accounts/recovery.js.map +1 -0
- package/dist/lib/accounts/rotation.d.ts +40 -0
- package/dist/lib/accounts/rotation.d.ts.map +1 -0
- package/dist/lib/accounts/rotation.js +204 -0
- package/dist/lib/accounts/rotation.js.map +1 -0
- package/dist/lib/accounts/state.d.ts +92 -0
- package/dist/lib/accounts/state.d.ts.map +1 -0
- package/dist/lib/accounts/state.js +366 -0
- package/dist/lib/accounts/state.js.map +1 -0
- package/dist/lib/accounts.d.ts +48 -91
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/lib/accounts.js +121 -699
- package/dist/lib/accounts.js.map +1 -1
- package/dist/lib/auth/auth.d.ts +1 -1
- package/dist/lib/auth/auth.d.ts.map +1 -1
- package/dist/lib/auth/auth.js +5 -2
- package/dist/lib/auth/auth.js.map +1 -1
- package/dist/lib/auth/login-runner.d.ts +32 -0
- package/dist/lib/auth/login-runner.d.ts.map +1 -1
- package/dist/lib/auth/login-runner.js +79 -64
- package/dist/lib/auth/login-runner.js.map +1 -1
- package/dist/lib/auth/server.d.ts.map +1 -1
- package/dist/lib/auth/server.js +3 -8
- package/dist/lib/auth/server.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +53 -2
- package/dist/lib/circuit-breaker.d.ts.map +1 -1
- package/dist/lib/circuit-breaker.js +65 -6
- package/dist/lib/circuit-breaker.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +77 -32
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/error-sentinels.d.ts +20 -0
- package/dist/lib/error-sentinels.d.ts.map +1 -0
- package/dist/lib/error-sentinels.js +28 -0
- package/dist/lib/error-sentinels.js.map +1 -0
- package/dist/lib/errors.d.ts +100 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +116 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/logger.d.ts +14 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +65 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/oauth-constants.d.ts +18 -0
- package/dist/lib/oauth-constants.d.ts.map +1 -0
- package/dist/lib/oauth-constants.js +18 -0
- package/dist/lib/oauth-constants.js.map +1 -0
- package/dist/lib/oauth-success.d.ts +2 -0
- package/dist/lib/oauth-success.d.ts.map +1 -0
- package/dist/lib/oauth-success.html +711 -711
- package/dist/lib/oauth-success.js +714 -0
- package/dist/lib/oauth-success.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -1
- package/dist/lib/prompts/codex.js +12 -3
- package/dist/lib/prompts/codex.js.map +1 -1
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -1
- package/dist/lib/prompts/opencode-codex.js +3 -2
- package/dist/lib/prompts/opencode-codex.js.map +1 -1
- package/dist/lib/recovery/hook.d.ts +43 -0
- package/dist/lib/recovery/hook.d.ts.map +1 -0
- package/dist/lib/recovery/hook.js +325 -0
- package/dist/lib/recovery/hook.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +1 -0
- package/dist/lib/recovery/index.d.ts.map +1 -1
- package/dist/lib/recovery/index.js +1 -0
- package/dist/lib/recovery/index.js.map +1 -1
- package/dist/lib/recovery/storage.d.ts.map +1 -1
- package/dist/lib/recovery/storage.js +5 -1
- package/dist/lib/recovery/storage.js.map +1 -1
- package/dist/lib/recovery.d.ts +15 -30
- package/dist/lib/recovery.d.ts.map +1 -1
- package/dist/lib/recovery.js +14 -312
- package/dist/lib/recovery.js.map +1 -1
- package/dist/lib/request/fetch-helpers.d.ts +1 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
- package/dist/lib/request/fetch-helpers.js +58 -10
- package/dist/lib/request/fetch-helpers.js.map +1 -1
- package/dist/lib/request/response-handler.d.ts.map +1 -1
- package/dist/lib/request/response-handler.js +8 -2
- package/dist/lib/request/response-handler.js.map +1 -1
- package/dist/lib/runtime.d.ts +101 -0
- package/dist/lib/runtime.d.ts.map +1 -0
- package/dist/lib/runtime.js +77 -0
- package/dist/lib/runtime.js.map +1 -0
- package/dist/lib/schemas.d.ts +164 -1
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +251 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/storage/atomic-write.d.ts +31 -0
- package/dist/lib/storage/atomic-write.d.ts.map +1 -0
- package/dist/lib/storage/atomic-write.js +75 -0
- package/dist/lib/storage/atomic-write.js.map +1 -0
- package/dist/lib/storage/backup.d.ts +13 -0
- package/dist/lib/storage/backup.d.ts.map +1 -0
- package/dist/lib/storage/backup.js +59 -0
- package/dist/lib/storage/backup.js.map +1 -0
- package/dist/lib/storage/errors.d.ts +17 -0
- package/dist/lib/storage/errors.d.ts.map +1 -0
- package/dist/lib/storage/errors.js +38 -0
- package/dist/lib/storage/errors.js.map +1 -0
- package/dist/lib/storage/export-import.d.ts +84 -0
- package/dist/lib/storage/export-import.d.ts.map +1 -0
- package/dist/lib/storage/export-import.js +222 -0
- package/dist/lib/storage/export-import.js.map +1 -0
- package/dist/lib/storage/flagged.d.ts +29 -0
- package/dist/lib/storage/flagged.d.ts.map +1 -0
- package/dist/lib/storage/flagged.js +207 -0
- package/dist/lib/storage/flagged.js.map +1 -0
- package/dist/lib/storage/identity.d.ts +72 -0
- package/dist/lib/storage/identity.d.ts.map +1 -0
- package/dist/lib/storage/identity.js +230 -0
- package/dist/lib/storage/identity.js.map +1 -0
- package/dist/lib/storage/keychain.d.ts +146 -0
- package/dist/lib/storage/keychain.d.ts.map +1 -0
- package/dist/lib/storage/keychain.js +278 -0
- package/dist/lib/storage/keychain.js.map +1 -0
- package/dist/lib/storage/load-save.d.ts +61 -0
- package/dist/lib/storage/load-save.d.ts.map +1 -0
- package/dist/lib/storage/load-save.js +606 -0
- package/dist/lib/storage/load-save.js.map +1 -0
- package/dist/lib/storage/migrations.d.ts +35 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -1
- package/dist/lib/storage/migrations.js +37 -0
- package/dist/lib/storage/migrations.js.map +1 -1
- package/dist/lib/storage/normalize.d.ts +22 -0
- package/dist/lib/storage/normalize.d.ts.map +1 -0
- package/dist/lib/storage/normalize.js +114 -0
- package/dist/lib/storage/normalize.js.map +1 -0
- package/dist/lib/storage/paths.d.ts.map +1 -1
- package/dist/lib/storage/paths.js +2 -1
- package/dist/lib/storage/paths.js.map +1 -1
- package/dist/lib/storage/state.d.ts +46 -0
- package/dist/lib/storage/state.d.ts.map +1 -0
- package/dist/lib/storage/state.js +100 -0
- package/dist/lib/storage/state.js.map +1 -0
- package/dist/lib/storage/worktree-lock.d.ts +79 -0
- package/dist/lib/storage/worktree-lock.d.ts.map +1 -0
- package/dist/lib/storage/worktree-lock.js +265 -0
- package/dist/lib/storage/worktree-lock.js.map +1 -0
- package/dist/lib/storage.d.ts +21 -155
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.js +24 -1167
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/tools/_shared.d.ts +16 -0
- package/dist/lib/tools/_shared.d.ts.map +1 -0
- package/dist/lib/tools/_shared.js +21 -0
- package/dist/lib/tools/_shared.js.map +1 -0
- package/dist/lib/tools/codex-dashboard.d.ts +8 -0
- package/dist/lib/tools/codex-dashboard.d.ts.map +1 -0
- package/dist/lib/tools/codex-dashboard.js +176 -0
- package/dist/lib/tools/codex-dashboard.js.map +1 -0
- package/dist/lib/tools/codex-diag.d.ts +19 -0
- package/dist/lib/tools/codex-diag.d.ts.map +1 -0
- package/dist/lib/tools/codex-diag.js +139 -0
- package/dist/lib/tools/codex-diag.js.map +1 -0
- package/dist/lib/tools/codex-diff.d.ts +44 -0
- package/dist/lib/tools/codex-diff.d.ts.map +1 -0
- package/dist/lib/tools/codex-diff.js +284 -0
- package/dist/lib/tools/codex-diff.js.map +1 -0
- package/dist/lib/tools/codex-doctor.d.ts +8 -0
- package/dist/lib/tools/codex-doctor.d.ts.map +1 -0
- package/dist/lib/tools/codex-doctor.js +284 -0
- package/dist/lib/tools/codex-doctor.js.map +1 -0
- package/dist/lib/tools/codex-export.d.ts +8 -0
- package/dist/lib/tools/codex-export.d.ts.map +1 -0
- package/dist/lib/tools/codex-export.js +63 -0
- package/dist/lib/tools/codex-export.js.map +1 -0
- package/dist/lib/tools/codex-health.d.ts +8 -0
- package/dist/lib/tools/codex-health.d.ts.map +1 -0
- package/dist/lib/tools/codex-health.js +124 -0
- package/dist/lib/tools/codex-health.js.map +1 -0
- package/dist/lib/tools/codex-help.d.ts +8 -0
- package/dist/lib/tools/codex-help.d.ts.map +1 -0
- package/dist/lib/tools/codex-help.js +127 -0
- package/dist/lib/tools/codex-help.js.map +1 -0
- package/dist/lib/tools/codex-import.d.ts +8 -0
- package/dist/lib/tools/codex-import.d.ts.map +1 -0
- package/dist/lib/tools/codex-import.js +97 -0
- package/dist/lib/tools/codex-import.js.map +1 -0
- package/dist/lib/tools/codex-keychain.d.ts +36 -0
- package/dist/lib/tools/codex-keychain.d.ts.map +1 -0
- package/dist/lib/tools/codex-keychain.js +289 -0
- package/dist/lib/tools/codex-keychain.js.map +1 -0
- package/dist/lib/tools/codex-label.d.ts +8 -0
- package/dist/lib/tools/codex-label.d.ts.map +1 -0
- package/dist/lib/tools/codex-label.js +143 -0
- package/dist/lib/tools/codex-label.js.map +1 -0
- package/dist/lib/tools/codex-limits.d.ts +8 -0
- package/dist/lib/tools/codex-limits.d.ts.map +1 -0
- package/dist/lib/tools/codex-limits.js +497 -0
- package/dist/lib/tools/codex-limits.js.map +1 -0
- package/dist/lib/tools/codex-list.d.ts +8 -0
- package/dist/lib/tools/codex-list.d.ts.map +1 -0
- package/dist/lib/tools/codex-list.js +260 -0
- package/dist/lib/tools/codex-list.js.map +1 -0
- package/dist/lib/tools/codex-metrics.d.ts +8 -0
- package/dist/lib/tools/codex-metrics.d.ts.map +1 -0
- package/dist/lib/tools/codex-metrics.js +180 -0
- package/dist/lib/tools/codex-metrics.js.map +1 -0
- package/dist/lib/tools/codex-next.d.ts +8 -0
- package/dist/lib/tools/codex-next.d.ts.map +1 -0
- package/dist/lib/tools/codex-next.js +54 -0
- package/dist/lib/tools/codex-next.js.map +1 -0
- package/dist/lib/tools/codex-note.d.ts +8 -0
- package/dist/lib/tools/codex-note.d.ts.map +1 -0
- package/dist/lib/tools/codex-note.js +79 -0
- package/dist/lib/tools/codex-note.js.map +1 -0
- package/dist/lib/tools/codex-refresh.d.ts +8 -0
- package/dist/lib/tools/codex-refresh.d.ts.map +1 -0
- package/dist/lib/tools/codex-refresh.js +78 -0
- package/dist/lib/tools/codex-refresh.js.map +1 -0
- package/dist/lib/tools/codex-remove.d.ts +8 -0
- package/dist/lib/tools/codex-remove.d.ts.map +1 -0
- package/dist/lib/tools/codex-remove.js +184 -0
- package/dist/lib/tools/codex-remove.js.map +1 -0
- package/dist/lib/tools/codex-setup.d.ts +8 -0
- package/dist/lib/tools/codex-setup.d.ts.map +1 -0
- package/dist/lib/tools/codex-setup.js +26 -0
- package/dist/lib/tools/codex-setup.js.map +1 -0
- package/dist/lib/tools/codex-status.d.ts +8 -0
- package/dist/lib/tools/codex-status.d.ts.map +1 -0
- package/dist/lib/tools/codex-status.js +263 -0
- package/dist/lib/tools/codex-status.js.map +1 -0
- package/dist/lib/tools/codex-switch.d.ts +8 -0
- package/dist/lib/tools/codex-switch.d.ts.map +1 -0
- package/dist/lib/tools/codex-switch.js +122 -0
- package/dist/lib/tools/codex-switch.js.map +1 -0
- package/dist/lib/tools/codex-tag.d.ts +8 -0
- package/dist/lib/tools/codex-tag.d.ts.map +1 -0
- package/dist/lib/tools/codex-tag.js +101 -0
- package/dist/lib/tools/codex-tag.js.map +1 -0
- package/dist/lib/tools/index.d.ts +143 -0
- package/dist/lib/tools/index.d.ts.map +1 -0
- package/dist/lib/tools/index.js +67 -0
- package/dist/lib/tools/index.js.map +1 -0
- package/dist/lib/ui/format.d.ts.map +1 -1
- package/dist/lib/ui/format.js +5 -0
- package/dist/lib/ui/format.js.map +1 -1
- package/dist/lib/ui/runtime.d.ts +10 -0
- package/dist/lib/ui/runtime.d.ts.map +1 -1
- package/dist/lib/ui/runtime.js +16 -11
- package/dist/lib/ui/runtime.js.map +1 -1
- package/dist/lib/ui/select.d.ts.map +1 -1
- package/dist/lib/ui/select.js +12 -3
- package/dist/lib/ui/select.js.map +1 -1
- package/dist/lib/ui/theme.d.ts +19 -0
- package/dist/lib/ui/theme.d.ts.map +1 -1
- package/dist/lib/ui/theme.js +27 -0
- package/dist/lib/ui/theme.js.map +1 -1
- package/package.json +24 -16
- package/scripts/copy-oauth-success.js +16 -6
- package/scripts/install-oc-codex-multi-auth-core.js +462 -381
- package/scripts/install-oc-codex-multi-auth.js +15 -15
- package/scripts/test-all-models.sh +1 -1
- package/dist/lib/audit.d.ts +0 -45
- package/dist/lib/audit.d.ts.map +0 -1
- package/dist/lib/audit.js +0 -154
- package/dist/lib/audit.js.map +0 -1
- package/dist/lib/auth-rate-limit.d.ts +0 -20
- package/dist/lib/auth-rate-limit.d.ts.map +0 -1
- package/dist/lib/auth-rate-limit.js +0 -91
- package/dist/lib/auth-rate-limit.js.map +0 -1
- package/dist/lib/runtime-contracts.d.ts +0 -16
- package/dist/lib/runtime-contracts.d.ts.map +0 -1
- package/dist/lib/runtime-contracts.js +0 -24
- package/dist/lib/runtime-contracts.js.map +0 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account export / import / preview pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. All three public functions here are
|
|
5
|
+
* thin wrappers around `withAccountStorageTransaction` so they inherit the
|
|
6
|
+
* same mutex + atomic-write guarantees as ordinary saves. The pre-import
|
|
7
|
+
* backup path is delegated to `./backup.ts`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Backup policy for `importAccounts`.
|
|
11
|
+
*
|
|
12
|
+
* - `none`: do not create a pre-import backup (destructive; opt-in only).
|
|
13
|
+
* - `timestamped`: create a timestamped pre-import backup, continue on failure.
|
|
14
|
+
* This is the default — see audit finding `docs/audits/04-high-priority.md`.
|
|
15
|
+
* - `best-effort`: legacy alias for `timestamped`, retained for callers that
|
|
16
|
+
* were written against the prior enum.
|
|
17
|
+
* - `required`: backup must succeed or the import aborts.
|
|
18
|
+
*/
|
|
19
|
+
export type ImportBackupMode = "none" | "timestamped" | "best-effort" | "required";
|
|
20
|
+
export interface ImportAccountsOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Optional prefix used for pre-import backup file names.
|
|
23
|
+
* Only applied when backupMode is not "none".
|
|
24
|
+
*/
|
|
25
|
+
preImportBackupPrefix?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Backup policy before import apply. Defaults to `"timestamped"` so that the
|
|
28
|
+
* prior on-disk state is preserved unless the caller explicitly opts out via
|
|
29
|
+
* `{ backupMode: "none" }`.
|
|
30
|
+
*/
|
|
31
|
+
backupMode?: ImportBackupMode;
|
|
32
|
+
}
|
|
33
|
+
export type ImportBackupStatus = "created" | "skipped" | "failed";
|
|
34
|
+
export interface ImportAccountsResult {
|
|
35
|
+
imported: number;
|
|
36
|
+
total: number;
|
|
37
|
+
skipped: number;
|
|
38
|
+
backupStatus: ImportBackupStatus;
|
|
39
|
+
backupPath?: string;
|
|
40
|
+
backupError?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ImportPreviewResult {
|
|
43
|
+
imported: number;
|
|
44
|
+
total: number;
|
|
45
|
+
skipped: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Import preview/apply analysis is pure in-memory work: it does not touch disk
|
|
49
|
+
* and it does not log token or workspace values. The surrounding
|
|
50
|
+
* `withAccountStorageTransaction` caller keeps Windows lock-retry and
|
|
51
|
+
* serialized read-modify-write behavior; see `test/storage.test.ts` for the
|
|
52
|
+
* overlapping transaction regression and pre-import backup lock coverage.
|
|
53
|
+
*/
|
|
54
|
+
export declare function previewImportAccounts(filePath: string): Promise<ImportPreviewResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Exports current accounts to a JSON file for backup/migration.
|
|
57
|
+
*
|
|
58
|
+
* Safety default (audit `docs/audits/04-high-priority.md`):
|
|
59
|
+
* `force` defaults to `false` so an existing export file is never silently
|
|
60
|
+
* overwritten. Callers that need the prior destructive behaviour must opt in
|
|
61
|
+
* via `exportAccounts(path, true)`.
|
|
62
|
+
*
|
|
63
|
+
* @param filePath - Destination file path.
|
|
64
|
+
* @param force - If true, overwrite any existing file at `filePath`. Defaults to
|
|
65
|
+
* `false`; when false and the file exists, a `StorageError` is thrown.
|
|
66
|
+
* @throws StorageError if the file already exists and `force` is `false`.
|
|
67
|
+
* @throws Error if there are no accounts to export.
|
|
68
|
+
*/
|
|
69
|
+
export declare function exportAccounts(filePath: string, force?: boolean): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Imports accounts from a JSON file, merging with existing accounts.
|
|
72
|
+
* Deduplicates by identity key first (organizationId -> accountId -> refreshToken),
|
|
73
|
+
* then applies legacy email dedupe only to entries without organizationId/accountId.
|
|
74
|
+
*
|
|
75
|
+
* Safety default (audit `docs/audits/04-high-priority.md`):
|
|
76
|
+
* `options.backupMode` defaults to `"timestamped"` so a pre-import snapshot of
|
|
77
|
+
* the existing accounts file is always written before apply. Callers that need
|
|
78
|
+
* the prior destructive default must pass `{ backupMode: "none" }` explicitly.
|
|
79
|
+
*
|
|
80
|
+
* @param filePath - Source file path
|
|
81
|
+
* @throws Error if file is invalid or would exceed MAX_ACCOUNTS
|
|
82
|
+
*/
|
|
83
|
+
export declare function importAccounts(filePath: string, options?: ImportAccountsOptions): Promise<ImportAccountsResult>;
|
|
84
|
+
//# sourceMappingURL=export-import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-import.d.ts","sourceRoot":"","sources":["../../../lib/storage/export-import.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsBH;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,aAAa,GAAG,aAAa,GAAG,UAAU,CAAC;AAEnF,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAoDD;;;;;;GAMG;AAEH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,CAAC,CAY9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBnF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,oBAAoB,CAAC,CA2H/B"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account export / import / preview pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. All three public functions here are
|
|
5
|
+
* thin wrappers around `withAccountStorageTransaction` so they inherit the
|
|
6
|
+
* same mutex + atomic-write guarantees as ordinary saves. The pre-import
|
|
7
|
+
* backup path is delegated to `./backup.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
10
|
+
import { basename, dirname } from "node:path";
|
|
11
|
+
import { ACCOUNT_LIMITS } from "../constants.js";
|
|
12
|
+
import { createLogger } from "../logger.js";
|
|
13
|
+
import { MODEL_FAMILIES } from "../prompts/codex.js";
|
|
14
|
+
import { createTimestampedBackupPath, writePreImportBackupFile } from "./backup.js";
|
|
15
|
+
import { StorageError } from "./errors.js";
|
|
16
|
+
import { clampIndex, deduplicateAccountsForStorage, extractActiveKeys, findAccountIndexByIdentityKeys, } from "./identity.js";
|
|
17
|
+
import { withAccountStorageTransaction } from "./load-save.js";
|
|
18
|
+
import { normalizeAccountStorage } from "./normalize.js";
|
|
19
|
+
import { resolvePath } from "./paths.js";
|
|
20
|
+
const log = createLogger("storage");
|
|
21
|
+
async function readAndNormalizeImportFile(filePath) {
|
|
22
|
+
const resolvedPath = resolvePath(filePath);
|
|
23
|
+
if (!existsSync(resolvedPath)) {
|
|
24
|
+
throw new Error(`Import file not found: ${resolvedPath}`);
|
|
25
|
+
}
|
|
26
|
+
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
27
|
+
let imported;
|
|
28
|
+
try {
|
|
29
|
+
imported = JSON.parse(content);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error(`Invalid JSON in import file: ${resolvedPath}`);
|
|
33
|
+
}
|
|
34
|
+
// Pass the resolved path so an UNSUPPORTED_SCHEMA_VERSION error includes
|
|
35
|
+
// the actual import file in its diagnostics.
|
|
36
|
+
const normalized = normalizeAccountStorage(imported, resolvedPath);
|
|
37
|
+
if (!normalized) {
|
|
38
|
+
throw new Error("Invalid account storage format");
|
|
39
|
+
}
|
|
40
|
+
return { resolvedPath, normalized };
|
|
41
|
+
}
|
|
42
|
+
function analyzeImportedAccounts(existingAccounts, importedAccounts) {
|
|
43
|
+
const merged = [...existingAccounts, ...importedAccounts];
|
|
44
|
+
const accounts = deduplicateAccountsForStorage(merged);
|
|
45
|
+
if (accounts.length > ACCOUNT_LIMITS.MAX_ACCOUNTS) {
|
|
46
|
+
throw new Error(`Import would exceed maximum of ${ACCOUNT_LIMITS.MAX_ACCOUNTS} accounts (would have ${accounts.length})`);
|
|
47
|
+
}
|
|
48
|
+
const imported = Math.max(0, accounts.length - existingAccounts.length);
|
|
49
|
+
const skipped = Math.max(0, importedAccounts.length - imported);
|
|
50
|
+
return {
|
|
51
|
+
accounts,
|
|
52
|
+
imported,
|
|
53
|
+
total: accounts.length,
|
|
54
|
+
skipped,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Import preview/apply analysis is pure in-memory work: it does not touch disk
|
|
59
|
+
* and it does not log token or workspace values. The surrounding
|
|
60
|
+
* `withAccountStorageTransaction` caller keeps Windows lock-retry and
|
|
61
|
+
* serialized read-modify-write behavior; see `test/storage.test.ts` for the
|
|
62
|
+
* overlapping transaction regression and pre-import backup lock coverage.
|
|
63
|
+
*/
|
|
64
|
+
export async function previewImportAccounts(filePath) {
|
|
65
|
+
const { normalized } = await readAndNormalizeImportFile(filePath);
|
|
66
|
+
return withAccountStorageTransaction((existing) => {
|
|
67
|
+
const existingAccounts = existing?.accounts ?? [];
|
|
68
|
+
const analysis = analyzeImportedAccounts(existingAccounts, normalized.accounts);
|
|
69
|
+
return Promise.resolve({
|
|
70
|
+
imported: analysis.imported,
|
|
71
|
+
total: analysis.total,
|
|
72
|
+
skipped: analysis.skipped,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Exports current accounts to a JSON file for backup/migration.
|
|
78
|
+
*
|
|
79
|
+
* Safety default (audit `docs/audits/04-high-priority.md`):
|
|
80
|
+
* `force` defaults to `false` so an existing export file is never silently
|
|
81
|
+
* overwritten. Callers that need the prior destructive behaviour must opt in
|
|
82
|
+
* via `exportAccounts(path, true)`.
|
|
83
|
+
*
|
|
84
|
+
* @param filePath - Destination file path.
|
|
85
|
+
* @param force - If true, overwrite any existing file at `filePath`. Defaults to
|
|
86
|
+
* `false`; when false and the file exists, a `StorageError` is thrown.
|
|
87
|
+
* @throws StorageError if the file already exists and `force` is `false`.
|
|
88
|
+
* @throws Error if there are no accounts to export.
|
|
89
|
+
*/
|
|
90
|
+
export async function exportAccounts(filePath, force = false) {
|
|
91
|
+
const resolvedPath = resolvePath(filePath);
|
|
92
|
+
if (!force && existsSync(resolvedPath)) {
|
|
93
|
+
throw new StorageError(`Refusing to overwrite existing export file: ${resolvedPath}. Pass force=true to overwrite.`, "EEXIST", resolvedPath, "Re-run the export with force=true if you intend to replace the existing file, or choose a new path.");
|
|
94
|
+
}
|
|
95
|
+
const storage = await withAccountStorageTransaction((current) => Promise.resolve(current));
|
|
96
|
+
if (!storage || storage.accounts.length === 0) {
|
|
97
|
+
throw new Error("No accounts to export");
|
|
98
|
+
}
|
|
99
|
+
await fs.mkdir(dirname(resolvedPath), { recursive: true });
|
|
100
|
+
const content = JSON.stringify(storage, null, 2);
|
|
101
|
+
await fs.writeFile(resolvedPath, content, { encoding: "utf-8", mode: 0o600 });
|
|
102
|
+
log.info("Exported accounts", { path: resolvedPath, count: storage.accounts.length });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Imports accounts from a JSON file, merging with existing accounts.
|
|
106
|
+
* Deduplicates by identity key first (organizationId -> accountId -> refreshToken),
|
|
107
|
+
* then applies legacy email dedupe only to entries without organizationId/accountId.
|
|
108
|
+
*
|
|
109
|
+
* Safety default (audit `docs/audits/04-high-priority.md`):
|
|
110
|
+
* `options.backupMode` defaults to `"timestamped"` so a pre-import snapshot of
|
|
111
|
+
* the existing accounts file is always written before apply. Callers that need
|
|
112
|
+
* the prior destructive default must pass `{ backupMode: "none" }` explicitly.
|
|
113
|
+
*
|
|
114
|
+
* @param filePath - Source file path
|
|
115
|
+
* @throws Error if file is invalid or would exceed MAX_ACCOUNTS
|
|
116
|
+
*/
|
|
117
|
+
export async function importAccounts(filePath, options = {}) {
|
|
118
|
+
const { resolvedPath, normalized } = await readAndNormalizeImportFile(filePath);
|
|
119
|
+
const backupMode = options.backupMode ?? "timestamped";
|
|
120
|
+
const backupPrefix = options.preImportBackupPrefix ?? "codex-pre-import-backup";
|
|
121
|
+
const { imported: importedCount, total, skipped: skippedCount, backupStatus, backupPath, backupError, } = await withAccountStorageTransaction(async (existing, persist) => {
|
|
122
|
+
const existingStorage = existing ??
|
|
123
|
+
{
|
|
124
|
+
version: 3,
|
|
125
|
+
accounts: [],
|
|
126
|
+
activeIndex: 0,
|
|
127
|
+
activeIndexByFamily: {},
|
|
128
|
+
};
|
|
129
|
+
const existingAccounts = existingStorage.accounts;
|
|
130
|
+
const existingActiveIndex = existingStorage.activeIndex;
|
|
131
|
+
const clampedExistingActiveIndex = clampIndex(existingActiveIndex, existingAccounts.length);
|
|
132
|
+
const existingActiveKeys = extractActiveKeys(existingAccounts, clampedExistingActiveIndex);
|
|
133
|
+
const existingActiveIndexByFamily = existingStorage.activeIndexByFamily ?? {};
|
|
134
|
+
let backupStatus = "skipped";
|
|
135
|
+
let backupPath;
|
|
136
|
+
let backupError;
|
|
137
|
+
let backupLogError;
|
|
138
|
+
if (backupMode !== "none" && existingAccounts.length > 0) {
|
|
139
|
+
backupPath = createTimestampedBackupPath(backupPrefix);
|
|
140
|
+
try {
|
|
141
|
+
await writePreImportBackupFile(backupPath, existingStorage);
|
|
142
|
+
backupStatus = "created";
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
backupStatus = "failed";
|
|
146
|
+
backupError = error instanceof Error ? error.message : String(error);
|
|
147
|
+
const backupCode = error?.code;
|
|
148
|
+
backupLogError = backupCode
|
|
149
|
+
? `pre-import backup failed (${backupCode})`
|
|
150
|
+
: "pre-import backup failed";
|
|
151
|
+
if (backupMode === "required") {
|
|
152
|
+
throw new Error(backupCode
|
|
153
|
+
? `Pre-import backup failed (${backupCode})`
|
|
154
|
+
: "Pre-import backup failed");
|
|
155
|
+
}
|
|
156
|
+
log.warn("Pre-import backup failed; continuing import apply", {
|
|
157
|
+
backupFile: backupPath ? basename(backupPath) : undefined,
|
|
158
|
+
error: backupLogError,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const analysis = analyzeImportedAccounts(existingAccounts, normalized.accounts);
|
|
163
|
+
const deduplicatedAccounts = analysis.accounts;
|
|
164
|
+
const mappedActiveIndex = (() => {
|
|
165
|
+
if (deduplicatedAccounts.length === 0)
|
|
166
|
+
return 0;
|
|
167
|
+
if (existingActiveKeys.length > 0) {
|
|
168
|
+
const idx = findAccountIndexByIdentityKeys(deduplicatedAccounts, existingActiveKeys);
|
|
169
|
+
if (idx >= 0)
|
|
170
|
+
return idx;
|
|
171
|
+
}
|
|
172
|
+
return clampIndex(clampedExistingActiveIndex, deduplicatedAccounts.length);
|
|
173
|
+
})();
|
|
174
|
+
const activeIndexByFamily = {};
|
|
175
|
+
for (const family of MODEL_FAMILIES) {
|
|
176
|
+
const rawFamilyIndex = existingActiveIndexByFamily[family];
|
|
177
|
+
const familyIndex = typeof rawFamilyIndex === "number" && Number.isFinite(rawFamilyIndex)
|
|
178
|
+
? rawFamilyIndex
|
|
179
|
+
: clampedExistingActiveIndex;
|
|
180
|
+
const familyKeys = extractActiveKeys(existingAccounts, clampIndex(familyIndex, existingAccounts.length));
|
|
181
|
+
if (familyKeys.length > 0) {
|
|
182
|
+
const idx = findAccountIndexByIdentityKeys(deduplicatedAccounts, familyKeys);
|
|
183
|
+
activeIndexByFamily[family] = idx >= 0 ? idx : mappedActiveIndex;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
activeIndexByFamily[family] = mappedActiveIndex;
|
|
187
|
+
}
|
|
188
|
+
const newStorage = {
|
|
189
|
+
version: 3,
|
|
190
|
+
accounts: deduplicatedAccounts,
|
|
191
|
+
activeIndex: mappedActiveIndex,
|
|
192
|
+
activeIndexByFamily,
|
|
193
|
+
};
|
|
194
|
+
await persist(newStorage);
|
|
195
|
+
return {
|
|
196
|
+
imported: analysis.imported,
|
|
197
|
+
total: analysis.total,
|
|
198
|
+
skipped: analysis.skipped,
|
|
199
|
+
backupStatus,
|
|
200
|
+
backupPath,
|
|
201
|
+
backupError,
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
log.info("Imported accounts", {
|
|
205
|
+
path: resolvedPath,
|
|
206
|
+
imported: importedCount,
|
|
207
|
+
skipped: skippedCount,
|
|
208
|
+
total,
|
|
209
|
+
backupStatus,
|
|
210
|
+
backupFile: backupPath ? basename(backupPath) : undefined,
|
|
211
|
+
backupError: backupError ? "available on command result" : undefined,
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
imported: importedCount,
|
|
215
|
+
total,
|
|
216
|
+
skipped: skippedCount,
|
|
217
|
+
backupStatus,
|
|
218
|
+
backupPath,
|
|
219
|
+
backupError,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=export-import.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-import.js","sourceRoot":"","sources":["../../../lib/storage/export-import.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAoB,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,UAAU,EACV,6BAA6B,EAC7B,iBAAiB,EACjB,8BAA8B,GAC/B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AA6CpC,KAAK,UAAU,0BAA0B,CAAC,QAAgB;IAIxD,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAEzD,IAAI,QAAiB,CAAC;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,YAAY,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,yEAAyE;IACzE,6CAA6C;IAC7C,MAAM,UAAU,GAAG,uBAAuB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IACnE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AACtC,CAAC;AAED,SAAS,uBAAuB,CAC9B,gBAAqC,EACrC,gBAA8C;IAE9C,MAAM,MAAM,GAAG,CAAC,GAAG,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,6BAA6B,CAAC,MAAM,CAAC,CAAC;IACvD,IAAI,QAAQ,CAAC,MAAM,GAAG,cAAc,CAAC,YAAY,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,kCAAkC,cAAc,CAAC,YAAY,yBAAyB,QAAQ,CAAC,MAAM,GAAG,CACzG,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC;IAChE,OAAO;QACL,QAAQ;QACR,QAAQ;QACR,KAAK,EAAE,QAAQ,CAAC,MAAM;QACtB,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AAEH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAgB;IAEhB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAElE,OAAO,6BAA6B,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChD,MAAM,gBAAgB,GAAG,QAAQ,EAAE,QAAQ,IAAI,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,uBAAuB,CAAC,gBAAgB,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QAChF,OAAO,OAAO,CAAC,OAAO,CAAC;YACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,OAAO,EAAE,QAAQ,CAAC,OAAO;SAC1B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,KAAK,GAAG,KAAK;IAClE,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,CAAC,KAAK,IAAI,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,YAAY,CACpB,+CAA+C,YAAY,iCAAiC,EAC5F,QAAQ,EACR,YAAY,EACZ,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,6BAA6B,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3F,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;AACxF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAgB,EAChB,UAAiC,EAAE;IAEnC,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,MAAM,0BAA0B,CAAC,QAAQ,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,aAAa,CAAC;IACvD,MAAM,YAAY,GAAG,OAAO,CAAC,qBAAqB,IAAI,yBAAyB,CAAC;IAEhF,MAAM,EACJ,QAAQ,EAAE,aAAa,EACvB,KAAK,EACL,OAAO,EAAE,YAAY,EACrB,YAAY,EACZ,UAAU,EACV,WAAW,GACZ,GACC,MAAM,6BAA6B,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC9D,MAAM,eAAe,GACnB,QAAQ;YACP;gBACC,OAAO,EAAE,CAAC;gBACV,QAAQ,EAAE,EAAE;gBACZ,WAAW,EAAE,CAAC;gBACd,mBAAmB,EAAE,EAAE;aACI,CAAC;QAChC,MAAM,gBAAgB,GAAG,eAAe,CAAC,QAAQ,CAAC;QAClD,MAAM,mBAAmB,GAAG,eAAe,CAAC,WAAW,CAAC;QACxD,MAAM,0BAA0B,GAAG,UAAU,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC5F,MAAM,kBAAkB,GAAG,iBAAiB,CAAC,gBAAgB,EAAE,0BAA0B,CAAC,CAAC;QAC3F,MAAM,2BAA2B,GAAG,eAAe,CAAC,mBAAmB,IAAI,EAAE,CAAC;QAE9E,IAAI,YAAY,GAAuB,SAAS,CAAC;QACjD,IAAI,UAA8B,CAAC;QACnC,IAAI,WAA+B,CAAC;QACpC,IAAI,cAAkC,CAAC;QACvC,IAAI,UAAU,KAAK,MAAM,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,UAAU,GAAG,2BAA2B,CAAC,YAAY,CAAC,CAAC;YACvD,IAAI,CAAC;gBACH,MAAM,wBAAwB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;gBAC5D,YAAY,GAAG,SAAS,CAAC;YAC3B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,YAAY,GAAG,QAAQ,CAAC;gBACxB,WAAW,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrE,MAAM,UAAU,GAAI,KAA+B,EAAE,IAAI,CAAC;gBAC1D,cAAc,GAAG,UAAU;oBACzB,CAAC,CAAC,6BAA6B,UAAU,GAAG;oBAC5C,CAAC,CAAC,0BAA0B,CAAC;gBAC/B,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;oBAC9B,MAAM,IAAI,KAAK,CACb,UAAU;wBACR,CAAC,CAAC,6BAA6B,UAAU,GAAG;wBAC5C,CAAC,CAAC,0BAA0B,CAC/B,CAAC;gBACJ,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,mDAAmD,EAAE;oBAC5D,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;oBACzD,KAAK,EAAE,cAAc;iBACtB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,uBAAuB,CAAC,gBAAgB,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC;QAChF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAE/C,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE;YAC9B,IAAI,oBAAoB,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,CAAC,CAAC;YAChD,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,GAAG,8BAA8B,CAAC,oBAAoB,EAAE,kBAAkB,CAAC,CAAC;gBACrF,IAAI,GAAG,IAAI,CAAC;oBAAE,OAAO,GAAG,CAAC;YAC3B,CAAC;YACD,OAAO,UAAU,CAAC,0BAA0B,EAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC;QAC7E,CAAC,CAAC,EAAE,CAAC;QAEL,MAAM,mBAAmB,GAAyC,EAAE,CAAC;QACrE,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;YACpC,MAAM,cAAc,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;YAC3D,MAAM,WAAW,GACf,OAAO,cAAc,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC;gBACnE,CAAC,CAAC,cAAc;gBAChB,CAAC,CAAC,0BAA0B,CAAC;YACjC,MAAM,UAAU,GAAG,iBAAiB,CAAC,gBAAgB,EAAE,UAAU,CAAC,WAAW,EAAE,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;YACzG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,MAAM,GAAG,GAAG,8BAA8B,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;gBAC7E,mBAAmB,CAAC,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAC;gBACjE,SAAS;YACX,CAAC;YACD,mBAAmB,CAAC,MAAM,CAAC,GAAG,iBAAiB,CAAC;QAClD,CAAC;QAED,MAAM,UAAU,GAAqB;YACnC,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,oBAAoB;YAC9B,WAAW,EAAE,iBAAiB;YAC9B,mBAAmB;SACpB,CAAC;QAEF,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;QAE1B,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,YAAY;YACZ,UAAU;YACV,WAAW;SACZ,CAAC;IACJ,CAAC,CAAC,CAAC;IAEL,GAAG,CAAC,IAAI,CAAC,mBAAmB,EAAE;QAC5B,IAAI,EAAE,YAAY;QAClB,QAAQ,EAAE,aAAa;QACvB,OAAO,EAAE,YAAY;QACrB,KAAK;QACL,YAAY;QACZ,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,SAAS;QACzD,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,SAAS;KACrE,CAAC,CAAC;IAEH,OAAO;QACL,QAAQ,EAAE,aAAa;QACvB,KAAK;QACL,OAAO,EAAE,YAAY;QACrB,YAAY;QACZ,UAAU;QACV,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flagged-account storage: load/save/clear + transactional update.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. Flagged accounts live in a sibling
|
|
5
|
+
* file next to the main accounts file and follow the same mutex, temp-file +
|
|
6
|
+
* rename, and legacy-file migration pattern — just with a simpler V1-only
|
|
7
|
+
* schema and different legacy filenames (flagged-accounts.json and the
|
|
8
|
+
* older blocked-accounts.json).
|
|
9
|
+
*/
|
|
10
|
+
import type { AccountMetadataV3 } from "./migrations.js";
|
|
11
|
+
export interface FlaggedAccountMetadataV1 extends AccountMetadataV3 {
|
|
12
|
+
flaggedAt: number;
|
|
13
|
+
flaggedReason?: string;
|
|
14
|
+
lastError?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface FlaggedAccountStorageV1 {
|
|
17
|
+
version: 1;
|
|
18
|
+
accounts: FlaggedAccountMetadataV1[];
|
|
19
|
+
}
|
|
20
|
+
export declare function getFlaggedAccountsPath(): string;
|
|
21
|
+
export declare function loadFlaggedAccounts(): Promise<FlaggedAccountStorageV1>;
|
|
22
|
+
/**
|
|
23
|
+
* Executes a read-modify-write transaction for flagged account storage under the
|
|
24
|
+
* shared storage lock so concurrent callers cannot lose updates.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withFlaggedAccountStorageTransaction<T>(handler: (current: FlaggedAccountStorageV1, persist: (storage: FlaggedAccountStorageV1) => Promise<void>) => Promise<T>): Promise<T>;
|
|
27
|
+
export declare function saveFlaggedAccounts(storage: FlaggedAccountStorageV1): Promise<void>;
|
|
28
|
+
export declare function clearFlaggedAccounts(): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=flagged.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flagged.d.ts","sourceRoot":"","sources":["../../../lib/storage/flagged.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAaH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIzD,MAAM,WAAW,wBAAyB,SAAQ,iBAAiB;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,wBAAwB,EAAE,CAAC;CACtC;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAoLD,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,uBAAuB,CAAC,CAE5E;AAED;;;GAGG;AACH,wBAAsB,oCAAoC,CAAC,CAAC,EAC1D,OAAO,EAAE,CACP,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,KACzD,OAAO,CAAC,CAAC,CAAC,GACd,OAAO,CAAC,CAAC,CAAC,CAKZ;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzF;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAW1D"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flagged-account storage: load/save/clear + transactional update.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. Flagged accounts live in a sibling
|
|
5
|
+
* file next to the main accounts file and follow the same mutex, temp-file +
|
|
6
|
+
* rename, and legacy-file migration pattern — just with a simpler V1-only
|
|
7
|
+
* schema and different legacy filenames (flagged-accounts.json and the
|
|
8
|
+
* older blocked-accounts.json).
|
|
9
|
+
*/
|
|
10
|
+
import { promises as fs, existsSync } from "node:fs";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { FLAGGED_ACCOUNTS_FILE_NAME, LEGACY_BLOCKED_ACCOUNTS_FILE_NAME, LEGACY_FLAGGED_ACCOUNTS_FILE_NAME, } from "../constants.js";
|
|
13
|
+
import { createLogger } from "../logger.js";
|
|
14
|
+
import { renameWithWindowsRetry } from "./atomic-write.js";
|
|
15
|
+
import { getWorkspaceIdentityKey, isRecord } from "./identity.js";
|
|
16
|
+
import { getStoragePath, withStorageLock } from "./state.js";
|
|
17
|
+
const log = createLogger("storage");
|
|
18
|
+
export function getFlaggedAccountsPath() {
|
|
19
|
+
return join(dirname(getStoragePath()), FLAGGED_ACCOUNTS_FILE_NAME);
|
|
20
|
+
}
|
|
21
|
+
function getLegacyFlaggedAccountsPath() {
|
|
22
|
+
return join(dirname(getStoragePath()), LEGACY_FLAGGED_ACCOUNTS_FILE_NAME);
|
|
23
|
+
}
|
|
24
|
+
function getLegacyBlockedAccountsPath() {
|
|
25
|
+
return join(dirname(getStoragePath()), LEGACY_BLOCKED_ACCOUNTS_FILE_NAME);
|
|
26
|
+
}
|
|
27
|
+
function normalizeFlaggedStorage(data) {
|
|
28
|
+
if (!isRecord(data) || data.version !== 1 || !Array.isArray(data.accounts)) {
|
|
29
|
+
return { version: 1, accounts: [] };
|
|
30
|
+
}
|
|
31
|
+
const byIdentityKey = new Map();
|
|
32
|
+
for (const rawAccount of data.accounts) {
|
|
33
|
+
if (!isRecord(rawAccount))
|
|
34
|
+
continue;
|
|
35
|
+
const refreshToken = typeof rawAccount.refreshToken === "string" ? rawAccount.refreshToken.trim() : "";
|
|
36
|
+
if (!refreshToken)
|
|
37
|
+
continue;
|
|
38
|
+
const flaggedAt = typeof rawAccount.flaggedAt === "number" ? rawAccount.flaggedAt : Date.now();
|
|
39
|
+
const isAccountIdSource = (value) => value === "token" || value === "id_token" || value === "org" || value === "manual";
|
|
40
|
+
const isSwitchReason = (value) => value === "rate-limit" || value === "initial" || value === "rotation";
|
|
41
|
+
const isCooldownReason = (value) => value === "auth-failure" || value === "network-error";
|
|
42
|
+
const normalizeTags = (value) => {
|
|
43
|
+
if (!Array.isArray(value))
|
|
44
|
+
return undefined;
|
|
45
|
+
const normalized = value
|
|
46
|
+
.filter((entry) => typeof entry === "string")
|
|
47
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
48
|
+
.filter((entry) => entry.length > 0);
|
|
49
|
+
return normalized.length > 0 ? Array.from(new Set(normalized)) : undefined;
|
|
50
|
+
};
|
|
51
|
+
let rateLimitResetTimes;
|
|
52
|
+
if (isRecord(rawAccount.rateLimitResetTimes)) {
|
|
53
|
+
const normalizedRateLimits = {};
|
|
54
|
+
for (const [key, value] of Object.entries(rawAccount.rateLimitResetTimes)) {
|
|
55
|
+
if (typeof value === "number") {
|
|
56
|
+
normalizedRateLimits[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (Object.keys(normalizedRateLimits).length > 0) {
|
|
60
|
+
rateLimitResetTimes = normalizedRateLimits;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const accountIdSource = isAccountIdSource(rawAccount.accountIdSource)
|
|
64
|
+
? rawAccount.accountIdSource
|
|
65
|
+
: undefined;
|
|
66
|
+
const lastSwitchReason = isSwitchReason(rawAccount.lastSwitchReason)
|
|
67
|
+
? rawAccount.lastSwitchReason
|
|
68
|
+
: undefined;
|
|
69
|
+
const cooldownReason = isCooldownReason(rawAccount.cooldownReason)
|
|
70
|
+
? rawAccount.cooldownReason
|
|
71
|
+
: undefined;
|
|
72
|
+
const accountTags = normalizeTags(rawAccount.accountTags);
|
|
73
|
+
const accountNote = typeof rawAccount.accountNote === "string" && rawAccount.accountNote.trim()
|
|
74
|
+
? rawAccount.accountNote.trim()
|
|
75
|
+
: undefined;
|
|
76
|
+
const normalized = {
|
|
77
|
+
refreshToken,
|
|
78
|
+
addedAt: typeof rawAccount.addedAt === "number" ? rawAccount.addedAt : flaggedAt,
|
|
79
|
+
lastUsed: typeof rawAccount.lastUsed === "number" ? rawAccount.lastUsed : flaggedAt,
|
|
80
|
+
organizationId: typeof rawAccount.organizationId === "string" ? rawAccount.organizationId : undefined,
|
|
81
|
+
accountId: typeof rawAccount.accountId === "string" ? rawAccount.accountId : undefined,
|
|
82
|
+
accountIdSource,
|
|
83
|
+
accountLabel: typeof rawAccount.accountLabel === "string" ? rawAccount.accountLabel : undefined,
|
|
84
|
+
accountTags,
|
|
85
|
+
accountNote,
|
|
86
|
+
email: typeof rawAccount.email === "string" ? rawAccount.email : undefined,
|
|
87
|
+
enabled: typeof rawAccount.enabled === "boolean" ? rawAccount.enabled : undefined,
|
|
88
|
+
lastSwitchReason,
|
|
89
|
+
rateLimitResetTimes,
|
|
90
|
+
coolingDownUntil: typeof rawAccount.coolingDownUntil === "number" ? rawAccount.coolingDownUntil : undefined,
|
|
91
|
+
cooldownReason,
|
|
92
|
+
flaggedAt,
|
|
93
|
+
flaggedReason: typeof rawAccount.flaggedReason === "string" ? rawAccount.flaggedReason : undefined,
|
|
94
|
+
lastError: typeof rawAccount.lastError === "string" ? rawAccount.lastError : undefined,
|
|
95
|
+
};
|
|
96
|
+
// Keep flagged dedup aligned with active cleanup so sibling workspaces only
|
|
97
|
+
// collapse when they resolve to the same shared workspace identity.
|
|
98
|
+
byIdentityKey.set(getWorkspaceIdentityKey(normalized), normalized);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
version: 1,
|
|
102
|
+
accounts: Array.from(byIdentityKey.values()),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async function loadFlaggedAccountsUnlocked(saveUnlocked) {
|
|
106
|
+
const path = getFlaggedAccountsPath();
|
|
107
|
+
const empty = { version: 1, accounts: [] };
|
|
108
|
+
try {
|
|
109
|
+
const content = await fs.readFile(path, "utf-8");
|
|
110
|
+
const data = JSON.parse(content);
|
|
111
|
+
return normalizeFlaggedStorage(data);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const code = error.code;
|
|
115
|
+
if (code !== "ENOENT") {
|
|
116
|
+
log.error("Failed to load flagged account storage", { path, error: String(error) });
|
|
117
|
+
return empty;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const legacyPath of [getLegacyFlaggedAccountsPath(), getLegacyBlockedAccountsPath()]) {
|
|
121
|
+
if (!existsSync(legacyPath)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const legacyContent = await fs.readFile(legacyPath, "utf-8");
|
|
126
|
+
const legacyData = JSON.parse(legacyContent);
|
|
127
|
+
const migrated = normalizeFlaggedStorage(legacyData);
|
|
128
|
+
if (migrated.accounts.length > 0) {
|
|
129
|
+
await saveUnlocked(migrated);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
await fs.unlink(legacyPath);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Best effort cleanup.
|
|
136
|
+
}
|
|
137
|
+
log.info("Migrated legacy flagged account storage", {
|
|
138
|
+
from: legacyPath,
|
|
139
|
+
to: path,
|
|
140
|
+
accounts: migrated.accounts.length,
|
|
141
|
+
});
|
|
142
|
+
return migrated;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
log.error("Failed to migrate legacy flagged account storage", {
|
|
146
|
+
from: legacyPath,
|
|
147
|
+
to: path,
|
|
148
|
+
error: String(error),
|
|
149
|
+
});
|
|
150
|
+
return empty;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return empty;
|
|
154
|
+
}
|
|
155
|
+
async function saveFlaggedAccountsUnlocked(storage) {
|
|
156
|
+
const path = getFlaggedAccountsPath();
|
|
157
|
+
const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
158
|
+
const tempPath = `${path}.${uniqueSuffix}.tmp`;
|
|
159
|
+
try {
|
|
160
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
161
|
+
const content = JSON.stringify(normalizeFlaggedStorage(storage), null, 2);
|
|
162
|
+
await fs.writeFile(tempPath, content, { encoding: "utf-8", mode: 0o600 });
|
|
163
|
+
await renameWithWindowsRetry(tempPath, path);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
try {
|
|
167
|
+
await fs.unlink(tempPath);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Ignore cleanup failures.
|
|
171
|
+
}
|
|
172
|
+
log.error("Failed to save flagged account storage", { path, error: String(error) });
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export async function loadFlaggedAccounts() {
|
|
177
|
+
return withStorageLock(async () => loadFlaggedAccountsUnlocked(saveFlaggedAccountsUnlocked));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Executes a read-modify-write transaction for flagged account storage under the
|
|
181
|
+
* shared storage lock so concurrent callers cannot lose updates.
|
|
182
|
+
*/
|
|
183
|
+
export async function withFlaggedAccountStorageTransaction(handler) {
|
|
184
|
+
return withStorageLock(async () => {
|
|
185
|
+
const current = await loadFlaggedAccountsUnlocked(saveFlaggedAccountsUnlocked);
|
|
186
|
+
return handler(current, saveFlaggedAccountsUnlocked);
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
export async function saveFlaggedAccounts(storage) {
|
|
190
|
+
return withStorageLock(async () => {
|
|
191
|
+
await saveFlaggedAccountsUnlocked(storage);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
export async function clearFlaggedAccounts() {
|
|
195
|
+
return withStorageLock(async () => {
|
|
196
|
+
try {
|
|
197
|
+
await fs.unlink(getFlaggedAccountsPath());
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
const code = error.code;
|
|
201
|
+
if (code !== "ENOENT") {
|
|
202
|
+
log.error("Failed to clear flagged account storage", { error: String(error) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=flagged.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flagged.js","sourceRoot":"","sources":["../../../lib/storage/flagged.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account identity + dedup helpers.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `lib/storage.ts` in RC-2. This module is pure and synchronous:
|
|
5
|
+
* it owns the identity-key hierarchy (`organizationId` -> `accountId` ->
|
|
6
|
+
* `refreshToken`) and the dedup + merge rules used by `normalizeAccountStorage`
|
|
7
|
+
* and the import transaction.
|
|
8
|
+
*
|
|
9
|
+
* The two public exports that were already re-exported from `lib/storage.ts`
|
|
10
|
+
* (`deduplicateAccounts`, `deduplicateAccountsByEmail`) keep their original
|
|
11
|
+
* shape so external callers can import them via either the barrel or this
|
|
12
|
+
* submodule.
|
|
13
|
+
*/
|
|
14
|
+
import type { AccountMetadataV3 } from "./migrations.js";
|
|
15
|
+
export type AccountLike = {
|
|
16
|
+
organizationId?: string;
|
|
17
|
+
accountId?: string;
|
|
18
|
+
accountIdSource?: AccountMetadataV3["accountIdSource"];
|
|
19
|
+
accountLabel?: string;
|
|
20
|
+
email?: string;
|
|
21
|
+
refreshToken: string;
|
|
22
|
+
addedAt?: number;
|
|
23
|
+
lastUsed?: number;
|
|
24
|
+
};
|
|
25
|
+
export declare function getWorkspaceIdentityKey(account: {
|
|
26
|
+
organizationId?: string;
|
|
27
|
+
accountId?: string;
|
|
28
|
+
refreshToken: string;
|
|
29
|
+
}): string;
|
|
30
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
31
|
+
export declare function clampIndex(index: number, length: number): number;
|
|
32
|
+
export declare function toAccountIdentityKeys(account: Pick<AccountMetadataV3, "organizationId" | "accountId" | "refreshToken">): string[];
|
|
33
|
+
export declare function toAccountIdentityKey(account: Pick<AccountMetadataV3, "organizationId" | "accountId" | "refreshToken">): string | undefined;
|
|
34
|
+
export declare function extractActiveKeys(accounts: unknown[], activeIndex: number): string[];
|
|
35
|
+
export declare function findAccountIndexByIdentityKeys(accounts: Pick<AccountMetadataV3, "organizationId" | "accountId" | "refreshToken">[], identityKeys: string[]): number;
|
|
36
|
+
/**
|
|
37
|
+
* Removes duplicate accounts, keeping the most recently used entry for each unique key.
|
|
38
|
+
* Deduplication identity hierarchy: organizationId -> accountId -> refreshToken.
|
|
39
|
+
* @param accounts - Array of accounts to deduplicate
|
|
40
|
+
* @returns New array with duplicates removed
|
|
41
|
+
*/
|
|
42
|
+
export declare function deduplicateAccounts<T extends {
|
|
43
|
+
organizationId?: string;
|
|
44
|
+
accountId?: string;
|
|
45
|
+
refreshToken: string;
|
|
46
|
+
lastUsed?: number;
|
|
47
|
+
addedAt?: number;
|
|
48
|
+
}>(accounts: T[]): T[];
|
|
49
|
+
/**
|
|
50
|
+
* Removes duplicate legacy accounts by email, keeping the most recently used entry.
|
|
51
|
+
* Accounts with organizationId/accountId are never merged by email to avoid collapsing workspace variants.
|
|
52
|
+
* Accounts without email are always preserved.
|
|
53
|
+
* @param accounts - Array of accounts to deduplicate
|
|
54
|
+
* @returns New array with email duplicates removed
|
|
55
|
+
*/
|
|
56
|
+
export declare function deduplicateAccountsByEmail<T extends {
|
|
57
|
+
organizationId?: string;
|
|
58
|
+
accountId?: string;
|
|
59
|
+
email?: string;
|
|
60
|
+
lastUsed?: number;
|
|
61
|
+
addedAt?: number;
|
|
62
|
+
}>(accounts: T[]): T[];
|
|
63
|
+
/**
|
|
64
|
+
* Applies storage deduplication semantics used by normalize/import paths.
|
|
65
|
+
* 1) Dedupe only exact identity duplicates (organizationId -> accountId -> refreshToken),
|
|
66
|
+
* preserving distinct workspace variants that share a refresh token.
|
|
67
|
+
* 2) Then apply legacy email dedupe only for entries that still do not have organizationId/accountId.
|
|
68
|
+
*/
|
|
69
|
+
export declare function deduplicateAccountsForStorage<T extends AccountLike & {
|
|
70
|
+
email?: string;
|
|
71
|
+
}>(accounts: T[]): T[];
|
|
72
|
+
//# sourceMappingURL=identity.d.ts.map
|