A CDP-like protocol for terminal automation. Control any terminal emulator programmatically — send keystrokes, read the screen, take screenshots, and run commands over WebSocket JSON-RPC.
Built for AI coding agents that need to interact with terminals the way humans do.
curl -fsSL https://raw.githubusercontent.com/moejay/wrightty/main/install.sh | shcargo install wrighttygit clone https://github.com/moejay/wrightty.git
cd wrightty
cargo build --release -p wrightty
# binary is at target/release/wrighttyThe headless feature (enabled by default) pulls in alacritty_terminal for virtual PTY support. For a lighter build with only bridge support:
cargo build --release -p wrightty --no-default-features --features bridge-tmux,bridge-wezterm,clientpip install wrightty # Python
npm install @moejay/wrightty # Node.js# Headless (no GUI, great for CI/automation)
wrightty term --headless
# Or bridge to your existing terminal:
wrightty term --bridge-tmux # tmux (works with any terminal)
wrightty term --bridge-wezterm # WezTerm
wrightty term --bridge-kitty # Kitty
wrightty term --bridge-ghostty # Ghostty
wrightty term --bridge-zellij # Zellij
# Or use the Alacritty fork with native support:
wrightty term --alacritty
# With a name and password:
wrightty term --headless --name my-server --password secret123wrightty run "ls -la" # run a command, print output
wrightty read # read the screen
wrightty send-keys Ctrl+c # send keystrokes
wrightty screenshot --format svg -o t.svg # take a screenshot
wrightty wait-for "BUILD SUCCESS" # wait for text
wrightty discover # find running serversPython:
from wrightty import Terminal
term = Terminal.connect() # auto-discovers running server
output = term.run("cargo test")
print(output)
term.wait_for("$")
term.send_keys("Ctrl+c")
svg = term.screenshot("svg")
term.close()Node.js:
import { Terminal } from "wrightty";
const term = await Terminal.connect();
const output = await term.run("cargo test");
console.log(output);
await term.waitFor("$");
await term.sendKeys("Ctrl+c");
const screenshot = await term.screenshot("svg");
term.close();Raw protocol:
wscat -c ws://127.0.0.1:9420{"jsonrpc":"2.0","id":1,"method":"Screen.getText","params":{"sessionId":"0"}}Spawns a virtual terminal with no window. Uses Alacritty's terminal emulator under the hood. Good for CI, testing, and headless automation.
wrightty term --headless
wrightty term --headless --port 9420 --max-sessions 64# Spawn a new session on the headless server
term = Terminal.spawn(server_url="ws://127.0.0.1:9420")
output = term.run("echo hello")
term.close()Uses a fork of Alacritty with wrightty built in. Zero overhead — reads directly from Alacritty's own terminal state.
# Install the fork first:
git clone -b wrightty-support https://github.com/moejay/alacritty.git
cd alacritty && cargo install --path alacritty --features wrightty
# Then launch via wrightty:
wrightty term --alacrittyBridges translate wrightty protocol calls into your terminal's existing IPC. No terminal modifications needed.
| Bridge | Command | Requirements |
|---|---|---|
| tmux | wrightty term --bridge-tmux |
tmux server running |
| WezTerm | wrightty term --bridge-wezterm |
WezTerm running |
| Kitty | wrightty term --bridge-kitty |
allow_remote_control yes in kitty.conf |
| Zellij | wrightty term --bridge-zellij |
Run from within a Zellij session |
| Ghostty | wrightty term --bridge-ghostty |
Ghostty running; xdotool for input on Linux |
For terminals without IPC (foot, GNOME Terminal, Rio, etc.), use tmux or zellij as a bridge — it works with any terminal.
Native Ghostty fork also available: moejay/ghostty has Wrightty built directly into Ghostty.
pip install wrightty
# or: pip install wrightty[mcp] # for MCP server supportfrom wrightty import Terminal
# Connect to a running terminal
term = Terminal.connect("ws://127.0.0.1:9420")
# Run a command and get its output
output = term.run("cargo build 2>&1", timeout=120)
# Read the screen
screen = term.read_screen()
# Wait for text to appear
term.wait_for("tests passed", timeout=60)
term.wait_for(r"error\[\w+\]", regex=True)
# Send keystrokes (for TUI apps)
term.send_keys("Escape", ":", "w", "q", "Enter") # vim save & quit
term.send_keys("Ctrl+c") # interrupt
# Screenshots
svg = term.screenshot("svg")
text = term.screenshot("text")
# Terminal info
cols, rows = term.get_size()
info = term.get_info()
# Recording — session (asciicast, compatible with asciinema)
rec_id = term.start_session_recording(include_input=True)
term.run("make build")
result = term.stop_session_recording(rec_id)
open("build.cast", "w").write(result["data"]) # asciinema play build.cast
# Recording — actions (generates replayable scripts)
rec_id = term.start_action_recording(format="python")
term.send_text("echo hello\n")
term.send_keys("Ctrl+c")
result = term.stop_action_recording(rec_id)
print(result["data"]) # prints a Python script that replays these actions
term.close()npm install @moejay/wrighttyimport { Terminal } from "@moejay/wrightty";
// Auto-discover running server
const term = await Terminal.connect();
// Or connect to a specific server
const term = await Terminal.connect({ url: "ws://127.0.0.1:9420" });
// Run commands
const output = await term.run("cargo test", 120_000);
// Read screen
const screen = await term.readScreen();
// Wait for text
await term.waitFor("tests passed", 60_000);
await term.waitFor(/error\[\w+\]/);
// Send keystrokes
await term.sendKeys("Escape", ":", "w", "q", "Enter");
await term.sendKeys("Ctrl+c");
// Screenshots
const svg = await term.screenshot("svg");
// Terminal info
const [cols, rows] = await term.getSize();
const info = await term.getInfo();
// Recording
const recId = await term.startSessionRecording(true);
await term.run("make build");
const result = await term.stopSessionRecording(recId);
term.close();# Server / bridge modes
wrightty term --headless # headless terminal server
wrightty term --alacritty # alacritty fork
wrightty term --bridge-tmux # tmux bridge
wrightty term --bridge-wezterm # wezterm bridge
wrightty term --bridge-kitty # kitty bridge
wrightty term --bridge-zellij # zellij bridge
wrightty term --bridge-ghostty # ghostty bridge
# Common options for `wrightty term`
--host 127.0.0.1 # bind address
--port 9420 # port (default: auto-select)
--max-sessions 64 # max sessions (headless)
--watchdog-interval 5 # health check interval (bridges)
# Client commands
wrightty run "ls -la" # run command, print output
wrightty run "cargo test" --timeout 120 # with timeout
wrightty read # read terminal screen
wrightty send-text "echo hello\n" # send raw text
wrightty send-keys Ctrl+c # send keystrokes
wrightty send-keys Escape : w q Enter # multiple keys
wrightty screenshot --format svg -o t.svg # screenshot
wrightty wait-for "BUILD SUCCESS" # wait for text
wrightty wait-for "error" --regex # regex pattern
wrightty discover # find servers
wrightty discover --json # machine-readable
wrightty info # server info
wrightty size # terminal dimensions
wrightty session list # list sessions
wrightty session create # create session (headless)
wrightty session destroy <id> # destroy session
# Connection options (all client commands)
--url ws://127.0.0.1:9420 # server URL
--session <id> # session IDWrightty includes an MCP server that exposes terminal control as tools for AI agents.
{
"mcpServers": {
"wrightty": {
"command": "python3",
"args": ["-m", "wrightty.mcp_server"],
"env": {
"WRIGHTTY_SOCKET": "ws://127.0.0.1:9420"
}
}
}
}Tools exposed: run_command, read_terminal, send_keys, send_text, screenshot, wait_for_text, terminal_info, start_recording, stop_recording, capture_screen_frame.
The full protocol specification is in PROTOCOL.md.
7 domains, 28 methods, all over WebSocket JSON-RPC 2.0:
| Domain | Methods |
|---|---|
| Wrightty | getInfo — capability negotiation |
| Session | create, destroy, list, getInfo |
| Input | sendKeys, sendText, sendMouse |
| Screen | getContents, getText, getScrollback, screenshot, waitForText, waitForCursor |
| Terminal | resize, getSize, setColorPalette, getColorPalette, getModes |
| Recording | startSession, stopSession, startActions, stopActions, captureScreen, startVideo, stopVideo |
| Events | subscribe, unsubscribe — screen updates, bell, title change, shell integration |
wrightty/
├── crates/
│ ├── wrightty/ # Unified CLI binary (this is what you install)
│ ├── wrightty-protocol/ # Protocol types (serde, no logic)
│ ├── wrightty-core/ # Headless terminal engine (alacritty_terminal + PTY)
│ ├── wrightty-server/ # WebSocket daemon library
│ ├── wrightty-client/ # Rust client SDK
│ ├── wrightty-bridge-wezterm/ # WezTerm bridge
│ ├── wrightty-bridge-tmux/ # tmux bridge
│ ├── wrightty-bridge-kitty/ # Kitty bridge
│ ├── wrightty-bridge-zellij/ # Zellij bridge
│ └── wrightty-bridge-ghostty/ # Ghostty bridge
├── sdks/
│ ├── python/ # Python SDK + MCP server
│ └── node/ # Node.js/TypeScript SDK
├── install.sh # One-liner installer
└── PROTOCOL.md # Full protocol specification
| Terminal | Read screen | Send input | Sessions | Screenshot | Integration |
|---|---|---|---|---|---|
| Headless | ✅ | ✅ | ✅ | ✅ text/json | wrightty term --headless |
| Alacritty | ✅ | ✅ | — | ✅ SVG | wrightty term --alacritty |
| WezTerm | ✅ | ✅ | ✅ | — | wrightty term --bridge-wezterm |
| Kitty | ✅ | ✅ | ✅ | — | wrightty term --bridge-kitty |
| tmux | ✅ | ✅ | ✅ | — | wrightty term --bridge-tmux |
| Zellij | ✅ | ✅ | ✅ | — | wrightty term --bridge-zellij |
| Ghostty | ✅ | ✅ | ✅ | ✅ text | wrightty term --bridge-ghostty |
| foot, GNOME Terminal, etc. | ✅ | ✅ | ✅ | — | Use with tmux/zellij bridge |
If your terminal has any way to read screen content and send input (CLI, socket, D-Bus, API), a wrightty bridge can be built. See crates/wrightty-bridge-wezterm/ for a reference implementation.
For terminals with no IPC, pair them with tmux or zellij and use that bridge.
MIT