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.
- package/LICENSE +21 -0
- package/README.md +757 -0
- package/bin/cli.js +146 -0
- package/package.json +97 -0
- package/public/Complaint Details.pdf +0 -0
- package/public/Cyber Crime Portal.pdf +0 -0
- package/public/app.js +229 -0
- package/public/css/src/input.css +523 -0
- package/public/css/style.css +1 -0
- package/public/favicon.png +0 -0
- package/public/index.html +549 -0
- package/public/js/components/account-manager.js +356 -0
- package/public/js/components/add-account-modal.js +414 -0
- package/public/js/components/claude-config.js +420 -0
- package/public/js/components/dashboard/charts.js +605 -0
- package/public/js/components/dashboard/filters.js +362 -0
- package/public/js/components/dashboard/stats.js +110 -0
- package/public/js/components/dashboard.js +236 -0
- package/public/js/components/logs-viewer.js +100 -0
- package/public/js/components/models.js +36 -0
- package/public/js/components/server-config.js +349 -0
- package/public/js/config/constants.js +102 -0
- package/public/js/data-store.js +375 -0
- package/public/js/settings-store.js +58 -0
- package/public/js/store.js +99 -0
- package/public/js/translations/en.js +367 -0
- package/public/js/translations/id.js +412 -0
- package/public/js/translations/pt.js +308 -0
- package/public/js/translations/tr.js +358 -0
- package/public/js/translations/zh.js +373 -0
- package/public/js/utils/account-actions.js +189 -0
- package/public/js/utils/error-handler.js +96 -0
- package/public/js/utils/model-config.js +42 -0
- package/public/js/utils/ui-logger.js +143 -0
- package/public/js/utils/validators.js +77 -0
- package/public/js/utils.js +69 -0
- package/public/proxy-server-64.png +0 -0
- package/public/views/accounts.html +361 -0
- package/public/views/dashboard.html +484 -0
- package/public/views/logs.html +97 -0
- package/public/views/models.html +331 -0
- package/public/views/settings.html +1327 -0
- package/src/account-manager/credentials.js +378 -0
- package/src/account-manager/index.js +462 -0
- package/src/account-manager/onboarding.js +112 -0
- package/src/account-manager/rate-limits.js +369 -0
- package/src/account-manager/storage.js +160 -0
- package/src/account-manager/strategies/base-strategy.js +109 -0
- package/src/account-manager/strategies/hybrid-strategy.js +339 -0
- package/src/account-manager/strategies/index.js +79 -0
- package/src/account-manager/strategies/round-robin-strategy.js +76 -0
- package/src/account-manager/strategies/sticky-strategy.js +138 -0
- package/src/account-manager/strategies/trackers/health-tracker.js +162 -0
- package/src/account-manager/strategies/trackers/index.js +9 -0
- package/src/account-manager/strategies/trackers/quota-tracker.js +120 -0
- package/src/account-manager/strategies/trackers/token-bucket-tracker.js +155 -0
- package/src/auth/database.js +169 -0
- package/src/auth/oauth.js +548 -0
- package/src/auth/token-extractor.js +117 -0
- package/src/cli/accounts.js +648 -0
- package/src/cloudcode/index.js +29 -0
- package/src/cloudcode/message-handler.js +510 -0
- package/src/cloudcode/model-api.js +248 -0
- package/src/cloudcode/rate-limit-parser.js +235 -0
- package/src/cloudcode/request-builder.js +93 -0
- package/src/cloudcode/session-manager.js +47 -0
- package/src/cloudcode/sse-parser.js +121 -0
- package/src/cloudcode/sse-streamer.js +293 -0
- package/src/cloudcode/streaming-handler.js +615 -0
- package/src/config.js +125 -0
- package/src/constants.js +407 -0
- package/src/errors.js +242 -0
- package/src/fallback-config.js +29 -0
- package/src/format/content-converter.js +193 -0
- package/src/format/index.js +20 -0
- package/src/format/request-converter.js +255 -0
- package/src/format/response-converter.js +120 -0
- package/src/format/schema-sanitizer.js +673 -0
- package/src/format/signature-cache.js +88 -0
- package/src/format/thinking-utils.js +648 -0
- package/src/index.js +148 -0
- package/src/modules/usage-stats.js +205 -0
- package/src/providers/anthropic-provider.js +258 -0
- package/src/providers/base-provider.js +157 -0
- package/src/providers/cloudcode.js +94 -0
- package/src/providers/copilot.js +399 -0
- package/src/providers/github-provider.js +287 -0
- package/src/providers/google-provider.js +192 -0
- package/src/providers/index.js +211 -0
- package/src/providers/openai-compatible.js +265 -0
- package/src/providers/openai-provider.js +271 -0
- package/src/providers/openrouter-provider.js +325 -0
- package/src/providers/setup.js +83 -0
- package/src/server.js +870 -0
- package/src/utils/claude-config.js +245 -0
- package/src/utils/helpers.js +51 -0
- package/src/utils/logger.js +142 -0
- package/src/utils/native-module-helper.js +162 -0
- 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
|
+
};
|