groove-dev 0.17.7 → 0.18.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/CLAUDE.md +0 -7
- package/node_modules/@groove-dev/daemon/google-oauth.json +5 -0
- package/node_modules/@groove-dev/daemon/integrations-registry.json +10 -48
- package/node_modules/@groove-dev/daemon/src/api.js +103 -12
- package/node_modules/@groove-dev/daemon/src/integrations.js +94 -16
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -0
- package/node_modules/@groove-dev/gui/.groove/codebase-index.json +64 -0
- package/node_modules/@groove-dev/gui/.groove/config.json +10 -0
- package/node_modules/@groove-dev/gui/.groove/coordination.md +5 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.host +1 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.pid +1 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.port +1 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.key +3 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.pub +3 -0
- package/node_modules/@groove-dev/gui/.groove/integrations/package.json +6 -0
- package/node_modules/@groove-dev/gui/.groove/state.json +3 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-DXkccbmd.js +182 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/App.jsx +27 -4
- package/node_modules/@groove-dev/gui/src/components/AgentChat.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +839 -586
- package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +85 -1
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +157 -42
- package/package.json +1 -2
- package/packages/daemon/integrations-registry.json +10 -48
- package/packages/daemon/src/api.js +103 -12
- package/packages/daemon/src/integrations.js +94 -16
- package/packages/daemon/src/providers/claude-code.js +4 -0
- package/packages/gui/dist/assets/index-DXkccbmd.js +182 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +68 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +1420 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +17 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +22 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +34 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +101 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +2534 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +789 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +115 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +1136 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +63 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +179 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +104 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +46 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +121 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +9237 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +9934 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/_metadata.json +184 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +5169 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +2000 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js +1115 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js +701 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +1776 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js +280 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js +1004 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js +292 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +1062 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +10985 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +3459 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/package.json +3 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js +20217 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react.js +5 -0
- package/packages/gui/node_modules/.vite/deps/react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js +56 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js.map +7 -0
- package/packages/gui/src/App.jsx +27 -4
- package/packages/gui/src/components/AgentChat.jsx +1 -1
- package/packages/gui/src/components/SpawnPanel.jsx +839 -586
- package/packages/gui/src/views/FileEditor.jsx +85 -1
- package/packages/gui/src/views/IntegrationsStore.jsx +157 -42
- package/docs/FILE-EDITOR-PLAN.md +0 -253
- package/docs/GUI_DESIGN_SPEC.md +0 -402
- package/docs/SKILLS-API-SPEC.md +0 -277
- package/node_modules/@groove-dev/gui/dist/assets/index-CsymvgNh.js +0 -156
- package/packages/gui/dist/assets/index-CsymvgNh.js +0 -156
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GROOVE GUI — File Editor View
|
|
2
2
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
3
|
|
|
4
|
-
import React, { useState, useCallback } from 'react';
|
|
4
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
5
5
|
import { useGrooveStore } from '../stores/groove';
|
|
6
6
|
import FileTree from '../components/FileTree';
|
|
7
7
|
import EditorTabs from '../components/EditorTabs';
|
|
@@ -18,9 +18,39 @@ export default function FileEditor() {
|
|
|
18
18
|
const saveFile = useGrooveStore((s) => s.saveFile);
|
|
19
19
|
const reloadFile = useGrooveStore((s) => s.reloadFile);
|
|
20
20
|
const dismissFileChange = useGrooveStore((s) => s.dismissFileChange);
|
|
21
|
+
const fetchTreeDir = useGrooveStore((s) => s.fetchTreeDir);
|
|
21
22
|
|
|
22
23
|
const [terminalOpen, setTerminalOpen] = useState(false);
|
|
23
24
|
const [terminalHeight, setTerminalHeight] = useState(220);
|
|
25
|
+
const [rootDir, setRootDir] = useState('');
|
|
26
|
+
const [rootInput, setRootInput] = useState('');
|
|
27
|
+
const [editingRoot, setEditingRoot] = useState(false);
|
|
28
|
+
|
|
29
|
+
// Fetch current editor root on mount
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
fetch('/api/files/root').then((r) => r.json()).then((d) => {
|
|
32
|
+
setRootDir(d.root || '');
|
|
33
|
+
setRootInput(d.root || '');
|
|
34
|
+
}).catch(() => {});
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
async function handleRootChange() {
|
|
38
|
+
if (!rootInput.trim()) return;
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch('/api/files/root', {
|
|
41
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify({ root: rootInput.trim() }),
|
|
43
|
+
});
|
|
44
|
+
const data = await res.json();
|
|
45
|
+
if (data.ok) {
|
|
46
|
+
setRootDir(data.root);
|
|
47
|
+
setEditingRoot(false);
|
|
48
|
+
// Clear tree cache and reload
|
|
49
|
+
useGrooveStore.setState({ editorTreeCache: {}, editorOpenTabs: [], editorActiveFile: null, editorFiles: {} });
|
|
50
|
+
fetchTreeDir('');
|
|
51
|
+
}
|
|
52
|
+
} catch { /* ignore */ }
|
|
53
|
+
}
|
|
24
54
|
|
|
25
55
|
const file = activeFile ? files[activeFile] : null;
|
|
26
56
|
const isChanged = activeFile && changedFiles[activeFile];
|
|
@@ -54,6 +84,29 @@ export default function FileEditor() {
|
|
|
54
84
|
|
|
55
85
|
return (
|
|
56
86
|
<div style={styles.container}>
|
|
87
|
+
{/* Directory bar */}
|
|
88
|
+
<div style={styles.dirBar}>
|
|
89
|
+
<span style={styles.dirLabel}>DIR</span>
|
|
90
|
+
{editingRoot ? (
|
|
91
|
+
<div style={{ display: 'flex', flex: 1, gap: 4 }}>
|
|
92
|
+
<input
|
|
93
|
+
style={styles.dirInput}
|
|
94
|
+
value={rootInput}
|
|
95
|
+
onChange={(e) => setRootInput(e.target.value)}
|
|
96
|
+
onKeyDown={(e) => { if (e.key === 'Enter') handleRootChange(); if (e.key === 'Escape') { setEditingRoot(false); setRootInput(rootDir); } }}
|
|
97
|
+
placeholder="/absolute/path/to/project"
|
|
98
|
+
autoFocus
|
|
99
|
+
/>
|
|
100
|
+
<button onClick={handleRootChange} style={styles.dirOkBtn}>Open</button>
|
|
101
|
+
<button onClick={() => { setEditingRoot(false); setRootInput(rootDir); }} style={styles.dirCancelBtn}>×</button>
|
|
102
|
+
</div>
|
|
103
|
+
) : (
|
|
104
|
+
<button onClick={() => setEditingRoot(true)} style={styles.dirPath} title="Click to change directory">
|
|
105
|
+
{rootDir || '...'}
|
|
106
|
+
</button>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
|
|
57
110
|
{/* Tab bar */}
|
|
58
111
|
<EditorTabs />
|
|
59
112
|
|
|
@@ -130,6 +183,37 @@ const styles = {
|
|
|
130
183
|
display: 'flex', flexDirection: 'column',
|
|
131
184
|
height: '100%', width: '100%',
|
|
132
185
|
},
|
|
186
|
+
dirBar: {
|
|
187
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
188
|
+
padding: '4px 12px', flexShrink: 0,
|
|
189
|
+
borderBottom: '1px solid var(--border)',
|
|
190
|
+
background: 'var(--bg-chrome)',
|
|
191
|
+
},
|
|
192
|
+
dirLabel: {
|
|
193
|
+
fontSize: 9, fontWeight: 700, color: 'var(--text-dim)',
|
|
194
|
+
letterSpacing: 1, flexShrink: 0,
|
|
195
|
+
},
|
|
196
|
+
dirPath: {
|
|
197
|
+
background: 'none', border: 'none', color: 'var(--text-primary)',
|
|
198
|
+
fontSize: 11, fontFamily: 'var(--font)', cursor: 'pointer',
|
|
199
|
+
padding: '2px 6px', borderRadius: 3, textAlign: 'left',
|
|
200
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
201
|
+
flex: 1,
|
|
202
|
+
},
|
|
203
|
+
dirInput: {
|
|
204
|
+
flex: 1, background: 'var(--bg-surface)', border: '1px solid var(--accent)',
|
|
205
|
+
borderRadius: 3, padding: '2px 8px', color: 'var(--text-primary)',
|
|
206
|
+
fontSize: 11, outline: 'none', fontFamily: 'var(--font)',
|
|
207
|
+
},
|
|
208
|
+
dirOkBtn: {
|
|
209
|
+
background: 'var(--accent)', color: 'var(--bg-base)', border: 'none',
|
|
210
|
+
borderRadius: 3, padding: '2px 10px', fontSize: 10, fontWeight: 600,
|
|
211
|
+
cursor: 'pointer', fontFamily: 'var(--font)',
|
|
212
|
+
},
|
|
213
|
+
dirCancelBtn: {
|
|
214
|
+
background: 'none', border: 'none', color: 'var(--text-dim)',
|
|
215
|
+
fontSize: 14, cursor: 'pointer', padding: '0 4px',
|
|
216
|
+
},
|
|
133
217
|
contentRow: {
|
|
134
218
|
flex: 1, display: 'flex', overflow: 'hidden',
|
|
135
219
|
},
|
|
@@ -84,10 +84,12 @@ function CredentialModal({ integration, onClose }) {
|
|
|
84
84
|
const [googleClientId, setGoogleClientId] = useState('');
|
|
85
85
|
const [googleClientSecret, setGoogleClientSecret] = useState('');
|
|
86
86
|
const [showGoogleSetup, setShowGoogleSetup] = useState(false);
|
|
87
|
+
const [errorMsg, setErrorMsg] = useState('');
|
|
87
88
|
|
|
88
89
|
useEffect(() => {
|
|
89
|
-
if (integration?.authType === 'oauth-google') {
|
|
90
|
+
if (integration?.authType === 'oauth-google' || integration?.authType === 'google-autoauth' || integration?._googleSetupNeeded) {
|
|
90
91
|
setOauthStatus('checking');
|
|
92
|
+
setShowGoogleSetup(integration?._googleSetupNeeded || false);
|
|
91
93
|
fetch('/api/integrations/google-oauth/status')
|
|
92
94
|
.then((r) => r.json())
|
|
93
95
|
.then((data) => setOauthStatus(data.configured ? 'ready' : 'not-configured'))
|
|
@@ -98,12 +100,14 @@ function CredentialModal({ integration, onClose }) {
|
|
|
98
100
|
if (!integration) return null;
|
|
99
101
|
|
|
100
102
|
const isOAuth = integration.authType === 'oauth-google';
|
|
103
|
+
const isGoogleAutoAuth = integration.authType === 'google-autoauth' || integration._googleSetupNeeded;
|
|
101
104
|
const envKeys = (integration.envKeys || []).filter((ek) => !ek.hidden);
|
|
102
105
|
const setupSteps = integration.setupSteps || [];
|
|
103
106
|
|
|
104
107
|
async function handleSave(key) {
|
|
105
108
|
if (!values[key]) return;
|
|
106
109
|
setSaving(true);
|
|
110
|
+
setErrorMsg('');
|
|
107
111
|
try {
|
|
108
112
|
const res = await fetch(`/api/integrations/${integration.id}/credentials`, {
|
|
109
113
|
method: 'POST',
|
|
@@ -113,28 +117,61 @@ function CredentialModal({ integration, onClose }) {
|
|
|
113
117
|
if (res.ok) {
|
|
114
118
|
setSaved((prev) => ({ ...prev, [key]: true }));
|
|
115
119
|
setValues((prev) => ({ ...prev, [key]: '' }));
|
|
120
|
+
} else {
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
setErrorMsg(data.error || `Failed to save ${key}`);
|
|
116
123
|
}
|
|
117
|
-
} catch {
|
|
124
|
+
} catch {
|
|
125
|
+
setErrorMsg('Could not reach the server');
|
|
126
|
+
}
|
|
118
127
|
setSaving(false);
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
async function handleGoogleSetup() {
|
|
122
131
|
if (!googleClientId || !googleClientSecret) return;
|
|
123
132
|
setSaving(true);
|
|
133
|
+
setErrorMsg('');
|
|
124
134
|
try {
|
|
125
|
-
await fetch('/api/integrations/google-oauth/setup', {
|
|
135
|
+
const res = await fetch('/api/integrations/google-oauth/setup', {
|
|
126
136
|
method: 'POST',
|
|
127
137
|
headers: { 'Content-Type': 'application/json' },
|
|
128
138
|
body: JSON.stringify({ clientId: googleClientId, clientSecret: googleClientSecret }),
|
|
129
139
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
if (res.ok) {
|
|
141
|
+
setOauthStatus('ready');
|
|
142
|
+
setShowGoogleSetup(false);
|
|
143
|
+
} else {
|
|
144
|
+
const data = await res.json();
|
|
145
|
+
setErrorMsg(data.error || 'Failed to save credentials');
|
|
146
|
+
}
|
|
147
|
+
} catch {
|
|
148
|
+
setErrorMsg('Could not reach the server');
|
|
149
|
+
}
|
|
133
150
|
setSaving(false);
|
|
134
151
|
}
|
|
135
152
|
|
|
153
|
+
async function handleAutoAuthConnect() {
|
|
154
|
+
setOauthStatus('connecting');
|
|
155
|
+
setErrorMsg('');
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch(`/api/integrations/${integration.id}/authenticate`, { method: 'POST' });
|
|
158
|
+
const data = await res.json();
|
|
159
|
+
if (data.ok) {
|
|
160
|
+
// MCP server spawned — it will open a browser for OAuth consent
|
|
161
|
+
setTimeout(() => onClose(), 3000);
|
|
162
|
+
} else {
|
|
163
|
+
setErrorMsg(data.error || 'Authentication failed');
|
|
164
|
+
setOauthStatus('ready');
|
|
165
|
+
}
|
|
166
|
+
} catch {
|
|
167
|
+
setErrorMsg('Could not reach the server');
|
|
168
|
+
setOauthStatus('ready');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
136
172
|
async function handleOAuthConnect() {
|
|
137
173
|
setOauthStatus('connecting');
|
|
174
|
+
setErrorMsg('');
|
|
138
175
|
try {
|
|
139
176
|
const res = await fetch(`/api/integrations/${integration.id}/oauth/start`, { method: 'POST' });
|
|
140
177
|
const data = await res.json();
|
|
@@ -154,8 +191,12 @@ function CredentialModal({ integration, onClose }) {
|
|
|
154
191
|
}, 2000);
|
|
155
192
|
// Stop polling after 5 minutes
|
|
156
193
|
setTimeout(() => clearInterval(poll), 300000);
|
|
194
|
+
} else {
|
|
195
|
+
setErrorMsg(data.error || 'Failed to start OAuth flow');
|
|
196
|
+
setOauthStatus('ready');
|
|
157
197
|
}
|
|
158
198
|
} catch {
|
|
199
|
+
setErrorMsg('Could not reach the server');
|
|
159
200
|
setOauthStatus('ready');
|
|
160
201
|
}
|
|
161
202
|
}
|
|
@@ -218,30 +259,59 @@ function CredentialModal({ integration, onClose }) {
|
|
|
218
259
|
</a>
|
|
219
260
|
)}
|
|
220
261
|
|
|
221
|
-
{/*
|
|
222
|
-
{
|
|
262
|
+
{/* Error message */}
|
|
263
|
+
{errorMsg && (
|
|
264
|
+
<div style={{
|
|
265
|
+
padding: '10px 14px', marginBottom: 14, borderRadius: 6,
|
|
266
|
+
background: 'rgba(224, 108, 117, 0.08)', border: '1px solid var(--red)',
|
|
267
|
+
fontSize: 11, color: 'var(--red)', lineHeight: 1.5,
|
|
268
|
+
}}>
|
|
269
|
+
{errorMsg}
|
|
270
|
+
</div>
|
|
271
|
+
)}
|
|
272
|
+
|
|
273
|
+
{/* OAuth flow for Google integrations (both oauth-google and google-autoauth) */}
|
|
274
|
+
{(isOAuth || isGoogleAutoAuth) && (
|
|
223
275
|
<div style={{ marginBottom: 16 }}>
|
|
224
|
-
{/*
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
276
|
+
{/* Show Connect button only when OAuth is configured */}
|
|
277
|
+
{oauthStatus === 'ready' && (
|
|
278
|
+
<button
|
|
279
|
+
onClick={isGoogleAutoAuth ? handleAutoAuthConnect : handleOAuthConnect}
|
|
280
|
+
disabled={oauthStatus === 'connecting'}
|
|
281
|
+
style={{
|
|
282
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
|
283
|
+
width: '100%', padding: '12px 16px', marginBottom: 12,
|
|
284
|
+
background: '#4285f4',
|
|
285
|
+
color: '#fff', border: 'none', borderRadius: 6,
|
|
286
|
+
fontSize: 13, fontWeight: 600, cursor: 'pointer',
|
|
287
|
+
fontFamily: 'var(--font)',
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
Connect with Google
|
|
291
|
+
</button>
|
|
292
|
+
)}
|
|
293
|
+
{oauthStatus === 'checking' && (
|
|
294
|
+
<div style={{
|
|
295
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
296
|
+
padding: '12px 16px', marginBottom: 12,
|
|
297
|
+
color: 'var(--text-muted)', fontSize: 12,
|
|
298
|
+
}}>
|
|
299
|
+
Checking Google credentials...
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
{oauthStatus === 'connecting' && (
|
|
303
|
+
<div style={{
|
|
304
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
305
|
+
padding: '12px 16px', marginBottom: 12,
|
|
306
|
+
background: 'var(--bg-active)', borderRadius: 6,
|
|
307
|
+
color: 'var(--text-primary)', fontSize: 12, fontWeight: 600,
|
|
308
|
+
}}>
|
|
309
|
+
Waiting for authorization — check your browser...
|
|
310
|
+
</div>
|
|
311
|
+
)}
|
|
242
312
|
|
|
243
|
-
{/* First-time setup:
|
|
244
|
-
{
|
|
313
|
+
{/* First-time setup: shown when OAuth not configured */}
|
|
314
|
+
{oauthStatus === 'not-configured' && (
|
|
245
315
|
<div style={{
|
|
246
316
|
padding: 14, borderRadius: 8,
|
|
247
317
|
background: 'var(--bg-surface)', border: '1px solid var(--border)',
|
|
@@ -292,9 +362,12 @@ function CredentialModal({ integration, onClose }) {
|
|
|
292
362
|
<button
|
|
293
363
|
onClick={async () => {
|
|
294
364
|
await handleGoogleSetup();
|
|
295
|
-
// After saving, immediately trigger the
|
|
365
|
+
// After saving, immediately trigger the appropriate connect flow
|
|
296
366
|
if (googleClientId && googleClientSecret) {
|
|
297
|
-
setTimeout(() =>
|
|
367
|
+
setTimeout(() => {
|
|
368
|
+
if (isGoogleAutoAuth) handleAutoAuthConnect();
|
|
369
|
+
else handleOAuthConnect();
|
|
370
|
+
}, 500);
|
|
298
371
|
}
|
|
299
372
|
}}
|
|
300
373
|
disabled={saving || !googleClientId || !googleClientSecret}
|
|
@@ -369,7 +442,8 @@ function CredentialModal({ integration, onClose }) {
|
|
|
369
442
|
function IntegrationDetailModal({ integration, installing, onInstall, onUninstall, onConfigure, onAuthenticate, onClose }) {
|
|
370
443
|
if (!integration) return null;
|
|
371
444
|
|
|
372
|
-
const isAutoAuth = integration.authType === 'none'
|
|
445
|
+
const isAutoAuth = (integration.authType === 'none' || integration.authType === 'google-autoauth')
|
|
446
|
+
&& (integration.envKeys || []).length === 0;
|
|
373
447
|
const hasCredentials = (integration.envKeys || []).filter((ek) => !ek.hidden).length > 0
|
|
374
448
|
|| integration.authType === 'oauth-google';
|
|
375
449
|
|
|
@@ -683,26 +757,42 @@ export default function IntegrationsStore() {
|
|
|
683
757
|
async function handleInstall(id) {
|
|
684
758
|
setInstalling(id);
|
|
685
759
|
try {
|
|
686
|
-
await fetch(`/api/integrations/${id}/install`, { method: 'POST' });
|
|
760
|
+
const res = await fetch(`/api/integrations/${id}/install`, { method: 'POST' });
|
|
761
|
+
const data = await res.json();
|
|
762
|
+
if (!res.ok) {
|
|
763
|
+
flash(data.error || 'Install failed', 'error');
|
|
764
|
+
setInstalling(null);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
687
767
|
await fetchIntegrations();
|
|
688
768
|
// After install, refresh selected item
|
|
689
769
|
if (selectedItem?.id === id) {
|
|
690
770
|
const updated = integrations.find((s) => s.id === id);
|
|
691
771
|
if (updated) setSelectedItem({ ...updated, installed: true });
|
|
692
772
|
}
|
|
693
|
-
} catch {
|
|
773
|
+
} catch (err) {
|
|
774
|
+
flash('Install failed — check daemon logs', 'error');
|
|
775
|
+
}
|
|
694
776
|
setInstalling(null);
|
|
695
777
|
}
|
|
696
778
|
|
|
697
779
|
async function handleUninstall(id) {
|
|
698
780
|
setInstalling(id);
|
|
699
781
|
try {
|
|
700
|
-
await fetch(`/api/integrations/${id}`, { method: 'DELETE' });
|
|
782
|
+
const res = await fetch(`/api/integrations/${id}`, { method: 'DELETE' });
|
|
783
|
+
if (!res.ok) {
|
|
784
|
+
const data = await res.json();
|
|
785
|
+
flash(data.error || 'Uninstall failed', 'error');
|
|
786
|
+
setInstalling(null);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
701
789
|
await fetchIntegrations();
|
|
702
790
|
if (selectedItem?.id === id) {
|
|
703
791
|
setSelectedItem((prev) => prev ? { ...prev, installed: false, configured: false } : null);
|
|
704
792
|
}
|
|
705
|
-
} catch {
|
|
793
|
+
} catch {
|
|
794
|
+
flash('Uninstall failed', 'error');
|
|
795
|
+
}
|
|
706
796
|
setInstalling(null);
|
|
707
797
|
}
|
|
708
798
|
|
|
@@ -716,23 +806,44 @@ export default function IntegrationsStore() {
|
|
|
716
806
|
}
|
|
717
807
|
|
|
718
808
|
async function handleAuthenticate(item) {
|
|
809
|
+
// For google-autoauth, check if Google OAuth is configured first
|
|
810
|
+
if (item.authType === 'google-autoauth') {
|
|
811
|
+
try {
|
|
812
|
+
const statusRes = await fetch('/api/integrations/google-oauth/status');
|
|
813
|
+
const statusData = await statusRes.json();
|
|
814
|
+
if (!statusData.configured) {
|
|
815
|
+
// Need Google OAuth setup first — open the credential modal with setup form
|
|
816
|
+
setSelectedItem(null);
|
|
817
|
+
setConfiguring({ ...item, _googleSetupNeeded: true });
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
// Can't check status — open setup form as fallback
|
|
822
|
+
setSelectedItem(null);
|
|
823
|
+
setConfiguring({ ...item, _googleSetupNeeded: true });
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// OAuth is configured — trigger the authentication flow
|
|
719
829
|
setSelectedItem(null);
|
|
830
|
+
setConfiguring({ ...item }); // Show credential modal in connecting state
|
|
720
831
|
try {
|
|
721
832
|
const res = await fetch(`/api/integrations/${item.id}/authenticate`, { method: 'POST' });
|
|
722
833
|
const data = await res.json();
|
|
723
834
|
if (data.ok) {
|
|
724
835
|
flash('Sign-in window opened — check your browser');
|
|
725
836
|
} else {
|
|
726
|
-
flash(data.error || 'Authentication failed');
|
|
837
|
+
flash(data.error || 'Authentication failed', 'error');
|
|
727
838
|
}
|
|
728
839
|
} catch {
|
|
729
|
-
flash('Authentication failed');
|
|
840
|
+
flash('Authentication failed — check daemon logs', 'error');
|
|
730
841
|
}
|
|
731
842
|
}
|
|
732
843
|
|
|
733
|
-
function flash(msg) {
|
|
734
|
-
setStatusMsg(msg);
|
|
735
|
-
setTimeout(() => setStatusMsg(''),
|
|
844
|
+
function flash(msg, type = 'info') {
|
|
845
|
+
setStatusMsg({ text: msg, type });
|
|
846
|
+
setTimeout(() => setStatusMsg(''), 6000);
|
|
736
847
|
}
|
|
737
848
|
|
|
738
849
|
function handleConfigureClose() {
|
|
@@ -783,8 +894,12 @@ export default function IntegrationsStore() {
|
|
|
783
894
|
|
|
784
895
|
{/* Status message */}
|
|
785
896
|
{statusMsg && (
|
|
786
|
-
<div style={{
|
|
787
|
-
|
|
897
|
+
<div style={{
|
|
898
|
+
padding: '8px 20px', fontSize: 12, fontWeight: 500, flexShrink: 0,
|
|
899
|
+
color: statusMsg.type === 'error' ? 'var(--red)' : 'var(--accent)',
|
|
900
|
+
background: statusMsg.type === 'error' ? 'rgba(224, 108, 117, 0.06)' : 'transparent',
|
|
901
|
+
}}>
|
|
902
|
+
{statusMsg.text || statusMsg}
|
|
788
903
|
</div>
|
|
789
904
|
)}
|
|
790
905
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
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)",
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
"claude-code",
|
|
23
23
|
"codex",
|
|
24
24
|
"gemini",
|
|
25
|
-
|
|
26
25
|
"ollama",
|
|
27
26
|
"coding-assistant",
|
|
28
27
|
"developer-tools",
|
|
@@ -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,
|
|
@@ -336,26 +338,6 @@
|
|
|
336
338
|
"ratingCount": 0,
|
|
337
339
|
"verified": "community"
|
|
338
340
|
},
|
|
339
|
-
{
|
|
340
|
-
"id": "filesystem",
|
|
341
|
-
"name": "Filesystem",
|
|
342
|
-
"description": "Read, write, search, and manage files on the local filesystem",
|
|
343
|
-
"category": "developer",
|
|
344
|
-
"icon": "folder",
|
|
345
|
-
"tags": ["files", "filesystem", "local", "storage"],
|
|
346
|
-
"roles": ["backend", "fullstack", "devops"],
|
|
347
|
-
"npmPackage": "@modelcontextprotocol/server-filesystem",
|
|
348
|
-
"transport": "stdio",
|
|
349
|
-
"command": "npx",
|
|
350
|
-
"args": ["-y", "@modelcontextprotocol/server-filesystem"],
|
|
351
|
-
"authType": "none",
|
|
352
|
-
"envKeys": [],
|
|
353
|
-
"featured": false,
|
|
354
|
-
"downloads": 0,
|
|
355
|
-
"rating": 0,
|
|
356
|
-
"ratingCount": 0,
|
|
357
|
-
"verified": "mcp-official"
|
|
358
|
-
},
|
|
359
341
|
{
|
|
360
342
|
"id": "google-maps",
|
|
361
343
|
"name": "Google Maps",
|
|
@@ -383,25 +365,5 @@
|
|
|
383
365
|
"rating": 0,
|
|
384
366
|
"ratingCount": 0,
|
|
385
367
|
"verified": "mcp-official"
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
"id": "sqlite",
|
|
389
|
-
"name": "SQLite",
|
|
390
|
-
"description": "Query and manage SQLite databases, inspect schemas, run SQL",
|
|
391
|
-
"category": "database",
|
|
392
|
-
"icon": "database",
|
|
393
|
-
"tags": ["sql", "database", "local", "lightweight"],
|
|
394
|
-
"roles": ["analyst", "backend"],
|
|
395
|
-
"npmPackage": "mcp-sqlite",
|
|
396
|
-
"transport": "stdio",
|
|
397
|
-
"command": "npx",
|
|
398
|
-
"args": ["-y", "mcp-sqlite"],
|
|
399
|
-
"authType": "none",
|
|
400
|
-
"envKeys": [],
|
|
401
|
-
"featured": false,
|
|
402
|
-
"downloads": 0,
|
|
403
|
-
"rating": 0,
|
|
404
|
-
"ratingCount": 0,
|
|
405
|
-
"verified": "mcp-official"
|
|
406
368
|
}
|
|
407
369
|
]
|