groove-dev 0.12.6 → 0.12.8

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.
@@ -4,7 +4,7 @@
4
4
  import express from 'express';
5
5
  import { resolve, dirname } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
- import { existsSync, readFileSync } from 'fs';
7
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
8
8
  import { listProviders } from './providers/index.js';
9
9
  import { validateAgentConfig } from './validate.js';
10
10
 
@@ -365,6 +365,66 @@ export function createApi(app, daemon) {
365
365
  res.json(daemon.adaptive.getAllProfiles());
366
366
  });
367
367
 
368
+ // --- Directory Browser ---
369
+
370
+ app.get('/api/browse', (req, res) => {
371
+ const relPath = req.query.path || '';
372
+
373
+ // Security: no absolute paths, no traversal
374
+ if (relPath.startsWith('/') || relPath.includes('..') || relPath.includes('\0')) {
375
+ return res.status(400).json({ error: 'Invalid path' });
376
+ }
377
+
378
+ const fullPath = relPath ? resolve(daemon.projectDir, relPath) : daemon.projectDir;
379
+
380
+ // Must stay within project directory
381
+ if (!fullPath.startsWith(daemon.projectDir)) {
382
+ return res.status(400).json({ error: 'Path outside project' });
383
+ }
384
+
385
+ if (!existsSync(fullPath)) {
386
+ return res.status(404).json({ error: 'Directory not found' });
387
+ }
388
+
389
+ try {
390
+ const entries = readdirSync(fullPath, { withFileTypes: true })
391
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules')
392
+ .sort((a, b) => a.name.localeCompare(b.name))
393
+ .map((e) => {
394
+ const childPath = relPath ? `${relPath}/${e.name}` : e.name;
395
+ const childFull = resolve(fullPath, e.name);
396
+ let hasChildren = false;
397
+ let childCount = 0;
398
+ let fileCount = 0;
399
+ try {
400
+ const children = readdirSync(childFull, { withFileTypes: true });
401
+ for (const c of children) {
402
+ if (c.name.startsWith('.') || c.name === 'node_modules') continue;
403
+ if (c.isDirectory()) { childCount++; hasChildren = true; }
404
+ else fileCount++;
405
+ }
406
+ } catch { /* unreadable */ }
407
+ return { name: e.name, path: childPath, hasChildren, childCount, fileCount };
408
+ });
409
+
410
+ // Count files in current dir
411
+ let currentFiles = 0;
412
+ try {
413
+ currentFiles = readdirSync(fullPath, { withFileTypes: true })
414
+ .filter((e) => e.isFile() && !e.name.startsWith('.')).length;
415
+ } catch { /* ignore */ }
416
+
417
+ res.json({
418
+ current: relPath || '.',
419
+ parent: relPath ? relPath.split('/').slice(0, -1).join('/') : null,
420
+ dirs: entries,
421
+ fileCount: currentFiles,
422
+ });
423
+ } catch (err) {
424
+ res.status(500).json({ error: err.message });
425
+ }
426
+ });
427
+
368
428
  // --- Codebase Indexer ---
369
429
 
370
430
  app.get('/api/indexer', (req, res) => {