mcp-tmux-server
Version:
MCP server for TMUX terminal multiplexer operations
118 lines (117 loc) • 4.72 kB
JavaScript
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;
}
}