groove-dev 0.27.7 → 0.27.11

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 (127) hide show
  1. package/CLAUDE.md +0 -7
  2. package/node_modules/@groove-dev/daemon/src/api.js +496 -44
  3. package/node_modules/@groove-dev/daemon/src/gateways/manager.js +25 -12
  4. package/node_modules/@groove-dev/daemon/src/index.js +7 -0
  5. package/node_modules/@groove-dev/daemon/src/introducer.js +72 -4
  6. package/node_modules/@groove-dev/daemon/src/journalist.js +66 -11
  7. package/node_modules/@groove-dev/daemon/src/process.js +128 -104
  8. package/node_modules/@groove-dev/daemon/src/registry.js +1 -1
  9. package/node_modules/@groove-dev/daemon/src/repo-import.js +541 -0
  10. package/node_modules/@groove-dev/daemon/src/rotator.js +28 -1
  11. package/node_modules/@groove-dev/daemon/src/supervisor.js +2 -1
  12. package/node_modules/@groove-dev/daemon/src/tunnel-manager.js +504 -0
  13. package/node_modules/@groove-dev/daemon/src/validate.js +13 -0
  14. package/node_modules/@groove-dev/daemon/test/journalist.test.js +5 -4
  15. package/node_modules/@groove-dev/daemon/test/rotator.test.js +4 -1
  16. package/node_modules/@groove-dev/gui/dist/assets/index-BE6lYcd7.css +1 -0
  17. package/node_modules/@groove-dev/gui/dist/assets/index-zdzOLAZM.js +677 -0
  18. package/node_modules/@groove-dev/gui/dist/index.html +2 -2
  19. package/node_modules/@groove-dev/gui/src/app.css +14 -0
  20. package/node_modules/@groove-dev/gui/src/app.jsx +13 -0
  21. package/node_modules/@groove-dev/gui/src/components/agents/agent-config.jsx +130 -1
  22. package/node_modules/@groove-dev/gui/src/components/agents/agent-feed.jsx +2 -2
  23. package/node_modules/@groove-dev/gui/src/components/agents/agent-mdfiles.jsx +43 -1
  24. package/node_modules/@groove-dev/gui/src/components/agents/agent-node.jsx +16 -17
  25. package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +141 -1
  26. package/node_modules/@groove-dev/gui/src/components/dashboard/fleet-panel.jsx +3 -3
  27. package/node_modules/@groove-dev/gui/src/components/dashboard/intel-panel.jsx +8 -8
  28. package/node_modules/@groove-dev/gui/src/components/dashboard/routing-chart.jsx +3 -3
  29. package/node_modules/@groove-dev/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  30. package/node_modules/@groove-dev/gui/src/components/layout/activity-bar.jsx +4 -4
  31. package/node_modules/@groove-dev/gui/src/components/layout/app-shell.jsx +7 -1
  32. package/node_modules/@groove-dev/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
  33. package/node_modules/@groove-dev/gui/src/components/layout/command-palette.jsx +14 -4
  34. package/node_modules/@groove-dev/gui/src/components/layout/status-bar.jsx +46 -11
  35. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-card.jsx +64 -0
  36. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-import.jsx +363 -0
  37. package/node_modules/@groove-dev/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
  38. package/node_modules/@groove-dev/gui/src/components/pro/pro-gate.jsx +22 -0
  39. package/node_modules/@groove-dev/gui/src/components/pro/upgrade-card.jsx +48 -0
  40. package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +129 -0
  41. package/node_modules/@groove-dev/gui/src/components/settings/remote-server-card.jsx +243 -0
  42. package/node_modules/@groove-dev/gui/src/components/settings/server-dialog.jsx +192 -0
  43. package/node_modules/@groove-dev/gui/src/components/ui/approval-modal.jsx +63 -0
  44. package/node_modules/@groove-dev/gui/src/components/ui/toast.jsx +1 -1
  45. package/node_modules/@groove-dev/gui/src/lib/edition.js +4 -0
  46. package/node_modules/@groove-dev/gui/src/lib/electron.js +17 -0
  47. package/node_modules/@groove-dev/gui/src/lib/status.js +1 -0
  48. package/node_modules/@groove-dev/gui/src/stores/groove.js +150 -6
  49. package/node_modules/@groove-dev/gui/src/views/dashboard.jsx +39 -40
  50. package/node_modules/@groove-dev/gui/src/views/marketplace.jsx +82 -0
  51. package/node_modules/@groove-dev/gui/src/views/settings.jsx +66 -0
  52. package/node_modules/@groove-dev/gui/vite.config.js +3 -0
  53. package/package.json +7 -2
  54. package/packages/daemon/src/api.js +496 -44
  55. package/packages/daemon/src/gateways/manager.js +25 -12
  56. package/packages/daemon/src/index.js +7 -0
  57. package/packages/daemon/src/introducer.js +72 -4
  58. package/packages/daemon/src/journalist.js +66 -11
  59. package/packages/daemon/src/process.js +128 -104
  60. package/packages/daemon/src/registry.js +1 -1
  61. package/packages/daemon/src/repo-import.js +541 -0
  62. package/packages/daemon/src/rotator.js +28 -1
  63. package/packages/daemon/src/supervisor.js +2 -1
  64. package/packages/daemon/src/tunnel-manager.js +504 -0
  65. package/packages/daemon/src/validate.js +13 -0
  66. package/packages/gui/dist/assets/index-BE6lYcd7.css +1 -0
  67. package/packages/gui/dist/assets/index-zdzOLAZM.js +677 -0
  68. package/packages/gui/dist/index.html +2 -2
  69. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-html.js +3 -3
  70. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-javascript.js +2 -2
  71. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-markdown.js +3 -3
  72. package/packages/gui/node_modules/.vite/deps/@codemirror_lang-python.js +5 -5
  73. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-dialog.js +3 -3
  74. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-scroll-area.js +1 -1
  75. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tabs.js +5 -5
  76. package/packages/gui/node_modules/.vite/deps/@radix-ui_react-tooltip.js +3 -3
  77. package/packages/gui/node_modules/.vite/deps/_metadata.json +53 -53
  78. package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js → chunk-DH7AESXW.js} +2 -2
  79. package/packages/gui/node_modules/.vite/deps/{chunk-KXLIKZFX.js → chunk-GFE3G4IN.js} +133 -133
  80. package/packages/gui/node_modules/.vite/deps/chunk-GFE3G4IN.js.map +7 -0
  81. package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js → chunk-LKZVMLRH.js} +6 -6
  82. package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js → chunk-MCVDVNE5.js} +2 -2
  83. package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js → chunk-SPKVQGZX.js} +6 -6
  84. package/packages/gui/src/app.css +14 -0
  85. package/packages/gui/src/app.jsx +13 -0
  86. package/packages/gui/src/components/agents/agent-config.jsx +130 -1
  87. package/packages/gui/src/components/agents/agent-feed.jsx +2 -2
  88. package/packages/gui/src/components/agents/agent-mdfiles.jsx +43 -1
  89. package/packages/gui/src/components/agents/agent-node.jsx +16 -17
  90. package/packages/gui/src/components/agents/spawn-wizard.jsx +141 -1
  91. package/packages/gui/src/components/dashboard/fleet-panel.jsx +3 -3
  92. package/packages/gui/src/components/dashboard/intel-panel.jsx +8 -8
  93. package/packages/gui/src/components/dashboard/routing-chart.jsx +3 -3
  94. package/packages/gui/src/components/dashboard/team-burn-panel.jsx +1 -1
  95. package/packages/gui/src/components/layout/activity-bar.jsx +4 -4
  96. package/packages/gui/src/components/layout/app-shell.jsx +7 -1
  97. package/packages/gui/src/components/layout/breadcrumb-bar.jsx +26 -8
  98. package/packages/gui/src/components/layout/command-palette.jsx +14 -4
  99. package/packages/gui/src/components/layout/status-bar.jsx +46 -11
  100. package/packages/gui/src/components/marketplace/repo-card.jsx +64 -0
  101. package/packages/gui/src/components/marketplace/repo-import.jsx +363 -0
  102. package/packages/gui/src/components/marketplace/repo-nuke-dialog.jsx +68 -0
  103. package/packages/gui/src/components/pro/pro-gate.jsx +22 -0
  104. package/packages/gui/src/components/pro/upgrade-card.jsx +48 -0
  105. package/packages/gui/src/components/settings/quick-connect.jsx +129 -0
  106. package/packages/gui/src/components/settings/remote-server-card.jsx +243 -0
  107. package/packages/gui/src/components/settings/server-dialog.jsx +192 -0
  108. package/packages/gui/src/components/ui/approval-modal.jsx +63 -0
  109. package/packages/gui/src/components/ui/toast.jsx +1 -1
  110. package/packages/gui/src/lib/edition.js +4 -0
  111. package/packages/gui/src/lib/electron.js +17 -0
  112. package/packages/gui/src/lib/status.js +1 -0
  113. package/packages/gui/src/stores/groove.js +150 -6
  114. package/packages/gui/src/views/dashboard.jsx +39 -40
  115. package/packages/gui/src/views/marketplace.jsx +82 -0
  116. package/packages/gui/src/views/settings.jsx +66 -0
  117. package/packages/gui/vite.config.js +3 -0
  118. package/node_modules/@groove-dev/gui/dist/assets/index-Bl1_J0sN.js +0 -652
  119. package/node_modules/@groove-dev/gui/dist/assets/index-DjORRpF0.css +0 -1
  120. package/packages/gui/dist/assets/index-Bl1_J0sN.js +0 -652
  121. package/packages/gui/dist/assets/index-DjORRpF0.css +0 -1
  122. package/packages/gui/node_modules/.vite/deps/chunk-KXLIKZFX.js.map +0 -7
  123. package/test-slack.mjs +0 -28
  124. /package/packages/gui/node_modules/.vite/deps/{chunk-WYSQD5ZG.js.map → chunk-DH7AESXW.js.map} +0 -0
  125. /package/packages/gui/node_modules/.vite/deps/{chunk-3LBP22MX.js.map → chunk-LKZVMLRH.js.map} +0 -0
  126. /package/packages/gui/node_modules/.vite/deps/{chunk-J6DMOQWP.js.map → chunk-MCVDVNE5.js.map} +0 -0
  127. /package/packages/gui/node_modules/.vite/deps/{chunk-3Q7HT7ZF.js.map → chunk-SPKVQGZX.js.map} +0 -0
@@ -27,13 +27,13 @@ export function ActivityBar({ activeView, detailPanel, onNavigate, onTogglePanel
27
27
  <button
28
28
  onClick={() => onNavigate(item.id)}
29
29
  className={cn(
30
- 'w-10 h-10 flex items-center justify-center rounded-md transition-colors cursor-pointer',
30
+ 'w-8 h-8 flex items-center justify-center rounded-md transition-colors cursor-pointer',
31
31
  activeView === item.id
32
32
  ? 'text-text-0 bg-surface-5'
33
33
  : 'text-text-3 hover:text-text-1 hover:bg-surface-4',
34
34
  )}
35
35
  >
36
- <item.icon size={20} strokeWidth={activeView === item.id ? 2 : 1.5} />
36
+ <item.icon size={16} strokeWidth={activeView === item.id ? 2 : 1.5} />
37
37
  </button>
38
38
  </Tooltip>
39
39
  ))}
@@ -53,13 +53,13 @@ export function ActivityBar({ activeView, detailPanel, onNavigate, onTogglePanel
53
53
  <button
54
54
  onClick={() => item.panel ? onTogglePanel(item.id) : onNavigate(item.id)}
55
55
  className={cn(
56
- 'w-10 h-10 flex items-center justify-center rounded-md transition-colors cursor-pointer',
56
+ 'w-8 h-8 flex items-center justify-center rounded-md transition-colors cursor-pointer',
57
57
  isActive
58
58
  ? 'text-text-0 bg-surface-5'
59
59
  : 'text-text-3 hover:text-text-1 hover:bg-surface-4',
60
60
  )}
61
61
  >
62
- <item.icon size={20} strokeWidth={isActive ? 2 : 1.5} />
62
+ <item.icon size={16} strokeWidth={isActive ? 2 : 1.5} />
63
63
  </button>
64
64
  </Tooltip>
65
65
  );
@@ -2,6 +2,8 @@
2
2
  import { useMemo } from 'react';
3
3
  import { useGrooveStore } from '../../stores/groove';
4
4
  import { useKeyboard } from '../../lib/hooks/use-keyboard';
5
+ import { isElectron } from '../../lib/electron';
6
+ import { cn } from '../../lib/cn';
5
7
  import { TooltipProvider } from '../ui/tooltip';
6
8
  import { ToastContainer } from '../ui/toast';
7
9
  import { ActivityBar } from './activity-bar';
@@ -9,6 +11,8 @@ import { BreadcrumbBar } from './breadcrumb-bar';
9
11
  import { StatusBar } from './status-bar';
10
12
  import { DetailPanel } from './detail-panel';
11
13
  import { CommandPalette } from './command-palette';
14
+ import { ApprovalModal } from '../ui/approval-modal';
15
+ import { QuickConnect } from '../settings/quick-connect';
12
16
  import { TeamTabBar } from '../../views/agents';
13
17
 
14
18
  export function AppShell({ children, detailContent, terminalContent }) {
@@ -55,7 +59,7 @@ export function AppShell({ children, detailContent, terminalContent }) {
55
59
 
56
60
  return (
57
61
  <TooltipProvider>
58
- <div className="w-full h-full flex flex-col bg-surface-2 text-text-1 font-sans">
62
+ <div className={cn('w-full h-full flex flex-col bg-surface-2 text-text-1 font-sans', isElectron() && 'electron-app')}>
59
63
  <BreadcrumbBar
60
64
  activeView={activeView}
61
65
  connected={connected}
@@ -113,6 +117,8 @@ export function AppShell({ children, detailContent, terminalContent }) {
113
117
  />
114
118
 
115
119
  <CommandPalette />
120
+ <QuickConnect />
121
+ <ApprovalModal />
116
122
  <ToastContainer />
117
123
  </div>
118
124
  </TooltipProvider>
@@ -1,6 +1,7 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
2
  import { Search, Plus, ChevronRight } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
+ import { isElectron, getPlatform } from '../../lib/electron';
4
5
 
5
6
  const VIEW_LABELS = {
6
7
  agents: 'Agents',
@@ -23,17 +24,34 @@ export function BreadcrumbBar({
23
24
  crumbs.push(editorActiveFile.split('/').pop());
24
25
  }
25
26
 
27
+ const electron = isElectron();
28
+ const darwinDrag = electron && getPlatform() === 'darwin';
29
+
26
30
  return (
27
- <header className="h-11 flex-shrink-0 flex items-center gap-3 px-4 bg-surface-3 border-b border-border">
31
+ <header
32
+ className={cn(
33
+ 'h-11 flex-shrink-0 flex items-center gap-3 px-4 bg-surface-3 border-b border-border',
34
+ darwinDrag && 'pl-20 electron-drag electron-no-drag-children',
35
+ )}
36
+ >
28
37
  {/* Logo */}
29
38
  <img src="/favicon.png" alt="Groove" className="h-7 w-7 rounded-full flex-shrink-0" />
30
39
 
31
- {/* Host badge */}
32
- {daemonHost && (
33
- <span className="text-2xs font-mono font-semibold text-text-3 bg-surface-5 px-1.5 py-0.5 rounded flex-shrink-0">
34
- {daemonHost}
35
- </span>
36
- )}
40
+ {/* Host badge — show instance name from ?instance= or raw host */}
41
+ {(() => {
42
+ const instance = new URLSearchParams(window.location.search).get('instance');
43
+ if (instance) return (
44
+ <span className="text-2xs font-mono font-semibold text-accent bg-accent/10 px-1.5 py-0.5 rounded flex-shrink-0">
45
+ {instance}
46
+ </span>
47
+ );
48
+ if (daemonHost) return (
49
+ <span className="text-2xs font-mono font-semibold text-text-3 bg-surface-5 px-1.5 py-0.5 rounded flex-shrink-0">
50
+ {daemonHost}
51
+ </span>
52
+ );
53
+ return null;
54
+ })()}
37
55
 
38
56
  <div className="flex-1 min-w-4" />
39
57
 
@@ -43,7 +61,7 @@ export function BreadcrumbBar({
43
61
  className={cn(
44
62
  'flex items-center gap-2.5 h-8 px-4 rounded-full w-full max-w-md',
45
63
  'bg-surface-1 border border-border-subtle',
46
- 'text-sm text-text-4 font-sans',
64
+ 'text-xs text-text-4 font-sans',
47
65
  'hover:border-border hover:text-text-3 transition-colors cursor-pointer',
48
66
  )}
49
67
  >
@@ -4,7 +4,7 @@ import { useGrooveStore } from '../../stores/groove';
4
4
  import {
5
5
  Network, Code2, ChartSpline, Puzzle, Users, Plus,
6
6
  RotateCw, Skull, MessageSquare, Terminal, Newspaper,
7
- Search,
7
+ Search, Radio, ExternalLink,
8
8
  } from 'lucide-react';
9
9
  import { cn } from '../../lib/cn';
10
10
  import { AnimatePresence, motion } from 'framer-motion';
@@ -26,6 +26,7 @@ export function CommandPalette() {
26
26
  const open = useGrooveStore((s) => s.commandPaletteOpen);
27
27
  const toggle = useGrooveStore((s) => s.toggleCommandPalette);
28
28
  const agents = useGrooveStore((s) => s.agents);
29
+ const savedTunnels = useGrooveStore((s) => s.savedTunnels);
29
30
  const store = useGrooveStore;
30
31
 
31
32
  const [query, setQuery] = useState('');
@@ -41,8 +42,17 @@ export function CommandPalette() {
41
42
  { id: `kill:${a.id}`, label: `Kill ${a.name}`, icon: Skull, category: 'Agents', action: (s) => { s.killAgent(a.id); } },
42
43
  ] : []),
43
44
  ]);
44
- return [...STATIC_COMMANDS, ...agentCommands];
45
- }, [agents]);
45
+ const tunnelCommands = [
46
+ { id: 'action:quickconnect', label: 'Quick Connect', icon: Radio, category: 'Remote', action: (s) => { s.toggleQuickConnect(); } },
47
+ ...savedTunnels.map((t) => t.active
48
+ ? { id: `tunnel:open:${t.id}`, label: `Open ${t.name}`, icon: ExternalLink, category: 'Remote', action: () => {
49
+ window.open(`http://localhost:${t.localPort}?instance=${encodeURIComponent(t.name)}`, '_blank');
50
+ }}
51
+ : { id: `tunnel:connect:${t.id}`, label: `Connect to ${t.name}`, icon: Radio, category: 'Remote', action: (s) => { s.connectTunnel(t.id); } }
52
+ ),
53
+ ];
54
+ return [...STATIC_COMMANDS, ...agentCommands, ...tunnelCommands];
55
+ }, [agents, savedTunnels]);
46
56
 
47
57
  // Filter
48
58
  const filtered = useMemo(() => {
@@ -104,7 +114,7 @@ export function CommandPalette() {
104
114
  value={query}
105
115
  onChange={(e) => { setQuery(e.target.value); setSelectedIndex(0); }}
106
116
  placeholder="Type a command..."
107
- className="flex-1 bg-transparent text-sm text-text-0 font-sans placeholder:text-text-4 focus:outline-none"
117
+ className="flex-1 bg-transparent text-xs text-text-0 font-sans placeholder:text-text-4 focus:outline-none"
108
118
  />
109
119
  </div>
110
120
 
@@ -1,8 +1,10 @@
1
1
  // FSL-1.1-Apache-2.0 — see LICENSE
2
- import { Terminal, BookOpen } from 'lucide-react';
2
+ import { Terminal, BookOpen, Radio, Plug } from 'lucide-react';
3
3
  import { cn } from '../../lib/cn';
4
4
  import { StatusDot } from '../ui/status-dot';
5
5
  import { fmtUptime } from '../../lib/format';
6
+ import { useGrooveStore } from '../../stores/groove';
7
+ import { isElectron, openExternal } from '../../lib/electron';
6
8
 
7
9
  export function StatusBar({
8
10
  connected,
@@ -12,6 +14,10 @@ export function StatusBar({
12
14
  terminalVisible,
13
15
  onToggleTerminal,
14
16
  }) {
17
+ const savedTunnels = useGrooveStore((s) => s.savedTunnels);
18
+ const activeTunnel = savedTunnels.find((t) => t.active);
19
+ const electron = isElectron();
20
+
15
21
  return (
16
22
  <footer className="h-6 flex-shrink-0 flex items-center px-3 bg-surface-3 border-t border-border text-2xs font-sans select-none">
17
23
  {/* Left: connection + stats */}
@@ -19,7 +25,7 @@ export function StatusBar({
19
25
  <div className="flex items-center gap-1.5">
20
26
  <StatusDot status={connected ? 'running' : 'crashed'} size="sm" />
21
27
  <span className={connected ? 'text-text-2' : 'text-danger'}>
22
- {connected ? 'Connected' : 'Offline'}
28
+ {connected ? (electron ? 'Desktop' : 'Connected') : 'Offline'}
23
29
  </span>
24
30
  </div>
25
31
  {connected && uptime > 0 && (
@@ -28,20 +34,49 @@ export function StatusBar({
28
34
  {connected && agentCount > 0 && (
29
35
  <span className="text-text-4">{runningCount}/{agentCount} agents</span>
30
36
  )}
37
+ {activeTunnel ? (
38
+ <button
39
+ onClick={() => {
40
+ const port = activeTunnel.localPort;
41
+ const name = encodeURIComponent(activeTunnel.name);
42
+ openExternal(`http://localhost:${port}?instance=${name}`);
43
+ }}
44
+ className="flex items-center gap-1.5 text-text-3 hover:text-text-1 cursor-pointer transition-colors"
45
+ title="Open remote GUI"
46
+ >
47
+ <Radio size={10} className="text-success" />
48
+ <span>{activeTunnel.name}</span>
49
+ <span className="w-1.5 h-1.5 rounded-full bg-success" />
50
+ {activeTunnel.latencyMs != null && (
51
+ <span className="text-text-4">{activeTunnel.latencyMs}ms</span>
52
+ )}
53
+ </button>
54
+ ) : savedTunnels.length > 0 && (
55
+ <button
56
+ onClick={() => useGrooveStore.getState().toggleQuickConnect()}
57
+ className="flex items-center gap-1.5 text-text-4 hover:text-text-1 cursor-pointer transition-colors"
58
+ title="Quick Connect to remote server"
59
+ >
60
+ <Plug size={10} />
61
+ <span>Connect</span>
62
+ </button>
63
+ )}
31
64
  </div>
32
65
 
33
66
  <div className="flex-1" />
34
67
 
35
68
  {/* Right: docs + terminal toggle */}
36
- <a
37
- href="https://docs.groovedev.ai"
38
- target="_blank"
39
- rel="noopener noreferrer"
40
- className="flex items-center gap-1.5 px-2 h-full text-text-3 hover:text-text-1 hover:bg-surface-5 transition-colors no-underline"
41
- >
42
- <BookOpen size={12} />
43
- <span>Docs</span>
44
- </a>
69
+ {!electron && (
70
+ <a
71
+ href="https://docs.groovedev.ai"
72
+ target="_blank"
73
+ rel="noopener noreferrer"
74
+ className="flex items-center gap-1.5 px-2 h-full text-text-3 hover:text-text-1 hover:bg-surface-5 transition-colors no-underline"
75
+ >
76
+ <BookOpen size={12} />
77
+ <span>Docs</span>
78
+ </a>
79
+ )}
45
80
  <button
46
81
  onClick={onToggleTerminal}
47
82
  className={cn(
@@ -0,0 +1,64 @@
1
+ // FSL-1.1-Apache-2.0 — see LICENSE
2
+ import { FolderOpen, Trash2, Bomb } from 'lucide-react';
3
+ import { Badge } from '../ui/badge';
4
+ import { cn } from '../../lib/cn';
5
+ import { timeAgo } from '../../lib/format';
6
+
7
+ export function RepoCard({ repo, onRemove, onNuke, onOpen }) {
8
+ return (
9
+ <div className="rounded-lg border border-border-subtle bg-surface-2 p-3 flex items-center gap-3">
10
+ <div className="flex-1 min-w-0">
11
+ <div className="flex items-center gap-2">
12
+ <span className="text-xs font-semibold text-text-0 font-sans truncate">{repo.repoName || repo.name}</span>
13
+ <span className="text-2xs text-text-3 font-sans">{repo.repoOwner || repo.owner}</span>
14
+ </div>
15
+ <div className="flex items-center gap-2 mt-0.5">
16
+ {repo.language && <Badge variant="outline" className="text-2xs">{repo.language}</Badge>}
17
+ <span className="text-2xs text-text-4 font-mono truncate max-w-[180px]">{repo.clonedTo || repo.path}</span>
18
+ </div>
19
+ <span className="text-2xs text-text-4 font-sans mt-0.5 block">
20
+ {repo.clonedAt ? `Imported ${timeAgo(repo.clonedAt)}` : repo.status || ''}
21
+ </span>
22
+ </div>
23
+
24
+ <div className="flex items-center gap-1 flex-shrink-0">
25
+ {onOpen && (
26
+ <button
27
+ onClick={() => onOpen(repo)}
28
+ className={cn(
29
+ 'flex items-center gap-1 px-2 py-1 rounded text-2xs font-sans cursor-pointer',
30
+ 'text-accent bg-accent/10 hover:bg-accent/20 border-0 transition-colors',
31
+ )}
32
+ >
33
+ <FolderOpen size={11} />
34
+ Open
35
+ </button>
36
+ )}
37
+ {onRemove && (
38
+ <button
39
+ onClick={() => onRemove(repo)}
40
+ className={cn(
41
+ 'flex items-center gap-1 px-2 py-1 rounded text-2xs font-sans cursor-pointer',
42
+ 'text-text-3 hover:text-text-1 hover:bg-surface-4 bg-transparent border-0 transition-colors',
43
+ )}
44
+ >
45
+ <Trash2 size={11} />
46
+ Remove
47
+ </button>
48
+ )}
49
+ {onNuke && (
50
+ <button
51
+ onClick={() => onNuke(repo)}
52
+ className={cn(
53
+ 'flex items-center gap-1 px-2 py-1 rounded text-2xs font-sans cursor-pointer',
54
+ 'text-danger bg-danger/10 hover:bg-danger/20 border-0 transition-colors',
55
+ )}
56
+ >
57
+ <Bomb size={11} />
58
+ Nuke
59
+ </button>
60
+ )}
61
+ </div>
62
+ </div>
63
+ );
64
+ }