interm-mcp
Version:
MCP server for terminal applications and TUI automation with 127 tools
1,302 lines • 99.3 kB
JavaScript
#!/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 =