import { useEffect, useState } from "react"; import { Command } from "cmdk"; import { useAtom } from "jotai"; import { useGrimoire } from "@/core/state"; import { manPages } from "@/types/man"; import { parseCommandInput, executeCommandParser } from "@/lib/command-parser"; import { commandLauncherEditModeAtom } from "@/core/command-launcher-state"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import { VisuallyHidden } from "@/components/ui/visually-hidden"; import "./command-launcher.css"; interface CommandLauncherProps { open: boolean; onOpenChange: (open: boolean) => void; } export default function CommandLauncher({ open, onOpenChange, }: CommandLauncherProps) { const [input, setInput] = useState(""); const [editMode, setEditMode] = useAtom(commandLauncherEditModeAtom); const { addWindow, updateWindow } = useGrimoire(); // Prefill input when entering edit mode useEffect(() => { if (open && editMode) { setInput(editMode.initialCommand); } else if (!open) { setInput(""); } }, [open, editMode]); // Parse input into command and arguments const parsed = parseCommandInput(input); const { commandName } = parsed; const recognizedCommand = parsed.command; // Filter commands by partial match on command name only const filteredCommands = Object.entries(manPages).filter(([name]) => name.toLowerCase().includes(commandName.toLowerCase()), ); // Execute command (async to support async argParsers) const executeCommand = async () => { if (!recognizedCommand) return; // Execute argParser and get props/title const result = await executeCommandParser(parsed); if (result.error || !result.props) { console.error("Failed to parse command:", result.error); return; } // Edit mode: update existing window if (editMode) { updateWindow(editMode.windowId, { props: result.props, title: result.title, commandString: input.trim(), appId: recognizedCommand.appId, }); setEditMode(null); // Clear edit mode } else { // Normal mode: create new window addWindow( recognizedCommand.appId, result.props, result.title, input.trim(), ); } onOpenChange(false); }; // Handle item selection (populate input, don't execute) const handleSelect = (selectedCommand: string) => { setInput(selectedCommand + " "); }; // Handle Enter key const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); executeCommand(); } }; // Define category order: Nostr first, then Documentation, then System const categoryOrder = ["Nostr", "Documentation", "System"]; const categories = Array.from( new Set(filteredCommands.map(([_, cmd]) => cmd.category)), ).sort((a, b) => { const indexA = categoryOrder.indexOf(a); const indexB = categoryOrder.indexOf(b); return (indexA === -1 ? 999 : indexA) - (indexB === -1 ? 999 : indexB); }); // Dynamic placeholder const placeholder = recognizedCommand ? recognizedCommand.synopsis : "Type a command..."; return ( Command Launcher
{commandName ? `No command found: ${commandName}` : "Start typing..."} {categories.map((category) => ( {filteredCommands .filter(([_, cmd]) => cmd.category === category) .map(([name, cmd]) => { const isExactMatch = name === commandName; return ( handleSelect(name)} className="command-item" data-exact-match={isExactMatch} >
{name} {cmd.synopsis !== name && ( {cmd.synopsis.replace(name, "").trim()} )} {isExactMatch && ( )}
{cmd.description.split(".")[0]}
); })}
))}
↑↓ navigate execute esc close
{recognizedCommand && (
Ready to execute
)}
); }