/** * tmux send-keys input translation. * * Translates IC-1 key names (and literal text) into tmux send-keys arguments. * Used by the input route (T-1.5) to deliver keystrokes to a pane. * * Owner: T-1.1 */ import { execFile } from "node:child_process"; import { promisify } from "node:util"; const execFileAsync = promisify(execFile); /** Named keys from IC-1 ClientToServer `{ type: "key"; name: string }`. */ const KEY_MAP: Record = { escape: "Escape", tab: "Tab", up: "Up", down: "Down", left: "Left", right: "Right", enter: "Enter", "shift-enter": "S-Enter", backspace: "BSpace", "ctrl-c": "C-c", "ctrl-d": "C-d", "ctrl-z": "C-z", }; /** * Send a single named key to a tmux pane. * Pane defaults to the first pane of the session (session:0.0). */ export async function sendKey(session: string, name: string): Promise { const tmuxKey = KEY_MAP[name.toLowerCase()]; if (!tmuxKey) { throw new Error( `Unknown key name: "${name}". Supported: ${Object.keys(KEY_MAP).join(", ")}`, ); } // Target just the session — tmux selects the active window/pane automatically. // Avoids base-index issues (user's tmux.conf may start windows at 1, not 0). await execFileAsync("tmux", ["send-keys", "-t", session, tmuxKey]); } /** * Send literal text to a tmux pane (IC-1 `{ type: "keys"; data: string }`). * Uses send-keys -l which sends each character literally. */ export async function sendKeys(session: string, data: string): Promise { await execFileAsync("tmux", ["send-keys", "-t", session, "-l", data]); } /** * Send bracketed-paste to a tmux pane (IC-1 `{ type: "paste"; data: string }`). * Wraps the data in bracketed-paste sequences then sends literally. */ export async function sendPaste(session: string, data: string): Promise { const wrapped = `\x1b[200~${data}\x1b[201~`; await execFileAsync("tmux", ["send-keys", "-t", session, "-l", wrapped]); }