execute-command
Run commands in a tmux pane, retrieve results, and handle interactive applications using rawMode. Avoid multi-line constructs for non-rawMode executions. Ideal for terminal session control and monitoring.
Instructions
Execute a command in a tmux pane and get results. For interactive applications (REPLs, editors), use rawMode=true. IMPORTANT: When rawMode=false (default), avoid heredoc syntax (cat << EOF) and other multi-line constructs as they conflict with command wrapping. For file writing, prefer: printf 'content\n' > file, echo statements, or write to temp files instead
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | Command to execute | |
| paneId | Yes | ID of the tmux pane | |
| rawMode | No | Execute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane to monitor interactive apps. |
Implementation Reference
- src/index.ts:347-391 (registration)Registration of the 'execute-command' MCP tool, including input schema (paneId, command, rawMode, noEnter), description, and handler function that delegates to tmux.executeCommand and handles rawMode/noEnter modes.server.tool( "execute-command", "Execute a command in a tmux pane and get results. For interactive applications (REPLs, editors), use `rawMode=true`. IMPORTANT: When `rawMode=false` (default), avoid heredoc syntax (cat << EOF) and other multi-line constructs as they conflict with command wrapping. For file writing, prefer: printf 'content\\n' > file, echo statements, or write to temp files instead", { paneId: z.string().describe("ID of the tmux pane"), command: z.string().describe("Command to execute"), rawMode: z.boolean().optional().describe("Execute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane after execution to verify command outcome."), noEnter: z.boolean().optional().describe("Send keystrokes without pressing Enter. For TUI navigation in apps like btop, vim, less. Supports special keys (Up, Down, Escape, Tab, etc.) and strings (sent char-by-char for proper filtering). Automatically applies rawMode. Use capture-pane after to see results.") }, async ({ paneId, command, rawMode, noEnter }) => { try { // If noEnter is true, automatically apply rawMode const effectiveRawMode = noEnter || rawMode; const commandId = await tmux.executeCommand(paneId, command, effectiveRawMode, noEnter); if (effectiveRawMode) { const modeText = noEnter ? "Keys sent without Enter" : "Interactive command started (rawMode)"; return { content: [{ type: "text", text: `${modeText}.\n\nStatus tracking is disabled.\nUse 'capture-pane' with paneId '${paneId}' to verify the command outcome.\n\nCommand ID: ${commandId}` }] }; } // Create the resource URI for this command's results const resourceUri = `tmux://command/${commandId}/result`; return { content: [{ type: "text", text: `Command execution started.\n\nTo get results, subscribe to and read resource: ${resourceUri}\n\nStatus will change from 'pending' to 'completed' or 'error' when finished.` }] }; } catch (error) { return { content: [{ type: "text", text: `Error executing command: ${error}` }], isError: true }; } } );
- src/tmux.ts:243-288 (handler)Core helper function implementing the command execution logic: generates commandId, wraps command with start/end markers (unless rawMode), tracks execution status, sends keys to tmux pane using tmux send-keys, handles special keys and char-by-char input for noEnter mode.export async function executeCommand(paneId: string, command: string, rawMode?: boolean, noEnter?: boolean): Promise<string> { // Generate unique ID for this command execution const commandId = uuidv4(); let fullCommand: string; if (rawMode || noEnter) { fullCommand = command; } else { const endMarkerText = getEndMarkerText(); fullCommand = `echo "${startMarkerText}"; ${command}; echo "${endMarkerText}"`; } // Store command in tracking map activeCommands.set(commandId, { id: commandId, paneId, command, status: 'pending', startTime: new Date(), rawMode: rawMode || noEnter }); // Send the command to the tmux pane if (noEnter) { // Check if this is a special key (e.g., Up, Down, Left, Right, Escape, Tab, etc.) // Special keys in tmux are typically capitalized or have special names const specialKeys = ['Up', 'Down', 'Left', 'Right', 'Escape', 'Tab', 'Enter', 'Space', 'BSpace', 'Delete', 'Home', 'End', 'PageUp', 'PageDown', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12']; if (specialKeys.includes(fullCommand)) { // Send special key as-is await executeTmux(`send-keys -t '${paneId}' ${fullCommand}`); } else { // For regular text, send each character individually to ensure proper processing // This handles both single characters (like 'q', 'f') and strings (like 'beam') for (const char of fullCommand) { await executeTmux(`send-keys -t '${paneId}' '${char.replace(/'/g, "'\\''")}'`); } } } else { await executeTmux(`send-keys -t '${paneId}' '${fullCommand.replace(/'/g, "'\\''")}' Enter`); } return commandId; }
- src/tmux.ts:290-334 (helper)Helper function to check command status by capturing pane content, detecting end markers to extract exit code and output, updating status to 'completed' or 'error'. Used by 'get-command-result' tool.export async function checkCommandStatus(commandId: string): Promise<CommandExecution | null> { const command = activeCommands.get(commandId); if (!command) return null; if (command.status !== 'pending') return command; const content = await capturePaneContent(command.paneId, 1000); if (command.rawMode) { command.result = 'Status tracking unavailable for rawMode commands. Use capture-pane to monitor interactive apps instead.'; return command; } // Find the last occurrence of the markers const startIndex = content.lastIndexOf(startMarkerText); const endIndex = content.lastIndexOf(endMarkerPrefix); if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) { command.result = "Command output could not be captured properly"; return command; } // Extract exit code from the end marker line const endLine = content.substring(endIndex).split('\n')[0]; const endMarkerRegex = new RegExp(`${endMarkerPrefix}(\\d+)`); const exitCodeMatch = endLine.match(endMarkerRegex); if (exitCodeMatch) { const exitCode = parseInt(exitCodeMatch[1], 10); command.status = exitCode === 0 ? 'completed' : 'error'; command.exitCode = exitCode; // Extract output between the start and end markers const outputStart = startIndex + startMarkerText.length; const outputContent = content.substring(outputStart, endIndex).trim(); command.result = outputContent.substring(outputContent.indexOf('\n') + 1).trim(); // Update in map activeCommands.set(commandId, command); } return command; }
- src/index.ts:351-355 (schema)Zod schema defining inputs for 'execute-command' tool: required paneId and command, optional rawMode and noEnter.paneId: z.string().describe("ID of the tmux pane"), command: z.string().describe("Command to execute"), rawMode: z.boolean().optional().describe("Execute command without wrapper markers for REPL/interactive compatibility. Disables get-command-result status tracking. Use capture-pane after execution to verify command outcome."), noEnter: z.boolean().optional().describe("Send keystrokes without pressing Enter. For TUI navigation in apps like btop, vim, less. Supports special keys (Up, Down, Escape, Tab, etc.) and strings (sent char-by-char for proper filtering). Automatically applies rawMode. Use capture-pane after to see results.") },