groove-dev 0.27.18 → 0.27.19

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.
@@ -10,7 +10,8 @@ import { RootNode } from '../components/agents/root-node';
10
10
  import { cn } from '../lib/cn';
11
11
  import { Button } from '../components/ui/button';
12
12
  import { Badge } from '../components/ui/badge';
13
- import { Plus, Users, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, ChevronLeft, ChevronRight, FolderOpen } from 'lucide-react';
13
+ import { Plus, Users, Zap, X, Check, Rocket, Server, Monitor, Code2, TestTube, Shield, Pencil, Copy, Trash2, ChevronLeft, ChevronRight, FolderOpen } from 'lucide-react';
14
+ import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuSeparator } from '../components/ui/context-menu';
14
15
 
15
16
  const NODE_TYPES = { agentNode: AgentNode, rootNode: RootNode };
16
17
  const NODE_W = 220;
@@ -78,7 +79,7 @@ export function TeamTabBar() {
78
79
  const createTeam = useGrooveStore((s) => s.createTeam);
79
80
  const deleteTeam = useGrooveStore((s) => s.deleteTeam);
80
81
  const renameTeam = useGrooveStore((s) => s.renameTeam);
81
-
82
+ const cloneTeam = useGrooveStore((s) => s.cloneTeam);
82
83
  const reorderTeams = useGrooveStore((s) => s.reorderTeams);
83
84
 
84
85
  const [creating, setCreating] = useState(false);
@@ -158,102 +159,96 @@ export function TeamTabBar() {
158
159
  const running = agents.filter((a) => a.teamId === team.id && (a.status === 'running' || a.status === 'starting')).length;
159
160
 
160
161
  return (
161
- <div
162
- key={team.id}
163
- draggable={!isRenaming}
164
- onDragStart={(e) => { setDragId(team.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', ''); }}
165
- onDragEnd={() => { setDragId(null); setDragOverId(null); }}
166
- onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (dragId && dragId !== team.id) setDragOverId(team.id); }}
167
- onDragLeave={() => { if (dragOverId === team.id) setDragOverId(null); }}
168
- onDrop={(e) => {
169
- e.preventDefault();
170
- if (!dragId || dragId === team.id) return;
171
- const from = teams.findIndex((t) => t.id === dragId);
172
- const to = teams.findIndex((t) => t.id === team.id);
173
- if (from !== -1 && to !== -1) reorderTeams(from, to);
174
- setDragId(null);
175
- setDragOverId(null);
176
- }}
177
- onClick={() => !isRenaming && switchTeam(team.id)}
178
- onDoubleClick={() => startRename(team)}
179
- className={cn(
180
- 'group relative flex items-center gap-2 px-4 h-9 text-xs font-sans cursor-pointer select-none transition-colors flex-shrink-0',
181
- isActive
182
- ? 'text-text-0 font-semibold border-x border-x-border bg-[#242830]'
183
- : 'text-text-3 hover:text-text-1 hover:bg-surface-3/50',
184
- dragId === team.id && 'opacity-40',
185
- dragOverId === team.id && dragId !== team.id && 'border-l-2 !border-l-accent',
186
- )}
187
- >
188
- {/* Thin accent line at top */}
189
- {isActive && <div className="absolute top-0 left-0 right-0 h-px bg-accent" style={{ height: '0.5px' }} />}
190
- {(() => {
191
- const status = teamStatus(agents, team.id);
192
- const iconColor = status === 'working' ? 'text-green-400'
193
- : status === 'completed' ? 'text-green-400'
194
- : status === 'crashed' ? 'text-red-400'
195
- : isActive ? 'text-accent' : 'text-text-4';
196
- return (
197
- <span className="relative flex-shrink-0">
198
- <Users size={13} className={cn(iconColor, status === 'working' && 'animate-pulse')} />
199
- {status === 'working' && (
200
- <span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
201
- )}
202
- </span>
203
- );
204
- })()}
205
-
206
- {isRenaming ? (
207
- <input
208
- value={renameValue}
209
- onChange={(e) => setRenameValue(e.target.value)}
210
- onKeyDown={(e) => { if (e.key === 'Enter') handleRename(); if (e.key === 'Escape') setRenamingId(null); }}
211
- onBlur={handleRename}
212
- className="h-5 w-24 px-1.5 text-xs bg-surface-0 border border-accent rounded text-text-0 font-sans focus:outline-none"
213
- autoFocus
214
- onClick={(e) => e.stopPropagation()}
215
- />
216
- ) : (
217
- <span className="truncate max-w-[120px]">{team.name}</span>
218
- )}
219
-
220
- {/* Agent count badge */}
221
- {count > 0 && !isRenaming && (
222
- <span className={cn(
223
- 'flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full text-2xs font-mono font-semibold',
224
- running > 0 ? 'bg-accent/15 text-accent' : 'bg-surface-4 text-text-3',
225
- )}>
226
- {count}
227
- </span>
228
- )}
229
-
230
- {/* Actions — rename + close */}
231
- {!isRenaming && (
232
- <div className="flex items-center gap-0.5 ml-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
233
- <button
234
- onClick={(e) => { e.stopPropagation(); startRename(team); }}
235
- className="p-0.5 rounded hover:bg-surface-5 text-text-4 hover:text-text-1 cursor-pointer"
236
- title="Rename team"
237
- >
238
- <Pencil size={10} />
239
- </button>
240
- {!team.isDefault && (
241
- <button
242
- onClick={(e) => { e.stopPropagation(); deleteTeam(team.id); }}
243
- className="p-0.5 rounded hover:bg-surface-5 text-text-4 hover:text-danger cursor-pointer"
244
- title="Delete team"
245
- >
246
- <X size={10} />
247
- </button>
162
+ <ContextMenu key={team.id}>
163
+ <ContextMenuTrigger asChild>
164
+ <div
165
+ draggable={!isRenaming}
166
+ onDragStart={(e) => { setDragId(team.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', ''); }}
167
+ onDragEnd={() => { setDragId(null); setDragOverId(null); }}
168
+ onDragOver={(e) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (dragId && dragId !== team.id) setDragOverId(team.id); }}
169
+ onDragLeave={() => { if (dragOverId === team.id) setDragOverId(null); }}
170
+ onDrop={(e) => {
171
+ e.preventDefault();
172
+ if (!dragId || dragId === team.id) return;
173
+ const from = teams.findIndex((t) => t.id === dragId);
174
+ const to = teams.findIndex((t) => t.id === team.id);
175
+ if (from !== -1 && to !== -1) reorderTeams(from, to);
176
+ setDragId(null);
177
+ setDragOverId(null);
178
+ }}
179
+ onClick={() => !isRenaming && switchTeam(team.id)}
180
+ onDoubleClick={() => startRename(team)}
181
+ className={cn(
182
+ 'relative flex items-center gap-2 px-3 h-9 text-xs font-sans cursor-pointer select-none transition-colors flex-shrink-0',
183
+ isActive
184
+ ? 'text-text-0 font-semibold border-x border-x-border bg-[#242830]'
185
+ : 'text-text-3 hover:text-text-1 hover:bg-surface-3/50',
186
+ dragId === team.id && 'opacity-40',
187
+ dragOverId === team.id && dragId !== team.id && 'border-l-2 !border-l-accent',
188
+ )}
189
+ >
190
+ {isActive && <div className="absolute top-0 left-0 right-0 h-px bg-accent" style={{ height: '0.5px' }} />}
191
+ {(() => {
192
+ const status = teamStatus(agents, team.id);
193
+ const iconColor = status === 'working' ? 'text-green-400'
194
+ : status === 'completed' ? 'text-green-400'
195
+ : status === 'crashed' ? 'text-red-400'
196
+ : isActive ? 'text-accent' : 'text-text-4';
197
+ return (
198
+ <span className="relative flex-shrink-0">
199
+ <Users size={13} className={cn(iconColor, status === 'working' && 'animate-pulse')} />
200
+ {status === 'working' && (
201
+ <span className="absolute -top-0.5 -right-0.5 w-1.5 h-1.5 rounded-full bg-green-400 animate-pulse" />
202
+ )}
203
+ </span>
204
+ );
205
+ })()}
206
+
207
+ {isRenaming ? (
208
+ <input
209
+ value={renameValue}
210
+ onChange={(e) => setRenameValue(e.target.value)}
211
+ onKeyDown={(e) => { if (e.key === 'Enter') handleRename(); if (e.key === 'Escape') setRenamingId(null); }}
212
+ onBlur={handleRename}
213
+ className="h-5 w-24 px-1.5 text-xs bg-surface-0 border border-accent rounded text-text-0 font-sans focus:outline-none"
214
+ autoFocus
215
+ onClick={(e) => e.stopPropagation()}
216
+ />
217
+ ) : (
218
+ <span className="truncate max-w-[120px]">{team.name}</span>
248
219
  )}
249
- </div>
250
- )}
251
220
 
252
- {/* Bottom edge hides the parent border for active tab */}
253
- {isActive && (
254
- <div className="absolute bottom-[-1px] left-0 right-0 h-px bg-[#242830]" />
255
- )}
256
- </div>
221
+ {count > 0 && !isRenaming && (
222
+ <span className={cn(
223
+ 'flex items-center justify-center min-w-[18px] h-[18px] px-1 rounded-full text-2xs font-mono font-semibold',
224
+ running > 0 ? 'bg-accent/15 text-accent' : 'bg-surface-4 text-text-3',
225
+ )}>
226
+ {count}
227
+ </span>
228
+ )}
229
+
230
+ {isActive && (
231
+ <div className="absolute bottom-[-1px] left-0 right-0 h-px bg-[#242830]" />
232
+ )}
233
+ </div>
234
+ </ContextMenuTrigger>
235
+ <ContextMenuContent>
236
+ <ContextMenuItem onSelect={() => startRename(team)}>
237
+ <Pencil size={12} /> Rename
238
+ </ContextMenuItem>
239
+ <ContextMenuItem onSelect={() => cloneTeam(team.id)}>
240
+ <Copy size={12} /> Clone
241
+ </ContextMenuItem>
242
+ {!team.isDefault && (
243
+ <>
244
+ <ContextMenuSeparator />
245
+ <ContextMenuItem danger onSelect={() => deleteTeam(team.id)}>
246
+ <Trash2 size={12} /> Delete
247
+ </ContextMenuItem>
248
+ </>
249
+ )}
250
+ </ContextMenuContent>
251
+ </ContextMenu>
257
252
  );
258
253
  })}
259
254
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groove-dev",
3
- "version": "0.27.18",
3
+ "version": "0.27.19",
4
4
  "description": "Open-source agent orchestration layer — the AI company OS. Local model agent engine (GGUF/Ollama/llama-server), HuggingFace model browser, MCP integrations (Slack, Gmail, Stripe, 15+), agent scheduling (cron), business roles (CMO, CFO, EA). GUI dashboard, multi-agent coordination, zero cold-start, infinite sessions. Works with Claude Code, Codex, Gemini CLI, Ollama, any local model.",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "author": "Groove Dev <hello@groovedev.ai> (https://groovedev.ai)",