groove-dev 0.17.8 → 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.
Files changed (110) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/daemon/google-oauth.json +5 -0
  3. package/node_modules/@groove-dev/daemon/integrations-registry.json +0 -40
  4. package/node_modules/@groove-dev/daemon/src/api.js +103 -12
  5. package/node_modules/@groove-dev/daemon/src/integrations.js +59 -20
  6. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -0
  7. package/node_modules/@groove-dev/gui/.groove/codebase-index.json +64 -0
  8. package/node_modules/@groove-dev/gui/.groove/config.json +10 -0
  9. package/node_modules/@groove-dev/gui/.groove/coordination.md +5 -0
  10. package/node_modules/@groove-dev/gui/.groove/daemon.host +1 -0
  11. package/node_modules/@groove-dev/gui/.groove/daemon.pid +1 -0
  12. package/node_modules/@groove-dev/gui/.groove/daemon.port +1 -0
  13. package/node_modules/@groove-dev/gui/.groove/federation/identity.key +3 -0
  14. package/node_modules/@groove-dev/gui/.groove/federation/identity.pub +3 -0
  15. package/node_modules/@groove-dev/gui/.groove/integrations/package.json +6 -0
  16. package/node_modules/@groove-dev/gui/.groove/state.json +3 -0
  17. package/{packages/gui/dist/assets/index-D5dtDQf0.js → node_modules/@groove-dev/gui/dist/assets/index-DXkccbmd.js} +76 -50
  18. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  19. package/node_modules/@groove-dev/gui/src/App.jsx +27 -4
  20. package/node_modules/@groove-dev/gui/src/components/AgentChat.jsx +1 -1
  21. package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +839 -586
  22. package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +85 -1
  23. package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +121 -44
  24. package/package.json +1 -2
  25. package/packages/daemon/integrations-registry.json +0 -40
  26. package/packages/daemon/src/api.js +103 -12
  27. package/packages/daemon/src/integrations.js +59 -20
  28. package/packages/daemon/src/providers/claude-code.js +4 -0
  29. package/{node_modules/@groove-dev/gui/dist/assets/index-D5dtDQf0.js → packages/gui/dist/assets/index-DXkccbmd.js} +76 -50
  30. package/packages/gui/dist/index.html +1 -1
  31. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +68 -0
  32. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +7 -0
  33. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +1420 -0
  34. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +7 -0
  35. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +17 -0
  36. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +7 -0
  37. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +22 -0
  38. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +7 -0
  39. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +34 -0
  40. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +7 -0
  41. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +101 -0
  42. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +7 -0
  43. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +2534 -0
  44. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +7 -0
  45. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +789 -0
  46. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +7 -0
  47. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +115 -0
  48. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +7 -0
  49. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +1136 -0
  50. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +7 -0
  51. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +63 -0
  52. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +7 -0
  53. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +179 -0
  54. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +7 -0
  55. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +104 -0
  56. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +7 -0
  57. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +46 -0
  58. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +7 -0
  59. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +121 -0
  60. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +7 -0
  61. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +9237 -0
  62. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +7 -0
  63. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +9934 -0
  64. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +7 -0
  65. package/packages/gui/node_modules/.vite/deps/_metadata.json +184 -0
  66. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +5169 -0
  67. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +7 -0
  68. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +2000 -0
  69. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +7 -0
  70. package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js +1115 -0
  71. package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js.map +7 -0
  72. package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js +701 -0
  73. package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js.map +7 -0
  74. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +1776 -0
  75. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +7 -0
  76. package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js +280 -0
  77. package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js.map +7 -0
  78. package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  79. package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  80. package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js +1004 -0
  81. package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js.map +7 -0
  82. package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js +292 -0
  83. package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js.map +7 -0
  84. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +1062 -0
  85. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +7 -0
  86. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +10985 -0
  87. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +7 -0
  88. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +3459 -0
  89. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +7 -0
  90. package/packages/gui/node_modules/.vite/deps/package.json +3 -0
  91. package/packages/gui/node_modules/.vite/deps/react-dom.js +6 -0
  92. package/packages/gui/node_modules/.vite/deps/react-dom.js.map +7 -0
  93. package/packages/gui/node_modules/.vite/deps/react-dom_client.js +20217 -0
  94. package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +7 -0
  95. package/packages/gui/node_modules/.vite/deps/react.js +5 -0
  96. package/packages/gui/node_modules/.vite/deps/react.js.map +7 -0
  97. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
  98. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  99. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
  100. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  101. package/packages/gui/node_modules/.vite/deps/zustand.js +56 -0
  102. package/packages/gui/node_modules/.vite/deps/zustand.js.map +7 -0
  103. package/packages/gui/src/App.jsx +27 -4
  104. package/packages/gui/src/components/AgentChat.jsx +1 -1
  105. package/packages/gui/src/components/SpawnPanel.jsx +839 -586
  106. package/packages/gui/src/views/FileEditor.jsx +85 -1
  107. package/packages/gui/src/views/IntegrationsStore.jsx +121 -44
  108. package/docs/FILE-EDITOR-PLAN.md +0 -253
  109. package/docs/GUI_DESIGN_SPEC.md +0 -402
  110. package/docs/SKILLS-API-SPEC.md +0 -277
@@ -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}>&times;</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,6 +84,7 @@ 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
90
  if (integration?.authType === 'oauth-google' || integration?.authType === 'google-autoauth' || integration?._googleSetupNeeded) {
@@ -106,6 +107,7 @@ function CredentialModal({ integration, onClose }) {
106
107
  async function handleSave(key) {
107
108
  if (!values[key]) return;
108
109
  setSaving(true);
110
+ setErrorMsg('');
109
111
  try {
110
112
  const res = await fetch(`/api/integrations/${integration.id}/credentials`, {
111
113
  method: 'POST',
@@ -115,44 +117,61 @@ function CredentialModal({ integration, onClose }) {
115
117
  if (res.ok) {
116
118
  setSaved((prev) => ({ ...prev, [key]: true }));
117
119
  setValues((prev) => ({ ...prev, [key]: '' }));
120
+ } else {
121
+ const data = await res.json();
122
+ setErrorMsg(data.error || `Failed to save ${key}`);
118
123
  }
119
- } catch { /* ignore */ }
124
+ } catch {
125
+ setErrorMsg('Could not reach the server');
126
+ }
120
127
  setSaving(false);
121
128
  }
122
129
 
123
130
  async function handleGoogleSetup() {
124
131
  if (!googleClientId || !googleClientSecret) return;
125
132
  setSaving(true);
133
+ setErrorMsg('');
126
134
  try {
127
- await fetch('/api/integrations/google-oauth/setup', {
135
+ const res = await fetch('/api/integrations/google-oauth/setup', {
128
136
  method: 'POST',
129
137
  headers: { 'Content-Type': 'application/json' },
130
138
  body: JSON.stringify({ clientId: googleClientId, clientSecret: googleClientSecret }),
131
139
  });
132
- setOauthStatus('ready');
133
- setShowGoogleSetup(false);
134
- } catch { /* ignore */ }
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
+ }
135
150
  setSaving(false);
136
151
  }
137
152
 
138
153
  async function handleAutoAuthConnect() {
139
154
  setOauthStatus('connecting');
155
+ setErrorMsg('');
140
156
  try {
141
157
  const res = await fetch(`/api/integrations/${integration.id}/authenticate`, { method: 'POST' });
142
158
  const data = await res.json();
143
- if (!data.ok) {
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');
144
164
  setOauthStatus('ready');
145
165
  }
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
166
  } catch {
167
+ setErrorMsg('Could not reach the server');
150
168
  setOauthStatus('ready');
151
169
  }
152
170
  }
153
171
 
154
172
  async function handleOAuthConnect() {
155
173
  setOauthStatus('connecting');
174
+ setErrorMsg('');
156
175
  try {
157
176
  const res = await fetch(`/api/integrations/${integration.id}/oauth/start`, { method: 'POST' });
158
177
  const data = await res.json();
@@ -172,8 +191,12 @@ function CredentialModal({ integration, onClose }) {
172
191
  }, 2000);
173
192
  // Stop polling after 5 minutes
174
193
  setTimeout(() => clearInterval(poll), 300000);
194
+ } else {
195
+ setErrorMsg(data.error || 'Failed to start OAuth flow');
196
+ setOauthStatus('ready');
175
197
  }
176
198
  } catch {
199
+ setErrorMsg('Could not reach the server');
177
200
  setOauthStatus('ready');
178
201
  }
179
202
  }
@@ -236,32 +259,59 @@ function CredentialModal({ integration, onClose }) {
236
259
  </a>
237
260
  )}
238
261
 
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
+
239
273
  {/* OAuth flow for Google integrations (both oauth-google and google-autoauth) */}
240
274
  {(isOAuth || isGoogleAutoAuth) && (
241
275
  <div style={{ marginBottom: 16 }}>
242
- {/* Always show the primary Connect button */}
243
- <button
244
- onClick={oauthStatus === 'ready'
245
- ? (isGoogleAutoAuth ? handleAutoAuthConnect : handleOAuthConnect)
246
- : () => setShowGoogleSetup(true)}
247
- disabled={oauthStatus === 'checking' || oauthStatus === 'connecting'}
248
- style={{
249
- display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
250
- width: '100%', padding: '12px 16px', marginBottom: 12,
251
- background: oauthStatus === 'connecting' ? 'var(--bg-active)' : '#4285f4',
252
- color: '#fff', border: 'none', borderRadius: 6,
253
- fontSize: 13, fontWeight: 600, cursor: 'pointer',
254
- fontFamily: 'var(--font)',
255
- opacity: oauthStatus === 'checking' ? 0.5 : 1,
256
- }}
257
- >
258
- {oauthStatus === 'checking' ? 'Checking...'
259
- : oauthStatus === 'connecting' ? 'Waiting for authorization...'
260
- : `Connect with Google`}
261
- </button>
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
+ )}
262
312
 
263
- {/* First-time setup: show inline when Connect is clicked and OAuth not configured */}
264
- {showGoogleSetup && oauthStatus === 'not-configured' && (
313
+ {/* First-time setup: shown when OAuth not configured */}
314
+ {oauthStatus === 'not-configured' && (
265
315
  <div style={{
266
316
  padding: 14, borderRadius: 8,
267
317
  background: 'var(--bg-surface)', border: '1px solid var(--border)',
@@ -707,26 +757,42 @@ export default function IntegrationsStore() {
707
757
  async function handleInstall(id) {
708
758
  setInstalling(id);
709
759
  try {
710
- 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
+ }
711
767
  await fetchIntegrations();
712
768
  // After install, refresh selected item
713
769
  if (selectedItem?.id === id) {
714
770
  const updated = integrations.find((s) => s.id === id);
715
771
  if (updated) setSelectedItem({ ...updated, installed: true });
716
772
  }
717
- } catch { /* ignore */ }
773
+ } catch (err) {
774
+ flash('Install failed — check daemon logs', 'error');
775
+ }
718
776
  setInstalling(null);
719
777
  }
720
778
 
721
779
  async function handleUninstall(id) {
722
780
  setInstalling(id);
723
781
  try {
724
- 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
+ }
725
789
  await fetchIntegrations();
726
790
  if (selectedItem?.id === id) {
727
791
  setSelectedItem((prev) => prev ? { ...prev, installed: false, configured: false } : null);
728
792
  }
729
- } catch { /* ignore */ }
793
+ } catch {
794
+ flash('Uninstall failed', 'error');
795
+ }
730
796
  setInstalling(null);
731
797
  }
732
798
 
@@ -746,31 +812,38 @@ export default function IntegrationsStore() {
746
812
  const statusRes = await fetch('/api/integrations/google-oauth/status');
747
813
  const statusData = await statusRes.json();
748
814
  if (!statusData.configured) {
749
- // Need Google OAuth setup first — open the credential modal
815
+ // Need Google OAuth setup first — open the credential modal with setup form
750
816
  setSelectedItem(null);
751
817
  setConfiguring({ ...item, _googleSetupNeeded: true });
752
818
  return;
753
819
  }
754
- } catch { /* proceed anyway */ }
820
+ } catch {
821
+ // Can't check status — open setup form as fallback
822
+ setSelectedItem(null);
823
+ setConfiguring({ ...item, _googleSetupNeeded: true });
824
+ return;
825
+ }
755
826
  }
756
827
 
828
+ // OAuth is configured — trigger the authentication flow
757
829
  setSelectedItem(null);
830
+ setConfiguring({ ...item }); // Show credential modal in connecting state
758
831
  try {
759
832
  const res = await fetch(`/api/integrations/${item.id}/authenticate`, { method: 'POST' });
760
833
  const data = await res.json();
761
834
  if (data.ok) {
762
835
  flash('Sign-in window opened — check your browser');
763
836
  } else {
764
- flash(data.error || 'Authentication failed');
837
+ flash(data.error || 'Authentication failed', 'error');
765
838
  }
766
839
  } catch {
767
- flash('Authentication failed');
840
+ flash('Authentication failed — check daemon logs', 'error');
768
841
  }
769
842
  }
770
843
 
771
- function flash(msg) {
772
- setStatusMsg(msg);
773
- setTimeout(() => setStatusMsg(''), 4000);
844
+ function flash(msg, type = 'info') {
845
+ setStatusMsg({ text: msg, type });
846
+ setTimeout(() => setStatusMsg(''), 6000);
774
847
  }
775
848
 
776
849
  function handleConfigureClose() {
@@ -821,8 +894,12 @@ export default function IntegrationsStore() {
821
894
 
822
895
  {/* Status message */}
823
896
  {statusMsg && (
824
- <div style={{ padding: '4px 20px', fontSize: 10, color: 'var(--accent)', flexShrink: 0 }}>
825
- {statusMsg}
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}
826
903
  </div>
827
904
  )}
828
905
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.17.8",
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",
@@ -338,26 +338,6 @@
338
338
  "ratingCount": 0,
339
339
  "verified": "community"
340
340
  },
341
- {
342
- "id": "filesystem",
343
- "name": "Filesystem",
344
- "description": "Read, write, search, and manage files on the local filesystem",
345
- "category": "developer",
346
- "icon": "folder",
347
- "tags": ["files", "filesystem", "local", "storage"],
348
- "roles": ["backend", "fullstack", "devops"],
349
- "npmPackage": "@modelcontextprotocol/server-filesystem",
350
- "transport": "stdio",
351
- "command": "npx",
352
- "args": ["-y", "@modelcontextprotocol/server-filesystem"],
353
- "authType": "none",
354
- "envKeys": [],
355
- "featured": false,
356
- "downloads": 0,
357
- "rating": 0,
358
- "ratingCount": 0,
359
- "verified": "mcp-official"
360
- },
361
341
  {
362
342
  "id": "google-maps",
363
343
  "name": "Google Maps",
@@ -385,25 +365,5 @@
385
365
  "rating": 0,
386
366
  "ratingCount": 0,
387
367
  "verified": "mcp-official"
388
- },
389
- {
390
- "id": "sqlite",
391
- "name": "SQLite",
392
- "description": "Query and manage SQLite databases, inspect schemas, run SQL",
393
- "category": "database",
394
- "icon": "database",
395
- "tags": ["sql", "database", "local", "lightweight"],
396
- "roles": ["analyst", "backend"],
397
- "npmPackage": "mcp-sqlite",
398
- "transport": "stdio",
399
- "command": "npx",
400
- "args": ["-y", "mcp-sqlite"],
401
- "authType": "none",
402
- "envKeys": [],
403
- "featured": false,
404
- "downloads": 0,
405
- "rating": 0,
406
- "ratingCount": 0,
407
- "verified": "mcp-official"
408
368
  }
409
369
  ]