agentgui 1.0.200 → 1.0.202
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/package.json +2 -1
- package/server.js +313 -1
- package/static/js/agent-auth.js +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.202",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"busboy": "^1.6.0",
|
|
29
29
|
"express": "^5.2.1",
|
|
30
30
|
"fsbrowse": "^0.2.13",
|
|
31
|
+
"google-auth-library": "^10.5.0",
|
|
31
32
|
"onnxruntime-node": "^1.24.1",
|
|
32
33
|
"webtalk": "github:AnEntrypoint/webtalk",
|
|
33
34
|
"ws": "^8.14.2"
|
package/server.js
CHANGED
|
@@ -3,10 +3,13 @@ import fs from 'fs';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import zlib from 'zlib';
|
|
6
|
+
import net from 'net';
|
|
7
|
+
import crypto from 'crypto';
|
|
6
8
|
import { fileURLToPath } from 'url';
|
|
7
9
|
import { WebSocketServer } from 'ws';
|
|
8
10
|
import { execSync, spawn } from 'child_process';
|
|
9
11
|
import { createRequire } from 'module';
|
|
12
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
10
13
|
import { queries } from './database.js';
|
|
11
14
|
import { runClaudeWithStreaming } from './lib/claude-runner.js';
|
|
12
15
|
let speechModule = null;
|
|
@@ -180,6 +183,248 @@ function discoverAgents() {
|
|
|
180
183
|
|
|
181
184
|
const discoveredAgents = discoverAgents();
|
|
182
185
|
|
|
186
|
+
const GEMINI_SCOPES = [
|
|
187
|
+
'https://www.googleapis.com/auth/cloud-platform',
|
|
188
|
+
'https://www.googleapis.com/auth/userinfo.email',
|
|
189
|
+
'https://www.googleapis.com/auth/userinfo.profile',
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
function extractOAuthFromFile(oauth2Path) {
|
|
193
|
+
try {
|
|
194
|
+
const src = fs.readFileSync(oauth2Path, 'utf8');
|
|
195
|
+
const idMatch = src.match(/OAUTH_CLIENT_ID\s*=\s*['"]([^'"]+)['"]/);
|
|
196
|
+
const secretMatch = src.match(/OAUTH_CLIENT_SECRET\s*=\s*['"]([^'"]+)['"]/);
|
|
197
|
+
if (idMatch && secretMatch) return { clientId: idMatch[1], clientSecret: secretMatch[1] };
|
|
198
|
+
} catch {}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getGeminiOAuthCreds() {
|
|
203
|
+
const oauthRelPath = path.join('node_modules', '@google', 'gemini-cli-core', 'dist', 'src', 'code_assist', 'oauth2.js');
|
|
204
|
+
try {
|
|
205
|
+
const geminiPath = execSync('which gemini', { encoding: 'utf8' }).trim();
|
|
206
|
+
const realPath = fs.realpathSync(geminiPath);
|
|
207
|
+
const pkgRoot = path.resolve(path.dirname(realPath), '..');
|
|
208
|
+
const result = extractOAuthFromFile(path.join(pkgRoot, oauthRelPath));
|
|
209
|
+
if (result) return result;
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.error('[gemini-oauth] which gemini lookup failed:', e.message);
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const npmCacheDirs = new Set();
|
|
215
|
+
const addDir = (d) => { if (d) npmCacheDirs.add(path.join(d, '_npx')); };
|
|
216
|
+
addDir(path.join(os.homedir(), '.npm'));
|
|
217
|
+
addDir(path.join(os.homedir(), '.cache', '.npm'));
|
|
218
|
+
if (process.env.NPM_CACHE) addDir(process.env.NPM_CACHE);
|
|
219
|
+
if (process.env.npm_config_cache) addDir(process.env.npm_config_cache);
|
|
220
|
+
try { addDir(execSync('npm config get cache', { encoding: 'utf8', timeout: 5000 }).trim()); } catch {}
|
|
221
|
+
for (const cacheDir of npmCacheDirs) {
|
|
222
|
+
if (!fs.existsSync(cacheDir)) continue;
|
|
223
|
+
for (const d of fs.readdirSync(cacheDir).filter(d => !d.startsWith('.'))) {
|
|
224
|
+
const result = extractOAuthFromFile(path.join(cacheDir, d, oauthRelPath));
|
|
225
|
+
if (result) return result;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.error('[gemini-oauth] npm cache scan failed:', e.message);
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const found = execSync('find / -path "*/gemini-cli-core/dist/src/code_assist/oauth2.js" -maxdepth 10 2>/dev/null | head -1', { encoding: 'utf8', timeout: 10000 }).trim();
|
|
233
|
+
if (found) {
|
|
234
|
+
const result = extractOAuthFromFile(found);
|
|
235
|
+
if (result) return result;
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.error('[gemini-oauth] filesystem search failed:', e.message);
|
|
239
|
+
}
|
|
240
|
+
console.error('[gemini-oauth] Could not find Gemini CLI OAuth credentials in any known location');
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const GEMINI_DIR = path.join(os.homedir(), '.gemini');
|
|
244
|
+
const GEMINI_OAUTH_FILE = path.join(GEMINI_DIR, 'oauth_creds.json');
|
|
245
|
+
const GEMINI_ACCOUNTS_FILE = path.join(GEMINI_DIR, 'google_accounts.json');
|
|
246
|
+
|
|
247
|
+
let geminiOAuthState = { status: 'idle', error: null, email: null };
|
|
248
|
+
let geminiOAuthCallbackServer = null;
|
|
249
|
+
|
|
250
|
+
function getAvailablePort() {
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
const srv = net.createServer();
|
|
253
|
+
srv.listen(0, () => {
|
|
254
|
+
const port = srv.address().port;
|
|
255
|
+
srv.close(() => resolve(port));
|
|
256
|
+
});
|
|
257
|
+
srv.on('error', reject);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function saveGeminiCredentials(tokens, email) {
|
|
262
|
+
if (!fs.existsSync(GEMINI_DIR)) fs.mkdirSync(GEMINI_DIR, { recursive: true });
|
|
263
|
+
fs.writeFileSync(GEMINI_OAUTH_FILE, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
264
|
+
try { fs.chmodSync(GEMINI_OAUTH_FILE, 0o600); } catch (_) {}
|
|
265
|
+
|
|
266
|
+
let accounts = { active: null, old: [] };
|
|
267
|
+
try {
|
|
268
|
+
if (fs.existsSync(GEMINI_ACCOUNTS_FILE)) {
|
|
269
|
+
accounts = JSON.parse(fs.readFileSync(GEMINI_ACCOUNTS_FILE, 'utf8'));
|
|
270
|
+
}
|
|
271
|
+
} catch (_) {}
|
|
272
|
+
|
|
273
|
+
if (email) {
|
|
274
|
+
if (accounts.active && accounts.active !== email && !accounts.old.includes(accounts.active)) {
|
|
275
|
+
accounts.old.push(accounts.active);
|
|
276
|
+
}
|
|
277
|
+
accounts.active = email;
|
|
278
|
+
}
|
|
279
|
+
fs.writeFileSync(GEMINI_ACCOUNTS_FILE, JSON.stringify(accounts, null, 2), { mode: 0o600 });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function geminiOAuthResultPage(title, message, success) {
|
|
283
|
+
const color = success ? '#10b981' : '#ef4444';
|
|
284
|
+
const icon = success ? '✓' : '✗';
|
|
285
|
+
return `<!DOCTYPE html><html><head><title>${title}</title></head>
|
|
286
|
+
<body style="margin:0;display:flex;align-items:center;justify-content:center;min-height:100vh;background:#111827;font-family:system-ui,sans-serif;color:white;">
|
|
287
|
+
<div style="text-align:center;max-width:400px;padding:2rem;">
|
|
288
|
+
<div style="font-size:4rem;color:${color};margin-bottom:1rem;">${icon}</div>
|
|
289
|
+
<h1 style="font-size:1.5rem;margin-bottom:0.5rem;">${title}</h1>
|
|
290
|
+
<p style="color:#9ca3af;">${message}</p>
|
|
291
|
+
<p style="color:#6b7280;margin-top:1rem;font-size:0.875rem;">You can close this tab.</p>
|
|
292
|
+
</div></body></html>`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function startGeminiOAuth() {
|
|
296
|
+
if (geminiOAuthCallbackServer) {
|
|
297
|
+
try { geminiOAuthCallbackServer.close(); } catch (_) {}
|
|
298
|
+
geminiOAuthCallbackServer = null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const creds = getGeminiOAuthCreds();
|
|
302
|
+
if (!creds) throw new Error('Could not find Gemini CLI OAuth credentials. Install gemini CLI first.');
|
|
303
|
+
|
|
304
|
+
const port = await getAvailablePort();
|
|
305
|
+
const redirectUri = `http://127.0.0.1:${port}/oauth2callback`;
|
|
306
|
+
const state = crypto.randomBytes(32).toString('hex');
|
|
307
|
+
|
|
308
|
+
const client = new OAuth2Client({
|
|
309
|
+
clientId: creds.clientId,
|
|
310
|
+
clientSecret: creds.clientSecret,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const authUrl = client.generateAuthUrl({
|
|
314
|
+
redirect_uri: redirectUri,
|
|
315
|
+
access_type: 'offline',
|
|
316
|
+
scope: GEMINI_SCOPES,
|
|
317
|
+
state,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
geminiOAuthState = { status: 'pending', error: null, email: null };
|
|
321
|
+
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const cbServer = http.createServer(async (req, res) => {
|
|
324
|
+
try {
|
|
325
|
+
const reqUrl = new URL(req.url, `http://127.0.0.1:${port}`);
|
|
326
|
+
if (reqUrl.pathname !== '/oauth2callback') {
|
|
327
|
+
res.writeHead(404);
|
|
328
|
+
res.end('Not found');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const error = reqUrl.searchParams.get('error');
|
|
333
|
+
if (error) {
|
|
334
|
+
const desc = reqUrl.searchParams.get('error_description') || error;
|
|
335
|
+
geminiOAuthState = { status: 'error', error: desc, email: null };
|
|
336
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
337
|
+
res.end(geminiOAuthResultPage('Authentication Failed', desc, false));
|
|
338
|
+
cbServer.close();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (reqUrl.searchParams.get('state') !== state) {
|
|
343
|
+
geminiOAuthState = { status: 'error', error: 'State mismatch', email: null };
|
|
344
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
345
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'State mismatch.', false));
|
|
346
|
+
cbServer.close();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const code = reqUrl.searchParams.get('code');
|
|
351
|
+
if (!code) {
|
|
352
|
+
geminiOAuthState = { status: 'error', error: 'No authorization code', email: null };
|
|
353
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
354
|
+
res.end(geminiOAuthResultPage('Authentication Failed', 'No authorization code received.', false));
|
|
355
|
+
cbServer.close();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const { tokens } = await client.getToken({ code, redirect_uri: redirectUri });
|
|
360
|
+
client.setCredentials(tokens);
|
|
361
|
+
|
|
362
|
+
let email = '';
|
|
363
|
+
try {
|
|
364
|
+
const { token } = await client.getAccessToken();
|
|
365
|
+
if (token) {
|
|
366
|
+
const resp = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
367
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
368
|
+
});
|
|
369
|
+
if (resp.ok) {
|
|
370
|
+
const info = await resp.json();
|
|
371
|
+
email = info.email || '';
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
} catch (_) {}
|
|
375
|
+
|
|
376
|
+
saveGeminiCredentials(tokens, email);
|
|
377
|
+
geminiOAuthState = { status: 'success', error: null, email };
|
|
378
|
+
|
|
379
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
380
|
+
res.end(geminiOAuthResultPage('Authentication Successful', email ? `Signed in as ${email}` : 'Gemini CLI credentials saved.', true));
|
|
381
|
+
cbServer.close();
|
|
382
|
+
} catch (e) {
|
|
383
|
+
geminiOAuthState = { status: 'error', error: e.message, email: null };
|
|
384
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
385
|
+
res.end(geminiOAuthResultPage('Authentication Failed', e.message, false));
|
|
386
|
+
cbServer.close();
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
cbServer.on('error', (err) => {
|
|
391
|
+
geminiOAuthState = { status: 'error', error: err.message, email: null };
|
|
392
|
+
reject(err);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
cbServer.listen(port, '127.0.0.1', () => {
|
|
396
|
+
geminiOAuthCallbackServer = cbServer;
|
|
397
|
+
resolve(authUrl);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
setTimeout(() => {
|
|
401
|
+
if (geminiOAuthState.status === 'pending') {
|
|
402
|
+
geminiOAuthState = { status: 'error', error: 'Authentication timed out', email: null };
|
|
403
|
+
try { cbServer.close(); } catch (_) {}
|
|
404
|
+
}
|
|
405
|
+
}, 5 * 60 * 1000);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function getGeminiOAuthStatus() {
|
|
410
|
+
try {
|
|
411
|
+
if (fs.existsSync(GEMINI_OAUTH_FILE)) {
|
|
412
|
+
const creds = JSON.parse(fs.readFileSync(GEMINI_OAUTH_FILE, 'utf8'));
|
|
413
|
+
if (creds.refresh_token || creds.access_token) {
|
|
414
|
+
let email = '';
|
|
415
|
+
try {
|
|
416
|
+
if (fs.existsSync(GEMINI_ACCOUNTS_FILE)) {
|
|
417
|
+
const accts = JSON.parse(fs.readFileSync(GEMINI_ACCOUNTS_FILE, 'utf8'));
|
|
418
|
+
email = accts.active || '';
|
|
419
|
+
}
|
|
420
|
+
} catch (_) {}
|
|
421
|
+
return { hasKey: true, apiKey: email || '****oauth', defaultModel: '', path: GEMINI_OAUTH_FILE, authMethod: 'oauth' };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
} catch (_) {}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
|
|
183
428
|
const PROVIDER_CONFIGS = {
|
|
184
429
|
'anthropic': {
|
|
185
430
|
name: 'Anthropic', configPaths: [
|
|
@@ -255,6 +500,13 @@ function maskKey(key) {
|
|
|
255
500
|
function getProviderConfigs() {
|
|
256
501
|
const configs = {};
|
|
257
502
|
for (const [providerId, config] of Object.entries(PROVIDER_CONFIGS)) {
|
|
503
|
+
if (providerId === 'google') {
|
|
504
|
+
const oauthStatus = getGeminiOAuthStatus();
|
|
505
|
+
if (oauthStatus) {
|
|
506
|
+
configs[providerId] = { name: config.name, ...oauthStatus };
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
258
510
|
for (const configPath of config.configPaths) {
|
|
259
511
|
try {
|
|
260
512
|
if (fs.existsSync(configPath)) {
|
|
@@ -792,15 +1044,29 @@ const server = http.createServer(async (req, res) => {
|
|
|
792
1044
|
status.detail = 'no credentials';
|
|
793
1045
|
}
|
|
794
1046
|
} else if (agent.id === 'gemini') {
|
|
1047
|
+
const oauthFile = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
|
|
795
1048
|
const acctFile = path.join(os.homedir(), '.gemini', 'google_accounts.json');
|
|
1049
|
+
let hasOAuth = false;
|
|
1050
|
+
if (fs.existsSync(oauthFile)) {
|
|
1051
|
+
try {
|
|
1052
|
+
const creds = JSON.parse(fs.readFileSync(oauthFile, 'utf-8'));
|
|
1053
|
+
if (creds.refresh_token || creds.access_token) hasOAuth = true;
|
|
1054
|
+
} catch (_) {}
|
|
1055
|
+
}
|
|
796
1056
|
if (fs.existsSync(acctFile)) {
|
|
797
1057
|
const accts = JSON.parse(fs.readFileSync(acctFile, 'utf-8'));
|
|
798
1058
|
if (accts.active) {
|
|
799
1059
|
status.authenticated = true;
|
|
800
1060
|
status.detail = accts.active;
|
|
1061
|
+
} else if (hasOAuth) {
|
|
1062
|
+
status.authenticated = true;
|
|
1063
|
+
status.detail = 'oauth';
|
|
801
1064
|
} else {
|
|
802
1065
|
status.detail = 'logged out';
|
|
803
1066
|
}
|
|
1067
|
+
} else if (hasOAuth) {
|
|
1068
|
+
status.authenticated = true;
|
|
1069
|
+
status.detail = 'oauth';
|
|
804
1070
|
} else {
|
|
805
1071
|
status.detail = 'no credentials';
|
|
806
1072
|
}
|
|
@@ -825,16 +1091,62 @@ const server = http.createServer(async (req, res) => {
|
|
|
825
1091
|
return;
|
|
826
1092
|
}
|
|
827
1093
|
|
|
1094
|
+
if (pathOnly === '/api/gemini-oauth/start' && req.method === 'POST') {
|
|
1095
|
+
try {
|
|
1096
|
+
const authUrl = await startGeminiOAuth();
|
|
1097
|
+
sendJSON(req, res, 200, { authUrl });
|
|
1098
|
+
} catch (e) {
|
|
1099
|
+
console.error('[gemini-oauth] /api/gemini-oauth/start failed:', e);
|
|
1100
|
+
sendJSON(req, res, 500, { error: e.message });
|
|
1101
|
+
}
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (pathOnly === '/api/gemini-oauth/status' && req.method === 'GET') {
|
|
1106
|
+
sendJSON(req, res, 200, geminiOAuthState);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
828
1110
|
const agentAuthMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/auth$/);
|
|
829
1111
|
if (agentAuthMatch && req.method === 'POST') {
|
|
830
1112
|
const agentId = agentAuthMatch[1];
|
|
831
1113
|
const agent = discoveredAgents.find(a => a.id === agentId);
|
|
832
1114
|
if (!agent) { sendJSON(req, res, 404, { error: 'Agent not found' }); return; }
|
|
833
1115
|
|
|
1116
|
+
if (agentId === 'gemini') {
|
|
1117
|
+
try {
|
|
1118
|
+
const authUrl = await startGeminiOAuth();
|
|
1119
|
+
const conversationId = '__agent_auth__';
|
|
1120
|
+
broadcastSync({ type: 'script_started', conversationId, script: 'auth-gemini', agentId: 'gemini', timestamp: Date.now() });
|
|
1121
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\x1b[36mOpening Google OAuth in your browser...\x1b[0m\r\n\r\nIf it doesn't open automatically, visit:\r\n${authUrl}\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1122
|
+
|
|
1123
|
+
const pollId = setInterval(() => {
|
|
1124
|
+
if (geminiOAuthState.status === 'success') {
|
|
1125
|
+
clearInterval(pollId);
|
|
1126
|
+
const email = geminiOAuthState.email || '';
|
|
1127
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\r\n\x1b[32mAuthentication successful${email ? ' (' + email + ')' : ''}\x1b[0m\r\n`, stream: 'stdout', timestamp: Date.now() });
|
|
1128
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 0, timestamp: Date.now() });
|
|
1129
|
+
} else if (geminiOAuthState.status === 'error') {
|
|
1130
|
+
clearInterval(pollId);
|
|
1131
|
+
broadcastSync({ type: 'script_output', conversationId, data: `\r\n\x1b[31mAuthentication failed: ${geminiOAuthState.error}\x1b[0m\r\n`, stream: 'stderr', timestamp: Date.now() });
|
|
1132
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 1, error: geminiOAuthState.error, timestamp: Date.now() });
|
|
1133
|
+
}
|
|
1134
|
+
}, 1000);
|
|
1135
|
+
|
|
1136
|
+
setTimeout(() => clearInterval(pollId), 5 * 60 * 1000);
|
|
1137
|
+
|
|
1138
|
+
sendJSON(req, res, 200, { ok: true, agentId, authUrl });
|
|
1139
|
+
return;
|
|
1140
|
+
} catch (e) {
|
|
1141
|
+
console.error('[gemini-oauth] /api/agents/gemini/auth failed:', e);
|
|
1142
|
+
sendJSON(req, res, 500, { error: e.message });
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
834
1147
|
const authCommands = {
|
|
835
1148
|
'claude-code': { cmd: 'claude', args: ['setup-token'] },
|
|
836
1149
|
'opencode': { cmd: 'opencode', args: ['auth', 'login'] },
|
|
837
|
-
'gemini': { cmd: 'gemini', args: [] }
|
|
838
1150
|
};
|
|
839
1151
|
const authCmd = authCommands[agentId];
|
|
840
1152
|
if (!authCmd) { sendJSON(req, res, 400, { error: 'No auth command for this agent' }); return; }
|
package/static/js/agent-auth.js
CHANGED
|
@@ -139,6 +139,9 @@
|
|
|
139
139
|
authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
140
140
|
var term = getTerminal();
|
|
141
141
|
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
|
|
142
|
+
if (data.authUrl) {
|
|
143
|
+
window.open(data.authUrl, '_blank');
|
|
144
|
+
}
|
|
142
145
|
}
|
|
143
146
|
}).catch(function() {});
|
|
144
147
|
}
|