groove-dev 0.27.14 → 0.27.17

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 (169) hide show
  1. package/README.md +37 -1
  2. package/developerID_application.cer +0 -0
  3. package/node_modules/@groove-dev/daemon/src/api.js +587 -68
  4. package/node_modules/@groove-dev/daemon/src/classifier.js +24 -0
  5. package/node_modules/@groove-dev/daemon/src/credentials.js +12 -2
  6. package/node_modules/@groove-dev/daemon/src/federation/ambassador.js +204 -0
  7. package/node_modules/@groove-dev/daemon/src/federation/connection.js +359 -0
  8. package/node_modules/@groove-dev/daemon/src/federation/contracts.js +112 -0
  9. package/node_modules/@groove-dev/daemon/src/federation/whitelist.js +190 -0
  10. package/node_modules/@groove-dev/daemon/src/federation.js +166 -7
  11. package/node_modules/@groove-dev/daemon/src/index.js +172 -19
  12. package/node_modules/@groove-dev/daemon/src/introducer.js +52 -7
  13. package/node_modules/@groove-dev/daemon/src/journalist.js +46 -1
  14. package/node_modules/@groove-dev/daemon/src/memory.js +36 -16
  15. package/node_modules/@groove-dev/daemon/src/process.js +140 -23
  16. package/node_modules/@groove-dev/daemon/src/providers/base.js +1 -0
  17. package/node_modules/@groove-dev/daemon/src/providers/claude-code.js +1 -0
  18. package/node_modules/@groove-dev/daemon/src/providers/codex.js +124 -28
  19. package/node_modules/@groove-dev/daemon/src/providers/gemini.js +104 -17
  20. package/node_modules/@groove-dev/daemon/src/providers/index.js +17 -0
  21. package/node_modules/@groove-dev/daemon/src/registry.js +10 -1
  22. package/node_modules/@groove-dev/daemon/src/rotator.js +93 -30
  23. package/node_modules/@groove-dev/daemon/src/skills.js +33 -3
  24. package/node_modules/@groove-dev/daemon/src/terminal-pty.js +9 -1
  25. package/node_modules/@groove-dev/daemon/src/tool-executor.js +11 -5
  26. package/node_modules/@groove-dev/daemon/src/toys.js +69 -0
  27. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +24 -5
  28. package/node_modules/@groove-dev/daemon/templates/toys-catalog.json +242 -0
  29. package/node_modules/@groove-dev/daemon/test/classifier.test.js +98 -0
  30. package/node_modules/@groove-dev/daemon/test/introducer.test.js +72 -1
  31. package/node_modules/@groove-dev/daemon/test/journalist.test.js +117 -0
  32. package/node_modules/@groove-dev/daemon/test/memory.test.js +37 -1
  33. package/node_modules/@groove-dev/daemon/test/rotator.test.js +183 -4
  34. package/node_modules/@groove-dev/gui/dist/assets/index-BglPgjlu.js +8607 -0
  35. package/node_modules/@groove-dev/gui/dist/assets/index-CGcwmmJv.css +1 -0
  36. package/node_modules/@groove-dev/gui/dist/index.html +3 -2
  37. package/node_modules/@groove-dev/gui/index.html +1 -0
  38. package/node_modules/@groove-dev/gui/src/app.css +7 -0
  39. package/node_modules/@groove-dev/gui/src/app.jsx +37 -10
  40. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +21 -31
  41. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +11 -6
  42. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
  43. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +42 -1
  44. package/node_modules/@groove-dev/gui/src/components/editor/breadcrumbs.jsx +30 -0
  45. package/node_modules/@groove-dev/gui/src/components/editor/code-editor.jsx +33 -2
  46. package/node_modules/@groove-dev/gui/src/components/editor/editor-status-bar.jsx +26 -0
  47. package/node_modules/@groove-dev/gui/src/components/editor/editor-tabs.jsx +113 -34
  48. package/node_modules/@groove-dev/gui/src/components/editor/goto-line.jsx +35 -0
  49. package/node_modules/@groove-dev/gui/src/components/editor/terminal.jsx +12 -6
  50. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +13 -3
  51. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +0 -1
  52. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
  53. package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +6 -2
  54. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +10 -9
  55. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +9 -1
  56. package/node_modules/@groove-dev/gui/src/components/onboarding/provider-card.jsx +134 -0
  57. package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +819 -0
  58. package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +12 -5
  59. package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +15 -8
  60. package/node_modules/@groove-dev/gui/src/components/pro/upgrade-modal.jsx +151 -0
  61. package/node_modules/@groove-dev/gui/src/components/settings/federation-activity.jsx +98 -0
  62. package/node_modules/@groove-dev/gui/src/components/settings/federation-panel.jsx +290 -0
  63. package/node_modules/@groove-dev/gui/src/components/settings/federation-peers.jsx +126 -0
  64. package/node_modules/@groove-dev/gui/src/components/settings/federation-wizard.jsx +293 -0
  65. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +110 -67
  66. package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +3 -3
  67. package/node_modules/@groove-dev/gui/src/components/settings/server-detail.jsx +310 -0
  68. package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +4 -1
  69. package/node_modules/@groove-dev/gui/src/components/settings/server-list.jsx +59 -0
  70. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +549 -0
  71. package/node_modules/@groove-dev/gui/src/components/toys/toy-card.jsx +78 -0
  72. package/node_modules/@groove-dev/gui/src/components/toys/toy-creator.jsx +144 -0
  73. package/node_modules/@groove-dev/gui/src/components/toys/toy-launcher.jsx +187 -0
  74. package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +2 -2
  75. package/node_modules/@groove-dev/gui/src/lib/electron.js +15 -0
  76. package/node_modules/@groove-dev/gui/src/lib/format.js +1 -0
  77. package/node_modules/@groove-dev/gui/src/stores/groove.js +373 -58
  78. package/node_modules/@groove-dev/gui/src/views/agents.jsx +148 -42
  79. package/node_modules/@groove-dev/gui/src/views/editor.jsx +92 -2
  80. package/node_modules/@groove-dev/gui/src/views/federation.jsx +37 -0
  81. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +2 -42
  82. package/node_modules/@groove-dev/gui/src/views/settings.jsx +32 -132
  83. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +327 -0
  84. package/node_modules/@groove-dev/gui/src/views/teams.jsx +3 -3
  85. package/node_modules/@groove-dev/gui/src/views/toys.jsx +162 -0
  86. package/package.json +1 -1
  87. package/packages/daemon/src/api.js +587 -68
  88. package/packages/daemon/src/classifier.js +24 -0
  89. package/packages/daemon/src/credentials.js +12 -2
  90. package/packages/daemon/src/federation/ambassador.js +204 -0
  91. package/packages/daemon/src/federation/connection.js +359 -0
  92. package/packages/daemon/src/federation/contracts.js +112 -0
  93. package/packages/daemon/src/federation/whitelist.js +190 -0
  94. package/packages/daemon/src/federation.js +166 -7
  95. package/packages/daemon/src/index.js +172 -19
  96. package/packages/daemon/src/introducer.js +52 -7
  97. package/packages/daemon/src/journalist.js +46 -1
  98. package/packages/daemon/src/memory.js +36 -16
  99. package/packages/daemon/src/process.js +140 -23
  100. package/packages/daemon/src/providers/base.js +1 -0
  101. package/packages/daemon/src/providers/claude-code.js +1 -0
  102. package/packages/daemon/src/providers/codex.js +124 -28
  103. package/packages/daemon/src/providers/gemini.js +104 -17
  104. package/packages/daemon/src/providers/index.js +17 -0
  105. package/packages/daemon/src/registry.js +10 -1
  106. package/packages/daemon/src/rotator.js +93 -30
  107. package/packages/daemon/src/skills.js +33 -3
  108. package/packages/daemon/src/terminal-pty.js +9 -1
  109. package/packages/daemon/src/tool-executor.js +11 -5
  110. package/packages/daemon/src/toys.js +69 -0
  111. package/packages/daemon/src/tunnel-manager.js +24 -5
  112. package/packages/daemon/templates/toys-catalog.json +242 -0
  113. package/packages/gui/dist/assets/index-BglPgjlu.js +8607 -0
  114. package/packages/gui/dist/assets/index-CGcwmmJv.css +1 -0
  115. package/packages/gui/dist/index.html +3 -2
  116. package/packages/gui/index.html +1 -0
  117. package/packages/gui/src/app.css +7 -0
  118. package/packages/gui/src/app.jsx +37 -10
  119. package/packages/gui/src/components/agents/agent-chat.jsx +21 -31
  120. package/packages/gui/src/components/agents/agent-config.jsx +11 -6
  121. package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
  122. package/packages/gui/src/components/agents/spawn-wizard.jsx +42 -1
  123. package/packages/gui/src/components/editor/breadcrumbs.jsx +30 -0
  124. package/packages/gui/src/components/editor/code-editor.jsx +33 -2
  125. package/packages/gui/src/components/editor/editor-status-bar.jsx +26 -0
  126. package/packages/gui/src/components/editor/editor-tabs.jsx +113 -34
  127. package/packages/gui/src/components/editor/goto-line.jsx +35 -0
  128. package/packages/gui/src/components/editor/terminal.jsx +12 -6
  129. package/packages/gui/src/components/layout/activity-bar.jsx +13 -3
  130. package/packages/gui/src/components/layout/app-shell.jsx +0 -1
  131. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +165 -47
  132. package/packages/gui/src/components/layout/command-palette.jsx +6 -2
  133. package/packages/gui/src/components/layout/terminal-panel.jsx +10 -9
  134. package/packages/gui/src/components/marketplace/repo-import.jsx +9 -1
  135. package/packages/gui/src/components/onboarding/provider-card.jsx +134 -0
  136. package/packages/gui/src/components/onboarding/setup-wizard.jsx +819 -0
  137. package/packages/gui/src/components/pro/pro-gate.jsx +12 -5
  138. package/packages/gui/src/components/pro/upgrade-card.jsx +15 -8
  139. package/packages/gui/src/components/pro/upgrade-modal.jsx +151 -0
  140. package/packages/gui/src/components/settings/federation-activity.jsx +98 -0
  141. package/packages/gui/src/components/settings/federation-panel.jsx +290 -0
  142. package/packages/gui/src/components/settings/federation-peers.jsx +126 -0
  143. package/packages/gui/src/components/settings/federation-wizard.jsx +293 -0
  144. package/packages/gui/src/components/settings/quick-connect.jsx +110 -67
  145. package/packages/gui/src/components/settings/remote-server-card.jsx +3 -3
  146. package/packages/gui/src/components/settings/server-detail.jsx +310 -0
  147. package/packages/gui/src/components/settings/server-dialog.jsx +4 -1
  148. package/packages/gui/src/components/settings/server-list.jsx +59 -0
  149. package/packages/gui/src/components/settings/ssh-wizard.jsx +549 -0
  150. package/packages/gui/src/components/toys/toy-card.jsx +78 -0
  151. package/packages/gui/src/components/toys/toy-creator.jsx +144 -0
  152. package/packages/gui/src/components/toys/toy-launcher.jsx +187 -0
  153. package/packages/gui/src/components/ui/toast.jsx +2 -2
  154. package/packages/gui/src/lib/electron.js +15 -0
  155. package/packages/gui/src/lib/format.js +1 -0
  156. package/packages/gui/src/stores/groove.js +373 -58
  157. package/packages/gui/src/views/agents.jsx +148 -42
  158. package/packages/gui/src/views/editor.jsx +92 -2
  159. package/packages/gui/src/views/federation.jsx +37 -0
  160. package/packages/gui/src/views/marketplace.jsx +2 -42
  161. package/packages/gui/src/views/settings.jsx +32 -132
  162. package/packages/gui/src/views/subscription-panel.jsx +327 -0
  163. package/packages/gui/src/views/teams.jsx +3 -3
  164. package/packages/gui/src/views/toys.jsx +162 -0
  165. package/plans/chat-persistence-refactor.md +154 -0
  166. package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +0 -1
  167. package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +0 -677
  168. package/packages/gui/dist/assets/index-BE6lYcd7.css +0 -1
  169. package/packages/gui/dist/assets/index-zdzOLAZM.js +0 -677
@@ -0,0 +1,310 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState, useEffect } from 'react';
3
+ import { Badge } from '../ui/badge';
4
+ import { StatusDot } from '../ui/status-dot';
5
+ import { Button } from '../ui/button';
6
+ import { useGrooveStore } from '../../stores/groove';
7
+ import { fmtUptime } from '../../lib/format';
8
+ import { cn } from '../../lib/cn';
9
+ import {
10
+ Plug, PlugZap, Pencil, Trash2, Loader2, Check, X, AlertTriangle,
11
+ ExternalLink, Server, Clock, Activity, KeyRound, Globe, Settings,
12
+ } from 'lucide-react';
13
+
14
+ export function ServerDetail({ server, onEdit, onDelete, onConnect, onDisconnect, onTest }) {
15
+ const [testResult, setTestResult] = useState(null);
16
+ const [testLoading, setTestLoading] = useState(false);
17
+ const [connecting, setConnecting] = useState(false);
18
+ const [connectStep, setConnectStep] = useState(null);
19
+
20
+ useEffect(() => {
21
+ function handleWs(e) {
22
+ try {
23
+ const msg = JSON.parse(e.data);
24
+ if (msg.type === 'tunnel.status' && msg.data?.id === server.id) {
25
+ setConnectStep(msg.data.step);
26
+ }
27
+ } catch {}
28
+ }
29
+ const ws = useGrooveStore.getState().ws;
30
+ if (ws) ws.addEventListener('message', handleWs);
31
+ return () => { if (ws) ws.removeEventListener('message', handleWs); };
32
+ }, [server.id]);
33
+
34
+ useEffect(() => {
35
+ setTestResult(null);
36
+ setConnectStep(null);
37
+ setConnecting(false);
38
+ }, [server.id]);
39
+
40
+ async function handleTest() {
41
+ setTestLoading(true);
42
+ setTestResult(null);
43
+ try {
44
+ const result = await onTest();
45
+ setTestResult(result);
46
+ } catch (err) {
47
+ setTestResult({ error: err.message || 'Test failed' });
48
+ }
49
+ setTestLoading(false);
50
+ }
51
+
52
+ async function handleConnect() {
53
+ setConnecting(true);
54
+ setConnectStep(null);
55
+ setTestResult(null);
56
+ try {
57
+ await onConnect();
58
+ setConnectStep(null);
59
+ } catch (err) {
60
+ const tr = err?.testResult || err?.body?.testResult;
61
+ if (tr) {
62
+ setTestResult(tr);
63
+ } else {
64
+ setTestResult({ error: err?.body?.error || err?.message || 'Connection failed' });
65
+ }
66
+ setConnectStep(null);
67
+ }
68
+ setConnecting(false);
69
+ }
70
+
71
+ async function handleDisconnect() {
72
+ setConnecting(true);
73
+ try {
74
+ await onDisconnect();
75
+ } catch {}
76
+ setConnecting(false);
77
+ }
78
+
79
+ function handleOpenRemote() {
80
+ const port = server.localPort;
81
+ const name = encodeURIComponent(server.name);
82
+ window.open(`http://localhost:${port}?instance=${name}`, '_blank');
83
+ }
84
+
85
+ const connectLabel = connectStep === 'installing'
86
+ ? 'Installing Groove...'
87
+ : connectStep === 'starting'
88
+ ? 'Starting daemon...'
89
+ : connecting
90
+ ? 'Connecting...'
91
+ : 'Connect';
92
+
93
+ const uptimeSeconds = server.active && server.startedAt
94
+ ? Math.floor((Date.now() - new Date(server.startedAt).getTime()) / 1000)
95
+ : 0;
96
+
97
+ return (
98
+ <div className="p-4 space-y-3">
99
+ {/* Header row */}
100
+ <div className="flex items-center gap-3 mb-1">
101
+ <div className="w-8 h-8 rounded-lg bg-surface-3 flex items-center justify-center">
102
+ <Server size={14} className="text-text-2" />
103
+ </div>
104
+ <div className="flex-1 min-w-0">
105
+ <h3 className="text-sm font-semibold text-text-0 font-sans truncate">{server.name}</h3>
106
+ <span className="text-2xs text-text-3 font-mono">
107
+ {server.user}@{server.host}:{server.port || 22}
108
+ </span>
109
+ </div>
110
+ {server.active ? (
111
+ <Badge variant="success" className="text-2xs gap-1">
112
+ <StatusDot status="running" size="sm" /> Connected
113
+ </Badge>
114
+ ) : (
115
+ <Badge variant="default" className="text-2xs">Disconnected</Badge>
116
+ )}
117
+ </div>
118
+
119
+ {/* Card grid */}
120
+ <div className="grid grid-cols-2 gap-3">
121
+ {/* Connection Info Card */}
122
+ <div className="rounded-lg border border-border-subtle bg-surface-1 px-4 py-3.5">
123
+ <div className="flex items-center gap-2 mb-3">
124
+ <div className="w-6 h-6 rounded bg-accent/8 flex items-center justify-center flex-shrink-0">
125
+ <Globe size={12} className="text-accent" />
126
+ </div>
127
+ <span className="text-[13px] font-medium text-text-0 font-sans">Connection</span>
128
+ </div>
129
+ <div className="space-y-2 text-2xs font-sans">
130
+ <div className="flex items-center justify-between">
131
+ <span className="text-text-3">Host</span>
132
+ <span className="text-text-0 font-mono">{server.host}</span>
133
+ </div>
134
+ <div className="flex items-center justify-between">
135
+ <span className="text-text-3">User</span>
136
+ <span className="text-text-0 font-mono">{server.user}</span>
137
+ </div>
138
+ <div className="flex items-center justify-between">
139
+ <span className="text-text-3">SSH Port</span>
140
+ <span className="text-text-0 font-mono">{server.port || 22}</span>
141
+ </div>
142
+ {server.sshKeyPath && (
143
+ <div className="flex items-center justify-between">
144
+ <span className="text-text-3">SSH Key</span>
145
+ <span className="text-text-0 font-mono truncate max-w-36">{server.sshKeyPath}</span>
146
+ </div>
147
+ )}
148
+ </div>
149
+ </div>
150
+
151
+ {/* Status / Stats Card */}
152
+ <div className="rounded-lg border border-border-subtle bg-surface-1 px-4 py-3.5">
153
+ <div className="flex items-center gap-2 mb-3">
154
+ <div className="w-6 h-6 rounded bg-accent/8 flex items-center justify-center flex-shrink-0">
155
+ <Settings size={12} className="text-accent" />
156
+ </div>
157
+ <span className="text-[13px] font-medium text-text-0 font-sans">Settings</span>
158
+ </div>
159
+ <div className="space-y-2 text-2xs font-sans">
160
+ <div className="flex items-center justify-between">
161
+ <span className="text-text-3">Auto-start daemon</span>
162
+ <Badge variant={server.autoStart ? 'accent' : 'default'} className="text-2xs">
163
+ {server.autoStart ? 'On' : 'Off'}
164
+ </Badge>
165
+ </div>
166
+ <div className="flex items-center justify-between">
167
+ <span className="text-text-3">Auto-connect</span>
168
+ <Badge variant={server.autoConnect ? 'accent' : 'default'} className="text-2xs">
169
+ {server.autoConnect ? 'On' : 'Off'}
170
+ </Badge>
171
+ </div>
172
+ {server.active && uptimeSeconds > 0 && (
173
+ <div className="flex items-center justify-between">
174
+ <span className="text-text-3">Uptime</span>
175
+ <span className="text-text-0 font-sans">{fmtUptime(uptimeSeconds)}</span>
176
+ </div>
177
+ )}
178
+ {server.active && server.latencyMs != null && (
179
+ <div className="flex items-center justify-between">
180
+ <span className="text-text-3">Latency</span>
181
+ <span className="text-text-0 font-mono">{server.latencyMs}ms</span>
182
+ </div>
183
+ )}
184
+ {server.active && server.localPort && (
185
+ <div className="flex items-center justify-between">
186
+ <span className="text-text-3">Local Port</span>
187
+ <span className="text-text-0 font-mono">{server.localPort}</span>
188
+ </div>
189
+ )}
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ {/* Action buttons */}
195
+ <div className="flex flex-wrap items-center gap-2">
196
+ {server.active ? (
197
+ <>
198
+ <Button
199
+ variant="primary"
200
+ size="sm"
201
+ onClick={handleOpenRemote}
202
+ className="h-8 text-xs gap-1.5"
203
+ >
204
+ <ExternalLink size={12} />
205
+ Open Remote GUI
206
+ </Button>
207
+ <Button
208
+ variant="ghost"
209
+ size="sm"
210
+ onClick={handleDisconnect}
211
+ disabled={connecting}
212
+ className="h-8 text-xs text-danger hover:text-danger gap-1.5"
213
+ >
214
+ <Plug size={12} />
215
+ {connecting ? 'Disconnecting...' : 'Disconnect'}
216
+ </Button>
217
+ </>
218
+ ) : (
219
+ <>
220
+ <Button
221
+ variant="primary"
222
+ size="sm"
223
+ onClick={handleConnect}
224
+ disabled={connecting}
225
+ className="h-8 text-xs gap-1.5"
226
+ >
227
+ {connecting ? <Loader2 size={12} className="animate-spin" /> : <PlugZap size={12} />}
228
+ {connectLabel}
229
+ </Button>
230
+ <Button
231
+ variant="ghost"
232
+ size="sm"
233
+ onClick={handleTest}
234
+ disabled={testLoading || connecting}
235
+ className="h-8 text-xs text-text-3 gap-1.5"
236
+ >
237
+ {testLoading ? <Loader2 size={12} className="animate-spin" /> : <PlugZap size={12} />}
238
+ Test
239
+ </Button>
240
+ </>
241
+ )}
242
+ <div className="flex-1" />
243
+ {!server.active && (
244
+ <>
245
+ <Button
246
+ variant="ghost"
247
+ size="sm"
248
+ onClick={() => onEdit(server)}
249
+ className="h-8 text-xs text-text-3 gap-1.5"
250
+ >
251
+ <Pencil size={12} />
252
+ Edit
253
+ </Button>
254
+ <Button
255
+ variant="ghost"
256
+ size="sm"
257
+ onClick={() => onDelete(server.id)}
258
+ className="h-8 text-xs text-danger hover:text-danger gap-1.5"
259
+ >
260
+ <Trash2 size={12} />
261
+ Delete
262
+ </Button>
263
+ </>
264
+ )}
265
+ </div>
266
+
267
+ {/* Inline test result */}
268
+ {testResult && !connecting && (
269
+ <div className={cn(
270
+ 'px-3 py-2.5 rounded-lg text-2xs font-sans flex items-start gap-2',
271
+ testResult.error
272
+ ? 'bg-danger/8 border border-danger/20 text-danger'
273
+ : testResult.reachable
274
+ ? 'bg-success/8 border border-success/20 text-success'
275
+ : 'bg-warning/8 border border-warning/20 text-warning',
276
+ )}>
277
+ {testResult.error ? (
278
+ <><X size={11} className="mt-0.5 flex-shrink-0" /> {testResult.error}</>
279
+ ) : testResult.reachable ? (
280
+ <>
281
+ <Check size={11} className="mt-0.5 flex-shrink-0" />
282
+ <span>
283
+ {testResult.daemonRunning
284
+ ? 'Connected. Groove running.'
285
+ : testResult.grooveInstalled
286
+ ? 'Connected. Groove installed but stopped.'
287
+ : 'Connected. Groove not installed.'}
288
+ {!testResult.daemonRunning && ' Click Connect to set up automatically.'}
289
+ </span>
290
+ </>
291
+ ) : (
292
+ <><AlertTriangle size={11} className="mt-0.5 flex-shrink-0" /> Host unreachable</>
293
+ )}
294
+ <button
295
+ onClick={() => setTestResult(null)}
296
+ className="ml-auto text-text-4 hover:text-text-1 cursor-pointer flex-shrink-0"
297
+ >
298
+ <X size={10} />
299
+ </button>
300
+ </div>
301
+ )}
302
+
303
+ {server.active && (
304
+ <div className="text-2xs text-text-4 bg-surface-1 rounded-lg px-3 py-2 border border-border-subtle">
305
+ Separate Groove instance on your remote server. Local teams are not affected.
306
+ </div>
307
+ )}
308
+ </div>
309
+ );
310
+ }
@@ -3,6 +3,7 @@ import { useState, useEffect } from 'react';
3
3
  import { Dialog, DialogContent } from '../ui/dialog';
4
4
  import { Button } from '../ui/button';
5
5
  import { FolderBrowser } from '../agents/folder-browser';
6
+ import { useGrooveStore } from '../../stores/groove';
6
7
  import { cn } from '../../lib/cn';
7
8
  import { FolderSearch } from 'lucide-react';
8
9
 
@@ -55,7 +56,9 @@ export function ServerDialog({ open, onOpenChange, server, onSave }) {
55
56
  if (server?.id) data.id = server.id;
56
57
  await onSave(data);
57
58
  onOpenChange(false);
58
- } catch {}
59
+ } catch (err) {
60
+ useGrooveStore.getState().addToast('error', 'Failed to save server', err.message);
61
+ }
59
62
  setSaving(false);
60
63
  }
61
64
 
@@ -0,0 +1,59 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { StatusDot } from '../ui/status-dot';
3
+ import { Button } from '../ui/button';
4
+ import { ScrollArea } from '../ui/scroll-area';
5
+ import { cn } from '../../lib/cn';
6
+ import { Plus, Radio } from 'lucide-react';
7
+
8
+ export function ServerList({ servers, selectedId, onSelect, onAddNew }) {
9
+ return (
10
+ <div className="flex flex-col h-full w-[220px] flex-shrink-0 border-r border-border-subtle bg-surface-1/50">
11
+ <div className="flex items-center justify-between px-3 py-2.5 border-b border-border-subtle">
12
+ <span className="text-2xs font-semibold text-text-2 font-sans uppercase tracking-wider">Servers</span>
13
+ <Button
14
+ variant="ghost"
15
+ size="sm"
16
+ onClick={onAddNew}
17
+ className="h-6 text-2xs gap-1 text-text-3 hover:text-accent"
18
+ >
19
+ <Plus size={11} /> Add
20
+ </Button>
21
+ </div>
22
+
23
+ <ScrollArea className="flex-1">
24
+ <div className="py-1">
25
+ {servers.length === 0 ? (
26
+ <div className="px-4 py-8 text-center">
27
+ <Radio size={18} className="text-text-4 mx-auto mb-2" />
28
+ <p className="text-2xs text-text-4 font-sans">No servers configured</p>
29
+ </div>
30
+ ) : (
31
+ servers.map((server) => (
32
+ <button
33
+ key={server.id}
34
+ onClick={() => onSelect(server.id)}
35
+ className={cn(
36
+ 'w-full text-left px-3 py-2.5 cursor-pointer transition-colors',
37
+ 'hover:bg-surface-3',
38
+ selectedId === server.id
39
+ ? 'bg-accent/8 border-l-2 border-accent'
40
+ : 'border-l-2 border-transparent',
41
+ )}
42
+ >
43
+ <div className="flex items-center gap-2 mb-0.5">
44
+ <StatusDot status={server.active ? 'running' : 'stopped'} size="sm" />
45
+ <span className="text-xs font-semibold text-text-0 font-sans truncate">
46
+ {server.name}
47
+ </span>
48
+ </div>
49
+ <div className="text-2xs text-text-3 font-mono truncate pl-4">
50
+ {server.user}@{server.host}:{server.port || 22}
51
+ </div>
52
+ </button>
53
+ ))
54
+ )}
55
+ </div>
56
+ </ScrollArea>
57
+ </div>
58
+ );
59
+ }