groove-dev 0.17.6 → 0.17.8
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 +10 -8
- package/node_modules/@groove-dev/daemon/src/integrations.js +76 -5
- package/node_modules/@groove-dev/gui/dist/assets/{index-CsymvgNh.js → index-D5dtDQf0.js} +22 -22
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +45 -7
- package/package.json +1 -1
- package/packages/daemon/integrations-registry.json +10 -8
- package/packages/daemon/src/integrations.js +76 -5
- package/packages/gui/dist/assets/{index-CsymvgNh.js → index-D5dtDQf0.js} +22 -22
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/views/IntegrationsStore.jsx +45 -7
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>GROOVE</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D5dtDQf0.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-BhjOFLBc.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -86,8 +86,9 @@ function CredentialModal({ integration, onClose }) {
|
|
|
86
86
|
const [showGoogleSetup, setShowGoogleSetup] = useState(false);
|
|
87
87
|
|
|
88
88
|
useEffect(() => {
|
|
89
|
-
if (integration?.authType === 'oauth-google') {
|
|
89
|
+
if (integration?.authType === 'oauth-google' || integration?.authType === 'google-autoauth' || integration?._googleSetupNeeded) {
|
|
90
90
|
setOauthStatus('checking');
|
|
91
|
+
setShowGoogleSetup(integration?._googleSetupNeeded || false);
|
|
91
92
|
fetch('/api/integrations/google-oauth/status')
|
|
92
93
|
.then((r) => r.json())
|
|
93
94
|
.then((data) => setOauthStatus(data.configured ? 'ready' : 'not-configured'))
|
|
@@ -98,6 +99,7 @@ function CredentialModal({ integration, onClose }) {
|
|
|
98
99
|
if (!integration) return null;
|
|
99
100
|
|
|
100
101
|
const isOAuth = integration.authType === 'oauth-google';
|
|
102
|
+
const isGoogleAutoAuth = integration.authType === 'google-autoauth' || integration._googleSetupNeeded;
|
|
101
103
|
const envKeys = (integration.envKeys || []).filter((ek) => !ek.hidden);
|
|
102
104
|
const setupSteps = integration.setupSteps || [];
|
|
103
105
|
|
|
@@ -133,6 +135,22 @@ function CredentialModal({ integration, onClose }) {
|
|
|
133
135
|
setSaving(false);
|
|
134
136
|
}
|
|
135
137
|
|
|
138
|
+
async function handleAutoAuthConnect() {
|
|
139
|
+
setOauthStatus('connecting');
|
|
140
|
+
try {
|
|
141
|
+
const res = await fetch(`/api/integrations/${integration.id}/authenticate`, { method: 'POST' });
|
|
142
|
+
const data = await res.json();
|
|
143
|
+
if (!data.ok) {
|
|
144
|
+
setOauthStatus('ready');
|
|
145
|
+
}
|
|
146
|
+
// The MCP server will open a browser — poll isn't needed since
|
|
147
|
+
// the server handles auth internally. Just close after a moment.
|
|
148
|
+
setTimeout(() => onClose(), 2000);
|
|
149
|
+
} catch {
|
|
150
|
+
setOauthStatus('ready');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
136
154
|
async function handleOAuthConnect() {
|
|
137
155
|
setOauthStatus('connecting');
|
|
138
156
|
try {
|
|
@@ -218,12 +236,14 @@ function CredentialModal({ integration, onClose }) {
|
|
|
218
236
|
</a>
|
|
219
237
|
)}
|
|
220
238
|
|
|
221
|
-
{/* OAuth flow for Google integrations */}
|
|
222
|
-
{isOAuth && (
|
|
239
|
+
{/* OAuth flow for Google integrations (both oauth-google and google-autoauth) */}
|
|
240
|
+
{(isOAuth || isGoogleAutoAuth) && (
|
|
223
241
|
<div style={{ marginBottom: 16 }}>
|
|
224
242
|
{/* Always show the primary Connect button */}
|
|
225
243
|
<button
|
|
226
|
-
onClick={oauthStatus === 'ready'
|
|
244
|
+
onClick={oauthStatus === 'ready'
|
|
245
|
+
? (isGoogleAutoAuth ? handleAutoAuthConnect : handleOAuthConnect)
|
|
246
|
+
: () => setShowGoogleSetup(true)}
|
|
227
247
|
disabled={oauthStatus === 'checking' || oauthStatus === 'connecting'}
|
|
228
248
|
style={{
|
|
229
249
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
|
@@ -292,9 +312,12 @@ function CredentialModal({ integration, onClose }) {
|
|
|
292
312
|
<button
|
|
293
313
|
onClick={async () => {
|
|
294
314
|
await handleGoogleSetup();
|
|
295
|
-
// After saving, immediately trigger the
|
|
315
|
+
// After saving, immediately trigger the appropriate connect flow
|
|
296
316
|
if (googleClientId && googleClientSecret) {
|
|
297
|
-
setTimeout(() =>
|
|
317
|
+
setTimeout(() => {
|
|
318
|
+
if (isGoogleAutoAuth) handleAutoAuthConnect();
|
|
319
|
+
else handleOAuthConnect();
|
|
320
|
+
}, 500);
|
|
298
321
|
}
|
|
299
322
|
}}
|
|
300
323
|
disabled={saving || !googleClientId || !googleClientSecret}
|
|
@@ -369,7 +392,8 @@ function CredentialModal({ integration, onClose }) {
|
|
|
369
392
|
function IntegrationDetailModal({ integration, installing, onInstall, onUninstall, onConfigure, onAuthenticate, onClose }) {
|
|
370
393
|
if (!integration) return null;
|
|
371
394
|
|
|
372
|
-
const isAutoAuth = integration.authType === 'none'
|
|
395
|
+
const isAutoAuth = (integration.authType === 'none' || integration.authType === 'google-autoauth')
|
|
396
|
+
&& (integration.envKeys || []).length === 0;
|
|
373
397
|
const hasCredentials = (integration.envKeys || []).filter((ek) => !ek.hidden).length > 0
|
|
374
398
|
|| integration.authType === 'oauth-google';
|
|
375
399
|
|
|
@@ -716,6 +740,20 @@ export default function IntegrationsStore() {
|
|
|
716
740
|
}
|
|
717
741
|
|
|
718
742
|
async function handleAuthenticate(item) {
|
|
743
|
+
// For google-autoauth, check if Google OAuth is configured first
|
|
744
|
+
if (item.authType === 'google-autoauth') {
|
|
745
|
+
try {
|
|
746
|
+
const statusRes = await fetch('/api/integrations/google-oauth/status');
|
|
747
|
+
const statusData = await statusRes.json();
|
|
748
|
+
if (!statusData.configured) {
|
|
749
|
+
// Need Google OAuth setup first — open the credential modal
|
|
750
|
+
setSelectedItem(null);
|
|
751
|
+
setConfiguring({ ...item, _googleSetupNeeded: true });
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
} catch { /* proceed anyway */ }
|
|
755
|
+
}
|
|
756
|
+
|
|
719
757
|
setSelectedItem(null);
|
|
720
758
|
try {
|
|
721
759
|
const res = await fetch(`/api/integrations/${item.id}/authenticate`, { method: 'POST' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.8",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -99,12 +99,13 @@
|
|
|
99
99
|
"transport": "stdio",
|
|
100
100
|
"command": "npx",
|
|
101
101
|
"args": ["-y", "@gongrzhe/server-calendar-autoauth-mcp"],
|
|
102
|
-
"authType": "
|
|
102
|
+
"authType": "google-autoauth",
|
|
103
|
+
"oauthKeysDir": ".calendar-mcp",
|
|
103
104
|
"envKeys": [],
|
|
104
105
|
"setupSteps": [
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"Done —
|
|
106
|
+
"One-time: link your Google Cloud OAuth app (shared across all Google integrations)",
|
|
107
|
+
"Click 'Sign in with Google' — a browser opens for authorization",
|
|
108
|
+
"Done — the MCP server handles token refresh automatically"
|
|
108
109
|
],
|
|
109
110
|
"featured": false,
|
|
110
111
|
"downloads": 0,
|
|
@@ -124,12 +125,13 @@
|
|
|
124
125
|
"transport": "stdio",
|
|
125
126
|
"command": "npx",
|
|
126
127
|
"args": ["-y", "@gongrzhe/server-gmail-autoauth-mcp"],
|
|
127
|
-
"authType": "
|
|
128
|
+
"authType": "google-autoauth",
|
|
129
|
+
"oauthKeysDir": ".gmail-mcp",
|
|
128
130
|
"envKeys": [],
|
|
129
131
|
"setupSteps": [
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"Done —
|
|
132
|
+
"One-time: link your Google Cloud OAuth app (shared across all Google integrations)",
|
|
133
|
+
"Click 'Sign in with Google' — a browser opens for authorization",
|
|
134
|
+
"Done — the MCP server handles token refresh automatically"
|
|
133
135
|
],
|
|
134
136
|
"featured": false,
|
|
135
137
|
"downloads": 0,
|
|
@@ -458,15 +458,21 @@ export class IntegrationStore {
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
/**
|
|
461
|
-
* Pre-authenticate an auto-auth integration by running its MCP server
|
|
462
|
-
*
|
|
463
|
-
*
|
|
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
464
|
* Returns a handle to track the auth process.
|
|
465
465
|
*/
|
|
466
466
|
authenticate(integrationId) {
|
|
467
467
|
const entry = this.registry.find((s) => s.id === integrationId);
|
|
468
468
|
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
469
469
|
|
|
470
|
+
// For google-autoauth integrations, write the gcp-oauth.keys.json file
|
|
471
|
+
// that the MCP server expects before it can start the OAuth browser flow
|
|
472
|
+
if (entry.authType === 'google-autoauth') {
|
|
473
|
+
this._writeGoogleOAuthKeys(entry);
|
|
474
|
+
}
|
|
475
|
+
|
|
470
476
|
const command = entry.command || 'npx';
|
|
471
477
|
const args = entry.args || ['-y', entry.npmPackage];
|
|
472
478
|
|
|
@@ -477,13 +483,45 @@ export class IntegrationStore {
|
|
|
477
483
|
if (val) env[ek.key] = val;
|
|
478
484
|
}
|
|
479
485
|
|
|
480
|
-
// Spawn the MCP server
|
|
486
|
+
// Spawn the MCP server with stdin/stdout for JSON-RPC,
|
|
487
|
+
// stderr inherited so it can open browsers and show auth prompts
|
|
481
488
|
const proc = cpSpawn(command, args, {
|
|
482
489
|
env: { ...process.env, ...env },
|
|
483
|
-
stdio: ['pipe', 'pipe', '
|
|
490
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
484
491
|
detached: false,
|
|
485
492
|
});
|
|
486
493
|
|
|
494
|
+
// Send MCP handshake to initialize the server — this triggers auth
|
|
495
|
+
const initMsg = JSON.stringify({
|
|
496
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
497
|
+
params: {
|
|
498
|
+
protocolVersion: '2024-11-05',
|
|
499
|
+
capabilities: {},
|
|
500
|
+
clientInfo: { name: 'groove', version: '1.0.0' },
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
const listToolsMsg = JSON.stringify({
|
|
504
|
+
jsonrpc: '2.0', id: 2, method: 'tools/list', params: {},
|
|
505
|
+
});
|
|
506
|
+
const initializedNotif = JSON.stringify({
|
|
507
|
+
jsonrpc: '2.0', method: 'notifications/initialized',
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Wait a moment for npx to download + start, then send handshake
|
|
511
|
+
proc.stdout.on('data', (chunk) => {
|
|
512
|
+
const text = chunk.toString();
|
|
513
|
+
// After initialize response, send initialized notification + tools/list
|
|
514
|
+
if (text.includes('"id":1') || text.includes('"id": 1')) {
|
|
515
|
+
proc.stdin.write(initializedNotif + '\n');
|
|
516
|
+
setTimeout(() => proc.stdin.write(listToolsMsg + '\n'), 500);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Send initialize after a brief delay for npx startup
|
|
521
|
+
setTimeout(() => {
|
|
522
|
+
try { proc.stdin.write(initMsg + '\n'); } catch { /* process may have exited */ }
|
|
523
|
+
}, 3000);
|
|
524
|
+
|
|
487
525
|
// Auto-kill after 2 minutes (auth should complete well before that)
|
|
488
526
|
const timeout = setTimeout(() => {
|
|
489
527
|
try { proc.kill('SIGTERM'); } catch { /* ignore */ }
|
|
@@ -499,6 +537,39 @@ export class IntegrationStore {
|
|
|
499
537
|
};
|
|
500
538
|
}
|
|
501
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Write gcp-oauth.keys.json for Google auto-auth MCP servers.
|
|
542
|
+
* These servers need a Google Cloud OAuth client file at a specific path
|
|
543
|
+
* before they can open the browser for user consent.
|
|
544
|
+
*/
|
|
545
|
+
_writeGoogleOAuthKeys(entry) {
|
|
546
|
+
const clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
|
|
547
|
+
const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
548
|
+
if (!clientId || !clientSecret) {
|
|
549
|
+
throw new Error('Google OAuth not configured. Click "Sign in with Google" to set up your Google Cloud credentials first.');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const keysContent = JSON.stringify({
|
|
553
|
+
installed: {
|
|
554
|
+
client_id: clientId,
|
|
555
|
+
client_secret: clientSecret,
|
|
556
|
+
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
|
|
557
|
+
token_uri: 'https://oauth2.googleapis.com/token',
|
|
558
|
+
redirect_uris: ['http://localhost'],
|
|
559
|
+
},
|
|
560
|
+
}, null, 2);
|
|
561
|
+
|
|
562
|
+
// Write to the directory the MCP server expects (e.g., ~/.gmail-mcp/)
|
|
563
|
+
const keysDir = entry.oauthKeysDir;
|
|
564
|
+
if (keysDir) {
|
|
565
|
+
const homedir = process.env.HOME || process.env.USERPROFILE || '~';
|
|
566
|
+
const dirPath = resolve(homedir, keysDir);
|
|
567
|
+
mkdirSync(dirPath, { recursive: true });
|
|
568
|
+
const keysPath = resolve(dirPath, 'gcp-oauth.keys.json');
|
|
569
|
+
writeFileSync(keysPath, keysContent, { mode: 0o600 });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
502
573
|
// --- Internal ---
|
|
503
574
|
|
|
504
575
|
_isInstalled(integrationId) {
|