@pikoloo/codex-proxy 1.0.7 → 1.2.2

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 (42) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +28 -11
  3. package/bin/cli.js +15 -15
  4. package/docs/ACCOUNT.md +104 -0
  5. package/docs/API.md +21 -29
  6. package/docs/ARCHITECTURE.md +9 -9
  7. package/docs/CLAUDE_INTEGRATION.md +3 -3
  8. package/docs/OAUTH.md +13 -13
  9. package/docs/OPENCLAW.md +1 -1
  10. package/docs/legal.md +6 -0
  11. package/images/dashboard-screenshot.png +0 -0
  12. package/images/readme-cover.png +0 -0
  13. package/images/settings-screenshot.png +0 -0
  14. package/package.json +19 -10
  15. package/public/css/style.css +802 -22
  16. package/public/index.html +236 -338
  17. package/public/js/app.js +140 -118
  18. package/src/account-manager.js +210 -292
  19. package/src/cli/account.js +236 -0
  20. package/src/direct-api.js +7 -9
  21. package/src/index.js +7 -7
  22. package/src/middleware/credentials.js +6 -47
  23. package/src/oauth.js +2 -1
  24. package/src/routes/{accounts-route.js → account-route.js} +25 -109
  25. package/src/routes/api-routes.js +18 -30
  26. package/src/routes/chat-route.js +3 -3
  27. package/src/routes/messages-route.js +37 -199
  28. package/src/routes/models-route.js +11 -21
  29. package/src/routes/settings-route.js +1 -41
  30. package/src/security.js +1 -1
  31. package/src/server-settings.js +30 -38
  32. package/src/utils/logger.js +14 -1
  33. package/docs/ACCOUNTS.md +0 -202
  34. package/images/demo-screenshot.png +0 -0
  35. package/images/f757093f-507b-4453-994e-f8275f8b07a9.png +0 -0
  36. package/src/account-rotation/index.js +0 -93
  37. package/src/account-rotation/rate-limits.js +0 -293
  38. package/src/account-rotation/strategies/base-strategy.js +0 -48
  39. package/src/account-rotation/strategies/index.js +0 -31
  40. package/src/account-rotation/strategies/round-robin-strategy.js +0 -42
  41. package/src/account-rotation/strategies/sticky-strategy.js +0 -97
  42. package/src/cli/accounts.js +0 -557
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Account Manager
3
- * Manages multiple ChatGPT accounts with manual switching
3
+ * Manages one local ChatGPT account for personal proxy use.
4
4
  */
5
5
 
6
6
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'fs';
@@ -9,20 +9,23 @@ import { homedir } from 'os';
9
9
  import { refreshAccessToken, extractAccountInfo } from './oauth.js';
10
10
  import { getAccountQuota as fetchQuota } from './model-api.js';
11
11
 
12
- const CONFIG_DIR = join(homedir(), '.codex-claude-proxy');
13
- const ACCOUNTS_FILE = join(CONFIG_DIR, 'accounts.json');
14
- const ACCOUNTS_DIR = join(CONFIG_DIR, 'accounts');
12
+ const CONFIG_DIR_ENV = 'CODEX_CLAUDE_PROXY_CONFIG_DIR';
13
+ const CONFIG_DIR = process.env[CONFIG_DIR_ENV] || join(homedir(), '.codex-claude-proxy');
14
+ const ACCOUNT_FILE = join(CONFIG_DIR, 'account.json');
15
+ const LEGACY_ACCOUNTS_FILE = join(CONFIG_DIR, 'accounts.json');
16
+ const ACCOUNT_AUTH_FILE = join(CONFIG_DIR, 'auth.json');
15
17
 
16
18
  const TOKEN_REFRESH_INTERVAL_MS = 55 * 60 * 1000;
17
19
  const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
18
20
 
19
- const DEFAULT_ACCOUNTS = {
21
+ const DEFAULT_STATE = {
20
22
  accounts: [],
21
23
  activeAccount: null,
22
- version: 1
24
+ version: 2
23
25
  };
24
26
 
25
27
  let autoRefreshIntervalId = null;
28
+ let startupRefreshTimeoutId = null;
26
29
  const tokenCache = new Map();
27
30
  let accountsData = null;
28
31
 
@@ -30,82 +33,106 @@ function ensureConfigDir() {
30
33
  if (!existsSync(CONFIG_DIR)) {
31
34
  mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
32
35
  }
33
- if (!existsSync(ACCOUNTS_DIR)) {
34
- mkdirSync(ACCOUNTS_DIR, { recursive: true, mode: 0o700 });
36
+ }
37
+
38
+ function normalizeAccount(account) {
39
+ if (!account || typeof account !== 'object' || !account.email) {
40
+ return null;
41
+ }
42
+
43
+ return {
44
+ ...account,
45
+ lastUsed: account.lastUsed || null
46
+ };
47
+ }
48
+
49
+ function pickConfiguredAccount(data = {}) {
50
+ const directAccount = normalizeAccount(data.account);
51
+ if (directAccount) return directAccount;
52
+
53
+ if (!Array.isArray(data.accounts) || data.accounts.length === 0) {
54
+ return null;
35
55
  }
56
+
57
+ const active = data.accounts.find((account) => account?.email === data.activeAccount);
58
+ return normalizeAccount(active || data.accounts[0]);
36
59
  }
37
60
 
38
- function sanitizeEmailForPath(email) {
39
- return email.replace(/[^a-zA-Z0-9._-]/g, '_');
61
+ function normalizeState(data = {}) {
62
+ const account = pickConfiguredAccount(data);
63
+ return {
64
+ accounts: account ? [account] : [],
65
+ activeAccount: account?.email || null,
66
+ version: 2
67
+ };
40
68
  }
41
69
 
42
- function getAccountDir(email) {
43
- const safeEmail = sanitizeEmailForPath(email);
44
- return join(ACCOUNTS_DIR, safeEmail);
70
+ function serializeState(state) {
71
+ const account = state.accounts[0] || null;
72
+ return {
73
+ account,
74
+ activeAccount: account?.email || null,
75
+ version: 2
76
+ };
45
77
  }
46
78
 
47
- function getAccountAuthFile(email) {
48
- return join(getAccountDir(email), 'auth.json');
79
+ function readJsonFile(path) {
80
+ return JSON.parse(readFileSync(path, 'utf8'));
81
+ }
82
+
83
+ function writeState(data) {
84
+ ensureConfigDir();
85
+ accountsData = normalizeState(data);
86
+ writeFileSync(ACCOUNT_FILE, JSON.stringify(serializeState(accountsData), null, 2), { mode: 0o600 });
87
+ return accountsData;
49
88
  }
50
89
 
51
90
  function loadAccounts() {
52
91
  if (accountsData !== null) {
53
92
  return accountsData;
54
93
  }
55
-
94
+
56
95
  ensureConfigDir();
57
-
58
- if (!existsSync(ACCOUNTS_FILE)) {
59
- accountsData = { ...DEFAULT_ACCOUNTS };
60
- return accountsData;
61
- }
62
-
96
+
63
97
  try {
64
- const data = JSON.parse(readFileSync(ACCOUNTS_FILE, 'utf8'));
65
- accountsData = { ...DEFAULT_ACCOUNTS, ...data };
66
- return accountsData;
67
- } catch (e) {
68
- console.error('[AccountManager] Error loading accounts:', e.message);
69
- accountsData = { ...DEFAULT_ACCOUNTS };
70
- return accountsData;
98
+ if (existsSync(ACCOUNT_FILE)) {
99
+ accountsData = normalizeState(readJsonFile(ACCOUNT_FILE));
100
+ return accountsData;
101
+ }
102
+
103
+ if (existsSync(LEGACY_ACCOUNTS_FILE)) {
104
+ return writeState(readJsonFile(LEGACY_ACCOUNTS_FILE));
105
+ }
106
+ } catch (error) {
107
+ console.error('[AccountManager] Error loading account:', error.message);
71
108
  }
109
+
110
+ accountsData = { ...DEFAULT_STATE };
111
+ return accountsData;
72
112
  }
73
113
 
74
114
  function saveAccounts(data) {
75
- ensureConfigDir();
76
- accountsData = data;
77
- writeFileSync(ACCOUNTS_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
115
+ return writeState(data);
78
116
  }
79
117
 
80
118
  function save() {
81
- if (accountsData === null) {
82
- loadAccounts();
83
- }
84
- ensureConfigDir();
85
- writeFileSync(ACCOUNTS_FILE, JSON.stringify(accountsData, null, 2), { mode: 0o600 });
119
+ return writeState(accountsData || loadAccounts());
86
120
  }
87
121
 
88
122
  function getAccount(email) {
89
- const data = loadAccounts();
90
- return data.accounts.find(a => a.email === email) || null;
123
+ const account = getActiveAccount();
124
+ if (!email) return account;
125
+ return account?.email === email ? account : null;
91
126
  }
92
127
 
93
128
  function getActiveAccount() {
94
129
  const data = loadAccounts();
95
- if (!data.activeAccount) return null;
96
- return data.accounts.find(a => a.email === data.activeAccount) || null;
130
+ return data.accounts[0] || null;
97
131
  }
98
132
 
99
133
  function updateAccountAuth(account) {
100
134
  if (!account) return;
101
-
102
- const accountDir = getAccountDir(account.email);
103
- const authFile = getAccountAuthFile(account.email);
104
-
105
- if (!existsSync(accountDir)) {
106
- mkdirSync(accountDir, { recursive: true, mode: 0o700 });
107
- }
108
-
135
+
109
136
  const authData = {
110
137
  auth_mode: 'chatgpt',
111
138
  OPENAI_API_KEY: null,
@@ -117,223 +144,181 @@ function updateAccountAuth(account) {
117
144
  },
118
145
  last_refresh: new Date().toISOString()
119
146
  };
120
-
147
+
121
148
  try {
122
- writeFileSync(authFile, JSON.stringify(authData, null, 2), { mode: 0o600 });
149
+ ensureConfigDir();
150
+ writeFileSync(ACCOUNT_AUTH_FILE, JSON.stringify(authData, null, 2), { mode: 0o600 });
123
151
  console.log(`[AccountManager] Updated auth for: ${account.email}`);
124
- } catch (e) {
125
- console.error('[AccountManager] Failed to update auth:', e.message);
152
+ } catch (error) {
153
+ console.error('[AccountManager] Failed to update auth:', error.message);
126
154
  }
127
155
  }
128
156
 
129
- function setActiveAccount(email) {
130
- const data = loadAccounts();
131
- const account = data.accounts.find(a => a.email === email);
132
-
133
- if (!account) {
134
- return { success: false, message: `Account not found: ${email}` };
157
+ function setConfiguredAccount(account) {
158
+ const normalized = normalizeAccount(account);
159
+ if (!normalized) {
160
+ return { success: false, message: 'No valid account provided' };
135
161
  }
136
-
137
- data.activeAccount = email;
138
- saveAccounts(data);
139
-
140
- updateAccountAuth(account);
141
-
142
- return { success: true, message: `Switched to account: ${email}` };
162
+
163
+ const state = saveAccounts({
164
+ accounts: [normalized],
165
+ activeAccount: normalized.email,
166
+ version: 2
167
+ });
168
+ updateAccountAuth(state.accounts[0]);
169
+ return { success: true, message: `Configured account: ${normalized.email}` };
143
170
  }
144
171
 
145
- function removeAccount(email) {
146
- const data = loadAccounts();
147
- const index = data.accounts.findIndex(a => a.email === email);
148
-
149
- if (index < 0) {
150
- return { success: false, message: `Account not found: ${email}` };
172
+ function removeAccount() {
173
+ const account = getActiveAccount();
174
+ if (!account) {
175
+ return { success: false, message: 'No account configured' };
151
176
  }
152
-
153
- const accountDir = getAccountDir(email);
177
+
154
178
  try {
155
- if (existsSync(accountDir)) {
156
- rmSync(accountDir, { recursive: true, force: true });
157
- }
158
- } catch (e) {
159
- console.error('[AccountManager] Failed to remove account directory:', e.message);
160
- }
161
-
162
- data.accounts.splice(index, 1);
163
-
164
- if (data.activeAccount === email) {
165
- data.activeAccount = data.accounts[0]?.email || null;
166
-
167
- if (data.activeAccount) {
168
- const newActive = data.accounts.find(a => a.email === data.activeAccount);
169
- updateAccountAuth(newActive);
179
+ if (existsSync(ACCOUNT_AUTH_FILE)) {
180
+ rmSync(ACCOUNT_AUTH_FILE, { force: true });
170
181
  }
182
+ } catch (error) {
183
+ console.error('[AccountManager] Failed to remove auth file:', error.message);
171
184
  }
172
-
173
- saveAccounts(data);
174
-
175
- return { success: true, message: `Account removed: ${email}` };
185
+
186
+ saveAccounts(DEFAULT_STATE);
187
+ tokenCache.clear();
188
+ return { success: true, message: `Account removed: ${account.email}` };
176
189
  }
177
190
 
178
191
  function listAccounts() {
179
- const data = loadAccounts();
180
-
181
- const accounts = data.accounts.map(account => {
182
- const info = extractAccountInfo(account.accessToken);
183
- return {
184
- email: account.email,
185
- accountId: account.accountId,
186
- planType: info?.planType || account.planType || 'unknown',
187
- addedAt: account.addedAt,
188
- lastUsed: account.lastUsed,
189
- isActive: account.email === data.activeAccount,
190
- tokenExpired: info?.expiresAt ? info.expiresAt < Date.now() : false,
191
- quota: account.quota || null
192
- };
193
- });
194
-
192
+ const account = getActiveAccount();
193
+ const info = account ? extractAccountInfo(account.accessToken) : null;
194
+ const publicAccount = account ? {
195
+ email: account.email,
196
+ accountId: account.accountId,
197
+ planType: info?.planType || account.planType || 'unknown',
198
+ addedAt: account.addedAt,
199
+ lastUsed: account.lastUsed,
200
+ isActive: true,
201
+ tokenExpired: info?.expiresAt ? info.expiresAt < Date.now() : false,
202
+ quota: account.quota || null
203
+ } : null;
204
+
195
205
  return {
196
- accounts,
197
- activeAccount: data.activeAccount,
198
- total: accounts.length
206
+ account: publicAccount,
207
+ activeAccount: publicAccount?.email || null,
208
+ total: publicAccount ? 1 : 0
199
209
  };
200
210
  }
201
211
 
202
212
  function updateAccountQuota(email, quotaData) {
203
213
  const data = loadAccounts();
204
- const account = data.accounts.find(a => a.email === email);
205
-
206
- if (!account) {
207
- return { success: false, message: `Account not found: ${email}` };
214
+ const account = data.accounts[0];
215
+
216
+ if (!account || (email && account.email !== email)) {
217
+ return { success: false, message: email ? `Account not found: ${email}` : 'No account configured' };
208
218
  }
209
-
219
+
210
220
  account.quota = {
211
221
  ...quotaData,
212
222
  lastChecked: new Date().toISOString()
213
223
  };
214
-
224
+
215
225
  saveAccounts(data);
216
- return { success: true, message: `Quota updated for: ${email}` };
226
+ return { success: true, message: `Quota updated for: ${account.email}` };
217
227
  }
218
228
 
219
229
  function getAccountQuota(email) {
220
- const data = loadAccounts();
221
- const account = data.accounts.find(a => a.email === email);
222
-
223
- if (!account) {
230
+ const account = getActiveAccount();
231
+ if (!account || (email && account.email !== email)) {
224
232
  return null;
225
233
  }
226
-
227
234
  return account.quota || null;
228
235
  }
229
236
 
230
237
  function isTokenExpiredOrExpiringSoon(account) {
231
- if (!account.expiresAt) return true;
238
+ if (!account?.expiresAt) return true;
232
239
  return Date.now() >= (account.expiresAt - TOKEN_EXPIRY_BUFFER_MS);
233
240
  }
234
241
 
235
242
  async function refreshAccountToken(email) {
236
- const data = loadAccounts();
237
- const account = data.accounts.find(a => a.email === email);
238
-
239
- if (!account) {
240
- return { success: false, message: `Account not found: ${email}` };
243
+ const account = getActiveAccount();
244
+
245
+ if (!account || (email && account.email !== email)) {
246
+ return { success: false, message: email ? `Account not found: ${email}` : 'No account configured' };
241
247
  }
242
-
248
+
243
249
  if (!account.refreshToken) {
244
250
  return { success: false, message: 'No refresh token available' };
245
251
  }
246
-
252
+
247
253
  try {
248
254
  const tokens = await refreshAccessToken(account.refreshToken);
249
255
  const accountInfo = extractAccountInfo(tokens.accessToken);
250
-
251
- const index = data.accounts.findIndex(a => a.email === email);
252
- if (index >= 0) {
253
- data.accounts[index].accessToken = tokens.accessToken;
254
- data.accounts[index].refreshToken = tokens.refreshToken || data.accounts[index].refreshToken;
255
- data.accounts[index].idToken = tokens.idToken || data.accounts[index].idToken;
256
- data.accounts[index].expiresAt = accountInfo?.expiresAt || (Date.now() + tokens.expiresIn * 1000);
257
- if (accountInfo?.planType) {
258
- data.accounts[index].planType = accountInfo.planType;
259
- }
260
- saveAccounts(data);
261
-
262
- tokenCache.set(email, {
263
- token: tokens.accessToken,
264
- extractedAt: Date.now()
265
- });
266
-
267
- if (data.activeAccount === email) {
268
- updateAccountAuth(data.accounts[index]);
269
- }
270
- }
271
-
272
- console.log(`[AccountManager] Token refreshed for: ${email}`);
273
-
274
- // Auto-fetch quota after refresh
256
+ const updatedAccount = {
257
+ ...account,
258
+ accessToken: tokens.accessToken,
259
+ refreshToken: tokens.refreshToken || account.refreshToken,
260
+ idToken: tokens.idToken || account.idToken,
261
+ expiresAt: accountInfo?.expiresAt || (Date.now() + tokens.expiresIn * 1000),
262
+ planType: accountInfo?.planType || account.planType
263
+ };
264
+
265
+ setConfiguredAccount(updatedAccount);
266
+ tokenCache.set(updatedAccount.email, {
267
+ token: updatedAccount.accessToken,
268
+ extractedAt: Date.now()
269
+ });
270
+
271
+ console.log(`[AccountManager] Token refreshed for: ${updatedAccount.email}`);
272
+
275
273
  try {
276
- const quotaData = await fetchQuota(tokens.accessToken, accountInfo.accountId);
277
- updateAccountQuota(email, quotaData);
278
- console.log(`[AccountManager] Quota refreshed for: ${email}`);
279
- } catch (qErr) {
280
- console.warn(`[AccountManager] Failed to auto-fetch quota for ${email}: ${qErr.message}`);
274
+ const quotaData = await fetchQuota(updatedAccount.accessToken, updatedAccount.accountId);
275
+ updateAccountQuota(updatedAccount.email, quotaData);
276
+ console.log(`[AccountManager] Quota refreshed for: ${updatedAccount.email}`);
277
+ } catch (error) {
278
+ console.warn(`[AccountManager] Failed to auto-fetch quota for ${updatedAccount.email}: ${error.message}`);
281
279
  }
282
280
 
283
- return { success: true, message: `Token refreshed for: ${email}` };
281
+ return { success: true, message: `Token refreshed for: ${updatedAccount.email}` };
284
282
  } catch (error) {
285
- console.error(`[AccountManager] Token refresh failed for ${email}:`, error.message);
283
+ console.error(`[AccountManager] Token refresh failed for ${account.email}:`, error.message);
286
284
  return { success: false, message: `Token refresh failed: ${error.message}` };
287
285
  }
288
286
  }
289
287
 
290
- async function refreshAllAccounts() {
291
- const data = loadAccounts();
292
- const results = [];
293
-
294
- for (const account of data.accounts) {
295
- if (account.refreshToken) {
296
- const result = await refreshAccountToken(account.email);
297
- results.push({ email: account.email, ...result });
298
- }
299
- }
300
-
301
- return results;
302
- }
303
-
304
288
  function startAutoRefresh() {
305
289
  if (autoRefreshIntervalId) {
306
290
  clearInterval(autoRefreshIntervalId);
307
291
  }
308
-
309
- const startupRefreshTimeout = setTimeout(async () => {
310
- console.log('[AccountManager] Startup: refreshing all account tokens...');
311
- const data = loadAccounts();
312
- for (const account of data.accounts) {
313
- if (account.refreshToken) {
314
- console.log(`[AccountManager] Startup refresh for ${account.email}`);
315
- await refreshAccountToken(account.email);
316
- }
292
+ if (startupRefreshTimeoutId) {
293
+ clearTimeout(startupRefreshTimeoutId);
294
+ }
295
+
296
+ startupRefreshTimeoutId = setTimeout(async () => {
297
+ const account = getActiveAccount();
298
+ if (account?.refreshToken) {
299
+ console.log(`[AccountManager] Startup refresh for ${account.email}`);
300
+ await refreshAccountToken(account.email);
317
301
  }
318
302
  }, 2000);
319
- startupRefreshTimeout.unref?.();
320
-
303
+ startupRefreshTimeoutId.unref?.();
304
+
321
305
  autoRefreshIntervalId = setInterval(async () => {
322
- const data = loadAccounts();
323
-
324
- for (const account of data.accounts) {
325
- if (account.refreshToken) {
326
- console.log(`[AccountManager] Periodic refresh for ${account.email}`);
327
- await refreshAccountToken(account.email);
328
- }
306
+ const account = getActiveAccount();
307
+ if (account?.refreshToken) {
308
+ console.log(`[AccountManager] Periodic refresh for ${account.email}`);
309
+ await refreshAccountToken(account.email);
329
310
  }
330
311
  }, TOKEN_REFRESH_INTERVAL_MS);
331
312
  autoRefreshIntervalId.unref?.();
332
-
313
+
333
314
  console.log('[AccountManager] Auto-refresh started (every 55 minutes)');
334
315
  }
335
316
 
336
317
  function stopAutoRefresh() {
318
+ if (startupRefreshTimeoutId) {
319
+ clearTimeout(startupRefreshTimeoutId);
320
+ startupRefreshTimeoutId = null;
321
+ }
337
322
  if (autoRefreshIntervalId) {
338
323
  clearInterval(autoRefreshIntervalId);
339
324
  autoRefreshIntervalId = null;
@@ -356,58 +341,27 @@ function setCachedToken(email, token) {
356
341
  async function refreshActiveAccount() {
357
342
  const account = getActiveAccount();
358
343
  if (!account) {
359
- return { success: false, message: 'No active account' };
360
- }
361
-
362
- if (!account.refreshToken) {
363
- return { success: false, message: 'No refresh token available' };
364
- }
365
-
366
- try {
367
- const tokens = await refreshAccessToken(account.refreshToken);
368
- const accountInfo = extractAccountInfo(tokens.accessToken);
369
-
370
- const data = loadAccounts();
371
- const index = data.accounts.findIndex(a => a.email === account.email);
372
-
373
- if (index >= 0) {
374
- data.accounts[index].accessToken = tokens.accessToken;
375
- data.accounts[index].refreshToken = tokens.refreshToken || data.accounts[index].refreshToken;
376
- data.accounts[index].idToken = tokens.idToken || data.accounts[index].idToken;
377
- data.accounts[index].expiresAt = accountInfo?.expiresAt || (Date.now() + tokens.expiresIn * 1000);
378
- if (accountInfo?.planType) {
379
- data.accounts[index].planType = accountInfo.planType;
380
- }
381
- saveAccounts(data);
382
-
383
- updateAccountAuth(data.accounts[index]);
384
- console.log(`[AccountManager] Active account token refreshed: ${account.email}`);
385
- }
386
-
387
- return { success: true, message: `Token refreshed for: ${account.email}` };
388
- } catch (error) {
389
- console.error(`[AccountManager] Token refresh failed for ${account.email}:`, error.message);
390
- return { success: false, message: `Token refresh failed: ${error.message}` };
344
+ return { success: false, message: 'No account configured' };
391
345
  }
346
+ return refreshAccountToken(account.email);
392
347
  }
393
348
 
394
349
  function importFromCodex() {
395
- const codeAuthFile = join(homedir(), '.codex', 'auth.json');
396
-
350
+ const codexAuthFile = join(homedir(), '.codex', 'auth.json');
351
+
397
352
  try {
398
- if (!existsSync(codeAuthFile)) {
353
+ if (!existsSync(codexAuthFile)) {
399
354
  return { success: false, message: 'No Codex auth.json found' };
400
355
  }
401
-
402
- const codexAuth = JSON.parse(readFileSync(codeAuthFile, 'utf8'));
403
-
356
+
357
+ const codexAuth = readJsonFile(codexAuthFile);
358
+
404
359
  if (!codexAuth.tokens?.access_token) {
405
360
  return { success: false, message: 'No valid tokens in Codex auth.json' };
406
361
  }
407
-
362
+
408
363
  const info = extractAccountInfo(codexAuth.tokens.access_token);
409
-
410
- const newAccount = {
364
+ const account = {
411
365
  email: info?.email || 'imported@unknown.com',
412
366
  accountId: codexAuth.tokens.account_id,
413
367
  planType: info?.planType || 'unknown',
@@ -419,26 +373,12 @@ function importFromCodex() {
419
373
  lastUsed: new Date().toISOString(),
420
374
  source: 'imported'
421
375
  };
422
-
423
- const data = loadAccounts();
424
-
425
- const existingIndex = data.accounts.findIndex(a => a.email === newAccount.email);
426
- if (existingIndex >= 0) {
427
- data.accounts[existingIndex] = newAccount;
428
- } else {
429
- data.accounts.push(newAccount);
430
- }
431
-
432
- if (!data.activeAccount) {
433
- data.activeAccount = newAccount.email;
434
- }
435
-
436
- saveAccounts(data);
437
- updateAccountAuth(newAccount);
438
-
376
+
377
+ setConfiguredAccount(account);
378
+
439
379
  return {
440
380
  success: true,
441
- message: `Imported account: ${newAccount.email} (${newAccount.planType})`
381
+ message: `Imported account: ${account.email} (${account.planType})`
442
382
  };
443
383
  } catch (error) {
444
384
  return { success: false, message: `Import failed: ${error.message}` };
@@ -446,34 +386,19 @@ function importFromCodex() {
446
386
  }
447
387
 
448
388
  function getStatus() {
449
- const data = loadAccounts();
450
- const accounts = data.accounts.map(a => {
451
- const info = extractAccountInfo(a.accessToken);
452
- return {
453
- email: a.email,
454
- planType: a.planType,
455
- isActive: a.email === data.activeAccount,
456
- quota: a.quota || null,
457
- tokenExpired: info?.expiresAt ? info.expiresAt < Date.now() : false,
458
- lastUsed: a.lastUsed
459
- };
460
- });
461
-
389
+ const { account, activeAccount, total } = listAccounts();
462
390
  return {
463
- total: data.accounts.length,
464
- active: data.activeAccount,
465
- accounts
391
+ total,
392
+ active: activeAccount,
393
+ account
466
394
  };
467
395
  }
468
396
 
469
397
  function ensureAccountsPersist() {
470
- const data = loadAccounts();
471
- if (data.accounts.length > 0 && data.activeAccount) {
472
- const active = data.accounts.find(a => a.email === data.activeAccount);
473
- if (active) {
474
- updateAccountAuth(active);
475
- console.log(`[AccountManager] Restored active account: ${active.email}`);
476
- }
398
+ const account = getActiveAccount();
399
+ if (account) {
400
+ updateAccountAuth(account);
401
+ console.log(`[AccountManager] Restored account: ${account.email}`);
477
402
  }
478
403
  }
479
404
 
@@ -483,12 +408,11 @@ export {
483
408
  save,
484
409
  getAccount,
485
410
  getActiveAccount,
486
- setActiveAccount,
411
+ setConfiguredAccount,
487
412
  removeAccount,
488
413
  listAccounts,
489
414
  refreshActiveAccount,
490
415
  refreshAccountToken,
491
- refreshAllAccounts,
492
416
  importFromCodex,
493
417
  getStatus,
494
418
  updateAccountAuth,
@@ -501,28 +425,22 @@ export {
501
425
  getCachedToken,
502
426
  setCachedToken,
503
427
  TOKEN_REFRESH_INTERVAL_MS,
504
- ACCOUNTS_FILE,
428
+ ACCOUNT_FILE,
429
+ LEGACY_ACCOUNTS_FILE,
430
+ ACCOUNT_AUTH_FILE,
505
431
  CONFIG_DIR
506
432
  };
507
433
 
508
434
  export default {
509
435
  getActiveAccount,
510
- setActiveAccount,
436
+ setConfiguredAccount,
511
437
  removeAccount,
512
438
  listAccounts,
513
439
  refreshActiveAccount,
514
440
  refreshAccountToken,
515
- refreshAllAccounts,
516
441
  importFromCodex,
517
442
  getStatus,
518
443
  ensureAccountsPersist,
519
444
  updateAccountQuota,
520
- getAccountQuota,
521
- startAutoRefresh,
522
- stopAutoRefresh,
523
- isTokenExpiredOrExpiringSoon,
524
- getCachedToken,
525
- setCachedToken,
526
- save,
527
- getAccount
445
+ getAccountQuota
528
446
  };