antigravity-claude-proxy 1.2.6 → 1.2.7
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/README.md +19 -2
- package/package.json +3 -2
- package/src/auth/oauth.js +51 -0
- package/src/cli/accounts.js +76 -4
package/README.md
CHANGED
|
@@ -69,7 +69,9 @@ If you have Antigravity installed and logged in, the proxy will automatically ex
|
|
|
69
69
|
|
|
70
70
|
**Option B: Add Google Accounts via OAuth (Recommended for Multi-Account)**
|
|
71
71
|
|
|
72
|
-
Add one or more Google accounts for load balancing
|
|
72
|
+
Add one or more Google accounts for load balancing.
|
|
73
|
+
|
|
74
|
+
#### Desktop/Laptop (with browser)
|
|
73
75
|
|
|
74
76
|
```bash
|
|
75
77
|
# If installed via npm
|
|
@@ -84,7 +86,22 @@ npm run accounts:add
|
|
|
84
86
|
|
|
85
87
|
This opens your browser for Google OAuth. Sign in and authorize access. Repeat for multiple accounts.
|
|
86
88
|
|
|
87
|
-
|
|
89
|
+
#### Headless Server (Docker, SSH, no desktop)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# If installed via npm
|
|
93
|
+
antigravity-claude-proxy accounts add --no-browser
|
|
94
|
+
|
|
95
|
+
# If using npx
|
|
96
|
+
npx antigravity-claude-proxy accounts add -- --no-browser
|
|
97
|
+
|
|
98
|
+
# If cloned locally
|
|
99
|
+
npm run accounts:add -- --no-browser
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This displays an OAuth URL you can open on another device (phone/laptop). After signing in, copy the redirect URL or authorization code and paste it back into the terminal.
|
|
103
|
+
|
|
104
|
+
#### Manage accounts
|
|
88
105
|
|
|
89
106
|
```bash
|
|
90
107
|
# List all accounts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-claude-proxy",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Proxy server to use Antigravity's Claude models with Claude Code CLI",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"test:interleaved": "node tests/test-interleaved-thinking.cjs",
|
|
27
27
|
"test:images": "node tests/test-images.cjs",
|
|
28
28
|
"test:caching": "node tests/test-caching-streaming.cjs",
|
|
29
|
-
"test:crossmodel": "node tests/test-cross-model-thinking.cjs"
|
|
29
|
+
"test:crossmodel": "node tests/test-cross-model-thinking.cjs",
|
|
30
|
+
"test:oauth": "node tests/test-oauth-no-browser.cjs"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
|
32
33
|
"claude",
|
package/src/auth/oauth.js
CHANGED
|
@@ -57,6 +57,56 @@ export function getAuthorizationUrl() {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Extract authorization code and state from user input.
|
|
62
|
+
* User can paste either:
|
|
63
|
+
* - Full callback URL: http://localhost:51121/oauth-callback?code=xxx&state=xxx
|
|
64
|
+
* - Just the code parameter: 4/0xxx...
|
|
65
|
+
*
|
|
66
|
+
* @param {string} input - User input (URL or code)
|
|
67
|
+
* @returns {{code: string, state: string|null}} Extracted code and optional state
|
|
68
|
+
*/
|
|
69
|
+
export function extractCodeFromInput(input) {
|
|
70
|
+
if (!input || typeof input !== 'string') {
|
|
71
|
+
throw new Error('No input provided');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const trimmed = input.trim();
|
|
75
|
+
|
|
76
|
+
// Check if it looks like a URL
|
|
77
|
+
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(trimmed);
|
|
80
|
+
const code = url.searchParams.get('code');
|
|
81
|
+
const state = url.searchParams.get('state');
|
|
82
|
+
const error = url.searchParams.get('error');
|
|
83
|
+
|
|
84
|
+
if (error) {
|
|
85
|
+
throw new Error(`OAuth error: ${error}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!code) {
|
|
89
|
+
throw new Error('No authorization code found in URL');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { code, state };
|
|
93
|
+
} catch (e) {
|
|
94
|
+
if (e.message.includes('OAuth error') || e.message.includes('No authorization code')) {
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
throw new Error('Invalid URL format');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Assume it's a raw code
|
|
102
|
+
// Google auth codes typically start with "4/" and are long
|
|
103
|
+
if (trimmed.length < 10) {
|
|
104
|
+
throw new Error('Input is too short to be a valid authorization code');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { code: trimmed, state: null };
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
/**
|
|
61
111
|
* Start a local server to receive the OAuth callback
|
|
62
112
|
* Returns a promise that resolves with the authorization code
|
|
@@ -338,6 +388,7 @@ export async function completeOAuthFlow(code, verifier) {
|
|
|
338
388
|
|
|
339
389
|
export default {
|
|
340
390
|
getAuthorizationUrl,
|
|
391
|
+
extractCodeFromInput,
|
|
341
392
|
startCallbackServer,
|
|
342
393
|
exchangeCode,
|
|
343
394
|
refreshAccessToken,
|
package/src/cli/accounts.js
CHANGED
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
startCallbackServer,
|
|
26
26
|
completeOAuthFlow,
|
|
27
27
|
refreshAccessToken,
|
|
28
|
-
getUserEmail
|
|
28
|
+
getUserEmail,
|
|
29
|
+
extractCodeFromInput
|
|
29
30
|
} from '../auth/oauth.js';
|
|
30
31
|
|
|
31
32
|
const SERVER_PORT = process.env.PORT || DEFAULT_PORT;
|
|
@@ -229,6 +230,63 @@ async function addAccount(existingAccounts) {
|
|
|
229
230
|
}
|
|
230
231
|
}
|
|
231
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Add a new account via OAuth with manual code input (no-browser mode)
|
|
235
|
+
* For headless servers without a desktop environment
|
|
236
|
+
*/
|
|
237
|
+
async function addAccountNoBrowser(existingAccounts, rl) {
|
|
238
|
+
console.log('\n=== Add Google Account (No-Browser Mode) ===\n');
|
|
239
|
+
|
|
240
|
+
// Generate authorization URL
|
|
241
|
+
const { url, verifier, state } = getAuthorizationUrl();
|
|
242
|
+
|
|
243
|
+
console.log('Copy the following URL and open it in a browser on another device:\n');
|
|
244
|
+
console.log(` ${url}\n`);
|
|
245
|
+
console.log('After signing in, you will be redirected to a localhost URL.');
|
|
246
|
+
console.log('Copy the ENTIRE redirect URL or just the authorization code.\n');
|
|
247
|
+
|
|
248
|
+
const input = await rl.question('Paste the callback URL or authorization code: ');
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const { code, state: extractedState } = extractCodeFromInput(input);
|
|
252
|
+
|
|
253
|
+
// Validate state if present
|
|
254
|
+
if (extractedState && extractedState !== state) {
|
|
255
|
+
console.log('\n⚠ State mismatch detected. This could indicate a security issue.');
|
|
256
|
+
console.log('Proceeding anyway as this is manual mode...');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
console.log('\nExchanging authorization code for tokens...');
|
|
260
|
+
const result = await completeOAuthFlow(code, verifier);
|
|
261
|
+
|
|
262
|
+
// Check if account already exists
|
|
263
|
+
const existing = existingAccounts.find(a => a.email === result.email);
|
|
264
|
+
if (existing) {
|
|
265
|
+
console.log(`\n⚠ Account ${result.email} already exists. Updating tokens.`);
|
|
266
|
+
existing.refreshToken = result.refreshToken;
|
|
267
|
+
existing.projectId = result.projectId;
|
|
268
|
+
existing.addedAt = new Date().toISOString();
|
|
269
|
+
return null; // Don't add duplicate
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(`\n✓ Successfully authenticated: ${result.email}`);
|
|
273
|
+
if (result.projectId) {
|
|
274
|
+
console.log(` Project ID: ${result.projectId}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
email: result.email,
|
|
279
|
+
refreshToken: result.refreshToken,
|
|
280
|
+
projectId: result.projectId,
|
|
281
|
+
addedAt: new Date().toISOString(),
|
|
282
|
+
modelRateLimits: {}
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error(`\n✗ Authentication failed: ${error.message}`);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
232
290
|
/**
|
|
233
291
|
* Interactive remove accounts flow
|
|
234
292
|
*/
|
|
@@ -275,8 +333,14 @@ async function interactiveRemove(rl) {
|
|
|
275
333
|
|
|
276
334
|
/**
|
|
277
335
|
* Interactive add accounts flow (Main Menu)
|
|
336
|
+
* @param {Object} rl - readline interface
|
|
337
|
+
* @param {boolean} noBrowser - if true, use manual code input mode
|
|
278
338
|
*/
|
|
279
|
-
async function interactiveAdd(rl) {
|
|
339
|
+
async function interactiveAdd(rl, noBrowser = false) {
|
|
340
|
+
if (noBrowser) {
|
|
341
|
+
console.log('\n📋 No-browser mode: You will manually paste the authorization code.\n');
|
|
342
|
+
}
|
|
343
|
+
|
|
280
344
|
const accounts = loadAccounts();
|
|
281
345
|
|
|
282
346
|
if (accounts.length > 0) {
|
|
@@ -307,7 +371,11 @@ async function interactiveAdd(rl) {
|
|
|
307
371
|
return;
|
|
308
372
|
}
|
|
309
373
|
|
|
310
|
-
|
|
374
|
+
// Use appropriate add function based on mode
|
|
375
|
+
const newAccount = noBrowser
|
|
376
|
+
? await addAccountNoBrowser(accounts, rl)
|
|
377
|
+
: await addAccount(accounts);
|
|
378
|
+
|
|
311
379
|
if (newAccount) {
|
|
312
380
|
accounts.push(newAccount);
|
|
313
381
|
saveAccounts(accounts);
|
|
@@ -388,9 +456,11 @@ async function verifyAccounts() {
|
|
|
388
456
|
async function main() {
|
|
389
457
|
const args = process.argv.slice(2);
|
|
390
458
|
const command = args[0] || 'add';
|
|
459
|
+
const noBrowser = args.includes('--no-browser');
|
|
391
460
|
|
|
392
461
|
console.log('╔════════════════════════════════════════╗');
|
|
393
462
|
console.log('║ Antigravity Proxy Account Manager ║');
|
|
463
|
+
console.log('║ Use --no-browser for headless mode ║');
|
|
394
464
|
console.log('╚════════════════════════════════════════╝');
|
|
395
465
|
|
|
396
466
|
const rl = createRL();
|
|
@@ -399,7 +469,7 @@ async function main() {
|
|
|
399
469
|
switch (command) {
|
|
400
470
|
case 'add':
|
|
401
471
|
await ensureServerStopped();
|
|
402
|
-
await interactiveAdd(rl);
|
|
472
|
+
await interactiveAdd(rl, noBrowser);
|
|
403
473
|
break;
|
|
404
474
|
case 'list':
|
|
405
475
|
await listAccounts();
|
|
@@ -418,6 +488,8 @@ async function main() {
|
|
|
418
488
|
console.log(' node src/cli/accounts.js verify Verify account tokens');
|
|
419
489
|
console.log(' node src/cli/accounts.js clear Remove all accounts');
|
|
420
490
|
console.log(' node src/cli/accounts.js help Show this help');
|
|
491
|
+
console.log('\nOptions:');
|
|
492
|
+
console.log(' --no-browser Manual authorization code input (for headless servers)');
|
|
421
493
|
break;
|
|
422
494
|
case 'remove':
|
|
423
495
|
await ensureServerStopped();
|