import { Box, Text, useInput } from "ink"; import { useCallback, useMemo, useState } from "react"; import { getVersion } from "../../version"; import { commands } from "../commands/registry"; import { colors } from "./colors"; const PAGE_SIZE = 10; type HelpTab = "commands" | "shortcuts"; const HELP_TABS: HelpTab[] = ["commands", "shortcuts"]; interface CommandItem { name: string; description: string; } interface ShortcutItem { keys: string; description: string; } interface HelpDialogProps { onClose: () => void; } export function HelpDialog({ onClose }: HelpDialogProps) { const [activeTab, setActiveTab] = useState("commands"); const [currentPage, setCurrentPage] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0); // Get all non-hidden commands const allCommands = useMemo(() => { return Object.entries(commands) .filter(([_, cmd]) => !cmd.hidden) .map(([name, cmd]) => ({ name, description: cmd.desc, })) .sort((a, b) => a.name.localeCompare(b.name)); }, []); // Keyboard shortcuts const shortcuts = useMemo(() => { return [ { keys: "/", description: "Open command autocomplete" }, { keys: "@", description: "Open file autocomplete" }, { keys: "Esc", description: "Cancel dialog / clear input (double press)", }, { keys: "Tab", description: "Autocomplete command or file path" }, { keys: "↓", description: "Navigate down / next command in history" }, { keys: "↑", description: "Navigate up / previous command in history" }, { keys: "Ctrl+C", description: "Interrupt operation / exit (double press)", }, { keys: "Ctrl+V", description: "Paste content or image" }, ]; }, []); const cycleTab = useCallback(() => { setActiveTab((current) => { const idx = HELP_TABS.indexOf(current); return HELP_TABS[(idx + 1) % HELP_TABS.length] as HelpTab; }); setCurrentPage(0); setSelectedIndex(0); }, []); const visibleItems = activeTab === "commands" ? allCommands : shortcuts; const totalPages = Math.ceil(visibleItems.length / PAGE_SIZE); const startIndex = currentPage * PAGE_SIZE; const visiblePageItems = visibleItems.slice( startIndex, startIndex + PAGE_SIZE, ); useInput( useCallback( (input, key) => { if (key.escape) { onClose(); } else if (key.tab) { cycleTab(); } else if (key.upArrow) { setSelectedIndex((prev) => Math.max(0, prev - 1)); } else if (key.downArrow) { setSelectedIndex((prev) => Math.min(visiblePageItems.length - 1, prev + 1), ); } else if (input === "j" || input === "J") { // Previous page if (currentPage > 0) { setCurrentPage((prev) => prev - 1); setSelectedIndex(0); } } else if (input === "k" || input === "K") { // Next page if (currentPage < totalPages - 1) { setCurrentPage((prev) => prev + 1); setSelectedIndex(0); } } else if (key.leftArrow && currentPage > 0) { setCurrentPage((prev) => prev - 1); setSelectedIndex(0); } else if (key.rightArrow && currentPage < totalPages - 1) { setCurrentPage((prev) => prev + 1); setSelectedIndex(0); } }, [currentPage, totalPages, visiblePageItems.length, onClose, cycleTab], ), { isActive: true }, ); const version = getVersion(); const getTabLabel = (tab: HelpTab) => { if (tab === "commands") return `Commands (${allCommands.length})`; return `Shortcuts (${shortcuts.length})`; }; return ( Letta Code v{version} (↑↓ navigate, ←→/jk page, ESC close) Tab: {HELP_TABS.map((tab, i) => ( {i > 0 && · } {getTabLabel(tab)} ))} (Tab to switch) Page {currentPage + 1}/{totalPages} {activeTab === "commands" && (visiblePageItems as CommandItem[]).map((command, index) => { const isSelected = index === selectedIndex; return ( {isSelected ? "›" : " "} {command.name} {command.description} ); })} {activeTab === "shortcuts" && (visiblePageItems as ShortcutItem[]).map((shortcut, index) => { const isSelected = index === selectedIndex; return ( {isSelected ? "›" : " "} {shortcut.keys} {shortcut.description} ); })} Getting started: • Run /init to initialize agent memory for this project • Press / at any time to see command autocomplete • Visit https://docs.letta.com/letta-code for more help ); }