@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/cli/accounts.js
DELETED
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { createInterface } from 'readline/promises';
|
|
4
|
-
import { stdin, stdout } from 'process';
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
|
-
import { dirname } from 'path';
|
|
7
|
-
import { spawn } from 'child_process';
|
|
8
|
-
import net from 'net';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import crypto from 'crypto';
|
|
12
|
-
|
|
13
|
-
const CONFIG_DIR = join(homedir(), '.codex-claude-proxy');
|
|
14
|
-
const ACCOUNTS_FILE = join(CONFIG_DIR, 'accounts.json');
|
|
15
|
-
const DEFAULT_PORT = 8081;
|
|
16
|
-
|
|
17
|
-
const OAUTH_CONFIG = {
|
|
18
|
-
clientId: 'app_EMoamEEZ73f0CkXaXp7hrann',
|
|
19
|
-
authUrl: 'https://auth.openai.com/oauth/authorize',
|
|
20
|
-
tokenUrl: 'https://auth.openai.com/oauth/token',
|
|
21
|
-
scopes: ['openid', 'profile', 'email', 'offline_access'],
|
|
22
|
-
callbackPort: 1455,
|
|
23
|
-
callbackPath: '/auth/callback'
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function loadAccounts() {
|
|
27
|
-
try {
|
|
28
|
-
if (existsSync(ACCOUNTS_FILE)) {
|
|
29
|
-
const data = readFileSync(ACCOUNTS_FILE, 'utf-8');
|
|
30
|
-
return JSON.parse(data);
|
|
31
|
-
}
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Error loading accounts:', error.message);
|
|
34
|
-
}
|
|
35
|
-
return { accounts: [], activeAccount: null, version: 1 };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function saveAccounts(data) {
|
|
39
|
-
try {
|
|
40
|
-
const dir = dirname(ACCOUNTS_FILE);
|
|
41
|
-
if (!existsSync(dir)) {
|
|
42
|
-
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
43
|
-
}
|
|
44
|
-
writeFileSync(ACCOUNTS_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
45
|
-
console.log(`\n✓ Saved ${data.accounts.length} account(s) to ${ACCOUNTS_FILE}`);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
console.error('Error saving accounts:', error.message);
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function displayAccounts(data) {
|
|
53
|
-
if (!data.accounts || data.accounts.length === 0) {
|
|
54
|
-
console.log('\nNo accounts configured.');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
console.log(`\n${data.accounts.length} account(s) saved:`);
|
|
59
|
-
data.accounts.forEach((acc, i) => {
|
|
60
|
-
const active = acc.email === data.activeAccount ? ' (ACTIVE)' : '';
|
|
61
|
-
console.log(` ${i + 1}. ${acc.email}${active}`);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function generatePKCE() {
|
|
66
|
-
const verifier = crypto.randomBytes(32).toString('base64url');
|
|
67
|
-
const challenge = crypto
|
|
68
|
-
.createHash('sha256')
|
|
69
|
-
.update(verifier)
|
|
70
|
-
.digest('base64url');
|
|
71
|
-
return { verifier, challenge };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function generateState() {
|
|
75
|
-
return crypto.randomBytes(16).toString('hex');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function getAuthorizationUrl(verifier, state, port) {
|
|
79
|
-
const challenge = crypto
|
|
80
|
-
.createHash('sha256')
|
|
81
|
-
.update(verifier)
|
|
82
|
-
.digest('base64url');
|
|
83
|
-
|
|
84
|
-
const redirectUri = `http://localhost:${port}${OAUTH_CONFIG.callbackPath}`;
|
|
85
|
-
|
|
86
|
-
const params = new URLSearchParams({
|
|
87
|
-
response_type: 'code',
|
|
88
|
-
client_id: OAUTH_CONFIG.clientId,
|
|
89
|
-
redirect_uri: redirectUri,
|
|
90
|
-
scope: OAUTH_CONFIG.scopes.join(' '),
|
|
91
|
-
code_challenge: challenge,
|
|
92
|
-
code_challenge_method: 'S256',
|
|
93
|
-
state: state,
|
|
94
|
-
prompt: 'login',
|
|
95
|
-
max_age: '0'
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
return `${OAUTH_CONFIG.authUrl}?${params.toString()}`;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function decodeJWT(token) {
|
|
102
|
-
try {
|
|
103
|
-
const parts = token.split('.');
|
|
104
|
-
if (parts.length !== 3) return null;
|
|
105
|
-
const payload = Buffer.from(parts[1], 'base64').toString('utf8');
|
|
106
|
-
return JSON.parse(payload);
|
|
107
|
-
} catch (e) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function extractAccountInfo(accessToken) {
|
|
113
|
-
const payload = decodeJWT(accessToken);
|
|
114
|
-
if (!payload) return null;
|
|
115
|
-
|
|
116
|
-
const authInfo = payload['https://api.openai.com/auth'] || {};
|
|
117
|
-
const profileInfo = payload['https://api.openai.com/profile'] || {};
|
|
118
|
-
|
|
119
|
-
return {
|
|
120
|
-
accountId: authInfo.chatgpt_account_id || null,
|
|
121
|
-
planType: authInfo.chatgpt_plan_type || 'free',
|
|
122
|
-
userId: authInfo.chatgpt_user_id || payload.sub || null,
|
|
123
|
-
email: profileInfo.email || payload.email || null,
|
|
124
|
-
expiresAt: payload.exp ? payload.exp * 1000 : null
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
async function exchangeCodeForTokens(code, verifier, port) {
|
|
129
|
-
const redirectUri = `http://localhost:${port}${OAUTH_CONFIG.callbackPath}`;
|
|
130
|
-
|
|
131
|
-
const response = await fetch(OAUTH_CONFIG.tokenUrl, {
|
|
132
|
-
method: 'POST',
|
|
133
|
-
headers: {
|
|
134
|
-
'Content-Type': 'application/x-www-form-urlencoded'
|
|
135
|
-
},
|
|
136
|
-
body: new URLSearchParams({
|
|
137
|
-
grant_type: 'authorization_code',
|
|
138
|
-
code: code,
|
|
139
|
-
redirect_uri: redirectUri,
|
|
140
|
-
client_id: OAUTH_CONFIG.clientId,
|
|
141
|
-
code_verifier: verifier
|
|
142
|
-
})
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
if (!response.ok) {
|
|
146
|
-
const error = await response.text();
|
|
147
|
-
throw new Error(`Token exchange failed: ${response.status} - ${error}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const tokens = await response.json();
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
accessToken: tokens.access_token,
|
|
154
|
-
refreshToken: tokens.refresh_token,
|
|
155
|
-
idToken: tokens.id_token,
|
|
156
|
-
expiresIn: tokens.expires_in
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function extractCodeFromInput(input) {
|
|
161
|
-
if (!input || typeof input !== 'string') {
|
|
162
|
-
throw new Error('No input provided');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const trimmed = input.trim();
|
|
166
|
-
|
|
167
|
-
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
168
|
-
try {
|
|
169
|
-
const url = new URL(trimmed);
|
|
170
|
-
const code = url.searchParams.get('code');
|
|
171
|
-
const state = url.searchParams.get('state');
|
|
172
|
-
const error = url.searchParams.get('error');
|
|
173
|
-
|
|
174
|
-
if (error) {
|
|
175
|
-
throw new Error(`OAuth error: ${error}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (!code) {
|
|
179
|
-
throw new Error('No authorization code found in URL');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { code, state };
|
|
183
|
-
} catch (e) {
|
|
184
|
-
if (e.message.includes('OAuth error') || e.message.includes('No authorization code')) {
|
|
185
|
-
throw e;
|
|
186
|
-
}
|
|
187
|
-
throw new Error('Invalid URL format');
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (trimmed.length < 10) {
|
|
192
|
-
throw new Error('Input is too short to be a valid authorization code');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return { code: trimmed, state: null };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function openBrowser(url) {
|
|
199
|
-
const platform = process.platform;
|
|
200
|
-
let command;
|
|
201
|
-
let args;
|
|
202
|
-
|
|
203
|
-
if (platform === 'darwin') {
|
|
204
|
-
command = 'open';
|
|
205
|
-
args = [url];
|
|
206
|
-
} else if (platform === 'win32') {
|
|
207
|
-
command = 'cmd';
|
|
208
|
-
args = ['/c', 'start', '', url.replace(/&/g, '^&')];
|
|
209
|
-
} else {
|
|
210
|
-
command = 'xdg-open';
|
|
211
|
-
args = [url];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
215
|
-
child.on('error', () => {
|
|
216
|
-
console.log('\n⚠ Could not open browser automatically.');
|
|
217
|
-
console.log('Please open this URL manually:', url);
|
|
218
|
-
});
|
|
219
|
-
child.unref();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function isServerRunning(port) {
|
|
223
|
-
return new Promise((resolve) => {
|
|
224
|
-
const socket = new net.Socket();
|
|
225
|
-
socket.setTimeout(1000);
|
|
226
|
-
|
|
227
|
-
socket.on('connect', () => {
|
|
228
|
-
socket.destroy();
|
|
229
|
-
resolve(true);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
socket.on('timeout', () => {
|
|
233
|
-
socket.destroy();
|
|
234
|
-
resolve(false);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
socket.on('error', () => {
|
|
238
|
-
socket.destroy();
|
|
239
|
-
resolve(false);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
socket.connect(port, 'localhost');
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function ensureServerStopped(port) {
|
|
247
|
-
const isRunning = await isServerRunning(port);
|
|
248
|
-
if (isRunning) {
|
|
249
|
-
console.error(`
|
|
250
|
-
\x1b[31mError: Proxy server is currently running on port ${port}.\x1b[0m
|
|
251
|
-
|
|
252
|
-
Please stop the server (Ctrl+C) before adding or managing accounts.
|
|
253
|
-
This ensures that your account changes are loaded correctly.
|
|
254
|
-
`);
|
|
255
|
-
process.exit(1);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function createRL() {
|
|
260
|
-
return createInterface({ input: stdin, output: stdout });
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async function addAccountManual(rl) {
|
|
264
|
-
console.log('\n=== Add ChatGPT Account (No-Browser Mode) ===\n');
|
|
265
|
-
|
|
266
|
-
const { verifier } = generatePKCE();
|
|
267
|
-
const state = generateState();
|
|
268
|
-
const url = getAuthorizationUrl(verifier, state, OAUTH_CONFIG.callbackPort);
|
|
269
|
-
|
|
270
|
-
console.log('Copy the following URL and open it in a browser on another device:\n');
|
|
271
|
-
console.log(` ${url}\n`);
|
|
272
|
-
console.log('After signing in, you will be redirected to a localhost URL.');
|
|
273
|
-
console.log('Copy the ENTIRE redirect URL or just the authorization code.\n');
|
|
274
|
-
|
|
275
|
-
const input = await rl.question('Paste the callback URL or authorization code: ');
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
const { code, state: extractedState } = extractCodeFromInput(input);
|
|
279
|
-
|
|
280
|
-
if (extractedState && extractedState !== state) {
|
|
281
|
-
throw new Error('OAuth state mismatch. Refusing to exchange the authorization code.');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
console.log('\nExchanging authorization code for tokens...');
|
|
285
|
-
const tokens = await exchangeCodeForTokens(code, verifier, OAUTH_CONFIG.callbackPort);
|
|
286
|
-
const accountInfo = extractAccountInfo(tokens.accessToken);
|
|
287
|
-
|
|
288
|
-
const data = loadAccounts();
|
|
289
|
-
|
|
290
|
-
const existingIndex = data.accounts.findIndex(a => a.email === accountInfo?.email);
|
|
291
|
-
const newAccount = {
|
|
292
|
-
email: accountInfo?.email || 'unknown',
|
|
293
|
-
accountId: accountInfo?.accountId,
|
|
294
|
-
planType: accountInfo?.planType || 'free',
|
|
295
|
-
accessToken: tokens.accessToken,
|
|
296
|
-
refreshToken: tokens.refreshToken,
|
|
297
|
-
idToken: tokens.idToken,
|
|
298
|
-
expiresAt: accountInfo?.expiresAt || (Date.now() + tokens.expiresIn * 1000),
|
|
299
|
-
addedAt: new Date().toISOString(),
|
|
300
|
-
lastUsed: null
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
if (existingIndex >= 0) {
|
|
304
|
-
data.accounts[existingIndex] = newAccount;
|
|
305
|
-
console.log(`\n⚠ Account ${newAccount.email} already exists. Updating tokens.`);
|
|
306
|
-
} else {
|
|
307
|
-
data.accounts.push(newAccount);
|
|
308
|
-
if (!data.activeAccount) {
|
|
309
|
-
data.activeAccount = newAccount.email;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
saveAccounts(data);
|
|
314
|
-
console.log(`\n✓ Successfully authenticated: ${newAccount.email}`);
|
|
315
|
-
|
|
316
|
-
} catch (error) {
|
|
317
|
-
console.error(`\n✗ Authentication failed: ${error.message}`);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function addAccountBrowser(rl) {
|
|
322
|
-
console.log('\n=== Add ChatGPT Account ===\n');
|
|
323
|
-
|
|
324
|
-
const { verifier } = generatePKCE();
|
|
325
|
-
const state = generateState();
|
|
326
|
-
const url = getAuthorizationUrl(verifier, state, OAUTH_CONFIG.callbackPort);
|
|
327
|
-
|
|
328
|
-
console.log('Opening browser for ChatGPT sign-in...');
|
|
329
|
-
console.log('(If browser does not open, copy this URL manually)\n');
|
|
330
|
-
console.log(` ${url}\n`);
|
|
331
|
-
|
|
332
|
-
openBrowser(url);
|
|
333
|
-
|
|
334
|
-
console.log('After authorization, paste the callback URL or code here.\n');
|
|
335
|
-
|
|
336
|
-
const input = await rl.question('Paste the callback URL or authorization code: ');
|
|
337
|
-
|
|
338
|
-
try {
|
|
339
|
-
const { code, state: extractedState } = extractCodeFromInput(input);
|
|
340
|
-
|
|
341
|
-
if (extractedState && extractedState !== state) {
|
|
342
|
-
throw new Error('OAuth state mismatch. Refusing to exchange the authorization code.');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
console.log('\nExchanging authorization code for tokens...');
|
|
346
|
-
const tokens = await exchangeCodeForTokens(code, verifier, OAUTH_CONFIG.callbackPort);
|
|
347
|
-
const accountInfo = extractAccountInfo(tokens.accessToken);
|
|
348
|
-
|
|
349
|
-
const data = loadAccounts();
|
|
350
|
-
|
|
351
|
-
const existingIndex = data.accounts.findIndex(a => a.email === accountInfo?.email);
|
|
352
|
-
const newAccount = {
|
|
353
|
-
email: accountInfo?.email || 'unknown',
|
|
354
|
-
accountId: accountInfo?.accountId,
|
|
355
|
-
planType: accountInfo?.planType || 'free',
|
|
356
|
-
accessToken: tokens.accessToken,
|
|
357
|
-
refreshToken: tokens.refreshToken,
|
|
358
|
-
idToken: tokens.idToken,
|
|
359
|
-
expiresAt: accountInfo?.expiresAt || (Date.now() + tokens.expiresIn * 1000),
|
|
360
|
-
addedAt: new Date().toISOString(),
|
|
361
|
-
lastUsed: null
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
if (existingIndex >= 0) {
|
|
365
|
-
data.accounts[existingIndex] = newAccount;
|
|
366
|
-
console.log(`\n⚠ Account ${newAccount.email} already exists. Updating tokens.`);
|
|
367
|
-
} else {
|
|
368
|
-
data.accounts.push(newAccount);
|
|
369
|
-
if (!data.activeAccount) {
|
|
370
|
-
data.activeAccount = newAccount.email;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
saveAccounts(data);
|
|
375
|
-
console.log(`\n✓ Successfully authenticated: ${newAccount.email}`);
|
|
376
|
-
|
|
377
|
-
} catch (error) {
|
|
378
|
-
console.error(`\n✗ Authentication failed: ${error.message}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function listAccounts() {
|
|
383
|
-
const data = loadAccounts();
|
|
384
|
-
displayAccounts(data);
|
|
385
|
-
if (data.accounts.length > 0) {
|
|
386
|
-
console.log(`\nConfig file: ${ACCOUNTS_FILE}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
async function clearAccounts(rl) {
|
|
391
|
-
const data = loadAccounts();
|
|
392
|
-
|
|
393
|
-
if (!data.accounts || data.accounts.length === 0) {
|
|
394
|
-
console.log('No accounts to clear.');
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
displayAccounts(data);
|
|
399
|
-
|
|
400
|
-
const confirm = await rl.question('\nAre you sure you want to remove all accounts? [y/N]: ');
|
|
401
|
-
if (confirm.toLowerCase() === 'y') {
|
|
402
|
-
data.accounts = [];
|
|
403
|
-
data.activeAccount = null;
|
|
404
|
-
saveAccounts(data);
|
|
405
|
-
console.log('All accounts removed.');
|
|
406
|
-
} else {
|
|
407
|
-
console.log('Cancelled.');
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
async function interactiveRemove(rl) {
|
|
412
|
-
while (true) {
|
|
413
|
-
const data = loadAccounts();
|
|
414
|
-
if (!data.accounts || data.accounts.length === 0) {
|
|
415
|
-
console.log('\nNo accounts to remove.');
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
displayAccounts(data);
|
|
420
|
-
console.log('\nEnter account number to remove (or 0 to cancel)');
|
|
421
|
-
|
|
422
|
-
const answer = await rl.question('> ');
|
|
423
|
-
const index = parseInt(answer, 10);
|
|
424
|
-
|
|
425
|
-
if (isNaN(index) || index < 0 || index > data.accounts.length) {
|
|
426
|
-
console.log('\n❌ Invalid selection.');
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (index === 0) {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const removed = data.accounts[index - 1];
|
|
435
|
-
const confirm = await rl.question(`\nAre you sure you want to remove ${removed.email}? [y/N]: `);
|
|
436
|
-
|
|
437
|
-
if (confirm.toLowerCase() === 'y') {
|
|
438
|
-
data.accounts.splice(index - 1, 1);
|
|
439
|
-
if (data.activeAccount === removed.email) {
|
|
440
|
-
data.activeAccount = data.accounts[0]?.email || null;
|
|
441
|
-
}
|
|
442
|
-
saveAccounts(data);
|
|
443
|
-
console.log(`\n✓ Removed ${removed.email}`);
|
|
444
|
-
} else {
|
|
445
|
-
console.log('\nCancelled.');
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (data.accounts.length === 0) {
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const removeMore = await rl.question('\nRemove another account? [y/N]: ');
|
|
453
|
-
if (removeMore.toLowerCase() !== 'y') {
|
|
454
|
-
break;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
async function verifyAccounts() {
|
|
460
|
-
const data = loadAccounts();
|
|
461
|
-
|
|
462
|
-
if (!data.accounts || data.accounts.length === 0) {
|
|
463
|
-
console.log('No accounts to verify.');
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
console.log('\nVerifying accounts...\n');
|
|
468
|
-
|
|
469
|
-
for (const account of data.accounts) {
|
|
470
|
-
try {
|
|
471
|
-
const response = await fetch(OAUTH_CONFIG.tokenUrl, {
|
|
472
|
-
method: 'POST',
|
|
473
|
-
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
474
|
-
body: new URLSearchParams({
|
|
475
|
-
grant_type: 'refresh_token',
|
|
476
|
-
refresh_token: account.refreshToken,
|
|
477
|
-
client_id: OAUTH_CONFIG.clientId
|
|
478
|
-
})
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
if (response.ok) {
|
|
482
|
-
console.log(` ✓ ${account.email} - OK`);
|
|
483
|
-
} else {
|
|
484
|
-
const error = await response.text();
|
|
485
|
-
console.log(` ✗ ${account.email} - ${response.status}: ${error}`);
|
|
486
|
-
}
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.log(` ✗ ${account.email} - ${error.message}`);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async function main() {
|
|
494
|
-
const args = process.argv.slice(2);
|
|
495
|
-
const command = args[0] || 'help';
|
|
496
|
-
const noBrowser = args.includes('--no-browser');
|
|
497
|
-
const port = parseInt(args.find(a => a.startsWith('--port='))?.split('=')[1]) || DEFAULT_PORT;
|
|
498
|
-
|
|
499
|
-
console.log('╔════════════════════════════════════════╗');
|
|
500
|
-
console.log('║ Codex Proxy Account Manager ║');
|
|
501
|
-
console.log('║ Use --no-browser for headless mode ║');
|
|
502
|
-
console.log('╚════════════════════════════════════════╝');
|
|
503
|
-
|
|
504
|
-
const rl = createRL();
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
switch (command) {
|
|
508
|
-
case 'add':
|
|
509
|
-
if (noBrowser) {
|
|
510
|
-
await addAccountManual(rl);
|
|
511
|
-
} else {
|
|
512
|
-
await ensureServerStopped(port);
|
|
513
|
-
await addAccountBrowser(rl);
|
|
514
|
-
}
|
|
515
|
-
break;
|
|
516
|
-
case 'list':
|
|
517
|
-
await listAccounts();
|
|
518
|
-
break;
|
|
519
|
-
case 'remove':
|
|
520
|
-
await ensureServerStopped(port);
|
|
521
|
-
await interactiveRemove(rl);
|
|
522
|
-
break;
|
|
523
|
-
case 'verify':
|
|
524
|
-
await verifyAccounts();
|
|
525
|
-
break;
|
|
526
|
-
case 'clear':
|
|
527
|
-
await ensureServerStopped(port);
|
|
528
|
-
await clearAccounts(rl);
|
|
529
|
-
break;
|
|
530
|
-
case 'help':
|
|
531
|
-
default:
|
|
532
|
-
console.log('\nUsage:');
|
|
533
|
-
console.log(' codex-proxy accounts add Add account (opens browser)');
|
|
534
|
-
console.log(' codex-proxy accounts add --no-browser Add account (manual code)');
|
|
535
|
-
console.log(' codex-proxy accounts list List all accounts');
|
|
536
|
-
console.log(' codex-proxy accounts remove Remove accounts interactively');
|
|
537
|
-
console.log(' codex-proxy accounts verify Verify account tokens');
|
|
538
|
-
console.log(' codex-proxy accounts clear Remove all accounts');
|
|
539
|
-
console.log(' codex-proxy accounts help Show this help');
|
|
540
|
-
console.log('\nAlias:');
|
|
541
|
-
console.log(' codex-claude-proxy accounts ... Legacy command name, still supported');
|
|
542
|
-
console.log('\nOptions:');
|
|
543
|
-
console.log(' --no-browser Manual authorization code input (for headless/VM servers)');
|
|
544
|
-
console.log(' --port=<port> Server port (default: 8081)');
|
|
545
|
-
console.log('\nHeadless/VM Usage:');
|
|
546
|
-
console.log(' 1. Run: codex-proxy accounts add --no-browser');
|
|
547
|
-
console.log(' 2. Copy the URL shown and open in browser on another device');
|
|
548
|
-
console.log(' 3. After login, paste the callback URL back in terminal');
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
} finally {
|
|
552
|
-
rl.close();
|
|
553
|
-
process.exit(0);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
main().catch(console.error);
|