groove-dev 0.27.71 → 0.27.72

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.71",
3
+ "version": "0.27.72",
4
4
  "description": "GROOVE CLI — manage AI coding agents from your terminal",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/daemon",
3
- "version": "0.27.71",
3
+ "version": "0.27.72",
4
4
  "description": "GROOVE daemon — agent orchestration engine",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -2,7 +2,7 @@
2
2
  // FSL-1.1-Apache-2.0 — see LICENSE
3
3
 
4
4
  import express from 'express';
5
- import { resolve, dirname, join, sep, relative } from 'path';
5
+ import { resolve, dirname, join, sep, relative, isAbsolute } from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, unlinkSync, renameSync, rmSync, createReadStream, copyFileSync, realpathSync } from 'fs';
8
8
  import { spawn, execFile, execFileSync } from 'child_process';
@@ -611,6 +611,7 @@ export function createApi(app, daemon) {
611
611
 
612
612
  const proc = spawn('npm', ['install', '-g', pkg], {
613
613
  stdio: ['ignore', 'pipe', 'pipe'],
614
+ shell: true,
614
615
  env: { ...process.env, NODE_ENV: undefined },
615
616
  });
616
617
 
@@ -719,6 +720,7 @@ export function createApi(app, daemon) {
719
720
 
720
721
  const proc = spawn('codex', ['login'], {
721
722
  stdio: ['ignore', 'pipe', 'pipe'],
723
+ shell: true,
722
724
  });
723
725
  let stdout = '';
724
726
  let stderr = '';
@@ -763,7 +765,7 @@ export function createApi(app, daemon) {
763
765
  if (customPath.length > 500) {
764
766
  return res.status(400).json({ error: 'Path too long' });
765
767
  }
766
- if (!customPath.startsWith('/')) {
768
+ if (!isAbsolute(customPath)) {
767
769
  return res.status(400).json({ error: 'Path must be absolute' });
768
770
  }
769
771
 
@@ -830,6 +832,7 @@ export function createApi(app, daemon) {
830
832
  encoding: 'utf8',
831
833
  timeout: 5000,
832
834
  stdio: ['pipe', 'pipe', 'pipe'],
835
+ shell: true,
833
836
  }).trim();
834
837
  } catch (err) {
835
838
  version = null;
@@ -1033,7 +1036,7 @@ export function createApi(app, daemon) {
1033
1036
  }
1034
1037
  try {
1035
1038
  daemon.setProjectDir(dirPath);
1036
- editorRootDir = daemon.projectDir;
1039
+ editorRootOverride = null;
1037
1040
  res.json({ projectDir: daemon.projectDir, recentProjects: daemon.config.recentProjects || [] });
1038
1041
  } catch (err) {
1039
1042
  res.status(400).json({ error: err.message });
@@ -2660,12 +2663,13 @@ Keep responses concise. Help them think, don't lecture them about the system the
2660
2663
  return LANG_MAP[ext] || 'text';
2661
2664
  }
2662
2665
 
2663
- const IGNORED_NAMES = new Set(['.git', 'node_modules', '.DS_Store', '.groove', '__pycache__', '.next', '.cache', 'dist', 'coverage']);
2666
+ const IGNORED_NAMES = new Set(['.DS_Store', '__pycache__']);
2664
2667
 
2665
- // Editor root directory — defaults to projectDir but can be changed at runtime
2666
- let editorRootDir = daemon.projectDir;
2668
+ // Editor root directory — always tracks daemon.projectDir unless explicitly
2669
+ // overridden via POST /api/files/root. Reset on project-dir change.
2670
+ let editorRootOverride = null;
2667
2671
 
2668
- function getEditorRoot() { return editorRootDir; }
2672
+ function getEditorRoot() { return editorRootOverride || daemon.projectDir; }
2669
2673
 
2670
2674
  function validateFilePath(relPath, projectDir) {
2671
2675
  if (!relPath || typeof relPath !== 'string') return { error: 'path is required' };
@@ -2689,13 +2693,12 @@ Keep responses concise. Help them think, don't lecture them about the system the
2689
2693
 
2690
2694
  // Get/set the editor working directory
2691
2695
  app.get('/api/files/root', (req, res) => {
2692
- res.json({ root: editorRootDir });
2696
+ res.json({ root: getEditorRoot() });
2693
2697
  });
2694
2698
 
2695
2699
  app.post('/api/files/root', (req, res) => {
2696
2700
  const { root } = req.body || {};
2697
2701
  if (!root || typeof root !== 'string') return res.status(400).json({ error: 'root path is required' });
2698
- // Must be absolute and exist
2699
2702
  if (!root.startsWith('/')) return res.status(400).json({ error: 'root must be an absolute path' });
2700
2703
  if (root.includes('\0') || root.includes('..')) return res.status(400).json({ error: 'Invalid path' });
2701
2704
  if (!existsSync(root)) return res.status(404).json({ error: 'Directory not found' });
@@ -2703,9 +2706,9 @@ Keep responses concise. Help them think, don't lecture them about the system the
2703
2706
  const stat = statSync(root);
2704
2707
  if (!stat.isDirectory()) return res.status(400).json({ error: 'Path is not a directory' });
2705
2708
  } catch { return res.status(400).json({ error: 'Cannot access directory' }); }
2706
- editorRootDir = root;
2709
+ editorRootOverride = root;
2707
2710
  daemon.audit.log('editor.root.set', { root });
2708
- res.json({ ok: true, root: editorRootDir });
2711
+ res.json({ ok: true, root: getEditorRoot() });
2709
2712
  });
2710
2713
 
2711
2714
  // File tree — returns dirs + files for a given path
@@ -2730,18 +2733,22 @@ Keep responses concise. Help them think, don't lecture them about the system the
2730
2733
  const raw = readdirSync(fullPath, { withFileTypes: true });
2731
2734
  const entries = [];
2732
2735
 
2733
- // Dirs first (sorted), then files (sorted)
2734
- const isDir = (e) => {
2736
+ const dirs = raw.filter((e) => {
2737
+ if (e.name === '.DS_Store') return false;
2735
2738
  if (e.isDirectory()) return true;
2736
- if (e.isSymbolicLink()) { try { return statSync(resolve(fullPath, e.name)).isDirectory(); } catch { return false; } }
2739
+ if (e.isSymbolicLink()) {
2740
+ try { return statSync(resolve(fullPath, e.name)).isDirectory(); }
2741
+ catch { return true; }
2742
+ }
2737
2743
  return false;
2738
- };
2739
- const dirs = raw.filter((e) => isDir(e) && !IGNORED_NAMES.has(e.name) && !e.name.startsWith('.'))
2740
- .sort((a, b) => a.name.localeCompare(b.name));
2744
+ }).sort((a, b) => a.name.localeCompare(b.name));
2741
2745
  const files = raw.filter((e) => {
2742
- if (e.name.startsWith('.')) return false;
2746
+ if (e.name === '.DS_Store') return false;
2743
2747
  if (e.isFile()) return true;
2744
- if (e.isSymbolicLink()) { try { return statSync(resolve(fullPath, e.name)).isFile(); } catch { return false; } }
2748
+ if (e.isSymbolicLink()) {
2749
+ try { return statSync(resolve(fullPath, e.name)).isFile(); }
2750
+ catch { return false; }
2751
+ }
2745
2752
  return false;
2746
2753
  }).sort((a, b) => a.name.localeCompare(b.name));
2747
2754
 
@@ -2751,7 +2758,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
2751
2758
  let hasChildren = false;
2752
2759
  try {
2753
2760
  const children = readdirSync(childFull, { withFileTypes: true });
2754
- hasChildren = children.some((c) => !c.name.startsWith('.') && !IGNORED_NAMES.has(c.name));
2761
+ hasChildren = children.some((c) => c.name !== '.DS_Store');
2755
2762
  } catch { /* unreadable */ }
2756
2763
  entries.push({ name: d.name, type: 'dir', path: childPath, hasChildren });
2757
2764
  }
@@ -4077,6 +4084,7 @@ Keep responses concise. Help them think, don't lecture them about the system the
4077
4084
 
4078
4085
  const proc = spawn('npm', ['install', '-g', pkg], {
4079
4086
  stdio: ['ignore', 'pipe', 'pipe'],
4087
+ shell: true,
4080
4088
  env: { ...process.env, NODE_ENV: undefined },
4081
4089
  });
4082
4090
 
@@ -336,6 +336,7 @@ export class ClaudeCodeProvider extends Provider {
336
336
  const child = cpSpawn('claude', ['auth', 'login', '--claudeai'], {
337
337
  detached: true,
338
338
  stdio: 'ignore',
339
+ shell: true,
339
340
  });
340
341
  child.unref();
341
342
  return { pid: child.pid };
@@ -359,6 +360,7 @@ export class ClaudeCodeProvider extends Provider {
359
360
  return new Promise((resolve) => {
360
361
  const child = cpSpawn('claude', ['auth', 'login', '--claudeai'], {
361
362
  stdio: ['ignore', 'pipe', 'pipe'],
363
+ shell: true,
362
364
  });
363
365
  let stdout = '';
364
366
  let stderr = '';
@@ -72,6 +72,7 @@ export class CodexProvider extends Provider {
72
72
  return new Promise((res) => {
73
73
  const proc = spawn('codex', ['login', '--with-api-key'], {
74
74
  stdio: ['pipe', 'pipe', 'pipe'],
75
+ shell: true,
75
76
  timeout: 15000,
76
77
  });
77
78
  let stderr = '';
@@ -2,7 +2,7 @@
2
2
  // FSL-1.1-Apache-2.0 — see LICENSE
3
3
 
4
4
  import { execSync } from 'child_process';
5
- import { dirname as pathDirname } from 'path';
5
+ import { dirname as pathDirname, delimiter as pathDelimiter } from 'path';
6
6
  import { ClaudeCodeProvider } from './claude-code.js';
7
7
  import { CodexProvider } from './codex.js';
8
8
  import { GeminiProvider } from './gemini.js';
@@ -26,10 +26,10 @@ function _augmentPathWithCustomPaths() {
26
26
  for (const p of Object.values(_providerPaths)) {
27
27
  if (p && typeof p === 'string') {
28
28
  const dir = pathDirname(p);
29
- if (dir && !cur.split(':').includes(dir)) dirs.push(dir);
29
+ if (dir && !cur.split(pathDelimiter).includes(dir)) dirs.push(dir);
30
30
  }
31
31
  }
32
- if (dirs.length) process.env.PATH = [...dirs, cur].join(':');
32
+ if (dirs.length) process.env.PATH = [...dirs, cur].join(pathDelimiter);
33
33
  }
34
34
 
35
35
  export function getProviderPath(id) {