UNPKG

interm-mcp

Version:

MCP server for terminal applications and TUI automation with 127 tools

1,302 lines 99.3 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; import { TerminalManager } from './terminal-manager.js'; import { SessionManager } from './session-manager.js'; import { TerminalNavigationManager } from './terminal-navigation-manager.js'; import { AdvancedMouseManager } from './advanced-mouse-manager.js'; import { InteractionReplayManager } from './interaction-replay-manager.js'; import { TerminalScreenshot } from './screenshot.js'; import { KeyboardManager } from './keyboard-manager.js'; import { MouseManager } from './mouse-manager.js'; import { ClipboardManager } from './clipboard-manager.js'; import { TouchManager } from './touch-manager.js'; import { AccessibilityManager } from './accessibility-manager.js'; import { InputProcessingManager } from './input-processing-manager.js'; import { EnvironmentManager } from './environment-manager.js'; import { registerTools } from './tools/index.js'; import { handleError, safeJsonStringify } from './utils/error-utils.js'; export class InterMServer { server; terminalManager; sessionManager; navigationManager; advancedMouseManager; interactionReplayManager; keyboardManager; mouseManager; clipboardManager; touchManager; accessibilityManager; inputProcessingManager; environmentManager; constructor() { this.server = new Server({ name: 'interm', version: '0.1.0', description: 'MCP server for terminal applications and TUIs' }, { capabilities: { tools: {} } }); this.terminalManager = TerminalManager.getInstance(); this.sessionManager = SessionManager.getInstance(); this.navigationManager = TerminalNavigationManager.getInstance(); this.advancedMouseManager = AdvancedMouseManager.getInstance(); this.interactionReplayManager = InteractionReplayManager.getInstance(); this.keyboardManager = KeyboardManager.getInstance(); this.mouseManager = MouseManager.getInstance(); this.clipboardManager = ClipboardManager.getInstance(); this.touchManager = TouchManager.getInstance(); this.accessibilityManager = AccessibilityManager.getInstance(); this.inputProcessingManager = InputProcessingManager.getInstance(); this.environmentManager = EnvironmentManager.getInstance(); this.setupHandlers(); this.setupErrorHandling(); } setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: registerTools() })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const result = await this.handleToolCall(name, args || {}); return { content: [ { type: 'text', text: safeJsonStringify(result) } ] }; } catch (error) { const handledError = handleError(error, `Tool call failed: ${name}`); throw new McpError(ErrorCode.InternalError, `${handledError.message}\n\nDetails: ${safeJsonStringify(handledError.details)}`); } }); } async handleToolCall(name, args) { // Add timeout wrapper for all tool calls return Promise.race([ this.executeToolCall(name, args), new Promise((_, reject) => setTimeout(() => reject(new Error('Tool call timeout')), 60000)) ]); } async executeToolCall(name, args) { switch (name) { // Session management tools case 'create_terminal_session': return this.createTerminalSession(args); case 'list_terminal_sessions': return this.listTerminalSessions(); case 'get_terminal_session': return this.getTerminalSession(args); case 'close_terminal_session': return this.closeTerminalSession(args); case 'resize_terminal': return this.resizeTerminal(args); // Command execution tools case 'execute_command': return this.executeCommand(args); case 'send_input': return this.sendInput(args); case 'send_keys': return this.sendKeys(args); case 'interrupt_command': return this.interruptCommand(args); // Capture tools case 'get_terminal_content': return this.getTerminalContent(args); case 'screenshot_terminal': return this.screenshotTerminal(args); case 'get_terminal_buffer': return this.getTerminalBuffer(args); case 'watch_terminal_output': return this.watchTerminalOutput(args); case 'recover_session': return this.recoverSession(args); // Keyboard interaction tools case 'send_function_keys': return this.sendFunctionKeys(args); case 'send_modifier_combination': return this.sendModifierCombination(args); case 'send_navigation_keys': return this.sendNavigationKeys(args); case 'send_editing_shortcuts': return this.sendEditingShortcuts(args); case 'send_key_sequence': return this.sendKeySequence(args); case 'send_simultaneous_keys': return this.sendSimultaneousKeys(args); case 'send_key_with_hold': return this.sendKeyWithHold(args); case 'send_unicode_input': return this.sendUnicodeInput(args); // Mouse interaction tools case 'mouse_move': return this.mouseMove(args); case 'mouse_click': return this.mouseClick(args); case 'mouse_drag': return this.mouseDrag(args); case 'mouse_scroll': return this.mouseScroll(args); case 'mouse_hover': return this.mouseHover(args); case 'mouse_gesture': return this.mouseGesture(args); case 'mouse_multi_button': return this.mouseMultiButton(args); case 'get_mouse_position': return this.getMousePosition(args); // Clipboard tools case 'clipboard_read': return this.clipboardRead(args); case 'clipboard_write': return this.clipboardWrite(args); case 'text_select': return this.textSelect(args); case 'text_copy': return this.textCopy(args); case 'text_paste': return this.textPaste(args); case 'clipboard_history': return this.clipboardHistory(args); case 'multi_select': return this.multiSelect(args); case 'selection_info': return this.selectionInfo(args); // Touch interaction tools case 'touch_input': return this.touchInput(args); case 'detect_gesture': return this.detectGesture(args); case 'get_touch_capabilities': return this.getTouchCapabilities(args); case 'get_active_touches': return this.getActiveTouches(args); case 'get_touch_history': return this.getTouchHistory(args); case 'get_gesture_history': return this.getGestureHistory(args); case 'configure_touch_gestures': return this.configureTouchGestures(args); case 'clear_touch_state': return this.clearTouchState(args); // Advanced touch tools case 'simulate_multi_touch': return this.simulateMultiTouch(args); case 'detect_touch_drag': return this.detectTouchDrag(args); case 'get_haptic_capabilities': return this.getHapticCapabilities(args); case 'recognize_complex_gesture': return this.recognizeComplexGesture(args); case 'configure_advanced_touch': return this.configureAdvancedTouch(args); // Accessibility tools case 'initialize_accessibility': return this.initializeAccessibility(args); case 'announce_to_screen_reader': return this.announceToScreenReader(args); case 'apply_high_contrast': return this.applyHighContrast(args); case 'configure_accessibility': return this.configureAccessibility(args); case 'handle_focus_change': return this.handleFocusChange(args); case 'get_accessibility_status': return this.getAccessibilityStatus(args); case 'get_keyboard_navigation_hints': return this.getKeyboardNavigationHints(args); case 'get_screen_reader_events': return this.getScreenReaderEvents(args); // Input processing tools case 'queue_input_event': return this.queueInputEvent(args); case 'start_input_recording': return this.startInputRecording(args); case 'stop_input_recording': return this.stopInputRecording(args); case 'playback_input_recording': return this.playbackInputRecording(args); case 'detect_input_devices': return this.detectInputDevices(args); case 'add_input_filter': return this.addInputFilter(args); case 'remove_input_filter': return this.removeInputFilter(args); case 'get_input_analytics': return this.getInputAnalytics(args); case 'optimize_input_latency': return this.optimizeInputLatency(args); case 'get_input_history': return this.getInputHistory(args); // Environment tools case 'set_environment_variable': return this.setEnvironmentVariable(args); case 'get_environment_variable': return this.getEnvironmentVariable(args); case 'list_environment_variables': return this.listEnvironmentVariables(args); case 'unset_environment_variable': return this.unsetEnvironmentVariable(args); case 'change_working_directory': return this.changeWorkingDirectory(args); case 'send_process_signal': return this.sendProcessSignal(args); case 'set_terminal_title': return this.setTerminalTitle(args); case 'control_job': return this.controlJob(args); // Session state tools case 'save_session_bookmark': return this.saveSessionBookmark(args); case 'restore_session_bookmark': return this.restoreSessionBookmark(args); case 'get_session_history': return this.getSessionHistory(args); case 'search_session_history': return this.searchSessionHistory(args); case 'list_session_bookmarks': return this.listSessionBookmarks(args); case 'serialize_session_state': return this.serializeSessionState(args); case 'undo_last_command': return this.undoLastCommand(args); case 'auto_save_session': return this.autoSaveSession(args); // Terminal control tools case 'send_terminal_bell': return this.sendTerminalBell(args); case 'set_cursor_style': return this.setCursorStyle(args); case 'switch_terminal_mode': return this.switchTerminalMode(args); // Terminal navigation tools case 'dynamic_terminal_resize': return this.dynamicTerminalResize(args); case 'toggle_fullscreen_mode': return this.toggleFullscreenMode(args); case 'create_terminal_tab': return this.createTerminalTab(args); case 'switch_terminal_tab': return this.switchTerminalTab(args); case 'split_terminal_pane': return this.splitTerminalPane(args); case 'focus_terminal_pane': return this.focusTerminalPane(args); case 'set_zoom_level': return this.setZoomLevel(args); case 'scroll_viewport': return this.scrollViewport(args); case 'set_terminal_opacity': return this.setTerminalOpacity(args); case 'get_navigation_status': return this.getNavigationStatus(args); // Advanced mouse tools case 'configure_mouse_acceleration': return this.configureMouseAcceleration(args); case 'configure_pressure_sensitivity': return this.configurePressureSensitivity(args); case 'track_multi_click_sequence': return this.trackMultiClickSequence(args); case 'configure_focus_follow_mouse': return this.configureFocusFollowMouse(args); case 'set_mouse_event_filter': return this.setMouseEventFilter(args); case 'get_advanced_mouse_status': return this.getAdvancedMouseStatus(args); // Interaction replay tools case 'start_interaction_recording': return this.startInteractionRecording(args); case 'stop_interaction_recording': return this.stopInteractionRecording(args); case 'replay_interaction_sequence': return this.replayInteractionSequence(args); case 'list_interaction_recordings': return this.listInteractionRecordings(args); case 'create_state_snapshot': return this.createStateSnapshot(args); case 'generate_state_diff': return this.generateStateDiff(args); case 'list_state_snapshots': return this.listStateSnapshots(args); case 'list_state_diffs': return this.listStateDiffs(args); default: throw new Error(`Unknown tool: ${name}`); } } // Session management methods async createTerminalSession(args) { const cols = args.cols || 80; const rows = args.rows || 24; const shell = args.shell || undefined; const workingDirectory = args.workingDirectory; const session = await this.terminalManager.createSession(cols, rows, shell, workingDirectory); return { success: true, data: session }; } async listTerminalSessions() { const sessions = this.terminalManager.getAllSessions(); return { success: true, data: { sessions } }; } async getTerminalSession(args) { const sessionId = args.sessionId; const session = this.terminalManager.getSession(sessionId); if (!session) { return { success: false, error: { type: 'SESSION_NOT_FOUND', message: `Session ${sessionId} not found` } }; } return { success: true, data: session }; } async closeTerminalSession(args) { const sessionId = args.sessionId; await this.terminalManager.closeSession(sessionId); return { success: true, data: { message: `Session ${sessionId} closed` } }; } async resizeTerminal(args) { const sessionId = args.sessionId; const cols = args.cols; const rows = args.rows; await this.terminalManager.resizeSession(sessionId, cols, rows); return { success: true, data: { sessionId, cols, rows } }; } // Command execution methods async executeCommand(args) { const sessionId = args.sessionId; const command = args.command; const timeout = args.timeout || 30000; const expectOutput = args.expectOutput !== false; const result = await this.terminalManager.executeCommand(sessionId, command, timeout, expectOutput); return { success: true, data: result }; } async sendInput(args) { const sessionId = args.sessionId; const input = args.input; await this.terminalManager.sendInput(sessionId, input); return { success: true, data: { message: 'Input sent' } }; } async sendKeys(args) { const sessionId = args.sessionId; const keys = args.keys; // Map key names to actual key sequences const keyMap = { 'enter': '\r', 'tab': '\t', 'space': ' ', 'backspace': '\b', 'delete': '\x7f', 'escape': '\x1b', 'ctrl+c': '\x03', 'ctrl+d': '\x04', 'ctrl+z': '\x1a', 'ctrl+l': '\x0c', 'arrow_up': '\x1b[A', 'arrow_down': '\x1b[B', 'arrow_right': '\x1b[C', 'arrow_left': '\x1b[D', 'home': '\x1b[H', 'end': '\x1b[F', 'page_up': '\x1b[5~', 'page_down': '\x1b[6~', 'f1': '\x1bOP', 'f2': '\x1bOQ', 'f3': '\x1bOR', 'f4': '\x1bOS' }; const keySequence = keyMap[keys] || keys; await this.terminalManager.sendInput(sessionId, keySequence); return { success: true, data: { message: `Keys sent: ${keys}` } }; } async interruptCommand(args) { const sessionId = args.sessionId; await this.terminalManager.sendInput(sessionId, '\x03'); // Ctrl+C return { success: true, data: { message: 'Interrupt signal sent' } }; } // Capture methods async getTerminalContent(args) { const sessionId = args.sessionId; const includeFormatting = args.includeFormatting; const lastNLines = args.lastNLines; const maxTokens = args.maxTokens; const result = await this.terminalManager.getTerminalContent(sessionId, { lastNLines, maxTokens, includeFormatting }); const state = await this.terminalManager.getTerminalState(sessionId); return { success: true, data: { content: result.content, truncated: result.truncated, totalLines: result.totalLines, cursor: state.cursor, dimensions: state.dimensions, ...(includeFormatting && { attributes: state.attributes }) } }; } async screenshotTerminal(args) { const sessionId = args.sessionId; const format = args.format || 'png'; const theme = args.theme || 'dark'; const fontSize = args.fontSize || 14; const fontFamily = args.fontFamily || 'monospace'; const background = args.background; const state = await this.terminalManager.getTerminalState(sessionId); const screenshot = await TerminalScreenshot.captureTerminal(state, { format, theme, fontSize, fontFamily, background }); return { success: true, data: { screenshot: screenshot.toString('base64'), format, size: screenshot.length } }; } async getTerminalBuffer(args) { const sessionId = args.sessionId; const includeScrollback = args.includeScrollback !== false; const maxLines = args.maxLines || 1000; const state = await this.terminalManager.getTerminalState(sessionId); let lines = state.content.split('\n'); if (!includeScrollback) { lines = lines.slice(-state.dimensions.rows); } if (lines.length > maxLines) { lines = lines.slice(-maxLines); } return { success: true, data: { buffer: lines.join('\n'), lineCount: lines.length, truncated: state.content.split('\n').length > maxLines } }; } async watchTerminalOutput(args) { const sessionId = args.sessionId; const pattern = args.pattern; const timeout = args.timeout || 30000; // This is a simplified implementation // In a real implementation, you'd set up proper event listeners const startTime = Date.now(); const regex = pattern ? new RegExp(pattern) : null; return new Promise((resolve) => { const checkOutput = async () => { const state = await this.terminalManager.getTerminalState(sessionId); if (!pattern || !regex || regex.test(state.content)) { resolve({ success: true, data: { matched: !!pattern, pattern, content: state.content, timestamp: new Date() } }); return; } if (Date.now() - startTime >= timeout) { resolve({ success: false, error: { type: 'TIMEOUT_ERROR', message: `Watch timeout after ${timeout}ms` } }); return; } setTimeout(checkOutput, 100); }; checkOutput(); }); } async recoverSession(args) { const sessionId = args.sessionId; try { await this.terminalManager.recoverSession(sessionId); return { success: true, data: { message: `Session ${sessionId} recovered successfully`, sessionId, timestamp: new Date() } }; } catch (error) { return { success: false, error: { type: 'RECOVERY_ERROR', message: `Failed to recover session: ${error instanceof Error ? error.message : String(error)}` } }; } } // Keyboard interaction methods async sendFunctionKeys(args) { const sessionId = args.sessionId; const functionKey = args.functionKey; try { const keySequence = this.keyboardManager.getFunctionKeySequence(functionKey); await this.terminalManager.sendInput(sessionId, keySequence); return { success: true, data: { message: `Function key sent: ${functionKey}` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send function key: ${error}` } }; } } async sendModifierCombination(args) { const sessionId = args.sessionId; const modifiers = args.modifiers; const key = args.key; try { const keySequence = this.keyboardManager.buildModifierCombination(modifiers, key); await this.terminalManager.sendInput(sessionId, keySequence); return { success: true, data: { message: `Modifier combination sent: ${modifiers.join('+')}+${key}` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send modifier combination: ${error}` } }; } } async sendNavigationKeys(args) { const sessionId = args.sessionId; const navigationKey = args.navigationKey; try { const keySequence = this.keyboardManager.getFunctionKeySequence(navigationKey); await this.terminalManager.sendInput(sessionId, keySequence); return { success: true, data: { message: `Navigation key sent: ${navigationKey}` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send navigation key: ${error}` } }; } } async sendEditingShortcuts(args) { const sessionId = args.sessionId; const editingAction = args.editingAction; const platform = args.platform || 'auto'; try { const keySequence = this.keyboardManager.getFunctionKeySequence(`edit_${editingAction}`); await this.terminalManager.sendInput(sessionId, keySequence); return { success: true, data: { message: `Editing shortcut sent: ${editingAction}` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send editing shortcut: ${error}` } }; } } async sendKeySequence(args) { const sessionId = args.sessionId; const sequence = args.sequence; const repeatCount = args.repeatCount || 1; try { for (let i = 0; i < repeatCount; i++) { const keySequence = this.keyboardManager.buildKeySequence(sequence); await this.terminalManager.sendInput(sessionId, keySequence); if (i < repeatCount - 1) { // Brief delay between repetitions await new Promise(resolve => setTimeout(resolve, 50)); } } return { success: true, data: { message: `Key sequence sent ${repeatCount} time(s)` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send key sequence: ${error}` } }; } } async sendSimultaneousKeys(args) { const sessionId = args.sessionId; const keys = args.keys; const holdDuration = args.holdDuration || 100; try { // For simultaneous keys, we'll send them in rapid succession // In a real implementation, this would need lower-level keyboard handling for (const key of keys) { const keySequence = this.keyboardManager.getFunctionKeySequence(key) || key; await this.terminalManager.sendInput(sessionId, keySequence); } return { success: true, data: { message: `Simultaneous keys sent: ${keys.join(', ')}` } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send simultaneous keys: ${error}` } }; } } async sendKeyWithHold(args) { const sessionId = args.sessionId; const key = args.key; const holdDuration = args.holdDuration; const repeatRate = args.repeatRate || 10; try { const keySequences = this.keyboardManager.simulateKeyHold(key, holdDuration, repeatRate); for (const keySequence of keySequences) { await this.terminalManager.sendInput(sessionId, keySequence); await new Promise(resolve => setTimeout(resolve, 1000 / repeatRate)); } return { success: true, data: { message: `Key hold sent: ${key}`, duration: holdDuration, repeatCount: keySequences.length } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send key hold: ${error}` } }; } } async sendUnicodeInput(args) { const sessionId = args.sessionId; const text = args.text; const inputMethod = args.inputMethod || 'direct'; const locale = args.locale; try { const processedText = this.keyboardManager.processUnicodeInput(text, inputMethod); await this.terminalManager.sendInput(sessionId, processedText); return { success: true, data: { message: `Unicode input sent`, text: processedText, inputMethod, locale } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to send unicode input: ${error}` } }; } } // Mouse interaction methods async mouseMove(args) { const sessionId = args.sessionId; const x = args.x; const y = args.y; const relative = args.relative || false; const smooth = args.smooth || false; const duration = args.duration || 200; try { let targetX = x, targetY = y; if (relative) { const currentPos = this.mouseManager.getCurrentPosition(); targetX = currentPos.x + x; targetY = currentPos.y + y; } const sequences = this.mouseManager.generateMoveSequence(targetX, targetY, smooth, duration); for (const sequence of sequences) { await this.terminalManager.sendInput(sessionId, sequence); if (smooth) { await new Promise(resolve => setTimeout(resolve, 16)); // ~60fps } } return { success: true, data: { message: `Mouse moved to (${targetX}, ${targetY})`, position: { x: targetX, y: targetY } } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to move mouse: ${error}` } }; } } async mouseClick(args) { const sessionId = args.sessionId; const button = args.button || 'left'; const x = args.x; const y = args.y; const clickCount = args.clickCount || 1; const modifiers = args.modifiers || []; const pressure = args.pressure || 0.5; try { let targetX, targetY; if (x !== undefined && y !== undefined) { targetX = x; targetY = y; } else { const currentPos = this.mouseManager.getCurrentPosition(); targetX = currentPos.x; targetY = currentPos.y; } const actualClickCount = clickCount > 1 ? this.mouseManager.detectMultiClick(targetX, targetY) : 1; const sequence = this.mouseManager.generateMouseSequence(sessionId, button, targetX, targetY, clickCount); await this.terminalManager.sendInput(sessionId, sequence); return { success: true, data: { message: `Mouse ${button} clicked ${clickCount} time(s) at (${targetX}, ${targetY})`, position: { x: targetX, y: targetY }, button, clickCount: actualClickCount } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to click mouse: ${error}` } }; } } async mouseDrag(args) { const sessionId = args.sessionId; const startX = args.startX; const startY = args.startY; const endX = args.endX; const endY = args.endY; const button = args.button || 'left'; const smooth = args.smooth ?? true; const duration = args.duration || 500; try { const sequences = this.mouseManager.generateDragSequence(startX, startY, endX, endY, button, smooth); for (let i = 0; i < sequences.length; i++) { await this.terminalManager.sendInput(sessionId, sequences[i]); if (smooth && i < sequences.length - 1) { await new Promise(resolve => setTimeout(resolve, duration / sequences.length)); } } return { success: true, data: { message: `Mouse dragged from (${startX}, ${startY}) to (${endX}, ${endY})`, startPosition: { x: startX, y: startY }, endPosition: { x: endX, y: endY }, button } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to drag mouse: ${error}` } }; } } async mouseScroll(args) { const sessionId = args.sessionId; const direction = args.direction; const amount = args.amount || 3; const x = args.x; const y = args.y; const precision = args.precision || false; try { let targetX, targetY; if (x !== undefined && y !== undefined) { targetX = x; targetY = y; } else { const currentPos = this.mouseManager.getCurrentPosition(); targetX = currentPos.x; targetY = currentPos.y; } const sequence = this.mouseManager.generateScrollSequence(direction, amount, targetX, targetY, precision); await this.terminalManager.sendInput(sessionId, sequence); return { success: true, data: { message: `Mouse scrolled ${direction} ${amount} units at (${targetX}, ${targetY})`, position: { x: targetX, y: targetY }, direction, amount } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to scroll mouse: ${error}` } }; } } async mouseHover(args) { const sessionId = args.sessionId; const x = args.x; const y = args.y; const duration = args.duration || 1000; try { // Move to position const moveSequences = this.mouseManager.generateMoveSequence(x, y); for (const sequence of moveSequences) { await this.terminalManager.sendInput(sessionId, sequence); } // Wait for hover duration await new Promise(resolve => setTimeout(resolve, duration)); return { success: true, data: { message: `Mouse hovered at (${x}, ${y}) for ${duration}ms`, position: { x, y }, duration } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to hover mouse: ${error}` } }; } } async mouseGesture(args) { const sessionId = args.sessionId; const gestureType = args.gestureType; const startX = args.startX; const startY = args.startY; const size = args.size || 50; const speed = args.speed || 3; try { const sequences = this.mouseManager.generateGestureSequence(gestureType, startX, startY, size); const delay = Math.max(10, 200 / speed); for (const sequence of sequences) { await this.terminalManager.sendInput(sessionId, sequence); await new Promise(resolve => setTimeout(resolve, delay)); } return { success: true, data: { message: `Mouse gesture executed: ${gestureType}`, gestureType, startPosition: { x: startX, y: startY }, size, speed } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to execute mouse gesture: ${error}` } }; } } async mouseMultiButton(args) { const sessionId = args.sessionId; const buttons = args.buttons; const x = args.x; const y = args.y; const holdDuration = args.holdDuration || 100; try { let targetX, targetY; if (x !== undefined && y !== undefined) { targetX = x; targetY = y; } else { const currentPos = this.mouseManager.getCurrentPosition(); targetX = currentPos.x; targetY = currentPos.y; } const sequence = this.mouseManager.generateMultiButtonSequence(buttons, targetX, targetY, holdDuration); await this.terminalManager.sendInput(sessionId, sequence); return { success: true, data: { message: `Multiple mouse buttons pressed: ${buttons.join(', ')}`, position: { x: targetX, y: targetY }, buttons, holdDuration } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to press multiple mouse buttons: ${error}` } }; } } async getMousePosition(args) { const sessionId = args.sessionId; try { const position = this.mouseManager.getCurrentPosition(); return { success: true, data: { position, sessionId } }; } catch (error) { return { success: false, error: { type: 'UNKNOWN_ERROR', message: `Failed to get mouse position: ${error}` } }; } } // Clipboard methods async clipboardRead(args) { const sessionId = args.sessionId; const format = args.format || 'text'; try { const entry = await this.clipboardManager.readClipboard(format); return { success: true, data: { content: entry?.content || '', format: entry?.format || format, timestamp: entry?.timestamp || new Date(), source: entry?.source || 'unknown' } }; } catch (error) { return { success: false, error: { type: 'RESOURCE_ERROR', message: `Failed to read clipboard: ${error}` } }; } } async clipboardWrite(args) { const sessionId = args.sessionId; const content = args.content; const format = args.format || 'text'; try { await this.clipboardManager.writeClipboard(content, format, 'api'); return { success: true, data: { message: 'Content written to clipboard', content: content.substring(0, 100) + (content.length > 100 ? '...' : ''), format } }; } catch (error) { return { success: false, error: { type: 'RESOURCE_ERROR', message: `Failed to write clipboard: ${error}` } }; } } async textSelect(args) { const sessionId = args.sessionId; const method = args.method; try { const terminalState = await this.terminalManager.getTerminalState(sessionId); let selection; switch (method) { case 'coordinates': selection = this.clipboardManager.createTextSelection(sessionId, args.startX, args.startY, args.endX, args.endY, 'rectangle'); break; case 'word': selection = this.clipboardManager.selectWord(terminalState.content, terminalState.dimensions.cols, args.wordX, args.wordY); break; case 'line': selection = this.clipboardManager.selectLine(terminalState.content, terminalState.dimensions.cols, args.wordY); break; case 'all': selection = this.clipboardManager.selectAll(terminalState.content, terminalState.dimensions.cols, terminalState.dimensions.rows); break; case 'pattern': const selections = this.clipboardManager.selectByPattern(terminalState.content, args.pattern); return { success: true, data: { message: `Pattern selection created: ${selections.length} matches`, selections, method } }; default: throw new Error(`Unknown selection method: ${method}`); } return { success: true, data: { message: `Text selected using ${method} method`, selection, method } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to select text: ${error}` } }; } } async textCopy(args) { const sessionId = args.sessionId; const source = args.source || 'selection'; try { const terminalState = await this.terminalManager.getTerminalState(sessionId); let content = ''; switch (source) { case 'selection': const selections = this.clipboardManager.getSelections(sessionId); if (selections.length > 0) { content = this.clipboardManager.extractSelectionContent(terminalState.content, selections[0]); } break; case 'screen': content = terminalState.content; break; case 'coordinates': const selection = this.clipboardManager.createTextSelection(sessionId, args.startX, args.startY, args.endX, args.endY); content = this.clipboardManager.extractSelectionContent(terminalState.content, selection); break; default: throw new Error(`Unknown copy source: ${source}`); } if (content) { await this.clipboardManager.writeClipboard(content, 'text', 'copy'); } return { success: true, data: { message: `Text copied from ${source}`, content: content.substring(0, 100) + (content.length > 100 ? '...' : ''), length: content.length } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to copy text: ${error}` } }; } } async textPaste(args) { const sessionId = args.sessionId; const source = args.source || 'clipboard'; const processing = args.processing || 'raw'; const chunkSize = args.chunkSize || 0; const chunkDelay = args.chunkDelay || 10; try { let content = ''; switch (source) { case 'clipboard': const entry = await this.clipboardManager.readClipboard('text'); content = entry?.content || ''; break; case 'text': content = args.text; break; case 'history': const historyEntry = this.clipboardManager.getHistoryEntry(args.historyIndex); content = historyEntry?.content || ''; break; default: throw new Error(`Unknown paste source: ${source}`); } // Process the text const processedContent = this.clipboardManager.processTextForPasting(content, processing); // Handle chunked pasting if (chunkSize > 0) { const chunks = this.clipboardManager.chunkText(processedContent, chunkSize); for (const chunk of chunks) { await this.terminalManager.sendInput(sessionId, chunk); await new Promise(resolve => setTimeout(resolve, chunkDelay)); } } else { await this.terminalManager.sendInput(sessionId, processedContent); } return { success: true, data: { message: `Text pasted from ${source}`, length: processedContent.length, processing, chunked: chunkSize > 0 } }; } catch (error) { return { success: false, error: { type: 'COMMAND_FAILED', message: `Failed to paste text: ${error}` } }; } } async clipboardHistory(args) { const sessionId = args.sessionId; const action = args.action; try { switch (action) { case 'list': const history = this.clipboardManager.getHistory(); return { success: true, data: { history: history.map(entry => ({ ...entry, content: entry.content.substring(0, 100) + (entry.content.length > 100 ? '...' : '') })) } }; case 'get': const entry = this.clipboardManager.getHistoryEntry(args.index); return { success: true, data: { entry } }; case 'add': await this.clipboardManager.writeClipboard(args.content, 'text', 'api'); return { success: true, data: { message: 'Entry added to clipboard history' } }; case 'remove': const removed =