groove-dev 0.27.119 → 0.27.121

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 (47) hide show
  1. package/moe-training/client/trajectory-capture.js +55 -0
  2. package/moe-training/test/client/trajectory-capture.test.js +63 -0
  3. package/node_modules/@groove-dev/cli/package.json +1 -1
  4. package/node_modules/@groove-dev/cli/src/commands/start.js +2 -1
  5. package/node_modules/@groove-dev/daemon/package.json +1 -1
  6. package/node_modules/@groove-dev/daemon/src/api.js +30 -10
  7. package/node_modules/@groove-dev/daemon/src/conversations.js +54 -32
  8. package/node_modules/@groove-dev/daemon/src/introducer.js +45 -20
  9. package/node_modules/@groove-dev/daemon/src/process.js +47 -1
  10. package/node_modules/@groove-dev/daemon/src/teams.js +33 -0
  11. package/node_modules/@groove-dev/gui/dist/assets/{index-DT6Jbf_q.css → index-BLd3MON8.css} +1 -1
  12. package/node_modules/@groove-dev/gui/dist/assets/{index-BxPCaxlC.js → index-bmkBX18f.js} +1721 -1721
  13. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  14. package/node_modules/@groove-dev/gui/package.json +1 -1
  15. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +3 -41
  16. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +4 -43
  17. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +8 -10
  18. package/node_modules/@groove-dev/gui/src/components/onboarding/setup-wizard.jsx +8 -23
  19. package/node_modules/@groove-dev/gui/src/components/settings/ProviderSetupWizard.jsx +54 -143
  20. package/node_modules/@groove-dev/gui/src/components/ui/data-sharing-modal.jsx +7 -57
  21. package/node_modules/@groove-dev/gui/src/stores/groove.js +13 -0
  22. package/node_modules/@groove-dev/gui/src/views/settings.jsx +50 -84
  23. package/node_modules/@groove-dev/gui/src/views/teams.jsx +61 -1
  24. package/node_modules/moe-training/client/trajectory-capture.js +55 -0
  25. package/node_modules/moe-training/test/client/trajectory-capture.test.js +63 -0
  26. package/package.json +1 -1
  27. package/packages/cli/package.json +1 -1
  28. package/packages/cli/src/commands/start.js +2 -1
  29. package/packages/daemon/package.json +1 -1
  30. package/packages/daemon/src/api.js +30 -10
  31. package/packages/daemon/src/conversations.js +54 -32
  32. package/packages/daemon/src/introducer.js +45 -20
  33. package/packages/daemon/src/process.js +47 -1
  34. package/packages/daemon/src/teams.js +33 -0
  35. package/packages/gui/dist/assets/{index-DT6Jbf_q.css → index-BLd3MON8.css} +1 -1
  36. package/packages/gui/dist/assets/{index-BxPCaxlC.js → index-bmkBX18f.js} +1721 -1721
  37. package/packages/gui/dist/index.html +2 -2
  38. package/packages/gui/package.json +1 -1
  39. package/packages/gui/src/components/agents/agent-config.jsx +3 -41
  40. package/packages/gui/src/components/agents/spawn-wizard.jsx +4 -43
  41. package/packages/gui/src/components/layout/status-bar.jsx +8 -10
  42. package/packages/gui/src/components/onboarding/setup-wizard.jsx +8 -23
  43. package/packages/gui/src/components/settings/ProviderSetupWizard.jsx +54 -143
  44. package/packages/gui/src/components/ui/data-sharing-modal.jsx +7 -57
  45. package/packages/gui/src/stores/groove.js +13 -0
  46. package/packages/gui/src/views/settings.jsx +50 -84
  47. package/packages/gui/src/views/teams.jsx +61 -1
@@ -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-BxPCaxlC.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-bmkBX18f.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/vendor-26L3JoZv.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/reactflow-DoBZjiHE.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/codemirror-CFF1Lrnz.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/xterm--7_ns2zW.js">
14
- <link rel="stylesheet" crossorigin href="/assets/index-DT6Jbf_q.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/index-BLd3MON8.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.119",
3
+ "version": "0.27.121",
4
4
  "description": "GROOVE GUI — visual agent control plane",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "type": "module",
@@ -6,7 +6,6 @@ import {
6
6
  AlertCircle, Layers, Activity,
7
7
  RotateCw, Skull, Copy, Trash2,
8
8
  Sparkles, Calendar, Plug, MessageCircle, Save, GitBranch,
9
- ExternalLink, Loader2,
10
9
  } from 'lucide-react';
11
10
  import { useGrooveStore } from '../../stores/groove';
12
11
  import { Badge } from '../ui/badge';
@@ -169,8 +168,6 @@ export function AgentConfig({ agent }) {
169
168
  const [savingPersonality, setSavingPersonality] = useState(false);
170
169
  const [installedIntegrations, setInstalledIntegrations] = useState([]);
171
170
  const [claudeAuth, setClaudeAuth] = useState(null);
172
- const [claudeAuthLoading, setClaudeAuthLoading] = useState(false);
173
- const [claudeAuthPolling, setClaudeAuthPolling] = useState(false);
174
171
 
175
172
  const isAlive = agent.status === 'running' || agent.status === 'starting';
176
173
 
@@ -217,23 +214,6 @@ export function AgentConfig({ agent }) {
217
214
  }).catch(() => {});
218
215
  }, [agent.id, agent.name]);
219
216
 
220
- useEffect(() => {
221
- if (!claudeAuthPolling) return;
222
- const start = Date.now();
223
- const interval = setInterval(() => {
224
- if (Date.now() - start > 300000) { setClaudeAuthPolling(false); clearInterval(interval); return; }
225
- api.get('/providers/claude-code/auth').then((data) => {
226
- if (data?.authenticated) {
227
- setClaudeAuth(data);
228
- setClaudeAuthPolling(false);
229
- setClaudeAuthLoading(false);
230
- clearInterval(interval);
231
- }
232
- }).catch(() => {});
233
- }, 2000);
234
- return () => clearInterval(interval);
235
- }, [claudeAuthPolling]);
236
-
237
217
  const currentProvider = providers.find((p) => p.id === agent.provider);
238
218
 
239
219
  async function handleModelSwap(providerId, modelId) {
@@ -304,16 +284,6 @@ export function AgentConfig({ agent }) {
304
284
  }
305
285
  }
306
286
 
307
- async function handleClaudeLogin() {
308
- setClaudeAuthLoading(true);
309
- try {
310
- await api.post('/providers/claude-code/login');
311
- setClaudeAuthPolling(true);
312
- } catch {
313
- setClaudeAuthLoading(false);
314
- }
315
- }
316
-
317
287
  const spawned = agent.spawnedAt || agent.createdAt;
318
288
 
319
289
  return (
@@ -475,17 +445,9 @@ export function AgentConfig({ agent }) {
475
445
  <AlertCircle size={13} className="text-warning flex-shrink-0" />
476
446
  <span className="text-xs font-semibold text-text-0 font-sans">Claude Code is not signed in</span>
477
447
  </div>
478
- {claudeAuthLoading ? (
479
- <div className="flex items-center gap-2 text-2xs text-text-2 font-sans">
480
- <Loader2 size={12} className="animate-spin text-accent" />
481
- Waiting for browser authentication...
482
- </div>
483
- ) : (
484
- <Button variant="primary" size="sm" onClick={handleClaudeLogin} className="text-2xs gap-1.5">
485
- <ExternalLink size={10} />
486
- Sign in to Claude
487
- </Button>
488
- )}
448
+ <p className="text-2xs text-text-2 font-sans">
449
+ Open the terminal and run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">claude</code>
450
+ </p>
489
451
  </div>
490
452
  )}
491
453
  {agent.provider === 'claude-code' && claudeAuth?.authenticated && (
@@ -12,7 +12,7 @@ import {
12
12
  Shield, Database, Megaphone, Calculator, UserCheck,
13
13
  Headphones, BarChart3, Rocket, ChevronDown, Pen, Presentation,
14
14
  Sparkles, X, Search, AlertTriangle, Plug, MessageCircle, GitBranch, Globe,
15
- Check, ExternalLink, Loader2,
15
+ Check,
16
16
  } from 'lucide-react';
17
17
  import { api } from '../../lib/api';
18
18
  import { Dialog, DialogContent } from '../ui/dialog';
@@ -83,8 +83,6 @@ export function SpawnWizard() {
83
83
  const [recommendations, setRecommendations] = useState([]);
84
84
  const [preflightDialog, setPreflightDialog] = useState(null);
85
85
  const [claudeAuth, setClaudeAuth] = useState(null);
86
- const [claudeAuthLoading, setClaudeAuthLoading] = useState(false);
87
- const [claudeAuthPolling, setClaudeAuthPolling] = useState(false);
88
86
  const federation = useGrooveStore((s) => s.federation);
89
87
 
90
88
  const selectedRole = role || customRole;
@@ -127,8 +125,6 @@ export function SpawnWizard() {
127
125
  setRecommendations([]);
128
126
  setPreflightDialog(null);
129
127
  setClaudeAuth(null);
130
- setClaudeAuthLoading(false);
131
- setClaudeAuthPolling(false);
132
128
  }
133
129
  }, [open, fetchProviders]);
134
130
 
@@ -153,23 +149,6 @@ export function SpawnWizard() {
153
149
  }).catch(() => setClaudeAuth(null));
154
150
  }, [open, provider]);
155
151
 
156
- useEffect(() => {
157
- if (!claudeAuthPolling) return;
158
- const start = Date.now();
159
- const interval = setInterval(() => {
160
- if (Date.now() - start > 300000) { setClaudeAuthPolling(false); clearInterval(interval); return; }
161
- api.get('/providers/claude-code/auth').then((data) => {
162
- if (data?.authenticated) {
163
- setClaudeAuth(data);
164
- setClaudeAuthPolling(false);
165
- setClaudeAuthLoading(false);
166
- clearInterval(interval);
167
- }
168
- }).catch(() => {});
169
- }, 2000);
170
- return () => clearInterval(interval);
171
- }, [claudeAuthPolling]);
172
-
173
152
  async function runSpawn() {
174
153
  setSpawning(true);
175
154
  try {
@@ -207,16 +186,6 @@ export function SpawnWizard() {
207
186
  runSpawn();
208
187
  }
209
188
 
210
- async function handleClaudeLogin() {
211
- setClaudeAuthLoading(true);
212
- try {
213
- await api.post('/providers/claude-code/login');
214
- setClaudeAuthPolling(true);
215
- } catch {
216
- setClaudeAuthLoading(false);
217
- }
218
- }
219
-
220
189
  const claudeNotAuthed = provider === 'claude-code' && claudeAuth && !claudeAuth.authenticated;
221
190
 
222
191
  return (
@@ -457,17 +426,9 @@ export function SpawnWizard() {
457
426
  <AlertTriangle size={13} className="text-warning flex-shrink-0" />
458
427
  <span className="text-xs font-semibold text-text-0 font-sans">Claude Code is not signed in</span>
459
428
  </div>
460
- {claudeAuthLoading ? (
461
- <div className="flex items-center gap-2 text-2xs text-text-2 font-sans">
462
- <Loader2 size={12} className="animate-spin text-accent" />
463
- Waiting for browser authentication...
464
- </div>
465
- ) : (
466
- <Button variant="primary" size="sm" onClick={handleClaudeLogin} className="text-2xs gap-1.5">
467
- <ExternalLink size={10} />
468
- Sign in to Claude
469
- </Button>
470
- )}
429
+ <p className="text-2xs text-text-2 font-sans">
430
+ Open the terminal and run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">claude</code>
431
+ </p>
471
432
  </div>
472
433
  )}
473
434
  {provider === 'claude-code' && claudeAuth?.authenticated && (
@@ -90,16 +90,14 @@ export function StatusBar({
90
90
  </button>
91
91
  </div>
92
92
  ))}
93
- {savedTunnels.length > 0 && (
94
- <button
95
- onClick={() => useGrooveStore.getState().toggleQuickConnect()}
96
- className="flex items-center gap-1.5 text-text-4 hover:text-text-1 cursor-pointer transition-colors"
97
- title="Quick Connect to remote server"
98
- >
99
- <Plug size={10} />
100
- <span>Connect</span>
101
- </button>
102
- )}
93
+ <button
94
+ onClick={() => useGrooveStore.getState().toggleQuickConnect()}
95
+ className="flex items-center gap-1.5 text-text-4 hover:text-text-1 cursor-pointer transition-colors"
96
+ title="Quick Connect to remote server"
97
+ >
98
+ <Plug size={10} />
99
+ <span>Connect</span>
100
+ </button>
103
101
  </>
104
102
  )}
105
103
  {connected && (
@@ -242,7 +242,7 @@ function InstallStep({ providerStatus, selected, onInstall, installing, statusCh
242
242
 
243
243
  // ── Step 4: Authentication ──────────────────────────────────
244
244
 
245
- function AuthCard({ provider, providerStatus, onSaveKey, onSubscriptionLogin }) {
245
+ function AuthCard({ provider, providerStatus, onSaveKey }) {
246
246
  const [authMode, setAuthMode] = useState(
247
247
  provider.authModes.includes('subscription') ? 'subscription' : 'apikey',
248
248
  );
@@ -307,14 +307,12 @@ function AuthCard({ provider, providerStatus, onSaveKey, onSubscriptionLogin })
307
307
 
308
308
  {authMode === 'subscription' && (
309
309
  <div className="space-y-3">
310
- <p className="text-xs text-text-2">Sign in with your Claude subscription</p>
311
- <button
312
- type="button"
313
- onClick={() => onSubscriptionLogin(provider.id)}
314
- className="h-8 px-4 rounded-md bg-purple/15 text-purple border border-purple/20 text-xs font-medium hover:bg-purple/25 transition-colors duration-100 cursor-pointer focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-purple"
315
- >
316
- Sign In
317
- </button>
310
+ <p className="text-xs text-text-2">Sign in via the terminal with your Claude subscription.</p>
311
+ <div className="space-y-1.5">
312
+ <p className="text-2xs text-text-2 font-sans">
313
+ Open a terminal and run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">claude</code> — then follow the prompts to sign in.
314
+ </p>
315
+ </div>
318
316
  {providerStatus?.authenticated && (
319
317
  <p className="text-xs text-success flex items-center gap-1.5">
320
318
  <Check className="w-3.5 h-3.5" /> Connected
@@ -388,7 +386,7 @@ function AuthCard({ provider, providerStatus, onSaveKey, onSubscriptionLogin })
388
386
  );
389
387
  }
390
388
 
391
- function AuthStep({ providerStatus, installedIds, onSaveKey, onSubscriptionLogin }) {
389
+ function AuthStep({ providerStatus, installedIds, onSaveKey }) {
392
390
  const installed = PROVIDERS.filter((p) => installedIds.includes(p.id) || providerStatus[p.id]?.installed);
393
391
  const hasAuthenticated = installed.some((p) => providerStatus[p.id]?.authenticated);
394
392
 
@@ -406,7 +404,6 @@ function AuthStep({ providerStatus, installedIds, onSaveKey, onSubscriptionLogin
406
404
  provider={p}
407
405
  providerStatus={providerStatus[p.id] || {}}
408
406
  onSaveKey={onSaveKey}
409
- onSubscriptionLogin={onSubscriptionLogin}
410
407
  />
411
408
  ))}
412
409
  </div>
@@ -687,17 +684,6 @@ export function SetupWizard() {
687
684
  }
688
685
  }, [addToast]);
689
686
 
690
- const handleSubscriptionLogin = useCallback((providerId) => {
691
- if (isElectron() && window.groove?.auth?.login) {
692
- window.groove.auth.login();
693
- } else {
694
- fetch('/api/auth/login-url')
695
- .then((r) => r.json())
696
- .then((d) => { if (d.url) window.open(d.url, '_blank'); })
697
- .catch(() => addToast('error', 'Failed to start login'));
698
- }
699
- }, [addToast]);
700
-
701
687
  const handleSetDefault = useCallback(async (provider, model) => {
702
688
  setDefaultProv(provider);
703
689
  setDefaultMod(model);
@@ -741,7 +727,6 @@ export function SetupWizard() {
741
727
  providerStatus={providerStatus}
742
728
  installedIds={selected}
743
729
  onSaveKey={handleSaveKey}
744
- onSubscriptionLogin={handleSubscriptionLogin}
745
730
  />,
746
731
  <DefaultModelStep
747
732
  key="default"
@@ -9,7 +9,7 @@ import { cn } from '../../lib/cn';
9
9
  import { api } from '../../lib/api';
10
10
  import {
11
11
  Download, Loader2, Check, ChevronDown, ChevronUp,
12
- Eye, EyeOff, Key, RotateCcw, ExternalLink, Sparkles,
12
+ Eye, EyeOff, Key, RotateCcw, Sparkles,
13
13
  } from 'lucide-react';
14
14
 
15
15
  const PROVIDER_META = {
@@ -166,15 +166,15 @@ function InstallStep({ providerId, meta }) {
166
166
  }
167
167
 
168
168
  function AuthenticateStep({ providerId, meta, onSaveKey }) {
169
- const loginProvider = useGrooveStore((s) => s.loginProvider);
170
169
  const addToast = useGrooveStore((s) => s.addToast);
171
170
  const [key, setKey] = useState('');
172
171
  const [showKey, setShowKey] = useState(false);
173
172
  const [saving, setSaving] = useState(false);
174
- const [loginStarted, setLoginStarted] = useState(false);
175
- const [verifying, setVerifying] = useState(false);
176
- const [verifyError, setVerifyError] = useState('');
177
- const [authMode, setAuthMode] = useState(meta.authType === 'subscription' ? 'subscription' : 'apikey');
173
+ const [authMode, setAuthMode] = useState(
174
+ providerId === 'claude-code' ? 'terminal' :
175
+ providerId === 'codex' ? 'terminal' :
176
+ 'apikey',
177
+ );
178
178
 
179
179
  async function handleSaveKey() {
180
180
  if (!key.trim()) return;
@@ -190,158 +190,69 @@ function AuthenticateStep({ providerId, meta, onSaveKey }) {
190
190
  }
191
191
  }
192
192
 
193
- async function handleLogin() {
194
- const body = authMode === 'chatgpt-plus' ? { method: 'chatgpt-plus' } : undefined;
195
- try {
196
- await loginProvider(providerId, body);
197
- setLoginStarted(true);
198
- } catch { /* handled in store */ }
199
- }
200
-
201
193
  return (
202
194
  <div className="space-y-4">
203
195
  <p className="text-sm font-medium text-text-0 font-sans">{meta.authLabel}</p>
204
196
 
205
197
  {providerId === 'claude-code' && (
206
- <div className="flex gap-1 bg-surface-3 p-0.5 rounded-md mb-4">
207
- <button
208
- onClick={() => { setAuthMode('subscription'); setLoginStarted(false); }}
209
- className={cn(
210
- 'flex-1 h-7 rounded text-xs font-medium transition-colors cursor-pointer font-sans',
211
- authMode === 'subscription' ? 'bg-surface-5 text-text-0' : 'text-text-3 hover:text-text-1',
212
- )}
213
- >
214
- Subscription
215
- </button>
198
+ <div className="space-y-3">
199
+ <p className="text-xs text-text-2 font-sans">Sign in via the terminal with your Claude subscription.</p>
200
+ <div className="space-y-1.5">
201
+ <div className="flex items-start gap-2">
202
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">1</span>
203
+ <p className="text-2xs text-text-2 font-sans">Open the Groove terminal below</p>
204
+ </div>
205
+ <div className="flex items-start gap-2">
206
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">2</span>
207
+ <p className="text-2xs text-text-2 font-sans">
208
+ Run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">claude</code>
209
+ </p>
210
+ </div>
211
+ <div className="flex items-start gap-2">
212
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">3</span>
213
+ <p className="text-2xs text-text-2 font-sans">Follow the prompts to sign in with your Anthropic account</p>
214
+ </div>
215
+ </div>
216
216
  <button
217
- onClick={() => { setAuthMode('apikey'); setLoginStarted(false); }}
218
- className={cn(
219
- 'flex-1 h-7 rounded text-xs font-medium transition-colors cursor-pointer font-sans',
220
- authMode === 'apikey' ? 'bg-surface-5 text-text-0' : 'text-text-3 hover:text-text-1',
221
- )}
217
+ onClick={() => setAuthMode(authMode === 'apikey' ? 'terminal' : 'apikey')}
218
+ className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans"
222
219
  >
223
- API Key
220
+ {authMode === 'apikey' ? 'Use terminal login instead' : 'I have an API key instead'}
224
221
  </button>
225
222
  </div>
226
223
  )}
227
224
 
228
225
  {providerId === 'codex' && (
229
- <div className="flex gap-1 bg-surface-3 p-0.5 rounded-md mb-4">
230
- <button
231
- onClick={() => { setAuthMode('apikey'); setLoginStarted(false); }}
232
- className={cn(
233
- 'flex-1 h-7 rounded text-xs font-medium transition-colors cursor-pointer font-sans',
234
- authMode === 'apikey' ? 'bg-surface-5 text-text-0' : 'text-text-3 hover:text-text-1',
235
- )}
236
- >
237
- API Key
238
- </button>
239
- <button
240
- onClick={() => { setAuthMode('chatgpt-plus'); setLoginStarted(false); }}
241
- className={cn(
242
- 'flex-1 h-7 rounded text-xs font-medium transition-colors cursor-pointer font-sans',
243
- authMode === 'chatgpt-plus' ? 'bg-surface-5 text-text-0' : 'text-text-3 hover:text-text-1',
244
- )}
245
- >
246
- ChatGPT Plus
247
- </button>
248
- </div>
249
- )}
250
-
251
- {authMode === 'subscription' && (
252
226
  <div className="space-y-3">
253
- <p className="text-xs text-text-2 font-sans">
254
- Click below to sign in with your existing Claude subscription.
255
- </p>
256
- {!loginStarted ? (
257
- <Button variant="primary" size="md" onClick={handleLogin} className="gap-1.5">
258
- <ExternalLink size={12} /> {meta.loginLabel}
259
- </Button>
260
- ) : (
261
- <div className="space-y-3">
262
- <div className="flex items-center gap-2 p-3 bg-accent/5 border border-accent/15 rounded-md">
263
- <ExternalLink size={12} className="text-accent" />
264
- <span className="text-xs text-accent font-sans">Sign-in opened in your browser</span>
265
- </div>
266
- <Button
267
- variant="primary"
268
- size="sm"
269
- disabled={verifying}
270
- onClick={async () => {
271
- setVerifying(true);
272
- setVerifyError('');
273
- try {
274
- const res = await api.post(`/providers/${providerId}/verify`);
275
- if (res.authenticated) {
276
- onSaveKey();
277
- } else {
278
- setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
279
- }
280
- } catch {
281
- setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
282
- } finally {
283
- setVerifying(false);
284
- }
285
- }}
286
- className="gap-1.5"
287
- >
288
- {verifying ? <Loader2 size={11} className="animate-spin" /> : <Check size={11} />} I've signed in
289
- </Button>
290
- {verifyError && (
291
- <p className="text-2xs text-danger font-sans">{verifyError}</p>
292
- )}
227
+ <p className="text-xs text-text-2 font-sans">Sign in via the terminal with your OpenAI account.</p>
228
+ <div className="space-y-1.5">
229
+ <div className="flex items-start gap-2">
230
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">1</span>
231
+ <p className="text-2xs text-text-2 font-sans">Open the Groove terminal below</p>
293
232
  </div>
294
- )}
295
- <p className="text-2xs text-text-4 font-sans">
296
- A browser window will open for you to sign in.
297
- </p>
298
- </div>
299
- )}
300
-
301
- {authMode === 'chatgpt-plus' && (
302
- <div className="space-y-3">
303
- <p className="text-xs text-text-2 font-sans">
304
- Click below to sign in with your ChatGPT Plus subscription.
305
- </p>
306
- {!loginStarted ? (
307
- <Button variant="primary" size="md" onClick={handleLogin} className="gap-1.5">
308
- <ExternalLink size={12} /> {meta.loginLabel}
309
- </Button>
310
- ) : (
311
- <div className="space-y-3">
312
- <div className="flex items-center gap-2 p-3 bg-accent/5 border border-accent/15 rounded-md">
313
- <ExternalLink size={12} className="text-accent" />
314
- <span className="text-xs text-accent font-sans">Sign-in opened in your browser</span>
315
- </div>
316
- <Button
317
- variant="primary"
318
- size="sm"
319
- disabled={verifying}
320
- onClick={async () => {
321
- setVerifying(true);
322
- setVerifyError('');
323
- try {
324
- const res = await api.post(`/providers/codex/verify`);
325
- if (res.authenticated) {
326
- onSaveKey();
327
- } else {
328
- setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
329
- }
330
- } catch {
331
- setVerifyError('Authentication not detected yet. Please complete sign-in in your browser and try again.');
332
- } finally {
333
- setVerifying(false);
334
- }
335
- }}
336
- className="gap-1.5"
337
- >
338
- {verifying ? <Loader2 size={11} className="animate-spin" /> : <Check size={11} />} I've signed in
339
- </Button>
340
- {verifyError && (
341
- <p className="text-2xs text-danger font-sans">{verifyError}</p>
342
- )}
233
+ <div className="flex items-start gap-2">
234
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">2</span>
235
+ <p className="text-2xs text-text-2 font-sans">
236
+ Run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">npm i -g @openai/codex</code> (if not installed)
237
+ </p>
343
238
  </div>
344
- )}
239
+ <div className="flex items-start gap-2">
240
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">3</span>
241
+ <p className="text-2xs text-text-2 font-sans">
242
+ Run: <code className="font-mono text-accent bg-surface-4 px-1.5 py-0.5 rounded text-2xs">codex login</code>
243
+ </p>
244
+ </div>
245
+ <div className="flex items-start gap-2">
246
+ <span className="text-2xs font-bold text-accent font-mono mt-0.5">4</span>
247
+ <p className="text-2xs text-text-2 font-sans">Follow the prompts to authenticate</p>
248
+ </div>
249
+ </div>
250
+ <button
251
+ onClick={() => setAuthMode(authMode === 'apikey' ? 'terminal' : 'apikey')}
252
+ className="text-2xs text-text-4 hover:text-accent cursor-pointer font-sans"
253
+ >
254
+ {authMode === 'apikey' ? 'Use terminal login instead' : 'I have an API key instead'}
255
+ </button>
345
256
  </div>
346
257
  )}
347
258
 
@@ -3,7 +3,7 @@ import { useState } from 'react';
3
3
  import { Dialog, DialogContent } from './dialog';
4
4
  import * as DialogPrimitive from '@radix-ui/react-dialog';
5
5
  import { useGrooveStore } from '../../stores/groove';
6
- import { Sparkles, Share2, Cpu, Gift, Shield, Check, X } from 'lucide-react';
6
+ import { Sparkles, Shield, X } from 'lucide-react';
7
7
 
8
8
  export function DataSharingModal() {
9
9
  const open = useGrooveStore((s) => s.dataSharingModalOpen);
@@ -14,8 +14,8 @@ export function DataSharingModal() {
14
14
  return (
15
15
  <Dialog open={open}>
16
16
  <DialogContent
17
- className="max-w-2xl"
18
- description="Review how your data helps build open source AI"
17
+ className="max-w-md"
18
+ description="Help improve Groove by sharing usage data"
19
19
  onInteractOutside={(e) => e.preventDefault()}
20
20
  onEscapeKeyDown={(e) => e.preventDefault()}
21
21
  >
@@ -27,51 +27,10 @@ export function DataSharingModal() {
27
27
  </div>
28
28
  </div>
29
29
  <DialogPrimitive.Title className="text-xl font-bold text-text-0 font-sans">
30
- Help Build Open Source Intelligence
30
+ Help Build a Better Groove
31
31
  </DialogPrimitive.Title>
32
32
  <p className="text-sm text-text-2 font-sans mt-2 max-w-md mx-auto">
33
- Your usage data trains a free, local MoE model that every Groove user gets to use — including you.
34
- </p>
35
- </div>
36
-
37
- {/* Value Proposition */}
38
- <div className="px-6 pt-5 pb-1">
39
- <div className="grid grid-cols-3 gap-3">
40
- {[
41
- { icon: Share2, title: 'You Share', desc: 'Anonymized agent session data: tool calls, error patterns, task flows' },
42
- { icon: Cpu, title: 'We Train', desc: 'A Groove-specific Mixture of Experts model built on real multi-agent workflows' },
43
- { icon: Gift, title: 'Everyone Wins', desc: 'Free, local, open source model for all Groove users. More data = smarter agents' },
44
- ].map(({ icon: Icon, title, desc }) => (
45
- <div key={title} className="rounded-lg border border-border-subtle bg-surface-2/50 p-3 text-center">
46
- <div className="flex justify-center mb-2">
47
- <Icon size={18} className="text-accent" />
48
- </div>
49
- <div className="text-xs font-semibold text-text-0 font-sans mb-1">{title}</div>
50
- <div className="text-2xs text-text-2 font-sans leading-relaxed">{desc}</div>
51
- </div>
52
- ))}
53
- </div>
54
- </div>
55
-
56
- {/* What We Collect */}
57
- <div className="px-6 pt-4">
58
- <div className="text-xs font-semibold uppercase text-text-3 tracking-wider font-sans mb-2.5">What We Collect</div>
59
- <div className="space-y-1.5">
60
- {[
61
- 'Agent tool calling patterns',
62
- 'Error and recovery sequences',
63
- 'Task complexity and coordination events',
64
- 'Model and provider usage metadata',
65
- 'Session duration and outcomes',
66
- ].map((item) => (
67
- <div key={item} className="flex items-center gap-2">
68
- <Check size={12} className="text-accent flex-shrink-0" />
69
- <span className="text-xs text-text-1 font-sans">{item}</span>
70
- </div>
71
- ))}
72
- </div>
73
- <p className="text-xs text-text-0 font-sans font-medium mt-3">
74
- That&apos;s it. Groove orchestration data only.
33
+ We collect errors and usage reports to improve the quality of Groove for everyone.
75
34
  </p>
76
35
  </div>
77
36
 
@@ -87,7 +46,7 @@ export function DataSharingModal() {
87
46
  'Your source code or file contents',
88
47
  'API keys, passwords, or credentials',
89
48
  'Personal information — emails, names, file paths',
90
- 'Anything that could identify your IP or projects',
49
+ 'Anything that could identify you, your IP or projects',
91
50
  ].map((item) => (
92
51
  <div key={item} className="flex items-center gap-2">
93
52
  <X size={12} className="text-danger flex-shrink-0" />
@@ -96,16 +55,7 @@ export function DataSharingModal() {
96
55
  ))}
97
56
  </div>
98
57
  <p className="text-xs text-text-0 font-sans font-medium mt-3">
99
- 13 categories of PII are automatically scrubbed before any data leaves your machine.
100
- </p>
101
- </div>
102
- </div>
103
-
104
- {/* Mission Statement */}
105
- <div className="px-6 pt-4">
106
- <div className="border-l-2 border-accent/30 pl-3">
107
- <p className="text-xs text-text-3 italic leading-relaxed font-sans">
108
- We believe in open source, decentralized intelligence. Not walled gardens. Not data hoarding. Every contribution makes the model better for everyone. We need your help to get there.
58
+ PII is automatically scrubbed before any data leaves your machine.
109
59
  </p>
110
60
  </div>
111
61
  </div>
@@ -1274,6 +1274,19 @@ export const useGrooveStore = create((set, get) => ({
1274
1274
  throw err;
1275
1275
  }
1276
1276
  },
1277
+
1278
+ async promoteTeam(id) {
1279
+ try {
1280
+ const team = await api.post(`/teams/${encodeURIComponent(id)}/promote`);
1281
+ set((s) => ({ teams: s.teams.map((t) => (t.id === id ? { ...t, ...team } : t)) }));
1282
+ get().addToast('success', 'Team promoted to production');
1283
+ return team;
1284
+ } catch (err) {
1285
+ get().addToast('error', 'Failed to promote team', err.message);
1286
+ throw err;
1287
+ }
1288
+ },
1289
+
1277
1290
  openDetail(descriptor) {
1278
1291
  const tid = get().activeTeamId;
1279
1292
  set((s) => ({ detailPanel: descriptor, teamDetailPanels: { ...s.teamDetailPanels, [tid]: descriptor } }));