groove-dev 0.27.142 → 0.27.144

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 (187) hide show
  1. package/node_modules/@groove-dev/cli/package.json +1 -1
  2. package/node_modules/@groove-dev/daemon/package.json +1 -1
  3. package/node_modules/@groove-dev/daemon/src/api.js +1086 -6532
  4. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +35 -1
  5. package/node_modules/@groove-dev/daemon/src/index.js +3 -0
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +23 -13
  7. package/node_modules/@groove-dev/daemon/src/mlx-server.js +365 -0
  8. package/node_modules/@groove-dev/daemon/src/model-lab.js +308 -12
  9. package/node_modules/@groove-dev/daemon/src/pm.js +1 -1
  10. package/node_modules/@groove-dev/daemon/src/process.js +2 -2
  11. package/node_modules/@groove-dev/daemon/src/providers/local.js +36 -8
  12. package/node_modules/@groove-dev/daemon/src/registry.js +21 -5
  13. package/node_modules/@groove-dev/daemon/src/routes/agents.js +889 -0
  14. package/node_modules/@groove-dev/daemon/src/routes/coordination.js +318 -0
  15. package/node_modules/@groove-dev/daemon/src/routes/files.js +751 -0
  16. package/node_modules/@groove-dev/daemon/src/routes/integrations.js +485 -0
  17. package/node_modules/@groove-dev/daemon/src/routes/network.js +1784 -0
  18. package/node_modules/@groove-dev/daemon/src/routes/providers.js +755 -0
  19. package/node_modules/@groove-dev/daemon/src/routes/schedules.js +110 -0
  20. package/node_modules/@groove-dev/daemon/src/routes/teams.js +650 -0
  21. package/node_modules/@groove-dev/daemon/src/scheduler.js +456 -24
  22. package/node_modules/@groove-dev/daemon/src/teams.js +1 -1
  23. package/node_modules/@groove-dev/daemon/src/validate.js +38 -1
  24. package/node_modules/@groove-dev/daemon/templates/mlx-setup.json +12 -0
  25. package/node_modules/@groove-dev/daemon/templates/tgi-setup.json +1 -1
  26. package/node_modules/@groove-dev/daemon/templates/vllm-setup.json +1 -1
  27. package/node_modules/@groove-dev/daemon/test/introducer.test.js +3 -3
  28. package/node_modules/@groove-dev/daemon/test/journalist.test.js +7 -10
  29. package/node_modules/@groove-dev/daemon/test/registry.test.js +38 -0
  30. package/node_modules/@groove-dev/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  31. package/node_modules/@groove-dev/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  32. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  33. package/node_modules/@groove-dev/gui/package.json +1 -1
  34. package/{packages/gui/src/app.jsx → node_modules/@groove-dev/gui/src/App.jsx} +0 -2
  35. package/node_modules/@groove-dev/gui/src/app.css +35 -0
  36. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +1 -128
  37. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +144 -31
  38. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +8 -13
  39. package/node_modules/@groove-dev/gui/src/components/agents/code-review.jsx +159 -122
  40. package/node_modules/@groove-dev/gui/src/components/agents/diff-viewer.jsx +23 -23
  41. package/node_modules/@groove-dev/gui/src/components/agents/journalist-panel.jsx +1 -1
  42. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +2 -135
  43. package/node_modules/@groove-dev/gui/src/components/automations/automation-card.jsx +274 -0
  44. package/node_modules/@groove-dev/gui/src/components/automations/automation-wizard.jsx +1136 -0
  45. package/node_modules/@groove-dev/gui/src/components/dashboard/activity-feed.jsx +3 -3
  46. package/node_modules/@groove-dev/gui/src/components/dashboard/cache-ring.jsx +5 -5
  47. package/node_modules/@groove-dev/gui/src/components/dashboard/context-gauges.jsx +6 -8
  48. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  49. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +238 -656
  50. package/node_modules/@groove-dev/gui/src/components/dashboard/kpi-card.jsx +3 -3
  51. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  52. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  53. package/node_modules/@groove-dev/gui/src/components/dashboard/token-chart.jsx +4 -4
  54. package/node_modules/@groove-dev/gui/src/components/editor/selection-menu.jsx +2 -0
  55. package/node_modules/@groove-dev/gui/src/components/lab/lab-assistant.jsx +316 -82
  56. package/node_modules/@groove-dev/gui/src/components/lab/metrics-panel.jsx +187 -32
  57. package/node_modules/@groove-dev/gui/src/components/lab/parameter-panel.jsx +195 -14
  58. package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +286 -102
  59. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +2 -4
  60. package/node_modules/@groove-dev/gui/src/components/layout/terminal-panel.jsx +4 -2
  61. package/node_modules/@groove-dev/gui/src/components/layout/welcome-splash.jsx +137 -108
  62. package/node_modules/@groove-dev/gui/src/components/network/network-health.jsx +2 -2
  63. package/node_modules/@groove-dev/gui/src/components/network/performance-dashboard.jsx +4 -4
  64. package/node_modules/@groove-dev/gui/src/components/settings/ssh-wizard.jsx +81 -99
  65. package/node_modules/@groove-dev/gui/src/components/ui/sheet.jsx +5 -2
  66. package/node_modules/@groove-dev/gui/src/lib/cron.js +64 -0
  67. package/node_modules/@groove-dev/gui/src/lib/status.js +24 -24
  68. package/node_modules/@groove-dev/gui/src/lib/theme-hex.js +1 -0
  69. package/node_modules/@groove-dev/gui/src/stores/groove.js +34 -3144
  70. package/node_modules/@groove-dev/gui/src/stores/helpers.js +10 -0
  71. package/node_modules/@groove-dev/gui/src/stores/slices/agents-slice.js +452 -0
  72. package/node_modules/@groove-dev/gui/src/stores/slices/automations-slice.js +96 -0
  73. package/node_modules/@groove-dev/gui/src/stores/slices/chat-slice.js +227 -0
  74. package/node_modules/@groove-dev/gui/src/stores/slices/editor-slice.js +285 -0
  75. package/node_modules/@groove-dev/gui/src/stores/slices/marketplace-slice.js +461 -0
  76. package/node_modules/@groove-dev/gui/src/stores/slices/network-slice.js +361 -0
  77. package/node_modules/@groove-dev/gui/src/stores/slices/preview-slice.js +109 -0
  78. package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +897 -0
  79. package/node_modules/@groove-dev/gui/src/stores/slices/teams-slice.js +413 -0
  80. package/node_modules/@groove-dev/gui/src/stores/slices/ui-slice.js +98 -0
  81. package/node_modules/@groove-dev/gui/src/views/agents.jsx +5 -5
  82. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +12 -13
  83. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +191 -3
  84. package/node_modules/@groove-dev/gui/src/views/model-lab.jsx +17 -6
  85. package/node_modules/@groove-dev/gui/src/views/models.jsx +410 -509
  86. package/node_modules/@groove-dev/gui/src/views/network.jsx +3 -3
  87. package/node_modules/@groove-dev/gui/src/views/settings.jsx +81 -94
  88. package/node_modules/@groove-dev/gui/src/views/teams.jsx +40 -483
  89. package/package.json +1 -1
  90. package/packages/cli/package.json +1 -1
  91. package/packages/daemon/package.json +1 -1
  92. package/packages/daemon/src/api.js +1086 -6532
  93. package/packages/daemon/src/gateways/manager.js +35 -1
  94. package/packages/daemon/src/index.js +3 -0
  95. package/packages/daemon/src/journalist.js +23 -13
  96. package/packages/daemon/src/mlx-server.js +365 -0
  97. package/packages/daemon/src/model-lab.js +308 -12
  98. package/packages/daemon/src/pm.js +1 -1
  99. package/packages/daemon/src/process.js +2 -2
  100. package/packages/daemon/src/providers/local.js +36 -8
  101. package/packages/daemon/src/registry.js +21 -5
  102. package/packages/daemon/src/routes/agents.js +889 -0
  103. package/packages/daemon/src/routes/coordination.js +318 -0
  104. package/packages/daemon/src/routes/files.js +751 -0
  105. package/packages/daemon/src/routes/integrations.js +485 -0
  106. package/packages/daemon/src/routes/network.js +1784 -0
  107. package/packages/daemon/src/routes/providers.js +755 -0
  108. package/packages/daemon/src/routes/schedules.js +110 -0
  109. package/packages/daemon/src/routes/teams.js +650 -0
  110. package/packages/daemon/src/scheduler.js +456 -24
  111. package/packages/daemon/src/teams.js +1 -1
  112. package/packages/daemon/src/validate.js +38 -1
  113. package/packages/daemon/templates/mlx-setup.json +12 -0
  114. package/packages/daemon/templates/tgi-setup.json +1 -1
  115. package/packages/daemon/templates/vllm-setup.json +1 -1
  116. package/packages/gui/dist/assets/index-BcoF6_eF.js +1012 -0
  117. package/packages/gui/dist/assets/index-Dd7qhiEd.css +1 -0
  118. package/packages/gui/dist/index.html +2 -2
  119. package/packages/gui/package.json +1 -1
  120. package/{node_modules/@groove-dev/gui/src/app.jsx → packages/gui/src/App.jsx} +0 -2
  121. package/packages/gui/src/app.css +35 -0
  122. package/packages/gui/src/components/agents/agent-config.jsx +1 -128
  123. package/packages/gui/src/components/agents/agent-feed.jsx +144 -31
  124. package/packages/gui/src/components/agents/agent-node.jsx +8 -13
  125. package/packages/gui/src/components/agents/code-review.jsx +159 -122
  126. package/packages/gui/src/components/agents/diff-viewer.jsx +23 -23
  127. package/packages/gui/src/components/agents/journalist-panel.jsx +1 -1
  128. package/packages/gui/src/components/agents/spawn-wizard.jsx +2 -135
  129. package/packages/gui/src/components/automations/automation-card.jsx +274 -0
  130. package/packages/gui/src/components/automations/automation-wizard.jsx +1136 -0
  131. package/packages/gui/src/components/dashboard/activity-feed.jsx +3 -3
  132. package/packages/gui/src/components/dashboard/cache-ring.jsx +5 -5
  133. package/packages/gui/src/components/dashboard/context-gauges.jsx +6 -8
  134. package/packages/gui/src/components/dashboard/fleet-panel.jsx +8 -14
  135. package/packages/gui/src/components/dashboard/intel-panel.jsx +238 -656
  136. package/packages/gui/src/components/dashboard/kpi-card.jsx +3 -3
  137. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  138. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  139. package/packages/gui/src/components/dashboard/token-chart.jsx +4 -4
  140. package/packages/gui/src/components/editor/selection-menu.jsx +2 -0
  141. package/packages/gui/src/components/lab/lab-assistant.jsx +316 -82
  142. package/packages/gui/src/components/lab/metrics-panel.jsx +187 -32
  143. package/packages/gui/src/components/lab/parameter-panel.jsx +195 -14
  144. package/packages/gui/src/components/lab/runtime-config.jsx +286 -102
  145. package/packages/gui/src/components/layout/activity-bar.jsx +2 -4
  146. package/packages/gui/src/components/layout/terminal-panel.jsx +4 -2
  147. package/packages/gui/src/components/layout/welcome-splash.jsx +137 -108
  148. package/packages/gui/src/components/network/network-health.jsx +2 -2
  149. package/packages/gui/src/components/network/performance-dashboard.jsx +4 -4
  150. package/packages/gui/src/components/settings/ssh-wizard.jsx +81 -99
  151. package/packages/gui/src/components/ui/sheet.jsx +5 -2
  152. package/packages/gui/src/lib/cron.js +64 -0
  153. package/packages/gui/src/lib/status.js +24 -24
  154. package/packages/gui/src/lib/theme-hex.js +1 -0
  155. package/packages/gui/src/stores/groove.js +34 -3144
  156. package/packages/gui/src/stores/helpers.js +10 -0
  157. package/packages/gui/src/stores/slices/agents-slice.js +452 -0
  158. package/packages/gui/src/stores/slices/automations-slice.js +96 -0
  159. package/packages/gui/src/stores/slices/chat-slice.js +227 -0
  160. package/packages/gui/src/stores/slices/editor-slice.js +285 -0
  161. package/packages/gui/src/stores/slices/marketplace-slice.js +461 -0
  162. package/packages/gui/src/stores/slices/network-slice.js +361 -0
  163. package/packages/gui/src/stores/slices/preview-slice.js +109 -0
  164. package/packages/gui/src/stores/slices/providers-slice.js +897 -0
  165. package/packages/gui/src/stores/slices/teams-slice.js +413 -0
  166. package/packages/gui/src/stores/slices/ui-slice.js +98 -0
  167. package/packages/gui/src/views/agents.jsx +5 -5
  168. package/packages/gui/src/views/dashboard.jsx +12 -13
  169. package/packages/gui/src/views/marketplace.jsx +191 -3
  170. package/packages/gui/src/views/model-lab.jsx +17 -6
  171. package/packages/gui/src/views/models.jsx +410 -509
  172. package/packages/gui/src/views/network.jsx +3 -3
  173. package/packages/gui/src/views/settings.jsx +81 -94
  174. package/packages/gui/src/views/teams.jsx +40 -483
  175. package/SECURITY_SWEEP.md +0 -228
  176. package/TRAINING_DATA_v4.md +0 -6
  177. package/node_modules/@groove-dev/gui/dist/assets/index-Bjd91ufV.js +0 -984
  178. package/node_modules/@groove-dev/gui/dist/assets/index-BqdwIFn4.css +0 -1
  179. package/node_modules/@groove-dev/gui/src/components/agents/agent-chat.jsx +0 -322
  180. package/node_modules/@groove-dev/gui/src/views/preview.jsx +0 -6
  181. package/node_modules/@groove-dev/gui/src/views/subscription-panel.jsx +0 -327
  182. package/packages/gui/dist/assets/index-Bjd91ufV.js +0 -984
  183. package/packages/gui/dist/assets/index-BqdwIFn4.css +0 -1
  184. package/packages/gui/src/components/agents/agent-chat.jsx +0 -322
  185. package/packages/gui/src/views/preview.jsx +0 -6
  186. package/packages/gui/src/views/subscription-panel.jsx +0 -327
  187. package/test.py +0 -571
@@ -62,10 +62,6 @@ export function SpawnWizard() {
62
62
  const [model, setModel] = useState('');
63
63
  const [prompt, setPrompt] = useState('');
64
64
  const [providers, setProviders] = useState([]);
65
- const [installedSkills, setInstalledSkills] = useState([]);
66
- const [selectedSkills, setSelectedSkills] = useState([]);
67
- const [skillModalOpen, setSkillModalOpen] = useState(false);
68
- const [skillSearch, setSkillSearch] = useState('');
69
65
  const [installedIntegrations, setInstalledIntegrations] = useState([]);
70
66
  const [selectedIntegrations, setSelectedIntegrations] = useState([]);
71
67
  const [integrationModalOpen, setIntegrationModalOpen] = useState(false);
@@ -82,7 +78,6 @@ export function SpawnWizard() {
82
78
  const [selectedPeerId, setSelectedPeerId] = useState('');
83
79
  const [recommendations, setRecommendations] = useState([]);
84
80
  const [preflightDialog, setPreflightDialog] = useState(null);
85
- const [claudeAuth, setClaudeAuth] = useState(null);
86
81
  const [ollamaInstalled, setOllamaInstalled] = useState([]);
87
82
  const [ollamaServerRunning, setOllamaServerRunning] = useState(false);
88
83
  const federation = useGrooveStore((s) => s.federation);
@@ -108,9 +103,6 @@ export function SpawnWizard() {
108
103
  setProvider(best);
109
104
  }
110
105
  }).catch(() => {});
111
- api.get('/skills/installed').then((data) => {
112
- setInstalledSkills(Array.isArray(data) ? data : []);
113
- }).catch(() => {});
114
106
  api.get('/integrations/installed').then((data) => {
115
107
  setInstalledIntegrations(Array.isArray(data) ? data : []);
116
108
  }).catch(() => {});
@@ -123,7 +115,6 @@ export function SpawnWizard() {
123
115
  setRole(''); setCustomRole(''); setName('');
124
116
  setProvider(_presetProvider); setModel(_presetModel);
125
117
  setPrompt('');
126
- setSelectedSkills([]);
127
118
  setSelectedIntegrations([]);
128
119
  setIntegrationApproval('manual');
129
120
  setSelectedRepos([]);
@@ -132,7 +123,6 @@ export function SpawnWizard() {
132
123
  setShowAdvanced(false);
133
124
  setRecommendations([]);
134
125
  setPreflightDialog(null);
135
- setClaudeAuth(null);
136
126
  }
137
127
  }, [open, fetchProviders]);
138
128
 
@@ -150,13 +140,6 @@ export function SpawnWizard() {
150
140
  }).catch(() => setRecommendations([]));
151
141
  }, [selectedRole, open]);
152
142
 
153
- useEffect(() => {
154
- if (!open || provider !== 'claude-code') { setClaudeAuth(null); return; }
155
- api.get('/providers/claude-code/auth').then((data) => {
156
- setClaudeAuth(data);
157
- }).catch(() => setClaudeAuth(null));
158
- }, [open, provider]);
159
-
160
143
  useEffect(() => {
161
144
  if (!open || provider !== 'ollama') { setOllamaInstalled([]); return; }
162
145
  api.get('/providers/ollama/models').then((data) => {
@@ -176,7 +159,6 @@ export function SpawnWizard() {
176
159
  ...(provider && { provider }),
177
160
  ...(model && { model }),
178
161
  ...(prompt && { prompt }),
179
- ...(selectedSkills.length > 0 && { skills: selectedSkills }),
180
162
  ...(selectedIntegrations.length > 0 && { integrations: selectedIntegrations }),
181
163
  ...(selectedIntegrations.length > 0 && { integrationApproval }),
182
164
  ...(selectedRepos.length > 0 && { repos: selectedRepos }),
@@ -204,11 +186,9 @@ export function SpawnWizard() {
204
186
  runSpawn();
205
187
  }
206
188
 
207
- const claudeNotAuthed = provider === 'claude-code' && claudeAuth && !claudeAuth.authenticated;
208
-
209
189
  return (
210
190
  <Sheet open={open} onOpenChange={(o) => { if (!o) closeDetail(); }}>
211
- <SheetContent title="Spawn Agent" width={480}>
191
+ <SheetContent title="Spawn Agent" width={480} onClose={() => closeDetail()}>
212
192
  <div className="flex flex-col h-[calc(100%-57px)]">
213
193
  {/* Scrollable content */}
214
194
  <div className="flex-1 overflow-y-auto px-5 py-4 space-y-6">
@@ -479,119 +459,6 @@ export function SpawnWizard() {
479
459
  </div>
480
460
  )}
481
461
 
482
- {/* Claude Code Auth */}
483
- {claudeNotAuthed && (
484
- <div className="rounded-lg border border-warning/30 bg-warning/5 px-4 py-3">
485
- <div className="flex items-center gap-2 mb-2">
486
- <AlertTriangle size={13} className="text-warning flex-shrink-0" />
487
- <span className="text-xs font-semibold text-text-0 font-sans">Claude Code is not signed in</span>
488
- </div>
489
- <p className="text-2xs text-text-2 font-sans">
490
- 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>
491
- </p>
492
- </div>
493
- )}
494
- {provider === 'claude-code' && claudeAuth?.authenticated && (
495
- <div className="flex items-center gap-2 text-2xs text-text-2 font-sans">
496
- <div className="w-2 h-2 rounded-full bg-success flex-shrink-0" />
497
- Signed in as {claudeAuth.email || 'Claude user'} ({claudeAuth.subscriptionType || 'subscription'})
498
- </div>
499
- )}
500
-
501
- {/* Skills */}
502
- <div className="space-y-1.5">
503
- <label className="text-xs font-medium text-text-2 font-sans">Skills</label>
504
- <div className="flex flex-wrap items-center gap-1.5">
505
- {selectedSkills.map((skillId) => {
506
- const skill = installedSkills.find((s) => s.id === skillId);
507
- return (
508
- <span
509
- key={skillId}
510
- className="inline-flex items-center gap-1 px-2 py-1 rounded bg-accent/12 text-accent border border-accent/25 text-2xs font-sans"
511
- >
512
- <Sparkles size={9} />
513
- {skill?.name || skillId}
514
- <button
515
- onClick={() => setSelectedSkills((prev) => prev.filter((s) => s !== skillId))}
516
- className="ml-0.5 hover:text-text-0 cursor-pointer"
517
- >
518
- <X size={9} />
519
- </button>
520
- </span>
521
- );
522
- })}
523
- <button
524
- onClick={() => { setSkillModalOpen(true); setSkillSearch(''); }}
525
- className={cn(
526
- 'inline-flex items-center gap-1.5 px-2.5 py-1 rounded text-2xs font-sans transition-colors cursor-pointer',
527
- 'bg-surface-0 text-text-2 border border-border-subtle hover:border-border hover:text-text-0',
528
- )}
529
- >
530
- <Sparkles size={10} />
531
- {selectedSkills.length > 0 ? 'Add skill' : 'Attach skill'}
532
- </button>
533
- </div>
534
- </div>
535
-
536
- {/* Skill picker modal */}
537
- <Dialog open={skillModalOpen} onOpenChange={setSkillModalOpen}>
538
- <DialogContent title="Select Skill" className="max-w-sm">
539
- <div className="space-y-3 p-4">
540
- <div className="relative">
541
- <Search size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-4" />
542
- <input
543
- value={skillSearch}
544
- onChange={(e) => setSkillSearch(e.target.value)}
545
- placeholder="Search skills..."
546
- autoFocus
547
- className="w-full h-8 pl-8 pr-3 text-xs rounded-md bg-surface-0 border border-border text-text-0 font-sans focus:outline-none focus:ring-1 focus:ring-accent"
548
- />
549
- </div>
550
- <div className="max-h-64 overflow-y-auto space-y-1">
551
- {installedSkills
552
- .filter((s) => {
553
- if (!skillSearch) return true;
554
- const q = skillSearch.toLowerCase();
555
- return (s.name || s.id).toLowerCase().includes(q) || (s.description || '').toLowerCase().includes(q);
556
- })
557
- .map((skill) => {
558
- const active = selectedSkills.includes(skill.id);
559
- return (
560
- <button
561
- key={skill.id}
562
- onClick={() => {
563
- setSelectedSkills((prev) =>
564
- active ? prev.filter((s) => s !== skill.id) : [...prev, skill.id]
565
- );
566
- }}
567
- className={cn(
568
- 'w-full flex items-center gap-2.5 px-3 py-2 rounded-md text-left transition-colors cursor-pointer',
569
- active
570
- ? 'bg-accent/10 border border-accent/25'
571
- : 'hover:bg-surface-3 border border-transparent',
572
- )}
573
- >
574
- <Sparkles size={12} className={active ? 'text-accent' : 'text-text-3'} />
575
- <div className="flex-1 min-w-0">
576
- <div className="text-xs font-semibold text-text-0 font-sans truncate">{skill.name || skill.id}</div>
577
- {skill.description && (
578
- <div className="text-2xs text-text-3 font-sans truncate">{skill.description}</div>
579
- )}
580
- </div>
581
- {active && <CheckMark />}
582
- </button>
583
- );
584
- })}
585
- {installedSkills.length === 0 && (
586
- <div className="text-center py-6 text-xs text-text-3 font-sans">
587
- No skills installed. Visit the Marketplace to install skills.
588
- </div>
589
- )}
590
- </div>
591
- </div>
592
- </DialogContent>
593
- </Dialog>
594
-
595
462
  {/* Integrations */}
596
463
  <div className="space-y-1.5">
597
464
  <label className="text-xs font-medium text-text-2 font-sans">Integrations</label>
@@ -900,7 +767,7 @@ export function SpawnWizard() {
900
767
  variant="primary"
901
768
  size="lg"
902
769
  onClick={handleSpawn}
903
- disabled={!selectedRole || spawning || installedProviders.length === 0 || claudeNotAuthed}
770
+ disabled={!selectedRole || spawning || installedProviders.length === 0}
904
771
  className="w-full"
905
772
  >
906
773
  {spawning ? 'Spawning...' : 'Spawn Agent'}
@@ -0,0 +1,274 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { useState, useRef, useEffect } from 'react';
3
+ import { useGrooveStore } from '../../stores/groove';
4
+ import { Badge } from '../ui/badge';
5
+ import { Button } from '../ui/button';
6
+ import { StatusDot } from '../ui/status-dot';
7
+ import { cronToHuman } from '../../lib/cron';
8
+ import { timeAgo, fmtDollar, fmtUptime } from '../../lib/format';
9
+ import { cn } from '../../lib/cn';
10
+ import {
11
+ Play, Pause, Clock, MoreHorizontal, Copy, Pencil, Trash2,
12
+ FileText, Folder, ExternalLink,
13
+ } from 'lucide-react';
14
+
15
+ export function AutomationCard({ automation }) {
16
+ const toggleAutomation = useGrooveStore((s) => s.toggleAutomation);
17
+ const deleteAutomation = useGrooveStore((s) => s.deleteAutomation);
18
+ const duplicateAutomation = useGrooveStore((s) => s.duplicateAutomation);
19
+ const runAutomation = useGrooveStore((s) => s.runAutomation);
20
+ const setEditingAutomation = useGrooveStore((s) => s.setEditingAutomation);
21
+ const openWizard = useGrooveStore((s) => s.openAutomationWizard);
22
+ const openDetail = useGrooveStore((s) => s.openDetail);
23
+ const agents = useGrooveStore((s) => s.agents);
24
+
25
+ const [menuOpen, setMenuOpen] = useState(false);
26
+ const menuRef = useRef(null);
27
+
28
+ useEffect(() => {
29
+ if (!menuOpen) return;
30
+ function handleClick(e) {
31
+ if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
32
+ }
33
+ document.addEventListener('mousedown', handleClick);
34
+ return () => document.removeEventListener('mousedown', handleClick);
35
+ }, [menuOpen]);
36
+
37
+ const a = automation;
38
+ const roles = a.teamConfig || (a.agentConfig ? [a.agentConfig] : (a.role ? [{ role: a.role }] : []));
39
+ const instructions = a.instructionSource?.type === 'inline'
40
+ ? a.instructionSource.content
41
+ : a.instructionSource?.type === 'file'
42
+ ? null
43
+ : a.prompt || null;
44
+ const filePath = a.instructionSource?.type === 'file' ? a.instructionSource.filePath : null;
45
+ const gatewayIds = a.outputConfig?.gatewayIds || [];
46
+ const lastStatus = a.lastRunStatus || (a.lastRunAt ? 'completed' : null);
47
+
48
+ const activeAgents = (a.activeAgentIds || [])
49
+ .map((id) => agents.find((ag) => ag.id === id))
50
+ .filter(Boolean);
51
+
52
+ const lastRunAgentIds = a.lastRun?.agentId
53
+ ? a.lastRun.agentId.split(',').filter(Boolean)
54
+ : [];
55
+ const lastRunAgents = !a.isRunning
56
+ ? lastRunAgentIds.map((id) => agents.find((ag) => ag.id === id)).filter(Boolean)
57
+ : [];
58
+
59
+ function openAgentPanel(agentId) {
60
+ openDetail({ type: 'agent', agentId });
61
+ }
62
+
63
+ return (
64
+ <div className={cn(
65
+ 'rounded-md border bg-surface-1 overflow-hidden transition-colors',
66
+ a.enabled ? 'border-border-subtle' : 'border-border-subtle/50 opacity-75',
67
+ )}>
68
+ {/* Top row */}
69
+ <div className="px-4 py-3 flex items-start gap-3">
70
+ <div className="flex-1 min-w-0">
71
+ <div className="flex items-center gap-2">
72
+ <span className="text-sm font-semibold text-text-0 font-sans truncate">{a.name}</span>
73
+ <Badge variant={a.enabled ? 'success' : 'default'} className="text-2xs flex-shrink-0">
74
+ {a.enabled ? 'Active' : 'Paused'}
75
+ </Badge>
76
+ </div>
77
+ {a.description && (
78
+ <p className="text-2xs text-text-3 font-sans truncate mt-0.5">{a.description}</p>
79
+ )}
80
+ </div>
81
+
82
+ <div className="flex items-center gap-1.5 flex-shrink-0">
83
+ <button
84
+ onClick={() => toggleAutomation(a.id, a.enabled)}
85
+ className={cn(
86
+ 'p-1.5 rounded transition-colors cursor-pointer',
87
+ a.enabled ? 'text-success hover:text-success/80' : 'text-text-4 hover:text-text-2',
88
+ )}
89
+ title={a.enabled ? 'Pause automation' : 'Enable automation'}
90
+ >
91
+ {a.enabled ? <Pause size={13} /> : <Play size={13} />}
92
+ </button>
93
+
94
+ <div ref={menuRef} className="relative">
95
+ <button
96
+ onClick={() => setMenuOpen(!menuOpen)}
97
+ className="p-1.5 rounded text-text-4 hover:text-text-2 transition-colors cursor-pointer"
98
+ >
99
+ <MoreHorizontal size={13} />
100
+ </button>
101
+ {menuOpen && (
102
+ <div className="absolute right-0 top-full mt-1 z-50 min-w-[150px] bg-surface-2 border border-border rounded-md shadow-lg py-1">
103
+ <button
104
+ onClick={() => { setEditingAutomation(a.id); openWizard(); setMenuOpen(false); }}
105
+ className="w-full text-left px-3 py-1.5 text-xs font-sans text-text-1 hover:bg-surface-5 transition-colors cursor-pointer flex items-center gap-2"
106
+ >
107
+ <Pencil size={10} /> Edit
108
+ </button>
109
+ <button
110
+ onClick={() => { duplicateAutomation(a.id); setMenuOpen(false); }}
111
+ className="w-full text-left px-3 py-1.5 text-xs font-sans text-text-1 hover:bg-surface-5 transition-colors cursor-pointer flex items-center gap-2"
112
+ >
113
+ <Copy size={10} /> Duplicate
114
+ </button>
115
+ <button
116
+ onClick={() => { runAutomation(a.id); setMenuOpen(false); }}
117
+ className="w-full text-left px-3 py-1.5 text-xs font-sans text-text-1 hover:bg-surface-5 transition-colors cursor-pointer flex items-center gap-2"
118
+ >
119
+ <Play size={10} /> Run Now
120
+ </button>
121
+ <div className="h-px my-1 bg-border-subtle" />
122
+ <button
123
+ onClick={() => { deleteAutomation(a.id); setMenuOpen(false); }}
124
+ className="w-full text-left px-3 py-1.5 text-xs font-sans text-danger hover:bg-danger/5 transition-colors cursor-pointer flex items-center gap-2"
125
+ >
126
+ <Trash2 size={10} /> Delete
127
+ </button>
128
+ </div>
129
+ )}
130
+ </div>
131
+ </div>
132
+ </div>
133
+
134
+ {/* Team strip */}
135
+ {roles.length > 0 && (
136
+ <div className="px-4 pb-2 flex items-center gap-1.5 flex-wrap">
137
+ {roles.map((r, i) => (
138
+ <Badge key={i} variant="default" className="text-2xs">
139
+ {r.role}{r.phase ? ` P${r.phase}` : ''}
140
+ </Badge>
141
+ ))}
142
+ {a.integrationIds?.length > 0 && (
143
+ <span className="text-2xs text-text-4 font-sans ml-1">+{a.integrationIds.length} integrations</span>
144
+ )}
145
+ </div>
146
+ )}
147
+
148
+ {/* Instructions preview */}
149
+ {(instructions || filePath) && (
150
+ <div className="px-4 pb-2">
151
+ {filePath ? (
152
+ <div className="flex items-center gap-1.5">
153
+ <Folder size={10} className="text-text-4 flex-shrink-0" />
154
+ <span className="text-2xs font-mono text-text-3 truncate">{filePath}</span>
155
+ </div>
156
+ ) : (
157
+ <p className="text-2xs text-text-3 font-sans truncate">
158
+ <FileText size={10} className="inline mr-1 text-text-4" />
159
+ {instructions.slice(0, 100)}{instructions.length > 100 ? '...' : ''}
160
+ </p>
161
+ )}
162
+ </div>
163
+ )}
164
+
165
+ {/* Schedule + last run row */}
166
+ <div className="px-4 py-2 border-t border-border-subtle bg-surface-0 flex items-center gap-3 flex-wrap">
167
+ <div className="flex items-center gap-1.5">
168
+ <Clock size={10} className="text-text-4" />
169
+ <span className="text-2xs font-mono text-text-2">{cronToHuman(a.cron)}</span>
170
+ </div>
171
+ {a.nextRunAt && (
172
+ <span className="text-2xs font-mono text-text-3">
173
+ Next: {timeAgo(a.nextRunAt)}
174
+ </span>
175
+ )}
176
+ {a.lastRunAt && (
177
+ <div className="flex items-center gap-1.5">
178
+ <span className="text-2xs font-mono text-text-3">Last: {timeAgo(a.lastRunAt)}</span>
179
+ <StatusDot
180
+ status={lastStatus === 'error' ? 'crashed' : lastStatus === 'running' ? 'running' : 'completed'}
181
+ size="sm"
182
+ />
183
+ </div>
184
+ )}
185
+ {!a.lastRunAt && (
186
+ <span className="text-2xs text-text-4 font-sans">Never run</span>
187
+ )}
188
+ </div>
189
+
190
+ {/* Output config */}
191
+ {(gatewayIds.length > 0 || a.outputConfig?.filePath || a.outputConfig?.customInstructions) && (
192
+ <div className="px-4 py-1.5 border-t border-border-subtle bg-surface-0 space-y-1">
193
+ {gatewayIds.length > 0 && (
194
+ <div className="flex items-center gap-1.5">
195
+ <span className="text-2xs text-text-4 font-sans">Gateways:</span>
196
+ <span className="text-2xs font-sans text-text-3">{gatewayIds.join(', ')}</span>
197
+ </div>
198
+ )}
199
+ {a.outputConfig?.filePath && (
200
+ <div className="flex items-center gap-1.5">
201
+ <span className="text-2xs text-text-4 font-sans">File:</span>
202
+ <span className="text-2xs font-mono text-text-3 truncate">{a.outputConfig.filePath}</span>
203
+ </div>
204
+ )}
205
+ {a.outputConfig?.customInstructions && (
206
+ <div className="flex items-center gap-1.5">
207
+ <span className="text-2xs text-text-4 font-sans">Custom:</span>
208
+ <span className="text-2xs font-sans text-text-3 truncate">{a.outputConfig.customInstructions.slice(0, 80)}{a.outputConfig.customInstructions.length > 80 ? '...' : ''}</span>
209
+ </div>
210
+ )}
211
+ </div>
212
+ )}
213
+
214
+ {/* Active agents */}
215
+ {activeAgents.length > 0 && (
216
+ <div className="px-4 py-2 border-t border-border-subtle bg-surface-0">
217
+ <div className="flex items-center gap-1.5 mb-1.5">
218
+ <StatusDot status="running" size="sm" />
219
+ <span className="text-2xs font-sans text-text-2 font-medium">Running</span>
220
+ </div>
221
+ <div className="flex items-center gap-1.5 flex-wrap">
222
+ {activeAgents.map((ag) => (
223
+ <button
224
+ key={ag.id}
225
+ onClick={() => openAgentPanel(ag.id)}
226
+ className="flex items-center gap-1.5 px-2 py-1 rounded bg-surface-4 hover:bg-surface-5 transition-colors cursor-pointer group"
227
+ >
228
+ <StatusDot status={ag.status} size="sm" />
229
+ <span className="text-2xs font-sans text-text-1 group-hover:text-text-0">{ag.name || ag.role}</span>
230
+ <ExternalLink size={9} className="text-text-4 group-hover:text-accent" />
231
+ </button>
232
+ ))}
233
+ </div>
234
+ </div>
235
+ )}
236
+
237
+ {/* Last run agents (when not currently running) */}
238
+ {!a.isRunning && lastRunAgents.length > 0 && (
239
+ <div className="px-4 py-2 border-t border-border-subtle bg-surface-0">
240
+ <div className="flex items-center gap-1.5 mb-1.5">
241
+ <span className="text-2xs font-sans text-text-3">Last run agents</span>
242
+ </div>
243
+ <div className="flex items-center gap-1.5 flex-wrap">
244
+ {lastRunAgents.map((ag) => (
245
+ <button
246
+ key={ag.id}
247
+ onClick={() => openAgentPanel(ag.id)}
248
+ className="flex items-center gap-1.5 px-2 py-1 rounded bg-surface-4 hover:bg-surface-5 transition-colors cursor-pointer group"
249
+ >
250
+ <StatusDot status={ag.status} size="sm" />
251
+ <span className="text-2xs font-sans text-text-2 group-hover:text-text-0">{ag.name || ag.role}</span>
252
+ <ExternalLink size={9} className="text-text-4 group-hover:text-accent" />
253
+ </button>
254
+ ))}
255
+ </div>
256
+ </div>
257
+ )}
258
+
259
+ {/* Bottom bar */}
260
+ <div className="px-4 py-2 border-t border-border-subtle flex items-center gap-3">
261
+ <Button variant="ghost" size="sm" onClick={() => runAutomation(a.id)} className="h-6 px-2 text-2xs gap-1">
262
+ <Play size={10} /> Run Now
263
+ </Button>
264
+ <div className="flex-1" />
265
+ {a.lastRunDuration != null && (
266
+ <span className="text-2xs font-mono text-text-4">{fmtUptime(a.lastRunDuration / 1000)}</span>
267
+ )}
268
+ {a.lastRunCost != null && a.lastRunCost > 0 && (
269
+ <span className="text-2xs font-mono text-text-4">{fmtDollar(a.lastRunCost)}</span>
270
+ )}
271
+ </div>
272
+ </div>
273
+ );
274
+ }