Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 104 additions & 118 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,170 +6,156 @@ last-reviewed: 2026-06-03

# phux

**A terminal multiplexer that doesn't re-parse your terminal.**
*A terminal multiplexer. It's pronounced however you just pronounced it.*

tmux and screen sit in the middle of the wire and re-interpret every byte your
programs emit — which is why kitty graphics, undercurl, pixel-precision mouse,
and half of OSC degrade or vanish across a detach/reattach. phux runs the *same*
VT parser ([libghostty][lghv]) on **both ends** of the connection, so modern
terminal protocols survive a remote attach losslessly. Structurally — not "we
patched the common cases."
It does roughly what tmux does. You attach, you split some panes, you
detach, your shell keeps running, you come back later and it's all still
there. So far, not a sales pitch.

And the thing it multiplexes isn't a *session* or a *pane*. It's a **terminal**:
a first-class object you can spawn, observe, drive, and address — by hand from a
tmux-shaped TUI, or headless from a script or an agent over a typed wire, on this
box or across a fleet.
Two things are a little different.

```sh
phux # attach (auto-starts a server) — the tmux-shaped TUI
phux ls --json # list sessions, machine-readable
phux send-keys build:0.1 'cargo test' Enter
phux run build "cargo test" # run in a real pane, get the exit code back
phux wait build --until "0 failed" # block until output matches, then exit 0
phux watch --json work:1.0 | jq . # live event stream: bells, titles, dirty/idle, lifecycle
```
**One.** The modern terminal stuff — kitty graphics, truecolor, hyperlinks,
the works — doesn't fall apart when you detach and reattach. It just keeps
working. Boring, on purpose.

<!-- DEMO: drop a ~10s asciinema cast / GIF here — kitty graphics + truecolor
surviving a detach/reattach that tmux would smear. This is the single
highest-leverage asset on the page; nothing converts this audience like
seeing the thing render. -->
**Two.** An AI agent can drive the same terminal you're looking at. Not a
screenshot of it. Not a scrape. The actual terminal, over an actual API. You
didn't have to turn on a "robot mode," because there isn't one. There's just
terminals — and some of the things attached to them happen to be people.

## What actually works today
That second one is doing more than it lets on. We'll get to it.

Two surfaces ride the same source-of-truth libghostty `Terminal`:
<!-- DEMO: a ~10s asciinema cast / GIF goes RIGHT HERE. Show kitty graphics +
truecolor surviving a detach/reattach, then a `phux run`/`phux watch`
line so the agent angle lands in the same breath. This is the single
most important pixel on the page. Recipe and storyboard: docs/demo.md.
When the GIF lands at docs/assets/demo.gif, replace this whole comment
with a standard markdown image pointing at that path. -->

- **The TUI** — auto-attach/detach/re-attach, multi-pane splits, status bar,
keybindings, multi-client attach. Full modern-protocol passthrough (Kitty
keyboard, truecolor, OSC 8 hyperlinks, OSC 133, images) because the parser is
identical on both ends.
- **The headless / agent surface** — every command above is real and tested.
A selector grammar (`name`, `name:window.pane`, `@id`, `.` focused, `=`
last-focused) addresses any pane; reads are side-effect-free (no attach, no
resize) and take `--json`. Same surface is exposed over MCP by the `phux-mcp`
crate — six tools, JSON-RPC over stdio, no protocol privilege. **This is the
part you point an agent at.**
## Try it

Both are real binaries you can run now, not a roadmap. The honest line on what's
stable vs. still moving is in [Status](#status).
```sh
brew install phall1/phux/phux
phux
```

## The idea
That's the whole thing. You're attached. Detach with `Ctrl-A d`; the server
keeps your shell alive. Type `phux` again and you're right back where you were.

Two decisions do all the work.
(No Homebrew, or you want to hack on it? [Install from source](./docs/INSTALL.md)
— it's a Nix dev shell and one `cargo run`.)

**The same parser on both ends.** The server's libghostty `Terminal` is
canonical; the client's is a local mirror for rendering. Nothing in the middle
re-parses VT — so new terminal features light up on the next libghostty bump, on
both ends, for free. Older multiplexers re-parse mid-path and degrade fidelity.
phux structurally can't.
## The part the GUI doesn't show you

**The terminal is the unit, not the session.** Sessions, windows, panes, splits
— the whole tmux vocabulary — live in the TUI's metadata layer, never on the
wire. An agent speaks to *terminals* and never hears the word "window." That's
why a non-human consumer is a first-class citizen instead of a screen-scraping
hack bolted onto a tool built for one human at a keyboard.
Everything above also works without a TTY. Same terminals, addressed by name
or id, driven from a script — or an agent — with clean JSON coming back:

The wire is layered — L1 Terminal / L2 Collection / L3 Metadata — and consumers
declare which tiers they speak so the server omits the rest. Identity is
federation-ready from byte zero: `TerminalId` is `LOCAL{id} | SATELLITE{host,id}`.
v0.1 constructs `LOCAL`; the wire already accepts `SATELLITE`; v0.2 routes it;
the bytes never change. Full mental model: [`docs/CONCEPTS.md`](./docs/CONCEPTS.md).
```sh
phux ls --json # what's running
phux send-keys build:0.1 'cargo test' Enter
phux run build "cargo test" # runs in a real pane, hands you the exit code
phux wait build --until "0 failed" # blocks until the output shows up, exits 0
phux watch --json work:1.0 | jq . # live events: bells, titles, idle, lifecycle
```

[lghv]: https://github.com/Uzaaft/libghostty-rs
Point an MCP-speaking agent at it instead and it gets the same surface as tools
— `phux-mcp` exposes the core verbs over JSON-RPC stdio. The agent isn't peeking
at a human's session through a keyhole. It's a first-class user with the same keys.

## Install
## Why it's built this way

Homebrew (macOS, Linux x86_64):
Old multiplexers were built for one person at one keyboard, because that was
the only kind of user there was. Fair enough at the time.

```sh
brew install phall1/phux/phux
```
Now some of the users are programs. A program doesn't want a screen to read
pixels off of — it wants to start a thing, find out when it's done, and read
the exit code. The funny part: if you build the terminal so a program can use
it cleanly, the human gets a better deal too. Nothing re-interprets your bytes
in the middle, so nothing gets mangled on the way through.

From source — the toolchain is Nix-pinned (Rust 1.90 + Zig for libghostty's
build), so the dev shell is the supported path:
So phux makes the **terminal** the thing — not the "session," not the
"window." A human's TUI and an agent's API are just two ways to hold the same
object, and neither one is the real customer.

```sh
nix develop # or: direnv allow
cargo run --bin phux # auto-spawns a server and attaches
```
> Under the hood: the same terminal engine ([libghostty][lghv]) runs on both
> ends of the wire, so bytes pass straight through instead of getting re-parsed
> and degraded. That's the unglamorous reason the fancy stuff survives a
> reattach. You don't have to care about it. It just means things work.

Detach with the default prefix, `Ctrl-A d`. Full walk-through:
[`docs/QUICKSTART.md`](./docs/QUICKSTART.md).
The longer version, with the boxes-and-arrows: [`docs/CONCEPTS.md`](./docs/CONCEPTS.md).

## Status
[lghv]: https://github.com/Uzaaft/libghostty-rs

**Stable, shipped — L1 terminal control plane:**
- TUI: attach / detach / re-attach, multi-pane splits, status bar, keybindings,
multi-client attach
- libghostty wire protocol (VT bytes server→client, structured input
client→server), version-negotiated. `phux-protocol` is the only crate
published to crates.io.
## What actually works today

It's v0.1. Calibrate accordingly, then get pleasantly surprised.

**Solid, won't move under you:**
- The TUI — attach / detach / re-attach, multi-pane splits, status bar,
keybindings, multiple clients on one session
- Full modern-protocol passthrough (Kitty keyboard, truecolor, OSC 8, OSC 133,
images), because the parser is identical on both ends
- The wire itself, version-negotiated. `phux-protocol` is the one crate on
crates.io.

**Shipped and tested, API still moving (pre-1.0):**
- Headless verbs: `ls`, `snapshot`, `send-keys`, `run`, `wait`, `watch`, `new`,
`kill`, `rename`, `config` — selector grammar + `--json`
- `phux-mcp`: MCP adapter exposing six tools over JSON-RPC stdio
- L2 agent protocol layer (collections, terminal-state reads, event subscriptions)
**Real and tested, but the API may still wiggle before 1.0:**
- The headless verbs above: `ls`, `snapshot`, `send-keys`, `run`, `wait`,
`watch`, `new`, `kill`, `rename`, `config`
- `phux-mcp` — the same surface as MCP tools

**Not yet — addressing is in the wire, routing is not:**
- Control-plane routing across satellites (`SATELLITE{host,id}` ids are accepted
today, not yet routed)
- Native GUI consumer over libghostty's surface API
**Designed, addressed-for, not wired yet:**
- Driving terminals across machines. The wire already speaks
`SATELLITE{host, id}`; nothing routes it yet. v0.2.
- A native GUI consumer, a typed Rust SDK crate, predictive local echo.

Building against it now? L1 is the part that won't move under you. Contributor
roadmap + constraints: [`CONTRIBUTING.md`](./CONTRIBUTING.md).
If it's not in one of those first two lists, it's a promise, not a feature.
We try to keep that line honest.

## Where to go next
## Where to go from here

| You want to | Read |
|---|---|
| Understand the model | [`docs/CONCEPTS.md`](./docs/CONCEPTS.md) |
| Run it | [`docs/QUICKSTART.md`](./docs/QUICKSTART.md) |
| Customize config and keybindings | [`docs/CONFIG.md`](./docs/CONFIG.md) |
| Get it on your machine | [`docs/INSTALL.md`](./docs/INSTALL.md) |
| First session, start to finish | [`docs/QUICKSTART.md`](./docs/QUICKSTART.md) |
| Actually understand it | [`docs/CONCEPTS.md`](./docs/CONCEPTS.md) |
| Drive it from an agent | [`docs/consumers/agents.md`](./docs/consumers/agents.md) · [`docs/consumers/mcp.md`](./docs/consumers/mcp.md) |
| Customize keys and config | [`docs/CONFIG.md`](./docs/CONFIG.md) |
| Read the wire spec | [`docs/spec/`](./docs/spec/) |
| Understand how it's built | [`docs/architecture/`](./docs/architecture/) |
| Read the TUI surface | [`docs/consumers/tui.md`](./docs/consumers/tui.md) |
| Read the long arc | [`docs/vision.md`](./docs/vision.md) |
| See how it's built | [`docs/architecture/`](./docs/architecture/) |
| Read where it's going | [`docs/vision.md`](./docs/vision.md) |
| See past decisions | [`ADR/README.md`](./ADR/README.md) |
| Contribute | [`CONTRIBUTING.md`](./CONTRIBUTING.md) |

The doc system itself is defined in [`docs/CONVENTIONS.md`](./docs/CONVENTIONS.md)
— frontmatter schema, TL;DR rule, ADR template, CI gates.
| Build it with us | [`CONTRIBUTING.md`](./CONTRIBUTING.md) |

## Crates

| Crate | Purpose |
| Crate | Does |
|---|---|
| `phux` | Binary; `attach`/`server` + the headless verbs |
| `phux-protocol` | Wire types, codec, version negotiation. The only crate intended for publication |
| `phux` | The binary: `attach` / `server` plus the headless verbs |
| `phux-protocol` | Wire types, codec, version negotiation. The only one meant for publishing |
| `phux-core` | Domain types: in-process terminal / collection registries |
| `phux-server` | Daemon: per-terminal actor, PTY supervision, output fanout |
| `phux-server` | The daemon: per-terminal actor, PTY supervision, output fanout |
| `phux-client-core` | Renderer + protocol client, ratatui-free (the boundary is compiler-enforced) |
| `phux-client` | TUI chrome (ratatui) over `phux-client-core` |
| `phux-client` | The TUI chrome (ratatui) over `phux-client-core` |
| `phux-config` | TOML config schema + status widget contract |
| `phux-mcp` | MCP adapter: the agent CLI surface over JSON-RPC stdio |

Future, not yet started: `phux-client-gui` (native GUI consumer over libghostty's
surface API).
| `phux-mcp` | The agent surface as MCP tools, over JSON-RPC stdio |

## Non-goals
## Things phux deliberately won't do

Each of these is a "no" that keeps the substrate honest, not a feature deferred:
Each of these is a "no" that keeps the thing honest, not a feature we forgot:

- **No embedded scripting language.** Commands are typed IPC messages. Logic that
- **No embedded scripting language.** Commands are typed 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 plugin host.** Hooks are typed events. A plugin contract, if it ever
shows up, comes after we know what's actually pluggable.
- **No copy-mode reinvention.** Selection belongs to your terminal. phux owns
exactly one primitive nobody else provides: 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.
dialect we'd have to maintain until the heat death of the universe.

Full rationale in [`CONTRIBUTING.md`](./CONTRIBUTING.md).
Full reasoning: [`CONTRIBUTING.md`](./CONTRIBUTING.md).

## License

Dual-licensed under [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE) at your
option.
Dual-licensed under [MIT](./LICENSE-MIT) or [Apache-2.0](./LICENSE-APACHE), your
call.
12 changes: 6 additions & 6 deletions docs/CONCEPTS.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
---
audience: humans, contributors, agents, consumers
stability: stable
last-reviewed: 2026-05-28
last-reviewed: 2026-06-03
---

# Concepts

**TL;DR.** phux is a libghostty-backed terminal control plane. The terminal is the model: spawned, observed, controlled, persisted, addressable across hosts. Sessions, windows, panes are the TUI's way to arrange them. The wire is layered; federation is in the addressing, not bolted on.
**TL;DR.** The thing phux manages is the *terminal* — spawned, observed, driven, persisted, addressable across hosts — not the session or the pane. A human's TUI and an agent's API are two consumers of the same terminal, and neither is privileged. Sessions, windows, and panes are just how the TUI arranges terminals for a person. The wire is layered (L1/L2/L3) so a consumer subscribes to only what it needs; identity is federation-ready from the first byte.

---

## Terminals, not panes

A terminal: runs a process, parses bytes into a grid, accepts structured input, reports events (title, cwd, command lifecycle, hyperlinks, bells).
A terminal: runs a process, parses bytes into a grid, accepts structured input, reports events (title, cwd, command lifecycle, hyperlinks, bells). That object is the whole model. A person looking at it and a program driving it are holding the same thing two different ways — that equivalence is the point, not a feature bolted on.

Sessions, windows, panes, splits — the entire tmux vocabulary — live in the TUI layer. Not on the wire. Not load-bearing for agents or control planes.

Expand Down Expand Up @@ -96,7 +96,7 @@ See [ADR-0007](../ADR/0007-mosh-class-transport-and-satellites.md) and [ADR-0016

| Dimension | phux | zmx | cmux | rmux |
|---|---|---|---|---|
| Agent SDK or programmatic API | ✓ (planned L1 SDK) | ✗ | ✓ (macOS native) | ✓ (Rust async) |
| Agent SDK or programmatic API | ✓ (CLI + MCP shipped; Rust SDK planned) | ✗ | ✓ (macOS native) | ✓ (Rust async) |
| Primary use case | Human multiplexer + control plane | Minimal session persistence | Agent UI (git, PR, notifications) | Agent automation |
| Cross-platform | ✓ | ✗ (implied) | ✗ (macOS only) | ✓ |
| Maturity | Pre-release v0.1 | Minimal scope, SSH bugs | Production (20k+ stars) | Public preview (v0.3.1) |
Expand All @@ -120,7 +120,7 @@ phux is both a human multiplexer and a federation-ready control plane. See [ADR-
Consumers in scope:

- **Reference TUI.** Tmux-shaped: sessions, windows, panes, splits, status bar, keybindings. Speaks L1 + L2 + L3. See [`consumers/tui.md`](./consumers/tui.md).
- **Agent SDK** (`phux-client-sdk`, planned). Typed Rust handle to spawn, observe, drive Terminals. L1 only.
- **Agent surface (shipped).** The headless CLI verbs (`ls`, `snapshot`, `send-keys`, `run`, `wait`, `watch`, …) and the [`phux-mcp`](./consumers/mcp.md) adapter, both over the same selector grammar and JSON shapes. L1-shaped: spawn a terminal, drive it, read events and exit codes. A typed Rust SDK crate (`phux-client-sdk`) is still planned.
- **Future:** native GUI, recorder, tmux control-mode adapter ([ADR-0010](../ADR/0010-frontend-agnostic-tmux-cc-reserved.md)).

[ADR-0017 (TUI not protocol-privileged)](../ADR/0017-tui-not-protocol-privileged.md): the reference TUI is one consumer with no protocol-level privileges. If the TUI needs a feature the wire doesn't provide, extend the spec (with an ADR), not add a TUI-shaped hook.
Expand All @@ -146,7 +146,7 @@ The TUI's vocabulary is user-facing. The substrate's vocabulary is what the wire
- **L2 is stable.** Collection lifecycle, membership, naming, kill semantics.
- **L3 exists as opaque storage.** Read / write / delete on metadata blobs. The TUI's conventions live in [`consumers/tui.md`](./consumers/tui.md) and the non-normative conventions appendix in [`spec/L3.md`](./spec/L3.md).
- **The reference TUI works** on L1 + L3 metadata: attach, detach, splits, layouts, status bar, keybindings.
- **The agent SDK ships** as a thin L1-only wrapper, with examples that spawn a build, wait for OSC 133 command-end, read exit code. This is what makes the agent-first thesis real instead of aspirational.
- **The agent surface ships** — not as a Rust SDK crate yet, but as the headless CLI verbs and the `phux-mcp` adapter. A program can spawn a build, wait for it to settle or for output to appear, and read the exit code today. That's what makes the agent-first thesis real instead of aspirational; the typed `phux-client-sdk` crate is the convenience layer still to come.

Federation and Automation are **designed for** in v0.1 (their hooks in the wire are present; their ADRs are written) and **shipped** in v0.2. Building for now, designed for later.

Expand Down
Loading
Loading