groove-dev 0.12.5 → 0.12.7
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/node_modules/@groove-dev/daemon/src/api.js +47 -1
- package/node_modules/@groove-dev/daemon/src/firstrun.js +25 -13
- package/node_modules/@groove-dev/gui/dist/assets/index-D4-7VM-v.js +73 -0
- package/node_modules/@groove-dev/gui/dist/index.html +1 -1
- package/node_modules/@groove-dev/gui/src/components/DirPicker.jsx +209 -0
- package/node_modules/@groove-dev/gui/src/components/SpawnPanel.jsx +53 -46
- package/package.json +1 -1
- package/packages/daemon/src/api.js +47 -1
- package/packages/daemon/src/firstrun.js +25 -13
- package/packages/gui/dist/assets/index-D4-7VM-v.js +73 -0
- package/packages/gui/dist/index.html +1 -1
- package/packages/gui/src/components/DirPicker.jsx +209 -0
- package/packages/gui/src/components/SpawnPanel.jsx +53 -46
- package/node_modules/@groove-dev/gui/dist/assets/index-B49YqEXS.js +0 -73
- package/packages/gui/dist/assets/index-B49YqEXS.js +0 -73
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>GROOVE</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D4-7VM-v.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-Gfb8Zxy9.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// GROOVE GUI — Directory Picker
|
|
2
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
3
|
+
|
|
4
|
+
import React, { useState, useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
export default function DirPicker({ onSelect, onClose, initial }) {
|
|
7
|
+
const [currentPath, setCurrentPath] = useState(initial || '');
|
|
8
|
+
const [dirs, setDirs] = useState([]);
|
|
9
|
+
const [parent, setParent] = useState(null);
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [error, setError] = useState('');
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
browse(currentPath);
|
|
15
|
+
}, [currentPath]);
|
|
16
|
+
|
|
17
|
+
async function browse(path) {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
setError('');
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch(`/api/browse?path=${encodeURIComponent(path)}`);
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const err = await res.json().catch(() => ({}));
|
|
24
|
+
setError(err.error || 'Failed to browse');
|
|
25
|
+
setDirs([]);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
setDirs(data.dirs);
|
|
30
|
+
setParent(data.parent);
|
|
31
|
+
} catch {
|
|
32
|
+
setError('Failed to connect');
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const breadcrumbs = currentPath
|
|
39
|
+
? ['root', ...currentPath.split('/')]
|
|
40
|
+
: ['root'];
|
|
41
|
+
|
|
42
|
+
function handleBreadcrumbClick(index) {
|
|
43
|
+
if (index === 0) {
|
|
44
|
+
setCurrentPath('');
|
|
45
|
+
} else {
|
|
46
|
+
const parts = currentPath.split('/');
|
|
47
|
+
setCurrentPath(parts.slice(0, index).join('/'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div style={styles.overlay} onClick={onClose}>
|
|
53
|
+
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
|
54
|
+
<div style={styles.header}>
|
|
55
|
+
<span style={styles.title}>SELECT DIRECTORY</span>
|
|
56
|
+
<button onClick={onClose} style={styles.closeBtn}>x</button>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
{/* Breadcrumb */}
|
|
60
|
+
<div style={styles.breadcrumb}>
|
|
61
|
+
{breadcrumbs.map((part, i) => (
|
|
62
|
+
<span key={i}>
|
|
63
|
+
{i > 0 && <span style={styles.breadSep}>/</span>}
|
|
64
|
+
<button
|
|
65
|
+
onClick={() => handleBreadcrumbClick(i)}
|
|
66
|
+
style={{
|
|
67
|
+
...styles.breadPart,
|
|
68
|
+
color: i === breadcrumbs.length - 1 ? 'var(--text-bright)' : 'var(--text-dim)',
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
{part}
|
|
72
|
+
</button>
|
|
73
|
+
</span>
|
|
74
|
+
))}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{/* Directory list */}
|
|
78
|
+
<div style={styles.list}>
|
|
79
|
+
{parent !== null && (
|
|
80
|
+
<button
|
|
81
|
+
onClick={() => setCurrentPath(parent)}
|
|
82
|
+
style={styles.dirRow}
|
|
83
|
+
>
|
|
84
|
+
<span style={styles.dirIcon}>..</span>
|
|
85
|
+
<span style={styles.dirName}>parent directory</span>
|
|
86
|
+
</button>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
{loading && <div style={styles.empty}>loading...</div>}
|
|
90
|
+
{error && <div style={styles.errorText}>{error}</div>}
|
|
91
|
+
|
|
92
|
+
{!loading && !error && dirs.length === 0 && (
|
|
93
|
+
<div style={styles.empty}>no subdirectories</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{dirs.map((dir) => (
|
|
97
|
+
<button
|
|
98
|
+
key={dir.path}
|
|
99
|
+
onClick={() => setCurrentPath(dir.path)}
|
|
100
|
+
style={styles.dirRow}
|
|
101
|
+
>
|
|
102
|
+
<span style={styles.dirIcon}>{dir.hasChildren ? '+' : ' '}</span>
|
|
103
|
+
<span style={styles.dirName}>{dir.name}</span>
|
|
104
|
+
</button>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Footer — select current */}
|
|
109
|
+
<div style={styles.footer}>
|
|
110
|
+
<div style={styles.selectedPath}>
|
|
111
|
+
{currentPath || '(project root)'}
|
|
112
|
+
</div>
|
|
113
|
+
<button
|
|
114
|
+
onClick={() => { onSelect(currentPath); onClose(); }}
|
|
115
|
+
style={styles.selectBtn}
|
|
116
|
+
>
|
|
117
|
+
Select
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const styles = {
|
|
126
|
+
overlay: {
|
|
127
|
+
position: 'fixed', inset: 0, zIndex: 200,
|
|
128
|
+
background: 'rgba(0, 0, 0, 0.6)',
|
|
129
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
130
|
+
},
|
|
131
|
+
modal: {
|
|
132
|
+
width: 360, maxHeight: '70vh',
|
|
133
|
+
background: 'var(--bg-chrome)',
|
|
134
|
+
border: '1px solid var(--border)',
|
|
135
|
+
borderRadius: 4,
|
|
136
|
+
display: 'flex', flexDirection: 'column',
|
|
137
|
+
fontFamily: 'var(--font)',
|
|
138
|
+
},
|
|
139
|
+
header: {
|
|
140
|
+
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
|
|
141
|
+
padding: '10px 12px',
|
|
142
|
+
borderBottom: '1px solid var(--border)',
|
|
143
|
+
},
|
|
144
|
+
title: {
|
|
145
|
+
fontSize: 11, fontWeight: 600, color: 'var(--text-dim)',
|
|
146
|
+
textTransform: 'uppercase', letterSpacing: 1.5,
|
|
147
|
+
},
|
|
148
|
+
closeBtn: {
|
|
149
|
+
background: 'none', border: 'none', color: 'var(--text-dim)',
|
|
150
|
+
fontSize: 14, cursor: 'pointer', fontFamily: 'var(--font)',
|
|
151
|
+
padding: '0 4px',
|
|
152
|
+
},
|
|
153
|
+
breadcrumb: {
|
|
154
|
+
padding: '8px 12px',
|
|
155
|
+
borderBottom: '1px solid var(--border)',
|
|
156
|
+
fontSize: 11,
|
|
157
|
+
},
|
|
158
|
+
breadSep: {
|
|
159
|
+
color: 'var(--text-muted)', margin: '0 2px',
|
|
160
|
+
},
|
|
161
|
+
breadPart: {
|
|
162
|
+
background: 'none', border: 'none', cursor: 'pointer',
|
|
163
|
+
fontFamily: 'var(--font)', fontSize: 11,
|
|
164
|
+
padding: 0,
|
|
165
|
+
},
|
|
166
|
+
list: {
|
|
167
|
+
flex: 1, overflowY: 'auto',
|
|
168
|
+
padding: '4px 0',
|
|
169
|
+
minHeight: 120, maxHeight: 300,
|
|
170
|
+
},
|
|
171
|
+
dirRow: {
|
|
172
|
+
display: 'flex', alignItems: 'center', gap: 6,
|
|
173
|
+
width: '100%', padding: '6px 12px',
|
|
174
|
+
background: 'none', border: 'none',
|
|
175
|
+
color: 'var(--text-primary)', fontSize: 12,
|
|
176
|
+
fontFamily: 'var(--font)', cursor: 'pointer',
|
|
177
|
+
textAlign: 'left',
|
|
178
|
+
},
|
|
179
|
+
dirIcon: {
|
|
180
|
+
color: 'var(--accent)', fontSize: 11, width: 12, textAlign: 'center',
|
|
181
|
+
flexShrink: 0,
|
|
182
|
+
},
|
|
183
|
+
dirName: {
|
|
184
|
+
flex: 1,
|
|
185
|
+
},
|
|
186
|
+
empty: {
|
|
187
|
+
padding: '16px 12px', color: 'var(--text-dim)', fontSize: 11,
|
|
188
|
+
textAlign: 'center',
|
|
189
|
+
},
|
|
190
|
+
errorText: {
|
|
191
|
+
padding: '16px 12px', color: 'var(--red)', fontSize: 11,
|
|
192
|
+
textAlign: 'center',
|
|
193
|
+
},
|
|
194
|
+
footer: {
|
|
195
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
196
|
+
padding: '10px 12px',
|
|
197
|
+
borderTop: '1px solid var(--border)',
|
|
198
|
+
},
|
|
199
|
+
selectedPath: {
|
|
200
|
+
flex: 1, fontSize: 11, color: 'var(--text-dim)',
|
|
201
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
202
|
+
},
|
|
203
|
+
selectBtn: {
|
|
204
|
+
padding: '5px 14px',
|
|
205
|
+
background: 'transparent', border: '1px solid var(--accent)',
|
|
206
|
+
borderRadius: 2, color: 'var(--accent)', fontSize: 11, fontWeight: 600,
|
|
207
|
+
fontFamily: 'var(--font)', cursor: 'pointer', flexShrink: 0,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import React, { useState, useEffect } from 'react';
|
|
5
5
|
import { useGrooveStore } from '../stores/groove';
|
|
6
|
+
import DirPicker from './DirPicker';
|
|
6
7
|
|
|
7
8
|
const ROLE_PRESETS = [
|
|
8
9
|
{ id: 'backend', label: 'Backend', desc: 'APIs, server logic, database', scope: ['src/api/**', 'src/server/**', 'src/lib/**', 'src/db/**'] },
|
|
@@ -39,6 +40,7 @@ export default function SpawnPanel() {
|
|
|
39
40
|
const [connectingProvider, setConnectingProvider] = useState(null);
|
|
40
41
|
const [apiKeyInput, setApiKeyInput] = useState('');
|
|
41
42
|
const [keySaving, setKeySaving] = useState(false);
|
|
43
|
+
const [showDirPicker, setShowDirPicker] = useState(false);
|
|
42
44
|
|
|
43
45
|
useEffect(() => {
|
|
44
46
|
fetchProviders();
|
|
@@ -210,40 +212,51 @@ export default function SpawnPanel() {
|
|
|
210
212
|
rows={3}
|
|
211
213
|
/>
|
|
212
214
|
|
|
213
|
-
{/*
|
|
214
|
-
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
215
|
+
{/* Directory picker */}
|
|
216
|
+
<div style={styles.label}>DIRECTORY</div>
|
|
217
|
+
<div style={styles.wsRow}>
|
|
218
|
+
<button
|
|
219
|
+
type="button"
|
|
220
|
+
onClick={() => setWorkingDir('')}
|
|
221
|
+
style={{
|
|
222
|
+
...styles.wsBtn,
|
|
223
|
+
...(!workingDir ? { borderColor: 'var(--accent)', color: 'var(--text-bright)' } : {}),
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
project root
|
|
227
|
+
</button>
|
|
228
|
+
{workspaces.map((ws) => (
|
|
229
|
+
<button
|
|
230
|
+
key={ws.path}
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => setWorkingDir(ws.path)}
|
|
233
|
+
style={{
|
|
234
|
+
...styles.wsBtn,
|
|
235
|
+
...(workingDir === ws.path ? { borderColor: 'var(--accent)', color: 'var(--text-bright)' } : {}),
|
|
236
|
+
}}
|
|
237
|
+
title={`${ws.name} (${ws.files} files)`}
|
|
238
|
+
>
|
|
239
|
+
{ws.path}
|
|
240
|
+
</button>
|
|
241
|
+
))}
|
|
242
|
+
<button
|
|
243
|
+
type="button"
|
|
244
|
+
onClick={() => setShowDirPicker(true)}
|
|
245
|
+
style={styles.browseBtn}
|
|
246
|
+
>
|
|
247
|
+
Browse...
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
{workingDir && (
|
|
251
|
+
<div style={styles.hint}>{workingDir}</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{showDirPicker && (
|
|
255
|
+
<DirPicker
|
|
256
|
+
initial={workingDir}
|
|
257
|
+
onSelect={(path) => setWorkingDir(path)}
|
|
258
|
+
onClose={() => setShowDirPicker(false)}
|
|
259
|
+
/>
|
|
247
260
|
)}
|
|
248
261
|
|
|
249
262
|
{/* Permissions */}
|
|
@@ -279,18 +292,6 @@ export default function SpawnPanel() {
|
|
|
279
292
|
|
|
280
293
|
{showAdvanced && (
|
|
281
294
|
<>
|
|
282
|
-
{/* Working directory — manual input for custom paths */}
|
|
283
|
-
<div style={styles.label}>WORKING DIRECTORY</div>
|
|
284
|
-
<input
|
|
285
|
-
style={styles.input}
|
|
286
|
-
placeholder="e.g. packages/frontend (default: project root)"
|
|
287
|
-
value={workingDir}
|
|
288
|
-
onChange={(e) => setWorkingDir(e.target.value)}
|
|
289
|
-
/>
|
|
290
|
-
<div style={styles.hint}>
|
|
291
|
-
Relative path — or use the directory buttons above
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
295
|
{/* Provider selector with connection flow */}
|
|
295
296
|
<div style={styles.label}>PROVIDER</div>
|
|
296
297
|
{providerList.map((p) => {
|
|
@@ -571,6 +572,12 @@ const styles = {
|
|
|
571
572
|
fontFamily: 'var(--font)',
|
|
572
573
|
transition: 'color 0.1s, border-color 0.1s',
|
|
573
574
|
},
|
|
575
|
+
browseBtn: {
|
|
576
|
+
background: 'none', border: '1px dashed var(--border)',
|
|
577
|
+
borderRadius: 2, padding: '3px 8px',
|
|
578
|
+
color: 'var(--text-dim)', fontSize: 10, cursor: 'pointer',
|
|
579
|
+
fontFamily: 'var(--font)',
|
|
580
|
+
},
|
|
574
581
|
error: {
|
|
575
582
|
color: 'var(--red)', fontSize: 11, marginTop: 8,
|
|
576
583
|
},
|