UNPKG

mcp-tmux-server

Version:

MCP server for TMUX terminal multiplexer operations

118 lines (117 loc) 4.72 kB
import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class TmuxController { async listSessions() { try { const { stdout } = await execAsync('tmux list-sessions -F "#{session_name}:#{session_id}:#{session_windows}:#{session_attached}:#{session_created}"'); return stdout.trim().split('\n').filter(line => line).map(line => { const [name, id, windows, attached, created] = line.split(':'); return { name, id, windows: parseInt(windows), attached: attached === '1', created: new Date(parseInt(created) * 1000).toISOString() }; }); } catch (error) { if (error.stderr?.includes('no server running')) { return []; } throw error; } } async createSession(name, command) { const cmd = command ? `tmux new-session -d -s "${name}" "${command}"` : `tmux new-session -d -s "${name}"`; await execAsync(cmd); return `Session "${name}" created successfully`; } async killSession(name) { await execAsync(`tmux kill-session -t "${name}"`); return `Session "${name}" killed successfully`; } async listWindows(session) { const target = session ? `-t "${session}"` : ''; const { stdout } = await execAsync(`tmux list-windows ${target} -F "#{window_id}:#{window_name}:#{window_active}:#{session_name}:#{window_panes}"`); return stdout.trim().split('\n').filter(line => line).map(line => { const [id, name, active, sessionName, panes] = line.split(':'); return { id, name, active: active === '1', session: sessionName, panes: parseInt(panes) }; }); } async createWindow(session, name, command) { let cmd = `tmux new-window -t "${session}"`; if (name) cmd += ` -n "${name}"`; if (command) cmd += ` "${command}"`; await execAsync(cmd); return `Window created in session "${session}"${name ? ` with name "${name}"` : ''}`; } async killWindow(target) { await execAsync(`tmux kill-window -t "${target}"`); return `Window "${target}" killed successfully`; } async sendKeys(target, keys, enter = true) { const cmd = enter ? `tmux send-keys -t "${target}" "${keys}" C-m` : `tmux send-keys -t "${target}" "${keys}"`; await execAsync(cmd); return `Sent keys "${keys}" to "${target}"`; } async splitWindow(target, vertical = false, command) { let cmd = `tmux split-window -t "${target}"`; if (vertical) cmd += ' -v'; if (command) cmd += ` "${command}"`; await execAsync(cmd); return `Split window in "${target}" ${vertical ? 'vertically' : 'horizontally'}`; } async listPanes(target) { const { stdout } = await execAsync(`tmux list-panes -t "${target}" -F "#{pane_id}:#{pane_active}:#{pane_width}:#{pane_height}:#{pane_current_command}"`); return stdout.trim().split('\n').filter(line => line).map(line => { const [id, active, width, height, command] = line.split(':'); return { id, active: active === '1', width: parseInt(width), height: parseInt(height), command }; }); } async getSessionInfo(name) { const { stdout } = await execAsync(`tmux display-message -t "${name}" -p "#{session_name}:#{session_id}:#{session_windows}:#{session_attached}:#{session_created}:#{client_width}:#{client_height}"`); const [sessionName, id, windows, attached, created, width, height] = stdout.trim().split(':'); return { name: sessionName, id, windows: parseInt(windows), attached: attached === '1', created: new Date(parseInt(created) * 1000).toISOString(), dimensions: { width: parseInt(width), height: parseInt(height) } }; } async capturePane(target, startLine, endLine) { let cmd = `tmux capture-pane -t "${target}" -p`; if (startLine !== undefined) cmd += ` -S ${startLine}`; if (endLine !== undefined) cmd += ` -E ${endLine}`; const { stdout } = await execAsync(cmd); return stdout; } }