execute-command
Execute commands in tmux panes to control terminal sessions, with options for interactive applications and TUI navigation.
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
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| paneId | Yes | ID of the tmux pane | |
| command | Yes | Command to execute | |
| rawMode | No | 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 | No | 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. |
Implementation Reference
- src/index.ts:356-389 (handler)The MCP tool handler for 'execute-command'. Prepares parameters, calls tmux.executeCommand, and returns appropriate response or resource URI based on mode.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/index.ts:350-355 (schema)Zod schema defining input parameters for the 'execute-command' tool.{ 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.") },
- src/index.ts:347-391 (registration)Registration of the 'execute-command' tool on the MCP server using server.tool().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 (helper)Helper function implementing the core logic of sending commands to a tmux pane via tmux send-keys, with support for raw mode, no-enter mode, and execution tracking.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; }