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
JavaScript
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;