@evolve.labs/devflow 0.8.0
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.
- package/.claude/commands/agents/architect.md +1162 -0
- package/.claude/commands/agents/architect.meta.yaml +124 -0
- package/.claude/commands/agents/builder.md +1432 -0
- package/.claude/commands/agents/builder.meta.yaml +117 -0
- package/.claude/commands/agents/chronicler.md +633 -0
- package/.claude/commands/agents/chronicler.meta.yaml +217 -0
- package/.claude/commands/agents/guardian.md +456 -0
- package/.claude/commands/agents/guardian.meta.yaml +127 -0
- package/.claude/commands/agents/strategist.md +483 -0
- package/.claude/commands/agents/strategist.meta.yaml +158 -0
- package/.claude/commands/agents/system-designer.md +1137 -0
- package/.claude/commands/agents/system-designer.meta.yaml +156 -0
- package/.claude/commands/devflow-help.md +93 -0
- package/.claude/commands/devflow-status.md +60 -0
- package/.claude/commands/quick/create-adr.md +82 -0
- package/.claude/commands/quick/new-feature.md +57 -0
- package/.claude/commands/quick/security-check.md +54 -0
- package/.claude/commands/quick/system-design.md +58 -0
- package/.claude_project +52 -0
- package/.devflow/agents/architect.meta.yaml +122 -0
- package/.devflow/agents/builder.meta.yaml +116 -0
- package/.devflow/agents/chronicler.meta.yaml +222 -0
- package/.devflow/agents/guardian.meta.yaml +127 -0
- package/.devflow/agents/strategist.meta.yaml +158 -0
- package/.devflow/agents/system-designer.meta.yaml +265 -0
- package/.devflow/project.yaml +242 -0
- package/.gitignore-template +84 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/bin/devflow.js +54 -0
- package/lib/autopilot.js +235 -0
- package/lib/autopilotConstants.js +213 -0
- package/lib/constants.js +95 -0
- package/lib/init.js +200 -0
- package/lib/update.js +181 -0
- package/lib/utils.js +157 -0
- package/lib/web.js +119 -0
- package/package.json +57 -0
- package/web/CHANGELOG.md +192 -0
- package/web/README.md +156 -0
- package/web/app/api/autopilot/execute/route.ts +102 -0
- package/web/app/api/autopilot/terminal-execute/route.ts +124 -0
- package/web/app/api/files/route.ts +280 -0
- package/web/app/api/files/tree/route.ts +160 -0
- package/web/app/api/git/route.ts +201 -0
- package/web/app/api/health/route.ts +94 -0
- package/web/app/api/project/open/route.ts +134 -0
- package/web/app/api/search/route.ts +247 -0
- package/web/app/api/specs/route.ts +405 -0
- package/web/app/api/terminal/route.ts +222 -0
- package/web/app/globals.css +160 -0
- package/web/app/ide/layout.tsx +43 -0
- package/web/app/ide/page.tsx +216 -0
- package/web/app/layout.tsx +34 -0
- package/web/app/page.tsx +303 -0
- package/web/components/agents/AgentIcons.tsx +281 -0
- package/web/components/autopilot/AutopilotConfigModal.tsx +245 -0
- package/web/components/autopilot/AutopilotPanel.tsx +299 -0
- package/web/components/dashboard/DashboardPanel.tsx +393 -0
- package/web/components/editor/Breadcrumbs.tsx +134 -0
- package/web/components/editor/EditorPanel.tsx +120 -0
- package/web/components/editor/EditorTabs.tsx +229 -0
- package/web/components/editor/MarkdownPreview.tsx +154 -0
- package/web/components/editor/MermaidDiagram.tsx +113 -0
- package/web/components/editor/MonacoEditor.tsx +177 -0
- package/web/components/editor/TabContextMenu.tsx +207 -0
- package/web/components/git/GitPanel.tsx +534 -0
- package/web/components/layout/Shell.tsx +15 -0
- package/web/components/layout/StatusBar.tsx +100 -0
- package/web/components/modals/CommandPalette.tsx +393 -0
- package/web/components/modals/GlobalSearch.tsx +348 -0
- package/web/components/modals/QuickOpen.tsx +241 -0
- package/web/components/modals/RecentFiles.tsx +208 -0
- package/web/components/projects/ProjectSelector.tsx +147 -0
- package/web/components/settings/SettingItem.tsx +150 -0
- package/web/components/settings/SettingsPanel.tsx +323 -0
- package/web/components/specs/SpecsPanel.tsx +1091 -0
- package/web/components/terminal/TerminalPanel.tsx +683 -0
- package/web/components/ui/ContextMenu.tsx +182 -0
- package/web/components/ui/LoadingSpinner.tsx +66 -0
- package/web/components/ui/ResizeHandle.tsx +110 -0
- package/web/components/ui/Skeleton.tsx +108 -0
- package/web/components/ui/SkipLinks.tsx +37 -0
- package/web/components/ui/Toaster.tsx +57 -0
- package/web/hooks/useFocusTrap.ts +141 -0
- package/web/hooks/useKeyboardShortcuts.ts +169 -0
- package/web/hooks/useListNavigation.ts +237 -0
- package/web/lib/autopilotConstants.ts +213 -0
- package/web/lib/constants/agents.ts +67 -0
- package/web/lib/git.ts +339 -0
- package/web/lib/ptyManager.ts +191 -0
- package/web/lib/specsParser.ts +299 -0
- package/web/lib/stores/autopilotStore.ts +288 -0
- package/web/lib/stores/fileStore.ts +550 -0
- package/web/lib/stores/gitStore.ts +386 -0
- package/web/lib/stores/projectStore.ts +196 -0
- package/web/lib/stores/settingsStore.ts +126 -0
- package/web/lib/stores/specsStore.ts +297 -0
- package/web/lib/stores/uiStore.ts +175 -0
- package/web/lib/types/index.ts +177 -0
- package/web/lib/utils.ts +98 -0
- package/web/next.config.js +50 -0
- package/web/package.json +54 -0
- package/web/postcss.config.js +6 -0
- package/web/tailwind.config.ts +68 -0
- package/web/tsconfig.json +41 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
4
|
+
import { useUIStore } from '@/lib/stores/uiStore';
|
|
5
|
+
import { useProjectStore } from '@/lib/stores/projectStore';
|
|
6
|
+
import {
|
|
7
|
+
Command,
|
|
8
|
+
Search,
|
|
9
|
+
FileText,
|
|
10
|
+
GitBranch,
|
|
11
|
+
Terminal,
|
|
12
|
+
Moon,
|
|
13
|
+
Sun,
|
|
14
|
+
Save,
|
|
15
|
+
X,
|
|
16
|
+
Eye,
|
|
17
|
+
EyeOff,
|
|
18
|
+
LayoutPanelLeft,
|
|
19
|
+
Maximize2,
|
|
20
|
+
Minimize2,
|
|
21
|
+
} from 'lucide-react';
|
|
22
|
+
import { cn } from '@/lib/utils';
|
|
23
|
+
import { useFileStore } from '@/lib/stores/fileStore';
|
|
24
|
+
import { useFocusTrap } from '@/hooks/useFocusTrap';
|
|
25
|
+
|
|
26
|
+
interface CommandItem {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
description?: string;
|
|
30
|
+
icon: React.ReactNode;
|
|
31
|
+
shortcut?: string;
|
|
32
|
+
category: string;
|
|
33
|
+
action: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function CommandPalette() {
|
|
37
|
+
const [query, setQuery] = useState('');
|
|
38
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
39
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
40
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
41
|
+
const modalRef = useRef<HTMLDivElement>(null);
|
|
42
|
+
|
|
43
|
+
const { activeModal, closeModal, openModal } = useUIStore();
|
|
44
|
+
const {
|
|
45
|
+
toggleSidebar,
|
|
46
|
+
sidebarVisible,
|
|
47
|
+
toggleTerminal,
|
|
48
|
+
terminalVisible,
|
|
49
|
+
togglePreview,
|
|
50
|
+
previewVisible,
|
|
51
|
+
toggleTerminalMaximized,
|
|
52
|
+
terminalMaximized,
|
|
53
|
+
theme,
|
|
54
|
+
setTheme,
|
|
55
|
+
setActivePanel,
|
|
56
|
+
} = useUIStore();
|
|
57
|
+
|
|
58
|
+
const { currentProject } = useProjectStore();
|
|
59
|
+
const { activeFile, saveFile, openFiles } = useFileStore();
|
|
60
|
+
|
|
61
|
+
const isOpen = activeModal === 'commandPalette';
|
|
62
|
+
|
|
63
|
+
// Focus trap for accessibility
|
|
64
|
+
useFocusTrap(modalRef, isOpen, {
|
|
65
|
+
onEscape: closeModal,
|
|
66
|
+
autoFocus: false, // We handle focus manually to the input
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Define all commands
|
|
70
|
+
const commands: CommandItem[] = useMemo(() => [
|
|
71
|
+
// File Commands
|
|
72
|
+
{
|
|
73
|
+
id: 'quick-open',
|
|
74
|
+
label: 'Go to File',
|
|
75
|
+
description: 'Quickly open a file by name',
|
|
76
|
+
icon: <Search className="w-4 h-4" />,
|
|
77
|
+
shortcut: '⌘P',
|
|
78
|
+
category: 'File',
|
|
79
|
+
action: () => {
|
|
80
|
+
closeModal();
|
|
81
|
+
setTimeout(() => openModal('quickOpen'), 50);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'global-search',
|
|
86
|
+
label: 'Search in Files',
|
|
87
|
+
description: 'Search text in all files',
|
|
88
|
+
icon: <FileText className="w-4 h-4" />,
|
|
89
|
+
shortcut: '⌘⇧F',
|
|
90
|
+
category: 'File',
|
|
91
|
+
action: () => {
|
|
92
|
+
closeModal();
|
|
93
|
+
setTimeout(() => openModal('globalSearch'), 50);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'save-file',
|
|
98
|
+
label: 'Save File',
|
|
99
|
+
description: 'Save the current file',
|
|
100
|
+
icon: <Save className="w-4 h-4" />,
|
|
101
|
+
shortcut: '⌘S',
|
|
102
|
+
category: 'File',
|
|
103
|
+
action: () => {
|
|
104
|
+
if (activeFile) {
|
|
105
|
+
saveFile(activeFile);
|
|
106
|
+
}
|
|
107
|
+
closeModal();
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// View Commands
|
|
112
|
+
{
|
|
113
|
+
id: 'toggle-sidebar',
|
|
114
|
+
label: sidebarVisible ? 'Hide Sidebar' : 'Show Sidebar',
|
|
115
|
+
icon: <LayoutPanelLeft className="w-4 h-4" />,
|
|
116
|
+
shortcut: '⌘B',
|
|
117
|
+
category: 'View',
|
|
118
|
+
action: () => {
|
|
119
|
+
toggleSidebar();
|
|
120
|
+
closeModal();
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'toggle-terminal',
|
|
125
|
+
label: terminalVisible ? 'Hide Terminal' : 'Show Terminal',
|
|
126
|
+
icon: <Terminal className="w-4 h-4" />,
|
|
127
|
+
shortcut: '⌘`',
|
|
128
|
+
category: 'View',
|
|
129
|
+
action: () => {
|
|
130
|
+
toggleTerminal();
|
|
131
|
+
closeModal();
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'toggle-terminal-max',
|
|
136
|
+
label: terminalMaximized ? 'Restore Terminal' : 'Maximize Terminal',
|
|
137
|
+
icon: terminalMaximized ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />,
|
|
138
|
+
category: 'View',
|
|
139
|
+
action: () => {
|
|
140
|
+
toggleTerminalMaximized();
|
|
141
|
+
closeModal();
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'toggle-preview',
|
|
146
|
+
label: previewVisible ? 'Hide Preview' : 'Show Preview',
|
|
147
|
+
icon: previewVisible ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />,
|
|
148
|
+
shortcut: '⌘⇧V',
|
|
149
|
+
category: 'View',
|
|
150
|
+
action: () => {
|
|
151
|
+
togglePreview();
|
|
152
|
+
closeModal();
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Panel Commands
|
|
157
|
+
{
|
|
158
|
+
id: 'show-git',
|
|
159
|
+
label: 'Show Source Control',
|
|
160
|
+
icon: <GitBranch className="w-4 h-4" />,
|
|
161
|
+
category: 'Panels',
|
|
162
|
+
action: () => {
|
|
163
|
+
setActivePanel('git');
|
|
164
|
+
closeModal();
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'show-specs',
|
|
169
|
+
label: 'Show Specs',
|
|
170
|
+
icon: <FileText className="w-4 h-4" />,
|
|
171
|
+
category: 'Panels',
|
|
172
|
+
action: () => {
|
|
173
|
+
setActivePanel('specs');
|
|
174
|
+
closeModal();
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
// Theme Commands
|
|
179
|
+
{
|
|
180
|
+
id: 'theme-dark',
|
|
181
|
+
label: 'Dark Theme',
|
|
182
|
+
icon: <Moon className="w-4 h-4" />,
|
|
183
|
+
category: 'Preferences',
|
|
184
|
+
action: () => {
|
|
185
|
+
setTheme('dark');
|
|
186
|
+
closeModal();
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'theme-light',
|
|
191
|
+
label: 'Light Theme',
|
|
192
|
+
icon: <Sun className="w-4 h-4" />,
|
|
193
|
+
category: 'Preferences',
|
|
194
|
+
action: () => {
|
|
195
|
+
setTheme('light');
|
|
196
|
+
closeModal();
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
], [
|
|
200
|
+
activeFile, saveFile, sidebarVisible, terminalVisible,
|
|
201
|
+
previewVisible, terminalMaximized, theme, closeModal, openModal,
|
|
202
|
+
toggleSidebar, toggleTerminal, togglePreview,
|
|
203
|
+
toggleTerminalMaximized, setTheme, setActivePanel
|
|
204
|
+
]);
|
|
205
|
+
|
|
206
|
+
// Filter commands based on query
|
|
207
|
+
const filteredCommands = useMemo(() => {
|
|
208
|
+
if (!query.trim()) return commands;
|
|
209
|
+
|
|
210
|
+
const lowerQuery = query.toLowerCase();
|
|
211
|
+
return commands.filter((cmd) => {
|
|
212
|
+
const searchText = `${cmd.label} ${cmd.description || ''} ${cmd.category}`.toLowerCase();
|
|
213
|
+
return searchText.includes(lowerQuery);
|
|
214
|
+
});
|
|
215
|
+
}, [commands, query]);
|
|
216
|
+
|
|
217
|
+
// Group commands by category
|
|
218
|
+
const groupedCommands = useMemo(() => {
|
|
219
|
+
const groups: Record<string, CommandItem[]> = {};
|
|
220
|
+
for (const cmd of filteredCommands) {
|
|
221
|
+
if (!groups[cmd.category]) {
|
|
222
|
+
groups[cmd.category] = [];
|
|
223
|
+
}
|
|
224
|
+
groups[cmd.category].push(cmd);
|
|
225
|
+
}
|
|
226
|
+
return groups;
|
|
227
|
+
}, [filteredCommands]);
|
|
228
|
+
|
|
229
|
+
// Flatten for keyboard navigation
|
|
230
|
+
const flatCommands = useMemo(() => {
|
|
231
|
+
return Object.values(groupedCommands).flat();
|
|
232
|
+
}, [groupedCommands]);
|
|
233
|
+
|
|
234
|
+
// Focus input when opened
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (isOpen) {
|
|
237
|
+
setQuery('');
|
|
238
|
+
setSelectedIndex(0);
|
|
239
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
240
|
+
}
|
|
241
|
+
}, [isOpen]);
|
|
242
|
+
|
|
243
|
+
// Keyboard navigation
|
|
244
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
245
|
+
switch (e.key) {
|
|
246
|
+
case 'ArrowDown':
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
setSelectedIndex((i) => Math.min(i + 1, flatCommands.length - 1));
|
|
249
|
+
break;
|
|
250
|
+
case 'ArrowUp':
|
|
251
|
+
e.preventDefault();
|
|
252
|
+
setSelectedIndex((i) => Math.max(i - 1, 0));
|
|
253
|
+
break;
|
|
254
|
+
case 'Enter':
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
if (flatCommands[selectedIndex]) {
|
|
257
|
+
flatCommands[selectedIndex].action();
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
case 'Escape':
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
closeModal();
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Scroll selected item into view
|
|
268
|
+
useEffect(() => {
|
|
269
|
+
const listElement = listRef.current;
|
|
270
|
+
if (!listElement) return;
|
|
271
|
+
|
|
272
|
+
const items = listElement.querySelectorAll('[data-command-item]');
|
|
273
|
+
const selectedElement = items[selectedIndex] as HTMLElement;
|
|
274
|
+
if (selectedElement) {
|
|
275
|
+
selectedElement.scrollIntoView({ block: 'nearest' });
|
|
276
|
+
}
|
|
277
|
+
}, [selectedIndex]);
|
|
278
|
+
|
|
279
|
+
if (!isOpen) return null;
|
|
280
|
+
|
|
281
|
+
let currentIndex = 0;
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<div className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh]">
|
|
285
|
+
{/* Backdrop */}
|
|
286
|
+
<div
|
|
287
|
+
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
|
|
288
|
+
onClick={closeModal}
|
|
289
|
+
aria-hidden="true"
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
{/* Modal */}
|
|
293
|
+
<div
|
|
294
|
+
ref={modalRef}
|
|
295
|
+
className="relative w-full max-w-xl bg-[#12121a] border border-white/10 rounded-xl shadow-2xl overflow-hidden"
|
|
296
|
+
role="dialog"
|
|
297
|
+
aria-modal="true"
|
|
298
|
+
aria-label="Command palette"
|
|
299
|
+
>
|
|
300
|
+
{/* Search Input */}
|
|
301
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-white/10">
|
|
302
|
+
<Command className="w-5 h-5 text-purple-400" />
|
|
303
|
+
<input
|
|
304
|
+
ref={inputRef}
|
|
305
|
+
type="text"
|
|
306
|
+
value={query}
|
|
307
|
+
onChange={(e) => {
|
|
308
|
+
setQuery(e.target.value);
|
|
309
|
+
setSelectedIndex(0);
|
|
310
|
+
}}
|
|
311
|
+
onKeyDown={handleKeyDown}
|
|
312
|
+
placeholder="Type a command..."
|
|
313
|
+
className="flex-1 bg-transparent text-white placeholder-gray-500 outline-none text-sm"
|
|
314
|
+
autoComplete="off"
|
|
315
|
+
spellCheck={false}
|
|
316
|
+
/>
|
|
317
|
+
{query && (
|
|
318
|
+
<button
|
|
319
|
+
onClick={() => setQuery('')}
|
|
320
|
+
className="p-1 hover:bg-white/10 rounded transition-colors"
|
|
321
|
+
>
|
|
322
|
+
<X className="w-4 h-4 text-gray-500" />
|
|
323
|
+
</button>
|
|
324
|
+
)}
|
|
325
|
+
<div className="text-xs text-gray-600 border border-white/10 px-1.5 py-0.5 rounded">
|
|
326
|
+
esc
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{/* Commands */}
|
|
331
|
+
<div ref={listRef} className="max-h-[50vh] overflow-y-auto" role="listbox" aria-label="Commands">
|
|
332
|
+
{flatCommands.length === 0 ? (
|
|
333
|
+
<div className="px-4 py-8 text-center text-gray-500 text-sm">
|
|
334
|
+
No commands found
|
|
335
|
+
</div>
|
|
336
|
+
) : (
|
|
337
|
+
Object.entries(groupedCommands).map(([category, cmds]) => (
|
|
338
|
+
<div key={category}>
|
|
339
|
+
<div className="px-4 py-2 text-xs font-medium text-gray-500 uppercase tracking-wider bg-black/20">
|
|
340
|
+
{category}
|
|
341
|
+
</div>
|
|
342
|
+
{cmds.map((cmd) => {
|
|
343
|
+
const index = currentIndex++;
|
|
344
|
+
const isSelected = index === selectedIndex;
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<button
|
|
348
|
+
key={cmd.id}
|
|
349
|
+
data-command-item
|
|
350
|
+
onClick={() => cmd.action()}
|
|
351
|
+
className={cn(
|
|
352
|
+
'w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors',
|
|
353
|
+
isSelected
|
|
354
|
+
? 'bg-purple-500/20 border-l-2 border-purple-500'
|
|
355
|
+
: 'hover:bg-white/5 border-l-2 border-transparent'
|
|
356
|
+
)}
|
|
357
|
+
role="option"
|
|
358
|
+
aria-selected={isSelected}
|
|
359
|
+
tabIndex={isSelected ? 0 : -1}
|
|
360
|
+
>
|
|
361
|
+
<div className="text-gray-400">{cmd.icon}</div>
|
|
362
|
+
<div className="flex-1 min-w-0">
|
|
363
|
+
<div className="text-sm text-white">{cmd.label}</div>
|
|
364
|
+
{cmd.description && (
|
|
365
|
+
<div className="text-xs text-gray-500 truncate">
|
|
366
|
+
{cmd.description}
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
{cmd.shortcut && (
|
|
371
|
+
<div className="text-xs text-gray-600 font-mono">
|
|
372
|
+
{cmd.shortcut}
|
|
373
|
+
</div>
|
|
374
|
+
)}
|
|
375
|
+
</button>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
</div>
|
|
379
|
+
))
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{/* Footer */}
|
|
384
|
+
<div className="flex items-center justify-between px-4 py-2 border-t border-white/10 text-xs text-gray-500">
|
|
385
|
+
<div className="flex items-center gap-4">
|
|
386
|
+
<span>↑↓ navigate</span>
|
|
387
|
+
<span>↵ execute</span>
|
|
388
|
+
</div>
|
|
389
|
+
</div>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
);
|
|
393
|
+
}
|