@pikoloo/codex-proxy 1.1.0 → 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.
- package/CHANGELOG.md +76 -0
- package/README.md +28 -11
- package/bin/cli.js +15 -15
- package/docs/ACCOUNT.md +104 -0
- package/docs/API.md +26 -19
- package/docs/ARCHITECTURE.md +9 -9
- package/docs/CLAUDE_INTEGRATION.md +3 -3
- package/docs/OAUTH.md +13 -13
- package/docs/OPENCLAW.md +1 -1
- package/docs/legal.md +6 -0
- package/package.json +10 -8
- package/public/css/style.css +4 -34
- package/public/index.html +105 -166
- package/public/js/app.js +23 -58
- package/src/account-manager.js +210 -292
- package/src/cli/account.js +236 -0
- package/src/direct-api.js +7 -9
- package/src/index.js +7 -7
- package/src/middleware/credentials.js +6 -47
- package/src/oauth.js +2 -1
- package/src/routes/{accounts-route.js → account-route.js} +25 -109
- package/src/routes/api-routes.js +18 -26
- package/src/routes/chat-route.js +2 -2
- package/src/routes/messages-route.js +29 -189
- package/src/routes/models-route.js +11 -21
- package/src/security.js +1 -1
- package/src/server-settings.js +1 -8
- package/docs/ACCOUNTS.md +0 -202
- package/src/account-rotation/index.js +0 -130
- package/src/account-rotation/rate-limits.js +0 -293
- package/src/cli/accounts.js +0 -557
package/src/account-manager.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Account Manager
|
|
3
|
-
* Manages
|
|
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
|
|
13
|
-
const
|
|
14
|
-
const
|
|
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
|
|
21
|
+
const DEFAULT_STATE = {
|
|
20
22
|
accounts: [],
|
|
21
23
|
activeAccount: null,
|
|
22
|
-
version:
|
|
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
|
-
|
|
34
|
-
|
|
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
|
|
39
|
-
|
|
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
|
|
43
|
-
const
|
|
44
|
-
return
|
|
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
|
|
48
|
-
return
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
125
|
-
console.error('[AccountManager] Failed to update auth:',
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('[AccountManager] Failed to update auth:', error.message);
|
|
126
154
|
}
|
|
127
155
|
}
|
|
128
156
|
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
|
146
|
-
const
|
|
147
|
-
|
|
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(
|
|
156
|
-
rmSync(
|
|
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(
|
|
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
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
197
|
-
activeAccount:
|
|
198
|
-
total:
|
|
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
|
|
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
|
|
221
|
-
|
|
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
|
|
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
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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(
|
|
277
|
-
updateAccountQuota(email, quotaData);
|
|
278
|
-
console.log(`[AccountManager] Quota refreshed for: ${email}`);
|
|
279
|
-
} catch (
|
|
280
|
-
console.warn(`[AccountManager] Failed to auto-fetch quota for ${email}: ${
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
320
|
-
|
|
303
|
+
startupRefreshTimeoutId.unref?.();
|
|
304
|
+
|
|
321
305
|
autoRefreshIntervalId = setInterval(async () => {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
|
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
|
|
396
|
-
|
|
350
|
+
const codexAuthFile = join(homedir(), '.codex', 'auth.json');
|
|
351
|
+
|
|
397
352
|
try {
|
|
398
|
-
if (!existsSync(
|
|
353
|
+
if (!existsSync(codexAuthFile)) {
|
|
399
354
|
return { success: false, message: 'No Codex auth.json found' };
|
|
400
355
|
}
|
|
401
|
-
|
|
402
|
-
const codexAuth =
|
|
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
|
-
|
|
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: ${
|
|
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
|
|
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
|
|
464
|
-
active:
|
|
465
|
-
|
|
391
|
+
total,
|
|
392
|
+
active: activeAccount,
|
|
393
|
+
account
|
|
466
394
|
};
|
|
467
395
|
}
|
|
468
396
|
|
|
469
397
|
function ensureAccountsPersist() {
|
|
470
|
-
const
|
|
471
|
-
if (
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|