@bitkyc08/opencodex 2.6.22 → 2.6.23

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 (51) hide show
  1. package/gui/dist/assets/{index-DDcEW0Cm.css → index-BcHhxo1I.css} +1 -1
  2. package/gui/dist/assets/index-Yuh5eZiD.js +9 -0
  3. package/gui/dist/index.html +2 -2
  4. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  5. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  6. package/gui/dist/provider-icons/antigravity.svg +1 -0
  7. package/gui/dist/provider-icons/claude-color.svg +1 -0
  8. package/gui/dist/provider-icons/claude.svg +1 -0
  9. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  10. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  11. package/gui/dist/provider-icons/copilot.svg +1 -0
  12. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  13. package/gui/dist/provider-icons/cursor.svg +2 -0
  14. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  15. package/gui/dist/provider-icons/discord.svg +1 -0
  16. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  17. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  18. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  19. package/gui/dist/provider-icons/gemini.svg +1 -0
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  22. package/gui/dist/provider-icons/grok-color.svg +1 -0
  23. package/gui/dist/provider-icons/grok.svg +1 -0
  24. package/gui/dist/provider-icons/groq-color.svg +1 -0
  25. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  26. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  27. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  28. package/gui/dist/provider-icons/kiro.svg +14 -0
  29. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  30. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  31. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  32. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  33. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  34. package/gui/dist/provider-icons/openai.svg +1 -0
  35. package/gui/dist/provider-icons/opencode.svg +1 -0
  36. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  37. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  38. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  39. package/gui/dist/provider-icons/telegram.svg +1 -0
  40. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  41. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  42. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  43. package/package.json +1 -1
  44. package/src/cli-help.ts +27 -0
  45. package/src/cli-models.ts +138 -0
  46. package/src/cli-provider.ts +414 -0
  47. package/src/cli.ts +27 -1
  48. package/src/codex-auth-api.ts +35 -20
  49. package/src/provider-quota.ts +330 -0
  50. package/src/server.ts +6 -0
  51. package/gui/dist/assets/index-C0YNrCNA.js +0 -9
@@ -0,0 +1,414 @@
1
+ /**
2
+ * `ocx provider` subcommand — non-interactive provider management.
3
+ *
4
+ * Subcommands:
5
+ * list List configured and available registry providers
6
+ * add <name> Add a provider from the registry or with custom flags
7
+ * remove <name> Remove a configured provider
8
+ * show <name> Show provider config details (secrets masked)
9
+ * set-default <name> Change the default provider
10
+ */
11
+ import { hasOwnProvider, isValidProviderName, loadConfig, saveConfig } from "./config";
12
+ import { hasHelpFlag } from "./cli-help";
13
+ import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "./providers/registry";
14
+ import { providerConfigSeed } from "./providers/derive";
15
+ import type { OcxProviderConfig } from "./types";
16
+ import { findLiveProxy } from "./proxy-liveness";
17
+ import { syncModelsToCodex } from "./codex-sync";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Arg helpers
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function consumeFlag(args: string[], flag: string): boolean {
24
+ const idx = args.indexOf(flag);
25
+ if (idx === -1) return false;
26
+ args.splice(idx, 1);
27
+ return true;
28
+ }
29
+
30
+ function consumeFlagValue(args: string[], flag: string): string | undefined {
31
+ const idx = args.indexOf(flag);
32
+ if (idx === -1 || idx + 1 >= args.length) return undefined;
33
+ const value = args[idx + 1];
34
+ args.splice(idx, 2);
35
+ return value;
36
+ }
37
+
38
+ /** Reject any leftover args (unknown flags or trailing values). */
39
+ function rejectUnknownArgs(args: string[], usage: string): void {
40
+ if (args.length === 0) return;
41
+ const unknown = args.filter(a => a.startsWith("-"));
42
+ if (unknown.length > 0) {
43
+ console.error(`Unknown flag(s): ${unknown.join(", ")}`);
44
+ } else {
45
+ console.error(`Unexpected argument(s): ${args.join(", ")}`);
46
+ }
47
+ console.error(usage);
48
+ process.exit(1);
49
+ }
50
+
51
+ function maskSecret(value: string): string {
52
+ if (value.length <= 8) return "****";
53
+ return `${value.slice(0, 4)}****${value.slice(-4)}`;
54
+ }
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Validation helper (F1 fix: validate before saveConfig)
58
+ // ---------------------------------------------------------------------------
59
+
60
+ function validateAndSave(config: ReturnType<typeof loadConfig>): void {
61
+ if (!config.providers || Object.keys(config.providers).length === 0) {
62
+ console.error("Error: config would have no providers. Aborting.");
63
+ process.exit(1);
64
+ }
65
+ if (!hasOwnProvider(config.providers, config.defaultProvider)) {
66
+ console.error(`Error: defaultProvider "${config.defaultProvider}" does not exist in providers. Aborting.`);
67
+ process.exit(1);
68
+ }
69
+ saveConfig(config);
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // provider list
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function handleList(args: string[]): void {
77
+ const wantsJson = consumeFlag(args, "--json");
78
+ rejectUnknownArgs(args, "Usage: ocx provider list [--json]");
79
+
80
+ const config = loadConfig();
81
+ const configured = Object.keys(config.providers);
82
+
83
+ if (wantsJson) {
84
+ const entries = configured.map(name => {
85
+ const prov = config.providers[name];
86
+ const registryEntry = getProviderRegistryEntry(name);
87
+ return {
88
+ name,
89
+ adapter: prov.adapter,
90
+ baseUrl: prov.baseUrl,
91
+ authMode: prov.authMode ?? "key",
92
+ defaultModel: prov.defaultModel ?? null,
93
+ isDefault: name === config.defaultProvider,
94
+ source: registryEntry ? "registry" : "custom",
95
+ models: prov.models ?? [],
96
+ };
97
+ });
98
+ console.log(JSON.stringify({ configured: entries, registryCount: PROVIDER_REGISTRY.length }, null, 2));
99
+ return;
100
+ }
101
+
102
+ console.log("Configured providers:\n");
103
+ for (const name of configured) {
104
+ const prov = config.providers[name];
105
+ const isDefault = name === config.defaultProvider ? " (default)" : "";
106
+ const registryEntry = getProviderRegistryEntry(name);
107
+ const source = registryEntry ? "" : " [custom]";
108
+ const model = prov.defaultModel ? ` model=${prov.defaultModel}` : "";
109
+ console.log(` ${name}${isDefault}${source} adapter=${prov.adapter}${model}`);
110
+ }
111
+
112
+ const available = PROVIDER_REGISTRY.filter(e => !configured.includes(e.id));
113
+ if (available.length > 0) {
114
+ console.log(`\nAvailable from registry (${available.length}):\n`);
115
+ for (const entry of available) {
116
+ const auth = entry.authKind === "forward" ? "chatgpt-login" : entry.authKind;
117
+ console.log(` ${entry.id.padEnd(24)} ${entry.label} (${auth})`);
118
+ }
119
+ console.log(`\nAdd with: ocx provider add <name> [--api-key <key>]`);
120
+ }
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // provider add
125
+ // ---------------------------------------------------------------------------
126
+
127
+ const ADD_USAGE = "Usage: ocx provider add <name> [--adapter <adapter>] [--base-url <url>] [--api-key <key>] [--default-model <model>] [--set-default] [--force] [--json] [--sync]";
128
+
129
+ async function handleAdd(args: string[]): Promise<void> {
130
+ const name = args[0];
131
+ if (!name || name.startsWith("-")) {
132
+ console.error(ADD_USAGE);
133
+ process.exit(1);
134
+ }
135
+
136
+ if (!isValidProviderName(name)) {
137
+ console.error(`Invalid provider name: "${name}". Use letters, numbers, dots, underscores, or hyphens.`);
138
+ process.exit(1);
139
+ }
140
+
141
+ const restArgs = args.slice(1);
142
+ const force = consumeFlag(restArgs, "--force");
143
+ const setDefault = consumeFlag(restArgs, "--set-default");
144
+ const wantsJson = consumeFlag(restArgs, "--json");
145
+ const wantsSync = consumeFlag(restArgs, "--sync");
146
+ const apiKey = consumeFlagValue(restArgs, "--api-key");
147
+ const adapter = consumeFlagValue(restArgs, "--adapter");
148
+ const baseUrl = consumeFlagValue(restArgs, "--base-url");
149
+ const defaultModel = consumeFlagValue(restArgs, "--default-model");
150
+ rejectUnknownArgs(restArgs, ADD_USAGE);
151
+
152
+ const config = loadConfig();
153
+
154
+ if (hasOwnProvider(config.providers, name) && !force) {
155
+ console.error(`Provider "${name}" already exists. Use --force to overwrite.`);
156
+ process.exit(1);
157
+ }
158
+
159
+ let provConfig: OcxProviderConfig;
160
+ const registryEntry = getProviderRegistryEntry(name);
161
+
162
+ if (registryEntry) {
163
+ provConfig = providerConfigSeed(registryEntry);
164
+ if (apiKey) {
165
+ if (registryEntry.authKind === "forward") {
166
+ console.warn(`Warning: provider "${name}" uses ChatGPT login (forward auth); --api-key is ignored.`);
167
+ } else if (registryEntry.authKind === "oauth") {
168
+ console.warn(`Warning: provider "${name}" uses OAuth auth; --api-key is ignored. Run: ocx login ${name}`);
169
+ } else {
170
+ provConfig.apiKey = apiKey;
171
+ }
172
+ }
173
+ if (defaultModel) provConfig.defaultModel = defaultModel;
174
+ if (adapter) provConfig.adapter = adapter;
175
+ if (baseUrl) provConfig.baseUrl = baseUrl;
176
+ } else {
177
+ if (!adapter || !baseUrl) {
178
+ console.error(`Provider "${name}" is not in the registry. --adapter and --base-url are required.`);
179
+ console.error("Usage: ocx provider add <name> --adapter <adapter> --base-url <url> [--api-key <key>]");
180
+ process.exit(1);
181
+ }
182
+ provConfig = {
183
+ adapter,
184
+ baseUrl,
185
+ ...(apiKey ? { apiKey } : {}),
186
+ ...(defaultModel ? { defaultModel } : {}),
187
+ };
188
+ }
189
+
190
+ config.providers[name] = provConfig;
191
+ if (setDefault) config.defaultProvider = name;
192
+
193
+ validateAndSave(config);
194
+
195
+ if (wantsJson) {
196
+ console.log(JSON.stringify({
197
+ action: "added",
198
+ provider: name,
199
+ adapter: provConfig.adapter,
200
+ baseUrl: provConfig.baseUrl,
201
+ defaultModel: provConfig.defaultModel ?? null,
202
+ isDefault: config.defaultProvider === name,
203
+ source: registryEntry ? "registry" : "custom",
204
+ needsSync: true,
205
+ }, null, 2));
206
+ return;
207
+ }
208
+
209
+ if (wantsSync) {
210
+ const live = await findLiveProxy();
211
+ if (live) {
212
+ await syncModelsToCodex(live.port).catch(e => {
213
+ console.error(`Warning: sync failed: ${e instanceof Error ? e.message : String(e)}`);
214
+ });
215
+ }
216
+ }
217
+
218
+ const registryLabel = registryEntry ? ` (${registryEntry.label})` : "";
219
+ console.log(`✅ Provider "${name}"${registryLabel} added.`);
220
+ if (setDefault) console.log(` Set as default provider.`);
221
+ if (registryEntry?.authKind === "oauth") {
222
+ console.log(` Authenticate with: ocx login ${name}`);
223
+ }
224
+ if (registryEntry?.authKind === "key" && !apiKey) {
225
+ const envKey = `${name.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
226
+ console.log(` Set API key with: ocx provider add ${name} --api-key <key> --force`);
227
+ console.log(` Or set env var: ${envKey}`);
228
+ }
229
+ if (wantsSync) {
230
+ console.log(` Models synced to Codex.`);
231
+ } else {
232
+ console.log(` Apply to Codex: ocx sync`);
233
+ }
234
+ }
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // provider remove
238
+ // ---------------------------------------------------------------------------
239
+
240
+ function handleRemove(args: string[]): void {
241
+ const restArgs = [...args];
242
+ const wantsJson = consumeFlag(restArgs, "--json");
243
+ const name = restArgs[0];
244
+ if (!name || name.startsWith("-")) {
245
+ console.error("Usage: ocx provider remove <name> [--json]");
246
+ process.exit(1);
247
+ }
248
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider remove <name> [--json]");
249
+
250
+ const config = loadConfig();
251
+ if (!hasOwnProvider(config.providers, name)) {
252
+ console.error(`Provider "${name}" is not configured.`);
253
+ process.exit(1);
254
+ }
255
+
256
+ if (name === config.defaultProvider) {
257
+ console.error(`Cannot remove "${name}" — it is the default provider. Change the default first: ocx provider set-default <other>`);
258
+ process.exit(1);
259
+ }
260
+
261
+ if (Object.keys(config.providers).length <= 1) {
262
+ console.error("Cannot remove the last provider.");
263
+ process.exit(1);
264
+ }
265
+
266
+ delete config.providers[name];
267
+ validateAndSave(config);
268
+
269
+
270
+ if (wantsJson) {
271
+ console.log(JSON.stringify({
272
+ action: "removed",
273
+ provider: name,
274
+ remainingProviders: Object.keys(config.providers),
275
+ defaultProvider: config.defaultProvider,
276
+ needsSync: true,
277
+ }, null, 2));
278
+ return;
279
+ }
280
+
281
+ console.log(`✅ Provider "${name}" removed.`);
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // provider show
286
+ // ---------------------------------------------------------------------------
287
+
288
+ function handleShow(args: string[]): void {
289
+ const restArgs = [...args];
290
+ const wantsJson = consumeFlag(restArgs, "--json");
291
+ const name = restArgs[0];
292
+ if (!name || name.startsWith("-")) {
293
+ console.error("Usage: ocx provider show <name> [--json]");
294
+ process.exit(1);
295
+ }
296
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider show <name> [--json]");
297
+
298
+ const config = loadConfig();
299
+ if (!hasOwnProvider(config.providers, name)) {
300
+ console.error(`Provider "${name}" is not configured.`);
301
+ process.exit(1);
302
+ }
303
+
304
+ const prov = config.providers[name];
305
+ const display = { ...prov, ...(prov.apiKey ? { apiKey: maskSecret(prov.apiKey) } : {}) };
306
+
307
+ if (wantsJson) {
308
+ console.log(JSON.stringify({ name, isDefault: name === config.defaultProvider, ...display }, null, 2));
309
+ return;
310
+ }
311
+
312
+ console.log(`Provider: ${name}${name === config.defaultProvider ? " (default)" : ""}`);
313
+ console.log(` adapter: ${display.adapter}`);
314
+ console.log(` baseUrl: ${display.baseUrl}`);
315
+ if (display.authMode) console.log(` authMode: ${display.authMode}`);
316
+ if (display.apiKey) console.log(` apiKey: ${display.apiKey}`);
317
+ if (display.defaultModel) console.log(` defaultModel: ${display.defaultModel}`);
318
+ if (display.models?.length) console.log(` models: ${display.models.join(", ")}`);
319
+ }
320
+
321
+ // ---------------------------------------------------------------------------
322
+ // provider set-default
323
+ // ---------------------------------------------------------------------------
324
+
325
+ function handleSetDefault(args: string[]): void {
326
+ const restArgs = [...args];
327
+ const wantsJson = consumeFlag(restArgs, "--json");
328
+ const name = restArgs[0];
329
+ if (!name || name.startsWith("-")) {
330
+ console.error("Usage: ocx provider set-default <name> [--json]");
331
+ process.exit(1);
332
+ }
333
+ rejectUnknownArgs(restArgs.slice(1), "Usage: ocx provider set-default <name> [--json]");
334
+
335
+ const config = loadConfig();
336
+ if (!hasOwnProvider(config.providers, name)) {
337
+ console.error(`Provider "${name}" is not configured. Add it first: ocx provider add ${name}`);
338
+ process.exit(1);
339
+ }
340
+
341
+ if (config.defaultProvider === name) {
342
+ if (wantsJson) {
343
+ console.log(JSON.stringify({ action: "noop", provider: name, defaultProvider: name, needsSync: false }, null, 2));
344
+ } else {
345
+ console.log(`"${name}" is already the default provider.`);
346
+ }
347
+ return;
348
+ }
349
+
350
+ config.defaultProvider = name;
351
+ validateAndSave(config);
352
+
353
+
354
+ if (wantsJson) {
355
+ console.log(JSON.stringify({ action: "set-default", provider: name, defaultProvider: name, needsSync: true }, null, 2));
356
+ return;
357
+ }
358
+
359
+ console.log(`✅ Default provider set to "${name}".`);
360
+ }
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // Router (F2 fix: handle help flags internally, like service/codex-shim)
364
+ // ---------------------------------------------------------------------------
365
+
366
+ const PROVIDER_USAGE = `Usage: ocx provider <subcommand>
367
+
368
+ Subcommands:
369
+ list List configured and available providers
370
+ add <name> Add a provider (registry or custom)
371
+ remove <name> Remove a configured provider
372
+ show <name> Show provider config details
373
+ set-default <name> Change the default provider
374
+
375
+ Examples:
376
+ ocx provider list
377
+ ocx provider add anthropic --api-key sk-ant-...
378
+ ocx provider add my-ollama --adapter openai-chat --base-url http://localhost:11434/v1
379
+ ocx provider show anthropic --json
380
+ ocx provider set-default anthropic
381
+ ocx provider remove my-ollama`;
382
+
383
+ export async function handleProviderCommand(args: string[]): Promise<void> {
384
+ const sub = args[0];
385
+
386
+ if (!sub || sub === "help" || hasHelpFlag(args)) {
387
+ console.log(PROVIDER_USAGE);
388
+ process.exit(0);
389
+ }
390
+
391
+ const subArgs = args.slice(1);
392
+
393
+ switch (sub) {
394
+ case "list":
395
+ handleList(subArgs);
396
+ break;
397
+ case "add":
398
+ await handleAdd(subArgs);
399
+ break;
400
+ case "remove":
401
+ handleRemove(subArgs);
402
+ break;
403
+ case "show":
404
+ handleShow(subArgs);
405
+ break;
406
+ case "set-default":
407
+ handleSetDefault(subArgs);
408
+ break;
409
+ default:
410
+ console.error(`Unknown provider subcommand: ${sub}`);
411
+ console.error(PROVIDER_USAGE);
412
+ process.exit(1);
413
+ }
414
+ }
package/src/cli.ts CHANGED
@@ -537,7 +537,33 @@ switch (command) {
537
537
  runGuiUpdateWorker(jobId, channel, args[3] === "restart");
538
538
  break;
539
539
  }
540
- case "help":
540
+ case "restart": {
541
+ await handleStop();
542
+ await handleEnsure();
543
+ break;
544
+ }
545
+ case "health": {
546
+ const healthArgs = args.slice(1);
547
+ const wantsHealthJson = healthArgs.includes("--json");
548
+ const live = await findLiveProxy();
549
+ if (wantsHealthJson) {
550
+ console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null }));
551
+ } else {
552
+ console.log(live ? `Proxy healthy (PID ${live.pid}, port ${live.port})` : "Proxy not healthy");
553
+ }
554
+ process.exit(live ? 0 : 1);
555
+ }
556
+ case "provider": {
557
+ const { handleProviderCommand } = await import("./cli-provider");
558
+ await handleProviderCommand(args.slice(1));
559
+ break;
560
+ }
561
+ case "models": {
562
+ const { handleModels } = await import("./cli-models");
563
+ handleModels(args.slice(1));
564
+ break;
565
+ }
566
+ case "help":
541
567
  case "--help":
542
568
  case "-h":
543
569
  case undefined:
@@ -68,7 +68,7 @@ function poolAccountDto(
68
68
  account: CodexAccount,
69
69
  quotaResult: PoolQuotaResult,
70
70
  hasCredential: boolean,
71
- ): Record<string, unknown> {
71
+ ): CodexAuthAccountDto {
72
72
  const quota = quotaForPlan(quotaResult.quota, account.plan);
73
73
  return {
74
74
  id: account.id,
@@ -229,6 +229,17 @@ interface PoolQuotaResult {
229
229
  needsReauth: boolean;
230
230
  }
231
231
 
232
+ export interface CodexAuthAccountDto {
233
+ id: string;
234
+ email: string;
235
+ plan?: string | null;
236
+ logLabel?: string;
237
+ isMain: boolean;
238
+ quota: (StoredAccountQuota | (Omit<StoredAccountQuota, "updatedAt"> & { updatedAt: number })) | null;
239
+ needsReauth?: boolean;
240
+ hasCredential: boolean;
241
+ }
242
+
232
243
  async function fetchPoolAccountQuota(accountId: string, forceRefresh = false, configuredPlan?: string): Promise<PoolQuotaResult> {
233
244
  const existing = getAccountQuota(accountId);
234
245
  if (!forceRefresh && existing && Date.now() - existing.updatedAt < POOL_CACHE_TTL) {
@@ -313,6 +324,28 @@ export function clearCodexQuotaPrimeState(): void {
313
324
  primeInFlight = null;
314
325
  }
315
326
 
327
+ export async function listCodexAuthAccounts(config: OcxConfig, forceRefresh = false): Promise<CodexAuthAccountDto[]> {
328
+ const runtimeConfig = getRuntimeConfig(config);
329
+ const poolAccounts = (runtimeConfig.codexAccounts ?? []).filter(a => !a.isMain);
330
+ const mainInfo = await fetchMainAccountInfo(forceRefresh);
331
+ const withQuota = await mapWithConcurrency(poolAccounts, POOL_QUOTA_REFRESH_CONCURRENCY, async a => {
332
+ const cred = getCodexAccountCredential(a.id);
333
+ const quotaResult = cred
334
+ ? await fetchPoolAccountQuota(a.id, forceRefresh, a.plan)
335
+ : { quota: null, needsReauth: true };
336
+ return poolAccountDto(a, quotaResult, !!cred);
337
+ });
338
+ const main: CodexAuthAccountDto = {
339
+ id: MAIN_CODEX_ACCOUNT_ID,
340
+ email: maskEmail(mainInfo.email) ?? "Codex App login",
341
+ plan: mainInfo.plan,
342
+ isMain: true,
343
+ hasCredential: true,
344
+ quota: mainInfo.quota ? { ...quotaForPlan({ ...mainInfo.quota, updatedAt: Date.now() }, mainInfo.plan) } : null,
345
+ };
346
+ return [main, ...withQuota];
347
+ }
348
+
316
349
  export async function handleCodexAuthAPI(
317
350
  req: Request,
318
351
  url: URL,
@@ -321,25 +354,7 @@ export async function handleCodexAuthAPI(
321
354
 
322
355
  if (url.pathname === "/api/codex-auth/accounts" && req.method === "GET") {
323
356
  const forceRefresh = url.searchParams.get("refresh") === "1" || url.searchParams.get("refresh") === "true";
324
- const runtimeConfig = getRuntimeConfig(config);
325
- const poolAccounts = (runtimeConfig.codexAccounts ?? []).filter(a => !a.isMain);
326
- const mainInfo = await fetchMainAccountInfo(forceRefresh);
327
- const withQuota = await mapWithConcurrency(poolAccounts, POOL_QUOTA_REFRESH_CONCURRENCY, async a => {
328
- const cred = getCodexAccountCredential(a.id);
329
- const quotaResult = cred
330
- ? await fetchPoolAccountQuota(a.id, forceRefresh, a.plan)
331
- : { quota: null, needsReauth: true };
332
- return poolAccountDto(a, quotaResult, !!cred);
333
- });
334
- const main = {
335
- id: MAIN_CODEX_ACCOUNT_ID,
336
- email: maskEmail(mainInfo.email) ?? "Codex App login",
337
- plan: mainInfo.plan,
338
- isMain: true,
339
- hasCredential: true,
340
- quota: mainInfo.quota ? { ...quotaForPlan({ ...mainInfo.quota, updatedAt: Date.now() }, mainInfo.plan) } : null,
341
- };
342
- return jsonResponse({ accounts: [main, ...withQuota] });
357
+ return jsonResponse({ accounts: await listCodexAuthAccounts(config, forceRefresh) });
343
358
  }
344
359
 
345
360
  if (url.pathname === "/api/codex-auth/accounts" && req.method === "POST") {