About RMUX
A local terminal multiplexer written from scratch in Rust, with daemon-owned shells, typed SDK automation, and explicit Web Share browser access.


Native, no WSL
Native ConPTY with named pipes on Windows. Unix PTYs with Unix-domain sockets on Linux and macOS. Network exposure only happens when you explicitly start Web Share or a tunnel.
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
RMUX 0.7.1 exposes a tmux-compatible 90-command surface, with persistent sessions, detach/reattach, and familiar script and key-binding workflows.
Installation
Install the RMUX binary with a package manager, a prebuilt installer, or Cargo.
Linux
Script
Fastest path for a fresh Linux machine.
curl -fsSL https://rmux.io/install.sh | shDebian/Ubuntu
Use the signed APT repository for Debian and Ubuntu.
curl -fsSL https://packages.rmux.io/debian/rmux.asc | sudo tee /usr/share/keyrings/rmux.asc >/dev/null
echo "deb [signed-by=/usr/share/keyrings/rmux.asc] https://packages.rmux.io/debian stable main" | sudo tee /etc/apt/sources.list.d/rmux.list
sudo apt update
sudo apt install rmuxFedora/RHEL
Use the RPM repository through DNF.
sudo dnf config-manager addrepo --from-repofile=https://packages.rmux.io/rpm/rmux.repo
sudo dnf install rmuxCargo
Build from crates.io with Rust installed.
cargo install rmux --lockedmacOS
Script
Downloads the signed release binary and verifies SHA256.
curl -fsSL https://rmux.io/install.sh | shCargo
Build from crates.io with Rust installed.
cargo install rmux --lockedHomebrew
Use the Homebrew formula on macOS.
brew install rmuxWindows
WinGet
Use Windows Package Manager on managed Windows machines.
winget install rmuxPowerShell
Downloads the signed release binary and verifies SHA256.
irm https://rmux.io/install.ps1 | iexCargo
Build from crates.io with Rust installed.
cargo install rmux --lockedScoop
Use the Scoop bucket when Scoop manages this machine.
scoop bucket add rmux https://github.com/Helvesec/scoop-rmux
scoop install rmuxChocolatey
Use Chocolatey when choco manages this machine.
choco install rmux0.6.0 live; 0.7.1 pending review
SDKs
Add the Rust, Python, or TypeScript SDK after the RMUX daemon binary is installed.
Rust SDK
Use the Rust SDK for typed async handles, snapshots, streams, Web Share control, and terminal automation.
cargo add rmux-sdkPython SDK
Use the official Python SDK when scripts or tests should drive RMUX without writing Rust.
python -m pip install librmuxTypeScript SDK
Use the official TypeScript SDK when Node scripts or tests should drive RMUX.
npm install @rmux/sdkrmux binary first. The Rust, Python, and TypeScript SDKs connect to the local RMUX daemon and target the RMUX 0.7 API surface.Configuration
rmux reads config at startup. RMUX config paths win first; if none load, a filtered tmux.conf can be used as a migration fallback.
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%
tmux.conf fallback
If no RMUX config file is loaded during the default startup search,
RMUX can import a filtered tmux.conf as a migration
fallback. Explicit config loading with -f does not use
this fallback.
Linux / macOS fallback
- /etc/tmux.conf
- ~/.tmux.conf
- $XDG_CONFIG_HOME/tmux/tmux.conf
- ~/.config/tmux/tmux.conf
Windows fallback
- %XDG_CONFIG_HOME%\tmux\tmux.conf
- %USERPROFILE%\.tmux.conf
- %APPDATA%\tmux\tmux.conf
#(cmd), recursive source-file entries, unsupported options, non-regular files, and files larger than 1 MiB. Set RMUX_DISABLE_TMUX_FALLBACK=1 to disable it.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. Create a real .rmux.conf when you want deterministic startup; tmux.conf is only a filtered migration fallback.Architecture
rmux runs a local daemon for shells and sessions, plus explicit Web Share endpoints when you choose browser access.
rmux runs as a local daemon that owns sessions, windows, panes, and the PTY processes inside them. Local clients use one IPC protocol. Web Share is explicit browser access: the daemon exposes a selected pane or session through an end-to-end encrypted WebSocket, while the shell stays local.
Runtime shape
Public surfaces
rmux— tmux-compatible CLI and the daemon entrypoint.rmux-sdk— public Rust SDK over the daemon. Typed async handles for sessions, windows, panes, Web Share, and automation.ratatui-rmux— ratatui integration consuming the SDK; embedding a live pane is a widget rather than a custom driver.rmux web-share— browser access for a pane or session. The browser receives encrypted frames, decrypts them locally, and the PTY stays on the local daemon.
Automation model
Locatorqueries visible terminal snapshots. It is terminal-native text matching, not a DOM or CSS selector model.PaneId,pane_by_id, and retained output make pane identity stable across index recompression during a daemon lifetime.Session::new_window_with(), pane split builders, and layout builders create terminal structure without target-string assembly.ProcessCommandSpec::ArgvandProcessCommandSpec::Shellmake launch mode explicit;command()remains legacy shell-compatible.- Keyboard, mouse, fill, capture, screenshot, quiet-state waits, and JSONL tracing are SDK automation layers over the existing daemon protocol.
OwnedSessioncleanup policies and daemon-side leases cover app-owned workspaces, includingKillOnOwnerExit.- Capability negotiation is cached by the SDK; unsupported daemon features surface as typed
RmuxError::Unsupporteddiagnostics.
Workspace crates
Twelve workspace crates are published on crates.io;
rmux-render-core remains an internal rendering building
block. Dependencies flow one way — lower crates never depend on
rmux-server or rmux.
| Crate | Role | Status |
|---|---|---|
rmux-types | Shared low-level value types | crates.io |
rmux-proto | Protocol DTOs, framing, wire-safe errors | crates.io |
rmux-os | OS-boundary helpers | crates.io |
rmux-ipc | Local IPC transport (Unix sockets, Windows named pipes) | crates.io |
rmux-sdk | Public daemon-backed Rust SDK | crates.io |
ratatui-rmux | Public ratatui integration widget | crates.io |
rmux-render-core | Shared rendering primitives | workspace-only |
rmux-web-crypto | Web Share E2EE core and WASM bindings | crates.io |
rmux-pty | PTY allocation, resize, child-process control | crates.io |
rmux-core | In-memory sessions, panes, layouts, hooks | crates.io |
rmux-server | Tokio daemon, lifecycle, request dispatch | crates.io |
rmux-client | Blocking local IPC client | crates.io |
rmux | Public CLI and hidden daemon entrypoint | crates.io |
Protocol v1
The local IPC protocol used by the CLI, SDK, and control clients follows this 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. Web Share uses a separate encrypted
record layer from rmux-web-crypto.
Platform model
- Linux / macOS — Unix-domain sockets for IPC, native Unix PTYs.
- Windows — named pipes for IPC, native ConPTY.
- Default control is local IPC only. Web Share starts an explicit loopback WebSocket for the selected pane or session; remote access requires an explicit tunnel or ingress.
From the shell
Create a detached session, send a command, synchronize with a tmux-style wait-for channel, capture the pane, then attach for interactive use.
rmux new-session -d -s demo
rmux split-window -h -t demo
rmux send-keys -t demo "printf 'test result: ok\n'; rmux wait-for -S demo-done" Enter
rmux wait-for demo-done
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 terminal state.
let pane = session.pane(0, 0);
pane.send_text("echo hello\n").await?;
pane.wait_for_text("hello").await?;
Terminal automation
Drive a real terminal pane with visible-text locators, keyboard actions, quiet-state waits, and visible assertions.
let pane = rmux
.find_panes()
.title("agent:claude")
.one()
.await?;
pane.get_by_text("Ready").wait_for().await?;
pane.keyboard().type_text("printf 'multiplexer ready\n'").await?;
pane.keyboard().press("Enter").await?;
pane.wait_for_load_state(TerminalLoadState::Quiet).await?;
pane.expect_visible_text().to_contain("multiplexer").await?;
PaneSet orchestration
Collect agent panes into a PaneSet, broadcast one prompt, wait across all or any pane, and inspect partial results.
let discovered = rmux
.find_panes()
.title_prefix("agent:")
.running()
.all()
.await?;
let agents = PaneSet::new(discovered.into_iter().map(|pane| pane.pane));
agents
.keyboard()
.type_text("Explain rmux in one sentence.")
.await?;
agents.keyboard().press("Enter").await?;
agents.expect_all().visible_text_contains("rmux").await;
let snapshots = agents.snapshot_all().await;
Run detached
Start a long-running process inside a detached RMUX session and return immediately.
let session = rmux
.ensure_session(
EnsureSession::try_named("web")?
.create_or_reuse()
.detached(true)
.shell("python3 -m http.server 8000"),
)
.await?;
Owned session cleanup
Create a session owned by your app, choose KillOnDrop, KillOnOwnerExit, or Preserve, and make cleanup explicit.
let mut owned = rmux
.owned_session(SessionName::new("agent-run")?)
.replace_existing(true)
.cleanup_policy(CleanupPolicy::KillOnOwnerExit)
.await?;
let _signals = owned.install_default_signal_handlers()?;
let session = owned.session();
session.pane(0, 0).keyboard().type_text("cargo test\n").await?;
owned.cleanup().await?;