commons-proxy 2.0.0

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +757 -0
  3. package/bin/cli.js +146 -0
  4. package/package.json +97 -0
  5. package/public/Complaint Details.pdf +0 -0
  6. package/public/Cyber Crime Portal.pdf +0 -0
  7. package/public/app.js +229 -0
  8. package/public/css/src/input.css +523 -0
  9. package/public/css/style.css +1 -0
  10. package/public/favicon.png +0 -0
  11. package/public/index.html +549 -0
  12. package/public/js/components/account-manager.js +356 -0
  13. package/public/js/components/add-account-modal.js +414 -0
  14. package/public/js/components/claude-config.js +420 -0
  15. package/public/js/components/dashboard/charts.js +605 -0
  16. package/public/js/components/dashboard/filters.js +362 -0
  17. package/public/js/components/dashboard/stats.js +110 -0
  18. package/public/js/components/dashboard.js +236 -0
  19. package/public/js/components/logs-viewer.js +100 -0
  20. package/public/js/components/models.js +36 -0
  21. package/public/js/components/server-config.js +349 -0
  22. package/public/js/config/constants.js +102 -0
  23. package/public/js/data-store.js +375 -0
  24. package/public/js/settings-store.js +58 -0
  25. package/public/js/store.js +99 -0
  26. package/public/js/translations/en.js +367 -0
  27. package/public/js/translations/id.js +412 -0
  28. package/public/js/translations/pt.js +308 -0
  29. package/public/js/translations/tr.js +358 -0
  30. package/public/js/translations/zh.js +373 -0
  31. package/public/js/utils/account-actions.js +189 -0
  32. package/public/js/utils/error-handler.js +96 -0
  33. package/public/js/utils/model-config.js +42 -0
  34. package/public/js/utils/ui-logger.js +143 -0
  35. package/public/js/utils/validators.js +77 -0
  36. package/public/js/utils.js +69 -0
  37. package/public/proxy-server-64.png +0 -0
  38. package/public/views/accounts.html +361 -0
  39. package/public/views/dashboard.html +484 -0
  40. package/public/views/logs.html +97 -0
  41. package/public/views/models.html +331 -0
  42. package/public/views/settings.html +1327 -0
  43. package/src/account-manager/credentials.js +378 -0
  44. package/src/account-manager/index.js +462 -0
  45. package/src/account-manager/onboarding.js +112 -0
  46. package/src/account-manager/rate-limits.js +369 -0
  47. package/src/account-manager/storage.js +160 -0
  48. package/src/account-manager/strategies/base-strategy.js +109 -0
  49. package/src/account-manager/strategies/hybrid-strategy.js +339 -0
  50. package/src/account-manager/strategies/index.js +79 -0
  51. package/src/account-manager/strategies/round-robin-strategy.js +76 -0
  52. package/src/account-manager/strategies/sticky-strategy.js +138 -0
  53. package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
  54. package/src/account-manager/strategies/trackers/index.js +9 -0
  55. package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
  56. package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
  57. package/src/auth/database.js +169 -0
  58. package/src/auth/oauth.js +548 -0
  59. package/src/auth/token-extractor.js +117 -0
  60. package/src/cli/accounts.js +648 -0
  61. package/src/cloudcode/index.js +29 -0
  62. package/src/cloudcode/message-handler.js +510 -0
  63. package/src/cloudcode/model-api.js +248 -0
  64. package/src/cloudcode/rate-limit-parser.js +235 -0
  65. package/src/cloudcode/request-builder.js +93 -0
  66. package/src/cloudcode/session-manager.js +47 -0
  67. package/src/cloudcode/sse-parser.js +121 -0
  68. package/src/cloudcode/sse-streamer.js +293 -0
  69. package/src/cloudcode/streaming-handler.js +615 -0
  70. package/src/config.js +125 -0
  71. package/src/constants.js +407 -0
  72. package/src/errors.js +242 -0
  73. package/src/fallback-config.js +29 -0
  74. package/src/format/content-converter.js +193 -0
  75. package/src/format/index.js +20 -0
  76. package/src/format/request-converter.js +255 -0
  77. package/src/format/response-converter.js +120 -0
  78. package/src/format/schema-sanitizer.js +673 -0
  79. package/src/format/signature-cache.js +88 -0
  80. package/src/format/thinking-utils.js +648 -0
  81. package/src/index.js +148 -0
  82. package/src/modules/usage-stats.js +205 -0
  83. package/src/providers/anthropic-provider.js +258 -0
  84. package/src/providers/base-provider.js +157 -0
  85. package/src/providers/cloudcode.js +94 -0
  86. package/src/providers/copilot.js +399 -0
  87. package/src/providers/github-provider.js +287 -0
  88. package/src/providers/google-provider.js +192 -0
  89. package/src/providers/index.js +211 -0
  90. package/src/providers/openai-compatible.js +265 -0
  91. package/src/providers/openai-provider.js +271 -0
  92. package/src/providers/openrouter-provider.js +325 -0
  93. package/src/providers/setup.js +83 -0
  94. package/src/server.js +870 -0
  95. package/src/utils/claude-config.js +245 -0
  96. package/src/utils/helpers.js +51 -0
  97. package/src/utils/logger.js +142 -0
  98. package/src/utils/native-module-helper.js +162 -0
  99. package/src/webui/index.js +1134 -0
@@ -0,0 +1,648 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Account Management CLI
5
+ *
6
+ * Interactive CLI for adding and managing accounts
7
+ * for CommonsProxy. Supports multiple providers.
8
+ *
9
+ * Usage:
10
+ * node src/cli/accounts.js # Interactive mode
11
+ * node src/cli/accounts.js add # Add new account(s)
12
+ * node src/cli/accounts.js add --provider=copilot # Add Copilot account
13
+ * node src/cli/accounts.js list # List all accounts
14
+ * node src/cli/accounts.js clear # Remove all accounts
15
+ */
16
+
17
+ import { createInterface } from 'readline/promises';
18
+ import { stdin, stdout } from 'process';
19
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
20
+ import { dirname } from 'path';
21
+ import { spawn } from 'child_process';
22
+ import net from 'net';
23
+ import { ACCOUNT_CONFIG_PATH, DEFAULT_PORT, MAX_ACCOUNTS } from '../constants.js';
24
+ import {
25
+ getAuthorizationUrl,
26
+ startCallbackServer,
27
+ completeOAuthFlow,
28
+ refreshAccessToken,
29
+ getUserEmail,
30
+ extractCodeFromInput
31
+ } from '../auth/oauth.js';
32
+
33
+ const SERVER_PORT = process.env.PORT || DEFAULT_PORT;
34
+
35
+ /**
36
+ * Check if the Antigravity Proxy server is running
37
+ * Returns true if port is occupied
38
+ */
39
+ function isServerRunning() {
40
+ return new Promise((resolve) => {
41
+ const socket = new net.Socket();
42
+ socket.setTimeout(1000);
43
+
44
+ socket.on('connect', () => {
45
+ socket.destroy();
46
+ resolve(true); // Server is running
47
+ });
48
+
49
+ socket.on('timeout', () => {
50
+ socket.destroy();
51
+ resolve(false);
52
+ });
53
+
54
+ socket.on('error', (err) => {
55
+ socket.destroy();
56
+ resolve(false); // Port free
57
+ });
58
+
59
+ socket.connect(SERVER_PORT, 'localhost');
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Enforce that server is stopped before proceeding
65
+ */
66
+ async function ensureServerStopped() {
67
+ const isRunning = await isServerRunning();
68
+ if (isRunning) {
69
+ console.error(`
70
+ \x1b[31mError: Antigravity Proxy server is currently running on port ${SERVER_PORT}.\x1b[0m
71
+
72
+ Please stop the server (Ctrl+C) before adding or managing accounts.
73
+ This ensures that your account changes are loaded correctly when you restart the server.
74
+ `);
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Create readline interface
81
+ */
82
+ function createRL() {
83
+ return createInterface({ input: stdin, output: stdout });
84
+ }
85
+
86
+ /**
87
+ * Open URL in default browser
88
+ */
89
+ function openBrowser(url) {
90
+ const platform = process.platform;
91
+ let command;
92
+ let args;
93
+
94
+ if (platform === 'darwin') {
95
+ command = 'open';
96
+ args = [url];
97
+ } else if (platform === 'win32') {
98
+ command = 'cmd';
99
+ args = ['/c', 'start', '', url.replace(/&/g, '^&')];
100
+ } else {
101
+ command = 'xdg-open';
102
+ args = [url];
103
+ }
104
+
105
+ const child = spawn(command, args, { stdio: 'ignore', detached: true });
106
+ child.on('error', () => {
107
+ console.log('\n⚠ Could not open browser automatically.');
108
+ console.log('Please open this URL manually:', url);
109
+ });
110
+ child.unref();
111
+ }
112
+
113
+ /**
114
+ * Load existing accounts from config
115
+ */
116
+ function loadAccounts() {
117
+ try {
118
+ if (existsSync(ACCOUNT_CONFIG_PATH)) {
119
+ const data = readFileSync(ACCOUNT_CONFIG_PATH, 'utf-8');
120
+ const config = JSON.parse(data);
121
+ return config.accounts || [];
122
+ }
123
+ } catch (error) {
124
+ console.error('Error loading accounts:', error.message);
125
+ }
126
+ return [];
127
+ }
128
+
129
+ /**
130
+ * Save accounts to config
131
+ */
132
+ function saveAccounts(accounts, settings = {}) {
133
+ try {
134
+ const dir = dirname(ACCOUNT_CONFIG_PATH);
135
+ if (!existsSync(dir)) {
136
+ mkdirSync(dir, { recursive: true });
137
+ }
138
+
139
+ const config = {
140
+ accounts: accounts.map(acc => ({
141
+ email: acc.email,
142
+ source: acc.source || 'oauth',
143
+ refreshToken: acc.refreshToken || undefined,
144
+ apiKey: acc.apiKey || undefined,
145
+ projectId: acc.projectId || undefined,
146
+ provider: acc.provider || 'google',
147
+ addedAt: acc.addedAt || new Date().toISOString(),
148
+ lastUsed: acc.lastUsed || null,
149
+ modelRateLimits: acc.modelRateLimits || {}
150
+ })),
151
+ settings: {
152
+ maxRetries: 5,
153
+ ...settings
154
+ },
155
+ activeIndex: 0
156
+ };
157
+
158
+ writeFileSync(ACCOUNT_CONFIG_PATH, JSON.stringify(config, null, 2));
159
+ console.log(`\nāœ“ Saved ${accounts.length} account(s) to ${ACCOUNT_CONFIG_PATH}`);
160
+ } catch (error) {
161
+ console.error('Error saving accounts:', error.message);
162
+ throw error;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Display current accounts
168
+ */
169
+ function displayAccounts(accounts) {
170
+ if (accounts.length === 0) {
171
+ console.log('\nNo accounts configured.');
172
+ return;
173
+ }
174
+
175
+ const providerLabels = {
176
+ google: 'šŸ”µ Google',
177
+ anthropic: '🟠 Anthropic',
178
+ openai: '🟢 OpenAI',
179
+ github: '🟣 GitHub',
180
+ copilot: '🟧 Copilot',
181
+ openrouter: '🟪 OpenRouter'
182
+ };
183
+
184
+ console.log(`\n${accounts.length} account(s) saved:`);
185
+ accounts.forEach((acc, i) => {
186
+ const hasActiveLimit = Object.values(acc.modelRateLimits || {}).some(
187
+ limit => limit.isRateLimited && limit.resetTime > Date.now()
188
+ );
189
+ const status = hasActiveLimit ? ' (rate-limited)' : '';
190
+ const provider = providerLabels[acc.provider || 'google'] || acc.provider || 'google';
191
+ console.log(` ${i + 1}. ${acc.email} [${provider}]${status}`);
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Add a new account via OAuth with automatic callback
197
+ */
198
+ async function addAccount(existingAccounts) {
199
+ console.log('\n=== Add Google Account ===\n');
200
+
201
+ // Generate authorization URL
202
+ const { url, verifier, state } = getAuthorizationUrl();
203
+
204
+ console.log('Opening browser for Google sign-in...');
205
+ console.log('(If browser does not open, copy this URL manually)\n');
206
+ console.log(` ${url}\n`);
207
+
208
+ // Open browser
209
+ openBrowser(url);
210
+
211
+ // Start callback server and wait for code
212
+ console.log('Waiting for authentication (timeout: 2 minutes)...\n');
213
+
214
+ try {
215
+ // startCallbackServer now returns { promise, abort }
216
+ const { promise } = startCallbackServer(state);
217
+ const code = await promise;
218
+
219
+ console.log('Received authorization code. Exchanging for tokens...');
220
+ const result = await completeOAuthFlow(code, verifier);
221
+
222
+ // Check if account already exists
223
+ const existing = existingAccounts.find(a => a.email === result.email);
224
+ if (existing) {
225
+ console.log(`\n⚠ Account ${result.email} already exists. Updating tokens.`);
226
+ existing.refreshToken = result.refreshToken;
227
+ // Note: projectId will be discovered and stored in refresh token on first use
228
+ existing.addedAt = new Date().toISOString();
229
+ return null; // Don't add duplicate
230
+ }
231
+
232
+ console.log(`\nāœ“ Successfully authenticated: ${result.email}`);
233
+ console.log(' Project will be discovered on first API request.');
234
+
235
+ return {
236
+ email: result.email,
237
+ refreshToken: result.refreshToken,
238
+ // Note: projectId stored in refresh token, not as separate field
239
+ addedAt: new Date().toISOString(),
240
+ modelRateLimits: {}
241
+ };
242
+ } catch (error) {
243
+ console.error(`\nāœ— Authentication failed: ${error.message}`);
244
+ return null;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Add a new account via OAuth with manual code input (no-browser mode)
250
+ * For headless servers without a desktop environment
251
+ */
252
+ async function addAccountNoBrowser(existingAccounts, rl) {
253
+ console.log('\n=== Add Google Account (No-Browser Mode) ===\n');
254
+
255
+ // Generate authorization URL
256
+ const { url, verifier, state } = getAuthorizationUrl();
257
+
258
+ console.log('Copy the following URL and open it in a browser on another device:\n');
259
+ console.log(` ${url}\n`);
260
+ console.log('After signing in, you will be redirected to a localhost URL.');
261
+ console.log('Copy the ENTIRE redirect URL or just the authorization code.\n');
262
+
263
+ const input = await rl.question('Paste the callback URL or authorization code: ');
264
+
265
+ try {
266
+ const { code, state: extractedState } = extractCodeFromInput(input);
267
+
268
+ // Validate state if present
269
+ if (extractedState && extractedState !== state) {
270
+ console.log('\n⚠ State mismatch detected. This could indicate a security issue.');
271
+ console.log('Proceeding anyway as this is manual mode...');
272
+ }
273
+
274
+ console.log('\nExchanging authorization code for tokens...');
275
+ const result = await completeOAuthFlow(code, verifier);
276
+
277
+ // Check if account already exists
278
+ const existing = existingAccounts.find(a => a.email === result.email);
279
+ if (existing) {
280
+ console.log(`\n⚠ Account ${result.email} already exists. Updating tokens.`);
281
+ existing.refreshToken = result.refreshToken;
282
+ // Note: projectId will be discovered and stored in refresh token on first use
283
+ existing.addedAt = new Date().toISOString();
284
+ return null; // Don't add duplicate
285
+ }
286
+
287
+ console.log(`\nāœ“ Successfully authenticated: ${result.email}`);
288
+ console.log(' Project will be discovered on first API request.');
289
+
290
+ return {
291
+ email: result.email,
292
+ refreshToken: result.refreshToken,
293
+ // Note: projectId stored in refresh token, not as separate field
294
+ addedAt: new Date().toISOString(),
295
+ modelRateLimits: {}
296
+ };
297
+ } catch (error) {
298
+ console.error(`\nāœ— Authentication failed: ${error.message}`);
299
+ return null;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Interactive remove accounts flow
305
+ */
306
+ async function interactiveRemove(rl) {
307
+ while (true) {
308
+ const accounts = loadAccounts();
309
+ if (accounts.length === 0) {
310
+ console.log('\nNo accounts to remove.');
311
+ return;
312
+ }
313
+
314
+ displayAccounts(accounts);
315
+ console.log('\nEnter account number to remove (or 0 to cancel)');
316
+
317
+ const answer = await rl.question('> ');
318
+ const index = parseInt(answer, 10);
319
+
320
+ if (isNaN(index) || index < 0 || index > accounts.length) {
321
+ console.log('\nāŒ Invalid selection.');
322
+ continue;
323
+ }
324
+
325
+ if (index === 0) {
326
+ return; // Exit
327
+ }
328
+
329
+ const removed = accounts[index - 1]; // 1-based to 0-based
330
+ const confirm = await rl.question(`\nAre you sure you want to remove ${removed.email}? [y/N]: `);
331
+
332
+ if (confirm.toLowerCase() === 'y') {
333
+ accounts.splice(index - 1, 1);
334
+ saveAccounts(accounts);
335
+ console.log(`\nāœ“ Removed ${removed.email}`);
336
+ } else {
337
+ console.log('\nCancelled.');
338
+ }
339
+
340
+ const removeMore = await rl.question('\nRemove another account? [y/N]: ');
341
+ if (removeMore.toLowerCase() !== 'y') {
342
+ break;
343
+ }
344
+ }
345
+ }
346
+
347
+ /**
348
+ * Interactive add accounts flow (Main Menu)
349
+ * @param {Object} rl - readline interface
350
+ * @param {boolean} noBrowser - if true, use manual code input mode
351
+ */
352
+ async function interactiveAdd(rl, noBrowser = false) {
353
+ if (noBrowser) {
354
+ console.log('\nšŸ“‹ No-browser mode: You will manually paste the authorization code.\n');
355
+ }
356
+
357
+ const accounts = loadAccounts();
358
+
359
+ if (accounts.length > 0) {
360
+ displayAccounts(accounts);
361
+
362
+ const choice = await rl.question('\n(a)dd new, (r)emove existing, (f)resh start, or (e)xit? [a/r/f/e]: ');
363
+ const c = choice.toLowerCase();
364
+
365
+ if (c === 'r') {
366
+ await interactiveRemove(rl);
367
+ return; // Return to main or exit? Given this is "add", we probably exit after sub-task.
368
+ } else if (c === 'f') {
369
+ console.log('\nStarting fresh - existing accounts will be replaced.');
370
+ accounts.length = 0;
371
+ } else if (c === 'a') {
372
+ console.log('\nAdding to existing accounts.');
373
+ } else if (c === 'e') {
374
+ console.log('\nExiting...');
375
+ return; // Exit cleanly
376
+ } else {
377
+ console.log('\nInvalid choice, defaulting to add.');
378
+ }
379
+ }
380
+
381
+ // Add single account
382
+ if (accounts.length >= MAX_ACCOUNTS) {
383
+ console.log(`\nMaximum of ${MAX_ACCOUNTS} accounts reached.`);
384
+ return;
385
+ }
386
+
387
+ // Use appropriate add function based on mode
388
+ const newAccount = noBrowser
389
+ ? await addAccountNoBrowser(accounts, rl)
390
+ : await addAccount(accounts);
391
+
392
+ if (newAccount) {
393
+ accounts.push(newAccount);
394
+ saveAccounts(accounts);
395
+ } else if (accounts.length > 0) {
396
+ // Even if newAccount is null (duplicate update), save the updated accounts
397
+ saveAccounts(accounts);
398
+ }
399
+
400
+ if (accounts.length > 0) {
401
+ displayAccounts(accounts);
402
+ console.log('\nTo add more accounts, run this command again.');
403
+ } else {
404
+ console.log('\nNo accounts to save.');
405
+ }
406
+ }
407
+
408
+ /**
409
+ * List accounts
410
+ */
411
+ async function listAccounts() {
412
+ const accounts = loadAccounts();
413
+ displayAccounts(accounts);
414
+
415
+ if (accounts.length > 0) {
416
+ console.log(`\nConfig file: ${ACCOUNT_CONFIG_PATH}`);
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Clear all accounts
422
+ */
423
+ async function clearAccounts(rl) {
424
+ const accounts = loadAccounts();
425
+
426
+ if (accounts.length === 0) {
427
+ console.log('No accounts to clear.');
428
+ return;
429
+ }
430
+
431
+ displayAccounts(accounts);
432
+
433
+ const confirm = await rl.question('\nAre you sure you want to remove all accounts? [y/N]: ');
434
+ if (confirm.toLowerCase() === 'y') {
435
+ saveAccounts([]);
436
+ console.log('All accounts removed.');
437
+ } else {
438
+ console.log('Cancelled.');
439
+ }
440
+ }
441
+
442
+ /**
443
+ * Verify accounts (test refresh tokens)
444
+ */
445
+ async function verifyAccounts() {
446
+ const accounts = loadAccounts();
447
+
448
+ if (accounts.length === 0) {
449
+ console.log('No accounts to verify.');
450
+ return;
451
+ }
452
+
453
+ console.log('\nVerifying accounts...\n');
454
+
455
+ for (const account of accounts) {
456
+ try {
457
+ const tokens = await refreshAccessToken(account.refreshToken);
458
+ const email = await getUserEmail(tokens.accessToken);
459
+ console.log(` āœ“ ${email} - OK`);
460
+ } catch (error) {
461
+ console.log(` āœ— ${account.email} - ${error.message}`);
462
+ }
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Add a GitHub Copilot account via device authorization flow
468
+ */
469
+ async function addCopilotAccount(rl) {
470
+ console.log('\n=== Add GitHub Copilot Account ===\n');
471
+ console.log('This will use GitHub\'s device authorization flow.');
472
+ console.log('You need an active GitHub Copilot subscription.\n');
473
+
474
+ const accounts = loadAccounts();
475
+
476
+ if (accounts.length >= MAX_ACCOUNTS) {
477
+ console.log(`\nMaximum of ${MAX_ACCOUNTS} accounts reached.`);
478
+ return;
479
+ }
480
+
481
+ try {
482
+ const { CopilotProvider } = await import('../providers/copilot.js');
483
+
484
+ // Step 1: Initiate device auth
485
+ console.log('Requesting device code from GitHub...');
486
+ const deviceData = await CopilotProvider.initiateDeviceAuth();
487
+
488
+ console.log(`\n\x1b[1m\x1b[33m Your code: ${deviceData.user_code}\x1b[0m\n`);
489
+ console.log(` Open this URL: ${deviceData.verification_uri}`);
490
+ console.log(' Enter the code above and authorize the application.\n');
491
+
492
+ // Try to open browser
493
+ openBrowser(deviceData.verification_uri);
494
+
495
+ console.log('Waiting for authorization...');
496
+ console.log('(Press Ctrl+C to cancel)\n');
497
+
498
+ // Step 2: Poll for token
499
+ const interval = (deviceData.interval || 5) * 1000;
500
+ const expiresAt = Date.now() + (deviceData.expires_in || 900) * 1000;
501
+
502
+ while (Date.now() < expiresAt) {
503
+ await new Promise(resolve => setTimeout(resolve, interval));
504
+
505
+ try {
506
+ const response = await fetch('https://github.com/login/oauth/access_token', {
507
+ method: 'POST',
508
+ headers: {
509
+ 'Accept': 'application/json',
510
+ 'Content-Type': 'application/json',
511
+ 'User-Agent': 'commons-proxy/2.0.0'
512
+ },
513
+ body: JSON.stringify({
514
+ client_id: 'Iv1.b507a08c87ecfe98',
515
+ device_code: deviceData.device_code,
516
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
517
+ })
518
+ });
519
+
520
+ const data = await response.json();
521
+
522
+ if (data.access_token) {
523
+ // Success! Get user info
524
+ console.log('Authorization received! Fetching user info...');
525
+ const userInfo = await CopilotProvider.getUserInfo(data.access_token);
526
+
527
+ // Check for duplicate
528
+ const existing = accounts.find(a => a.email === userInfo.email && a.provider === 'copilot');
529
+ if (existing) {
530
+ console.log(`\n⚠ Account ${userInfo.email} already exists. Updating token.`);
531
+ existing.apiKey = data.access_token;
532
+ existing.addedAt = new Date().toISOString();
533
+ } else {
534
+ accounts.push({
535
+ email: userInfo.email,
536
+ source: 'manual',
537
+ provider: 'copilot',
538
+ apiKey: data.access_token,
539
+ addedAt: new Date().toISOString(),
540
+ modelRateLimits: {}
541
+ });
542
+ console.log(`\nāœ“ Successfully added: ${userInfo.email} [🟧 Copilot]`);
543
+ }
544
+
545
+ saveAccounts(accounts);
546
+ displayAccounts(accounts);
547
+ return;
548
+ }
549
+
550
+ if (data.error === 'authorization_pending') {
551
+ process.stdout.write('.');
552
+ continue;
553
+ }
554
+
555
+ if (data.error === 'slow_down') {
556
+ process.stdout.write('.');
557
+ await new Promise(resolve => setTimeout(resolve, 5000));
558
+ continue;
559
+ }
560
+
561
+ if (data.error === 'expired_token') {
562
+ console.log('\n\nāœ— Device code expired. Please try again.');
563
+ return;
564
+ }
565
+
566
+ if (data.error) {
567
+ console.log(`\n\nāœ— Authorization failed: ${data.error_description || data.error}`);
568
+ return;
569
+ }
570
+ } catch (pollError) {
571
+ console.error(`\n\nāœ— Polling error: ${pollError.message}`);
572
+ return;
573
+ }
574
+ }
575
+
576
+ console.log('\n\nāœ— Authorization timed out. Please try again.');
577
+ } catch (error) {
578
+ console.error(`\nāœ— Copilot device auth failed: ${error.message}`);
579
+ }
580
+ }
581
+
582
+ /**
583
+ * Main CLI
584
+ */
585
+ async function main() {
586
+ const args = process.argv.slice(2);
587
+ const command = args[0] || 'add';
588
+ const noBrowser = args.includes('--no-browser');
589
+
590
+ console.log('╔════════════════════════════════════════╗');
591
+ console.log('ā•‘ CommonsProxy Account Manager ā•‘');
592
+ console.log('ā•‘ Use --provider=copilot for Copilot ā•‘');
593
+ console.log('ā•‘ Use --no-browser for headless mode ā•‘');
594
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
595
+
596
+ const rl = createRL();
597
+
598
+ // Parse --provider flag
599
+ const providerArg = args.find(a => a.startsWith('--provider='));
600
+ const provider = providerArg ? providerArg.split('=')[1] : null;
601
+
602
+ try {
603
+ switch (command) {
604
+ case 'add':
605
+ await ensureServerStopped();
606
+ if (provider === 'copilot') {
607
+ await addCopilotAccount(rl);
608
+ } else {
609
+ await interactiveAdd(rl, noBrowser);
610
+ }
611
+ break;
612
+ case 'list':
613
+ await listAccounts();
614
+ break;
615
+ case 'clear':
616
+ await ensureServerStopped();
617
+ await clearAccounts(rl);
618
+ break;
619
+ case 'verify':
620
+ await verifyAccounts();
621
+ break;
622
+ case 'help':
623
+ console.log('\nUsage:');
624
+ console.log(' node src/cli/accounts.js add Add new account(s)');
625
+ console.log(' node src/cli/accounts.js list List all accounts');
626
+ console.log(' node src/cli/accounts.js verify Verify account tokens');
627
+ console.log(' node src/cli/accounts.js clear Remove all accounts');
628
+ console.log(' node src/cli/accounts.js help Show this help');
629
+ console.log('\nOptions:');
630
+ console.log(' --provider=copilot Add a GitHub Copilot account via device auth');
631
+ console.log(' --no-browser Manual authorization code input (for headless servers)');
632
+ break;
633
+ case 'remove':
634
+ await ensureServerStopped();
635
+ await interactiveRemove(rl);
636
+ break;
637
+ default:
638
+ console.log(`Unknown command: ${command}`);
639
+ console.log('Run with "help" for usage information.');
640
+ }
641
+ } finally {
642
+ rl.close();
643
+ // Force exit to prevent hanging
644
+ process.exit(0);
645
+ }
646
+ }
647
+
648
+ main().catch(console.error);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Cloud Code Client for Antigravity
3
+ *
4
+ * Communicates with Google's Cloud Code internal API using the
5
+ * v1internal:streamGenerateContent endpoint with proper request wrapping.
6
+ *
7
+ * Supports multi-account load balancing with automatic failover.
8
+ *
9
+ * Based on: https://github.com/NoeFabris/opencode-cloudcode-auth
10
+ */
11
+
12
+ // Re-export public API
13
+ export { sendMessage } from './message-handler.js';
14
+ export { sendMessageStream } from './streaming-handler.js';
15
+ export { listModels, fetchAvailableModels, getModelQuotas, getSubscriptionTier } from './model-api.js';
16
+
17
+ // Default export for backwards compatibility
18
+ import { sendMessage } from './message-handler.js';
19
+ import { sendMessageStream } from './streaming-handler.js';
20
+ import { listModels, fetchAvailableModels, getModelQuotas, getSubscriptionTier } from './model-api.js';
21
+
22
+ export default {
23
+ sendMessage,
24
+ sendMessageStream,
25
+ listModels,
26
+ fetchAvailableModels,
27
+ getModelQuotas,
28
+ getSubscriptionTier
29
+ };