groove-dev 0.16.4 → 0.17.1

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 (34) hide show
  1. package/README.md +18 -16
  2. package/node_modules/@groove-dev/daemon/integrations-registry.json +417 -0
  3. package/node_modules/@groove-dev/daemon/src/api.js +204 -0
  4. package/node_modules/@groove-dev/daemon/src/index.js +9 -0
  5. package/node_modules/@groove-dev/daemon/src/integrations.js +475 -0
  6. package/node_modules/@groove-dev/daemon/src/introducer.js +23 -0
  7. package/node_modules/@groove-dev/daemon/src/process.js +59 -0
  8. package/node_modules/@groove-dev/daemon/src/registry.js +2 -1
  9. package/node_modules/@groove-dev/daemon/src/scheduler.js +336 -0
  10. package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
  11. package/node_modules/@groove-dev/gui/dist/assets/index-CEf7nLM2.js +156 -0
  12. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  13. package/node_modules/@groove-dev/gui/src/App.jsx +6 -0
  14. package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +98 -7
  15. package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +1171 -0
  16. package/node_modules/@groove-dev/gui/src/views/ScheduleManager.jsx +614 -0
  17. package/package.json +2 -2
  18. package/packages/daemon/integrations-registry.json +417 -0
  19. package/packages/daemon/src/api.js +204 -0
  20. package/packages/daemon/src/index.js +9 -0
  21. package/packages/daemon/src/integrations.js +475 -0
  22. package/packages/daemon/src/introducer.js +23 -0
  23. package/packages/daemon/src/process.js +59 -0
  24. package/packages/daemon/src/registry.js +2 -1
  25. package/packages/daemon/src/scheduler.js +336 -0
  26. package/packages/daemon/src/validate.js +10 -0
  27. package/packages/gui/dist/assets/index-CEf7nLM2.js +156 -0
  28. package/packages/gui/dist/index.html +1 -1
  29. package/packages/gui/src/App.jsx +6 -0
  30. package/packages/gui/src/components/SpawnPanel.jsx +98 -7
  31. package/packages/gui/src/views/IntegrationsStore.jsx +1171 -0
  32. package/packages/gui/src/views/ScheduleManager.jsx +614 -0
  33. package/node_modules/@groove-dev/gui/dist/assets/index-B_VHpncx.js +0 -153
  34. package/packages/gui/dist/assets/index-B_VHpncx.js +0 -153
@@ -444,6 +444,210 @@ export function createApi(app, daemon) {
444
444
  res.json({ id: agent.id, skills });
445
445
  });
446
446
 
447
+ // --- Integrations ---
448
+
449
+ app.get('/api/integrations/registry', async (req, res) => {
450
+ const integrations = await daemon.integrations.getRegistry({
451
+ search: req.query.search || '',
452
+ category: req.query.category || 'all',
453
+ });
454
+ res.json({
455
+ integrations,
456
+ categories: daemon.integrations.getCategories(),
457
+ });
458
+ });
459
+
460
+ app.get('/api/integrations/installed', (req, res) => {
461
+ res.json(daemon.integrations.getInstalled());
462
+ });
463
+
464
+ app.post('/api/integrations/:id/install', async (req, res) => {
465
+ try {
466
+ const result = await daemon.integrations.install(req.params.id);
467
+ res.json(result);
468
+ } catch (err) {
469
+ res.status(400).json({ error: err.message });
470
+ }
471
+ });
472
+
473
+ app.delete('/api/integrations/:id', async (req, res) => {
474
+ try {
475
+ const result = await daemon.integrations.uninstall(req.params.id);
476
+ res.json(result);
477
+ } catch (err) {
478
+ res.status(400).json({ error: err.message });
479
+ }
480
+ });
481
+
482
+ app.get('/api/integrations/:id/status', (req, res) => {
483
+ const status = daemon.integrations.getStatus(req.params.id);
484
+ if (!status) return res.status(404).json({ error: 'Integration not found' });
485
+ res.json(status);
486
+ });
487
+
488
+ app.post('/api/integrations/:id/credentials', (req, res) => {
489
+ try {
490
+ const { key, value } = req.body || {};
491
+ if (!key || !value) return res.status(400).json({ error: 'key and value are required' });
492
+ daemon.integrations.setCredential(req.params.id, key, value);
493
+ res.json({ ok: true });
494
+ } catch (err) {
495
+ res.status(400).json({ error: err.message });
496
+ }
497
+ });
498
+
499
+ app.delete('/api/integrations/:id/credentials/:key', (req, res) => {
500
+ try {
501
+ daemon.integrations.deleteCredential(req.params.id, req.params.key);
502
+ res.json({ ok: true });
503
+ } catch (err) {
504
+ res.status(400).json({ error: err.message });
505
+ }
506
+ });
507
+
508
+ // --- Google OAuth flow ---
509
+
510
+ app.get('/api/integrations/google-oauth/status', (req, res) => {
511
+ res.json({ configured: daemon.integrations.isGoogleOAuthConfigured() });
512
+ });
513
+
514
+ app.post('/api/integrations/google-oauth/setup', (req, res) => {
515
+ try {
516
+ const { clientId, clientSecret } = req.body || {};
517
+ if (!clientId || !clientSecret) return res.status(400).json({ error: 'clientId and clientSecret are required' });
518
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_ID', clientId);
519
+ daemon.integrations.setCredential('google-oauth', 'GOOGLE_CLIENT_SECRET', clientSecret);
520
+ res.json({ ok: true });
521
+ } catch (err) {
522
+ res.status(400).json({ error: err.message });
523
+ }
524
+ });
525
+
526
+ app.post('/api/integrations/:id/oauth/start', (req, res) => {
527
+ try {
528
+ const url = daemon.integrations.getOAuthUrl(req.params.id);
529
+ res.json({ url });
530
+ } catch (err) {
531
+ res.status(400).json({ error: err.message });
532
+ }
533
+ });
534
+
535
+ app.get('/api/integrations/oauth/callback', async (req, res) => {
536
+ try {
537
+ const { code, state } = req.query;
538
+ if (!code || !state) return res.status(400).send('Missing code or state parameter');
539
+ await daemon.integrations.handleOAuthCallback(code, state);
540
+ // Return a nice HTML page that auto-closes
541
+ res.send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e6e6e6">
542
+ <div style="text-align:center">
543
+ <div style="font-size:48px;margin-bottom:16px">&#10003;</div>
544
+ <h2>Connected!</h2>
545
+ <p style="color:#7a8394">You can close this tab and return to Groove.</p>
546
+ <script>setTimeout(()=>window.close(),2000)</script>
547
+ </div>
548
+ </body></html>`);
549
+ } catch (err) {
550
+ res.status(400).send(`<!DOCTYPE html><html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e2127;color:#e06c75">
551
+ <div style="text-align:center">
552
+ <h2>Connection Failed</h2>
553
+ <p>${err.message}</p>
554
+ <p style="color:#7a8394">Close this tab and try again in Groove.</p>
555
+ </div>
556
+ </body></html>`);
557
+ }
558
+ });
559
+
560
+ // --- Agent Integrations (attach/detach) ---
561
+
562
+ app.post('/api/agents/:agentId/integrations/:integrationId', (req, res) => {
563
+ const agent = daemon.registry.get(req.params.agentId);
564
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
565
+ const integrationId = req.params.integrationId;
566
+ if (!daemon.integrations._isInstalled(integrationId)) {
567
+ return res.status(400).json({ error: 'Integration not installed. Install it first.' });
568
+ }
569
+ const integrations = agent.integrations || [];
570
+ if (integrations.includes(integrationId)) {
571
+ return res.json({ id: agent.id, integrations });
572
+ }
573
+ daemon.registry.update(agent.id, { integrations: [...integrations, integrationId] });
574
+ daemon.audit.log('integration.attach', { agentId: agent.id, integrationId });
575
+ res.json({ id: agent.id, integrations: [...integrations, integrationId] });
576
+ });
577
+
578
+ app.delete('/api/agents/:agentId/integrations/:integrationId', (req, res) => {
579
+ const agent = daemon.registry.get(req.params.agentId);
580
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
581
+ const integrations = (agent.integrations || []).filter((s) => s !== req.params.integrationId);
582
+ daemon.registry.update(agent.id, { integrations });
583
+ daemon.audit.log('integration.detach', { agentId: agent.id, integrationId: req.params.integrationId });
584
+ res.json({ id: agent.id, integrations });
585
+ });
586
+
587
+ // --- Schedules ---
588
+
589
+ app.get('/api/schedules', (req, res) => {
590
+ res.json(daemon.scheduler.list());
591
+ });
592
+
593
+ app.post('/api/schedules', (req, res) => {
594
+ try {
595
+ const schedule = daemon.scheduler.create(req.body);
596
+ res.status(201).json(schedule);
597
+ } catch (err) {
598
+ res.status(400).json({ error: err.message });
599
+ }
600
+ });
601
+
602
+ app.get('/api/schedules/:id', (req, res) => {
603
+ const schedule = daemon.scheduler.get(req.params.id);
604
+ if (!schedule) return res.status(404).json({ error: 'Schedule not found' });
605
+ res.json(schedule);
606
+ });
607
+
608
+ app.patch('/api/schedules/:id', (req, res) => {
609
+ try {
610
+ const schedule = daemon.scheduler.update(req.params.id, req.body);
611
+ res.json(schedule);
612
+ } catch (err) {
613
+ res.status(400).json({ error: err.message });
614
+ }
615
+ });
616
+
617
+ app.delete('/api/schedules/:id', (req, res) => {
618
+ try {
619
+ daemon.scheduler.delete(req.params.id);
620
+ res.json({ ok: true });
621
+ } catch (err) {
622
+ res.status(400).json({ error: err.message });
623
+ }
624
+ });
625
+
626
+ app.post('/api/schedules/:id/enable', (req, res) => {
627
+ try {
628
+ res.json(daemon.scheduler.enable(req.params.id));
629
+ } catch (err) {
630
+ res.status(400).json({ error: err.message });
631
+ }
632
+ });
633
+
634
+ app.post('/api/schedules/:id/disable', (req, res) => {
635
+ try {
636
+ res.json(daemon.scheduler.disable(req.params.id));
637
+ } catch (err) {
638
+ res.status(400).json({ error: err.message });
639
+ }
640
+ });
641
+
642
+ app.post('/api/schedules/:id/run', async (req, res) => {
643
+ try {
644
+ const agent = await daemon.scheduler.run(req.params.id);
645
+ res.json({ ok: true, agentId: agent.id });
646
+ } catch (err) {
647
+ res.status(400).json({ error: err.message });
648
+ }
649
+ });
650
+
447
651
  // --- Directory Browser ---
448
652
 
449
653
  app.get('/api/browse', (req, res) => {
@@ -28,6 +28,8 @@ import { CodebaseIndexer } from './indexer.js';
28
28
  import { AuditLogger } from './audit.js';
29
29
  import { Federation } from './federation.js';
30
30
  import { SkillStore } from './skills.js';
31
+ import { IntegrationStore } from './integrations.js';
32
+ import { Scheduler } from './scheduler.js';
31
33
  import { FileWatcher } from './filewatcher.js';
32
34
  import { TerminalManager } from './terminal-pty.js';
33
35
  import { isFirstRun, runFirstTimeSetup, loadConfig, saveConfig, printWelcome } from './firstrun.js';
@@ -119,6 +121,8 @@ export class Daemon {
119
121
  this.audit = new AuditLogger(this.grooveDir);
120
122
  this.federation = new Federation(this);
121
123
  this.skills = new SkillStore(this);
124
+ this.integrations = new IntegrationStore(this);
125
+ this.scheduler = new Scheduler(this);
122
126
  this.fileWatcher = new FileWatcher(this);
123
127
  this.terminalManager = new TerminalManager(this);
124
128
 
@@ -276,6 +280,7 @@ export class Daemon {
276
280
  // Start background services
277
281
  this.journalist.start();
278
282
  this.rotator.start();
283
+ this.scheduler.start();
279
284
 
280
285
  // Scan codebase for workspace/structure awareness
281
286
  this.indexer.scan();
@@ -293,6 +298,7 @@ export class Daemon {
293
298
  // Stop background services
294
299
  this.journalist.stop();
295
300
  this.rotator.stop();
301
+ this.scheduler.stop();
296
302
 
297
303
  // Clean up file watchers and terminal sessions
298
304
  this.fileWatcher.unwatchAll();
@@ -310,6 +316,9 @@ export class Daemon {
310
316
  unlinkSync(hostFile);
311
317
  }
312
318
 
319
+ // Clean up MCP config (remove groove-* entries from .mcp.json)
320
+ this.integrations.cleanupMcpJson();
321
+
313
322
  // Clean up generated files
314
323
  const registryPath = resolve(this.projectDir, 'AGENTS_REGISTRY.md');
315
324
  if (existsSync(registryPath)) {