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.
- package/README.md +18 -16
- package/node_modules/@groove-dev/daemon/integrations-registry.json +417 -0
- package/node_modules/@groove-dev/daemon/src/api.js +204 -0
- package/node_modules/@groove-dev/daemon/src/index.js +9 -0
- package/node_modules/@groove-dev/daemon/src/integrations.js +475 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +23 -0
- package/node_modules/@groove-dev/daemon/src/process.js +59 -0
- package/node_modules/@groove-dev/daemon/src/registry.js +2 -1
- package/node_modules/@groove-dev/daemon/src/scheduler.js +336 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CEf7nLM2.js +156 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/App.jsx +6 -0
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +98 -7
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +1171 -0
- package/node_modules/@groove-dev/gui/src/views/ScheduleManager.jsx +614 -0
- package/package.json +2 -2
- package/packages/daemon/integrations-registry.json +417 -0
- package/packages/daemon/src/api.js +204 -0
- package/packages/daemon/src/index.js +9 -0
- package/packages/daemon/src/integrations.js +475 -0
- package/packages/daemon/src/introducer.js +23 -0
- package/packages/daemon/src/process.js +59 -0
- package/packages/daemon/src/registry.js +2 -1
- package/packages/daemon/src/scheduler.js +336 -0
- package/packages/daemon/src/validate.js +10 -0
- package/packages/gui/dist/assets/index-CEf7nLM2.js +156 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/App.jsx +6 -0
- package/packages/gui/src/components/SpawnPanel.jsx +98 -7
- package/packages/gui/src/views/IntegrationsStore.jsx +1171 -0
- package/packages/gui/src/views/ScheduleManager.jsx +614 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-B_VHpncx.js +0 -153
- 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">✓</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)) {
|