groove-dev 0.27.155 → 0.27.156
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/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/routes/files.js +26 -4
- package/node_modules/@groove-dev/gui/dist/assets/{index-BTLb6zTD.js → index-COQYX12F.js} +2 -2
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-file-tree.jsx +12 -14
- package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +6 -8
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/routes/files.js +26 -4
- package/packages/gui/dist/assets/{index-BTLb6zTD.js → index-COQYX12F.js} +2 -2
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/agent-file-tree.jsx +12 -14
- package/packages/gui/src/components/editor/file-tree.jsx +6 -8
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
|
8
8
|
<title>Groove GUI</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-COQYX12F.js"></script>
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/codemirror-BYKpdS2W.js">
|
|
@@ -106,10 +106,10 @@ function ContextMenu({ x, y, items, onClose }) {
|
|
|
106
106
|
);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function downloadFile(path) {
|
|
109
|
+
function downloadFile(path, isDir) {
|
|
110
110
|
const a = document.createElement('a');
|
|
111
111
|
a.href = `/api/files/download?path=${encodeURIComponent(path)}`;
|
|
112
|
-
a.download = path.split('/').pop();
|
|
112
|
+
a.download = isDir ? `${path.split('/').pop()}.zip` : path.split('/').pop();
|
|
113
113
|
document.body.appendChild(a);
|
|
114
114
|
a.click();
|
|
115
115
|
a.remove();
|
|
@@ -138,8 +138,8 @@ function TreeEntry({ entry, depth, onOpen, expandedDirs, onToggleDir, onContextM
|
|
|
138
138
|
onDragStartEntry(entry.path);
|
|
139
139
|
}}
|
|
140
140
|
onDragEnd={onDragEndEntry}
|
|
141
|
-
onDragOver={
|
|
142
|
-
onDrop={
|
|
141
|
+
onDragOver={(e) => { e.preventDefault(); if (isDir) { e.stopPropagation(); onSetDragOver(entry.path); } }}
|
|
142
|
+
onDrop={(e) => onDropOnDir(isDir ? entry.path : (entry.path.includes('/') ? entry.path.split('/').slice(0, -1).join('/') : ''), e)}
|
|
143
143
|
onClick={() => isDir ? onToggleDir(entry.path) : onOpen(entry.path)}
|
|
144
144
|
onDoubleClick={handleCtxMenu}
|
|
145
145
|
onContextMenu={handleCtxMenu}
|
|
@@ -500,11 +500,9 @@ export function AgentFileTree({ agentId, onCollapse }) {
|
|
|
500
500
|
if (isDir) {
|
|
501
501
|
items.push({ icon: FilePlus, label: 'New File', action: () => handleNewFileIn(entry.path) });
|
|
502
502
|
items.push({ icon: FolderPlus, label: 'New Folder', action: () => handleNewFolderIn(entry.path) });
|
|
503
|
-
items.push({ separator: true });
|
|
504
|
-
} else {
|
|
505
|
-
items.push({ icon: Download, label: 'Download', action: () => downloadFile(entry.path) });
|
|
506
|
-
items.push({ separator: true });
|
|
507
503
|
}
|
|
504
|
+
items.push({ icon: Download, label: isDir ? 'Download as ZIP' : 'Download', action: () => downloadFile(entry.path, isDir) });
|
|
505
|
+
items.push({ separator: true });
|
|
508
506
|
items.push({ icon: Pencil, label: 'Rename', action: () => handleRename(entry) });
|
|
509
507
|
items.push({ icon: Trash2, label: 'Delete', danger: true, action: () => handleDelete(entry) });
|
|
510
508
|
return items;
|
|
@@ -533,7 +531,11 @@ export function AgentFileTree({ agentId, onCollapse }) {
|
|
|
533
531
|
)}
|
|
534
532
|
</div>
|
|
535
533
|
<ScrollArea className="flex-1 min-h-0">
|
|
536
|
-
<div
|
|
534
|
+
<div
|
|
535
|
+
className="py-2 min-h-full"
|
|
536
|
+
onDragOver={(e) => { e.preventDefault(); if (dragState.draggingPath) setDragOverDir(null); }}
|
|
537
|
+
onDrop={(e) => handleDropOnDir('', e)}
|
|
538
|
+
>
|
|
537
539
|
{inlineInput && (
|
|
538
540
|
<InlineInput
|
|
539
541
|
placeholder={inlineInput.type === 'file' ? 'filename.ext' : 'folder-name'}
|
|
@@ -595,11 +597,7 @@ export function AgentFileTree({ agentId, onCollapse }) {
|
|
|
595
597
|
No files in scope
|
|
596
598
|
</div>
|
|
597
599
|
) : (
|
|
598
|
-
<div
|
|
599
|
-
className="px-1"
|
|
600
|
-
onDragOver={(e) => { e.preventDefault(); if (dragState.draggingPath) setDragOverDir(null); }}
|
|
601
|
-
onDrop={(e) => handleDropOnDir('', e)}
|
|
602
|
-
>
|
|
600
|
+
<div className="px-1">
|
|
603
601
|
<div className="flex items-center gap-1.5 px-2 py-1.5 text-2xs font-semibold text-text-3 uppercase tracking-wider">
|
|
604
602
|
<Folder size={10} />
|
|
605
603
|
Scope
|
|
@@ -107,10 +107,10 @@ function GitDot({ status }) {
|
|
|
107
107
|
return <span className={cn('w-1.5 h-1.5 rounded-full flex-shrink-0', color)} />;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
function downloadFile(path) {
|
|
110
|
+
function downloadFile(path, isDir) {
|
|
111
111
|
const a = document.createElement('a');
|
|
112
112
|
a.href = `/api/files/download?path=${encodeURIComponent(path)}`;
|
|
113
|
-
a.download = path.split('/').pop();
|
|
113
|
+
a.download = isDir ? `${path.split('/').pop()}.zip` : path.split('/').pop();
|
|
114
114
|
document.body.appendChild(a);
|
|
115
115
|
a.click();
|
|
116
116
|
a.remove();
|
|
@@ -140,8 +140,8 @@ function TreeNode({ entry, depth = 0, activePath, onFileClick, onDirToggle, expa
|
|
|
140
140
|
onDragStartEntry(entry.path);
|
|
141
141
|
}}
|
|
142
142
|
onDragEnd={onDragEndEntry}
|
|
143
|
-
onDragOver={
|
|
144
|
-
onDrop={
|
|
143
|
+
onDragOver={(e) => { e.preventDefault(); if (isDir) { e.stopPropagation(); onSetDragOver(entry.path); } }}
|
|
144
|
+
onDrop={(e) => onDropOnDir(isDir ? entry.path : (entry.path.includes('/') ? entry.path.split('/').slice(0, -1).join('/') : ''), e)}
|
|
145
145
|
onClick={() => isDir ? onDirToggle(entry.path) : onFileClick(entry.path)}
|
|
146
146
|
onDoubleClick={handleContextMenu}
|
|
147
147
|
onContextMenu={handleContextMenu}
|
|
@@ -455,9 +455,7 @@ export function FileTree({ rootDir, onCollapse }) {
|
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
if (entry.name !== 'root') {
|
|
458
|
-
|
|
459
|
-
items.push({ icon: Download, label: 'Download', action: () => downloadFile(entry.path) });
|
|
460
|
-
}
|
|
458
|
+
items.push({ icon: Download, label: isDir ? 'Download as ZIP' : 'Download', action: () => downloadFile(entry.path, isDir) });
|
|
461
459
|
if (items.length > 0) items.push({ separator: true });
|
|
462
460
|
items.push({ icon: Pencil, label: 'Rename', action: () => handleRename(entry) });
|
|
463
461
|
items.push({ icon: Trash2, label: 'Delete', danger: true, action: () => handleDelete(entry) });
|
|
@@ -527,7 +525,7 @@ export function FileTree({ rootDir, onCollapse }) {
|
|
|
527
525
|
{/* Tree */}
|
|
528
526
|
<ScrollArea className="flex-1">
|
|
529
527
|
<div
|
|
530
|
-
className="py-1"
|
|
528
|
+
className="py-1 min-h-full"
|
|
531
529
|
onDragOver={(e) => { e.preventDefault(); if (dragState.draggingPath) setDragOverDir(null); }}
|
|
532
530
|
onDrop={(e) => handleDropOnDir('', e)}
|
|
533
531
|
>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groove-dev",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.156",
|
|
4
4
|
"description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
2
|
import { resolve, sep, isAbsolute, basename } from 'path';
|
|
3
3
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, unlinkSync, renameSync, rmSync, createReadStream, realpathSync } from 'fs';
|
|
4
|
-
import { execFile, execFileSync } from 'child_process';
|
|
4
|
+
import { execFile, execFileSync, spawn } from 'child_process';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { lookup as mimeLookup } from '../mimetypes.js';
|
|
7
7
|
|
|
@@ -331,15 +331,37 @@ export function registerFileRoutes(app, daemon) {
|
|
|
331
331
|
}
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
-
// Download a file (
|
|
334
|
+
// Download a file or folder (folders are streamed as zip)
|
|
335
335
|
app.get('/api/files/download', (req, res) => {
|
|
336
336
|
const relPath = req.query.path;
|
|
337
337
|
const result = validateFilePath(relPath, getEditorRoot(daemon));
|
|
338
338
|
if (result.error) return res.status(400).json({ error: result.error });
|
|
339
|
-
if (!existsSync(result.fullPath)) return res.status(404).json({ error: '
|
|
339
|
+
if (!existsSync(result.fullPath)) return res.status(404).json({ error: 'Not found' });
|
|
340
340
|
|
|
341
341
|
const stat = statSync(result.fullPath);
|
|
342
|
-
|
|
342
|
+
|
|
343
|
+
if (stat.isDirectory()) {
|
|
344
|
+
const folderName = basename(result.fullPath);
|
|
345
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(folderName)}.zip"`);
|
|
346
|
+
res.setHeader('Content-Type', 'application/zip');
|
|
347
|
+
|
|
348
|
+
const zipProc = spawn('zip', [
|
|
349
|
+
'-r', '-q', '-',
|
|
350
|
+
relPath,
|
|
351
|
+
'-x', `${relPath}/.git/*`,
|
|
352
|
+
'-x', `${relPath}/node_modules/*`,
|
|
353
|
+
], {
|
|
354
|
+
cwd: getEditorRoot(daemon),
|
|
355
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
zipProc.stdout.pipe(res);
|
|
359
|
+
zipProc.stderr.on('data', () => {});
|
|
360
|
+
zipProc.on('error', () => {
|
|
361
|
+
if (!res.headersSent) res.status(500).json({ error: 'Failed to create zip' });
|
|
362
|
+
});
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
343
365
|
|
|
344
366
|
const name = basename(result.fullPath);
|
|
345
367
|
const mime = mimeLookup(name) || 'application/octet-stream';
|