A libghostty-backed terminal control plane. Spawn, observe, control, persist, and address terminals — locally or across a fleet — with a tmux-shaped TUI riding on top as one consumer among several.
The gap it fills, in the words of libghostty's author:
"Need to replace tmux with a libghostty-based multiplexer so it can understand KIP." — Mitchell Hashimoto
The unit of work is the terminal, not the session or the pane.
Both ends of the wire run libghostty_vt::Terminal: the
server's is the canonical state for a managed terminal; the client's
is a local mirror used for rendering. Nothing in the middle re-parses
VT. Kitty keyboard, true colour, OSC 8, OSC 133, images — they all
pass through end-to-end because the parser is identical on both ends.
The wire is layered. Consumers declare which tiers they speak and the server omits messages from layers they don't subscribe to:
- L1 — Terminal. PTY, bytes-out, structured input, snapshot, lifecycle, events.
- L2 — Collection. Named lifecycle bundle of Terminals. Optional.
- L3 — Metadata. Opaque KV the server stores but doesn't interpret. Where the TUI keeps its layout, window names, focus pointer.
Identity is federation-ready from the first byte. TerminalId is a
LOCAL { id } | SATELLITE { host, id } tagged union. Day-1 servers
construct LOCAL only; day-N hubs route SATELLITE ids to satellites
on other machines. The wire bytes don't change.
- No re-parse in the middle. libghostty parses on both ends. Modern terminal protocols pass through unchanged.
- The terminal is the substrate, not the pane. Sessions, windows, panes, splits live in L3 metadata, not on the wire — an agent SDK never hears them.
- Federation is in the addressing. Local UDS and the remote hub speak the same wire. Not a single-machine tool that one day might support remote attach.
- No consumer is privileged. The reference TUI ships in-tree because the substrate is only real if a real consumer rides it.
For the full mental model, read docs/CONCEPTS.md.
For the long arc, read docs/vision.md.
phux solves one problem and refuses to grow a second: spawn, observe, control, persist, and address libghostty terminals — locally or across a fleet — over a layered wire whose tiers a consumer can target without inheriting everything else. The terminal is the substrate; a consumer is anything that rides it. The reference TUI is the proof that the substrate is real, not the product the substrate exists to serve. Everything that doesn't make terminals more spawnable, observable, or addressable is out of scope on purpose — see Non-goals below.
Pre-alpha. The wire spec leads; the implementation follows it.
Shipped and usable today:
- Auto-attach to a single session; detach; re-attach
- Multi-pane splits, kill, focus, click-to-focus
- Status bar with typed widgets; keybindings (prefix + global chords); help overlay
- Multi-client attach to the same session
- Full bytes-on-wire pass-through (Kitty keyboard, OSC 8, OSC 133, true colour, images)
On the roadmap, with the wire hooks already in place: most of L2
Collection lifecycle, most L3 metadata commands, federation routing,
the agent SDK, predictive local echo, and the rest of the phux <subcommand> CLI surface. The L1 substrate is the part worth building
against now; the rest is designed and spec'd, not yet wired. If that
boundary is where you want to work, CONTRIBUTING.md
is the way in. docs/QUICKSTART.md has the
exact line between the two.
Prebuilt binaries via Homebrew (macOS arm64/x86_64, Linux x86_64):
brew install phall1/phux/phuxTo hack on it instead, build from source — see Quickstart below. The
binary is not on crates.io (it needs zig to build libghostty-vt); only
the phux-protocol wire crate
publishes there. Release mechanics live in
docs/RELEASING.md.
nix develop # or direnv allow once
just ci # the bar — fmt-check + lint + test + deny + doc
cargo run --bin phux # auto-spawns a server and attachesDetach with the default prefix (Ctrl-A d). Walk-through in
docs/QUICKSTART.md.
| You want to | Read |
|---|---|
| Understand the model | docs/CONCEPTS.md |
| Run it | docs/QUICKSTART.md |
| Read the wire spec | docs/spec/ |
| Understand how it's built | docs/architecture/ |
| Read the TUI surface | docs/consumers/tui.md |
| Read the long arc | docs/vision.md |
| See past decisions | ADR/README.md |
| Contribute | CONTRIBUTING.md |
The doc system itself is defined in
docs/CONVENTIONS.md — frontmatter schema,
TL;DR rule, ADR template, CI gates.
| Crate | Purpose |
|---|---|
phux |
Binary; attach and server subcommands today |
phux-protocol |
Wire types, codec, version negotiation. The only crate intended for publication |
phux-core |
Domain types: in-process terminal / collection registries |
phux-server |
Daemon: per-terminal actor, PTY supervision, output fanout |
phux-client |
TUI client: local libghostty Terminal + RenderState redraw + ratatui chrome |
phux-config |
TOML config schema + status widget contract |
Future, not yet started: phux-client-sdk (L1-only typed Rust handle
for agents) and phux-client-gui (native GUI consumer over
libghostty's surface API).
Each of these is a "no" that keeps the substrate honest, not a feature deferred:
- No embedded scripting language. Commands are typed IPC messages. Logic that wants a runtime can shell out to one.
- No plugin host. Hooks are typed events. A plugin contract, if it ever lands, comes after we know what is genuinely pluggable.
- No copy-mode reinvention. Selection and extraction belong to libghostty and the host terminal. phux owns exactly one primitive libghostty doesn't provide: literal search over scrollback.
- No homegrown crypto. SSH and Unix-socket permissions are the trust model.
- No format-template DSL. The status bar takes typed widgets, not a printf dialect to maintain forever.
Full rationale in CONTRIBUTING.md.
Dual-licensed under MIT or Apache-2.0 at your option.