UNPKG

automagik-cli

Version:

Automagik CLI - A powerful command-line interface for interacting with Automagik Hive multi-agent AI systems

186 lines (185 loc) 12.1 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useCallback, useEffect, useState, useRef } from 'react'; import { Box, Text, useStdin, useStdout, useInput, } from 'ink'; import { StreamingState, MessageType } from './types.js'; import { useTerminalSize } from './hooks/useTerminalSize.js'; import { useLocalAPIStream } from './hooks/useLocalAPIStream.js'; import { useLoadingIndicator } from './hooks/useLoadingIndicator.js'; import { Header } from './components/Header.js'; import { LoadingIndicator } from './components/LoadingIndicator.js'; import { WorkingGeminiInput } from './components/WorkingGeminiInput.js'; import { Footer } from './components/Footer.js'; import { ChatDisplay } from './components/ChatDisplay.js'; import { TargetTypeDialog } from './components/TargetTypeDialog.js'; import { TargetSelectionDialog } from './components/TargetSelectionDialog.js'; import { SessionSelectionDialog } from './components/SessionSelectionDialog.js'; import { SessionProvider, useSession } from './contexts/SessionContext.js'; import { StreamingProvider } from './contexts/StreamingContext.js'; import { appConfig } from '../config/settings.js'; import { localAPIClient } from '../config/localClient.js'; const CTRL_EXIT_PROMPT_DURATION_MS = 1000; export const AppWrapper = (props) => (_jsx(SessionProvider, { children: _jsx(StreamingProvider, { children: _jsx(App, { ...props }) }) })); const App = ({ version }) => { const { stdout } = useStdout(); const { stdin, setRawMode } = useStdin(); const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize(); // Session and history management const { history, addMessage, clearHistory, currentSessionId, saveSession, loadSession, } = useSession(); // UI state const [showHelp, setShowHelp] = useState(false); const [debugMessage, setDebugMessage] = useState(''); const [ctrlCPressedOnce, setCtrlCPressedOnce] = useState(false); const [ctrlDPressedOnce, setCtrlDPressedOnce] = useState(false); const ctrlCTimerRef = useRef(null); const ctrlDTimerRef = useRef(null); // API state const [connectionStatus, setConnectionStatus] = useState('connecting'); const [selectedTarget, setSelectedTarget] = useState(null); const [availableTargets, setAvailableTargets] = useState({ agents: [], teams: [], workflows: [] }); // UI flow state const [uiState, setUiState] = useState('selecting_type'); const [selectedTargetType, setSelectedTargetType] = useState(null); // Local API streaming const { streamingState, submitQuery, cancelStream, initError, pendingMessage, } = useLocalAPIStream(addMessage, selectedTarget, currentSessionId, setDebugMessage); const { elapsedTime, currentLoadingPhrase } = useLoadingIndicator(streamingState); // Check API connection and load available targets useEffect(() => { const initializeAPI = async () => { try { // Health check const healthResponse = await localAPIClient.healthCheck(); if (healthResponse.error) { throw new Error(healthResponse.error); } // Load available targets const [agentsResponse, teamsResponse, workflowsResponse] = await Promise.all([ localAPIClient.listAgents(), localAPIClient.listTeams(), localAPIClient.listWorkflows(), ]); setAvailableTargets({ agents: agentsResponse.data || [], teams: teamsResponse.data || [], workflows: workflowsResponse.data || [], }); setConnectionStatus('connected'); if (appConfig.cliDebug) { console.log(`Loaded ${agentsResponse.data?.length || 0} agents, ${teamsResponse.data?.length || 0} teams, ${workflowsResponse.data?.length || 0} workflows`); } // Start with target type selection setUiState('selecting_type'); } catch (error) { console.error('Failed to connect to API:', error); setConnectionStatus('error'); addMessage({ type: MessageType.ERROR, text: `Failed to connect to API at ${appConfig.apiBaseUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`, timestamp: Date.now(), }); } }; initializeAPI(); }, []); // Remove addMessage dependency to prevent re-initialization // Handle keyboard shortcuts const handleExit = useCallback((pressedOnce, setPressedOnce, timerRef) => { if (pressedOnce) { if (timerRef.current) { clearTimeout(timerRef.current); } process.exit(0); } else { setPressedOnce(true); timerRef.current = setTimeout(() => { setPressedOnce(false); timerRef.current = null; }, CTRL_EXIT_PROMPT_DURATION_MS); } }, []); const isGlobalInputActive = streamingState === StreamingState.Idle && connectionStatus === 'connected' && uiState === 'chatting'; useInput((input, key) => { if (key.ctrl && (input === 'c' || input === 'C')) { handleExit(ctrlCPressedOnce, setCtrlCPressedOnce, ctrlCTimerRef); } else if (key.ctrl && (input === 'd' || input === 'D')) { handleExit(ctrlDPressedOnce, setCtrlDPressedOnce, ctrlDTimerRef); } else if (key.ctrl && input === 'h') { setShowHelp((prev) => !prev); } else if (key.ctrl && input === 'l') { clearHistory(); stdout.write('\\x1B[2J\\x1B[3J\\x1B[H'); // Clear screen } else if (key.escape) { // Cancel current run/streaming - only when not actively typing in input if (streamingState !== StreamingState.Idle) { cancelStream(); } } }, { isActive: !isGlobalInputActive }); const handleSubmit = useCallback((message) => { const trimmedMessage = message.trim(); if (trimmedMessage.length > 0 && selectedTarget) { submitQuery(trimmedMessage); } }, [submitQuery, selectedTarget]); // Interactive flow handlers const handleTargetTypeSelect = useCallback((targetType) => { setSelectedTargetType(targetType); setUiState('selecting_target'); }, []); const handleTargetSelect = useCallback((target) => { setSelectedTarget(target); setUiState('selecting_session'); }, []); const handleSessionSelect = useCallback((sessionAction) => { if (sessionAction === 'new') { // Start a new session setUiState('chatting'); } // TODO: Handle existing session loading }, []); const handleBackToTargetType = useCallback(() => { setSelectedTargetType(null); setUiState('selecting_type'); }, []); const handleBackToTargetSelection = useCallback(() => { setSelectedTarget(null); setUiState('selecting_target'); }, []); const isInputActive = isGlobalInputActive; const widthFraction = 0.9; const inputWidth = Math.max(20, Math.floor(terminalWidth * widthFraction) - 3); // Show connection error if (connectionStatus === 'error') { return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsxs(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, marginY: 1, children: [_jsxs(Text, { color: "red", children: ["Failed to connect to API at ", appConfig.apiBaseUrl] }), _jsx(Text, { children: "Make sure the multi-agent server is running and try again." })] }), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })); } // Show loading while connecting if (connectionStatus === 'connecting') { return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsxs(Text, { children: ["\uD83E\uDDDE Connecting to ", appConfig.apiBaseUrl, "..."] }), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })); } // Interactive setup flow if (uiState === 'selecting_type') { return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsx(TargetTypeDialog, { onSelect: handleTargetTypeSelect, availableTargets: availableTargets }), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })); } if (uiState === 'selecting_target' && selectedTargetType) { const targets = selectedTargetType === 'agent' ? availableTargets.agents : selectedTargetType === 'team' ? availableTargets.teams : availableTargets.workflows; return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsx(TargetSelectionDialog, { targetType: selectedTargetType, targets: targets, onSelect: handleTargetSelect, onBack: handleBackToTargetType }), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })); } if (uiState === 'selecting_session' && selectedTarget) { return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsx(SessionSelectionDialog, { selectedTarget: selectedTarget, onSelect: handleSessionSelect, onBack: handleBackToTargetSelection }), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })); } // Main chat interface return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "90%", children: [_jsx(Header, { terminalWidth: terminalWidth, version: version, connectionStatus: connectionStatus, selectedTarget: selectedTarget, availableTargets: availableTargets, onTargetChange: () => setUiState('selecting_type') }), _jsx(ChatDisplay, { history: history, pendingMessage: pendingMessage, terminalWidth: terminalWidth, terminalHeight: terminalHeight }), showHelp && (_jsx(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, marginY: 1, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Automagik Local CLI Help" }), _jsx(Text, { children: "\u2022 Type messages to chat with the selected agent/team/workflow" }), _jsx(Text, { children: "\u2022 Ctrl+H: Toggle this help" }), _jsx(Text, { children: "\u2022 Ctrl+L: Clear screen" }), _jsx(Text, { children: "\u2022 Ctrl+C or Ctrl+D (twice): Exit" }), _jsx(Text, { children: "\u2022 Esc: Cancel current run/streaming" }), _jsx(Text, { children: "\u2022 Click target name to change selection" })] }) })), _jsxs(Box, { flexDirection: "column", children: [ctrlCPressedOnce ? (_jsx(Text, { color: "yellow", children: "Press Ctrl+C again to exit." })) : ctrlDPressedOnce ? (_jsx(Text, { color: "yellow", children: "Press Ctrl+D again to exit." })) : null, _jsx(LoadingIndicator, { currentLoadingPhrase: currentLoadingPhrase, elapsedTime: elapsedTime, streamingState: streamingState }), initError && (_jsx(Box, { borderStyle: "round", borderColor: "red", paddingX: 1, marginBottom: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", initError] }) })), isInputActive && uiState === 'chatting' && (_jsx(WorkingGeminiInput, { onSubmit: handleSubmit, disabled: !selectedTarget, placeholder: selectedTarget ? `Message ${selectedTarget.name}...` : 'No target selected', focus: true })), _jsx(Footer, { debugMode: appConfig.cliDebug, debugMessage: debugMessage, sessionId: currentSessionId, apiUrl: appConfig.apiBaseUrl })] })] })); }; export default App;