groove-dev 0.17.7 → 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.
@@ -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-CsymvgNh.js"></script>
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' ? handleOAuthConnect : () => setShowGoogleSetup(true)}
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 OAuth connect flow
315
+ // After saving, immediately trigger the appropriate connect flow
296
316
  if (googleClientId && googleClientSecret) {
297
- setTimeout(() => handleOAuthConnect(), 500);
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' && (integration.envKeys || []).length === 0;
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.7",
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": "none",
102
+ "authType": "google-autoauth",
103
+ "oauthKeysDir": ".calendar-mcp",
103
104
  "envKeys": [],
104
105
  "setupSteps": [
105
- "Click Install, then click 'Sign in with Google'",
106
- "A browser window will openauthorize Groove",
107
- "Done — no API keys or tokens needed"
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": "none",
128
+ "authType": "google-autoauth",
129
+ "oauthKeysDir": ".gmail-mcp",
128
130
  "envKeys": [],
129
131
  "setupSteps": [
130
- "Click Install, then click 'Sign in with Google'",
131
- "A browser window will openauthorize Groove",
132
- "Done — no API keys or tokens needed"
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,
@@ -467,6 +467,12 @@ export class IntegrationStore {
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
 
@@ -531,6 +537,39 @@ export class IntegrationStore {
531
537
  };
532
538
  }
533
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
+
534
573
  // --- Internal ---
535
574
 
536
575
  _isInstalled(integrationId) {