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,144 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState } from 'react';
3
+ import { Sheet, SheetContent } from '../ui/sheet';
4
+ import { Button } from '../ui/button';
5
+ import { ScrollArea } from '../ui/scroll-area';
6
+ import { api } from '../../lib/api';
7
+ import { useToast } from '../../lib/hooks/use-toast';
8
+ import { Globe, Loader2, Sparkles, Link2 } from 'lucide-react';
9
+
10
+ const STEPS = {
11
+ input: 'input',
12
+ researching: 'researching',
13
+ };
14
+
15
+ export function ToyCreator({ open, onClose, onCreated }) {
16
+ const [url, setUrl] = useState('');
17
+ const [name, setName] = useState('');
18
+ const [step, setStep] = useState(STEPS.input);
19
+ const toast = useToast();
20
+
21
+ function reset() {
22
+ setUrl('');
23
+ setName('');
24
+ setStep(STEPS.input);
25
+ }
26
+
27
+ function handleClose() {
28
+ if (step === STEPS.researching) return;
29
+ reset();
30
+ onClose();
31
+ }
32
+
33
+ async function handleCreate() {
34
+ const trimmed = url.trim();
35
+ if (!trimmed) {
36
+ toast.warning('URL required', 'Paste an API documentation or reference URL.');
37
+ return;
38
+ }
39
+
40
+ setStep(STEPS.researching);
41
+ try {
42
+ const toy = await api.post('/toys', {
43
+ docsUrl: trimmed,
44
+ name: name.trim() || undefined,
45
+ });
46
+ toast.success('Toy created', `${toy.name || 'New toy'} is ready to launch.`);
47
+ onCreated?.(toy);
48
+ reset();
49
+ onClose();
50
+ } catch (err) {
51
+ toast.error('Research failed', err.message);
52
+ setStep(STEPS.input);
53
+ }
54
+ }
55
+
56
+ return (
57
+ <Sheet open={open} onOpenChange={(v) => !v && handleClose()}>
58
+ <SheetContent title="New Toy" width={440}>
59
+ <ScrollArea className="h-[calc(100%-65px)]">
60
+ <div className="px-5 py-4 space-y-5">
61
+ {step === STEPS.input && (
62
+ <>
63
+ <div className="flex items-start gap-3 p-3 rounded-lg bg-accent/5 border border-accent/10">
64
+ <Sparkles size={16} className="text-accent mt-0.5 flex-shrink-0" />
65
+ <p className="text-xs text-text-2 font-sans leading-relaxed">
66
+ Paste a docs URL and Groove's AI will research the API — endpoints, auth, data structures, rate limits — and build a reusable toy card you can launch anytime.
67
+ </p>
68
+ </div>
69
+
70
+ <div>
71
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">
72
+ API Docs URL
73
+ </h4>
74
+ <div className="relative">
75
+ <Link2 size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-4" />
76
+ <input
77
+ type="url"
78
+ value={url}
79
+ onChange={(e) => setUrl(e.target.value)}
80
+ placeholder="https://api.example.com/docs"
81
+ className="w-full bg-surface-3 border border-border-subtle rounded pl-8 pr-3 py-2 text-xs text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:border-accent/50"
82
+ onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
83
+ autoFocus
84
+ />
85
+ </div>
86
+ </div>
87
+
88
+ <div>
89
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">
90
+ Name <span className="text-text-4 normal-case tracking-normal">(optional)</span>
91
+ </h4>
92
+ <input
93
+ type="text"
94
+ value={name}
95
+ onChange={(e) => setName(e.target.value)}
96
+ placeholder="Auto-detected from docs"
97
+ className="w-full bg-surface-3 border border-border-subtle rounded px-3 py-2 text-xs text-text-0 font-sans placeholder:text-text-4 focus:outline-none focus:border-accent/50"
98
+ onKeyDown={(e) => e.key === 'Enter' && handleCreate()}
99
+ />
100
+ </div>
101
+ </>
102
+ )}
103
+
104
+ {step === STEPS.researching && (
105
+ <div className="flex flex-col items-center justify-center py-12 gap-4">
106
+ <div className="w-14 h-14 rounded-xl bg-accent/10 border border-accent/20 flex items-center justify-center">
107
+ <Globe size={24} className="text-accent animate-pulse" />
108
+ </div>
109
+ <div className="text-center space-y-1.5">
110
+ <p className="text-sm font-semibold text-text-0 font-sans">Researching API...</p>
111
+ <p className="text-xs text-text-3 font-sans max-w-[260px]">
112
+ Reading documentation, mapping endpoints, and building your toy card. This takes 15–30 seconds.
113
+ </p>
114
+ </div>
115
+ <Loader2 size={16} className="text-accent animate-spin" />
116
+ </div>
117
+ )}
118
+ </div>
119
+ </ScrollArea>
120
+
121
+ <div className="px-5 py-3 border-t border-border-subtle bg-surface-1">
122
+ {step === STEPS.input && (
123
+ <Button
124
+ variant="primary"
125
+ size="lg"
126
+ className="w-full gap-2"
127
+ onClick={handleCreate}
128
+ disabled={!url.trim()}
129
+ >
130
+ <Sparkles size={14} />
131
+ Research & Create
132
+ </Button>
133
+ )}
134
+ {step === STEPS.researching && (
135
+ <Button variant="ghost" size="lg" className="w-full" disabled>
136
+ <Loader2 size={14} className="animate-spin mr-2" />
137
+ AI is analyzing...
138
+ </Button>
139
+ )}
140
+ </div>
141
+ </SheetContent>
142
+ </Sheet>
143
+ );
144
+ }
@@ -0,0 +1,187 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState } from 'react';
3
+ import { Sheet, SheetContent } from '../ui/sheet';
4
+ import { Button } from '../ui/button';
5
+ import { Badge } from '../ui/badge';
6
+ import { ScrollArea } from '../ui/scroll-area';
7
+ import { api } from '../../lib/api';
8
+ import { useToast } from '../../lib/hooks/use-toast';
9
+ import { useGrooveStore } from '../../stores/groove';
10
+ import { ExternalLink, Eye, EyeOff, Rocket, Loader2 } from 'lucide-react';
11
+ import * as Icons from 'lucide-react';
12
+
13
+ function resolveIcon(name) {
14
+ if (!name) return Icons.Box;
15
+ const pascal = name.replace(/(^|-)(\w)/g, (_, __, c) => c.toUpperCase());
16
+ return Icons[pascal] || Icons[name] || Icons.Box;
17
+ }
18
+
19
+ export function ToyLauncher({ toy, open, onClose }) {
20
+ const [apiKey, setApiKey] = useState('');
21
+ const [showKey, setShowKey] = useState(false);
22
+ const [selectedPrompt, setSelectedPrompt] = useState(null);
23
+ const [launching, setLaunching] = useState(false);
24
+ const toast = useToast();
25
+ const setActiveView = useGrooveStore((s) => s.setActiveView);
26
+
27
+ if (!toy) return null;
28
+ const Icon = resolveIcon(toy.icon);
29
+ const needsKey = toy.authType !== 'none';
30
+
31
+ async function handleLaunch() {
32
+ if (needsKey && !apiKey.trim()) {
33
+ toast.warning('API key required', `${toy.name} needs an API key to work.`);
34
+ return;
35
+ }
36
+ setLaunching(true);
37
+ try {
38
+ await api.post(`/toys/${toy.id}/launch`, {
39
+ apiKey: needsKey ? apiKey.trim() : undefined,
40
+ starterPrompt: selectedPrompt || undefined,
41
+ });
42
+ toast.success(`${toy.name} launched`, 'Team is spinning up — switching to agents view.');
43
+ setActiveView('agents');
44
+ onClose();
45
+ } catch (err) {
46
+ toast.error('Launch failed', err.message);
47
+ }
48
+ setLaunching(false);
49
+ }
50
+
51
+ return (
52
+ <Sheet open={open} onOpenChange={(v) => !v && onClose()}>
53
+ <SheetContent title={toy.name} width={440}>
54
+ <ScrollArea className="h-[calc(100%-65px)]">
55
+ <div className="px-5 py-4 space-y-5">
56
+ {/* Header */}
57
+ <div className="flex items-start gap-3">
58
+ <div className="w-12 h-12 rounded-lg flex items-center justify-center flex-shrink-0 bg-accent/10 border border-accent/20 text-accent">
59
+ <Icon size={24} />
60
+ </div>
61
+ <div className="flex-1 min-w-0">
62
+ <div className="flex items-center gap-2">
63
+ <Badge variant={toy.authType === 'none' ? 'success' : 'warning'}>
64
+ {toy.authType === 'none' ? 'Free' : 'Key Required'}
65
+ </Badge>
66
+ <Badge variant="default">{toy.category}</Badge>
67
+ </div>
68
+ <p className="text-xs text-text-2 font-sans leading-relaxed mt-2">{toy.description}</p>
69
+ </div>
70
+ </div>
71
+
72
+ {/* Base URL */}
73
+ {toy.baseUrl && (
74
+ <div>
75
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Base URL</h4>
76
+ <code className="text-xs text-accent font-mono bg-surface-3 px-2.5 py-1.5 rounded block truncate">
77
+ {toy.baseUrl}
78
+ </code>
79
+ </div>
80
+ )}
81
+
82
+ {/* Sample Endpoints */}
83
+ {toy.sampleEndpoints?.length > 0 && (
84
+ <div>
85
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Endpoints</h4>
86
+ <div className="space-y-1">
87
+ {toy.sampleEndpoints.map((ep, i) => {
88
+ const str = typeof ep === 'string' ? ep : `${ep.method || 'GET'} ${ep.path || ''}`;
89
+ const spaceIdx = str.indexOf(' ');
90
+ const method = spaceIdx > 0 ? str.slice(0, spaceIdx) : 'GET';
91
+ const path = spaceIdx > 0 ? str.slice(spaceIdx + 1) : str;
92
+ return (
93
+ <div key={i} className="flex items-center gap-2 text-xs font-mono text-text-2 bg-surface-3 px-2.5 py-1.5 rounded">
94
+ <Badge variant="accent" className="text-[9px] px-1 py-0">{method}</Badge>
95
+ <span className="truncate">{path}</span>
96
+ </div>
97
+ );
98
+ })}
99
+ </div>
100
+ </div>
101
+ )}
102
+
103
+ {/* Starter Ideas */}
104
+ {toy.starterPrompts?.length > 0 && (
105
+ <div>
106
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">Starter Ideas</h4>
107
+ <div className="flex flex-wrap gap-1.5">
108
+ {toy.starterPrompts.map((prompt, i) => (
109
+ <button
110
+ key={i}
111
+ onClick={() => setSelectedPrompt(selectedPrompt === prompt ? null : prompt)}
112
+ className={`px-3 py-1.5 text-xs font-sans rounded-full cursor-pointer select-none transition-colors border ${
113
+ selectedPrompt === prompt
114
+ ? 'bg-accent/15 text-accent border-accent/25'
115
+ : 'text-text-2 bg-surface-3 border-border-subtle hover:text-text-0 hover:border-border'
116
+ }`}
117
+ >
118
+ {prompt}
119
+ </button>
120
+ ))}
121
+ </div>
122
+ </div>
123
+ )}
124
+
125
+ {/* API Key Input */}
126
+ {needsKey && (
127
+ <div>
128
+ <h4 className="text-xs font-semibold text-text-3 font-sans uppercase tracking-wider mb-1.5">API Key</h4>
129
+ <div className="relative">
130
+ <input
131
+ type={showKey ? 'text' : 'password'}
132
+ value={apiKey}
133
+ onChange={(e) => setApiKey(e.target.value)}
134
+ placeholder={`Enter your ${toy.name} API key`}
135
+ className="w-full bg-surface-3 border border-border-subtle rounded px-3 py-2 text-xs text-text-0 font-mono placeholder:text-text-4 focus:outline-none focus:border-accent/50 pr-9"
136
+ />
137
+ <button
138
+ onClick={() => setShowKey(!showKey)}
139
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-text-4 hover:text-text-1 cursor-pointer bg-transparent border-0 p-0.5"
140
+ >
141
+ {showKey ? <EyeOff size={14} /> : <Eye size={14} />}
142
+ </button>
143
+ </div>
144
+ </div>
145
+ )}
146
+
147
+ {/* Docs link */}
148
+ {toy.docsUrl && (
149
+ <a
150
+ href={toy.docsUrl}
151
+ target="_blank"
152
+ rel="noopener noreferrer"
153
+ className="inline-flex items-center gap-1.5 text-xs text-accent font-sans hover:underline"
154
+ >
155
+ <ExternalLink size={12} />
156
+ View Documentation
157
+ </a>
158
+ )}
159
+ </div>
160
+ </ScrollArea>
161
+
162
+ {/* Footer — sticky launch button */}
163
+ <div className="px-5 py-3 border-t border-border-subtle bg-surface-1">
164
+ <Button
165
+ variant="primary"
166
+ size="lg"
167
+ className="w-full gap-2"
168
+ onClick={handleLaunch}
169
+ disabled={launching || (needsKey && !apiKey.trim())}
170
+ >
171
+ {launching ? (
172
+ <>
173
+ <Loader2 size={14} className="animate-spin" />
174
+ Launching...
175
+ </>
176
+ ) : (
177
+ <>
178
+ <Rocket size={14} />
179
+ Launch Team
180
+ </>
181
+ )}
182
+ </Button>
183
+ </div>
184
+ </SheetContent>
185
+ </Sheet>
186
+ );
187
+ }
@@ -52,8 +52,8 @@ function ToastItem({ toast }) {
52
52
  exit={{ opacity: 0, x: 80, scale: 0.95 }}
53
53
  transition={{ duration: 0.2 }}
54
54
  className={cn(
55
- 'w-80 rounded-md border border-border bg-surface-1 shadow-xl',
56
- 'border-l-4 flex items-center gap-3 px-4 py-3',
55
+ 'w-80 border border-border bg-surface-1 shadow-xl',
56
+ 'border-l-2 flex items-center gap-3 px-4 py-3',
57
57
  BORDER_COLORS[toast.type],
58
58
  )}
59
59
  >
@@ -16,6 +16,21 @@ export function openExternal(url) {
16
16
  }
17
17
  }
18
18
 
19
+ export async function selectFolder(options = {}) {
20
+ if (window.groove?.folders?.select) {
21
+ return window.groove.folders.select(options);
22
+ }
23
+ return null;
24
+ }
25
+
26
+ export async function setProjectDir(dir) {
27
+ if (window.groove?.folders?.setProjectDir) {
28
+ return window.groove.folders.setProjectDir(dir);
29
+ }
30
+ const { api } = await import('./api.js');
31
+ return api.post('/project-dir', { dir });
32
+ }
33
+
19
34
  export const electronAuth = {
20
35
  login: () => window.groove?.auth?.login(),
21
36
  logout: () => window.groove?.auth?.logout(),
@@ -3,6 +3,7 @@
3
3
 
4
4
  export function fmtNum(n) {
5
5
  if (n == null || isNaN(n)) return '0';
6
+ if (n >= 1_000_000_000) return (n / 1_000_000_000).toFixed(1).replace(/\.0$/, '') + 'B';
6
7
  if (n >= 1_000_000) return (n / 1_000_000).toFixed(1).replace(/\.0$/, '') + 'M';
7
8
  if (n >= 1_000) return (n / 1_000).toFixed(1).replace(/\.0$/, '') + 'k';
8
9
  return Math.round(n).toLocaleString();