codex-team 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,8 +24,6 @@ codexm switch <name>
24
24
  codexm switch --auto --dry-run
25
25
  codexm remove <name> --yes
26
26
  codexm rename <old> <new>
27
- codexm quota refresh [name]
28
- codexm doctor
29
27
  ```
30
28
 
31
29
  Use `--json` on query and mutation commands when you need machine-readable output.
@@ -36,7 +34,9 @@ Use `--json` on query and mutation commands when you need machine-readable outpu
36
34
  2. Save the current auth snapshot with `codexm save <name>`.
37
35
  3. Repeat for other accounts.
38
36
  4. Switch between saved accounts with `codexm switch <name>` or let the tool choose with `codexm switch --auto`.
39
- 5. Refresh and inspect quota usage with `codexm list` or `codexm quota refresh`.
37
+ 5. Refresh and inspect quota usage with `codexm list`.
38
+
39
+ For ChatGPT auth snapshots, `codex-team` can save and switch different users under the same ChatGPT account/workspace as separate managed entries when the local login tokens distinguish them.
40
40
 
41
41
  ## Development
42
42
 
package/dist/cli.cjs CHANGED
@@ -13,7 +13,7 @@ var __webpack_modules__ = {
13
13
  const utc_js_namespaceObject = require("dayjs/plugin/utc.js");
14
14
  var utc_js_default = /*#__PURE__*/ __webpack_require__.n(utc_js_namespaceObject);
15
15
  var package_namespaceObject = {
16
- rE: "0.0.7"
16
+ rE: "0.0.9"
17
17
  };
18
18
  const external_node_crypto_namespaceObject = require("node:crypto");
19
19
  const promises_namespaceObject = require("node:fs/promises");
@@ -48,10 +48,44 @@ var __webpack_modules__ = {
48
48
  const normalized = normalizeAuthMode(authMode);
49
49
  return "chatgpt" === normalized || "chatgpt_auth_tokens" === normalized;
50
50
  }
51
+ function extractAuthClaim(payload) {
52
+ const value = payload["https://api.openai.com/auth"];
53
+ return isRecord(value) ? value : void 0;
54
+ }
55
+ function extractStringClaim(payload, key) {
56
+ const value = payload[key];
57
+ return "string" == typeof value && "" !== value.trim() ? value : void 0;
58
+ }
59
+ function extractSnapshotJwtPayloads(snapshot) {
60
+ const tokens = snapshot.tokens ?? {};
61
+ const payloads = [];
62
+ for (const tokenName of [
63
+ "id_token",
64
+ "access_token"
65
+ ]){
66
+ const token = tokens[tokenName];
67
+ if ("string" == typeof token && "" !== token.trim()) try {
68
+ payloads.push(decodeJwtPayload(token));
69
+ } catch {}
70
+ }
71
+ return payloads;
72
+ }
73
+ function getSnapshotUserId(snapshot) {
74
+ for (const payload of extractSnapshotJwtPayloads(snapshot)){
75
+ const authClaim = extractAuthClaim(payload);
76
+ const chatGPTUserId = authClaim?.chatgpt_user_id;
77
+ if ("string" == typeof chatGPTUserId && "" !== chatGPTUserId.trim()) return chatGPTUserId;
78
+ const userId = extractStringClaim(payload, "user_id");
79
+ if (userId) return userId;
80
+ }
81
+ }
51
82
  function fingerprintApiKey(apiKey) {
52
83
  return (0, external_node_crypto_namespaceObject.createHash)("sha256").update(apiKey).digest("hex").slice(0, 16);
53
84
  }
54
- function getSnapshotIdentity(snapshot) {
85
+ function composeIdentity(accountId, userId) {
86
+ return "string" == typeof userId && "" !== userId.trim() ? `${accountId}:${userId}` : accountId;
87
+ }
88
+ function getSnapshotAccountId(snapshot) {
55
89
  if (isApiKeyAuthMode(snapshot.auth_mode)) {
56
90
  const apiKey = snapshot.OPENAI_API_KEY;
57
91
  if ("string" != typeof apiKey || "" === apiKey.trim()) throw new Error('Field "OPENAI_API_KEY" must be a non-empty string for apikey auth.');
@@ -61,6 +95,12 @@ var __webpack_modules__ = {
61
95
  if ("string" != typeof accountId || "" === accountId.trim()) throw new Error('Field "tokens.account_id" must be a non-empty string.');
62
96
  return accountId;
63
97
  }
98
+ function getSnapshotIdentity(snapshot) {
99
+ return composeIdentity(getSnapshotAccountId(snapshot), isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0);
100
+ }
101
+ function getMetaIdentity(meta) {
102
+ return composeIdentity(meta.account_id, meta.user_id);
103
+ }
64
104
  function defaultQuotaSnapshot() {
65
105
  return {
66
106
  status: "stale"
@@ -138,7 +178,8 @@ var __webpack_modules__ = {
138
178
  return {
139
179
  name,
140
180
  auth_mode: snapshot.auth_mode,
141
- account_id: getSnapshotIdentity(snapshot),
181
+ account_id: getSnapshotAccountId(snapshot),
182
+ user_id: isSupportedChatGPTAuthMode(snapshot.auth_mode) ? getSnapshotUserId(snapshot) : void 0,
142
183
  created_at: existingCreatedAt ?? timestamp,
143
184
  updated_at: timestamp,
144
185
  last_switched_at: null,
@@ -159,6 +200,7 @@ var __webpack_modules__ = {
159
200
  name: asNonEmptyString(parsed.name, "name"),
160
201
  auth_mode: asNonEmptyString(parsed.auth_mode, "auth_mode"),
161
202
  account_id: asNonEmptyString(parsed.account_id, "account_id"),
203
+ user_id: asOptionalString(parsed.user_id, "user_id"),
162
204
  created_at: asNonEmptyString(parsed.created_at, "created_at"),
163
205
  updated_at: asNonEmptyString(parsed.updated_at, "updated_at"),
164
206
  last_switched_at: lastSwitchedAt,
@@ -187,11 +229,11 @@ var __webpack_modules__ = {
187
229
  function quota_client_isRecord(value) {
188
230
  return "object" == typeof value && null !== value && !Array.isArray(value);
189
231
  }
190
- function extractAuthClaim(payload) {
232
+ function quota_client_extractAuthClaim(payload) {
191
233
  const value = payload["https://api.openai.com/auth"];
192
234
  return quota_client_isRecord(value) ? value : void 0;
193
235
  }
194
- function extractStringClaim(payload, key) {
236
+ function quota_client_extractStringClaim(payload, key) {
195
237
  const value = payload[key];
196
238
  return "string" == typeof value && "" !== value.trim() ? value : void 0;
197
239
  }
@@ -204,7 +246,7 @@ var __webpack_modules__ = {
204
246
  const token = tokens[tokenName];
205
247
  if ("string" == typeof token && "" !== token.trim()) try {
206
248
  const payload = decodeJwtPayload(token);
207
- const authClaim = extractAuthClaim(payload);
249
+ const authClaim = quota_client_extractAuthClaim(payload);
208
250
  const planType = authClaim?.chatgpt_plan_type;
209
251
  if ("string" == typeof planType && "" !== planType.trim()) return planType;
210
252
  } catch {}
@@ -228,7 +270,7 @@ var __webpack_modules__ = {
228
270
  const token = tokens[tokenName];
229
271
  if ("string" == typeof token && "" !== token.trim()) try {
230
272
  const payload = decodeJwtPayload(token);
231
- const authClaim = extractAuthClaim(payload);
273
+ const authClaim = quota_client_extractAuthClaim(payload);
232
274
  if (!accountId) {
233
275
  const maybeAccountId = authClaim?.chatgpt_account_id;
234
276
  if ("string" == typeof maybeAccountId && "" !== maybeAccountId.trim()) accountId = maybeAccountId;
@@ -237,8 +279,8 @@ var __webpack_modules__ = {
237
279
  const maybePlanType = authClaim?.chatgpt_plan_type;
238
280
  if ("string" == typeof maybePlanType && "" !== maybePlanType.trim()) planType = maybePlanType;
239
281
  }
240
- issuer ??= extractStringClaim(payload, "iss");
241
- clientId ??= extractStringClaim(payload, "client_id") ?? extractStringClaim(payload, "azp") ?? ("string" == typeof payload.aud ? payload.aud : void 0);
282
+ issuer ??= quota_client_extractStringClaim(payload, "iss");
283
+ clientId ??= quota_client_extractStringClaim(payload, "client_id") ?? quota_client_extractStringClaim(payload, "azp") ?? ("string" == typeof payload.aud ? payload.aud : void 0);
242
284
  } catch {}
243
285
  }
244
286
  if (!supported) return {
@@ -508,6 +550,13 @@ var __webpack_modules__ = {
508
550
  async function readJsonFile(path) {
509
551
  return (0, promises_namespaceObject.readFile)(path, "utf8");
510
552
  }
553
+ function canAutoMigrateLegacyChatGPTMeta(meta, snapshot) {
554
+ if (!isSupportedChatGPTAuthMode(meta.auth_mode) || !isSupportedChatGPTAuthMode(snapshot.auth_mode)) return false;
555
+ if ("string" == typeof meta.user_id && "" !== meta.user_id.trim()) return false;
556
+ const snapshotUserId = getSnapshotUserId(snapshot);
557
+ if (!snapshotUserId) return false;
558
+ return meta.account_id === getSnapshotAccountId(snapshot);
559
+ }
511
560
  async function detectRunningCodexProcesses() {
512
561
  try {
513
562
  const { stdout } = await execFile("ps", [
@@ -605,6 +654,8 @@ var __webpack_modules__ = {
605
654
  return {
606
655
  name: account.name,
607
656
  account_id: account.account_id,
657
+ user_id: account.user_id ?? null,
658
+ identity: account.identity,
608
659
  plan_type: planType,
609
660
  credits_balance: account.quota.credits_balance ?? null,
610
661
  status: account.quota.status,
@@ -645,11 +696,21 @@ var __webpack_modules__ = {
645
696
  readJsonFile(metaPath),
646
697
  readAuthSnapshotFile(authPath)
647
698
  ]);
648
- const meta = parseSnapshotMeta(rawMeta);
699
+ let meta = parseSnapshotMeta(rawMeta);
649
700
  if (meta.name !== name) throw new Error(`Account metadata name mismatch for "${name}".`);
650
- if (meta.account_id !== getSnapshotIdentity(snapshot)) throw new Error(`Account metadata account_id mismatch for "${name}".`);
701
+ const snapshotIdentity = getSnapshotIdentity(snapshot);
702
+ if (getMetaIdentity(meta) !== snapshotIdentity) if (canAutoMigrateLegacyChatGPTMeta(meta, snapshot)) {
703
+ meta = {
704
+ ...meta,
705
+ account_id: getSnapshotAccountId(snapshot),
706
+ user_id: getSnapshotUserId(snapshot)
707
+ };
708
+ await this.writeAccountMeta(name, meta);
709
+ } else throw new Error(`Account metadata account_id mismatch for "${name}".`);
710
+ if (getMetaIdentity(meta) !== snapshotIdentity) throw new Error(`Account metadata account_id mismatch for "${name}".`);
651
711
  return {
652
712
  ...meta,
713
+ identity: getMetaIdentity(meta),
653
714
  authPath,
654
715
  metaPath,
655
716
  configPath: await pathExists(this.accountConfigPath(name)) ? this.accountConfigPath(name) : null,
@@ -669,12 +730,12 @@ var __webpack_modules__ = {
669
730
  warnings.push(`Account "${entry.name}" is invalid: ${error.message}`);
670
731
  }
671
732
  const counts = new Map();
672
- for (const account of accounts)counts.set(account.account_id, (counts.get(account.account_id) ?? 0) + 1);
733
+ for (const account of accounts)counts.set(account.identity, (counts.get(account.identity) ?? 0) + 1);
673
734
  accounts.sort((left, right)=>left.name.localeCompare(right.name));
674
735
  return {
675
736
  accounts: accounts.map((account)=>({
676
737
  ...account,
677
- duplicateAccountId: (counts.get(account.account_id) ?? 0) > 1
738
+ duplicateAccountId: (counts.get(account.identity) ?? 0) > 1
678
739
  })),
679
740
  warnings
680
741
  };
@@ -685,6 +746,8 @@ var __webpack_modules__ = {
685
746
  exists: false,
686
747
  auth_mode: null,
687
748
  account_id: null,
749
+ user_id: null,
750
+ identity: null,
688
751
  matched_accounts: [],
689
752
  managed: false,
690
753
  duplicate_match: false,
@@ -692,11 +755,15 @@ var __webpack_modules__ = {
692
755
  };
693
756
  const snapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
694
757
  const currentIdentity = getSnapshotIdentity(snapshot);
695
- const matchedAccounts = accounts.filter((account)=>account.account_id === currentIdentity).map((account)=>account.name);
758
+ const currentAccountId = getSnapshotAccountId(snapshot);
759
+ const currentUserId = getSnapshotUserId(snapshot) ?? null;
760
+ const matchedAccounts = accounts.filter((account)=>account.identity === currentIdentity).map((account)=>account.name);
696
761
  return {
697
762
  exists: true,
698
763
  auth_mode: snapshot.auth_mode,
699
- account_id: currentIdentity,
764
+ account_id: currentAccountId,
765
+ user_id: currentUserId,
766
+ identity: currentIdentity,
700
767
  matched_accounts: matchedAccounts,
701
768
  managed: matchedAccounts.length > 0,
702
769
  duplicate_match: matchedAccounts.length > 1,
@@ -714,9 +781,16 @@ var __webpack_modules__ = {
714
781
  const authPath = this.accountAuthPath(name);
715
782
  const metaPath = this.accountMetaPath(name);
716
783
  const configPath = this.accountConfigPath(name);
784
+ const identity = getSnapshotIdentity(snapshot);
717
785
  const accountExists = await pathExists(accountDir);
718
786
  const existingMeta = accountExists && await pathExists(metaPath) ? parseSnapshotMeta(await readJsonFile(metaPath)) : void 0;
719
787
  if (accountExists && !force) throw new Error(`Account "${name}" already exists. Use --force to overwrite it.`);
788
+ const { accounts } = await this.listAccounts();
789
+ const duplicateIdentityAccounts = accounts.filter((account)=>account.name !== name && account.identity === identity);
790
+ if (duplicateIdentityAccounts.length > 0) {
791
+ const joinedNames = duplicateIdentityAccounts.map((account)=>`"${account.name}"`).join(", ");
792
+ throw new Error(`Identity ${identity} is already managed by ${joinedNames}.`);
793
+ }
720
794
  this.validateConfigSnapshot(name, snapshot, rawConfig);
721
795
  await ensureDirectory(accountDir, DIRECTORY_MODE);
722
796
  await atomicWriteFile(authPath, `${rawSnapshot.trimEnd()}\n`);
@@ -787,7 +861,7 @@ var __webpack_modules__ = {
787
861
  await atomicWriteFile(this.paths.currentConfigPath, this.sanitizeConfigForAccountAuth(currentRawConfig));
788
862
  }
789
863
  const writtenSnapshot = await readAuthSnapshotFile(this.paths.currentAuthPath);
790
- if (getSnapshotIdentity(writtenSnapshot) !== account.account_id) throw new Error(`Switch verification failed for account "${name}".`);
864
+ if (getSnapshotIdentity(writtenSnapshot) !== account.identity) throw new Error(`Switch verification failed for account "${name}".`);
791
865
  const meta = parseSnapshotMeta(await readJsonFile(account.metaPath));
792
866
  meta.last_switched_at = new Date().toISOString();
793
867
  meta.updated_at = meta.last_switched_at;
@@ -831,7 +905,8 @@ var __webpack_modules__ = {
831
905
  await this.syncCurrentAuthIfMatching(result.authSnapshot);
832
906
  }
833
907
  meta.auth_mode = result.authSnapshot.auth_mode;
834
- meta.account_id = getSnapshotIdentity(result.authSnapshot);
908
+ meta.account_id = getSnapshotAccountId(result.authSnapshot);
909
+ meta.user_id = getSnapshotUserId(result.authSnapshot);
835
910
  meta.updated_at = now.toISOString();
836
911
  meta.quota = result.quota;
837
912
  await this.writeAccountMeta(name, meta);
@@ -944,7 +1019,7 @@ var __webpack_modules__ = {
944
1019
  if ((511 & authStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" auth permissions must be 600.`);
945
1020
  if ((511 & metaStat.mode) !== FILE_MODE) issues.push(`Account "${account.name}" metadata permissions must be 600.`);
946
1021
  if ("apikey" === account.auth_mode && !account.configPath) issues.push(`Account "${account.name}" is missing config.toml snapshot required for apikey auth.`);
947
- if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.account_id} with another saved account.`);
1022
+ if (account.duplicateAccountId) warnings.push(`Account "${account.name}" shares identity ${account.identity} with another saved account.`);
948
1023
  }
949
1024
  let currentAuthPresent = false;
950
1025
  if (await pathExists(this.paths.currentAuthPath)) {
@@ -1012,12 +1087,10 @@ Usage:
1012
1087
  codexm list [name] [--json]
1013
1088
  codexm save <name> [--force] [--json]
1014
1089
  codexm update [--json]
1015
- codexm quota refresh [name] [--json]
1016
1090
  codexm switch <name> [--json]
1017
1091
  codexm switch --auto [--dry-run] [--json]
1018
1092
  codexm remove <name> [--yes] [--json]
1019
1093
  codexm rename <old> <new> [--json]
1020
- codexm doctor [--json]
1021
1094
 
1022
1095
  Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1023
1096
  `);
@@ -1027,7 +1100,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1027
1100
  if (status.exists) {
1028
1101
  lines.push("Current auth: present");
1029
1102
  lines.push(`Auth mode: ${status.auth_mode}`);
1030
- lines.push(`Identity: ${maskAccountId(status.account_id ?? "")}`);
1103
+ lines.push(`Identity: ${maskAccountId(status.identity ?? "")}`);
1031
1104
  if (0 === status.matched_accounts.length) lines.push("Managed account: no (unmanaged)");
1032
1105
  else if (1 === status.matched_accounts.length) lines.push(`Managed account: ${status.matched_accounts[0]}`);
1033
1106
  else lines.push(`Managed account: multiple (${status.matched_accounts.join(", ")})`);
@@ -1035,16 +1108,6 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1035
1108
  for (const warning of status.warnings)lines.push(`Warning: ${warning}`);
1036
1109
  return lines.join("\n");
1037
1110
  }
1038
- function describeDoctor(report) {
1039
- const lines = [
1040
- report.healthy ? "Doctor checks passed." : "Doctor checks found issues.",
1041
- `Saved accounts: ${report.account_count}`,
1042
- `Current auth present: ${report.current_auth_present ? "yes" : "no"}`
1043
- ];
1044
- for (const issue of report.issues)lines.push(`Issue: ${issue}`);
1045
- for (const warning of report.warnings)lines.push(`Warning: ${warning}`);
1046
- return lines.join("\n");
1047
- }
1048
1111
  function formatUsagePercent(window) {
1049
1112
  if (!window) return "-";
1050
1113
  return `${window.used_percent}%`;
@@ -1090,6 +1153,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1090
1153
  return {
1091
1154
  name: account.name,
1092
1155
  account_id: account.account_id,
1156
+ identity: account.identity,
1093
1157
  plan_type: account.plan_type,
1094
1158
  available: computeAvailability(account),
1095
1159
  refresh_status: "ok",
@@ -1122,7 +1186,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1122
1186
  }
1123
1187
  function describeAutoSwitchSelection(candidate, dryRun, backupPath, warnings) {
1124
1188
  const lines = [
1125
- dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.account_id)}).` : `Auto-switched to "${candidate.name}" (${maskAccountId(candidate.account_id)}).`,
1189
+ dryRun ? `Best account: "${candidate.name}" (${maskAccountId(candidate.identity)}).` : `Auto-switched to "${candidate.name}" (${maskAccountId(candidate.identity)}).`,
1126
1190
  `Score: ${candidate.effective_score}`,
1127
1191
  `5H remaining: ${candidate.remain_5h}%`,
1128
1192
  `1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
@@ -1133,7 +1197,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1133
1197
  }
1134
1198
  function describeAutoSwitchNoop(candidate, warnings) {
1135
1199
  const lines = [
1136
- `Current account "${candidate.name}" (${maskAccountId(candidate.account_id)}) is already the best available account.`,
1200
+ `Current account "${candidate.name}" (${maskAccountId(candidate.identity)}) is already the best available account.`,
1137
1201
  `Score: ${candidate.effective_score}`,
1138
1202
  `5H remaining: ${candidate.remain_5h}%`,
1139
1203
  `1W remaining (5H-equivalent): ${candidate.remain_1w_eq_5h}%`
@@ -1145,7 +1209,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1145
1209
  if (0 === accounts.length) return 0 === warnings.length ? "No saved accounts." : warnings.map((warning)=>`Warning: ${warning}`).join("\n");
1146
1210
  const table = formatTable(accounts.map((account)=>({
1147
1211
  name: account.name,
1148
- account_id: maskAccountId(account.account_id),
1212
+ account_id: maskAccountId(account.identity),
1149
1213
  plan_type: account.plan_type ?? "-",
1150
1214
  available: computeAvailability(account) ?? "-",
1151
1215
  five_hour: formatUsagePercent(account.five_hour),
@@ -1270,11 +1334,13 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1270
1334
  account: {
1271
1335
  name: account.name,
1272
1336
  account_id: account.account_id,
1337
+ user_id: account.user_id ?? null,
1338
+ identity: account.identity,
1273
1339
  auth_mode: account.auth_mode
1274
1340
  }
1275
1341
  };
1276
1342
  if (json) writeJson(streams.stdout, payload);
1277
- else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.account_id)}).\n`);
1343
+ else streams.stdout.write(`Saved account "${account.name}" (${maskAccountId(account.identity)}).\n`);
1278
1344
  return 0;
1279
1345
  }
1280
1346
  case "update":
@@ -1296,6 +1362,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1296
1362
  account: {
1297
1363
  name: result.account.name,
1298
1364
  account_id: result.account.account_id,
1365
+ user_id: result.account.user_id ?? null,
1366
+ identity: result.account.identity,
1299
1367
  auth_mode: result.account.auth_mode
1300
1368
  },
1301
1369
  quota,
@@ -1303,23 +1371,11 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1303
1371
  };
1304
1372
  if (json) writeJson(streams.stdout, payload);
1305
1373
  else {
1306
- streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.account_id)}).\n`);
1374
+ streams.stdout.write(`Updated managed account "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
1307
1375
  for (const warning of warnings)streams.stdout.write(`Warning: ${warning}\n`);
1308
1376
  }
1309
1377
  return 0;
1310
1378
  }
1311
- case "quota":
1312
- {
1313
- const quotaCommand = parsed.positionals[0];
1314
- if ("refresh" === quotaCommand) {
1315
- const targetName = parsed.positionals[1];
1316
- const result = await store.refreshAllQuotas(targetName);
1317
- if (json) writeJson(streams.stdout, toCliQuotaRefreshResult(result));
1318
- else streams.stdout.write(`${describeQuotaRefresh(result)}\n`);
1319
- return 0 === result.failures.length ? 0 : 1;
1320
- }
1321
- throw new Error("Usage: codexm quota refresh [name] [--json]");
1322
- }
1323
1379
  case "switch":
1324
1380
  {
1325
1381
  const auto = parsed.flags.has("--auto");
@@ -1358,7 +1414,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1358
1414
  reason: "already_current_best",
1359
1415
  account: {
1360
1416
  name: selected.name,
1361
- account_id: selected.account_id
1417
+ account_id: selected.account_id,
1418
+ identity: selected.identity
1362
1419
  },
1363
1420
  selected,
1364
1421
  candidates,
@@ -1378,6 +1435,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1378
1435
  account: {
1379
1436
  name: result.account.name,
1380
1437
  account_id: result.account.account_id,
1438
+ user_id: result.account.user_id ?? null,
1439
+ identity: result.account.identity,
1381
1440
  auth_mode: result.account.auth_mode
1382
1441
  },
1383
1442
  selected,
@@ -1407,6 +1466,8 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1407
1466
  account: {
1408
1467
  name: result.account.name,
1409
1468
  account_id: result.account.account_id,
1469
+ user_id: result.account.user_id ?? null,
1470
+ identity: result.account.identity,
1410
1471
  auth_mode: result.account.auth_mode
1411
1472
  },
1412
1473
  quota,
@@ -1415,7 +1476,7 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1415
1476
  };
1416
1477
  if (json) writeJson(streams.stdout, payload);
1417
1478
  else {
1418
- streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.account_id)}).\n`);
1479
+ streams.stdout.write(`Switched to "${result.account.name}" (${maskAccountId(result.account.identity)}).\n`);
1419
1480
  if (result.backup_path) streams.stdout.write(`Backup: ${result.backup_path}\n`);
1420
1481
  for (const warning of result.warnings)streams.stdout.write(`Warning: ${warning}\n`);
1421
1482
  }
@@ -1457,19 +1518,14 @@ Account names must match /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.
1457
1518
  account: {
1458
1519
  name: account.name,
1459
1520
  account_id: account.account_id,
1521
+ user_id: account.user_id ?? null,
1522
+ identity: account.identity,
1460
1523
  auth_mode: account.auth_mode
1461
1524
  }
1462
1525
  });
1463
1526
  else streams.stdout.write(`Renamed "${oldName}" to "${newName}".\n`);
1464
1527
  return 0;
1465
1528
  }
1466
- case "doctor":
1467
- {
1468
- const report = await store.doctor();
1469
- if (json) writeJson(streams.stdout, report);
1470
- else streams.stdout.write(`${describeDoctor(report)}\n`);
1471
- return report.healthy ? 0 : 1;
1472
- }
1473
1529
  default:
1474
1530
  throw new Error(`Unknown command "${parsed.command}".`);
1475
1531
  }