groove-dev 0.26.38 → 0.27.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 (171) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/CLAUDE.md +24 -19
  3. package/node_modules/@groove-dev/cli/bin/groove.js +2 -0
  4. package/node_modules/@groove-dev/cli/package.json +1 -1
  5. package/node_modules/@groove-dev/cli/src/commands/nuke.js +16 -4
  6. package/node_modules/@groove-dev/cli/src/commands/stop.js +17 -2
  7. package/node_modules/@groove-dev/daemon/integrations-registry.json +681 -75
  8. package/node_modules/@groove-dev/daemon/package.json +1 -1
  9. package/node_modules/@groove-dev/daemon/src/adaptive.js +23 -25
  10. package/node_modules/@groove-dev/daemon/src/api.js +346 -22
  11. package/node_modules/@groove-dev/daemon/src/classifier.js +53 -6
  12. package/node_modules/@groove-dev/daemon/src/firstrun.js +14 -1
  13. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +2 -2
  14. package/node_modules/@groove-dev/daemon/src/index.js +28 -4
  15. package/node_modules/@groove-dev/daemon/src/integrations.js +215 -14
  16. package/node_modules/@groove-dev/daemon/src/introducer.js +84 -11
  17. package/node_modules/@groove-dev/daemon/src/journalist.js +43 -1
  18. package/node_modules/@groove-dev/daemon/src/lockmanager.js +60 -0
  19. package/node_modules/@groove-dev/daemon/src/mcp-manager.js +270 -0
  20. package/node_modules/@groove-dev/daemon/src/memory.js +370 -0
  21. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  22. package/node_modules/@groove-dev/daemon/src/process.js +141 -9
  23. package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
  24. package/node_modules/@groove-dev/daemon/src/rotator.js +334 -31
  25. package/node_modules/@groove-dev/daemon/src/router.js +43 -0
  26. package/node_modules/@groove-dev/daemon/src/tokentracker.js +70 -18
  27. package/node_modules/@groove-dev/daemon/src/validate.js +5 -13
  28. package/node_modules/@groove-dev/daemon/templates/groove-slides.cjs +306 -0
  29. package/node_modules/@groove-dev/daemon/test/classifier.test.js +3 -5
  30. package/node_modules/@groove-dev/daemon/test/lockmanager.test.js +64 -0
  31. package/node_modules/@groove-dev/daemon/test/memory.test.js +252 -0
  32. package/node_modules/@groove-dev/daemon/test/rotator.test.js +108 -0
  33. package/node_modules/@groove-dev/daemon/test/router.test.js +64 -0
  34. package/node_modules/@groove-dev/daemon/test/slides-engine.test.js +230 -0
  35. package/node_modules/@groove-dev/daemon/test/tokentracker.test.js +78 -0
  36. package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +1 -0
  37. package/node_modules/@groove-dev/gui/dist/assets/index-eCrVowF0.js +652 -0
  38. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  39. package/node_modules/@groove-dev/gui/package.json +1 -4
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +26 -17
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +22 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +53 -21
  43. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +132 -90
  44. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +212 -1
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +6 -2
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +495 -174
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +12 -2
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
  49. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +3 -3
  50. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +24 -19
  51. package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +2 -2
  52. package/node_modules/@groove-dev/gui/src/components/marketplace/integration-wizard.jsx +391 -61
  53. package/node_modules/@groove-dev/gui/src/components/marketplace/marketplace-card.jsx +29 -7
  54. package/node_modules/@groove-dev/gui/src/lib/format.js +0 -6
  55. package/node_modules/@groove-dev/gui/src/lib/hooks/use-dashboard.js +23 -5
  56. package/node_modules/@groove-dev/gui/src/stores/groove.js +59 -9
  57. package/node_modules/@groove-dev/gui/src/views/agents.jsx +84 -10
  58. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +24 -21
  59. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +153 -85
  60. package/package.json +2 -8
  61. package/packages/cli/bin/groove.js +2 -0
  62. package/packages/cli/package.json +1 -1
  63. package/packages/cli/src/commands/nuke.js +16 -4
  64. package/packages/cli/src/commands/stop.js +17 -2
  65. package/packages/daemon/integrations-registry.json +681 -75
  66. package/packages/daemon/package.json +1 -1
  67. package/packages/daemon/src/adaptive.js +23 -25
  68. package/packages/daemon/src/api.js +346 -22
  69. package/packages/daemon/src/classifier.js +53 -6
  70. package/packages/daemon/src/firstrun.js +14 -1
  71. package/packages/daemon/src/gateways/manager.js +2 -2
  72. package/packages/daemon/src/index.js +28 -4
  73. package/packages/daemon/src/integrations.js +215 -14
  74. package/packages/daemon/src/introducer.js +84 -11
  75. package/packages/daemon/src/journalist.js +43 -1
  76. package/packages/daemon/src/lockmanager.js +60 -0
  77. package/packages/daemon/src/mcp-manager.js +270 -0
  78. package/packages/daemon/src/memory.js +370 -0
  79. package/packages/daemon/src/pm.js +1 -1
  80. package/packages/daemon/src/process.js +141 -9
  81. package/packages/daemon/src/registry.js +1 -1
  82. package/packages/daemon/src/rotator.js +334 -31
  83. package/packages/daemon/src/router.js +43 -0
  84. package/packages/daemon/src/tokentracker.js +70 -18
  85. package/packages/daemon/src/validate.js +5 -13
  86. package/packages/daemon/templates/groove-slides.cjs +306 -0
  87. package/packages/gui/dist/assets/index-DjORRpF0.css +1 -0
  88. package/packages/gui/dist/assets/index-eCrVowF0.js +652 -0
  89. package/packages/gui/dist/index.html +2 -2
  90. package/packages/gui/package.json +1 -4
  91. package/packages/gui/src/components/agents/agent-chat.jsx +26 -17
  92. package/packages/gui/src/components/agents/agent-config.jsx +22 -1
  93. package/packages/gui/src/components/agents/agent-feed.jsx +53 -21
  94. package/packages/gui/src/components/agents/agent-node.jsx +132 -90
  95. package/packages/gui/src/components/agents/spawn-wizard.jsx +212 -1
  96. package/packages/gui/src/components/dashboard/cache-ring.jsx +6 -2
  97. package/packages/gui/src/components/dashboard/intel-panel.jsx +495 -174
  98. package/packages/gui/src/components/dashboard/kpi-card.jsx +12 -2
  99. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +55 -0
  100. package/packages/gui/src/components/layout/activity-bar.jsx +3 -3
  101. package/packages/gui/src/components/layout/app-shell.jsx +24 -19
  102. package/packages/gui/src/components/layout/command-palette.jsx +2 -2
  103. package/packages/gui/src/components/marketplace/integration-wizard.jsx +391 -61
  104. package/packages/gui/src/components/marketplace/marketplace-card.jsx +29 -7
  105. package/packages/gui/src/lib/format.js +0 -6
  106. package/packages/gui/src/lib/hooks/use-dashboard.js +23 -5
  107. package/packages/gui/src/stores/groove.js +59 -9
  108. package/packages/gui/src/views/agents.jsx +84 -10
  109. package/packages/gui/src/views/dashboard.jsx +24 -21
  110. package/packages/gui/src/views/marketplace.jsx +153 -85
  111. package/node_modules/@groove-dev/gui/dist/assets/index-CEFKgLGB.css +0 -1
  112. package/node_modules/@groove-dev/gui/dist/assets/index-CaKBNWcK.js +0 -638
  113. package/node_modules/@groove-dev/gui/dist/groove-logo-short.png +0 -0
  114. package/node_modules/@groove-dev/gui/dist/groove-logo.png +0 -0
  115. package/node_modules/@groove-dev/gui/public/groove-logo-short.png +0 -0
  116. package/node_modules/@groove-dev/gui/public/groove-logo.png +0 -0
  117. package/node_modules/@groove-dev/gui/src/components/ui/dropdown-menu.jsx +0 -60
  118. package/node_modules/@groove-dev/gui/src/lib/hooks/use-media-query.js +0 -18
  119. package/node_modules/@radix-ui/react-dropdown-menu/LICENSE +0 -21
  120. package/node_modules/@radix-ui/react-dropdown-menu/README.md +0 -3
  121. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.mts +0 -97
  122. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.d.ts +0 -97
  123. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js +0 -337
  124. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.js.map +0 -7
  125. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs +0 -305
  126. package/node_modules/@radix-ui/react-dropdown-menu/dist/index.mjs.map +0 -7
  127. package/node_modules/@radix-ui/react-dropdown-menu/package.json +0 -75
  128. package/node_modules/@radix-ui/react-popover/LICENSE +0 -21
  129. package/node_modules/@radix-ui/react-popover/README.md +0 -3
  130. package/node_modules/@radix-ui/react-popover/dist/index.d.mts +0 -85
  131. package/node_modules/@radix-ui/react-popover/dist/index.d.ts +0 -85
  132. package/node_modules/@radix-ui/react-popover/dist/index.js +0 -352
  133. package/node_modules/@radix-ui/react-popover/dist/index.js.map +0 -7
  134. package/node_modules/@radix-ui/react-popover/dist/index.mjs +0 -320
  135. package/node_modules/@radix-ui/react-popover/dist/index.mjs.map +0 -7
  136. package/node_modules/@radix-ui/react-popover/package.json +0 -82
  137. package/node_modules/@radix-ui/react-separator/LICENSE +0 -21
  138. package/node_modules/@radix-ui/react-separator/README.md +0 -3
  139. package/node_modules/@radix-ui/react-separator/dist/index.d.mts +0 -21
  140. package/node_modules/@radix-ui/react-separator/dist/index.d.ts +0 -21
  141. package/node_modules/@radix-ui/react-separator/dist/index.js +0 -65
  142. package/node_modules/@radix-ui/react-separator/dist/index.js.map +0 -7
  143. package/node_modules/@radix-ui/react-separator/dist/index.mjs +0 -32
  144. package/node_modules/@radix-ui/react-separator/dist/index.mjs.map +0 -7
  145. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/LICENSE +0 -21
  146. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/README.md +0 -3
  147. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.mts +0 -52
  148. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.d.ts +0 -52
  149. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js +0 -80
  150. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.js.map +0 -7
  151. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs +0 -47
  152. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/dist/index.mjs.map +0 -7
  153. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive/package.json +0 -69
  154. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/LICENSE +0 -21
  155. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/README.md +0 -3
  156. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.mts +0 -22
  157. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.d.ts +0 -22
  158. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js +0 -152
  159. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.js.map +0 -7
  160. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs +0 -119
  161. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/dist/index.mjs.map +0 -7
  162. package/node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot/package.json +0 -64
  163. package/node_modules/@radix-ui/react-separator/package.json +0 -69
  164. package/packages/gui/dist/assets/index-CEFKgLGB.css +0 -1
  165. package/packages/gui/dist/assets/index-CaKBNWcK.js +0 -638
  166. package/packages/gui/dist/groove-logo-short.png +0 -0
  167. package/packages/gui/dist/groove-logo.png +0 -0
  168. package/packages/gui/public/groove-logo-short.png +0 -0
  169. package/packages/gui/public/groove-logo.png +0 -0
  170. package/packages/gui/src/components/ui/dropdown-menu.jsx +0 -60
  171. package/packages/gui/src/lib/hooks/use-media-query.js +0 -18
@@ -17,7 +17,7 @@ import { api } from '../lib/api';
17
17
  import { useToast } from '../lib/hooks/use-toast';
18
18
  import { fmtNum, timeAgo } from '../lib/format';
19
19
  import { useGrooveStore } from '../stores/groove';
20
- import { IntegrationWizard } from '../components/marketplace/integration-wizard';
20
+ import { IntegrationWizard, GoogleWorkspaceWizard } from '../components/marketplace/integration-wizard';
21
21
  import {
22
22
  ChevronLeft, ChevronDown, Sparkles, Plug, LogIn, LogOut,
23
23
  User, Upload, Package, Download, ShoppingBag, RefreshCw, Trash2,
@@ -275,12 +275,15 @@ function SkillsBrowse() {
275
275
  }
276
276
 
277
277
  // ── Integrations Browse ──────────────────────────────────
278
+ const GOOGLE_IDS = new Set(['gmail', 'google-calendar', 'google-drive', 'google-docs', 'google-sheets', 'google-slides']);
279
+
278
280
  function IntegrationsBrowse() {
279
281
  const [items, setItems] = useState([]);
280
282
  const [loading, setLoading] = useState(true);
281
283
  const [search, setSearch] = useState('');
282
284
  const [selectedIntegration, setSelectedIntegration] = useState(null);
283
285
  const [showWizard, setShowWizard] = useState(false);
286
+ const [showGoogleWizard, setShowGoogleWizard] = useState(false);
284
287
 
285
288
  const fetchItems = () => {
286
289
  setLoading(true);
@@ -292,6 +295,11 @@ function IntegrationsBrowse() {
292
295
 
293
296
  useEffect(() => { fetchItems(); }, [search]);
294
297
 
298
+ // Split items: Google services get grouped, rest stay as individual cards
299
+ const googleItems = items.filter((i) => GOOGLE_IDS.has(i.id));
300
+ const otherItems = items.filter((i) => !GOOGLE_IDS.has(i.id));
301
+ const googleInstalledCount = googleItems.filter((i) => i.installed).length;
302
+
295
303
  function handleCardClick(item) {
296
304
  setSelectedIntegration(item);
297
305
  setShowWizard(true);
@@ -300,10 +308,13 @@ function IntegrationsBrowse() {
300
308
  function handleWizardClose() {
301
309
  setShowWizard(false);
302
310
  setSelectedIntegration(null);
303
- // Refresh list to pick up install/uninstall changes
304
311
  fetchItems();
305
312
  }
306
313
 
314
+ // Check if search matches any Google service
315
+ const googleMatchesSearch = !search || 'google workspace gmail calendar drive docs sheets slides'
316
+ .includes(search.toLowerCase());
317
+
307
318
  return (
308
319
  <ScrollArea className="h-full">
309
320
  <div className="px-5 py-4">
@@ -312,17 +323,42 @@ function IntegrationsBrowse() {
312
323
  <SearchBar value={search} onChange={setSearch} placeholder="Search integrations..." />
313
324
  </div>
314
325
  <div className="flex-1" />
315
- <span className="text-2xs text-text-4 font-mono flex-shrink-0">{items.length}</span>
326
+ <span className="text-2xs text-text-4 font-mono flex-shrink-0">
327
+ {otherItems.length + (googleItems.length > 0 ? 1 : 0)}
328
+ </span>
316
329
  </div>
317
330
 
318
331
  <div className="mt-4 grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))' }}>
319
- {loading
320
- ? Array.from({ length: 6 }).map((_, i) => <SkillCardSkeleton key={i} />)
321
- : items.map((item) => <MarketplaceCard key={item.id} item={item} onClick={() => handleCardClick(item)} />)
322
- }
332
+ {loading ? (
333
+ Array.from({ length: 6 }).map((_, i) => <SkillCardSkeleton key={i} />)
334
+ ) : (
335
+ <>
336
+ {/* Google Workspace card */}
337
+ {googleItems.length > 0 && googleMatchesSearch && (
338
+ <MarketplaceCard
339
+ key="google-workspace"
340
+ item={{
341
+ id: 'google-workspace',
342
+ name: 'Google Workspace',
343
+ description: `Gmail, Calendar, Drive, Docs, Sheets, Slides — one sign-in for all`,
344
+ category: 'productivity',
345
+ tags: ['google', 'email', 'calendar', 'drive', 'docs'],
346
+ verified: 'mcp-official',
347
+ installed: googleInstalledCount > 0,
348
+ _installedCount: googleInstalledCount,
349
+ }}
350
+ onClick={() => setShowGoogleWizard(true)}
351
+ />
352
+ )}
353
+ {/* Other integrations */}
354
+ {otherItems.map((item) => (
355
+ <MarketplaceCard key={item.id} item={item} onClick={() => handleCardClick(item)} />
356
+ ))}
357
+ </>
358
+ )}
323
359
  </div>
324
360
 
325
- {!loading && items.length === 0 && (
361
+ {!loading && otherItems.length === 0 && googleItems.length === 0 && (
326
362
  <div className="text-center py-16 text-text-4 font-sans text-sm">No integrations found.</div>
327
363
  )}
328
364
  </div>
@@ -332,6 +368,12 @@ function IntegrationsBrowse() {
332
368
  open={showWizard}
333
369
  onClose={handleWizardClose}
334
370
  />
371
+
372
+ <GoogleWorkspaceWizard
373
+ integrations={items}
374
+ open={showGoogleWizard}
375
+ onClose={() => { setShowGoogleWizard(false); fetchItems(); }}
376
+ />
335
377
  </ScrollArea>
336
378
  );
337
379
  }
@@ -341,15 +383,24 @@ function MyLibrary() {
341
383
  const authenticated = useGrooveStore((s) => s.marketplaceAuthenticated);
342
384
  const login = useGrooveStore((s) => s.marketplaceLogin);
343
385
  const [purchases, setPurchases] = useState([]);
344
- const [installed, setInstalled] = useState([]);
386
+ const [installedSkills, setInstalledSkills] = useState([]);
387
+ const [installedIntegrations, setInstalledIntegrations] = useState([]);
345
388
  const [loading, setLoading] = useState(true);
346
- const [busyId, setBusyId] = useState(null);
389
+ const [libraryFilter, setLibraryFilter] = useState('all');
390
+ const [librarySearch, setLibrarySearch] = useState('');
391
+ const [selectedSkill, setSelectedSkill] = useState(null);
392
+ const [wizardIntegration, setWizardIntegration] = useState(null);
393
+ const [showWizard, setShowWizard] = useState(false);
347
394
  const toast = useToast();
348
395
  const fileRef = useRef(null);
349
396
 
350
397
  const refreshInstalled = async () => {
351
- const data = await api.get('/skills/installed');
352
- setInstalled(Array.isArray(data) ? data : data.skills || []);
398
+ const [skillsData, intData] = await Promise.all([
399
+ api.get('/skills/installed').catch(() => []),
400
+ api.get('/integrations/installed').catch(() => []),
401
+ ]);
402
+ setInstalledSkills(Array.isArray(skillsData) ? skillsData : skillsData.skills || []);
403
+ setInstalledIntegrations(Array.isArray(intData) ? intData : intData.integrations || []);
353
404
  };
354
405
 
355
406
  useEffect(() => {
@@ -357,32 +408,14 @@ function MyLibrary() {
357
408
  Promise.all([
358
409
  authenticated ? api.get('/auth/purchases').then((d) => d.purchases || []).catch(() => []) : Promise.resolve([]),
359
410
  api.get('/skills/installed').then((d) => Array.isArray(d) ? d : d.skills || []).catch(() => []),
360
- ]).then(([p, i]) => {
411
+ api.get('/integrations/installed').then((d) => Array.isArray(d) ? d : d.integrations || []).catch(() => []),
412
+ ]).then(([p, s, i]) => {
361
413
  setPurchases(p);
362
- setInstalled(i);
414
+ setInstalledSkills(s);
415
+ setInstalledIntegrations(i);
363
416
  }).finally(() => setLoading(false));
364
417
  }, [authenticated]);
365
418
 
366
- async function handleUpdate(s) {
367
- setBusyId(s.id);
368
- try {
369
- await api.post(`/skills/${s.id}/update`);
370
- toast.success(`${s.name || s.id} updated`);
371
- await refreshInstalled();
372
- } catch (err) { toast.error('Update failed', err.message); }
373
- setBusyId(null);
374
- }
375
-
376
- async function handleUninstall(s) {
377
- setBusyId(s.id);
378
- try {
379
- await api.delete(`/skills/${s.id}`);
380
- toast.success(`${s.name || s.id} uninstalled`);
381
- await refreshInstalled();
382
- } catch (err) { toast.error('Uninstall failed', err.message); }
383
- setBusyId(null);
384
- }
385
-
386
419
  async function handleImport(e) {
387
420
  const file = e.target.files?.[0];
388
421
  if (!file) return;
@@ -398,6 +431,35 @@ function MyLibrary() {
398
431
  e.target.value = '';
399
432
  }
400
433
 
434
+ if (selectedSkill) {
435
+ return <SkillDetail skill={selectedSkill} onBack={() => setSelectedSkill(null)} />;
436
+ }
437
+
438
+ const searchLower = librarySearch.toLowerCase();
439
+ const filteredSkills = (libraryFilter === 'all' || libraryFilter === 'skills')
440
+ ? installedSkills.filter((s) => !searchLower || (s.name || s.id || '').toLowerCase().includes(searchLower) || (s.description || '').toLowerCase().includes(searchLower))
441
+ : [];
442
+ const filteredIntegrations = (libraryFilter === 'all' || libraryFilter === 'integrations')
443
+ ? installedIntegrations.filter((i) => !searchLower || (i.name || i.id || '').toLowerCase().includes(searchLower) || (i.description || '').toLowerCase().includes(searchLower))
444
+ : [];
445
+
446
+ const allItems = [
447
+ ...filteredSkills.map((s) => ({ ...s, _type: 'skill' })),
448
+ ...filteredIntegrations.map((i) => ({ ...i, _type: 'integration' })),
449
+ ];
450
+
451
+ const filters = [
452
+ { id: 'all', label: 'All' },
453
+ { id: 'skills', label: 'Skills' },
454
+ { id: 'integrations', label: 'Integrations' },
455
+ ];
456
+
457
+ const emptyMessages = {
458
+ all: 'No skills or integrations installed yet. Visit the Marketplace to get started.',
459
+ skills: 'No skills installed.',
460
+ integrations: 'No integrations installed.',
461
+ };
462
+
401
463
  if (loading) {
402
464
  return (
403
465
  <div className="p-5 space-y-3">
@@ -408,9 +470,28 @@ function MyLibrary() {
408
470
 
409
471
  return (
410
472
  <ScrollArea className="h-full">
411
- <div className="px-5 py-4 space-y-6">
412
- {/* Import button */}
413
- <div className="flex items-center gap-3">
473
+ <div className="px-5 py-4 space-y-5">
474
+ {/* Search + filter pills + import */}
475
+ <div className="flex items-center gap-3 flex-wrap">
476
+ <div className="w-72">
477
+ <SearchBar value={librarySearch} onChange={setLibrarySearch} placeholder="Search library..." />
478
+ </div>
479
+ <div className="flex items-center gap-1">
480
+ {filters.map((f) => (
481
+ <button
482
+ key={f.id}
483
+ onClick={() => setLibraryFilter(f.id)}
484
+ className={`px-3 py-1.5 text-xs font-semibold font-sans rounded-full cursor-pointer select-none transition-colors ${
485
+ libraryFilter === f.id
486
+ ? 'bg-accent/15 text-accent border border-accent/25'
487
+ : 'text-text-3 hover:text-text-1 border border-transparent hover:border-border-subtle'
488
+ }`}
489
+ >
490
+ {f.label}
491
+ </button>
492
+ ))}
493
+ </div>
494
+ <div className="flex-1" />
414
495
  <input ref={fileRef} type="file" accept=".md" onChange={handleImport} className="hidden" />
415
496
  <Button
416
497
  variant="secondary"
@@ -419,9 +500,9 @@ function MyLibrary() {
419
500
  className="gap-1.5"
420
501
  >
421
502
  <Upload size={13} />
422
- Import .md Skill
503
+ Import .md
423
504
  </Button>
424
- <span className="text-2xs text-text-4 font-sans">Drop a markdown skill file to install locally</span>
505
+ <span className="text-2xs text-text-4 font-mono flex-shrink-0">{allItems.length}</span>
425
506
  </div>
426
507
 
427
508
  {/* Purchases */}
@@ -465,53 +546,40 @@ function MyLibrary() {
465
546
  </div>
466
547
  )}
467
548
 
468
- {/* Installed Skills */}
469
- <div>
470
- <h3 className="text-xs font-semibold text-text-2 font-sans uppercase tracking-wider mb-3 flex items-center gap-1.5">
471
- <Package size={12} />
472
- Installed ({installed.length})
473
- </h3>
474
- {installed.length === 0 ? (
475
- <div className="bg-surface-1 border border-border-subtle rounded-md px-4 py-6 text-center">
476
- <Download size={20} className="mx-auto text-text-4 mb-2" />
477
- <p className="text-xs text-text-3 font-sans">No skills installed — browse the Skills tab or import a .md file</p>
478
- </div>
479
- ) : (
480
- <div className="space-y-1.5">
481
- {installed.map((s) => (
482
- <div key={s.id} className="flex items-center gap-3 px-3 py-2.5 rounded-md bg-surface-1 border border-border-subtle group">
483
- <div className="w-8 h-8 rounded-md bg-accent/10 flex items-center justify-center text-sm flex-shrink-0">
484
- {s.icon || s.name?.[0]?.toUpperCase() || '?'}
485
- </div>
486
- <div className="flex-1 min-w-0">
487
- <div className="text-xs font-semibold text-text-0 font-sans truncate">{s.name || s.id}</div>
488
- <div className="text-2xs text-text-3 font-sans truncate">{s.description || s.category || 'local skill'}</div>
489
- </div>
490
- <div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
491
- <button
492
- onClick={() => handleUpdate(s)}
493
- disabled={busyId === s.id}
494
- title="Pull latest version"
495
- className="p-1.5 rounded text-text-3 hover:text-accent hover:bg-accent/10 cursor-pointer transition-colors disabled:opacity-50"
496
- >
497
- <RefreshCw size={12} className={busyId === s.id ? 'animate-spin' : ''} />
498
- </button>
499
- <button
500
- onClick={() => handleUninstall(s)}
501
- disabled={busyId === s.id}
502
- title="Uninstall"
503
- className="p-1.5 rounded text-text-3 hover:text-error hover:bg-error/10 cursor-pointer transition-colors disabled:opacity-50"
504
- >
505
- <Trash2 size={12} />
506
- </button>
507
- </div>
508
- <Badge variant="accent" className="text-2xs flex-shrink-0">Installed</Badge>
509
- </div>
510
- ))}
511
- </div>
512
- )}
513
- </div>
549
+ {/* Card grid */}
550
+ {allItems.length > 0 ? (
551
+ <div className="grid gap-3" style={{ gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))' }}>
552
+ {allItems.map((item) => item._type === 'skill' ? (
553
+ <MarketplaceCard
554
+ key={`skill-${item.id}`}
555
+ item={{ ...item, installed: true }}
556
+ onClick={() => setSelectedSkill(item)}
557
+ />
558
+ ) : (
559
+ <MarketplaceCard
560
+ key={`int-${item.id}`}
561
+ item={{ ...item, installed: true }}
562
+ onClick={() => { setWizardIntegration(item); setShowWizard(true); }}
563
+ statusBadge={
564
+ <Badge variant={item.configured ? 'success' : 'warning'} className="text-2xs">
565
+ {item.configured ? 'Active' : 'Not configured'}
566
+ </Badge>
567
+ }
568
+ />
569
+ ))}
570
+ </div>
571
+ ) : (
572
+ <div className="text-center py-16 text-text-4 font-sans text-sm">
573
+ {emptyMessages[libraryFilter]}
574
+ </div>
575
+ )}
514
576
  </div>
577
+
578
+ <IntegrationWizard
579
+ integration={wizardIntegration}
580
+ open={showWizard}
581
+ onClose={() => { setShowWizard(false); setWizardIntegration(null); refreshInstalled(); }}
582
+ />
515
583
  </ScrollArea>
516
584
  );
517
585
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.26.38",
3
+ "version": "0.27.0",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, 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, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
@@ -52,15 +52,9 @@
52
52
  "dev:daemon": "npm run dev -w packages/daemon",
53
53
  "dev:gui": "npm run dev -w packages/gui",
54
54
  "build": "npm run build -w packages/gui",
55
- "build:gui": "npm run build -w packages/gui",
56
55
  "test": "node --test packages/daemon/test/*.test.js",
57
- "prepublishOnly": "npm run build:gui"
56
+ "prepublishOnly": "npm run build"
58
57
  },
59
- "bundledDependencies": [
60
- "@groove-dev/daemon",
61
- "@groove-dev/cli",
62
- "@groove-dev/gui"
63
- ],
64
58
  "bundleDependencies": [
65
59
  "@groove-dev/daemon",
66
60
  "@groove-dev/cli",
@@ -39,6 +39,7 @@ program
39
39
  program
40
40
  .command('stop')
41
41
  .description('Stop the GROOVE daemon')
42
+ .option('-f, --force', 'Stop even if agents are still running (destroys their work)')
42
43
  .action(stop);
43
44
 
44
45
  program
@@ -69,6 +70,7 @@ program
69
70
  program
70
71
  .command('nuke')
71
72
  .description('Kill all agents and stop the daemon')
73
+ .option('-f, --force', 'Required when agents are still running')
72
74
  .action(nuke);
73
75
 
74
76
  program
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.11.0",
3
+ "version": "0.27.0",
4
4
  "description": "GROOVE CLI \u2014 manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -4,14 +4,26 @@
4
4
  import chalk from 'chalk';
5
5
  import { apiCall } from '../client.js';
6
6
 
7
- export async function nuke() {
7
+ export async function nuke(options = {}) {
8
+ let status;
9
+ try {
10
+ status = await apiCall('GET', '/api/status');
11
+ } catch {
12
+ console.log(chalk.yellow('No running daemon found.'));
13
+ return;
14
+ }
15
+
16
+ if (status.running > 0 && !options.force) {
17
+ console.error(chalk.red(`Refusing to nuke: ${status.running} agent(s) still running.`));
18
+ console.error(chalk.dim(' Nuke will kill every agent in every team and stop the daemon.'));
19
+ console.error(chalk.dim(' Re-run with "groove nuke --force" to confirm.'));
20
+ process.exit(1);
21
+ }
22
+
8
23
  try {
9
24
  console.log(chalk.yellow('Nuking all agents...'));
10
25
  await apiCall('DELETE', '/api/agents');
11
-
12
- const status = await apiCall('GET', '/api/status');
13
26
  process.kill(status.pid, 'SIGTERM');
14
-
15
27
  console.log(chalk.green('All agents killed. Daemon stopped.'));
16
28
  } catch {
17
29
  console.log(chalk.yellow('No running daemon found.'));
@@ -4,9 +4,24 @@
4
4
  import chalk from 'chalk';
5
5
  import { apiCall } from '../client.js';
6
6
 
7
- export async function stop() {
7
+ export async function stop(options = {}) {
8
+ let status;
9
+ try {
10
+ status = await apiCall('GET', '/api/status');
11
+ } catch {
12
+ console.log(chalk.yellow('No running daemon found.'));
13
+ return;
14
+ }
15
+
16
+ if (status.running > 0 && !options.force) {
17
+ console.error(chalk.red(`Refusing to stop: ${status.running} agent(s) still running.`));
18
+ console.error(chalk.dim(' Stopping the daemon destroys all running agents in every team.'));
19
+ console.error(chalk.dim(' Kill specific agents first with "groove kill <id>",'));
20
+ console.error(chalk.dim(' or override with "groove stop --force".'));
21
+ process.exit(1);
22
+ }
23
+
8
24
  try {
9
- const status = await apiCall('GET', '/api/status');
10
25
  process.kill(status.pid, 'SIGTERM');
11
26
  console.log(chalk.green('GROOVE daemon stopped.'));
12
27
  } catch {