groove-dev 0.27.71 → 0.27.73

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.
Files changed (137) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +38 -27
  4. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +2 -0
  5. package/node_modules/@groove-dev/daemon/src/providers/codex.js +1 -0
  6. package/node_modules/@groove-dev/daemon/src/providers/index.js +3 -3
  7. package/node_modules/@groove-dev/gui/dist/assets/index-BFc7Ov6v.css +1 -0
  8. package/node_modules/@groove-dev/gui/dist/assets/{index-BK6tvmxx.js → index-Deza1S0i.js} +160 -160
  9. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  10. package/node_modules/@groove-dev/gui/package.json +1 -1
  11. package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +5 -5
  12. package/node_modules/@groove-dev/gui/src/components/editor/file-tree.jsx +11 -0
  13. package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +7 -2
  14. package/node_modules/@groove-dev/gui/src/stores/groove.js +46 -54
  15. package/node_modules/@groove-dev/gui/src/views/editor.jsx +4 -3
  16. package/package.json +1 -1
  17. package/packages/cli/package.json +1 -1
  18. package/packages/daemon/package.json +1 -1
  19. package/packages/daemon/src/api.js +38 -27
  20. package/packages/daemon/src/providers/claude-code.js +2 -0
  21. package/packages/daemon/src/providers/codex.js +1 -0
  22. package/packages/daemon/src/providers/index.js +3 -3
  23. package/packages/gui/dist/assets/index-BFc7Ov6v.css +1 -0
  24. package/packages/gui/dist/assets/{index-BK6tvmxx.js → index-Deza1S0i.js} +160 -160
  25. package/packages/gui/dist/index.html +2 -2
  26. package/packages/gui/package.json +1 -1
  27. package/packages/gui/src/components/editor/editor-tabs.jsx +5 -5
  28. package/packages/gui/src/components/editor/file-tree.jsx +11 -0
  29. package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +7 -2
  30. package/packages/gui/src/stores/groove.js +46 -54
  31. package/packages/gui/src/views/editor.jsx +4 -3
  32. package/node_modules/@groove-dev/gui/dist/assets/index-74E3YTkT.css +0 -1
  33. package/packages/gui/dist/assets/index-74E3YTkT.css +0 -1
  34. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js +0 -68
  35. package/packages/gui/node_modules/.vite/deps/@codemirror_autocomplete.js.map +0 -7
  36. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js +0 -1420
  37. package/packages/gui/node_modules/.vite/deps/@codemirror_commands.js.map +0 -7
  38. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js +0 -17
  39. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-css.js.map +0 -7
  40. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +0 -22
  41. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js.map +0 -7
  42. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +0 -34
  43. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js.map +0 -7
  44. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js +0 -101
  45. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-json.js.map +0 -7
  46. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +0 -2534
  47. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js.map +0 -7
  48. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +0 -789
  49. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js.map +0 -7
  50. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js +0 -115
  51. package/packages/gui/node_modules/.vite/deps/@codemirror_language.js.map +0 -7
  52. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js +0 -1136
  53. package/packages/gui/node_modules/.vite/deps/@codemirror_search.js.map +0 -7
  54. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js +0 -63
  55. package/packages/gui/node_modules/.vite/deps/@codemirror_state.js.map +0 -7
  56. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js +0 -179
  57. package/packages/gui/node_modules/.vite/deps/@codemirror_theme-one-dark.js.map +0 -7
  58. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js +0 -104
  59. package/packages/gui/node_modules/.vite/deps/@codemirror_view.js.map +0 -7
  60. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-context-menu.js +0 -1202
  61. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-context-menu.js.map +0 -7
  62. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +0 -365
  63. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js.map +0 -7
  64. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +0 -757
  65. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js.map +0 -7
  66. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +0 -219
  67. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js.map +0 -7
  68. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +0 -562
  69. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js.map +0 -7
  70. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js +0 -46
  71. package/packages/gui/node_modules/.vite/deps/@xterm_addon-fit.js.map +0 -7
  72. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js +0 -121
  73. package/packages/gui/node_modules/.vite/deps/@xterm_addon-web-links.js.map +0 -7
  74. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js +0 -9237
  75. package/packages/gui/node_modules/.vite/deps/@xterm_xterm.js.map +0 -7
  76. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js +0 -9934
  77. package/packages/gui/node_modules/.vite/deps/@xyflow_react.js.map +0 -7
  78. package/packages/gui/node_modules/.vite/deps/_metadata.json +0 -259
  79. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js +0 -5169
  80. package/packages/gui/node_modules/.vite/deps/chunk-3EE34IFC.js.map +0 -7
  81. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js +0 -2000
  82. package/packages/gui/node_modules/.vite/deps/chunk-3IB5EUP7.js.map +0 -7
  83. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js +0 -1776
  84. package/packages/gui/node_modules/.vite/deps/chunk-44CLUOQE.js.map +0 -7
  85. package/packages/gui/node_modules/.vite/deps/chunk-4JHIKDUF.js +0 -435
  86. package/packages/gui/node_modules/.vite/deps/chunk-4JHIKDUF.js.map +0 -7
  87. package/packages/gui/node_modules/.vite/deps/chunk-ACEP5X2F.js +0 -292
  88. package/packages/gui/node_modules/.vite/deps/chunk-ACEP5X2F.js.map +0 -7
  89. package/packages/gui/node_modules/.vite/deps/chunk-CDLO3JFW.js +0 -1004
  90. package/packages/gui/node_modules/.vite/deps/chunk-CDLO3JFW.js.map +0 -7
  91. package/packages/gui/node_modules/.vite/deps/chunk-FOR2E6IR.js +0 -105
  92. package/packages/gui/node_modules/.vite/deps/chunk-FOR2E6IR.js.map +0 -7
  93. package/packages/gui/node_modules/.vite/deps/chunk-HPXOV7QR.js +0 -23
  94. package/packages/gui/node_modules/.vite/deps/chunk-HPXOV7QR.js.map +0 -7
  95. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js +0 -1062
  96. package/packages/gui/node_modules/.vite/deps/chunk-HVFOBSCQ.js.map +0 -7
  97. package/packages/gui/node_modules/.vite/deps/chunk-PGL3TTJA.js +0 -1115
  98. package/packages/gui/node_modules/.vite/deps/chunk-PGL3TTJA.js.map +0 -7
  99. package/packages/gui/node_modules/.vite/deps/chunk-PGMATLPZ.js +0 -2294
  100. package/packages/gui/node_modules/.vite/deps/chunk-PGMATLPZ.js.map +0 -7
  101. package/packages/gui/node_modules/.vite/deps/chunk-PR4QN5HX.js +0 -42
  102. package/packages/gui/node_modules/.vite/deps/chunk-PR4QN5HX.js.map +0 -7
  103. package/packages/gui/node_modules/.vite/deps/chunk-QX3PPTYO.js +0 -315
  104. package/packages/gui/node_modules/.vite/deps/chunk-QX3PPTYO.js.map +0 -7
  105. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js +0 -10985
  106. package/packages/gui/node_modules/.vite/deps/chunk-RE2FU7ZU.js.map +0 -7
  107. package/packages/gui/node_modules/.vite/deps/chunk-SPKVQGZX.js +0 -701
  108. package/packages/gui/node_modules/.vite/deps/chunk-SPKVQGZX.js.map +0 -7
  109. package/packages/gui/node_modules/.vite/deps/chunk-YAD7VOVN.js +0 -280
  110. package/packages/gui/node_modules/.vite/deps/chunk-YAD7VOVN.js.map +0 -7
  111. package/packages/gui/node_modules/.vite/deps/chunk-YEUURE4V.js +0 -264
  112. package/packages/gui/node_modules/.vite/deps/chunk-YEUURE4V.js.map +0 -7
  113. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js +0 -3459
  114. package/packages/gui/node_modules/.vite/deps/chunk-YYJMNVCJ.js.map +0 -7
  115. package/packages/gui/node_modules/.vite/deps/chunk-Z5NIFSCT.js +0 -1106
  116. package/packages/gui/node_modules/.vite/deps/chunk-Z5NIFSCT.js.map +0 -7
  117. package/packages/gui/node_modules/.vite/deps/clsx.js +0 -22
  118. package/packages/gui/node_modules/.vite/deps/clsx.js.map +0 -7
  119. package/packages/gui/node_modules/.vite/deps/framer-motion.js +0 -12732
  120. package/packages/gui/node_modules/.vite/deps/framer-motion.js.map +0 -7
  121. package/packages/gui/node_modules/.vite/deps/lucide-react.js +0 -29653
  122. package/packages/gui/node_modules/.vite/deps/lucide-react.js.map +0 -7
  123. package/packages/gui/node_modules/.vite/deps/package.json +0 -3
  124. package/packages/gui/node_modules/.vite/deps/react-dom.js +0 -6
  125. package/packages/gui/node_modules/.vite/deps/react-dom.js.map +0 -7
  126. package/packages/gui/node_modules/.vite/deps/react-dom_client.js +0 -20217
  127. package/packages/gui/node_modules/.vite/deps/react-dom_client.js.map +0 -7
  128. package/packages/gui/node_modules/.vite/deps/react.js +0 -5
  129. package/packages/gui/node_modules/.vite/deps/react.js.map +0 -7
  130. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js +0 -278
  131. package/packages/gui/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +0 -7
  132. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js +0 -6
  133. package/packages/gui/node_modules/.vite/deps/react_jsx-runtime.js.map +0 -7
  134. package/packages/gui/node_modules/.vite/deps/tailwind-merge.js +0 -3263
  135. package/packages/gui/node_modules/.vite/deps/tailwind-merge.js.map +0 -7
  136. package/packages/gui/node_modules/.vite/deps/zustand.js +0 -56
  137. package/packages/gui/node_modules/.vite/deps/zustand.js.map +0 -7
@@ -6,12 +6,12 @@
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-BK6tvmxx.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-Deza1S0i.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-C0HXlhrU.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-BQPfi37R.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-BBL3i_JW.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-74E3YTkT.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BFc7Ov6v.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/gui",
3
- "version": "0.27.71",
3
+ "version": "0.27.73",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -55,14 +55,14 @@ export function EditorTabs() {
55
55
  if (openTabs.length === 0) return null;
56
56
 
57
57
  return (
58
- <div className="flex items-center h-8 bg-surface-3 border-b border-border-subtle flex-shrink-0">
58
+ <div className="flex items-stretch h-9 bg-surface-3 border-b border-border-subtle flex-shrink-0">
59
59
  {overflows && (
60
60
  <button onClick={scrollLeft} className="flex-shrink-0 px-1 h-full text-text-4 hover:text-text-1 hover:bg-surface-4 transition-colors cursor-pointer">
61
61
  <ChevronLeft size={14} />
62
62
  </button>
63
63
  )}
64
64
 
65
- <div ref={scrollRef} className="flex items-center flex-1 min-w-0 overflow-x-auto scrollbar-none scroll-smooth" style={{ scrollSnapType: 'x mandatory' }}>
65
+ <div ref={scrollRef} className="flex items-stretch flex-1 min-w-0 overflow-x-auto scrollbar-none scroll-smooth" style={{ scrollSnapType: 'x mandatory' }}>
66
66
  {openTabs.map((path) => {
67
67
  const isActive = path === activeFile;
68
68
  const file = files[path];
@@ -74,12 +74,12 @@ export function EditorTabs() {
74
74
  <ContextMenuTrigger asChild>
75
75
  <div
76
76
  className={cn(
77
- 'flex items-center gap-1.5 h-full px-3 text-xs font-sans cursor-pointer select-none',
77
+ 'flex items-center gap-1.5 px-3 text-xs font-sans cursor-pointer select-none',
78
78
  'border-r border-white/5',
79
79
  'transition-colors duration-75 flex-shrink-0',
80
80
  isActive
81
- ? 'bg-surface-1 text-text-0'
82
- : 'bg-surface-3 text-text-4 hover:text-text-1 hover:bg-surface-4',
81
+ ? 'bg-surface-0 text-text-0 border-b border-b-accent'
82
+ : 'bg-surface-3 text-text-4 hover:text-text-1 hover:bg-surface-4 border-b border-b-transparent',
83
83
  )}
84
84
  style={{ scrollSnapAlign: 'start' }}
85
85
  onClick={() => setActiveFile(path)}
@@ -431,6 +431,17 @@ export function FileTree({ rootDir }) {
431
431
  )}
432
432
  </div>
433
433
  ))}
434
+ {rootEntries.length === 0 && !filter && (
435
+ <div className="px-3 py-6 text-center">
436
+ <p className="text-xs text-text-4">No files found</p>
437
+ <button
438
+ onClick={() => fetchTreeDir('')}
439
+ className="mt-2 text-xs text-accent hover:underline cursor-pointer"
440
+ >
441
+ Retry
442
+ </button>
443
+ </div>
444
+ )}
434
445
  </div>
435
446
  </ScrollArea>
436
447
 
@@ -123,7 +123,7 @@ function InstallStep({ providerId, meta }) {
123
123
  'text-2xs font-sans',
124
124
  hasError ? 'text-danger' : isDone ? 'text-success' : 'text-text-3',
125
125
  )}>
126
- {hasError ? 'Something went wrong' : isDone ? 'Installed successfully' : progress?.message || 'Preparing...'}
126
+ {hasError ? (typeof hasError === 'string' ? hasError : 'Something went wrong') : isDone ? 'Installed successfully' : progress?.message || 'Preparing...'}
127
127
  </span>
128
128
  {isInstalling && (
129
129
  <button
@@ -145,7 +145,12 @@ function InstallStep({ providerId, meta }) {
145
145
 
146
146
  {hasError && (
147
147
  <div className="space-y-3">
148
- <p className="text-xs text-text-2 font-sans">Make sure you are connected to the internet and try again.</p>
148
+ {typeof hasError === 'string' && hasError.length > 40 && (
149
+ <div className="p-3 bg-surface-0 border border-border-subtle rounded-md max-h-24 overflow-y-auto">
150
+ <p className="text-2xs font-mono text-danger/80 whitespace-pre-wrap break-all">{hasError}</p>
151
+ </div>
152
+ )}
153
+ <p className="text-xs text-text-2 font-sans">Check that npm is in your PATH and try again.</p>
149
154
  <Button
150
155
  variant="primary"
151
156
  size="sm"
@@ -1471,17 +1471,21 @@ export const useGrooveStore = create((set, get) => ({
1471
1471
  projectDir: data.projectDir,
1472
1472
  recentProjects: data.recentProjects || [],
1473
1473
  showProjectPicker: isHome || (data.recentProjects || []).length === 0,
1474
+ editorTreeCache: {},
1474
1475
  });
1475
1476
  } catch {}
1476
1477
  },
1477
1478
 
1478
1479
  async setProjectDir(path) {
1479
1480
  const data = await api.post('/project-dir', { path });
1481
+ try { await api.post('/files/root', { root: data.projectDir }); } catch {}
1480
1482
  set({
1481
1483
  projectDir: data.projectDir,
1482
1484
  recentProjects: data.recentProjects || [],
1483
1485
  showProjectPicker: false,
1486
+ editorTreeCache: {},
1484
1487
  });
1488
+ get().fetchTreeDir('');
1485
1489
  },
1486
1490
 
1487
1491
  toggleProjectPicker() {
@@ -1652,12 +1656,15 @@ export const useGrooveStore = create((set, get) => ({
1652
1656
  providerInstallProgress: {},
1653
1657
 
1654
1658
  async installProvider(providerId) {
1655
- set((s) => ({
1659
+ const update = (patch) => set((s) => ({
1656
1660
  providerInstallProgress: {
1657
1661
  ...s.providerInstallProgress,
1658
- [providerId]: { installing: true, percent: 0, message: 'Starting install...', error: null, done: false },
1662
+ [providerId]: { ...s.providerInstallProgress[providerId], ...patch },
1659
1663
  },
1660
1664
  }));
1665
+
1666
+ update({ installing: true, percent: 0, message: 'Starting install...', error: null, done: false });
1667
+
1661
1668
  try {
1662
1669
  const res = await fetch(`/api/providers/${encodeURIComponent(providerId)}/install`, {
1663
1670
  method: 'POST',
@@ -1667,59 +1674,41 @@ export const useGrooveStore = create((set, get) => ({
1667
1674
  const err = await res.text();
1668
1675
  throw new Error(err || `Install failed (${res.status})`);
1669
1676
  }
1670
- const ct = res.headers.get('content-type') || '';
1671
- if (ct.includes('ndjson') || ct.includes('octet-stream') || ct.includes('chunked')) {
1672
- const reader = res.body.getReader();
1673
- const decoder = new TextDecoder();
1674
- let buf = '';
1675
- let lastError = null;
1676
- while (true) {
1677
- const { done, value } = await reader.read();
1678
- if (done) break;
1679
- buf += decoder.decode(value, { stream: true });
1680
- const lines = buf.split('\n');
1681
- buf = lines.pop();
1682
- for (const line of lines) {
1683
- if (!line.trim()) continue;
1684
- try {
1685
- const ev = JSON.parse(line);
1686
- const isError = ev.status === 'error';
1687
- const isDone = ev.status === 'complete';
1688
- if (isError) lastError = ev.output || 'Install failed';
1689
- set((s) => ({
1690
- providerInstallProgress: {
1691
- ...s.providerInstallProgress,
1692
- [providerId]: {
1693
- ...s.providerInstallProgress[providerId],
1694
- percent: ev.progress ?? s.providerInstallProgress[providerId]?.percent ?? 0,
1695
- message: ev.output || s.providerInstallProgress[providerId]?.message,
1696
- error: isError ? (ev.output || 'Install failed') : null,
1697
- done: isDone,
1698
- installing: !isDone && !isError,
1699
- },
1700
- },
1701
- }));
1702
- } catch { /* skip malformed NDJSON line */ }
1703
- }
1704
- }
1705
- if (lastError) throw new Error(lastError);
1677
+
1678
+ let body;
1679
+ try {
1680
+ body = await res.text();
1681
+ } catch (e) {
1682
+ throw new Error(`Failed to read response: ${e.message}`);
1706
1683
  }
1707
- const progress = get().providerInstallProgress[providerId];
1708
- if (progress?.error) throw new Error(progress.error);
1709
- set((s) => ({
1710
- providerInstallProgress: {
1711
- ...s.providerInstallProgress,
1712
- [providerId]: { installing: false, percent: 100, message: 'Installed', error: null, done: true },
1713
- },
1714
- }));
1684
+
1685
+ let lastError = null;
1686
+ let completed = false;
1687
+ for (const line of body.split('\n')) {
1688
+ if (!line.trim()) continue;
1689
+ try {
1690
+ const ev = JSON.parse(line);
1691
+ const isError = ev.status === 'error';
1692
+ const isDone = ev.status === 'complete';
1693
+ if (isError) lastError = ev.output || 'Install failed';
1694
+ if (isDone) completed = true;
1695
+ update({
1696
+ percent: ev.progress ?? get().providerInstallProgress[providerId]?.percent ?? 0,
1697
+ message: ev.output || get().providerInstallProgress[providerId]?.message,
1698
+ error: isError ? (ev.output || 'Install failed') : null,
1699
+ done: isDone,
1700
+ installing: !isDone && !isError,
1701
+ });
1702
+ } catch { /* skip malformed line */ }
1703
+ }
1704
+
1705
+ if (lastError) throw new Error(lastError);
1706
+ if (!completed) throw new Error(body.slice(0, 500) || 'Install ended without confirmation');
1707
+
1708
+ update({ installing: false, percent: 100, message: 'Installed', error: null, done: true });
1715
1709
  get().addToast('success', `${providerId} installed`);
1716
1710
  } catch (err) {
1717
- set((s) => ({
1718
- providerInstallProgress: {
1719
- ...s.providerInstallProgress,
1720
- [providerId]: { installing: false, percent: 0, message: null, error: err.message, done: false },
1721
- },
1722
- }));
1711
+ update({ installing: false, percent: 0, message: null, error: err.message, done: false });
1723
1712
  get().addToast('error', `Install failed: ${providerId}`, err.message);
1724
1713
  throw err;
1725
1714
  }
@@ -2082,8 +2071,11 @@ export const useGrooveStore = create((set, get) => ({
2082
2071
  async fetchTreeDir(dirPath) {
2083
2072
  try {
2084
2073
  const data = await api.get(`/files/tree?path=${encodeURIComponent(dirPath)}`);
2085
- set((s) => ({ editorTreeCache: { ...s.editorTreeCache, [dirPath]: data.entries } }));
2086
- } catch { /* ignore */ }
2074
+ set((s) => ({ editorTreeCache: { ...s.editorTreeCache, [dirPath]: data.entries || [] } }));
2075
+ } catch (err) {
2076
+ console.error('[file-tree] fetchTreeDir failed for', dirPath, err.message);
2077
+ set((s) => ({ editorTreeCache: { ...s.editorTreeCache, [dirPath]: [] } }));
2078
+ }
2087
2079
  },
2088
2080
 
2089
2081
  async createFile(relPath) {
@@ -33,6 +33,7 @@ export default function EditorView() {
33
33
  const sidebarWidth = useGrooveStore((s) => s.editorSidebarWidth);
34
34
  const setSidebarWidth = useGrooveStore((s) => s.setEditorSidebarWidth);
35
35
 
36
+ const projectDir = useGrooveStore((s) => s.projectDir);
36
37
  const [rootDir, setRootDir] = useState('');
37
38
  const [previewMode, setPreviewMode] = useState(false);
38
39
  const [previewKey, setPreviewKey] = useState(0);
@@ -44,10 +45,10 @@ export default function EditorView() {
44
45
  const startX = useRef(0);
45
46
  const startW = useRef(0);
46
47
 
47
- // Fetch root dir
48
+ // Fetch root dir on mount and when project directory changes
48
49
  useEffect(() => {
49
50
  api.get('/files/root').then((d) => setRootDir(d.root || '')).catch(() => {});
50
- }, []);
51
+ }, [projectDir]);
51
52
 
52
53
  // Reset preview mode when switching files
53
54
  useEffect(() => { setPreviewMode(false); }, [activeFile]);
@@ -118,7 +119,7 @@ export default function EditorView() {
118
119
  </div>
119
120
 
120
121
  {/* Editor area */}
121
- <div className="flex-1 flex flex-col min-w-0">
122
+ <div className="flex-1 flex flex-col min-w-0 bg-surface-0">
122
123
  {/* Tab bar */}
123
124
  <EditorTabs />
124
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.71",
3
+ "version": "0.27.73",
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@groove-dev/cli",
3
- "version": "0.27.71",
3
+ "version": "0.27.73",
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.73",
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
 
@@ -5650,20 +5658,23 @@ Keep responses concise. Help them think, don't lecture them about the system the
5650
5658
  } catch { /* non-fatal */ }
5651
5659
  };
5652
5660
 
5653
- // Serve GUI static files (built GUI) — no-cache headers to prevent stale bundles
5661
+ // Serve GUI static files (built GUI) — no-cache headers prevent stale bundles on SSH reconnect
5654
5662
  const guiPath = process.env.GROOVE_GUI_PATH || resolve(__dirname, '../../gui/dist');
5655
- app.use(express.static(guiPath, { etag: false, maxAge: 0, lastModified: false }));
5656
- app.use((req, res, next) => {
5657
- if (!req.path.startsWith('/api/')) {
5663
+ app.use(express.static(guiPath, {
5664
+ etag: false,
5665
+ maxAge: 0,
5666
+ lastModified: false,
5667
+ setHeaders: (res) => {
5658
5668
  res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
5659
5669
  res.set('Pragma', 'no-cache');
5660
- }
5661
- next();
5662
- });
5670
+ },
5671
+ }));
5663
5672
 
5664
5673
  // SPA fallback
5665
5674
  app.get('*', (req, res, next) => {
5666
5675
  if (req.path.startsWith('/api/')) return next();
5676
+ res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
5677
+ res.set('Pragma', 'no-cache');
5667
5678
  res.sendFile(resolve(guiPath, 'index.html'), (err) => {
5668
5679
  if (err) res.status(404).json({ error: 'GUI not built yet. Run: npm run build:gui' });
5669
5680
  });
@@ -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) {