opencode-copilot-account-switcher 0.13.2 → 0.13.3

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.
@@ -0,0 +1,32 @@
1
+ import type { CodexAccountEntry, CodexStoreFile } from "./codex-store.js";
2
+ export type CodexRecoveryCandidate = {
3
+ name: string;
4
+ entry: CodexAccountEntry;
5
+ };
6
+ export type CodexSetAuthInput = {
7
+ path: {
8
+ id: string;
9
+ };
10
+ body: {
11
+ type: "oauth";
12
+ refresh?: string;
13
+ access?: string;
14
+ expires?: number;
15
+ accountId?: string;
16
+ };
17
+ };
18
+ export type RecoverInvalidCodexAccountResult = {
19
+ removed: string;
20
+ replacement?: string;
21
+ switched: boolean;
22
+ weekRecoveryOnly: boolean;
23
+ noCandidates: boolean;
24
+ store: CodexStoreFile;
25
+ };
26
+ export declare function getCodexDisplayName(entry: CodexAccountEntry | undefined, fallbackName: string): string;
27
+ export declare function sortCodexRecoveryCandidates(candidates: CodexRecoveryCandidate[]): CodexRecoveryCandidate[];
28
+ export declare function recoverInvalidCodexAccount(input: {
29
+ store: CodexStoreFile;
30
+ invalidAccountName: string;
31
+ setAuth?: (next: CodexSetAuthInput) => Promise<unknown>;
32
+ }): Promise<RecoverInvalidCodexAccountResult>;
@@ -0,0 +1,106 @@
1
+ function pickPositiveNumber(value) {
2
+ if (typeof value !== "number" || !Number.isFinite(value))
3
+ return 0;
4
+ return value > 0 ? value : 0;
5
+ }
6
+ function pickFiniteNumber(value) {
7
+ if (typeof value !== "number" || !Number.isFinite(value))
8
+ return undefined;
9
+ return value;
10
+ }
11
+ function getWeekRemaining(entry) {
12
+ return pickPositiveNumber(entry.snapshot?.usageWeek?.remaining);
13
+ }
14
+ function get5hRemaining(entry) {
15
+ return pickPositiveNumber(entry.snapshot?.usage5h?.remaining);
16
+ }
17
+ function compareResetAt(a, b) {
18
+ const aMissing = a === undefined;
19
+ const bMissing = b === undefined;
20
+ if (aMissing && bMissing)
21
+ return 0;
22
+ if (aMissing)
23
+ return 1;
24
+ if (bMissing)
25
+ return -1;
26
+ if (a === b)
27
+ return 0;
28
+ return a < b ? -1 : 1;
29
+ }
30
+ export function getCodexDisplayName(entry, fallbackName) {
31
+ return entry?.workspaceName
32
+ ?? entry?.name
33
+ ?? entry?.email
34
+ ?? entry?.accountId
35
+ ?? fallbackName;
36
+ }
37
+ export function sortCodexRecoveryCandidates(candidates) {
38
+ const withIndex = candidates.map((candidate, index) => ({ candidate, index }));
39
+ const weekPositive = withIndex.filter(({ candidate }) => getWeekRemaining(candidate.entry) > 0);
40
+ const pool = weekPositive.length > 0 ? weekPositive : withIndex;
41
+ const has5hPositiveInPool = pool.some(({ candidate }) => get5hRemaining(candidate.entry) > 0);
42
+ return pool
43
+ .slice()
44
+ .sort((a, b) => {
45
+ if (weekPositive.length > 0 && has5hPositiveInPool) {
46
+ const a5h = get5hRemaining(a.candidate.entry);
47
+ const b5h = get5hRemaining(b.candidate.entry);
48
+ if (a5h > 0 && b5h <= 0)
49
+ return -1;
50
+ if (a5h <= 0 && b5h > 0)
51
+ return 1;
52
+ const by5hResetAt = compareResetAt(pickFiniteNumber(a.candidate.entry.snapshot?.usage5h?.resetAt), pickFiniteNumber(b.candidate.entry.snapshot?.usage5h?.resetAt));
53
+ if (by5hResetAt !== 0)
54
+ return by5hResetAt;
55
+ }
56
+ const byWeekResetAt = compareResetAt(pickFiniteNumber(a.candidate.entry.snapshot?.usageWeek?.resetAt), pickFiniteNumber(b.candidate.entry.snapshot?.usageWeek?.resetAt));
57
+ if (byWeekResetAt !== 0)
58
+ return byWeekResetAt;
59
+ return a.index - b.index;
60
+ })
61
+ .map(({ candidate }) => candidate);
62
+ }
63
+ export async function recoverInvalidCodexAccount(input) {
64
+ const store = {
65
+ ...input.store,
66
+ accounts: { ...input.store.accounts },
67
+ };
68
+ delete store.accounts[input.invalidAccountName];
69
+ const candidates = Object.entries(store.accounts).map(([name, entry]) => ({
70
+ name,
71
+ entry,
72
+ }));
73
+ const sorted = sortCodexRecoveryCandidates(candidates);
74
+ const replacement = sorted[0];
75
+ const noCandidates = !replacement;
76
+ const switched = Boolean(replacement);
77
+ const weekRecoveryOnly = Boolean(replacement
78
+ && getWeekRemaining(replacement.entry) > 0
79
+ && get5hRemaining(replacement.entry) <= 0);
80
+ if (replacement) {
81
+ store.active = replacement.name;
82
+ }
83
+ else if (store.active === input.invalidAccountName) {
84
+ delete store.active;
85
+ }
86
+ if (replacement && input.setAuth) {
87
+ await input.setAuth({
88
+ path: { id: "openai" },
89
+ body: {
90
+ type: "oauth",
91
+ refresh: replacement.entry.refresh,
92
+ access: replacement.entry.access,
93
+ expires: replacement.entry.expires,
94
+ accountId: replacement.entry.accountId,
95
+ },
96
+ });
97
+ }
98
+ return {
99
+ removed: input.invalidAccountName,
100
+ replacement: replacement?.name,
101
+ switched,
102
+ weekRecoveryOnly,
103
+ noCandidates,
104
+ store,
105
+ };
106
+ }
@@ -7,11 +7,33 @@ export type TokenResponse = {
7
7
  export type IdTokenClaims = {
8
8
  chatgpt_account_id?: string;
9
9
  organizations?: Array<{
10
- id: string;
10
+ id?: string;
11
+ name?: string;
12
+ display_name?: string;
13
+ workspace_name?: string;
14
+ slug?: string;
11
15
  }>;
16
+ organization?: {
17
+ id?: string;
18
+ name?: string;
19
+ display_name?: string;
20
+ workspace_name?: string;
21
+ slug?: string;
22
+ };
23
+ workspace?: {
24
+ id?: string;
25
+ name?: string;
26
+ display_name?: string;
27
+ workspace_name?: string;
28
+ slug?: string;
29
+ };
30
+ workspace_name?: string;
12
31
  email?: string;
13
32
  "https://api.openai.com/auth"?: {
14
33
  chatgpt_account_id?: string;
34
+ workspace_name?: string;
35
+ workspace_id?: string;
36
+ organization_id?: string;
15
37
  };
16
38
  };
17
39
  export type CodexOAuthAccount = {
@@ -20,6 +42,7 @@ export type CodexOAuthAccount = {
20
42
  expires?: number;
21
43
  accountId?: string;
22
44
  email?: string;
45
+ workspaceName?: string;
23
46
  };
24
47
  type OAuthMode = "browser" | "headless";
25
48
  type RunCodexOAuthInput = {
@@ -35,5 +58,7 @@ type RunCodexOAuthInput = {
35
58
  export declare function parseJwtClaims(token: string): IdTokenClaims | undefined;
36
59
  export declare function extractAccountIdFromClaims(claims: IdTokenClaims): string | undefined;
37
60
  export declare function extractAccountId(tokens: TokenResponse): string | undefined;
61
+ export declare function extractWorkspaceNameFromClaims(claims: IdTokenClaims): string | undefined;
62
+ export declare function extractWorkspaceName(tokens: TokenResponse): string | undefined;
38
63
  export declare function runCodexOAuth(input?: RunCodexOAuthInput): Promise<CodexOAuthAccount | undefined>;
39
64
  export {};
@@ -82,6 +82,32 @@ export function extractAccountId(tokens) {
82
82
  const claims = parseJwtClaims(tokens.access_token);
83
83
  return claims ? extractAccountIdFromClaims(claims) : undefined;
84
84
  }
85
+ function pickWorkspaceLikeLabel(input) {
86
+ if (!input)
87
+ return undefined;
88
+ return input.workspace_name ?? input.display_name ?? input.name ?? input.slug ?? input.id;
89
+ }
90
+ export function extractWorkspaceNameFromClaims(claims) {
91
+ return (claims.workspace_name
92
+ || claims["https://api.openai.com/auth"]?.workspace_name
93
+ || claims["https://api.openai.com/auth"]?.workspace_id
94
+ || claims["https://api.openai.com/auth"]?.organization_id
95
+ || pickWorkspaceLikeLabel(claims.workspace)
96
+ || pickWorkspaceLikeLabel(claims.organization)
97
+ || pickWorkspaceLikeLabel(claims.organizations?.[0]));
98
+ }
99
+ export function extractWorkspaceName(tokens) {
100
+ if (tokens.id_token) {
101
+ const claims = parseJwtClaims(tokens.id_token);
102
+ const workspaceName = claims && extractWorkspaceNameFromClaims(claims);
103
+ if (workspaceName)
104
+ return workspaceName;
105
+ }
106
+ if (!tokens.access_token)
107
+ return undefined;
108
+ const claims = parseJwtClaims(tokens.access_token);
109
+ return claims ? extractWorkspaceNameFromClaims(claims) : undefined;
110
+ }
85
111
  function extractEmail(tokens) {
86
112
  if (tokens.id_token) {
87
113
  const claims = parseJwtClaims(tokens.id_token);
@@ -291,12 +317,14 @@ function normalizeTokens(tokens, now) {
291
317
  const access = tokens.access_token;
292
318
  if (!refresh && !access)
293
319
  return undefined;
320
+ const workspaceName = extractWorkspaceName(tokens);
294
321
  return {
295
322
  refresh,
296
323
  access,
297
324
  expires: now() + (tokens.expires_in ?? 3600) * 1000,
298
325
  accountId: extractAccountId(tokens),
299
326
  email: extractEmail(tokens),
327
+ ...(workspaceName ? { workspaceName } : {}),
300
328
  };
301
329
  }
302
330
  export async function runCodexOAuth(input = {}) {
@@ -1,6 +1,7 @@
1
1
  import { resolveCodexAuthSource } from "./codex-auth-source.js";
2
2
  import { fetchCodexStatus } from "./codex-status-fetcher.js";
3
3
  import { getActiveCodexAccount, normalizeCodexStore, readCodexStore, writeCodexStore, } from "./codex-store.js";
4
+ import { getCodexDisplayName, recoverInvalidCodexAccount } from "./codex-invalid-account.js";
4
5
  import { readAuth } from "./store.js";
5
6
  export class CodexStatusCommandHandledError extends Error {
6
7
  constructor() {
@@ -44,6 +45,12 @@ function ratio(remaining, entitlement) {
44
45
  function value(value) {
45
46
  return value === undefined ? "n/a" : String(value);
46
47
  }
48
+ function pickWorkspaceLabel(input) {
49
+ return input.workspaceName
50
+ ?? input.name
51
+ ?? input.email
52
+ ?? input.accountId;
53
+ }
47
54
  function renderWindow(label, window) {
48
55
  if (window.entitlement === 100 && window.remaining !== undefined) {
49
56
  return `${label}: ${window.remaining}% left`;
@@ -51,16 +58,16 @@ function renderWindow(label, window) {
51
58
  return `${label}: ${ratio(window.remaining, window.entitlement)}`;
52
59
  }
53
60
  function renderStatus(status) {
61
+ const identity = status.identity;
54
62
  return [
55
- "Codex status updated.",
56
- "[identity]",
57
- `account: ${value(status.identity.accountId)}`,
58
- `email: ${value(status.identity.email)}`,
59
- `plan: ${value(status.identity.plan)}`,
60
- "[usage]",
63
+ `账号: ${value(identity.accountId ?? identity.email)}`,
64
+ `Workspace: ${value(pickWorkspaceLabel({
65
+ workspaceName: identity.workspaceName,
66
+ email: identity.email,
67
+ accountId: identity.accountId,
68
+ }))}`,
61
69
  renderWindow("5h", status.windows.primary),
62
70
  renderWindow("week", status.windows.secondary),
63
- `credits: ${ratio(status.credits.remaining, status.credits.total)}`,
64
71
  ].join("\n");
65
72
  }
66
73
  function renderCachedStatus(store) {
@@ -68,14 +75,15 @@ function renderCachedStatus(store) {
68
75
  const entry = active?.entry;
69
76
  const snapshot = entry?.snapshot;
70
77
  return [
71
- "[identity]",
72
- `account: ${value(entry?.accountId ?? active?.name)}`,
73
- `email: ${value(entry?.email)}`,
74
- `plan: ${value(snapshot?.plan)}`,
75
- "[usage]",
78
+ `账号: ${value(entry?.accountId ?? active?.name ?? entry?.email)}`,
79
+ `Workspace: ${value(pickWorkspaceLabel({
80
+ workspaceName: entry?.workspaceName,
81
+ name: entry?.name ?? active?.name,
82
+ email: entry?.email,
83
+ accountId: entry?.accountId,
84
+ }))}`,
76
85
  renderWindow("5h", snapshot?.usage5h ?? {}),
77
- "week: n/a",
78
- "credits: n/a",
86
+ renderWindow("week", snapshot?.usageWeek ?? {}),
79
87
  ].join("\n");
80
88
  }
81
89
  function getCachedAccountForSource(store, input) {
@@ -96,14 +104,15 @@ function renderCachedStatusForAccount(store, input) {
96
104
  const entry = active?.entry;
97
105
  const snapshot = entry?.snapshot;
98
106
  return [
99
- "[identity]",
100
- `account: ${value(entry?.accountId ?? active?.name)}`,
101
- `email: ${value(entry?.email)}`,
102
- `plan: ${value(snapshot?.plan)}`,
103
- "[usage]",
107
+ `账号: ${value(entry?.accountId ?? active?.name ?? entry?.email)}`,
108
+ `Workspace: ${value(pickWorkspaceLabel({
109
+ workspaceName: entry?.workspaceName,
110
+ name: entry?.name ?? active?.name,
111
+ email: entry?.email,
112
+ accountId: entry?.accountId,
113
+ }))}`,
104
114
  renderWindow("5h", snapshot?.usage5h ?? {}),
105
- "week: n/a",
106
- "credits: n/a",
115
+ renderWindow("week", snapshot?.usageWeek ?? {}),
107
116
  ].join("\n");
108
117
  }
109
118
  function hasCachedStore(store) {
@@ -230,12 +239,55 @@ export async function handleCodexStatusCommand(input) {
230
239
  },
231
240
  }));
232
241
  if (!fetched.ok) {
242
+ if (fetched.error.kind === "invalid_account") {
243
+ const currentRaw = await readStore().catch(() => ({}));
244
+ const currentStore = normalizeCodexStore(currentRaw);
245
+ const invalid = getCachedAccountForSource(currentStore, { accountId: source.accountId });
246
+ const invalidName = invalid?.name ?? currentStore.active;
247
+ if (invalidName && currentStore.accounts[invalidName]) {
248
+ const recovered = await recoverInvalidCodexAccount({
249
+ store: currentStore,
250
+ invalidAccountName: invalidName,
251
+ setAuth: input.client?.auth?.set
252
+ ? async (next) => {
253
+ const authClient = input.client?.auth;
254
+ const setAuth = input.client?.auth?.set;
255
+ if (!setAuth)
256
+ return;
257
+ await setAuth.call(authClient, next);
258
+ }
259
+ : undefined,
260
+ });
261
+ await writeStore(recovered.store);
262
+ const removedDisplay = getCodexDisplayName(invalid?.entry, recovered.removed);
263
+ const messageLines = [`无效账号${removedDisplay}已移除,请及时检查核对`];
264
+ if (recovered.replacement) {
265
+ const replacementEntry = recovered.store.accounts[recovered.replacement];
266
+ const replacementDisplay = getCodexDisplayName(replacementEntry, recovered.replacement);
267
+ messageLines.push(`已切换到${replacementDisplay}`);
268
+ if (recovered.weekRecoveryOnly) {
269
+ messageLines.push("请检查账号状态");
270
+ }
271
+ }
272
+ await showToast({
273
+ client: input.client,
274
+ message: messageLines.join("\n"),
275
+ variant: "warning",
276
+ });
277
+ throw new CodexStatusCommandHandledError();
278
+ }
279
+ }
233
280
  const cachedRaw = await readStore().catch(() => ({}));
234
281
  const cached = normalizeCodexStore(cachedRaw);
235
282
  if (hasCachedStore(cached)) {
236
283
  await showToast({
237
284
  client: input.client,
238
- message: `Codex status fetch failed (${fetched.error.message}); showing cached snapshot.\n${renderCachedStatusForAccount(cached, { accountId: source.accountId })}`,
285
+ message: `Codex status fetch failed: ${fetched.error.message}`,
286
+ variant: "warning",
287
+ });
288
+ await showToast({
289
+ client: input.client,
290
+ message: renderCachedStatusForAccount(cached, { accountId: source.accountId }),
239
291
  variant: "warning",
240
292
  });
241
293
  }
@@ -279,6 +331,7 @@ export async function handleCodexStatusCommand(input) {
279
331
  providerId: previousEntry.providerId ?? "codex",
280
332
  accountId: fetched.status.identity.accountId ?? previousEntry.accountId ?? source.accountId,
281
333
  email: fetched.status.identity.email ?? previousEntry.email,
334
+ workspaceName: fetched.status.identity.workspaceName ?? previousEntry.workspaceName,
282
335
  lastUsed: fetched.status.updatedAt,
283
336
  snapshot: {
284
337
  ...(previousEntry.snapshot ?? {}),
@@ -23,6 +23,10 @@ export type CodexStatusSnapshot = {
23
23
  updatedAt: number;
24
24
  };
25
25
  export type CodexStatusError = {
26
+ kind: "invalid_account";
27
+ status: 400;
28
+ message: string;
29
+ } | {
26
30
  kind: "rate_limited";
27
31
  status: 429;
28
32
  message: string;
@@ -1,4 +1,49 @@
1
1
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/codex/usage";
2
+ function isInvalidAccountRefreshError(error) {
3
+ if (!error || typeof error !== "object")
4
+ return false;
5
+ const value = error;
6
+ return value.kind === "invalid_account"
7
+ && value.status === 400
8
+ && typeof value.message === "string"
9
+ && value.message.length > 0;
10
+ }
11
+ function readErrorStatus(error) {
12
+ if (!error || typeof error !== "object")
13
+ return undefined;
14
+ const value = error.status;
15
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
16
+ }
17
+ function readErrorMessage(error) {
18
+ if (error instanceof Error)
19
+ return error.message;
20
+ if (typeof error === "string")
21
+ return error;
22
+ if (error && typeof error === "object") {
23
+ const message = error.message;
24
+ if (typeof message === "string" && message.length > 0)
25
+ return message;
26
+ }
27
+ return String(error);
28
+ }
29
+ function mapRefreshTokenError(error) {
30
+ if (isInvalidAccountRefreshError(error))
31
+ return error;
32
+ const message = readErrorMessage(error);
33
+ const status = readErrorStatus(error);
34
+ const hasRefresh400Message = /refresh/i.test(message) && /\b400\b/.test(message);
35
+ if (status === 400 || hasRefresh400Message) {
36
+ return {
37
+ kind: "invalid_account",
38
+ status: 400,
39
+ message,
40
+ };
41
+ }
42
+ return {
43
+ kind: "network_error",
44
+ message,
45
+ };
46
+ }
2
47
  function asRecord(input) {
3
48
  if (!input || typeof input !== "object" || Array.isArray(input))
4
49
  return undefined;
@@ -154,12 +199,10 @@ export async function fetchCodexStatus(input) {
154
199
  refreshed = await input.refreshTokens(oauth);
155
200
  }
156
201
  catch (error) {
202
+ const mappedError = mapRefreshTokenError(error);
157
203
  return {
158
204
  ok: false,
159
- error: {
160
- kind: "network_error",
161
- message: error instanceof Error ? error.message : String(error),
162
- },
205
+ error: mappedError,
163
206
  };
164
207
  }
165
208
  if (!refreshed || !refreshed.access) {
@@ -14,6 +14,7 @@ export type CodexAccountSnapshot = {
14
14
  export type CodexAccountEntry = {
15
15
  name?: string;
16
16
  providerId?: string;
17
+ workspaceName?: string;
17
18
  refresh?: string;
18
19
  access?: string;
19
20
  expires?: number;
@@ -60,6 +60,8 @@ function pickEntry(input) {
60
60
  next.name = pickString(source.name);
61
61
  if (pickString(source.providerId))
62
62
  next.providerId = pickString(source.providerId);
63
+ if (pickString(source.workspaceName))
64
+ next.workspaceName = pickString(source.workspaceName);
63
65
  if (pickString(source.refresh))
64
66
  next.refresh = pickString(source.refresh);
65
67
  if (pickString(source.access))
@@ -3,6 +3,7 @@ import { stdin as input, stdout as output } from "node:process";
3
3
  import { fetchCodexStatus } from "../codex-status-fetcher.js";
4
4
  import { runCodexOAuth } from "../codex-oauth.js";
5
5
  import { getActiveCodexAccount, readCodexStore, writeCodexStore, } from "../codex-store.js";
6
+ import { recoverInvalidCodexAccount } from "../codex-invalid-account.js";
6
7
  import { readAuth } from "../store.js";
7
8
  function pickName(input) {
8
9
  const accountId = input.accountId?.trim();
@@ -44,6 +45,13 @@ function toMenuQuota(entry) {
44
45
  completions: undefined,
45
46
  };
46
47
  }
48
+ function withoutRecoveryWarning(snapshot) {
49
+ if (!snapshot)
50
+ return undefined;
51
+ const next = { ...snapshot };
52
+ delete next.recoveryWarning;
53
+ return next;
54
+ }
47
55
  async function promptText(message) {
48
56
  const rl = createInterface({ input, output });
49
57
  try {
@@ -66,8 +74,11 @@ export function createCodexMenuAdapter(inputDeps) {
66
74
  const authorizeOpenAIOAuth = inputDeps.runCodexOAuth ?? runCodexOAuth;
67
75
  const refreshSnapshots = async (store) => {
68
76
  const names = Object.keys(store.accounts);
77
+ const pendingRecoveryWarnings = new Map();
69
78
  for (const name of names) {
70
79
  const entry = store.accounts[name];
80
+ if (!entry)
81
+ continue;
71
82
  const oauth = toOAuth(entry);
72
83
  if (!oauth.access && !oauth.refresh) {
73
84
  store.accounts[name] = {
@@ -88,6 +99,30 @@ export function createCodexMenuAdapter(inputDeps) {
88
99
  },
89
100
  }));
90
101
  if (!result.ok) {
102
+ if (result.error.kind === "invalid_account") {
103
+ const recovered = await recoverInvalidCodexAccount({
104
+ store,
105
+ invalidAccountName: name,
106
+ setAuth: async (next) => {
107
+ await inputDeps.client.auth.set(next);
108
+ },
109
+ });
110
+ store.accounts = recovered.store.accounts;
111
+ store.active = recovered.store.active;
112
+ if (recovered.replacement) {
113
+ const replacement = store.accounts[recovered.replacement];
114
+ const weekRemaining = replacement?.snapshot?.usageWeek?.remaining ?? 0;
115
+ const fiveHourRemaining = replacement?.snapshot?.usage5h?.remaining ?? 0;
116
+ if (weekRemaining > 0 && fiveHourRemaining <= 0) {
117
+ pendingRecoveryWarnings.set(recovered.replacement, {
118
+ code: "week_recovery_only",
119
+ removed: recovered.removed,
120
+ replacement: recovered.replacement,
121
+ });
122
+ }
123
+ }
124
+ continue;
125
+ }
91
126
  store.accounts[name] = {
92
127
  ...entry,
93
128
  snapshot: {
@@ -113,10 +148,11 @@ export function createCodexMenuAdapter(inputDeps) {
113
148
  ...(result.authPatch?.accountId !== undefined ? { accountId: result.authPatch.accountId } : {}),
114
149
  name: nextName,
115
150
  providerId: "openai",
151
+ workspaceName: result.status.identity.workspaceName ?? entry.workspaceName,
116
152
  accountId: result.status.identity.accountId ?? result.authPatch?.accountId ?? entry.accountId,
117
153
  email: result.status.identity.email ?? entry.email,
118
154
  snapshot: {
119
- ...(entry.snapshot ?? {}),
155
+ ...(withoutRecoveryWarning(entry.snapshot) ?? {}),
120
156
  plan: result.status.identity.plan ?? entry.snapshot?.plan,
121
157
  usage5h: {
122
158
  entitlement: result.status.windows.primary.entitlement,
@@ -141,6 +177,24 @@ export function createCodexMenuAdapter(inputDeps) {
141
177
  if (store.active === name || !store.active)
142
178
  store.active = nextName;
143
179
  }
180
+ for (const [name, entry] of Object.entries(store.accounts)) {
181
+ const warning = pendingRecoveryWarnings.get(name);
182
+ const snapshot = withoutRecoveryWarning(entry.snapshot);
183
+ store.accounts[name] = {
184
+ ...entry,
185
+ ...(snapshot ? { snapshot } : {}),
186
+ };
187
+ if (!warning)
188
+ continue;
189
+ const nextSnapshot = {
190
+ ...(store.accounts[name].snapshot ?? {}),
191
+ };
192
+ nextSnapshot.recoveryWarning = warning;
193
+ store.accounts[name] = {
194
+ ...store.accounts[name],
195
+ snapshot: nextSnapshot,
196
+ };
197
+ }
144
198
  };
145
199
  return {
146
200
  key: "codex",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-copilot-account-switcher",
3
- "version": "0.13.2",
3
+ "version": "0.13.3",
4
4
  "description": "GitHub Copilot account switcher plugin for OpenCode",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",