1b15df83
docs: add marketing documentation
a73x 2026-09-09 11:16
Commit message
README.md
| Old | New | ||
|---|---|---|---|
| @@ -4,8 +4,11 @@ Terminal multiplexer where detach/reattach is **state sync, not replay**: | |||
| 4 | the terminal engine (ghostty-vt) runs authoritatively in a daemon (`mux d`), | 4 | the terminal engine (ghostty-vt) runs authoritatively in a daemon (`mux d`), |
| 5 | and the wire carries its grid as cells that the client (`mux`) copies into a | 5 | and the wire carries its grid as cells that the client (`mux`) copies into a |
| 6 | grid of its own. Attach costs one snapshot; everything after is row deltas; | 6 | grid of its own. Attach costs one snapshot; everything after is row deltas; |
| 7 | a dropped connection is a non-event. Linux and Apple-silicon macOS, prototype | 7 | a dropped connection is a non-event. Mux publicly supports Linux x86_64 only. |
| 8 | (see [Status](#status)). | 8 | |
| 9 | - [Ethos](docs/Ethos.md) | ||
| 10 | - [Architecture](docs/Architecture.md) | ||
| 11 | - [Features](docs/Features.md) | ||
| 9 | 12 | ||
| 10 | ## Build | 13 | ## Build |
| 11 | 14 | ||
| @@ -69,8 +72,8 @@ are an ordered list of installed monospace `font-family` values and a | |||
| 69 | `font-size` from 1–192 points. The first family supplies grid metrics; each | 72 | `font-size` from 1–192 points. The first family supplies grid metrics; each |
| 70 | later family is tried when an entire cell grapheme is unavailable in the | 73 | later family is tried when an entire cell grapheme is unavailable in the |
| 71 | families before it. | 74 | families before it. |
| 72 | Fractional sizes are preserved until rasterization: points × 96/72 on Linux | 75 | Fractional sizes are preserved until rasterization: points × 96/72 on Linux, |
| 73 | (points × 1 on macOS), then display scale, rounded to a pixel. Blank lines and | 76 | then display scale, rounded to a pixel. Blank lines and |
| 74 | full-line `#` comments are accepted; a family may be double quoted. Duplicate | 77 | full-line `#` comments are accepted; a family may be double quoted. Duplicate |
| 75 | singleton keys, unknown mux config keys, malformed values and unavailable | 78 | singleton keys, unknown mux config keys, malformed values and unavailable |
| 76 | families produce diagnostics. Per-style families, escapes and inline comments | 79 | families produce diagnostics. Per-style families, escapes and inline comments |
| @@ -93,9 +96,9 @@ That choice remains fixed until release, even if Shift changes or the pointer | |||
| 93 | crosses another pane. Ctrl+Shift+C copies the current local selection. | 96 | crosses another pane. Ctrl+Shift+C copies the current local selection. |
| 94 | Application OSC 52 writes update the desktop clipboard (`c`) or primary | 97 | Application OSC 52 writes update the desktop clipboard (`c`) or primary |
| 95 | selection (`p`/`s`); clipboard queries remain refused. Ctrl+Shift+V pastes | 98 | selection (`p`/`s`); clipboard queries remain refused. Ctrl+Shift+V pastes |
| 96 | into the focused pane or an editable picker field; Cmd+V does the same on | 99 | into the focused pane or an editable picker field. A terminal that enables |
| 97 | macOS. A terminal that enables bracketed paste receives one bracket pair | 100 | bracketed paste receives one bracket pair around the complete clipboard text, |
| 98 | around the complete clipboard text, including when the payload is chunked. | 101 | including when the payload is chunked. |
| 99 | 102 | ||
| 100 | Choose a Ghostty theme file by name in `$XDG_CONFIG_HOME/mux/themes/` (or | 103 | Choose a Ghostty theme file by name in `$XDG_CONFIG_HOME/mux/themes/` (or |
| 101 | `~/.config/mux/themes/`), or by absolute path: | 104 | `~/.config/mux/themes/`), or by absolute path: |
| @@ -218,39 +221,31 @@ Binaries land in `zig-out/bin/`. For remote machines, build a static binary | |||
| 218 | that runs on any x86_64 Linux: | 221 | that runs on any x86_64 Linux: |
| 219 | 222 | ||
| 220 | ```sh | 223 | ```sh |
| 221 | ~/Downloads/zig-x86_64-linux-0.15.2/zig build -Dtarget=x86_64-linux-musl | 224 | mise exec -- zig build -Dtarget=x86_64-linux-musl |
| 222 | ``` | 225 | ``` |
| 223 | 226 | ||
| 224 | `make release` builds both `mux` and `muxg` in `ReleaseSafe`, strips them, | 227 | `make release` builds both `mux` and `muxg` in `ReleaseSafe`, strips them, |
| 225 | checks both against `build.zig`'s version, and packages them in `dist/`. | 228 | checks both against `build.zig`'s version, and packages them in `dist/`. On |
| 226 | On Linux, `mux` remains static musl; `muxg` uses the build host's native target | 229 | Linux, `mux` remains static musl; `muxg` uses the build host's native target |
| 227 | and needs compatible CPU, libc, SDL3, FreeType, fontconfig, and HarfBuzz libraries | 230 | and needs compatible CPU, libc, SDL3, FreeType, fontconfig, and HarfBuzz |
| 228 | on the destination, plus an OpenGL-capable display. These libraries are not | 231 | libraries on the destination, plus an OpenGL-capable display. These libraries |
| 229 | bundled. The tarball is what to hand another machine, because it | 232 | are not bundled. The tarball carries the executable bit for installation on |
| 230 | carries the exec bit that a bare downloaded binary does not: | 233 | another machine: |
| 231 | 234 | ||
| 232 | ```sh | 235 | ```sh |
| 233 | make release | 236 | make release |
| 234 | ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < dist/mux-vVERSION-x86_64-linux-musl.tar.gz | 237 | ssh HOST 'mkdir -p ~/.local/bin && tar xzf - -C ~/.local/bin' < dist/mux-vVERSION-x86_64-linux-musl.tar.gz |
| 235 | ``` | 238 | ``` |
| 236 | 239 | ||
| 237 | A release is one tarball per OS, retaining the daemon target's name: | 240 | Linux x86_64 releases are published as |
| 238 | `mux-vVERSION-x86_64-linux-musl.tar.gz` and | 241 | `mux-vVERSION-x86_64-linux-musl.tar.gz` under |
| 239 | `mux-vVERSION-aarch64-macos.tar.gz`. Each is cut on that OS — `make release` | 242 | `https://git.a73x.sh/mux/releases/vVERSION/`. Install a published release: |
| 240 | builds for the host it runs on — and both are published under | ||
| 241 | `https://git.a73x.sh/mux/releases/vVERSION/`, so installing a published | ||
| 242 | build is one line: | ||
| 243 | 243 | ||
| 244 | ```sh | 244 | ```sh |
| 245 | mkdir -p ~/.local/bin | 245 | mkdir -p ~/.local/bin |
| 246 | curl -fsSL https://git.a73x.sh/mux/releases/vVERSION/mux-vVERSION-x86_64-linux-musl.tar.gz | tar xzf - -C ~/.local/bin | 246 | curl -fsSL https://git.a73x.sh/mux/releases/vVERSION/mux-vVERSION-x86_64-linux-musl.tar.gz | tar xzf - -C ~/.local/bin |
| 247 | curl -fsSL https://git.a73x.sh/mux/releases/vVERSION/mux-vVERSION-aarch64-macos.tar.gz | tar xzf - -C ~/.local/bin | ||
| 248 | ``` | 247 | ``` |
| 249 | 248 | ||
| 250 | On an Apple-silicon Mac the same build and install commands work, with the | ||
| 251 | toolchain and native dependencies installed first — see | ||
| 252 | [macOS](#macos). | ||
| 253 | |||
| 254 | ## Quick start, local | 249 | ## Quick start, local |
| 255 | 250 | ||
| 256 | ```sh | 251 | ```sh |
| @@ -907,26 +902,9 @@ dropped connection can never truncate a binary, then runs that box's own | |||
| 907 | running just gets the install (exit 0, and it says so); a box with no mux | 902 | running just gets the install (exit 0, and it says so); a box with no mux |
| 908 | at all is refused — a push replaces an install, it does not invent one. | 903 | at all is refused — a push replaces an install, it does not invent one. |
| 909 | 904 | ||
| 910 | Coming from v0.0.1-15 or older is the one upgrade `mux d upgrade` cannot | 905 | Older daemons may not support in-place upgrade or newer session-discovery |
| 911 | do: that daemon probes a candidate for `muxd <version>` and this binary | 906 | requests. Stop and start those daemons with the current binary instead. |
| 912 | prints `mux <version>`, so it refuses. `mux d stop` then `mux d start -d`, | 907 | |
| 913 | once per box. A v0.0.1-16 daemon is the same story for the same reason: it | ||
| 914 | execs its candidate as `mux d run`, and the verb is `start` now. `make install` deletes `muxd`, `muxa` and `muxweb` from the | ||
| 915 | install directory on its way in — they are modes of `mux` now, and a | ||
| 916 | `muxd` left on PATH is a second daemon waiting for someone to start it. | ||
| 917 | |||
| 918 | One more mixed-version caveat runs the other way, daemon newer than client. | ||
| 919 | A daemon now answers `sessions_req` with a `# holds NAME N` line per session | ||
| 920 | beside the names, which an older client skips as it skips the version line — | ||
| 921 | but a client built before those lines existed sized its receive buffer for | ||
| 922 | what it expected and reads anything longer as a transport error. Released | ||
| 923 | v0.0.1-16 sized it for the names alone, 1056 bytes; a development build off | ||
| 924 | main after the version line and before the holds lines sized it at 1101. | ||
| 925 | Either way a new daemon holding roughly fourteen or more sessions with | ||
| 926 | 32-character names reads `unreachable` on that wall until the wall is | ||
| 927 | upgraded. Upgrading the client end is the fix; there is nothing to do on the | ||
| 928 | daemon. (Say it by what the client was BUILT from, not by a version number: | ||
| 929 | this branch stamps 0.0.1-17 itself, and no v0.0.1-17 tag exists yet.) | ||
| 930 | 908 | ||
| 931 | Copy and paste work through the session: a mux drag and an application's own | 909 | Copy and paste work through the session: a mux drag and an application's own |
| 932 | OSC 52 write both reach your terminal's clipboard (including from a remote box over QUIC, | 910 | OSC 52 write both reach your terminal's clipboard (including from a remote box over QUIC, |
| @@ -936,103 +914,6 @@ bell rings. The clipboard READ direction (`OSC 52` query) is refused | |||
| 936 | deliberately — answering it would let anything in any session read | 914 | deliberately — answering it would let anything in any session read |
| 937 | whatever you last copied. | 915 | whatever you last copied. |
| 938 | 916 | ||
| 939 | ## macOS | ||
| 940 | |||
| 941 | mux builds and runs on an Apple-silicon Mac — `aarch64` only. There is no | ||
| 942 | Intel build, no universal binary, and no way to cross-compile the Mac | ||
| 943 | binary from Linux, so a Mac binary is made on a Mac. | ||
| 944 | |||
| 945 | You need Xcode's Command Line Tools for the SDK and `cmake` for the QUIC | ||
| 946 | dependencies, and the pinned toolchain in the same place a Linux checkout | ||
| 947 | puts it: unpack the aarch64 Zig 0.15.2 tarball so that the binary is at | ||
| 948 | `deps/zig/zig`. The Makefile spells that path and nothing else, so a | ||
| 949 | system `zig` is neither used nor enough. | ||
| 950 | |||
| 951 | ```sh | ||
| 952 | xcode-select --install | ||
| 953 | brew install cmake | ||
| 954 | make build # first build fetches + compiles the QUIC deps, as on Linux | ||
| 955 | make install # mux + muxg in ~/.local/bin; also needs the native libraries listed above | ||
| 956 | ``` | ||
| 957 | |||
| 958 | The Mac tarball of a release is cut by `make release-mac`, which runs | ||
| 959 | `make release` on the Mac named by `MAC_BUILDER` — the same box, and the | ||
| 960 | same variable, the macOS gates build on — and publishes it from there. | ||
| 961 | |||
| 962 | `make build` runs `deps/mac-sdk.sh` first, which may build a shadow SDK | ||
| 963 | under `deps/mac-sdk/`. Xcode 26.4 and later ship a `libSystem` stub that | ||
| 964 | lists only the `arm64e` slice, and the pinned Zig 0.15.2 matches the bare | ||
| 965 | `arm64` one, so against that SDK every system symbol comes out undefined | ||
| 966 | and the build cannot start. The shadow is the real SDK by symlink | ||
| 967 | everywhere except that one stub, where Zig's own copy stands in, plus an | ||
| 968 | `xcrun` on PATH that points at it. It costs a few seconds once, retires | ||
| 969 | itself when a future Xcode lists `arm64` again, and needs nothing of you. | ||
| 970 | |||
| 971 | One runtime difference worth knowing. The daemon's default socket lives in | ||
| 972 | `$XDG_RUNTIME_DIR` when that is set, and macOS does not set it, so mux | ||
| 973 | falls back to `/tmp/mux-<uid>` — created 0700 and re-checked for owner and | ||
| 974 | mode on every use, the way tmux uses `/tmp/tmux-UID`. The macOS `$TMPDIR` | ||
| 975 | is not used for this: the longest socket name mux creates would not fit in | ||
| 976 | the 103 bytes a unix socket path gets. | ||
| 977 | |||
| 978 | `make test` passes on a Mac. `make e2e` has two groups that do not, both | ||
| 979 | deliberately and both for the fallback above. A leg in the select group | ||
| 980 | requires `mux d stats` and a bare `mux` to REFUSE when | ||
| 981 | `$XDG_RUNTIME_DIR` is unset, and a leg in the askpass group requires a | ||
| 982 | wall in that state to start no prompt socket at all. On macOS there is | ||
| 983 | always a runtime directory, so mux answers instead of refusing and the | ||
| 984 | wall arms its socket, which is the fallback working. The suite is linear, | ||
| 985 | so a `make e2e` on a Mac stops at the first of the two. The other gates — | ||
| 986 | `make agent`, `make soak`, `make throughput`, `make xversion` — have not | ||
| 987 | been run on a Mac at all. | ||
| 988 | |||
| 989 | The gate for macOS is `make mac`, the macOS half of what `make vm` is for | ||
| 990 | Linux. Both macOS gates take the machines they use BY NAME, as ssh targets | ||
| 991 | in the environment, and neither has a default for one: | ||
| 992 | |||
| 993 | | Name | What it is | | ||
| 994 | |---|---| | ||
| 995 | | `MAC_BOX` | the macOS box under test, with no developer tooling on it. Scrubbed to a mux-less state at the start of a run and again at the end, so make it a VM. | | ||
| 996 | | `MAC_BUILDER` | a Mac holding this repo with zig and the shadow SDK. It builds, and nothing else: no mux is installed there and nothing is reset there. Defaults to `MAC_BOX`. | | ||
| 997 | | `LINUX_BOX` | `make xos` only: an x86_64 Linux VM that both this box and `MAC_BOX` can reach. Scrubbed the same way. | | ||
| 998 | |||
| 999 | `make mac` pushes this commit to the builder, builds and runs the unit suite | ||
| 1000 | there, streams the binary onto `MAC_BOX`, and then runs the user journeys — | ||
| 1001 | so what is graded is the product on a bare Mac rather than on the machine | ||
| 1002 | that compiled it. A run looks like this: | ||
| 1003 | |||
| 1004 | ```sh | ||
| 1005 | eval "$(test/provision-mac.sh)" # prints: export MAC_BOX=admin@192.168.0.x | ||
| 1006 | MAC_BUILDER=squirtle make mac | ||
| 1007 | test/provision-mac.sh --down # when you are done with the guest | ||
| 1008 | ``` | ||
| 1009 | |||
| 1010 | `test/provision-mac.sh` is where the virtual machine's life is spelled: it | ||
| 1011 | clones a pristine base image on a Mac that has Cirrus Labs `tart`, boots the | ||
| 1012 | clone bridged onto the LAN so this box can ssh to it directly, and installs | ||
| 1013 | this box's public key on it. The base image itself is made once, by hand, on | ||
| 1014 | that Mac: | ||
| 1015 | |||
| 1016 | ```sh | ||
| 1017 | brew trust cirruslabs/cli && brew install cirruslabs/cli/tart | ||
| 1018 | tart clone ghcr.io/cirruslabs/macos-tahoe-base:latest mux-mac-base | ||
| 1019 | tart run --no-graphics mux-mac-base & # user admin, password admin | ||
| 1020 | tart exec mux-mac-base sh -c 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo "PUBKEY" >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys' | ||
| 1021 | tart stop mux-mac-base | ||
| 1022 | ``` | ||
| 1023 | |||
| 1024 | `PUBKEY` is that Mac's own `~/.ssh/id_ed25519.pub`. Nothing stops you | ||
| 1025 | pointing `MAC_BOX` at some other spare Mac instead, as long as you mean the | ||
| 1026 | word scrubbed. | ||
| 1027 | |||
| 1028 | `make mac` and `make vm` each run one operating system on both ends of every | ||
| 1029 | connection. `make xos` is the gate that does not: it attaches a macOS client | ||
| 1030 | to a Linux daemon and a Linux client to a macOS daemon, over the ssh handoff | ||
| 1031 | and over QUIC, and checks that a cross-architecture `mux d upgrade` is | ||
| 1032 | refused in both directions before any of the image is streamed. It uses all | ||
| 1033 | three names above, and pairs the two boxes' ssh keys itself, because each | ||
| 1034 | one dials the other from the inside. | ||
| 1035 | |||
| 1036 | ## How it works | 917 | ## How it works |
| 1037 | 918 | ||
| 1038 | One real terminal engine runs, and it is the daemon's: shell output mutates | 919 | One real terminal engine runs, and it is the daemon's: shell output mutates |
| @@ -1053,23 +934,5 @@ issue list` (`docs/roadmap.md` is the older ranked queue). | |||
| 1053 | 934 | ||
| 1054 | ## Status | 935 | ## Status |
| 1055 | 936 | ||
| 1056 | Prototype, eighteen milestones in, current cut tagged `v0.0.1-15`. Linux | 937 | Prototype. Mux publicly supports Linux x86_64 only. See the feature and |
| 1057 | and Apple-silicon macOS; every gate below was measured on Linux. All | 938 | architecture documentation above for the current product scope. |
| 1058 | founding kill criteria cleared, measured on real networks rather than | ||
| 1059 | loopback: reattach into a live full-screen `nvim` in ~5ms from one | ||
| 1060 | snapshot; steady-state delta traffic ~65 bytes per keystroke, and a cold | ||
| 1061 | attach to a fresh session 86 bytes of cells where it was 5644 of VT; the | ||
| 1062 | protocol adds ~4ms to a raw ssh round trip (constant, not a multiple of | ||
| 1063 | RTT); ten consecutive real network tears resumed hands-off, all | ||
| 1064 | delta-served, on both ssh and QUIC transports; QUIC cold attach beats | ||
| 1065 | ssh-via (6.9ms vs 9.8ms clean, 234.1ms vs 235.7ms with 75ms emulated | ||
| 1066 | delay on a real path); and a predicted keystroke paints in **0.1ms** on a | ||
| 1067 | 150ms path where the same keystroke's authoritative echo takes **154ms** — | ||
| 1068 | typing stops waiting for the round trip, and the two numbers come from one | ||
| 1069 | keystroke on one connection so the gap is not an artefact of comparing | ||
| 1070 | runs. Auto-start is here for local unix-socket attaches — `mux` and `mux | ||
| 1071 | --sock PATH` start a daemon when nothing is serving that path — and the wall | ||
| 1072 | starts the one on the default socket for the same reason. A remote daemon | ||
| 1073 | is started only by the dial a user ASKED for — `mux HOST`, a picker Enter — | ||
| 1074 | and never by a dial nobody asked for. Not yet here: reconnect backoff | ||
| 1075 | tuning, prediction for backspace and for multi-byte input. | ||
RETRO.md
| Old | New | ||
|---|---|---|---|
| @@ -1584,3 +1584,5 @@ and stopped localhost demo server PID 257482; port 18778 is closed. Its composit | |||
| 1584 | and app fixtures had already stopped. Retained the page, 25-second recording and | 1584 | and app fixtures had already stopped. Retained the page, 25-second recording and |
| 1585 | check evidence, preserved unrelated services, and left the known NVIDIA frame | 1585 | check evidence, preserved unrelated services, and left the known NVIDIA frame |
| 1586 | budget debt open. No next sprint was started. | 1586 | budget debt open. No next sprint was started. |
| 1587 | |||
| 1588 | Documentation update — 2026-09-06: Identifying the public support boundary required removing stale platform and release copy; concise source-backed architecture and feature pages made the current scope clear. | ||
docs/Architecture.md
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,39 @@ | |||
| 1 | # Architecture | ||
| 2 | |||
| 3 | Mux separates terminal execution from terminal presentation. The daemon owns sessions, PTYs, and the Ghostty VT engine. Each client holds a replica of the terminal grid: it receives a cell snapshot when needed, then row deltas as the authoritative grid changes. | ||
| 4 | |||
| 5 | ```mermaid | ||
| 6 | flowchart LR | ||
| 7 | B[Browser client replica] | ||
| 8 | W[`mux web` hub] | ||
| 9 | S[Unix socket] | ||
| 10 | H[SSH handoff] | ||
| 11 | Q[QUIC] | ||
| 12 | V[`--via` command] | ||
| 13 | |||
| 14 | subgraph Daemon | ||
| 15 | D[mux daemon] | ||
| 16 | VT[Daemon-owned Ghostty VT state] | ||
| 17 | D --> VT | ||
| 18 | end | ||
| 19 | |||
| 20 | B -->|local HTTP/WebSocket: input and commands| W | ||
| 21 | W -->|input and commands| S --> D | ||
| 22 | W -->|input and commands| H --> D | ||
| 23 | W -->|input and commands| Q --> D | ||
| 24 | W -->|input and commands| V --> D | ||
| 25 | VT -->|snapshots and row deltas| S --> W | ||
| 26 | VT -->|snapshots and row deltas| H --> W | ||
| 27 | VT -->|snapshots and row deltas| Q --> W | ||
| 28 | VT -->|snapshots and row deltas| V --> W | ||
| 29 | W -->|local HTTP/WebSocket: snapshots and row deltas| B | ||
| 30 | ``` | ||
| 31 | |||
| 32 | The browser reaches a local `mux web` hub over HTTP and WebSocket; it does not | ||
| 33 | speak directly to a Unix socket. The hub uses the same client services to reach | ||
| 34 | daemon sessions through a local Unix socket, SSH handoff, QUIC, or `--via`. | ||
| 35 | Those transports carry bytes and do not interpret terminal frames. | ||
| 36 | |||
| 37 | The daemon owns VT parsing and terminal state. It receives client input and | ||
| 38 | commands, then sends snapshots and row deltas for each client replica to apply | ||
| 39 | before rendering. | ||
docs/Ethos.md
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,19 @@ | |||
| 1 | # Ethos | ||
| 2 | |||
| 3 | Mux keeps terminal sessions alive and makes their current state available wherever you attach. It is a state-sync terminal multiplexer: the daemon owns the terminal state, while clients render replicas of it. | ||
| 4 | |||
| 5 | ## Keep the terminal authoritative | ||
| 6 | |||
| 7 | The daemon runs the Ghostty VT engine. Clients do not replay escape sequences; they receive terminal cells, starting with a snapshot and continuing with row deltas. Reconnecting restores the current screen rather than replaying a terminal transcript. | ||
| 8 | |||
| 9 | ## Keep clients flexible | ||
| 10 | |||
| 11 | Use the terminal/TUI client, a browser wall, the native `muxg` client, or the `mux a` automation interface against the same daemon sessions. Clients can connect locally or remotely without changing the terminal-state model. | ||
| 12 | |||
| 13 | ## Keep control explicit | ||
| 14 | |||
| 15 | Sessions outlive client connections. Detaching is separate from ending a session or stopping a daemon. A read-only check does not start a daemon. SSH-agent forwarding is opt-in through `mux -A`; `mux a` does not offer an agent. | ||
| 16 | |||
| 17 | ## Platform support | ||
| 18 | |||
| 19 | Mux publicly supports Linux x86_64 only. | ||
docs/Features.md
| Old | New | ||
|---|---|---|---|
| @@ -0,0 +1,31 @@ | |||
| 1 | # Features | ||
| 2 | |||
| 3 | ## State-synced sessions | ||
| 4 | |||
| 5 | Detach and reattach without replaying terminal output. The daemon keeps the authoritative Ghostty VT state; clients receive a snapshot and then row deltas. | ||
| 6 | |||
| 7 | ## Multiple clients | ||
| 8 | |||
| 9 | Use the terminal/TUI client (`mux`), browser wall (`mux web`), native client (`muxg`), or `mux a` automation interface with daemon sessions. Terminal and native clients can attach locally, through SSH handoff, over QUIC, or through a `--via` command. | ||
| 10 | |||
| 11 | ## Session control | ||
| 12 | |||
| 13 | Create, browse, and arrange sessions in terminal and native client layouts. Detaching leaves sessions alive. Ending a session and stopping a daemon are explicit actions. | ||
| 14 | |||
| 15 | ## Agent interface | ||
| 16 | |||
| 17 | `mux a` inspects and drives an existing session through JSON replies. It attaches at 0×0, so it does not claim session geometry, and it never offers SSH-agent forwarding. | ||
| 18 | |||
| 19 | ## SSH-agent forwarding | ||
| 20 | |||
| 21 | `mux -A` is opt-in terminal-client behavior. It forwards that terminal client's SSH agent into its attached session; `mux a` does not offer an agent. | ||
| 22 | |||
| 23 | ## muxg-only TCP loopback forwarding | ||
| 24 | |||
| 25 | TCP loopback forwarding is available only in `muxg`. `muxg --forward LOCAL_PORT:REMOTE_PORT TARGET` binds `127.0.0.1:LOCAL_PORT` and forwards TCP to `127.0.0.1:REMOTE_PORT` on the target host. It supports repeated rules, but not hostname destinations, wildcard binds, reverse forwarding, or UDP. | ||
| 26 | |||
| 27 | The native picker does not configure forwarding yet. Picker-based forwarding is future work. | ||
| 28 | |||
| 29 | ## Platform support | ||
| 30 | |||
| 31 | Mux publicly supports Linux x86_64 only. | ||