antigravity-claude-proxy 1.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.
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Account Management CLI
5
+ *
6
+ * Interactive CLI for adding and managing Google accounts
7
+ * for the Antigravity Claude Proxy.
8
+ *
9
+ * Usage:
10
+ * node src/accounts-cli.js # Interactive mode
11
+ * node src/accounts-cli.js add # Add new account(s)
12
+ * node src/accounts-cli.js list # List all accounts
13
+ * node src/accounts-cli.js clear # Remove all accounts
14
+ */
15
+
16
+ import { createInterface } from 'readline/promises';
17
+ import { stdin, stdout } from 'process';
18
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
19
+ import { dirname } from 'path';
20
+ import { exec } from 'child_process';
21
+ import net from 'net';
22
+ import { ACCOUNT_CONFIG_PATH, DEFAULT_PORT, MAX_ACCOUNTS } from './constants.js';
23
+ import {
24
+ getAuthorizationUrl,
25
+ startCallbackServer,
26
+ completeOAuthFlow,
27
+ refreshAccessToken,
28
+ getUserEmail
29
+ } from './oauth.js';
30
+
31
+ const SERVER_PORT = process.env.PORT || DEFAULT_PORT;
32
+
33
+ /**
34
+ * Check if the Antigravity Proxy server is running
35
+ * Returns true if port is occupied
36
+ */
37
+ function isServerRunning() {
38
+ return new Promise((resolve) => {
39
+ const socket = new net.Socket();
40
+ socket.setTimeout(1000);
41
+
42
+ socket.on('connect', () => {
43
+ socket.destroy();
44
+ resolve(true); // Server is running
45
+ });
46
+
47
+ socket.on('timeout', () => {
48
+ socket.destroy();
49
+ resolve(false);
50
+ });
51
+
52
+ socket.on('error', (err) => {
53
+ socket.destroy();
54
+ resolve(false); // Port free
55
+ });
56
+
57
+ socket.connect(SERVER_PORT, 'localhost');
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Enforce that server is stopped before proceeding
63
+ */
64
+ async function ensureServerStopped() {
65
+ const isRunning = await isServerRunning();
66
+ if (isRunning) {
67
+ console.error(`
68
+ \x1b[31mError: Antigravity Proxy server is currently running on port ${SERVER_PORT}.\x1b[0m
69
+
70
+ Please stop the server (Ctrl+C) before adding or managing accounts.
71
+ This ensures that your account changes are loaded correctly when you restart the server.
72
+ `);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Create readline interface
79
+ */
80
+ function createRL() {
81
+ return createInterface({ input: stdin, output: stdout });
82
+ }
83
+
84
+ /**
85
+ * Open URL in default browser
86
+ */
87
+ function openBrowser(url) {
88
+ const platform = process.platform;
89
+ let command;
90
+
91
+ if (platform === 'darwin') {
92
+ command = `open "${url}"`;
93
+ } else if (platform === 'win32') {
94
+ command = `start "" "${url}"`;
95
+ } else {
96
+ command = `xdg-open "${url}"`;
97
+ }
98
+
99
+ exec(command, (error) => {
100
+ if (error) {
101
+ console.log('\n⚠ Could not open browser automatically.');
102
+ console.log('Please open this URL manually:', url);
103
+ }
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Load existing accounts from config
109
+ */
110
+ function loadAccounts() {
111
+ try {
112
+ if (existsSync(ACCOUNT_CONFIG_PATH)) {
113
+ const data = readFileSync(ACCOUNT_CONFIG_PATH, 'utf-8');
114
+ const config = JSON.parse(data);
115
+ return config.accounts || [];
116
+ }
117
+ } catch (error) {
118
+ console.error('Error loading accounts:', error.message);
119
+ }
120
+ return [];
121
+ }
122
+
123
+ /**
124
+ * Save accounts to config
125
+ */
126
+ function saveAccounts(accounts, settings = {}) {
127
+ try {
128
+ const dir = dirname(ACCOUNT_CONFIG_PATH);
129
+ if (!existsSync(dir)) {
130
+ mkdirSync(dir, { recursive: true });
131
+ }
132
+
133
+ const config = {
134
+ accounts: accounts.map(acc => ({
135
+ email: acc.email,
136
+ source: 'oauth',
137
+ refreshToken: acc.refreshToken,
138
+ projectId: acc.projectId,
139
+ addedAt: acc.addedAt || new Date().toISOString(),
140
+ lastUsed: acc.lastUsed || null,
141
+ isRateLimited: acc.isRateLimited || false,
142
+ rateLimitResetTime: acc.rateLimitResetTime || null
143
+ })),
144
+ settings: {
145
+ cooldownDurationMs: 60000,
146
+ maxRetries: 5,
147
+ ...settings
148
+ },
149
+ activeIndex: 0
150
+ };
151
+
152
+ writeFileSync(ACCOUNT_CONFIG_PATH, JSON.stringify(config, null, 2));
153
+ console.log(`\n✓ Saved ${accounts.length} account(s) to ${ACCOUNT_CONFIG_PATH}`);
154
+ } catch (error) {
155
+ console.error('Error saving accounts:', error.message);
156
+ throw error;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Display current accounts
162
+ */
163
+ function displayAccounts(accounts) {
164
+ if (accounts.length === 0) {
165
+ console.log('\nNo accounts configured.');
166
+ return;
167
+ }
168
+
169
+ console.log(`\n${accounts.length} account(s) saved:`);
170
+ accounts.forEach((acc, i) => {
171
+ const status = acc.isRateLimited ? ' (rate-limited)' : '';
172
+ console.log(` ${i + 1}. ${acc.email}${status}`);
173
+ });
174
+ }
175
+
176
+ /**
177
+ * Add a new account via OAuth with automatic callback
178
+ */
179
+ async function addAccount(existingAccounts) {
180
+ console.log('\n=== Add Google Account ===\n');
181
+
182
+ // Generate authorization URL
183
+ const { url, verifier, state } = getAuthorizationUrl();
184
+
185
+ console.log('Opening browser for Google sign-in...');
186
+ console.log('(If browser does not open, copy this URL manually)\n');
187
+ console.log(` ${url}\n`);
188
+
189
+ // Open browser
190
+ openBrowser(url);
191
+
192
+ // Start callback server and wait for code
193
+ console.log('Waiting for authentication (timeout: 2 minutes)...\n');
194
+
195
+ try {
196
+ const code = await startCallbackServer(state);
197
+
198
+ console.log('Received authorization code. Exchanging for tokens...');
199
+ const result = await completeOAuthFlow(code, verifier);
200
+
201
+ // Check if account already exists
202
+ const existing = existingAccounts.find(a => a.email === result.email);
203
+ if (existing) {
204
+ console.log(`\n⚠ Account ${result.email} already exists. Updating tokens.`);
205
+ existing.refreshToken = result.refreshToken;
206
+ existing.projectId = result.projectId;
207
+ existing.addedAt = new Date().toISOString();
208
+ return null; // Don't add duplicate
209
+ }
210
+
211
+ console.log(`\n✓ Successfully authenticated: ${result.email}`);
212
+ if (result.projectId) {
213
+ console.log(` Project ID: ${result.projectId}`);
214
+ }
215
+
216
+ return {
217
+ email: result.email,
218
+ refreshToken: result.refreshToken,
219
+ projectId: result.projectId,
220
+ addedAt: new Date().toISOString(),
221
+ isRateLimited: false,
222
+ rateLimitResetTime: null
223
+ };
224
+ } catch (error) {
225
+ console.error(`\n✗ Authentication failed: ${error.message}`);
226
+ return null;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Interactive remove accounts flow
232
+ */
233
+ async function interactiveRemove(rl) {
234
+ while (true) {
235
+ const accounts = loadAccounts();
236
+ if (accounts.length === 0) {
237
+ console.log('\nNo accounts to remove.');
238
+ return;
239
+ }
240
+
241
+ displayAccounts(accounts);
242
+ console.log('\nEnter account number to remove (or 0 to cancel)');
243
+
244
+ const answer = await rl.question('> ');
245
+ const index = parseInt(answer, 10);
246
+
247
+ if (isNaN(index) || index < 0 || index > accounts.length) {
248
+ console.log('\n❌ Invalid selection.');
249
+ continue;
250
+ }
251
+
252
+ if (index === 0) {
253
+ return; // Exit
254
+ }
255
+
256
+ const removed = accounts[index - 1]; // 1-based to 0-based
257
+ const confirm = await rl.question(`\nAre you sure you want to remove ${removed.email}? [y/N]: `);
258
+
259
+ if (confirm.toLowerCase() === 'y') {
260
+ accounts.splice(index - 1, 1);
261
+ saveAccounts(accounts);
262
+ console.log(`\n✓ Removed ${removed.email}`);
263
+ } else {
264
+ console.log('\nCancelled.');
265
+ }
266
+
267
+ const removeMore = await rl.question('\nRemove another account? [y/N]: ');
268
+ if (removeMore.toLowerCase() !== 'y') {
269
+ break;
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Interactive add accounts flow (Main Menu)
276
+ */
277
+ async function interactiveAdd(rl) {
278
+ const accounts = loadAccounts();
279
+
280
+ if (accounts.length > 0) {
281
+ displayAccounts(accounts);
282
+
283
+ const choice = await rl.question('\n(a)dd new, (r)emove existing, or (f)resh start? [a/r/f]: ');
284
+ const c = choice.toLowerCase();
285
+
286
+ if (c === 'r') {
287
+ await interactiveRemove(rl);
288
+ return; // Return to main or exit? Given this is "add", we probably exit after sub-task.
289
+ } else if (c === 'f') {
290
+ console.log('\nStarting fresh - existing accounts will be replaced.');
291
+ accounts.length = 0;
292
+ } else if (c === 'a') {
293
+ console.log('\nAdding to existing accounts.');
294
+ } else {
295
+ console.log('\nInvalid choice, defaulting to add.');
296
+ }
297
+ }
298
+
299
+ // Add accounts loop
300
+ while (accounts.length < MAX_ACCOUNTS) {
301
+ const newAccount = await addAccount(accounts);
302
+ if (newAccount) {
303
+ accounts.push(newAccount);
304
+ // Auto-save after each successful add to prevent data loss
305
+ saveAccounts(accounts);
306
+ } else if (accounts.length > 0) {
307
+ // Even if newAccount is null (duplicate update), save the updated accounts
308
+ saveAccounts(accounts);
309
+ }
310
+
311
+ if (accounts.length >= MAX_ACCOUNTS) {
312
+ console.log(`\nMaximum of ${MAX_ACCOUNTS} accounts reached.`);
313
+ break;
314
+ }
315
+
316
+ const addMore = await rl.question('\nAdd another account? [y/N]: ');
317
+ if (addMore.toLowerCase() !== 'y') {
318
+ break;
319
+ }
320
+ }
321
+
322
+ if (accounts.length > 0) {
323
+ displayAccounts(accounts);
324
+ } else {
325
+ console.log('\nNo accounts to save.');
326
+ }
327
+ }
328
+
329
+ /**
330
+ * List accounts
331
+ */
332
+ async function listAccounts() {
333
+ const accounts = loadAccounts();
334
+ displayAccounts(accounts);
335
+
336
+ if (accounts.length > 0) {
337
+ console.log(`\nConfig file: ${ACCOUNT_CONFIG_PATH}`);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Clear all accounts
343
+ */
344
+ async function clearAccounts(rl) {
345
+ const accounts = loadAccounts();
346
+
347
+ if (accounts.length === 0) {
348
+ console.log('No accounts to clear.');
349
+ return;
350
+ }
351
+
352
+ displayAccounts(accounts);
353
+
354
+ const confirm = await rl.question('\nAre you sure you want to remove all accounts? [y/N]: ');
355
+ if (confirm.toLowerCase() === 'y') {
356
+ saveAccounts([]);
357
+ console.log('All accounts removed.');
358
+ } else {
359
+ console.log('Cancelled.');
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Verify accounts (test refresh tokens)
365
+ */
366
+ async function verifyAccounts() {
367
+ const accounts = loadAccounts();
368
+
369
+ if (accounts.length === 0) {
370
+ console.log('No accounts to verify.');
371
+ return;
372
+ }
373
+
374
+ console.log('\nVerifying accounts...\n');
375
+
376
+ for (const account of accounts) {
377
+ try {
378
+ const tokens = await refreshAccessToken(account.refreshToken);
379
+ const email = await getUserEmail(tokens.accessToken);
380
+ console.log(` ✓ ${email} - OK`);
381
+ } catch (error) {
382
+ console.log(` ✗ ${account.email} - ${error.message}`);
383
+ }
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Main CLI
389
+ */
390
+ async function main() {
391
+ const args = process.argv.slice(2);
392
+ const command = args[0] || 'add';
393
+
394
+ console.log('╔════════════════════════════════════════╗');
395
+ console.log('║ Antigravity Proxy Account Manager ║');
396
+ console.log('╚════════════════════════════════════════╝');
397
+
398
+ const rl = createRL();
399
+
400
+ try {
401
+ switch (command) {
402
+ case 'add':
403
+ await ensureServerStopped();
404
+ await interactiveAdd(rl);
405
+ break;
406
+ case 'list':
407
+ await listAccounts();
408
+ break;
409
+ case 'clear':
410
+ await ensureServerStopped();
411
+ await clearAccounts(rl);
412
+ break;
413
+ case 'verify':
414
+ await verifyAccounts();
415
+ break;
416
+ case 'help':
417
+ console.log('\nUsage:');
418
+ console.log(' node src/accounts-cli.js add Add new account(s)');
419
+ console.log(' node src/accounts-cli.js list List all accounts');
420
+ console.log(' node src/accounts-cli.js verify Verify account tokens');
421
+ console.log(' node src/accounts-cli.js clear Remove all accounts');
422
+ console.log(' node src/accounts-cli.js help Show this help');
423
+ break;
424
+ case 'remove':
425
+ await ensureServerStopped();
426
+ await interactiveRemove(rl);
427
+ break;
428
+ default:
429
+ console.log(`Unknown command: ${command}`);
430
+ console.log('Run with "help" for usage information.');
431
+ }
432
+ } finally {
433
+ rl.close();
434
+ }
435
+ }
436
+
437
+ main().catch(console.error);