groove-dev 0.17.5 → 0.17.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/node_modules/@groove-dev/daemon/integrations-registry.json +6 -6
- package/node_modules/@groove-dev/daemon/src/api.js +10 -0
- package/node_modules/@groove-dev/daemon/src/integrations.js +75 -1
- package/node_modules/@groove-dev/gui/dist/assets/{index-DYqJ7W7j.js → index-CsymvgNh.js} +18 -18
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +38 -1
- package/package.json +1 -1
- package/packages/daemon/integrations-registry.json +6 -6
- package/packages/daemon/src/api.js +10 -0
- package/packages/daemon/src/integrations.js +75 -1
- package/packages/gui/dist/assets/{index-DYqJ7W7j.js → index-CsymvgNh.js} +18 -18
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/views/IntegrationsStore.jsx +38 -1
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
"authType": "none",
|
|
103
103
|
"envKeys": [],
|
|
104
104
|
"setupSteps": [
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
105
|
+
"Click Install, then click 'Sign in with Google'",
|
|
106
|
+
"A browser window will open — authorize Groove",
|
|
107
|
+
"Done — no API keys or tokens needed"
|
|
108
108
|
],
|
|
109
109
|
"featured": false,
|
|
110
110
|
"downloads": 0,
|
|
@@ -127,9 +127,9 @@
|
|
|
127
127
|
"authType": "none",
|
|
128
128
|
"envKeys": [],
|
|
129
129
|
"setupSteps": [
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
130
|
+
"Click Install, then click 'Sign in with Google'",
|
|
131
|
+
"A browser window will open — authorize Groove",
|
|
132
|
+
"Done — no API keys or tokens needed"
|
|
133
133
|
],
|
|
134
134
|
"featured": false,
|
|
135
135
|
"downloads": 0,
|
|
@@ -506,6 +506,16 @@ export function createApi(app, daemon) {
|
|
|
506
506
|
|
|
507
507
|
// Parameterized :id routes (after specific routes above)
|
|
508
508
|
|
|
509
|
+
app.post('/api/integrations/:id/authenticate', (req, res) => {
|
|
510
|
+
try {
|
|
511
|
+
const handle = daemon.integrations.authenticate(req.params.id);
|
|
512
|
+
res.json({ ok: true, pid: handle.pid });
|
|
513
|
+
// Auto-cleanup tracked by the handle timeout
|
|
514
|
+
} catch (err) {
|
|
515
|
+
res.status(400).json({ error: err.message });
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
509
519
|
app.post('/api/integrations/:id/install', async (req, res) => {
|
|
510
520
|
try {
|
|
511
521
|
const result = await daemon.integrations.install(req.params.id);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'fs';
|
|
5
5
|
import { resolve, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
-
import { execFileSync } from 'child_process';
|
|
7
|
+
import { execFileSync, spawn as cpSpawn } from 'child_process';
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
|
|
@@ -457,6 +457,80 @@ export class IntegrationStore {
|
|
|
457
457
|
return !!(clientId && clientSecret);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
+
/**
|
|
461
|
+
* Pre-authenticate an auto-auth integration by running its MCP server
|
|
462
|
+
* and sending the MCP handshake (initialize + tools/list). This triggers
|
|
463
|
+
* the server's built-in OAuth flow which opens a browser for sign-in.
|
|
464
|
+
* Returns a handle to track the auth process.
|
|
465
|
+
*/
|
|
466
|
+
authenticate(integrationId) {
|
|
467
|
+
const entry = this.registry.find((s) => s.id === integrationId);
|
|
468
|
+
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
469
|
+
|
|
470
|
+
const command = entry.command || 'npx';
|
|
471
|
+
const args = entry.args || ['-y', entry.npmPackage];
|
|
472
|
+
|
|
473
|
+
// Build env with any configured credentials
|
|
474
|
+
const env = {};
|
|
475
|
+
for (const ek of (entry.envKeys || [])) {
|
|
476
|
+
const val = this.getCredential(integrationId, ek.key);
|
|
477
|
+
if (val) env[ek.key] = val;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Spawn the MCP server with stdin/stdout for JSON-RPC,
|
|
481
|
+
// stderr inherited so it can open browsers and show auth prompts
|
|
482
|
+
const proc = cpSpawn(command, args, {
|
|
483
|
+
env: { ...process.env, ...env },
|
|
484
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
485
|
+
detached: false,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Send MCP handshake to initialize the server — this triggers auth
|
|
489
|
+
const initMsg = JSON.stringify({
|
|
490
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
491
|
+
params: {
|
|
492
|
+
protocolVersion: '2024-11-05',
|
|
493
|
+
capabilities: {},
|
|
494
|
+
clientInfo: { name: 'groove', version: '1.0.0' },
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
const listToolsMsg = JSON.stringify({
|
|
498
|
+
jsonrpc: '2.0', id: 2, method: 'tools/list', params: {},
|
|
499
|
+
});
|
|
500
|
+
const initializedNotif = JSON.stringify({
|
|
501
|
+
jsonrpc: '2.0', method: 'notifications/initialized',
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Wait a moment for npx to download + start, then send handshake
|
|
505
|
+
proc.stdout.on('data', (chunk) => {
|
|
506
|
+
const text = chunk.toString();
|
|
507
|
+
// After initialize response, send initialized notification + tools/list
|
|
508
|
+
if (text.includes('"id":1') || text.includes('"id": 1')) {
|
|
509
|
+
proc.stdin.write(initializedNotif + '\n');
|
|
510
|
+
setTimeout(() => proc.stdin.write(listToolsMsg + '\n'), 500);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Send initialize after a brief delay for npx startup
|
|
515
|
+
setTimeout(() => {
|
|
516
|
+
try { proc.stdin.write(initMsg + '\n'); } catch { /* process may have exited */ }
|
|
517
|
+
}, 3000);
|
|
518
|
+
|
|
519
|
+
// Auto-kill after 2 minutes (auth should complete well before that)
|
|
520
|
+
const timeout = setTimeout(() => {
|
|
521
|
+
try { proc.kill('SIGTERM'); } catch { /* ignore */ }
|
|
522
|
+
}, 120_000);
|
|
523
|
+
|
|
524
|
+
proc.on('exit', () => clearTimeout(timeout));
|
|
525
|
+
|
|
526
|
+
this.daemon.audit.log('integration.authenticate', { id: integrationId });
|
|
527
|
+
|
|
528
|
+
return {
|
|
529
|
+
pid: proc.pid,
|
|
530
|
+
kill: () => { clearTimeout(timeout); try { proc.kill('SIGTERM'); } catch { /* ignore */ } },
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
460
534
|
// --- Internal ---
|
|
461
535
|
|
|
462
536
|
_isInstalled(integrationId) {
|