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.
- package/README.md +18 -16
- package/node_modules/@groove-dev/daemon/integrations-registry.json +321 -0
- package/node_modules/@groove-dev/daemon/src/api.js +152 -0
- package/node_modules/@groove-dev/daemon/src/index.js +13 -1
- package/node_modules/@groove-dev/daemon/src/integrations.js +389 -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/terminal-pty.js +119 -54
- package/node_modules/@groove-dev/daemon/src/validate.js +10 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-C5k-qSwi.js +153 -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/components/Terminal.jsx +29 -12
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +954 -0
- package/node_modules/@groove-dev/gui/src/views/ScheduleManager.jsx +614 -0
- package/package.json +2 -2
- package/packages/daemon/integrations-registry.json +321 -0
- package/packages/daemon/src/api.js +152 -0
- package/packages/daemon/src/index.js +13 -1
- package/packages/daemon/src/integrations.js +389 -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/terminal-pty.js +119 -54
- package/packages/daemon/src/validate.js +10 -0
- package/packages/gui/dist/assets/index-C5k-qSwi.js +153 -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/components/Terminal.jsx +29 -12
- package/packages/gui/src/views/IntegrationsStore.jsx +954 -0
- package/packages/gui/src/views/ScheduleManager.jsx +614 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-CFeltwTB.js +0 -153
- 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)) {
|