About RMUX
A local client/server terminal multiplexer written from scratch in Rust. One daemon and one wire protocol behind three public surfaces: a tmux-compatible CLI, a typed Rust SDK, and a ratatui widget.


Native, no WSL
Native ConPTY with named pipes on Windows. Unix PTYs with Unix-domain sockets on Linux and macOS. The daemon never opens a network listener.
Programmable from Rust
Typed handles for Session, Window,
Pane. Take structured snapshots, send keys, wait
for output, subscribe to pane events. No TTY scraping.
Drop-in tmux
The full tmux 4.0.1 command surface — 90 commands, persistent sessions, your existing scripts and key bindings keep working.
Installation
Install the RMUX binary from the repository and add the SDK to your Cargo project.
CLI
cargo install --path . --lockedSDK
[dependencies]
rmux-sdk = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Configuration
rmux reads a config file at startup. Search order below — the first match wins.
Linux / macOS
- /etc/rmux.conf
- ~/.rmux.conf
- $XDG_CONFIG_HOME/rmux/rmux.conf
- ~/.config/rmux/rmux.conf
Windows
- %XDG_CONFIG_HOME%\rmux\rmux.conf
- %USERPROFILE%\.rmux.conf
- %APPDATA%\rmux\rmux.conf
- %RMUX_CONFIG_FILE%
Example
# Prefix key
set -g prefix C-a
unbind C-b
bind C-a send-prefix
# Splits
bind | split-window -h
bind - split-window -v
# Mouse
set -g mouse onset, bind, unbind, source-file. Existing tmux configs are a good starting point.Architecture
rmux runs as a local daemon. Three public surfaces — CLI, SDK, ratatui widget — share one wire protocol.
rmux runs as a local daemon that owns sessions, windows, panes,
and the PTY processes inside them. Three public surfaces — a
rmux CLI, a rmux-sdk Rust crate, and a
ratatui-rmux widget — talk to the daemon over the
same wire protocol, so anything one surface can do, the others
can do too.
Runtime shape
Public surfaces
rmux— tmux-compatible CLI. The full tmux 4.0.1 command surface plus rmux-specific primitives likewait-for.rmux-sdk— public Rust SDK over the daemon. Typed handles, async, no exposed wire-format types.ratatui-rmux— ratatui integration consuming the SDK; embedding a live pane is a widget rather than a custom driver.
Workspace crates
Six crates are prepared for crates.io; the other six are
internal building blocks. Dependencies flow one way — lower
crates never depend on rmux-server or rmux.
| Crate | Role | Status |
|---|---|---|
rmux-types | Shared low-level value types | public |
rmux-proto | Protocol DTOs, framing, wire-safe errors | public |
rmux-os | OS-boundary helpers | public |
rmux-ipc | Local IPC transport (Unix sockets, Windows named pipes) | public |
rmux-sdk | Public daemon-backed Rust SDK | public |
ratatui-rmux | Public ratatui integration widget | public |
rmux-render-core | Shared rendering primitives | internal |
rmux-pty | PTY allocation, resize, child-process control | internal |
rmux-core | In-memory sessions, panes, layouts, hooks | internal |
rmux-server | Tokio daemon, lifecycle, request dispatch | internal |
rmux-client | Blocking local IPC client | internal |
rmux | Public CLI and hidden daemon entrypoint | internal |
Protocol v1
Every frame on the wire follows the same envelope, defined in rmux-proto:
magic byte 0x52
wire version varint (LEB128)
payload length little-endian u32
payload bincode v1 DTO
The supported wire revisions are tracked in V1_FRAME_LEDGER;
breaking changes bump the varint and add a new entry rather than
mutating the existing frame.
Platform model
- Linux / macOS — Unix-domain sockets for IPC, native Unix PTYs.
- Windows — named pipes for IPC, native ConPTY.
- Local-only. The daemon never opens a network listener.
From the shell
Create a detached session, send a command, wait for a known line in the output, capture the pane, then attach for interactive use. Everything below works the same as in tmux.
rmux new-session -d -s demo
rmux split-window -h -t demo
rmux send-keys -t demo 'cargo test' Enter
rmux wait-for -t demo 'test result: ok'
rmux capture-pane -p -t demo
rmux attach-session -t demo
From Rust
Start or connect to the daemon, ensure one session, write text, wait for it, then capture a snapshot.
let pane = session.pane(0, 0);
pane.send_text("echo hello\n").await?;
pane.wait_for_text("hello").await?;
Run detached
Spawn a long-running process inside a fresh session and walk away — the daemon supervises it while the agent moves on.
use rmux_sdk::{EnsureSession, ProcessSpec, Rmux};
let rmux = Rmux::builder().connect_or_start().await?;
let _session = rmux
.ensure_session(
EnsureSession::try_named("api-server")?
.create_or_reuse()
.detached(true)
.process(ProcessSpec {
command: Some(vec![
"python3".into(),
"-m".into(),
"http.server".into(),
"8080".into(),
]),
environment: None,
}),
)
.await?;
// The handle is dropped here. The daemon keeps the python server
// alive in the background — the agent can move on and reconnect
// to inspect output later.