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.
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/bin/cli.js +109 -0
- package/package.json +54 -0
- package/src/account-manager.js +633 -0
- package/src/accounts-cli.js +437 -0
- package/src/cloudcode-client.js +1018 -0
- package/src/constants.js +164 -0
- package/src/errors.js +159 -0
- package/src/format-converter.js +731 -0
- package/src/index.js +40 -0
- package/src/oauth.js +346 -0
- package/src/server.js +517 -0
- package/src/token-extractor.js +146 -0
- package/src/utils/helpers.js +33 -0
|
@@ -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);
|