groove-dev 0.16.3 → 0.17.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 (38) hide show
  1. package/README.md +18 -16
  2. package/node_modules/@groove-dev/daemon/integrations-registry.json +321 -0
  3. package/node_modules/@groove-dev/daemon/src/api.js +152 -0
  4. package/node_modules/@groove-dev/daemon/src/index.js +13 -1
  5. package/node_modules/@groove-dev/daemon/src/integrations.js +389 -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/terminal-pty.js +119 -54
  11. package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
  12. package/node_modules/@groove-dev/gui/dist/assets/index-C5k-qSwi.js +153 -0
  13. package/node_modules/@groove-dev/gui/dist/index.html +1 -1
  14. package/node_modules/@groove-dev/gui/src/App.jsx +6 -0
  15. package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +98 -7
  16. package/node_modules/@groove-dev/gui/src/components/Terminal.jsx +29 -12
  17. package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +954 -0
  18. package/node_modules/@groove-dev/gui/src/views/ScheduleManager.jsx +614 -0
  19. package/package.json +2 -2
  20. package/packages/daemon/integrations-registry.json +321 -0
  21. package/packages/daemon/src/api.js +152 -0
  22. package/packages/daemon/src/index.js +13 -1
  23. package/packages/daemon/src/integrations.js +389 -0
  24. package/packages/daemon/src/introducer.js +23 -0
  25. package/packages/daemon/src/process.js +59 -0
  26. package/packages/daemon/src/registry.js +2 -1
  27. package/packages/daemon/src/scheduler.js +336 -0
  28. package/packages/daemon/src/terminal-pty.js +119 -54
  29. package/packages/daemon/src/validate.js +10 -0
  30. package/packages/gui/dist/assets/index-C5k-qSwi.js +153 -0
  31. package/packages/gui/dist/index.html +1 -1
  32. package/packages/gui/src/App.jsx +6 -0
  33. package/packages/gui/src/components/SpawnPanel.jsx +98 -7
  34. package/packages/gui/src/components/Terminal.jsx +29 -12
  35. package/packages/gui/src/views/IntegrationsStore.jsx +954 -0
  36. package/packages/gui/src/views/ScheduleManager.jsx +614 -0
  37. package/node_modules/@groove-dev/gui/dist/assets/index-CFeltwTB.js +0 -153
  38. package/packages/gui/dist/assets/index-CFeltwTB.js +0 -153
@@ -0,0 +1,321 @@
1
+ [
2
+ {
3
+ "id": "slack",
4
+ "name": "Slack",
5
+ "description": "Send and read messages, manage channels, search history, post reactions and thread replies",
6
+ "category": "communication",
7
+ "icon": "slack",
8
+ "tags": ["chat", "messaging", "notifications", "team"],
9
+ "roles": ["cmo", "ea", "support", "fullstack"],
10
+ "npmPackage": "@modelcontextprotocol/server-slack",
11
+ "transport": "stdio",
12
+ "command": "npx",
13
+ "args": ["-y", "@modelcontextprotocol/server-slack"],
14
+ "envKeys": [
15
+ { "key": "SLACK_BOT_TOKEN", "label": "Bot Token", "placeholder": "xoxb-...", "required": true },
16
+ { "key": "SLACK_TEAM_ID", "label": "Team ID", "placeholder": "T01234567", "required": true }
17
+ ],
18
+ "featured": true,
19
+ "downloads": 0,
20
+ "rating": 0,
21
+ "ratingCount": 0,
22
+ "verified": "mcp-official"
23
+ },
24
+ {
25
+ "id": "github",
26
+ "name": "GitHub",
27
+ "description": "Manage repos, issues, pull requests, code search, file operations, and workflows",
28
+ "category": "developer",
29
+ "icon": "github",
30
+ "tags": ["git", "repos", "issues", "pull-requests", "ci"],
31
+ "roles": ["backend", "frontend", "fullstack", "devops"],
32
+ "npmPackage": "@modelcontextprotocol/server-github",
33
+ "transport": "stdio",
34
+ "command": "npx",
35
+ "args": ["-y", "@modelcontextprotocol/server-github"],
36
+ "envKeys": [
37
+ { "key": "GITHUB_PERSONAL_ACCESS_TOKEN", "label": "Personal Access Token", "placeholder": "ghp_...", "required": true }
38
+ ],
39
+ "featured": true,
40
+ "downloads": 0,
41
+ "rating": 0,
42
+ "ratingCount": 0,
43
+ "verified": "mcp-official"
44
+ },
45
+ {
46
+ "id": "stripe",
47
+ "name": "Stripe",
48
+ "description": "Manage customers, payments, subscriptions, invoices, and financial data",
49
+ "category": "finance",
50
+ "icon": "stripe",
51
+ "tags": ["payments", "billing", "subscriptions", "finance"],
52
+ "roles": ["cfo", "analyst", "fullstack"],
53
+ "npmPackage": "@stripe/agent-toolkit",
54
+ "transport": "stdio",
55
+ "command": "npx",
56
+ "args": ["-y", "@stripe/agent-toolkit", "mcp"],
57
+ "envKeys": [
58
+ { "key": "STRIPE_SECRET_KEY", "label": "Secret Key", "placeholder": "sk_...", "required": true }
59
+ ],
60
+ "featured": true,
61
+ "downloads": 0,
62
+ "rating": 0,
63
+ "ratingCount": 0,
64
+ "verified": "verified"
65
+ },
66
+ {
67
+ "id": "google-calendar",
68
+ "name": "Google Calendar",
69
+ "description": "Create, list, and update calendar events, check availability, manage schedules",
70
+ "category": "productivity",
71
+ "icon": "calendar",
72
+ "tags": ["calendar", "scheduling", "events", "meetings"],
73
+ "roles": ["ea", "cmo"],
74
+ "npmPackage": "@anthropic-ai/mcp-server-google-calendar",
75
+ "transport": "stdio",
76
+ "command": "npx",
77
+ "args": ["-y", "@anthropic-ai/mcp-server-google-calendar"],
78
+ "envKeys": [
79
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
80
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
81
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
82
+ ],
83
+ "featured": false,
84
+ "downloads": 0,
85
+ "rating": 0,
86
+ "ratingCount": 0,
87
+ "verified": "community"
88
+ },
89
+ {
90
+ "id": "gmail",
91
+ "name": "Gmail",
92
+ "description": "Send, read, search, and draft emails via Gmail",
93
+ "category": "communication",
94
+ "icon": "email",
95
+ "tags": ["email", "inbox", "messaging", "notifications"],
96
+ "roles": ["ea", "cmo", "support"],
97
+ "npmPackage": "@anthropic-ai/mcp-server-gmail",
98
+ "transport": "stdio",
99
+ "command": "npx",
100
+ "args": ["-y", "@anthropic-ai/mcp-server-gmail"],
101
+ "envKeys": [
102
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
103
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
104
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
105
+ ],
106
+ "featured": false,
107
+ "downloads": 0,
108
+ "rating": 0,
109
+ "ratingCount": 0,
110
+ "verified": "community"
111
+ },
112
+ {
113
+ "id": "postgres",
114
+ "name": "PostgreSQL",
115
+ "description": "Query databases, inspect schemas, run SQL, and analyze data",
116
+ "category": "database",
117
+ "icon": "database",
118
+ "tags": ["sql", "database", "queries", "analytics"],
119
+ "roles": ["analyst", "backend", "cfo"],
120
+ "npmPackage": "@modelcontextprotocol/server-postgres",
121
+ "transport": "stdio",
122
+ "command": "npx",
123
+ "args": ["-y", "@modelcontextprotocol/server-postgres"],
124
+ "envKeys": [
125
+ { "key": "POSTGRES_CONNECTION_STRING", "label": "Connection String", "placeholder": "postgresql://user:pass@host:5432/db", "required": true }
126
+ ],
127
+ "featured": false,
128
+ "downloads": 0,
129
+ "rating": 0,
130
+ "ratingCount": 0,
131
+ "verified": "mcp-official"
132
+ },
133
+ {
134
+ "id": "brave-search",
135
+ "name": "Brave Search",
136
+ "description": "Web search and local search via Brave Search API",
137
+ "category": "analytics",
138
+ "icon": "search",
139
+ "tags": ["search", "web", "research", "data"],
140
+ "roles": ["cmo", "analyst", "planner"],
141
+ "npmPackage": "@modelcontextprotocol/server-brave-search",
142
+ "transport": "stdio",
143
+ "command": "npx",
144
+ "args": ["-y", "@modelcontextprotocol/server-brave-search"],
145
+ "envKeys": [
146
+ { "key": "BRAVE_API_KEY", "label": "API Key", "placeholder": "BSA...", "required": true }
147
+ ],
148
+ "featured": false,
149
+ "downloads": 0,
150
+ "rating": 0,
151
+ "ratingCount": 0,
152
+ "verified": "mcp-official"
153
+ },
154
+ {
155
+ "id": "google-drive",
156
+ "name": "Google Drive",
157
+ "description": "Search, read, and manage files in Google Drive",
158
+ "category": "productivity",
159
+ "icon": "drive",
160
+ "tags": ["files", "documents", "sheets", "storage"],
161
+ "roles": ["ea", "analyst", "cmo"],
162
+ "npmPackage": "@modelcontextprotocol/server-gdrive",
163
+ "transport": "stdio",
164
+ "command": "npx",
165
+ "args": ["-y", "@modelcontextprotocol/server-gdrive"],
166
+ "envKeys": [
167
+ { "key": "GOOGLE_CLIENT_ID", "label": "OAuth Client ID", "required": true },
168
+ { "key": "GOOGLE_CLIENT_SECRET", "label": "OAuth Client Secret", "required": true },
169
+ { "key": "GOOGLE_REFRESH_TOKEN", "label": "Refresh Token", "required": true }
170
+ ],
171
+ "featured": false,
172
+ "downloads": 0,
173
+ "rating": 0,
174
+ "ratingCount": 0,
175
+ "verified": "mcp-official"
176
+ },
177
+ {
178
+ "id": "linear",
179
+ "name": "Linear",
180
+ "description": "Create and manage issues, projects, and sprints in Linear",
181
+ "category": "productivity",
182
+ "icon": "linear",
183
+ "tags": ["issues", "project-management", "tickets", "sprints"],
184
+ "roles": ["fullstack", "backend", "devops"],
185
+ "npmPackage": "@ibraheem4/linear-mcp-server",
186
+ "transport": "stdio",
187
+ "command": "npx",
188
+ "args": ["-y", "@ibraheem4/linear-mcp-server"],
189
+ "envKeys": [
190
+ { "key": "LINEAR_API_KEY", "label": "API Key", "required": true }
191
+ ],
192
+ "featured": false,
193
+ "downloads": 0,
194
+ "rating": 0,
195
+ "ratingCount": 0,
196
+ "verified": "community"
197
+ },
198
+ {
199
+ "id": "notion",
200
+ "name": "Notion",
201
+ "description": "Read, create, and update Notion pages and databases",
202
+ "category": "productivity",
203
+ "icon": "notion",
204
+ "tags": ["wiki", "docs", "notes", "databases"],
205
+ "roles": ["ea", "cmo", "analyst"],
206
+ "npmPackage": "@notionhq/notion-mcp-server",
207
+ "transport": "stdio",
208
+ "command": "npx",
209
+ "args": ["-y", "@notionhq/notion-mcp-server"],
210
+ "envKeys": [
211
+ { "key": "NOTION_API_KEY", "label": "Integration Token", "placeholder": "ntn_...", "required": true }
212
+ ],
213
+ "featured": false,
214
+ "downloads": 0,
215
+ "rating": 0,
216
+ "ratingCount": 0,
217
+ "verified": "community"
218
+ },
219
+ {
220
+ "id": "discord",
221
+ "name": "Discord",
222
+ "description": "Send and read messages, manage channels and servers on Discord",
223
+ "category": "communication",
224
+ "icon": "discord",
225
+ "tags": ["chat", "community", "messaging", "voice"],
226
+ "roles": ["cmo", "support"],
227
+ "npmPackage": "mcp-discord",
228
+ "transport": "stdio",
229
+ "command": "npx",
230
+ "args": ["-y", "mcp-discord"],
231
+ "envKeys": [
232
+ { "key": "DISCORD_BOT_TOKEN", "label": "Bot Token", "required": true }
233
+ ],
234
+ "featured": false,
235
+ "downloads": 0,
236
+ "rating": 0,
237
+ "ratingCount": 0,
238
+ "verified": "community"
239
+ },
240
+ {
241
+ "id": "home-assistant",
242
+ "name": "Home Assistant",
243
+ "description": "Control smart home devices, query states, and manage automations",
244
+ "category": "smart-home",
245
+ "icon": "home",
246
+ "tags": ["iot", "smart-home", "automation", "devices"],
247
+ "roles": ["home"],
248
+ "npmPackage": "mcp-home-assistant",
249
+ "transport": "stdio",
250
+ "command": "npx",
251
+ "args": ["-y", "mcp-home-assistant"],
252
+ "envKeys": [
253
+ { "key": "HOME_ASSISTANT_URL", "label": "HA URL", "placeholder": "http://homeassistant.local:8123", "required": true },
254
+ { "key": "HOME_ASSISTANT_TOKEN", "label": "Long-Lived Access Token", "required": true }
255
+ ],
256
+ "featured": false,
257
+ "downloads": 0,
258
+ "rating": 0,
259
+ "ratingCount": 0,
260
+ "verified": "community"
261
+ },
262
+ {
263
+ "id": "filesystem",
264
+ "name": "Filesystem",
265
+ "description": "Read, write, search, and manage files on the local filesystem",
266
+ "category": "developer",
267
+ "icon": "folder",
268
+ "tags": ["files", "filesystem", "local", "storage"],
269
+ "roles": ["backend", "fullstack", "devops"],
270
+ "npmPackage": "@modelcontextprotocol/server-filesystem",
271
+ "transport": "stdio",
272
+ "command": "npx",
273
+ "args": ["-y", "@modelcontextprotocol/server-filesystem"],
274
+ "envKeys": [],
275
+ "featured": false,
276
+ "downloads": 0,
277
+ "rating": 0,
278
+ "ratingCount": 0,
279
+ "verified": "mcp-official"
280
+ },
281
+ {
282
+ "id": "google-maps",
283
+ "name": "Google Maps",
284
+ "description": "Geocoding, directions, place search, and distance calculations",
285
+ "category": "analytics",
286
+ "icon": "map",
287
+ "tags": ["maps", "location", "directions", "places"],
288
+ "roles": ["analyst", "ea"],
289
+ "npmPackage": "@modelcontextprotocol/server-google-maps",
290
+ "transport": "stdio",
291
+ "command": "npx",
292
+ "args": ["-y", "@modelcontextprotocol/server-google-maps"],
293
+ "envKeys": [
294
+ { "key": "GOOGLE_MAPS_API_KEY", "label": "API Key", "required": true }
295
+ ],
296
+ "featured": false,
297
+ "downloads": 0,
298
+ "rating": 0,
299
+ "ratingCount": 0,
300
+ "verified": "mcp-official"
301
+ },
302
+ {
303
+ "id": "sqlite",
304
+ "name": "SQLite",
305
+ "description": "Query and manage SQLite databases, inspect schemas, run SQL",
306
+ "category": "database",
307
+ "icon": "database",
308
+ "tags": ["sql", "database", "local", "lightweight"],
309
+ "roles": ["analyst", "backend"],
310
+ "npmPackage": "@modelcontextprotocol/server-sqlite",
311
+ "transport": "stdio",
312
+ "command": "npx",
313
+ "args": ["-y", "@modelcontextprotocol/server-sqlite"],
314
+ "envKeys": [],
315
+ "featured": false,
316
+ "downloads": 0,
317
+ "rating": 0,
318
+ "ratingCount": 0,
319
+ "verified": "mcp-official"
320
+ }
321
+ ]
@@ -444,6 +444,158 @@ 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
+ // --- Agent Integrations (attach/detach) ---
509
+
510
+ app.post('/api/agents/:agentId/integrations/:integrationId', (req, res) => {
511
+ const agent = daemon.registry.get(req.params.agentId);
512
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
513
+ const integrationId = req.params.integrationId;
514
+ if (!daemon.integrations._isInstalled(integrationId)) {
515
+ return res.status(400).json({ error: 'Integration not installed. Install it first.' });
516
+ }
517
+ const integrations = agent.integrations || [];
518
+ if (integrations.includes(integrationId)) {
519
+ return res.json({ id: agent.id, integrations });
520
+ }
521
+ daemon.registry.update(agent.id, { integrations: [...integrations, integrationId] });
522
+ daemon.audit.log('integration.attach', { agentId: agent.id, integrationId });
523
+ res.json({ id: agent.id, integrations: [...integrations, integrationId] });
524
+ });
525
+
526
+ app.delete('/api/agents/:agentId/integrations/:integrationId', (req, res) => {
527
+ const agent = daemon.registry.get(req.params.agentId);
528
+ if (!agent) return res.status(404).json({ error: 'Agent not found' });
529
+ const integrations = (agent.integrations || []).filter((s) => s !== req.params.integrationId);
530
+ daemon.registry.update(agent.id, { integrations });
531
+ daemon.audit.log('integration.detach', { agentId: agent.id, integrationId: req.params.integrationId });
532
+ res.json({ id: agent.id, integrations });
533
+ });
534
+
535
+ // --- Schedules ---
536
+
537
+ app.get('/api/schedules', (req, res) => {
538
+ res.json(daemon.scheduler.list());
539
+ });
540
+
541
+ app.post('/api/schedules', (req, res) => {
542
+ try {
543
+ const schedule = daemon.scheduler.create(req.body);
544
+ res.status(201).json(schedule);
545
+ } catch (err) {
546
+ res.status(400).json({ error: err.message });
547
+ }
548
+ });
549
+
550
+ app.get('/api/schedules/:id', (req, res) => {
551
+ const schedule = daemon.scheduler.get(req.params.id);
552
+ if (!schedule) return res.status(404).json({ error: 'Schedule not found' });
553
+ res.json(schedule);
554
+ });
555
+
556
+ app.patch('/api/schedules/:id', (req, res) => {
557
+ try {
558
+ const schedule = daemon.scheduler.update(req.params.id, req.body);
559
+ res.json(schedule);
560
+ } catch (err) {
561
+ res.status(400).json({ error: err.message });
562
+ }
563
+ });
564
+
565
+ app.delete('/api/schedules/:id', (req, res) => {
566
+ try {
567
+ daemon.scheduler.delete(req.params.id);
568
+ res.json({ ok: true });
569
+ } catch (err) {
570
+ res.status(400).json({ error: err.message });
571
+ }
572
+ });
573
+
574
+ app.post('/api/schedules/:id/enable', (req, res) => {
575
+ try {
576
+ res.json(daemon.scheduler.enable(req.params.id));
577
+ } catch (err) {
578
+ res.status(400).json({ error: err.message });
579
+ }
580
+ });
581
+
582
+ app.post('/api/schedules/:id/disable', (req, res) => {
583
+ try {
584
+ res.json(daemon.scheduler.disable(req.params.id));
585
+ } catch (err) {
586
+ res.status(400).json({ error: err.message });
587
+ }
588
+ });
589
+
590
+ app.post('/api/schedules/:id/run', async (req, res) => {
591
+ try {
592
+ const agent = await daemon.scheduler.run(req.params.id);
593
+ res.json({ ok: true, agentId: agent.id });
594
+ } catch (err) {
595
+ res.status(400).json({ error: err.message });
596
+ }
597
+ });
598
+
447
599
  // --- Directory Browser ---
448
600
 
449
601
  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
 
@@ -174,13 +178,16 @@ export class Daemon {
174
178
  break;
175
179
  // Terminal
176
180
  case 'terminal:spawn': {
177
- const id = this.terminalManager.spawn(ws, { cwd: msg.cwd });
181
+ const id = this.terminalManager.spawn(ws, { cwd: msg.cwd, cols: msg.cols, rows: msg.rows });
178
182
  ws.send(JSON.stringify({ type: 'terminal:spawned', id }));
179
183
  break;
180
184
  }
181
185
  case 'terminal:input':
182
186
  if (msg.id && msg.data) this.terminalManager.write(msg.id, msg.data);
183
187
  break;
188
+ case 'terminal:resize':
189
+ if (msg.id && msg.rows && msg.cols) this.terminalManager.resize(msg.id, msg.rows, msg.cols);
190
+ break;
184
191
  case 'terminal:kill':
185
192
  if (msg.id) this.terminalManager.kill(msg.id);
186
193
  break;
@@ -273,6 +280,7 @@ export class Daemon {
273
280
  // Start background services
274
281
  this.journalist.start();
275
282
  this.rotator.start();
283
+ this.scheduler.start();
276
284
 
277
285
  // Scan codebase for workspace/structure awareness
278
286
  this.indexer.scan();
@@ -290,6 +298,7 @@ export class Daemon {
290
298
  // Stop background services
291
299
  this.journalist.stop();
292
300
  this.rotator.stop();
301
+ this.scheduler.stop();
293
302
 
294
303
  // Clean up file watchers and terminal sessions
295
304
  this.fileWatcher.unwatchAll();
@@ -307,6 +316,9 @@ export class Daemon {
307
316
  unlinkSync(hostFile);
308
317
  }
309
318
 
319
+ // Clean up MCP config (remove groove-* entries from .mcp.json)
320
+ this.integrations.cleanupMcpJson();
321
+
310
322
  // Clean up generated files
311
323
  const registryPath = resolve(this.projectDir, 'AGENTS_REGISTRY.md');
312
324
  if (existsSync(registryPath)) {