codex-multi-auth 2.8.7 → 2.9.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.
Files changed (62) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/README.md +14 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +34 -0
  5. package/dist/lib/account-policy.d.ts +3 -1
  6. package/dist/lib/account-policy.d.ts.map +1 -1
  7. package/dist/lib/account-policy.js +14 -1
  8. package/dist/lib/budget-guard.d.ts +1 -0
  9. package/dist/lib/budget-guard.d.ts.map +1 -1
  10. package/dist/lib/budget-guard.js +13 -3
  11. package/dist/lib/codex-manager/account-pool-write.d.ts +8 -0
  12. package/dist/lib/codex-manager/account-pool-write.d.ts.map +1 -1
  13. package/dist/lib/codex-manager/account-pool-write.js +1 -1
  14. package/dist/lib/codex-manager/help.d.ts +2 -0
  15. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  16. package/dist/lib/codex-manager/help.js +38 -1
  17. package/dist/lib/codex-manager/login-flow.d.ts +3 -0
  18. package/dist/lib/codex-manager/login-flow.d.ts.map +1 -1
  19. package/dist/lib/codex-manager/login-flow.js +80 -6
  20. package/dist/lib/codex-manager/login-menu-actions.d.ts.map +1 -1
  21. package/dist/lib/codex-manager/login-menu-actions.js +41 -5
  22. package/dist/lib/codex-manager/login-oauth.d.ts +38 -2
  23. package/dist/lib/codex-manager/login-oauth.d.ts.map +1 -1
  24. package/dist/lib/codex-manager/login-oauth.js +205 -23
  25. package/dist/lib/policy/runtime-policy.d.ts +7 -0
  26. package/dist/lib/policy/runtime-policy.d.ts.map +1 -1
  27. package/dist/lib/policy/runtime-policy.js +1 -0
  28. package/dist/lib/request/fetch-helpers.d.ts +7 -0
  29. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  30. package/dist/lib/request/fetch-helpers.js +1 -1
  31. package/dist/lib/request/response-handler.d.ts +3 -1
  32. package/dist/lib/request/response-handler.d.ts.map +1 -1
  33. package/dist/lib/request/response-handler.js +26 -5
  34. package/dist/lib/request/stream-failover-runtime.d.ts +8 -1
  35. package/dist/lib/request/stream-failover-runtime.d.ts.map +1 -1
  36. package/dist/lib/request/stream-failover-runtime.js +9 -1
  37. package/dist/lib/request/stream-failover.d.ts.map +1 -1
  38. package/dist/lib/request/stream-failover.js +41 -0
  39. package/dist/lib/runtime/account-check.d.ts.map +1 -1
  40. package/dist/lib/runtime/account-check.js +8 -1
  41. package/dist/lib/runtime-rotation-proxy.d.ts.map +1 -1
  42. package/dist/lib/runtime-rotation-proxy.js +46 -3
  43. package/dist/lib/storage.d.ts +19 -0
  44. package/dist/lib/storage.d.ts.map +1 -1
  45. package/dist/lib/storage.js +32 -0
  46. package/dist/lib/usage/index.d.ts +2 -0
  47. package/dist/lib/usage/index.d.ts.map +1 -1
  48. package/dist/lib/usage/index.js +2 -0
  49. package/dist/lib/usage/ledger.d.ts.map +1 -1
  50. package/dist/lib/usage/ledger.js +7 -0
  51. package/dist/lib/usage/pricing.d.ts +15 -0
  52. package/dist/lib/usage/pricing.d.ts.map +1 -1
  53. package/dist/lib/usage/pricing.js +24 -0
  54. package/dist/lib/usage/stream-usage-deferral.d.ts +39 -0
  55. package/dist/lib/usage/stream-usage-deferral.d.ts.map +1 -0
  56. package/dist/lib/usage/stream-usage-deferral.js +54 -0
  57. package/dist/lib/usage/types.d.ts +6 -0
  58. package/dist/lib/usage/types.d.ts.map +1 -1
  59. package/dist/lib/usage/usage-extraction.d.ts +42 -0
  60. package/dist/lib/usage/usage-extraction.d.ts.map +1 -0
  61. package/dist/lib/usage/usage-extraction.js +178 -0
  62. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-multi-auth",
3
- "version": "2.8.7",
3
+ "version": "2.9.1",
4
4
  "description": "Install and operate codex-multi-auth for the official @openai/codex CLI with multi-account OAuth rotation, switching, health checks, and recovery tools.",
5
5
  "interface": {
6
6
  "composerIcon": "./assets/codex-multi-auth-icon.svg"
package/README.md CHANGED
@@ -161,6 +161,19 @@ codex-multi-auth forecast --live
161
161
 
162
162
  If browser launch is blocked, use the alternate login paths in [docs/getting-started.md](docs/getting-started.md#alternate-login-paths).
163
163
  For remote or headless shells, prefer `codex-multi-auth login --device-auth`.
164
+ To refresh one saved account without changing the active selection or manual
165
+ pin, run:
166
+
167
+ ```bash
168
+ codex-multi-auth login --account <index|email|account_id> --preserve-selection
169
+ ```
170
+
171
+ Add `--device-auth` on a remote shell. The refreshed OAuth identity must match
172
+ the requested saved account or nothing is written. Refreshing the account Codex
173
+ is currently using still publishes its new tokens to the native CLI; refreshing
174
+ any other account leaves the active selection and the manual pin untouched. A
175
+ disabled account stays disabled. `--account` cannot be combined with `--org`,
176
+ which would rebind the row to a different workspace.
164
177
 
165
178
  ---
166
179
 
@@ -392,7 +405,7 @@ codex-multi-auth doctor --json
392
405
 
393
406
  ## Release Notes
394
407
 
395
- - Current stable: [docs/releases/v2.8.7.md](docs/releases/v2.8.7.md) — install via `npm i -g codex-multi-auth`
408
+ - Current stable: [docs/releases/v2.9.1.md](docs/releases/v2.9.1.md) — install via `npm i -g codex-multi-auth`
396
409
  - Previous stable: [docs/releases/v2.8.3.md](docs/releases/v2.8.3.md)
397
410
  - Previous stable: [docs/releases/v2.8.2.md](docs/releases/v2.8.2.md)
398
411
  - Previous stable: [docs/releases/v2.7.1.md](docs/releases/v2.7.1.md)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AA2V5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA2uI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAoW5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAuwI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -38,6 +38,14 @@ import { LiveAccountSync } from "./lib/live-account-sync.js";
38
38
  import { clearCorrelationId, initLogger, logDebug, logError, logInfo, logRequest, logWarn, setCorrelationId, } from "./lib/logger.js";
39
39
  import { PreemptiveQuotaScheduler, readQuotaSchedulerSnapshot, } from "./lib/preemptive-quota-scheduler.js";
40
40
  import { createRuntimeUsageRecorder, evaluateRuntimePolicy, loadRuntimePolicyState, } from "./lib/policy/runtime-policy.js";
41
+ import { createStreamUsageDeferral } from "./lib/usage/stream-usage-deferral.js";
42
+ /**
43
+ * How long a streaming request's usage-ledger row may wait for the upstream
44
+ * `usage` event before it is written without token counts. Long enough not to
45
+ * truncate a genuinely slow completion; the timer is unref'd so it never holds
46
+ * the process open. See createStreamUsageDeferral.
47
+ */
48
+ const USAGE_STREAM_RECORD_FALLBACK_MS = 15 * 60_000;
41
49
  import { getModelFamily, MODEL_FAMILIES, prewarmCodexInstructions, } from "./lib/prompts/codex.js";
42
50
  import { prewarmHostCodexPrompt } from "./lib/prompts/host-codex-prompt.js";
43
51
  import { fetchCodexQuotaSnapshot, formatQuotaSnapshotLine, } from "./lib/quota-probe.js";
@@ -546,6 +554,18 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
546
554
  async fetch(input, init) {
547
555
  let usageRecorder = null;
548
556
  let usageCompletion = null;
557
+ // A non-streaming response reports its token counts while
558
+ // handleSuccessResponse is still assembling the body, so they
559
+ // are folded straight into usageCompletion below. A streaming
560
+ // one reports them only as the client drains the body, long
561
+ // after the `finally` that writes the row — so that row is
562
+ // handed to the deferral instead of written empty.
563
+ const usageDeferral = createStreamUsageDeferral({
564
+ record: (completion) => {
565
+ void usageRecorder?.record(completion);
566
+ },
567
+ fallbackMs: USAGE_STREAM_RECORD_FALLBACK_MS,
568
+ });
549
569
  try {
550
570
  if (cachedAccountManager &&
551
571
  cachedAccountManager !== accountManager) {
@@ -1594,6 +1614,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1594
1614
  sessionAffinityStore?.updateLastResponseId(sessionAffinityKey, responseId, Date.now(), sessionAffinityVersion);
1595
1615
  storedResponseIdForSuccess = true;
1596
1616
  },
1617
+ onUsage: usageDeferral.onUsage,
1597
1618
  streamStallTimeoutMs,
1598
1619
  });
1599
1620
  if (!isStreaming && emptyResponseMaxRetries > 0) {
@@ -1679,7 +1700,20 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1679
1700
  accountId: successAccountForResponse.accountId ?? null,
1680
1701
  email: successAccountForResponse.email ?? null,
1681
1702
  },
1703
+ // Already known for a non-streaming response, which
1704
+ // reported its usage while the body was being
1705
+ // assembled above.
1706
+ ...(usageDeferral.captured() ?? {}),
1682
1707
  };
1708
+ if (isStreaming && !usageDeferral.captured()) {
1709
+ // Writing the row in the `finally` below would stamp
1710
+ // zero tokens — the client has not read a byte yet —
1711
+ // and the ledger is append-only, so the real counts
1712
+ // could never correct it. Zero tokens is exactly what
1713
+ // left maxTokens / maxCostUsd unenforceable here.
1714
+ usageDeferral.defer(usageCompletion);
1715
+ usageCompletion = null;
1716
+ }
1683
1717
  if (lastCodexCliActiveSyncIndex !==
1684
1718
  successAccountForResponse.index) {
1685
1719
  void accountManager.syncCodexCliActiveSelectionForIndex(successAccountForResponse.index);
@@ -13,7 +13,9 @@ export interface AccountPolicyStore {
13
13
  accounts: Record<string, AccountPolicy>;
14
14
  }
15
15
  export declare function getAccountPolicyPath(): string;
16
- export declare function getAccountPolicyKey(account: Pick<AccountMetadataV3, "accountId" | "email">, _index?: number): string;
16
+ export declare function getAccountPolicyKey(account: Pick<AccountMetadataV3, "accountId" | "email"> & {
17
+ refreshToken?: string | null;
18
+ }, _index?: number): string;
17
19
  export declare function loadAccountPolicyStore(): Promise<AccountPolicyStore>;
18
20
  export declare function saveAccountPolicyStore(store: AccountPolicyStore): Promise<void>;
19
21
  export declare function upsertAccountPolicy(store: AccountPolicyStore, accountKey: string, mutate: (policy: AccountPolicy) => void, now?: number): AccountPolicy;
@@ -1 +1 @@
1
- {"version":3,"file":"account-policy.d.ts","sourceRoot":"","sources":["../../lib/account-policy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAItD,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC;AAkFD,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,OAAO,CAAC,EACvD,MAAM,CAAC,EAAE,MAAM,GACb,MAAM,CAMR;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAa1E;AAED,wBAAsB,sBAAsB,CAC3C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,EACvC,GAAG,SAAa,GACd,aAAa,CAgBf;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D"}
1
+ {"version":3,"file":"account-policy.d.ts","sourceRoot":"","sources":["../../lib/account-policy.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAItD,MAAM,WAAW,aAAa;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACxC;AAkFD,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,OAAO,CAAC,GAAG;IACzD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,EACD,MAAM,CAAC,EAAE,MAAM,GACb,MAAM,CAmBR;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAa1E;AAED,wBAAsB,sBAAsB,CAC3C,KAAK,EAAE,kBAAkB,GACvB,OAAO,CAAC,IAAI,CAAC,CAsCf;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,kBAAkB,EACzB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,EACvC,GAAG,SAAa,GACd,aAAa,CAgBf;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEtE;AAED,wBAAgB,oCAAoC,IAAI,IAAI,CAE3D"}
@@ -81,9 +81,22 @@ export function getAccountPolicyPath() {
81
81
  return join(getCodexMultiAuthDir(), ACCOUNT_POLICY_FILE_NAME);
82
82
  }
83
83
  export function getAccountPolicyKey(account, _index) {
84
+ // The key must be stable across processes and independent of the account's
85
+ // slot (policy state is persisted and survives reordering), so it is derived
86
+ // from identity, never from `_index`.
87
+ //
88
+ // An account whose accountId AND email are both missing — freshly imported,
89
+ // or not yet hydrated from its token — used to hash the literal "unknown",
90
+ // which gave EVERY such account the same key: pausing, draining or tagging
91
+ // one silently applied to all of them. Fall back to the refresh token
92
+ // instead, which every account has and no two accounts share. It is
93
+ // namespaced before hashing so a token value can never collide with an
94
+ // email or accountId of the same text, and only its digest is persisted.
95
+ // Mirrors getAccountIdentityKey's `allowRefreshFallback`.
96
+ const refreshFallback = account.refreshToken?.trim();
84
97
  const identity = account.accountId?.trim() ||
85
98
  account.email?.trim().toLowerCase() ||
86
- "unknown";
99
+ (refreshFallback ? `refresh:${refreshFallback}` : "unknown");
87
100
  return `sha256:${createHash("sha256").update(identity).digest("hex")}`;
88
101
  }
89
102
  export async function loadAccountPolicyStore() {
@@ -21,6 +21,7 @@ export interface BudgetGuardEvaluation {
21
21
  requests: number;
22
22
  totalTokens: number;
23
23
  costUsd: number;
24
+ unpricedRequests: number;
24
25
  };
25
26
  limits: {
26
27
  maxRequests: number | null;
@@ -1 +1 @@
1
- {"version":3,"file":"budget-guard.d.ts","sourceRoot":"","sources":["../../lib/budget-guard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,MAAM,EAAE;QACP,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;CACF;AA6ED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAatE;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCjF;AAED,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EACrC,GAAG,SAAa,GACd,WAAW,CAOb;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,SAAa,GAAG,MAAM,CAoBnF;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,YAAY,GACnB,qBAAqB,CAsCvB;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
1
+ {"version":3,"file":"budget-guard.d.ts","sourceRoot":"","sources":["../../lib/budget-guard.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAIrD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE7D,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,gBAAgB,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,MAAM,EAAE;QACP,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;CACF;AA6ED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAatE;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsCjF;AAED,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,EACrC,GAAG,SAAa,GACd,WAAW,CAOb;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,SAAa,GAAG,MAAM,CAoBnF;AAED,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,YAAY,GACnB,qBAAqB,CA+CvB;AAED,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
@@ -175,9 +175,18 @@ export function evaluateBudgetGuard(limit, summary) {
175
175
  summary.totals.totalTokens >= limit.maxTokens) {
176
176
  reasons.push(`token limit reached (${summary.totals.totalTokens}/${limit.maxTokens})`);
177
177
  }
178
- if (typeof limit.maxCostUsd === "number" &&
179
- summary.totals.costUsd >= limit.maxCostUsd) {
180
- reasons.push(`cost limit reached (${summary.totals.costUsd.toFixed(6)}/${limit.maxCostUsd.toFixed(6)})`);
178
+ if (typeof limit.maxCostUsd === "number") {
179
+ if (summary.totals.costUsd >= limit.maxCostUsd) {
180
+ reasons.push(`cost limit reached (${summary.totals.costUsd.toFixed(6)}/${limit.maxCostUsd.toFixed(6)})`);
181
+ }
182
+ else if ((summary.totals.unpricedRequests ?? 0) > 0) {
183
+ // The window contains token usage we have no rate for, so `costUsd` is
184
+ // an under-count by an unknown amount and this limit cannot be
185
+ // evaluated. Counting unknown cost as zero is what made a cost budget
186
+ // silently unenforceable for every unpriced model; an unevaluable spend
187
+ // limit fails closed instead. Priced models are unaffected.
188
+ reasons.push(`cost limit cannot be evaluated: ${summary.totals.unpricedRequests} request(s) in this window used a model with no known price, so recorded cost (${summary.totals.costUsd.toFixed(6)}) is incomplete. Price the model in lib/usage/pricing.ts, or budget on --requests/--tokens instead of --cost.`);
189
+ }
181
190
  }
182
191
  return {
183
192
  key: limit.key,
@@ -188,6 +197,7 @@ export function evaluateBudgetGuard(limit, summary) {
188
197
  requests: summary.totals.requests,
189
198
  totalTokens: summary.totals.totalTokens,
190
199
  costUsd: summary.totals.costUsd,
200
+ unpricedRequests: summary.totals.unpricedRequests,
191
201
  },
192
202
  limits: {
193
203
  maxRequests: limit.maxRequests ?? null,
@@ -30,6 +30,14 @@ export interface ResolvedAccountWrite {
30
30
  accessToken?: string;
31
31
  expiresAt?: number;
32
32
  workspaces?: Workspace[];
33
+ /**
34
+ * Keep the saved row's `enabled` flag instead of re-enabling it. A targeted
35
+ * re-authentication only tops up credentials for a row the user named, so it
36
+ * must not pull a deliberately disabled account back into rotation behind
37
+ * their back. A plain login still re-enables: signing in is the user asking
38
+ * for that account to be usable again.
39
+ */
40
+ preserveEnabledState?: boolean;
33
41
  now: number;
34
42
  }
35
43
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"account-pool-write.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/account-pool-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,SAAS,EAAE,EACvB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAWR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,EAC/C,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,GAC/B,SAAS,EAAE,GAAG,SAAS,CAYzB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,uBAAuB,CAAC,EACzE,gBAAgB,EAAE,SAAS,EAAE,GAAG,SAAS,GACvC,MAAM,GAAG,SAAS,CAwBpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,oBAAoB,GACzB;IAAE,OAAO,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAwBrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,EAAE,oBAAoB,GACzB;IAAE,OAAO,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CA+ChE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,EAAE,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,QAAQ,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;KACtB,EACD,OAAO,EAAE;QAAE,wCAAwC,EAAE,OAAO,CAAA;KAAE,KAC1D,MAAM,GAAG,SAAS,CAAC;CACxB,GAAG;IACH,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;CACxC,CA6CA"}
1
+ {"version":3,"file":"account-pool-write.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/account-pool-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAEpE;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,uBAAuB,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAEzE;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IACvD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACxC,UAAU,EAAE,SAAS,EAAE,EACvB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,CAWR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,EAC/C,QAAQ,EAAE,SAAS,EAAE,GAAG,SAAS,GAC/B,SAAS,EAAE,GAAG,SAAS,CAYzB;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC3C,QAAQ,EAAE,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,uBAAuB,CAAC,EACzE,gBAAgB,EAAE,SAAS,EAAE,GAAG,SAAS,GACvC,MAAM,GAAG,SAAS,CAwBpB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,oBAAoB,GACzB;IAAE,OAAO,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAwBrD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,iBAAiB,EAC3B,KAAK,EAAE,oBAAoB,GACzB;IAAE,OAAO,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CA+ChE;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,EAAE,CACzB,QAAQ,EAAE,iBAAiB,EAAE,EAC7B,QAAQ,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,YAAY,CAAC,EAAE,MAAM,CAAC;KACtB,EACD,OAAO,EAAE;QAAE,wCAAwC,EAAE,OAAO,CAAA;KAAE,KAC1D,MAAM,GAAG,SAAS,CAAC;CACxB,GAAG;IACH,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAAC;CACxC,CA6CA"}
@@ -115,7 +115,7 @@ export function buildUpdatedAccount(existing, write) {
115
115
  refreshToken: write.refreshToken,
116
116
  accessToken: write.accessToken,
117
117
  expiresAt: write.expiresAt,
118
- enabled: true,
118
+ enabled: write.preserveEnabledState ? existing.enabled : true,
119
119
  authInvalidatedAt: undefined,
120
120
  authInvalidationErrorCode: undefined,
121
121
  lastUsed: write.now,
@@ -2,7 +2,9 @@ export declare function printUsage(): void;
2
2
  export type AuthLoginOptions = {
3
3
  manual: boolean;
4
4
  deviceAuth: boolean;
5
+ preserveSelection?: boolean;
5
6
  org?: string;
7
+ account?: string;
6
8
  };
7
9
  export type ParsedAuthLoginArgs = {
8
10
  ok: true;
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAsDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAgEtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/help.ts"],"names":[],"mappings":"AAIA,wBAAgB,UAAU,IAAI,IAAI,CAsDjC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,GACvC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,mBAAmB,CAuGtE;AAED,UAAU,cAAc;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC7B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,wBAAgB,cAAc,IAAI,IAAI,CAgBrC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAyD5D"}
@@ -5,7 +5,7 @@ export function printUsage() {
5
5
  "Codex Multi-Auth CLI",
6
6
  "",
7
7
  "Start here:",
8
- " codex-multi-auth login [--device-auth|--manual|--no-browser] [--org <org_id>]",
8
+ " codex-multi-auth login [--device-auth|--manual|--no-browser] [--org <org_id>] [--preserve-selection] [--account <index|email|account_id>]",
9
9
  " codex-multi-auth status [--json] (list is the same command)",
10
10
  " codex-multi-auth check (always live-probes)",
11
11
  "",
@@ -72,6 +72,33 @@ export function parseAuthLoginArgs(args) {
72
72
  options.deviceAuth = true;
73
73
  continue;
74
74
  }
75
+ if (arg === "--preserve-selection") {
76
+ options.preserveSelection = true;
77
+ continue;
78
+ }
79
+ if (arg === "--account" || arg?.startsWith("--account=")) {
80
+ let value;
81
+ if (arg === "--account") {
82
+ value = args[i + 1];
83
+ i += 1;
84
+ }
85
+ else {
86
+ value = arg.slice("--account=".length);
87
+ }
88
+ const trimmed = value?.trim();
89
+ if (!trimmed || trimmed.startsWith("--")) {
90
+ return {
91
+ ok: false,
92
+ reason: "error",
93
+ message: "Missing value for --account. Usage: codex-multi-auth login --account <index|email|account_id>",
94
+ };
95
+ }
96
+ options.account = trimmed;
97
+ // A targeted re-auth must never select the refreshed account as a
98
+ // side effect, even if the caller omits the redundant safety flag.
99
+ options.preserveSelection = true;
100
+ continue;
101
+ }
75
102
  if (arg === "--org" || arg?.startsWith("--org=")) {
76
103
  // Bind this login to a specific workspace/org id (issue #491). Reuses
77
104
  // the CODEX_AUTH_ACCOUNT_ID override mechanism so the same email's
@@ -112,6 +139,16 @@ export function parseAuthLoginArgs(args) {
112
139
  message: `Cannot combine --device-auth with ${conflict}`,
113
140
  };
114
141
  }
142
+ // --org rebinds a login to a different workspace, which is exactly the
143
+ // identity change a targeted re-auth is built to refuse. Rejecting the pair
144
+ // up front beats failing after the user has already completed a sign-in.
145
+ if (options.account && options.org) {
146
+ return {
147
+ ok: false,
148
+ reason: "error",
149
+ message: "Cannot combine --account with --org. Re-authenticate the saved account on its own workspace, or register the other workspace with --org on its own.",
150
+ };
151
+ }
115
152
  return { ok: true, options };
116
153
  }
117
154
  export function printBestUsage() {
@@ -1,3 +1,4 @@
1
+ import { type AccountMetadataV3, type AccountStorageV3 } from "../storage.js";
1
2
  import { type RepairCommandDeps } from "./repair-commands.js";
2
3
  /**
3
4
  * Interactive control loop for the `login` command: the account dashboard menu
@@ -13,5 +14,7 @@ export interface LoginFlowDeps {
13
14
  createRepairCommandDeps(): RepairCommandDeps;
14
15
  }
15
16
  /** @internal */
17
+ export declare function resolveLoginAccountTarget(storage: AccountStorageV3, identifier: string): AccountMetadataV3 | null;
18
+ /** @internal */
16
19
  export declare function runAuthLogin(args: string[], deps: LoginFlowDeps): Promise<number>;
17
20
  //# sourceMappingURL=login-flow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-flow.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-flow.ts"],"names":[],"mappings":"AAmDA,OAAO,EACN,KAAK,iBAAiB,EAGtB,MAAM,sBAAsB,CAAC;AAM9B;;;;;;;GAOG;AAEH,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC7B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,uBAAuB,IAAI,iBAAiB,CAAC;CAC7C;AAkCD,gBAAgB;AAChB,wBAAsB,YAAY,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
1
+ {"version":3,"file":"login-flow.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-flow.ts"],"names":[],"mappings":"AAQA,OAAO,EACN,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EAUrB,MAAM,eAAe,CAAC;AAmCvB,OAAO,EACN,KAAK,iBAAiB,EAGtB,MAAM,sBAAsB,CAAC;AAM9B;;;;;;;GAOG;AAEH,gBAAgB;AAChB,MAAM,WAAW,aAAa;IAC7B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,uBAAuB,IAAI,iBAAiB,CAAC;CAC7C;AAkCD,gBAAgB;AAChB,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,MAAM,GAChB,iBAAiB,GAAG,IAAI,CAkB1B;AAED,gBAAgB;AAChB,wBAAsB,YAAY,CACjC,IAAI,EAAE,MAAM,EAAE,EACd,IAAI,EAAE,aAAa,GACjB,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
@@ -1,3 +1,4 @@
1
+ import { sanitizeEmail } from "../accounts.js";
1
2
  import { isBrowserLaunchSuppressed } from "../auth/browser.js";
2
3
  import { promptAddAnotherAccount, promptLoginMode } from "../cli.js";
3
4
  import { ACCOUNT_LIMITS } from "../constants.js";
@@ -6,6 +7,7 @@ import { createLogger } from "../logger.js";
6
7
  import { loadQuotaCache } from "../quota-cache.js";
7
8
  import { resolveActiveIndex } from "../runtime/account-status.js";
8
9
  import { clearAccounts, formatStorageErrorHint, getNamedBackups, loadAccounts, loadFlaggedAccounts, restoreAccountsFromBackup, setStoragePath, StorageError, } from "../storage.js";
10
+ import { CodexValidationError } from "../errors.js";
9
11
  import { UI_COPY } from "../ui/ui-copy.js";
10
12
  import { confirm } from "../ui/confirm.js";
11
13
  import { stylePromptText } from "./formatters/index.js";
@@ -38,6 +40,23 @@ async function clearAccountsAndReset() {
38
40
  await clearAccounts();
39
41
  }
40
42
  /** @internal */
43
+ export function resolveLoginAccountTarget(storage, identifier) {
44
+ if (/^[1-9]\d*$/.test(identifier)) {
45
+ const byIndex = storage.accounts[Number(identifier) - 1];
46
+ return byIndex ?? null;
47
+ }
48
+ const byAccountId = storage.accounts.filter((account) => account.accountId === identifier);
49
+ if (byAccountId.length === 1)
50
+ return byAccountId[0] ?? null;
51
+ if (byAccountId.length > 1)
52
+ return null;
53
+ const email = sanitizeEmail(identifier);
54
+ if (!email)
55
+ return null;
56
+ const byEmail = storage.accounts.filter((account) => sanitizeEmail(account.email) === email);
57
+ return byEmail.length === 1 ? (byEmail[0] ?? null) : null;
58
+ }
59
+ /** @internal */
41
60
  export async function runAuthLogin(args, deps) {
42
61
  const parsedArgs = parseAuthLoginArgs(args);
43
62
  if (!parsedArgs.ok) {
@@ -197,7 +216,10 @@ async function runAuthLoginFlow(loginOptions, deps) {
197
216
  // (--device-auth, --manual, --no-browser), they want to add a new account
198
217
  // directly. Skipping the dashboard menu keeps `login --device-auth`
199
218
  // usable from scripts and matches the documented behavior of the help text.
200
- const explicitSignInMode = loginOptions.deviceAuth || loginOptions.manual;
219
+ const explicitSignInMode = loginOptions.deviceAuth ||
220
+ loginOptions.manual ||
221
+ loginOptions.preserveSelection ||
222
+ Boolean(loginOptions.account);
201
223
  loginFlow: while (true) {
202
224
  const existingStorage = await loadAccounts();
203
225
  if (!explicitSignInMode &&
@@ -210,6 +232,23 @@ async function runAuthLoginFlow(loginOptions, deps) {
210
232
  }
211
233
  const refreshedStorage = await loadAccounts();
212
234
  let existingCount = refreshedStorage?.accounts.length ?? 0;
235
+ const expectedAccount = loginOptions.account
236
+ ? refreshedStorage
237
+ ? resolveLoginAccountTarget(refreshedStorage, loginOptions.account)
238
+ : null
239
+ : undefined;
240
+ if (loginOptions.account && !expectedAccount) {
241
+ console.error("The requested Codex account is missing or ambiguous. No login was started.");
242
+ return 1;
243
+ }
244
+ // Positional hint for the write, re-verified (never trusted) inside the
245
+ // storage transaction. resolveLoginAccountTarget hands back a row out of
246
+ // this very array, so indexOf is an exact identity lookup.
247
+ const expectedAccountIndex = expectedAccount && refreshedStorage
248
+ ? refreshedStorage.accounts.indexOf(expectedAccount)
249
+ : -1;
250
+ // A non-empty pool already forces a fresh sign-in, and a resolved target
251
+ // implies the pool was non-empty, so it adds no condition of its own.
213
252
  let forceNewLogin = existingCount > 0;
214
253
  let onboardingBackupDiscoveryWarning = null;
215
254
  const loadNamedBackupsForOnboarding = async () => {
@@ -290,6 +329,13 @@ async function runAuthLoginFlow(loginOptions, deps) {
290
329
  parsed: targetIndex + 1,
291
330
  switchReason: "restore",
292
331
  preserveActiveIndexByFamily: true,
332
+ // A restore replaces the whole account list, so every
333
+ // index moves. Session affinity remembers accounts BY
334
+ // INDEX, so without this a live proxy keeps routing
335
+ // in-flight sessions to whatever now occupies the old
336
+ // slot. `switch`/`best` already bump for a single
337
+ // move; a restore moves everything. See issue #474.
338
+ bumpAffinityGeneration: true,
293
339
  });
294
340
  console.log(UI_COPY.oauth.restoreBackupLoaded(selectedBackup.fileName, restoredStorage.accounts.length));
295
341
  if (!synced) {
@@ -339,9 +385,31 @@ async function runAuthLoginFlow(loginOptions, deps) {
339
385
  console.error(`Login failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
340
386
  return 1;
341
387
  }
342
- const resolved = resolveAccountSelection(tokenResult, loginOptions.org);
343
- const persistOutcome = await persistAccountPool([resolved], false);
344
- await syncSelectionToCodex(resolved);
388
+ const resolved = resolveAccountSelection(tokenResult, loginOptions.org, expectedAccount?.accountId);
389
+ let persistResult;
390
+ try {
391
+ persistResult = await persistAccountPool([resolved], false, {
392
+ preserveSelection: loginOptions.preserveSelection,
393
+ expectedAccount: expectedAccount ?? undefined,
394
+ expectedAccountIndex: expectedAccountIndex >= 0 ? expectedAccountIndex : undefined,
395
+ });
396
+ }
397
+ catch (error) {
398
+ if (error instanceof CodexValidationError) {
399
+ console.error(`Re-authentication failed: ${error.message}`);
400
+ return 1;
401
+ }
402
+ throw error;
403
+ }
404
+ // Publish to ~/.codex/auth.json exactly when this write owns the
405
+ // selection. Skipping the sync whenever --preserve-selection was passed
406
+ // stranded the native CLI on the expired tokens of the very account the
407
+ // user had just refreshed, and nothing downstream repaired it: the
408
+ // drift check compares identity, not tokens, so a same-identity refresh
409
+ // never looks like drift.
410
+ if (!persistResult || persistResult.isActiveAccount) {
411
+ await syncSelectionToCodex(resolved);
412
+ }
345
413
  const latestStorage = await loadAccounts();
346
414
  const count = latestStorage?.accounts.length ?? 1;
347
415
  existingCount = count;
@@ -350,16 +418,22 @@ async function runAuthLoginFlow(loginOptions, deps) {
350
418
  // Only claim a new saved slot when one was actually appended. A
351
419
  // same-email login that maps onto an existing entry updates or
352
420
  // rebinds it instead of growing the pool (issue #512).
353
- const outcomeMessage = persistOutcome === "rebound"
421
+ const outcomeMessage = persistResult?.outcome === "rebound"
354
422
  ? `Rebound workspace for existing account. Total: ${count}`
355
- : persistOutcome === "updated"
423
+ : persistResult?.outcome === "updated"
356
424
  ? `Updated existing account. Total: ${count}`
357
425
  : `Added account. Total: ${count}`;
358
426
  console.log(outcomeMessage);
427
+ if (persistResult && !persistResult.accountEnabled) {
428
+ console.log(`Account ${persistResult.accountIndex + 1} is still disabled and stays out of rotation. Re-enable it from the manage menu in "codex-multi-auth login".`);
429
+ }
359
430
  console.log("Next steps:");
360
431
  console.log(" codex-multi-auth status Check that the wrapper is active.");
361
432
  console.log(" codex-multi-auth check Confirm your saved accounts look healthy.");
362
433
  console.log(" codex-multi-auth list Review saved accounts before switching.");
434
+ if (loginOptions.preserveSelection) {
435
+ return 0;
436
+ }
363
437
  if (count >= ACCOUNT_LIMITS.MAX_ACCOUNTS) {
364
438
  console.log(`Reached maximum account limit (${ACCOUNT_LIMITS.MAX_ACCOUNTS}).`);
365
439
  // Same reasoning as the addAnother=false branch below: in
@@ -1 +1 @@
1
- {"version":3,"file":"login-menu-actions.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAEN,KAAK,gBAAgB,EAIrB,KAAK,kBAAkB,EAGvB,MAAM,eAAe,CAAC;AASvB,OAAO,EACN,KAAK,eAAe,EAKpB,MAAM,kBAAkB,CAAC;AAG1B;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7D,gBAAgB;AAChB,wBAAsB,qBAAqB,CAC1C,YAAY,EAAE,kBAAkB,GAAG,IAAI,EACvC,sBAAsB,GAAE,MAAM,GAAG,IAAW,GAC1C,OAAO,CAAC,eAAe,CAAC,CA6D1B;AAED,gBAAgB;AAChB,wBAAsB,uBAAuB,CAC5C,YAAY,EAAE,kBAAkB,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CA0C5B;AAED,gBAAgB;AAChB,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,kBAAkB,EAAE,GAC3B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkCpC;AA8GD,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CA0Hf"}
1
+ {"version":3,"file":"login-menu-actions.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-menu-actions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAEN,KAAK,gBAAgB,EAKrB,KAAK,kBAAkB,EAGvB,MAAM,eAAe,CAAC;AASvB,OAAO,EACN,KAAK,eAAe,EAKpB,MAAM,kBAAkB,CAAC;AAG1B;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7D,gBAAgB;AAChB,wBAAsB,qBAAqB,CAC1C,YAAY,EAAE,kBAAkB,GAAG,IAAI,EACvC,sBAAsB,GAAE,MAAM,GAAG,IAAW,GAC1C,OAAO,CAAC,eAAe,CAAC,CA6D1B;AAED,gBAAgB;AAChB,wBAAsB,uBAAuB,CAC5C,YAAY,EAAE,kBAAkB,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CA0C5B;AAED,gBAAgB;AAChB,wBAAsB,2BAA2B,CAChD,OAAO,EAAE,kBAAkB,EAAE,GAC3B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAkCpC;AA8GD,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,GACrD,OAAO,CAAC,IAAI,CAAC,CAkKf"}
@@ -1,7 +1,8 @@
1
1
  import { stdin as input, stdout as output } from "node:process";
2
2
  import { sanitizeEmail } from "../accounts.js";
3
+ import { CodexValidationError } from "../errors.js";
3
4
  import { MODEL_FAMILIES } from "../prompts/codex.js";
4
- import { findMatchingAccountIndex, loadAccounts, reconcilePinnedAccountIndex, setStoragePath, withAccountStorageTransaction, } from "../storage.js";
5
+ import { bumpStorageAffinityGeneration, findMatchingAccountIndex, loadAccounts, reconcilePinnedAccountIndex, setStoragePath, withAccountStorageTransaction, } from "../storage.js";
5
6
  import { UI_COPY } from "../ui/ui-copy.js";
6
7
  import { getUiRuntimeOptions } from "../ui/runtime.js";
7
8
  import { select } from "../ui/select.js";
@@ -249,6 +250,12 @@ export async function handleManageAction(storage, menuResult) {
249
250
  nextStorage.accounts.splice(nextIndex, 1);
250
251
  resetManageActionSelection(nextStorage, nextIndex);
251
252
  nextStorage.pinnedAccountIndex = reconcilePinnedAccountIndex(pinnedAccount, nextStorage.accounts);
253
+ // The splice shifts every index above the removed slot, and session
254
+ // affinity remembers accounts BY INDEX. Without a generation bump a
255
+ // live proxy keeps routing in-flight sessions to the account that
256
+ // slid into the old slot. Reconciling the pin is not enough — the
257
+ // affinity map is separate state. See issue #474.
258
+ bumpStorageAffinityGeneration(nextStorage);
252
259
  await persist(nextStorage);
253
260
  replaceManageActionStorage(storage, nextStorage);
254
261
  deleted = true;
@@ -306,9 +313,38 @@ export async function handleManageAction(storage, menuResult) {
306
313
  console.error(`Refresh failed: ${tokenResult.message ?? tokenResult.reason ?? "unknown error"}`);
307
314
  return;
308
315
  }
309
- const resolved = resolveAccountSelection(tokenResult);
310
- await persistAccountPool([resolved], false);
311
- await syncSelectionToCodex(resolved);
312
- console.log(`Refreshed account ${idx + 1}.`);
316
+ const resolved = resolveAccountSelection(tokenResult, undefined, existing.accountId);
317
+ let persistResult;
318
+ try {
319
+ persistResult = await persistAccountPool([resolved], false, {
320
+ preserveSelection: true,
321
+ expectedAccount: existing,
322
+ expectedAccountIndex: idx,
323
+ });
324
+ }
325
+ catch (error) {
326
+ // A mismatched sign-in (the user picked a different ChatGPT account in
327
+ // the browser) is a refusal, not a crash. Nothing between here and the
328
+ // process entrypoint catches this, so an uncaught throw would take the
329
+ // whole interactive dashboard down with a raw stack trace.
330
+ if (error instanceof CodexValidationError) {
331
+ console.error(`Refresh failed: ${error.message}`);
332
+ return;
333
+ }
334
+ throw error;
335
+ }
336
+ // Refreshing the account Codex is currently using must publish its new
337
+ // tokens; refreshing any other row must leave the selection alone.
338
+ if (!persistResult || persistResult.isActiveAccount) {
339
+ await syncSelectionToCodex(resolved);
340
+ }
341
+ // Report the row that was actually written. The pool can shift between the
342
+ // dashboard render and this transaction, so the menu index may name a
343
+ // different account by now.
344
+ const refreshedIndex = persistResult?.accountIndex ?? idx;
345
+ console.log(`Refreshed account ${refreshedIndex + 1}.`);
346
+ if (persistResult && !persistResult.accountEnabled) {
347
+ console.log(`Account ${refreshedIndex + 1} is still disabled and stays out of rotation. Toggle it from this menu to bring it back.`);
348
+ }
313
349
  }
314
350
  }
@@ -1,4 +1,5 @@
1
1
  import { type Workspace } from "../accounts.js";
2
+ import { type AccountMetadataV3 } from "../storage.js";
2
3
  import type { AccountIdSource, TokenResult } from "../types.js";
3
4
  import { type AccountPoolWriteOutcome } from "./account-pool-write.js";
4
5
  /**
@@ -35,7 +36,7 @@ export declare function isAbortError(error: unknown): boolean;
35
36
  *
36
37
  * @internal
37
38
  */
38
- export declare function resolveAccountSelection(tokens: TokenSuccess, orgOverride?: string): TokenSuccessWithAccount;
39
+ export declare function resolveAccountSelection(tokens: TokenSuccess, orgOverride?: string, targetAccountId?: string): TokenSuccessWithAccount;
39
40
  /** @internal */
40
41
  export type OAuthSignInMode = "browser" | "manual" | "device" | "restore-backup" | "cancel";
41
42
  /** @internal */
@@ -49,7 +50,42 @@ export declare function runSignInFlow(forceNewLogin: boolean, signInMode: Extrac
49
50
  /** @internal */
50
51
  export type PersistAccountPoolOutcome = AccountPoolWriteOutcome;
51
52
  /** @internal */
52
- export declare function persistAccountPool(results: TokenSuccessWithAccount[], replaceAll: boolean): Promise<PersistAccountPoolOutcome | null>;
53
+ export interface PersistAccountPoolResult {
54
+ /** How the pool absorbed this login (appended, refreshed, or rebound). */
55
+ outcome: PersistAccountPoolOutcome;
56
+ /** Index of the row the login actually wrote. */
57
+ accountIndex: number;
58
+ /** Global selection index after the write. */
59
+ activeIndex: number;
60
+ /**
61
+ * Whether the written row is the one plain `codex` will use. Callers gate the
62
+ * ~/.codex/auth.json sync on this: refreshing the active account must publish
63
+ * its new tokens, and refreshing any other row must not steal the selection.
64
+ */
65
+ isActiveAccount: boolean;
66
+ /** Whether the written row is enabled for rotation after the write. */
67
+ accountEnabled: boolean;
68
+ }
69
+ /** @internal */
70
+ export type ExpectedLoginAccount = Pick<AccountMetadataV3, "recordId" | "accountId" | "email" | "refreshToken">;
71
+ /** @internal */
72
+ export interface PersistAccountPoolOptions {
73
+ /** Keep every global/model-family selection on its pre-login identity. */
74
+ preserveSelection?: boolean;
75
+ /** Refuse the write unless the OAuth result is this saved account. */
76
+ expectedAccount?: ExpectedLoginAccount;
77
+ /**
78
+ * Position {@link expectedAccount} occupied when the caller resolved it.
79
+ * Treated as a hint that is verified against the row actually sitting there,
80
+ * never trusted: the pool can change between the caller's read and this
81
+ * transaction. It exists so an unambiguous positional request such as
82
+ * `login --account 2` stays deterministic when several saved rows share an
83
+ * identity, which identity matching alone cannot tell apart.
84
+ */
85
+ expectedAccountIndex?: number;
86
+ }
87
+ /** @internal */
88
+ export declare function persistAccountPool(results: TokenSuccessWithAccount[], replaceAll: boolean, options?: PersistAccountPoolOptions): Promise<PersistAccountPoolResult | null>;
53
89
  /** @internal */
54
90
  export declare function syncSelectionToCodex(tokens: TokenSuccessWithAccount): Promise<void>;
55
91
  //# sourceMappingURL=login-oauth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login-oauth.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-oauth.ts"],"names":[],"mappings":"AAEA,OAAO,EAON,KAAK,SAAS,EACd,MAAM,gBAAgB,CAAC;AAsBxB,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EACN,KAAK,uBAAuB,EAG5B,MAAM,yBAAyB,CAAC;AAOjC;;;;;GAKG;AAEH,gBAAgB;AAChB,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAC;AACrE,gBAAgB;AAChB,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CACzB,CAAC;AAIF,gBAAgB;AAChB,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,GAC/C,OAAO,CAGT;AAED,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,YAAY,EACpB,WAAW,CAAC,EAAE,MAAM,GAClB,uBAAuB,CA+DzB;AA0FD,gBAAgB;AAChB,MAAM,MAAM,eAAe,GACxB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,gBAAgB,GAChB,QAAQ,CAAC;AACZ,gBAAgB;AAChB,MAAM,MAAM,iBAAiB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,gBAAgB;AAChB,wBAAsB,YAAY,CACjC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,CAAC,GACxD,OAAO,CAAC,WAAW,CAAC,CA6ItB;AAED,gBAAgB;AAChB,wBAAsB,aAAa,CAClC,aAAa,EAAE,OAAO,EACtB,UAAU,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC,EACrE,OAAO,GAAE,iBAAsB,GAC7B,OAAO,CAAC,WAAW,CAAC,CAetB;AAED,gBAAgB;AAChB,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,CAAC;AAEhE,gBAAgB;AAChB,wBAAsB,kBAAkB,CACvC,OAAO,EAAE,uBAAuB,EAAE,EAClC,UAAU,EAAE,OAAO,GACjB,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC,CAuD3C;AAED,gBAAgB;AAChB,wBAAsB,oBAAoB,CACzC,MAAM,EAAE,uBAAuB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
1
+ {"version":3,"file":"login-oauth.d.ts","sourceRoot":"","sources":["../../../lib/codex-manager/login-oauth.ts"],"names":[],"mappings":"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"}