Roost has two active development surfaces:
- The Rust workspace at
crates/(roost-ipc,roost-vt,roost-osc,roost-cli,roost-linux,roost-iced). - The Swift package at
mac/(the macOS UI,Roost.app).
Both link the same vendored libghostty-vt static archive built from third_party/ghostty/.
| Tool | Use |
|---|---|
mise |
Provisions Rust 1.97.1 + Zig 0.15.x at the pinned versions |
| Xcode Command Line Tools | Builds the Mac UI (SwiftPM) |
| GTK4 + libadwaita dev packages | Builds the Linux UI (Linux + macOS dev) |
uv |
Builds the documentation site |
See Installation for the per-platform package commands.
git clone https://github.com/charliek/roost.git
cd roost
mise install # Rust + Zig
./third_party/ghostty/build.sh # libghostty-vt — only needs Zig
~/.cargo/bin/cargo build --workspace # all Rust crates
./mac/scripts/bundle.sh debug # macOS .app bundlethird_party/ghostty/build.sh is the only step that needs Zig. After it finishes, normal Rust + Swift workflows work without invoking Zig again.
| Goal | Command |
|---|---|
| Run the Linux UI (iced, what the package ships) | ~/.cargo/bin/cargo run -p roost-iced |
| Run the Linux UI (GTK, in-repo dev/parity) | ~/.cargo/bin/cargo run -p roost-linux |
| Run the Mac UI | ./mac/scripts/bundle.sh debug && open mac/build/Roost.app |
| Smoke-test the CLI | ~/.cargo/bin/cargo run -p roost-cli -- identify |
| Rust unit tests | ~/.cargo/bin/cargo test --workspace --exclude roost-linux |
| Linux UI tests (GTK) | ~/.cargo/bin/cargo test -p roost-linux (needs GTK) |
| Linux UI tests (iced) | ~/.cargo/bin/cargo test -p roost-iced |
| Mac unit tests | cd mac && swift test |
| Rust formatting | ~/.cargo/bin/cargo fmt --all |
| Rust lint | ~/.cargo/bin/cargo clippy --workspace --all-targets |
| Build the docs site | make docs (or make docs-serve for live-reload at http://127.0.0.1:7070) |
The IPC sockets live at:
| OS | Swift/AppKit | GTK | Iced (dev build) |
|---|---|---|---|
| macOS | ~/Library/Caches/Roost/roost.sock |
~/Library/Caches/Roost-gtk/roost.sock |
~/Library/Caches/Roost-iced/roost.sock |
| Linux | n/a | $XDG_RUNTIME_DIR/roost/roost.sock |
$XDG_RUNTIME_DIR/roost-iced/roost.sock |
Without XDG_RUNTIME_DIR, Linux falls back to /tmp/roost-<uid> for GTK and
/tmp/roost-iced-<uid> for Iced. State, lock, and log paths use the same
profile separation. Select a live UI with roostctl --target mac|gtk|iced.
Each UI writes a log file and tees to stdout. Iced uses
~/Library/Logs/Roost-iced/roost.log on macOS and
$XDG_STATE_HOME/roost-iced/roost.log on Linux; its socket, lock, state, and
log paths are distinct from Swift and GTK. roostctl --help and
docs/reference/ipc.md document the wire surface.
Rust tests live next to the code they exercise. Major coverage:
| Crate | What's covered |
|---|---|
roost-ipc |
Frame reader/writer, JSON wire vectors, target selection (probe alive + env precedence) |
roost-osc |
OSC 9 / 777 streaming parser, ST terminator, hook suppression |
roost-vt |
FFI smoke tests against the vendored libghostty-vt archive (gated on --features ffi) |
roost-engine |
Workspace, PTY supervision, persistence, events, IPC dispatch, instance lock |
roost-ui-model |
Config, theme, keybind, palette, provider, and agent projection models |
roost-linux |
GTK presentation, native ports, input, and terminal rendering adapter — in-repo dev/parity UI |
roost-iced |
iced presentation, native ports, input, and terminal rendering adapter — what the Linux package ships |
roost-cli |
Escape decoder, shell quoter, target arg mapping |
Mac tests are under mac/Tests/RoostTests/; they cover the workspace state machine, PTY supervisor lifecycle, IPC server framing, single-instance flock, renderer, OSC scanner, key encoder, drag/drop math, and tab pill state machine. They run in headless swift test (no NSWindow required for any covered surface).
Markdown sources live in docs/. Zensical builds them through uv:
make docs # static site under site-build/ (`zensical build --strict`)
make docs-serve # live-reload server at http://127.0.0.1:7070uv sync --locked --group docs runs automatically; no global Python install needed beyond the uv binary. zensical serve --strict is unsupported — verify with make docs.
The voice for new docs is set in zensical.toml: professional + direct (no marketing), tables for option lists, code blocks with language hints, admonitions only for important notes/warnings, copy-pasteable examples, one topic per page.
libghostty-vt's API is documented as unstable. Bumps land in their own commit:
- Edit
third_party/ghostty/build.sh— updateGHOSTTY_SHA. ./third_party/ghostty/build.sh --forceto rebuild from the new SHA.- Fix any FFI breakage in
crates/roost-vt. The C symbols are listed insrc/lib_vt.zigof the Ghostty source. - Re-run
cargo test --workspaceandswift testfrommac/. - Commit with the SHA + date in the message.
If the bump also moves past Zig 0.15.x, drop the maybe_arm64_sdk_shim helper in third_party/ghostty/build.sh — it exists only because Zig 0.15.x links host artifacts as arm64-macos, which Apple's macOS 26+ SDK no longer exposes.
The full set is in CLAUDE.md at the repo root. Highlights:
- Concrete types until duplication forces an interface — no premature
Manager/Coordinatorabstractions. - Errors are returned, not logged-and-swallowed. Log at the boundary that handles them.
- Default to no comments. Add one when the why is non-obvious (a hidden constraint, a workaround, a tricky invariant).
- UI calls happen on the main thread; background work marshals via
glib::idle_add(Linux) orDispatchQueue.main/@MainActor(macOS). - The JSON IPC schema is the durable boundary — change
crates/roost-ipc/src/messages.rs, update vectors undertests/ipc-vectors/, and bump the Swift mirror inmac/Sources/Roost/IPCMessages.swiftin the same commit.