groove-dev 0.17.8 → 0.18.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/CLAUDE.md +0 -7
- package/node_modules/@groove-dev/daemon/google-oauth.json +5 -0
- package/node_modules/@groove-dev/daemon/integrations-registry.json +0 -40
- package/node_modules/@groove-dev/daemon/src/api.js +103 -12
- package/node_modules/@groove-dev/daemon/src/integrations.js +59 -20
- package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +4 -0
- package/node_modules/@groove-dev/gui/.groove/codebase-index.json +64 -0
- package/node_modules/@groove-dev/gui/.groove/config.json +10 -0
- package/node_modules/@groove-dev/gui/.groove/coordination.md +5 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.host +1 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.pid +1 -0
- package/node_modules/@groove-dev/gui/.groove/daemon.port +1 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.key +3 -0
- package/node_modules/@groove-dev/gui/.groove/federation/identity.pub +3 -0
- package/node_modules/@groove-dev/gui/.groove/integrations/package.json +6 -0
- package/node_modules/@groove-dev/gui/.groove/state.json +3 -0
- package/{packages/gui/dist/assets/index-D5dtDQf0.js → node_modules/@groove-dev/gui/dist/assets/index-DXkccbmd.js} +76 -50
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/App.jsx +27 -4
- package/node_modules/@groove-dev/gui/src/components/AgentChat.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +839 -586
- package/node_modules/@groove-dev/gui/src/views/FileEditor.jsx +85 -1
- package/node_modules/@groove-dev/gui/src/views/IntegrationsStore.jsx +121 -44
- package/package.json +1 -2
- package/packages/daemon/integrations-registry.json +0 -40
- package/packages/daemon/src/api.js +103 -12
- package/packages/daemon/src/integrations.js +59 -20
- package/packages/daemon/src/providers/claude-code.js +4 -0
- package/{node_modules/@groove-dev/gui/dist/assets/index-D5dtDQf0.js → packages/gui/dist/assets/index-DXkccbmd.js} +76 -50
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +68 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +1420 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +17 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +22 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +34 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +101 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +2534 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +789 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +115 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +1136 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +63 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +179 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +104 -0
- package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +46 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +121 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +9237 -0
- package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +9934 -0
- package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/_metadata.json +184 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +5169 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +2000 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js +1115 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3LBP22MX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js +701 -0
- package/packages/gui/node_modules/.vite/deps/chunk-3Q7HT7ZF.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +1776 -0
- package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js +280 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5RZAEUNX.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
- package/packages/gui/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js +1004 -0
- package/packages/gui/node_modules/.vite/deps/chunk-7FYDPZIO.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js +292 -0
- package/packages/gui/node_modules/.vite/deps/chunk-BX6POZPY.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +1062 -0
- package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +10985 -0
- package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +3459 -0
- package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/package.json +3 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js +20217 -0
- package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react.js +5 -0
- package/packages/gui/node_modules/.vite/deps/react.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +278 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +6 -0
- package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js +56 -0
- package/packages/gui/node_modules/.vite/deps/zustand.js.map +7 -0
- package/packages/gui/src/App.jsx +27 -4
- package/packages/gui/src/components/AgentChat.jsx +1 -1
- package/packages/gui/src/components/SpawnPanel.jsx +839 -586
- package/packages/gui/src/views/FileEditor.jsx +85 -1
- package/packages/gui/src/views/IntegrationsStore.jsx +121 -44
- package/docs/FILE-EDITOR-PLAN.md +0 -253
- package/docs/GUI_DESIGN_SPEC.md +0 -402
- package/docs/SKILLS-API-SPEC.md +0 -277
package/CLAUDE.md
CHANGED
|
@@ -195,10 +195,3 @@ Fully functional multi-agent orchestration system. Tested end-to-end: planner
|
|
|
195
195
|
- Remote access (--host 0.0.0.0 + token auth)
|
|
196
196
|
- Semantic degradation detection
|
|
197
197
|
- Distribution: demo video, HN launch, Twitter content
|
|
198
|
-
|
|
199
|
-
<!-- GROOVE:START -->
|
|
200
|
-
## GROOVE Orchestration (auto-injected)
|
|
201
|
-
Active agents: 0
|
|
202
|
-
See AGENTS_REGISTRY.md for full agent state.
|
|
203
|
-
**Memory policy:** Ignore auto-memory. Do not read or write MEMORY.md. GROOVE manages all context.
|
|
204
|
-
<!-- GROOVE:END -->
|
|
@@ -338,26 +338,6 @@
|
|
|
338
338
|
"ratingCount": 0,
|
|
339
339
|
"verified": "community"
|
|
340
340
|
},
|
|
341
|
-
{
|
|
342
|
-
"id": "filesystem",
|
|
343
|
-
"name": "Filesystem",
|
|
344
|
-
"description": "Read, write, search, and manage files on the local filesystem",
|
|
345
|
-
"category": "developer",
|
|
346
|
-
"icon": "folder",
|
|
347
|
-
"tags": ["files", "filesystem", "local", "storage"],
|
|
348
|
-
"roles": ["backend", "fullstack", "devops"],
|
|
349
|
-
"npmPackage": "@modelcontextprotocol/server-filesystem",
|
|
350
|
-
"transport": "stdio",
|
|
351
|
-
"command": "npx",
|
|
352
|
-
"args": ["-y", "@modelcontextprotocol/server-filesystem"],
|
|
353
|
-
"authType": "none",
|
|
354
|
-
"envKeys": [],
|
|
355
|
-
"featured": false,
|
|
356
|
-
"downloads": 0,
|
|
357
|
-
"rating": 0,
|
|
358
|
-
"ratingCount": 0,
|
|
359
|
-
"verified": "mcp-official"
|
|
360
|
-
},
|
|
361
341
|
{
|
|
362
342
|
"id": "google-maps",
|
|
363
343
|
"name": "Google Maps",
|
|
@@ -385,25 +365,5 @@
|
|
|
385
365
|
"rating": 0,
|
|
386
366
|
"ratingCount": 0,
|
|
387
367
|
"verified": "mcp-official"
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
"id": "sqlite",
|
|
391
|
-
"name": "SQLite",
|
|
392
|
-
"description": "Query and manage SQLite databases, inspect schemas, run SQL",
|
|
393
|
-
"category": "database",
|
|
394
|
-
"icon": "database",
|
|
395
|
-
"tags": ["sql", "database", "local", "lightweight"],
|
|
396
|
-
"roles": ["analyst", "backend"],
|
|
397
|
-
"npmPackage": "mcp-sqlite",
|
|
398
|
-
"transport": "stdio",
|
|
399
|
-
"command": "npx",
|
|
400
|
-
"args": ["-y", "mcp-sqlite"],
|
|
401
|
-
"authType": "none",
|
|
402
|
-
"envKeys": [],
|
|
403
|
-
"featured": false,
|
|
404
|
-
"downloads": 0,
|
|
405
|
-
"rating": 0,
|
|
406
|
-
"ratingCount": 0,
|
|
407
|
-
"verified": "mcp-official"
|
|
408
368
|
}
|
|
409
369
|
]
|
|
@@ -5,8 +5,9 @@ import express from 'express';
|
|
|
5
5
|
import { resolve, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, unlinkSync, renameSync, rmSync, createReadStream } from 'fs';
|
|
8
|
+
import { spawn as cpSpawn } from 'child_process';
|
|
8
9
|
import { lookup as mimeLookup } from './mimetypes.js';
|
|
9
|
-
import { listProviders } from './providers/index.js';
|
|
10
|
+
import { listProviders, getProvider } from './providers/index.js';
|
|
10
11
|
import { validateAgentConfig } from './validate.js';
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -351,6 +352,67 @@ export function createApi(app, daemon) {
|
|
|
351
352
|
});
|
|
352
353
|
});
|
|
353
354
|
|
|
355
|
+
// Plan chat — direct API (fast, sub-second) when API key available, CLI fallback otherwise
|
|
356
|
+
const PLAN_SYSTEM = `You are the planning assistant built into Groove's spawn panel. The user is configuring an AI agent right now — they're looking at a form with role selection, file scope, skills, integrations, effort level, and a task prompt. Your conversation will be synthesized into the agent's task prompt when they click "Generate Prompt."
|
|
357
|
+
|
|
358
|
+
Your job: help them think through what the agent should do, then craft a clear plan. Be direct and practical. Don't ask how they'll feed input to agents or what tools to use — they're already inside Groove doing it. Focus on the TASK itself.
|
|
359
|
+
|
|
360
|
+
What you know about the system:
|
|
361
|
+
- The user is in the spawn panel, configuring an agent before launching it
|
|
362
|
+
- The left panel has: role picker, directory, permissions, effort, integrations, skills, schedule
|
|
363
|
+
- When done planning, "Generate Prompt" synthesizes this chat into the agent's task prompt
|
|
364
|
+
- Agents are Claude Code instances with full terminal/file access in the specified directory
|
|
365
|
+
- Agents can read/write files, run commands, use MCP integrations (Slack, GitHub, etc.)
|
|
366
|
+
- The journalist system prevents cold starts during context rotation — agents don't lose context
|
|
367
|
+
|
|
368
|
+
Keep responses concise. Help them think, don't lecture them about the system they built.`;
|
|
369
|
+
|
|
370
|
+
app.post('/api/journalist/query', async (req, res) => {
|
|
371
|
+
try {
|
|
372
|
+
const { prompt } = req.body || {};
|
|
373
|
+
if (!prompt) return res.status(400).json({ error: 'prompt is required' });
|
|
374
|
+
|
|
375
|
+
// Fast path: direct Anthropic API call (sub-second)
|
|
376
|
+
const apiKey = daemon.credentials.getKey('anthropic-api');
|
|
377
|
+
if (apiKey) {
|
|
378
|
+
const apiRes = await fetch('https://api.anthropic.com/v1/messages', {
|
|
379
|
+
method: 'POST',
|
|
380
|
+
headers: {
|
|
381
|
+
'Content-Type': 'application/json',
|
|
382
|
+
'x-api-key': apiKey,
|
|
383
|
+
'anthropic-version': '2023-06-01',
|
|
384
|
+
},
|
|
385
|
+
body: JSON.stringify({
|
|
386
|
+
model: 'claude-haiku-4-5-20251001',
|
|
387
|
+
max_tokens: 1024,
|
|
388
|
+
system: PLAN_SYSTEM,
|
|
389
|
+
messages: [{ role: 'user', content: prompt }],
|
|
390
|
+
}),
|
|
391
|
+
});
|
|
392
|
+
const data = await apiRes.json();
|
|
393
|
+
if (data.content?.[0]?.text) {
|
|
394
|
+
return res.json({ response: data.content[0].text, mode: 'fast' });
|
|
395
|
+
}
|
|
396
|
+
if (data.error) {
|
|
397
|
+
return res.status(400).json({ error: data.error.message || 'API error' });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Slow path: CLI fallback for subscription auth (~10s)
|
|
402
|
+
const fullPrompt = `${PLAN_SYSTEM}\n\n${prompt}`;
|
|
403
|
+
const response = await daemon.journalist.callHeadless(fullPrompt);
|
|
404
|
+
res.json({ response, mode: 'cli' });
|
|
405
|
+
} catch (err) {
|
|
406
|
+
res.status(500).json({ error: err.message });
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Check if Anthropic API key is configured
|
|
411
|
+
app.get('/api/anthropic-key/status', (req, res) => {
|
|
412
|
+
const hasKey = !!daemon.credentials.getKey('anthropic-api');
|
|
413
|
+
res.json({ configured: hasKey });
|
|
414
|
+
});
|
|
415
|
+
|
|
354
416
|
// Trigger journalist cycle manually
|
|
355
417
|
app.post('/api/journalist/cycle', async (req, res) => {
|
|
356
418
|
try {
|
|
@@ -507,11 +569,13 @@ export function createApi(app, daemon) {
|
|
|
507
569
|
// Parameterized :id routes (after specific routes above)
|
|
508
570
|
|
|
509
571
|
app.post('/api/integrations/:id/authenticate', (req, res) => {
|
|
572
|
+
console.log(`[Groove:API] POST /api/integrations/${req.params.id}/authenticate`);
|
|
510
573
|
try {
|
|
511
574
|
const handle = daemon.integrations.authenticate(req.params.id);
|
|
575
|
+
console.log(`[Groove:API] Authenticate started, PID: ${handle.pid}`);
|
|
512
576
|
res.json({ ok: true, pid: handle.pid });
|
|
513
|
-
// Auto-cleanup tracked by the handle timeout
|
|
514
577
|
} catch (err) {
|
|
578
|
+
console.log(`[Groove:API] Authenticate error: ${err.message}`);
|
|
515
579
|
res.status(400).json({ error: err.message });
|
|
516
580
|
}
|
|
517
581
|
});
|
|
@@ -740,6 +804,11 @@ export function createApi(app, daemon) {
|
|
|
740
804
|
|
|
741
805
|
const IGNORED_NAMES = new Set(['.git', 'node_modules', '.DS_Store', '.groove', '__pycache__', '.next', '.cache', 'dist', 'coverage']);
|
|
742
806
|
|
|
807
|
+
// Editor root directory — defaults to projectDir but can be changed at runtime
|
|
808
|
+
let editorRootDir = daemon.projectDir;
|
|
809
|
+
|
|
810
|
+
function getEditorRoot() { return editorRootDir; }
|
|
811
|
+
|
|
743
812
|
function validateFilePath(relPath, projectDir) {
|
|
744
813
|
if (!relPath || typeof relPath !== 'string') return { error: 'path is required' };
|
|
745
814
|
if (relPath.startsWith('/') || relPath.includes('..') || relPath.includes('\0')) {
|
|
@@ -750,6 +819,27 @@ export function createApi(app, daemon) {
|
|
|
750
819
|
return { fullPath };
|
|
751
820
|
}
|
|
752
821
|
|
|
822
|
+
// Get/set the editor working directory
|
|
823
|
+
app.get('/api/files/root', (req, res) => {
|
|
824
|
+
res.json({ root: editorRootDir });
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
app.post('/api/files/root', (req, res) => {
|
|
828
|
+
const { root } = req.body || {};
|
|
829
|
+
if (!root || typeof root !== 'string') return res.status(400).json({ error: 'root path is required' });
|
|
830
|
+
// Must be absolute and exist
|
|
831
|
+
if (!root.startsWith('/')) return res.status(400).json({ error: 'root must be an absolute path' });
|
|
832
|
+
if (root.includes('\0') || root.includes('..')) return res.status(400).json({ error: 'Invalid path' });
|
|
833
|
+
if (!existsSync(root)) return res.status(404).json({ error: 'Directory not found' });
|
|
834
|
+
try {
|
|
835
|
+
const stat = statSync(root);
|
|
836
|
+
if (!stat.isDirectory()) return res.status(400).json({ error: 'Path is not a directory' });
|
|
837
|
+
} catch { return res.status(400).json({ error: 'Cannot access directory' }); }
|
|
838
|
+
editorRootDir = root;
|
|
839
|
+
daemon.audit.log('editor.root.set', { root });
|
|
840
|
+
res.json({ ok: true, root: editorRootDir });
|
|
841
|
+
});
|
|
842
|
+
|
|
753
843
|
// File tree — returns dirs + files for a given path
|
|
754
844
|
app.get('/api/files/tree', (req, res) => {
|
|
755
845
|
const relPath = req.query.path || '';
|
|
@@ -759,8 +849,9 @@ export function createApi(app, daemon) {
|
|
|
759
849
|
return res.status(400).json({ error: 'Invalid path' });
|
|
760
850
|
}
|
|
761
851
|
|
|
762
|
-
const
|
|
763
|
-
|
|
852
|
+
const rootDir = getEditorRoot();
|
|
853
|
+
const fullPath = relPath ? resolve(rootDir, relPath) : rootDir;
|
|
854
|
+
if (!fullPath.startsWith(rootDir)) {
|
|
764
855
|
return res.status(400).json({ error: 'Path outside project' });
|
|
765
856
|
}
|
|
766
857
|
if (!existsSync(fullPath)) {
|
|
@@ -810,7 +901,7 @@ export function createApi(app, daemon) {
|
|
|
810
901
|
|
|
811
902
|
// Read file contents
|
|
812
903
|
app.get('/api/files/read', (req, res) => {
|
|
813
|
-
const result = validateFilePath(req.query.path,
|
|
904
|
+
const result = validateFilePath(req.query.path, getEditorRoot());
|
|
814
905
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
815
906
|
|
|
816
907
|
if (!existsSync(result.fullPath)) {
|
|
@@ -846,7 +937,7 @@ export function createApi(app, daemon) {
|
|
|
846
937
|
// Write file contents
|
|
847
938
|
app.post('/api/files/write', (req, res) => {
|
|
848
939
|
const { path: relPath, content } = req.body;
|
|
849
|
-
const result = validateFilePath(relPath,
|
|
940
|
+
const result = validateFilePath(relPath, getEditorRoot());
|
|
850
941
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
851
942
|
|
|
852
943
|
if (typeof content !== 'string') {
|
|
@@ -868,7 +959,7 @@ export function createApi(app, daemon) {
|
|
|
868
959
|
// Create a new file
|
|
869
960
|
app.post('/api/files/create', (req, res) => {
|
|
870
961
|
const { path: relPath, content = '' } = req.body;
|
|
871
|
-
const result = validateFilePath(relPath,
|
|
962
|
+
const result = validateFilePath(relPath, getEditorRoot());
|
|
872
963
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
873
964
|
|
|
874
965
|
if (existsSync(result.fullPath)) {
|
|
@@ -893,7 +984,7 @@ export function createApi(app, daemon) {
|
|
|
893
984
|
// Create a new directory
|
|
894
985
|
app.post('/api/files/mkdir', (req, res) => {
|
|
895
986
|
const { path: relPath } = req.body;
|
|
896
|
-
const result = validateFilePath(relPath,
|
|
987
|
+
const result = validateFilePath(relPath, getEditorRoot());
|
|
897
988
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
898
989
|
|
|
899
990
|
if (existsSync(result.fullPath)) {
|
|
@@ -912,7 +1003,7 @@ export function createApi(app, daemon) {
|
|
|
912
1003
|
// Delete a file or directory
|
|
913
1004
|
app.delete('/api/files/delete', (req, res) => {
|
|
914
1005
|
const relPath = req.query.path || req.body?.path;
|
|
915
|
-
const result = validateFilePath(relPath,
|
|
1006
|
+
const result = validateFilePath(relPath, getEditorRoot());
|
|
916
1007
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
917
1008
|
|
|
918
1009
|
if (!existsSync(result.fullPath)) {
|
|
@@ -936,9 +1027,9 @@ export function createApi(app, daemon) {
|
|
|
936
1027
|
// Rename / move a file or directory
|
|
937
1028
|
app.post('/api/files/rename', (req, res) => {
|
|
938
1029
|
const { oldPath, newPath } = req.body;
|
|
939
|
-
const oldResult = validateFilePath(oldPath,
|
|
1030
|
+
const oldResult = validateFilePath(oldPath, getEditorRoot());
|
|
940
1031
|
if (oldResult.error) return res.status(400).json({ error: oldResult.error });
|
|
941
|
-
const newResult = validateFilePath(newPath,
|
|
1032
|
+
const newResult = validateFilePath(newPath, getEditorRoot());
|
|
942
1033
|
if (newResult.error) return res.status(400).json({ error: newResult.error });
|
|
943
1034
|
|
|
944
1035
|
if (!existsSync(oldResult.fullPath)) {
|
|
@@ -962,7 +1053,7 @@ export function createApi(app, daemon) {
|
|
|
962
1053
|
|
|
963
1054
|
// Serve raw file (images, video, etc.)
|
|
964
1055
|
app.get('/api/files/raw', (req, res) => {
|
|
965
|
-
const result = validateFilePath(req.query.path,
|
|
1056
|
+
const result = validateFilePath(req.query.path, getEditorRoot());
|
|
966
1057
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
967
1058
|
|
|
968
1059
|
if (!existsSync(result.fullPath)) {
|
|
@@ -380,10 +380,8 @@ export class IntegrationStore {
|
|
|
380
380
|
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
381
381
|
if (entry.authType !== 'oauth-google') throw new Error('Integration does not use OAuth');
|
|
382
382
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
386
|
-
if (!clientId || !clientSecret) {
|
|
383
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
384
|
+
if (!creds) {
|
|
387
385
|
throw new Error('Google OAuth not configured. Set up your Google Cloud project first.');
|
|
388
386
|
}
|
|
389
387
|
|
|
@@ -392,7 +390,7 @@ export class IntegrationStore {
|
|
|
392
390
|
const scopes = entry.oauthScopes || [];
|
|
393
391
|
|
|
394
392
|
const params = new URLSearchParams({
|
|
395
|
-
client_id: clientId,
|
|
393
|
+
client_id: creds.clientId,
|
|
396
394
|
redirect_uri: redirectUri,
|
|
397
395
|
response_type: 'code',
|
|
398
396
|
scope: scopes.join(' '),
|
|
@@ -408,11 +406,11 @@ export class IntegrationStore {
|
|
|
408
406
|
* Handle OAuth callback — exchange code for tokens.
|
|
409
407
|
*/
|
|
410
408
|
async handleOAuthCallback(code, integrationId) {
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
if (!clientId || !clientSecret) {
|
|
409
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
410
|
+
if (!creds) {
|
|
414
411
|
throw new Error('Google OAuth credentials not found');
|
|
415
412
|
}
|
|
413
|
+
const { clientId, clientSecret } = creds;
|
|
416
414
|
|
|
417
415
|
const port = this.daemon.port || 31415;
|
|
418
416
|
const redirectUri = `http://localhost:${port}/api/integrations/oauth/callback`;
|
|
@@ -451,10 +449,32 @@ export class IntegrationStore {
|
|
|
451
449
|
/**
|
|
452
450
|
* Check if Google OAuth is configured (user has set up their Cloud project).
|
|
453
451
|
*/
|
|
452
|
+
/**
|
|
453
|
+
* Get Google OAuth credentials from user config OR bundled defaults.
|
|
454
|
+
* User-configured credentials take priority over bundled defaults.
|
|
455
|
+
*/
|
|
456
|
+
_getGoogleOAuthCredentials() {
|
|
457
|
+
// 1. Check user-configured credentials (encrypted store)
|
|
458
|
+
let clientId = this.getCredential('google-oauth', 'GOOGLE_CLIENT_ID');
|
|
459
|
+
let clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
460
|
+
if (clientId && clientSecret) return { clientId, clientSecret, source: 'user' };
|
|
461
|
+
|
|
462
|
+
// 2. Check bundled defaults (shipped with Groove)
|
|
463
|
+
try {
|
|
464
|
+
const defaultsPath = resolve(__dirname, '../google-oauth.json');
|
|
465
|
+
if (existsSync(defaultsPath)) {
|
|
466
|
+
const defaults = JSON.parse(readFileSync(defaultsPath, 'utf8'));
|
|
467
|
+
if (defaults.client_id && defaults.client_secret) {
|
|
468
|
+
return { clientId: defaults.client_id, clientSecret: defaults.client_secret, source: 'bundled' };
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} catch { /* no bundled defaults */ }
|
|
472
|
+
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
454
476
|
isGoogleOAuthConfigured() {
|
|
455
|
-
|
|
456
|
-
const clientSecret = this.getCredential('google-oauth', 'GOOGLE_CLIENT_SECRET');
|
|
457
|
-
return !!(clientId && clientSecret);
|
|
477
|
+
return !!this._getGoogleOAuthCredentials();
|
|
458
478
|
}
|
|
459
479
|
|
|
460
480
|
/**
|
|
@@ -467,14 +487,18 @@ export class IntegrationStore {
|
|
|
467
487
|
const entry = this.registry.find((s) => s.id === integrationId);
|
|
468
488
|
if (!entry) throw new Error(`Integration not found: ${integrationId}`);
|
|
469
489
|
|
|
490
|
+
console.log(`[Groove:Integrations] authenticate(${integrationId}) — authType: ${entry.authType}`);
|
|
491
|
+
|
|
470
492
|
// For google-autoauth integrations, write the gcp-oauth.keys.json file
|
|
471
493
|
// that the MCP server expects before it can start the OAuth browser flow
|
|
472
494
|
if (entry.authType === 'google-autoauth') {
|
|
495
|
+
console.log(`[Groove:Integrations] Writing gcp-oauth.keys.json for ${integrationId}`);
|
|
473
496
|
this._writeGoogleOAuthKeys(entry);
|
|
474
497
|
}
|
|
475
498
|
|
|
476
499
|
const command = entry.command || 'npx';
|
|
477
500
|
const args = entry.args || ['-y', entry.npmPackage];
|
|
501
|
+
console.log(`[Groove:Integrations] Spawning: ${command} ${args.join(' ')}`);
|
|
478
502
|
|
|
479
503
|
// Build env with any configured credentials
|
|
480
504
|
const env = {};
|
|
@@ -491,6 +515,8 @@ export class IntegrationStore {
|
|
|
491
515
|
detached: false,
|
|
492
516
|
});
|
|
493
517
|
|
|
518
|
+
console.log(`[Groove:Integrations] Process spawned, PID: ${proc.pid}`);
|
|
519
|
+
|
|
494
520
|
// Send MCP handshake to initialize the server — this triggers auth
|
|
495
521
|
const initMsg = JSON.stringify({
|
|
496
522
|
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
@@ -507,28 +533,38 @@ export class IntegrationStore {
|
|
|
507
533
|
jsonrpc: '2.0', method: 'notifications/initialized',
|
|
508
534
|
});
|
|
509
535
|
|
|
510
|
-
// Wait a moment for npx to download + start, then send handshake
|
|
511
536
|
proc.stdout.on('data', (chunk) => {
|
|
512
537
|
const text = chunk.toString();
|
|
538
|
+
console.log(`[Groove:Integrations] MCP stdout: ${text.slice(0, 200)}`);
|
|
513
539
|
// After initialize response, send initialized notification + tools/list
|
|
514
540
|
if (text.includes('"id":1') || text.includes('"id": 1')) {
|
|
541
|
+
console.log('[Groove:Integrations] Got initialize response, sending initialized + tools/list');
|
|
515
542
|
proc.stdin.write(initializedNotif + '\n');
|
|
516
543
|
setTimeout(() => proc.stdin.write(listToolsMsg + '\n'), 500);
|
|
517
544
|
}
|
|
518
545
|
});
|
|
519
546
|
|
|
547
|
+
proc.on('error', (err) => {
|
|
548
|
+
console.log(`[Groove:Integrations] Process error: ${err.message}`);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
proc.on('exit', (code, signal) => {
|
|
552
|
+
console.log(`[Groove:Integrations] Process exited: code=${code} signal=${signal}`);
|
|
553
|
+
clearTimeout(timeout);
|
|
554
|
+
});
|
|
555
|
+
|
|
520
556
|
// Send initialize after a brief delay for npx startup
|
|
521
557
|
setTimeout(() => {
|
|
522
|
-
|
|
558
|
+
console.log('[Groove:Integrations] Sending MCP initialize message');
|
|
559
|
+
try { proc.stdin.write(initMsg + '\n'); } catch (e) { console.log('[Groove:Integrations] stdin write failed:', e.message); }
|
|
523
560
|
}, 3000);
|
|
524
561
|
|
|
525
|
-
// Auto-kill after 2 minutes
|
|
562
|
+
// Auto-kill after 2 minutes
|
|
526
563
|
const timeout = setTimeout(() => {
|
|
564
|
+
console.log('[Groove:Integrations] Auth timeout — killing process');
|
|
527
565
|
try { proc.kill('SIGTERM'); } catch { /* ignore */ }
|
|
528
566
|
}, 120_000);
|
|
529
567
|
|
|
530
|
-
proc.on('exit', () => clearTimeout(timeout));
|
|
531
|
-
|
|
532
568
|
this.daemon.audit.log('integration.authenticate', { id: integrationId });
|
|
533
569
|
|
|
534
570
|
return {
|
|
@@ -543,11 +579,11 @@ export class IntegrationStore {
|
|
|
543
579
|
* before they can open the browser for user consent.
|
|
544
580
|
*/
|
|
545
581
|
_writeGoogleOAuthKeys(entry) {
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
throw new Error('Google OAuth not configured. Click "Sign in with Google" to set up your Google Cloud credentials first.');
|
|
582
|
+
const creds = this._getGoogleOAuthCredentials();
|
|
583
|
+
if (!creds) {
|
|
584
|
+
throw new Error('Google OAuth not configured. Set up your Google Cloud credentials first.');
|
|
550
585
|
}
|
|
586
|
+
const { clientId, clientSecret } = creds;
|
|
551
587
|
|
|
552
588
|
const keysContent = JSON.stringify({
|
|
553
589
|
installed: {
|
|
@@ -567,6 +603,9 @@ export class IntegrationStore {
|
|
|
567
603
|
mkdirSync(dirPath, { recursive: true });
|
|
568
604
|
const keysPath = resolve(dirPath, 'gcp-oauth.keys.json');
|
|
569
605
|
writeFileSync(keysPath, keysContent, { mode: 0o600 });
|
|
606
|
+
console.log(`[Groove:Integrations] Wrote OAuth keys to: ${keysPath}`);
|
|
607
|
+
} else {
|
|
608
|
+
console.log(`[Groove:Integrations] WARNING: No oauthKeysDir for ${entry.id}`);
|
|
570
609
|
}
|
|
571
610
|
}
|
|
572
611
|
|
|
@@ -48,6 +48,10 @@ export class ClaudeCodeProvider extends Provider {
|
|
|
48
48
|
args.push('--model', agent.model);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
if (agent.effort) {
|
|
52
|
+
args.push('--effort', agent.effort);
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
// Pass the initial prompt as positional arg (includes GROOVE context)
|
|
52
56
|
const fullPrompt = this.buildFullPrompt(agent);
|
|
53
57
|
if (fullPrompt) {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projectName": "gui",
|
|
3
|
+
"scannedAt": "2026-04-08T01:26:36.485Z",
|
|
4
|
+
"stats": {
|
|
5
|
+
"totalFiles": 32,
|
|
6
|
+
"totalDirs": 5,
|
|
7
|
+
"treeDepth": 4
|
|
8
|
+
},
|
|
9
|
+
"workspaces": [],
|
|
10
|
+
"keyFiles": [
|
|
11
|
+
"package.json"
|
|
12
|
+
],
|
|
13
|
+
"tree": [
|
|
14
|
+
{
|
|
15
|
+
"path": ".",
|
|
16
|
+
"depth": 0,
|
|
17
|
+
"dirs": 2,
|
|
18
|
+
"files": 3,
|
|
19
|
+
"children": [
|
|
20
|
+
"public",
|
|
21
|
+
"src"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "public",
|
|
26
|
+
"depth": 1,
|
|
27
|
+
"dirs": 0,
|
|
28
|
+
"files": 3,
|
|
29
|
+
"children": []
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "src",
|
|
33
|
+
"depth": 1,
|
|
34
|
+
"dirs": 3,
|
|
35
|
+
"files": 3,
|
|
36
|
+
"children": [
|
|
37
|
+
"components",
|
|
38
|
+
"stores",
|
|
39
|
+
"views"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"path": "src/components",
|
|
44
|
+
"depth": 2,
|
|
45
|
+
"dirs": 0,
|
|
46
|
+
"files": 15,
|
|
47
|
+
"children": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"path": "src/stores",
|
|
51
|
+
"depth": 2,
|
|
52
|
+
"dirs": 0,
|
|
53
|
+
"files": 1,
|
|
54
|
+
"children": []
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "src/views",
|
|
58
|
+
"depth": 2,
|
|
59
|
+
"dirs": 0,
|
|
60
|
+
"files": 7,
|
|
61
|
+
"children": []
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
127.0.0.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
13629
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
31416
|