Skip to content

phall1/phux

phux

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

What it is

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.

Why it looks different from tmux underneath

  • 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.

Philosophy

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.

Status

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.

Install

Prebuilt binaries via Homebrew (macOS arm64/x86_64, Linux x86_64):

brew install phall1/phux/phux

To 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.

Quickstart

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 attaches

Detach with the default prefix (Ctrl-A d). Walk-through in docs/QUICKSTART.md.

Where to go next

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.

Crates

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).

Non-goals

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.

License

Dual-licensed under MIT or Apache-2.0 at your option.

Packages

 
 
 

Contributors

Languages