groove-dev 0.27.7 → 0.27.11
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/daemon/src/api.js +496 -44
- package/node_modules/@groove-dev/daemon/src/gateways/manager.js +25 -12
- package/node_modules/@groove-dev/daemon/src/index.js +7 -0
- package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
- package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
- package/node_modules/@groove-dev/daemon/src/process.js +128 -104
- package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
- package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
- package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
- package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
- package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
- package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
- package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
- package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
- package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/src/app.css +14 -0
- package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
- package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
- package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +16 -17
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +8 -8
- package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
- package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
- package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
- package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
- package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
- package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
- package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
- package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
- package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
- package/node_modules/@groove-dev/gui/src/lib/electron.js +17 -0
- package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
- package/node_modules/@groove-dev/gui/src/stores/groove.js +150 -6
- package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +39 -40
- package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
- package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
- package/node_modules/@groove-dev/gui/vite.config.js +3 -0
- package/package.json +7 -2
- package/packages/daemon/src/api.js +496 -44
- package/packages/daemon/src/gateways/manager.js +25 -12
- package/packages/daemon/src/index.js +7 -0
- package/packages/daemon/src/introducer.js +72 -4
- package/packages/daemon/src/journalist.js +66 -11
- package/packages/daemon/src/process.js +128 -104
- package/packages/daemon/src/registry.js +1 -1
- package/packages/daemon/src/repo-import.js +541 -0
- package/packages/daemon/src/rotator.js +28 -1
- package/packages/daemon/src/supervisor.js +2 -1
- package/packages/daemon/src/tunnel-manager.js +504 -0
- package/packages/daemon/src/validate.js +13 -0
- package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
- package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
- package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
- package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
- package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
- package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
- package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
- package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
- package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
- package/packages/gui/src/app.css +14 -0
- package/packages/gui/src/app.jsx +13 -0
- package/packages/gui/src/components/agents/agent-config.jsx +130 -1
- package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
- package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
- package/packages/gui/src/components/agents/agent-node.jsx +16 -17
- package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
- package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
- package/packages/gui/src/components/dashboard/intel-panel.jsx +8 -8
- package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
- package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
- package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
- package/packages/gui/src/components/layout/app-shell.jsx +7 -1
- package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
- package/packages/gui/src/components/layout/command-palette.jsx +14 -4
- package/packages/gui/src/components/layout/status-bar.jsx +46 -11
- package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
- package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
- package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
- package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
- package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
- package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
- package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
- package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
- package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
- package/packages/gui/src/components/ui/toast.jsx +1 -1
- package/packages/gui/src/lib/edition.js +4 -0
- package/packages/gui/src/lib/electron.js +17 -0
- package/packages/gui/src/lib/status.js +1 -0
- package/packages/gui/src/stores/groove.js +150 -6
- package/packages/gui/src/views/dashboard.jsx +39 -40
- package/packages/gui/src/views/marketplace.jsx +82 -0
- package/packages/gui/src/views/settings.jsx +66 -0
- package/packages/gui/vite.config.js +3 -0
- package/node_modules/@groove-dev/gui/dist/assets/index-Bl1_J0sN.js +0 -652
- package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +0 -1
- package/packages/gui/dist/assets/index-Bl1_J0sN.js +0 -652
- package/packages/gui/dist/assets/index-DjORRpF0.css +0 -1
- package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
- package/test-slack.mjs +0 -28
- /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
- /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState, useCallback } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Search, GitBranch, Star, ExternalLink, Loader2, Check,
|
|
5
|
+
FolderOpen, HardDrive, Package, PenLine, Users, ArrowLeft, Download,
|
|
6
|
+
} from 'lucide-react';
|
|
7
|
+
import { Button } from '../ui/button';
|
|
8
|
+
import { Badge } from '../ui/badge';
|
|
9
|
+
import { cn } from '../../lib/cn';
|
|
10
|
+
import { fmtNum } from '../../lib/format';
|
|
11
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
12
|
+
import { useToast } from '../../lib/hooks/use-toast';
|
|
13
|
+
|
|
14
|
+
const GITHUB_RE = /github\.com\/([^/]+)\/([^/\s#?]+)/;
|
|
15
|
+
|
|
16
|
+
export function RepoImport() {
|
|
17
|
+
const [step, setStep] = useState('input');
|
|
18
|
+
const [url, setUrl] = useState('');
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
const [preview, setPreview] = useState(null);
|
|
21
|
+
const [pathOption, setPathOption] = useState('standalone');
|
|
22
|
+
const [customPath, setCustomPath] = useState('');
|
|
23
|
+
const [createTeam, setCreateTeam] = useState(true);
|
|
24
|
+
const [teamName, setTeamName] = useState('');
|
|
25
|
+
|
|
26
|
+
const previewRepo = useGrooveStore((s) => s.previewRepo);
|
|
27
|
+
const importRepo = useGrooveStore((s) => s.importRepo);
|
|
28
|
+
const importInProgress = useGrooveStore((s) => s.importInProgress);
|
|
29
|
+
const toast = useToast();
|
|
30
|
+
|
|
31
|
+
const doPreview = useCallback(async (repoUrl) => {
|
|
32
|
+
const match = repoUrl.match(GITHUB_RE);
|
|
33
|
+
if (!match) return;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
try {
|
|
36
|
+
const data = await previewRepo(repoUrl);
|
|
37
|
+
setPreview(data);
|
|
38
|
+
setTeamName(data.name || match[2]);
|
|
39
|
+
setStep('preview');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
toast.error('Preview failed', err.message);
|
|
42
|
+
} finally {
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
}, [previewRepo, toast]);
|
|
46
|
+
|
|
47
|
+
const handleUrlChange = useCallback((e) => {
|
|
48
|
+
const val = e.target.value;
|
|
49
|
+
setUrl(val);
|
|
50
|
+
if (GITHUB_RE.test(val) && step === 'input') {
|
|
51
|
+
doPreview(val);
|
|
52
|
+
}
|
|
53
|
+
}, [step, doPreview]);
|
|
54
|
+
|
|
55
|
+
const handleImport = useCallback(async () => {
|
|
56
|
+
if (!preview) return;
|
|
57
|
+
let targetPath;
|
|
58
|
+
if (pathOption === 'standalone') targetPath = `~/Projects/${preview.name}`;
|
|
59
|
+
else if (pathOption === 'subdirectory') targetPath = `./packages/${preview.name}`;
|
|
60
|
+
else targetPath = customPath;
|
|
61
|
+
try {
|
|
62
|
+
await importRepo(url, targetPath, createTeam, teamName);
|
|
63
|
+
toast.success(`Importing ${preview.name}`, 'Setup agent will handle the rest');
|
|
64
|
+
setStep('input');
|
|
65
|
+
setUrl('');
|
|
66
|
+
setPreview(null);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
toast.error('Import failed', err.message);
|
|
69
|
+
}
|
|
70
|
+
}, [preview, pathOption, customPath, url, createTeam, teamName, importRepo, toast]);
|
|
71
|
+
|
|
72
|
+
// ── Step 1: URL Input ──────────────────────────────────
|
|
73
|
+
if (step === 'input') {
|
|
74
|
+
return (
|
|
75
|
+
<div className="relative">
|
|
76
|
+
<Search size={14} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-4 pointer-events-none" />
|
|
77
|
+
<input
|
|
78
|
+
type="text"
|
|
79
|
+
value={url}
|
|
80
|
+
onChange={handleUrlChange}
|
|
81
|
+
placeholder="Paste a GitHub URL..."
|
|
82
|
+
className={cn(
|
|
83
|
+
'w-full h-9 rounded-lg pl-9 pr-20 text-sm font-sans',
|
|
84
|
+
'bg-surface-1 border border-border text-text-0',
|
|
85
|
+
'placeholder:text-text-4',
|
|
86
|
+
'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
|
|
87
|
+
'transition-colors duration-100',
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
<Button
|
|
91
|
+
variant="primary"
|
|
92
|
+
size="sm"
|
|
93
|
+
className="absolute right-1.5 top-1/2 -translate-y-1/2"
|
|
94
|
+
onClick={() => doPreview(url)}
|
|
95
|
+
disabled={!GITHUB_RE.test(url) || loading}
|
|
96
|
+
>
|
|
97
|
+
{loading ? <Loader2 size={12} className="animate-spin" /> : 'Preview'}
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Step 2: Preview ────────────────────────────────────
|
|
104
|
+
if (step === 'preview' && preview) {
|
|
105
|
+
return (
|
|
106
|
+
<div className="space-y-4">
|
|
107
|
+
<div className="rounded-lg border border-border-subtle bg-surface-2 p-5">
|
|
108
|
+
<div className="flex items-start gap-4">
|
|
109
|
+
<div className="w-12 h-12 rounded-xl bg-accent/8 flex items-center justify-center flex-shrink-0">
|
|
110
|
+
<GitBranch size={22} className="text-accent" />
|
|
111
|
+
</div>
|
|
112
|
+
<div className="flex-1 min-w-0">
|
|
113
|
+
<div className="flex items-center gap-2.5 mb-1">
|
|
114
|
+
<span className="text-lg font-bold text-text-0 font-sans">{preview.name}</span>
|
|
115
|
+
<span className="text-xs text-text-4 font-sans">{preview.owner}</span>
|
|
116
|
+
</div>
|
|
117
|
+
<div className="flex items-center gap-3 text-2xs text-text-3 font-sans">
|
|
118
|
+
{preview.language && <Badge variant="outline" className="text-2xs">{preview.language}</Badge>}
|
|
119
|
+
{preview.stars != null && (
|
|
120
|
+
<span className="flex items-center gap-1">
|
|
121
|
+
<Star size={10} className="text-warning" fill="currentColor" />
|
|
122
|
+
{fmtNum(preview.stars)}
|
|
123
|
+
</span>
|
|
124
|
+
)}
|
|
125
|
+
{preview.license && <span>{preview.license}</span>}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
129
|
+
<Button variant="primary" size="sm" onClick={() => setStep('configure')} className="h-8 text-xs gap-1.5 px-4">
|
|
130
|
+
<FolderOpen size={13} />
|
|
131
|
+
Clone & Setup
|
|
132
|
+
</Button>
|
|
133
|
+
<Button
|
|
134
|
+
variant="ghost"
|
|
135
|
+
size="sm"
|
|
136
|
+
onClick={() => window.open(url.startsWith('http') ? url : `https://${url}`, '_blank')}
|
|
137
|
+
className="h-8 text-xs gap-1.5"
|
|
138
|
+
>
|
|
139
|
+
<ExternalLink size={12} />
|
|
140
|
+
GitHub
|
|
141
|
+
</Button>
|
|
142
|
+
<button
|
|
143
|
+
onClick={() => { setStep('input'); setPreview(null); }}
|
|
144
|
+
className="text-2xs text-text-4 font-sans hover:text-text-2 cursor-pointer bg-transparent border-0 ml-1"
|
|
145
|
+
>
|
|
146
|
+
Cancel
|
|
147
|
+
</button>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{preview.description && (
|
|
153
|
+
<div className="rounded-lg border border-border-subtle bg-surface-1 px-5 py-4">
|
|
154
|
+
<h4 className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-2">About</h4>
|
|
155
|
+
<p className="text-sm text-text-1 font-sans leading-relaxed">{preview.description}</p>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{preview.readmePreview && (
|
|
160
|
+
<div className="rounded-lg border border-border-subtle bg-surface-1 px-5 py-4">
|
|
161
|
+
<h4 className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-3">README</h4>
|
|
162
|
+
<div className="text-sm text-text-2 font-sans leading-relaxed whitespace-pre-wrap">{preview.readmePreview}</div>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Step 3: Configure ──────────────────────────────────
|
|
170
|
+
if (step === 'configure' && preview) {
|
|
171
|
+
const pathOptions = [
|
|
172
|
+
{
|
|
173
|
+
id: 'standalone',
|
|
174
|
+
icon: HardDrive,
|
|
175
|
+
title: 'Standalone project',
|
|
176
|
+
description: 'Clone to its own directory, separate from this workspace',
|
|
177
|
+
path: `~/Projects/${preview.name}`,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'subdirectory',
|
|
181
|
+
icon: Package,
|
|
182
|
+
title: 'Workspace package',
|
|
183
|
+
description: 'Add as a package inside this project\'s monorepo',
|
|
184
|
+
path: `./packages/${preview.name}`,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'custom',
|
|
188
|
+
icon: PenLine,
|
|
189
|
+
title: 'Custom location',
|
|
190
|
+
description: 'Choose your own path',
|
|
191
|
+
path: null,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<div className="rounded-xl border border-border-subtle bg-surface-2 overflow-hidden">
|
|
197
|
+
{/* Header */}
|
|
198
|
+
<div className="px-5 py-4 border-b border-border-subtle bg-surface-3/50">
|
|
199
|
+
<div className="flex items-center gap-3">
|
|
200
|
+
<div className="w-9 h-9 rounded-lg bg-accent/10 flex items-center justify-center flex-shrink-0">
|
|
201
|
+
<Download size={16} className="text-accent" />
|
|
202
|
+
</div>
|
|
203
|
+
<div>
|
|
204
|
+
<h3 className="text-sm font-semibold text-text-0 font-sans">
|
|
205
|
+
Clone {preview.name}
|
|
206
|
+
</h3>
|
|
207
|
+
<p className="text-2xs text-text-4 font-sans mt-0.5">
|
|
208
|
+
{preview.owner}/{preview.name} — configure where to install
|
|
209
|
+
</p>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
<div className="px-5 py-4 space-y-5">
|
|
215
|
+
{/* Location picker */}
|
|
216
|
+
<div>
|
|
217
|
+
<label className="text-2xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-2.5 block">
|
|
218
|
+
Install location
|
|
219
|
+
</label>
|
|
220
|
+
<div className="space-y-2">
|
|
221
|
+
{pathOptions.map((opt) => {
|
|
222
|
+
const Icon = opt.icon;
|
|
223
|
+
const selected = pathOption === opt.id;
|
|
224
|
+
return (
|
|
225
|
+
<button
|
|
226
|
+
key={opt.id}
|
|
227
|
+
onClick={() => setPathOption(opt.id)}
|
|
228
|
+
className={cn(
|
|
229
|
+
'w-full text-left rounded-lg border p-3.5 transition-all duration-150 cursor-pointer',
|
|
230
|
+
selected
|
|
231
|
+
? 'border-accent bg-accent/5 ring-1 ring-accent/30'
|
|
232
|
+
: 'border-border-subtle bg-surface-1 hover:border-border hover:bg-surface-1/80',
|
|
233
|
+
)}
|
|
234
|
+
>
|
|
235
|
+
<div className="flex items-start gap-3">
|
|
236
|
+
<div className={cn(
|
|
237
|
+
'w-8 h-8 rounded-md flex items-center justify-center flex-shrink-0 mt-0.5',
|
|
238
|
+
selected ? 'bg-accent/15 text-accent' : 'bg-surface-3 text-text-4',
|
|
239
|
+
)}>
|
|
240
|
+
<Icon size={15} />
|
|
241
|
+
</div>
|
|
242
|
+
<div className="flex-1 min-w-0">
|
|
243
|
+
<div className="flex items-center gap-2">
|
|
244
|
+
<span className={cn(
|
|
245
|
+
'text-xs font-semibold font-sans',
|
|
246
|
+
selected ? 'text-text-0' : 'text-text-2',
|
|
247
|
+
)}>
|
|
248
|
+
{opt.title}
|
|
249
|
+
</span>
|
|
250
|
+
{selected && (
|
|
251
|
+
<div className="w-4 h-4 rounded-full bg-accent flex items-center justify-center">
|
|
252
|
+
<Check size={10} className="text-white" />
|
|
253
|
+
</div>
|
|
254
|
+
)}
|
|
255
|
+
</div>
|
|
256
|
+
<p className="text-2xs text-text-4 font-sans mt-0.5 leading-relaxed">
|
|
257
|
+
{opt.description}
|
|
258
|
+
</p>
|
|
259
|
+
{opt.path && selected && (
|
|
260
|
+
<code className="text-2xs text-accent/80 font-mono mt-1.5 block truncate">
|
|
261
|
+
{opt.path}
|
|
262
|
+
</code>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</button>
|
|
267
|
+
);
|
|
268
|
+
})}
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* Custom path input */}
|
|
272
|
+
{pathOption === 'custom' && (
|
|
273
|
+
<div className="mt-2.5 ml-11">
|
|
274
|
+
<input
|
|
275
|
+
value={customPath}
|
|
276
|
+
onChange={(e) => setCustomPath(e.target.value)}
|
|
277
|
+
placeholder="/path/to/clone"
|
|
278
|
+
autoFocus
|
|
279
|
+
className={cn(
|
|
280
|
+
'w-full h-9 px-3 text-xs font-mono rounded-md',
|
|
281
|
+
'bg-surface-0 border border-border text-text-0',
|
|
282
|
+
'placeholder:text-text-4',
|
|
283
|
+
'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
|
|
284
|
+
'transition-colors',
|
|
285
|
+
)}
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
{/* Team toggle */}
|
|
292
|
+
<div className="border-t border-border-subtle pt-4">
|
|
293
|
+
<div className="flex items-center justify-between">
|
|
294
|
+
<div className="flex items-center gap-2.5">
|
|
295
|
+
<div className="w-8 h-8 rounded-md bg-surface-3 flex items-center justify-center">
|
|
296
|
+
<Users size={14} className="text-text-4" />
|
|
297
|
+
</div>
|
|
298
|
+
<div>
|
|
299
|
+
<span className="text-xs font-semibold text-text-2 font-sans block">Create a team</span>
|
|
300
|
+
<span className="text-2xs text-text-4 font-sans">Organize agents working on this repo into their own team</span>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
<button
|
|
304
|
+
onClick={() => setCreateTeam(!createTeam)}
|
|
305
|
+
className={cn(
|
|
306
|
+
'w-9 h-5 rounded-full p-0.5 transition-colors cursor-pointer flex-shrink-0',
|
|
307
|
+
createTeam ? 'bg-accent' : 'bg-surface-5',
|
|
308
|
+
)}
|
|
309
|
+
>
|
|
310
|
+
<div className={cn(
|
|
311
|
+
'w-4 h-4 rounded-full bg-white shadow-sm transition-transform',
|
|
312
|
+
createTeam ? 'translate-x-4' : 'translate-x-0',
|
|
313
|
+
)} />
|
|
314
|
+
</button>
|
|
315
|
+
</div>
|
|
316
|
+
{createTeam && (
|
|
317
|
+
<div className="mt-2.5 ml-11">
|
|
318
|
+
<input
|
|
319
|
+
value={teamName}
|
|
320
|
+
onChange={(e) => setTeamName(e.target.value)}
|
|
321
|
+
placeholder="Team name"
|
|
322
|
+
className={cn(
|
|
323
|
+
'w-full h-9 px-3 text-xs font-sans rounded-md',
|
|
324
|
+
'bg-surface-0 border border-border text-text-0',
|
|
325
|
+
'placeholder:text-text-4',
|
|
326
|
+
'focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent',
|
|
327
|
+
'transition-colors',
|
|
328
|
+
)}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
{/* Footer */}
|
|
336
|
+
<div className="px-5 py-3.5 border-t border-border-subtle bg-surface-3/30 flex items-center justify-between">
|
|
337
|
+
<button
|
|
338
|
+
onClick={() => setStep('preview')}
|
|
339
|
+
className="flex items-center gap-1.5 text-2xs text-text-4 font-sans hover:text-text-2 cursor-pointer bg-transparent border-0 transition-colors"
|
|
340
|
+
>
|
|
341
|
+
<ArrowLeft size={11} />
|
|
342
|
+
Back
|
|
343
|
+
</button>
|
|
344
|
+
<Button
|
|
345
|
+
variant="primary"
|
|
346
|
+
size="sm"
|
|
347
|
+
onClick={handleImport}
|
|
348
|
+
disabled={importInProgress || (pathOption === 'custom' && !customPath.trim())}
|
|
349
|
+
className="h-8 text-xs gap-1.5 px-5"
|
|
350
|
+
>
|
|
351
|
+
{importInProgress ? (
|
|
352
|
+
<><Loader2 size={12} className="animate-spin" /> Importing...</>
|
|
353
|
+
) : (
|
|
354
|
+
<><Download size={12} /> Clone & Setup</>
|
|
355
|
+
)}
|
|
356
|
+
</Button>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { AlertTriangle } from 'lucide-react';
|
|
4
|
+
import { Dialog, DialogContent } from '../ui/dialog';
|
|
5
|
+
import { Button } from '../ui/button';
|
|
6
|
+
|
|
7
|
+
export function RepoNukeDialog({ repo, open, onClose, onConfirm }) {
|
|
8
|
+
const [deleteFiles, setDeleteFiles] = useState(true);
|
|
9
|
+
|
|
10
|
+
if (!repo) return null;
|
|
11
|
+
|
|
12
|
+
const agents = repo.agents?.length || 0;
|
|
13
|
+
const processes = repo.processes?.length || 0;
|
|
14
|
+
const credentials = repo.credentialKeys?.length || 0;
|
|
15
|
+
const fileCount = repo.fileCount || 0;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Dialog open={open} onOpenChange={(v) => { if (!v) onClose(); }}>
|
|
19
|
+
<DialogContent title={`Nuke ${repo.repoName || repo.name}?`} description="Confirm destructive removal of imported repo">
|
|
20
|
+
<div className="px-5 py-4 space-y-4">
|
|
21
|
+
<div className="flex items-start gap-2">
|
|
22
|
+
<AlertTriangle size={16} className="text-danger flex-shrink-0 mt-0.5" />
|
|
23
|
+
<p className="text-sm text-text-1 font-sans">This cannot be undone.</p>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className="space-y-1.5 text-xs text-text-2 font-sans">
|
|
27
|
+
{agents > 0 && <div className="flex items-center gap-2">
|
|
28
|
+
<span className="text-success">✓</span> Kill {agents} agent{agents !== 1 ? 's' : ''}
|
|
29
|
+
</div>}
|
|
30
|
+
{processes > 0 && <div className="flex items-center gap-2">
|
|
31
|
+
<span className="text-success">✓</span> Stop {processes} process{processes !== 1 ? 'es' : ''}
|
|
32
|
+
</div>}
|
|
33
|
+
{credentials > 0 && <div className="flex items-center gap-2">
|
|
34
|
+
<span className="text-success">✓</span> Remove {credentials} credential{credentials !== 1 ? 's' : ''}
|
|
35
|
+
</div>}
|
|
36
|
+
<div className="flex items-center gap-2">
|
|
37
|
+
<span className="text-success">✓</span> Delete team "{repo.teamId || repo.repoName || repo.name}"
|
|
38
|
+
</div>
|
|
39
|
+
<div className="flex items-center gap-2">
|
|
40
|
+
<span className="text-success">✓</span> Clean all .groove state
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
45
|
+
<input
|
|
46
|
+
type="checkbox"
|
|
47
|
+
checked={deleteFiles}
|
|
48
|
+
onChange={(e) => setDeleteFiles(e.target.checked)}
|
|
49
|
+
className="accent-[var(--color-danger)]"
|
|
50
|
+
/>
|
|
51
|
+
<span className="text-xs text-text-1 font-sans">
|
|
52
|
+
Delete repo files{fileCount > 0 ? ` (${fileCount} files)` : ''}
|
|
53
|
+
</span>
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<div className="flex items-center gap-2 pt-1">
|
|
57
|
+
<Button variant="danger" size="sm" onClick={() => onConfirm(deleteFiles)}>
|
|
58
|
+
Nuke Everything
|
|
59
|
+
</Button>
|
|
60
|
+
<Button variant="ghost" size="sm" onClick={onClose}>
|
|
61
|
+
Cancel
|
|
62
|
+
</Button>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</DialogContent>
|
|
66
|
+
</Dialog>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
3
|
+
import { UpgradeCard } from './upgrade-card';
|
|
4
|
+
|
|
5
|
+
export function ProGate({ feature, description, children }) {
|
|
6
|
+
const authenticated = useGrooveStore((s) => s.marketplaceAuthenticated);
|
|
7
|
+
const user = useGrooveStore((s) => s.marketplaceUser);
|
|
8
|
+
|
|
9
|
+
if (__GROOVE_EDITION__ !== 'pro') {
|
|
10
|
+
return <UpgradeCard feature={feature} description={description} variant="community" />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!authenticated) {
|
|
14
|
+
return <UpgradeCard feature={feature} description={description} variant="sign-in" />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!user?.subscription?.active) {
|
|
18
|
+
return <UpgradeCard feature={feature} description={description} variant="subscribe" />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return children;
|
|
22
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { Lock, Download, LogIn, Sparkles } from 'lucide-react';
|
|
3
|
+
import { openExternal } from '../../lib/electron';
|
|
4
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
5
|
+
|
|
6
|
+
const VARIANTS = {
|
|
7
|
+
community: {
|
|
8
|
+
heading: 'Get Groove Desktop',
|
|
9
|
+
cta: 'Download',
|
|
10
|
+
icon: Download,
|
|
11
|
+
action: () => openExternal('https://groovedev.ai/download'),
|
|
12
|
+
},
|
|
13
|
+
'sign-in': {
|
|
14
|
+
heading: 'Sign in to unlock',
|
|
15
|
+
cta: 'Sign in',
|
|
16
|
+
icon: LogIn,
|
|
17
|
+
action: () => useGrooveStore.getState().marketplaceLogin(),
|
|
18
|
+
},
|
|
19
|
+
subscribe: {
|
|
20
|
+
heading: 'Upgrade to Pro',
|
|
21
|
+
cta: 'Subscribe',
|
|
22
|
+
icon: Sparkles,
|
|
23
|
+
action: () => openExternal('https://groovedev.ai/pro'),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function UpgradeCard({ feature, description, variant = 'community' }) {
|
|
28
|
+
const v = VARIANTS[variant] || VARIANTS.community;
|
|
29
|
+
const CtaIcon = v.icon;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="rounded-lg border border-border-subtle bg-surface-1/50 px-5 py-6 text-center">
|
|
33
|
+
<div className="mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-purple/10">
|
|
34
|
+
<Lock size={18} className="text-purple" />
|
|
35
|
+
</div>
|
|
36
|
+
<h3 className="text-sm font-semibold text-text-1 font-sans">{v.heading}</h3>
|
|
37
|
+
<p className="mt-1.5 text-2xs text-text-3 font-sans">{feature}</p>
|
|
38
|
+
<p className="mt-1 text-2xs text-text-4 font-sans max-w-xs mx-auto">{description}</p>
|
|
39
|
+
<button
|
|
40
|
+
onClick={v.action}
|
|
41
|
+
className="mt-4 inline-flex items-center gap-1.5 h-7 px-4 rounded-full bg-purple/15 text-purple text-xs font-semibold font-sans hover:bg-purple/25 transition-colors cursor-pointer"
|
|
42
|
+
>
|
|
43
|
+
<CtaIcon size={13} />
|
|
44
|
+
{v.cta}
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// FSL-1.1-Apache-2.0 — see LICENSE
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useGrooveStore } from '../../stores/groove';
|
|
4
|
+
import { cn } from '../../lib/cn';
|
|
5
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
6
|
+
import {
|
|
7
|
+
Server, Radio, ExternalLink, Loader2, X, Plus, Settings,
|
|
8
|
+
} from 'lucide-react';
|
|
9
|
+
import { StatusDot } from '../ui/status-dot';
|
|
10
|
+
|
|
11
|
+
export function QuickConnect() {
|
|
12
|
+
const open = useGrooveStore((s) => s.quickConnectOpen);
|
|
13
|
+
const toggle = useGrooveStore((s) => s.toggleQuickConnect);
|
|
14
|
+
const savedTunnels = useGrooveStore((s) => s.savedTunnels);
|
|
15
|
+
const [connectingId, setConnectingId] = useState(null);
|
|
16
|
+
|
|
17
|
+
if (!open) return null;
|
|
18
|
+
|
|
19
|
+
async function handleConnect(id) {
|
|
20
|
+
setConnectingId(id);
|
|
21
|
+
try {
|
|
22
|
+
await useGrooveStore.getState().connectTunnel(id);
|
|
23
|
+
toggle();
|
|
24
|
+
} catch {}
|
|
25
|
+
setConnectingId(null);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleOpenRemote(server) {
|
|
29
|
+
const port = server.localPort;
|
|
30
|
+
const name = encodeURIComponent(server.name);
|
|
31
|
+
window.open(`http://localhost:${port}?instance=${name}`, '_blank');
|
|
32
|
+
toggle();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<div className="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm" onClick={toggle} />
|
|
38
|
+
|
|
39
|
+
<AnimatePresence>
|
|
40
|
+
<motion.div
|
|
41
|
+
initial={{ opacity: 0, y: -20, scale: 0.96 }}
|
|
42
|
+
animate={{ opacity: 1, y: 0, scale: 1 }}
|
|
43
|
+
exit={{ opacity: 0, y: -10, scale: 0.98 }}
|
|
44
|
+
transition={{ duration: 0.15 }}
|
|
45
|
+
className="fixed top-[20%] left-1/2 -translate-x-1/2 z-50 w-[400px] bg-surface-1 border border-border rounded-lg shadow-2xl overflow-hidden"
|
|
46
|
+
>
|
|
47
|
+
{/* Header */}
|
|
48
|
+
<div className="flex items-center justify-between px-4 py-3 border-b border-border-subtle">
|
|
49
|
+
<div className="flex items-center gap-2">
|
|
50
|
+
<Radio size={15} className="text-accent" />
|
|
51
|
+
<span className="text-sm font-semibold text-text-0 font-sans">Quick Connect</span>
|
|
52
|
+
</div>
|
|
53
|
+
<button onClick={toggle} className="p-1 text-text-4 hover:text-text-1 cursor-pointer transition-colors">
|
|
54
|
+
<X size={14} />
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{/* Server list */}
|
|
59
|
+
<div className="overflow-y-auto max-h-[320px] py-1">
|
|
60
|
+
{savedTunnels.length === 0 ? (
|
|
61
|
+
<div className="px-4 py-8 text-center">
|
|
62
|
+
<Server size={24} className="text-text-4 mx-auto mb-2" />
|
|
63
|
+
<p className="text-sm text-text-3 font-sans">No saved servers</p>
|
|
64
|
+
<p className="text-2xs text-text-4 font-sans mt-1">Add one in Settings to get started.</p>
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => {
|
|
67
|
+
toggle();
|
|
68
|
+
useGrooveStore.getState().setActiveView('settings');
|
|
69
|
+
}}
|
|
70
|
+
className="mt-3 inline-flex items-center gap-1.5 text-xs text-accent hover:text-accent/80 font-sans cursor-pointer transition-colors"
|
|
71
|
+
>
|
|
72
|
+
<Settings size={12} /> Go to Settings
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
) : (
|
|
76
|
+
savedTunnels.map((server) => (
|
|
77
|
+
<button
|
|
78
|
+
key={server.id}
|
|
79
|
+
onClick={() => server.active ? handleOpenRemote(server) : handleConnect(server.id)}
|
|
80
|
+
disabled={connectingId === server.id}
|
|
81
|
+
className={cn(
|
|
82
|
+
'w-full flex items-center gap-3 px-4 py-2.5 text-left cursor-pointer transition-colors',
|
|
83
|
+
'hover:bg-surface-5',
|
|
84
|
+
connectingId === server.id && 'opacity-60 pointer-events-none',
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
<Server size={15} className={server.active ? 'text-success' : 'text-text-4'} />
|
|
88
|
+
<div className="flex-1 min-w-0">
|
|
89
|
+
<div className="flex items-center gap-2">
|
|
90
|
+
<span className="text-sm font-medium text-text-0 font-sans truncate">{server.name}</span>
|
|
91
|
+
{server.active && <StatusDot status="running" size="sm" />}
|
|
92
|
+
</div>
|
|
93
|
+
<span className="text-2xs text-text-4 font-mono">{server.user}@{server.host}</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="flex-shrink-0">
|
|
96
|
+
{connectingId === server.id ? (
|
|
97
|
+
<Loader2 size={14} className="text-text-3 animate-spin" />
|
|
98
|
+
) : server.active ? (
|
|
99
|
+
<span className="flex items-center gap-1 text-2xs text-success font-sans">
|
|
100
|
+
<ExternalLink size={11} /> Open
|
|
101
|
+
</span>
|
|
102
|
+
) : (
|
|
103
|
+
<span className="text-2xs text-text-3 font-sans">Connect</span>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
</button>
|
|
107
|
+
))
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
{/* Footer */}
|
|
112
|
+
{savedTunnels.length > 0 && (
|
|
113
|
+
<div className="px-4 py-2 border-t border-border-subtle">
|
|
114
|
+
<button
|
|
115
|
+
onClick={() => {
|
|
116
|
+
toggle();
|
|
117
|
+
useGrooveStore.getState().setActiveView('settings');
|
|
118
|
+
}}
|
|
119
|
+
className="flex items-center gap-1.5 text-2xs text-text-4 hover:text-text-2 font-sans cursor-pointer transition-colors"
|
|
120
|
+
>
|
|
121
|
+
<Plus size={10} /> Manage servers in Settings
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
</motion.div>
|
|
126
|
+
</AnimatePresence>
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
}
|