Repository guidance for agent sessions.
Sinclair is a GPU-accelerated terminal emulator for macOS and Linux, written in
Rust as a Cargo workspace. The GUI is built on gpui
(pulled as a git dependency from the zed repo). The GUI is the app crate,
whose bin target is sinclairdev: a dev build (cargo run -p app, debug or
--release) is named sinclairdev so it never collides with an installed
sinclair — it gets its own window title, app id, and single-instance socket and
runs side by side. The release scripts install the same binary as the shipped
sinclair command. The app derives this name from its own executable at runtime
(see crates/app/src/appid.rs).
cargo run -p app --release # build and launch the terminal
cargo build --release # build the workspace
cargo test # run all tests (workspace)
cargo test -p vt # test one crate
cargo test -p vt screen # run tests matching "screen" in one crate
cargo clippy --all-targets # lint
scripts/bundle.sh # cargo build --release + assemble dist/Sinclair.app
scripts/dmg.sh # package dist/Sinclair.dmg (needs bundle first)
scripts/linux.sh [x86_64|aarch64] # build + package .tar.gz/.deb/.AppImage (Linux)Each crate keeps its tests in a sibling tests/ directory (e.g.
crates/vt/tests/), mirroring the src/ layout. These are not ordinary
integration tests: every crate sets autotests = false, and each source file
pulls its test file back in as a private module so unit tests keep access to
private items and the app binary can be tested:
// at the bottom of src/foo.rs
#[cfg(test)]
#[path = "../tests/foo.rs"]
mod tests;Add a new test file the same way (and create the #[path] stub in the source
file). Genuine integration tests that exercise only the public API are declared
explicitly as [[test]] targets (see crates/vt and crates/terminal). The
vt and config crates carry the bulk of the coverage and are pure logic —
prefer adding there.
gpui and gpui_platform come from a pinned zed git rev. Because cargo
[patch.crates-io] entries do not propagate through git dependencies, the root
Cargo.toml must mirror zed's own patches (async-process, async-task).
Requires Rust stable >= 1.96. If you bump the zed rev, re-check zed's root
Cargo.toml patch section and update ours to match. See docs/gpui.md.
The workspace is layered bottom-up; each crate depends only on those below it.
vt— the terminal emulation core. Pure, no I/O: avte-driven parser feeds aGrid/Screenwith cursor, modes, scrollback, selection, hyperlinks, search, and SGR/charset state.term/holds CSI/OSC/DCS dispatch and reports. Everything here is testable in isolation.pty— Unix pty allocation and child-process spawn (rustix). Unix-only.terminal— runtime glue:Session::spawnruns a child on a pty, feeds its bytes into avt::Terminalon a reader thread, and emitsEvents (wakeup, title, bell, exit) over an async-capable flume receiver.cast— asciinema v2.castrecording: aRecorderwrites a header line plus timestamped output events as bytes arrive (output only; UTF-8 split across reads is carried over). Used byterminalfor session capture.container— container-backed terminals, pure argv construction with no I/O beyond a$PATHprobe: Docker/Podman detection, the OS profiles behind "OS Tabs", and the project sandbox — one long-lived container a human and a whole agent team share. The sandbox identity-mounts the project (a path means the same thing inside and out, so git worktrees stay valid from both sides), generates the imageRecipethat installs the agent CLIs, and discovers containers a project already has by label so an editor's devcontainer is entered rather than duplicated.appdrives it;relayuses the same builders so adocker execassembled on either side cannot drift. Seedocs/sandbox.md.input— keyboard/mouse encoding to terminal byte sequences (CSI, kitty keyboard protocol, mouse reporting, bracketed paste).config— layered settings: compiled-in defaults overridden by the user'ssettings.json(JSON with comments, parsed byjson.rs), with live file watching.kind.rsis the typed key schema; bad values become friendly diagnostics plus the default and never abort the load.jsonedit.rsdoes comment-preserving single-key writes. Default path$XDG_CONFIG_HOME/sinclair/settings.jsonor~/.config/sinclair/settings.json; the legacykey = valueconfigfile is still parsed for one-time migration (app'sconfwrite::migrate).theme— 22 built-in color schemes (builtin/) plus per-color overrides.plugin— parsesplugin.tomlmanifests contributing:[[command]]actions (+ default keybindings), a[panel]block-tree drawer,[[trigger]]event hooks, and[[tool]]MCP tools exposed to agents (mcpbridgemerges them intosinclair mcp's tool list). Also owns install state (installed.toml: version, source, enabled flag, granted capabilities) and bundled-plugin discovery. Pure parsing/validation; the host (app) drives the runtime, renders panels, and dispatches triggers.pluginrt— the one plugin runtime: awasmtimecomponent-model host. Owns the WIT world (wit/plugin.wit), the capability-gated linker, and one resident instance per plugin. Capabilities are enforced at link time — the host adds a host interface only if the plugin declared it and the user granted it, so a component importing something ungranted fails to instantiate. Fuel bounds a runaway guest. Defines theAppHosttraitappimplements, keepingwasmtimeout ofapp's dependency surface. Seedocs/plugins.md.macros— record/replay of typed command sequences, stored as plain text.mcp— a minimal Model Context Protocol server (JSON-RPC over stdio). Transport/framing only; the caller supplies the tool list and handler. Knows nothing about terminals.assist— local, offline terminal assistance (command ranking, suggestions, paste-risk safety checks). Optionalcandlefeature (off by default) for the candle-core backend; code is gated with#[cfg(feature = "candle")]and has a non-candle fallback.updater— self-update mechanics, gpui-free (Zed'sauto_updatedesign): GitHub release check, install detection, and in-place installs — macOS mounts the.dmgand rsyncs the new bundle's contents onto the installed.app(never swap the bundle directory: LaunchServices' stale registration relaunches the bare Mach-O inside Terminal.app), Linux renames the new AppImage over the running one, staged on the same filesystem. Returns aRelaunchdecision thatapp'supdateui.rshands to gpui's restart —Relaunch::Currentrestarts with no explicit path so gpui reopens the running bundle viaNSBundle. A release is only offered once it has published the asset this machine installs (Release::ready_for, matched by OS and architecture); GitHub publishes a release before CI uploads to it, so the gate is what stops a prompt whose Update button could only fail. Installs reportStages as they run soupdateuican show real progress.relay— the agent mesh, shipped as a standalone sidecar binary (relay), not part of the terminal. Lets independent coding-agent sessions coordinate over a shared SQLite bus: agentsregister, message each other / channels, andwait(a single blocking SSE call) to park for free between tasks. Built on tokio + axum + sqlx; MCP transport is Streamable HTTP so many sessions share one server. Submodules:protocol/(wire types),db/(SQLite bus),state/(in-memory app + wake signal),bus.rs(core park/deliver shared by both planes),tools/(MCP tool impls),mcp/+transport/(MCP dispatch over HTTP),control/(plain-HTTP control plane for the CLI and non-MCP bridges),spawn/(background workers), andcli/(therelaysubcommands). The app never runs the mesh in-process —app/src/relay/starts/stops the bundled binary as a detached daemon and builds therelay launchcommand lines the app runs: single agents into splits, and a whole team into its own window (one member per pane) unlessrelay-team-windowis off. Reads no env vars; every parameter comes from settings, passed explicitly. Seedocs/relay.md.libsinclair— the terminal as an embeddable library: curated re-exports of the headless stack (Session+ vt + input + theme) plus the gpui rendering layer moved out ofapp— thecolors/metrics/mouse/boxdrawpolicy modules,element::TerminalElement(grid painting, damage-aware frame reuse), the pointer glue, the eventbridge, andtermview::TermView, a drop-in terminal pane for other gpui apps. gpui sits behind the defaultuifeature, sodefault-features = falseis a gpui-free headless core. Consumed as a git dependency; seedocs/libsinclair.mdandcrates/libsinclair/examples/embed.rs.app— the gpui application that wires everything together. Owns windows, splits, settings UI, the About panel, font handling, and the process-entry dispatch inmain.rs. The grid renderer comes fromlibsinclair;view/layers Sinclair's pane behavior (search, hints, copy mode, suggestions, triggers) on top ofTerminalElement. The window opens with a transparent native title bar;titlebar.rsdraws the chrome itself — a themed strip with the tabs folded in (tabbar.rs), window dragging, and, on Linux, custom minimize/maximize/close controls plus resize edges.
The sinclair binary dispatches on argv before starting the GUI:
sinclair --toggle-quick— signals a running instance to summon the quick terminal (used by Wayland compositor keybinds), then exits.sinclair mcp— runs the MCP stdio server (mcpbridge), bridging tool calls into a running GUI instance.sinclair notify [--title T] <message>— posts a desktop notification, for agent hooks that can't emit an OSC 9/777/99 escape themselves.- otherwise — loads config and launches the gpui app.
A per-user unix socket carries one newline-terminated JSON request →
response per connection. Both --toggle-quick and sinclair mcp are clients;
the live GUI window is the server and does the real work. This is how the MCP
bridge and quick-terminal summon reach the running terminal.
terminal::Session emits events through a flume receiver that is also a
futures stream; libsinclair::bridge is the zero-thread adapter consumed by
gpui foreground tasks. Keep the vt/terminal layers free of gpui types — the
boundary is the bridge.
- In this project the agent has full authority to run git and everything else — branching, committing, pushing, tagging, cutting releases, and any other operation needed to move the work forward. The owner's usual "I handle git" rule does not apply here; act directly.
- Commit messages, PRs, and release notes carry no assistant attribution or co-author trailer.
- Releases ship straight from
main: a workspace version bump committed and pushed tomainis the release (see the version convention below). Run the full gate first —cargo test,cargo clippy --all-targets, and a release build — before pushing a version bump.
- Crates are layered; do not introduce upward dependencies (e.g.
vtmust not depend onapp). Keep terminal emulation logic invtand gpui concerns inapp. - The workspace version in the root
Cargo.tomldrives releases: pushing aCargo.tomlversion bump tomaintags and publishes a GitHub release with the macOS.dmgand Linux.tar.gz/.deb/.AppImage(x86_64 + aarch64), and updates the Homebrew cask (see.github/workflows/release.ymlanddocs/release.md). - Linux-only code (
linux.rs, the#[cfg(target_os = "linux")]blocks intitlebar.rs/main.rs) is not compiled on the macOS dev host; validate it with theLinux Buildworkflow (.github/workflows/linux.yml, runs on PRs).
docs/roadmap.md— built vs. planned.docs/parity.md— terminal feature coverage and known gaps.docs/release.md— signing, notarization, release cutting.docs/gpui.md— the gpui/zed dependency recipe.docs/libsinclair.md— embedding the terminal in other apps: thelibsinclaircrate,TermView, and headless usage.docs/guise.md— the guise component-library migration: howvendor/guiseis wired (the single-gpui patch), the theme bridge, and the surface-by-surface port status.docs/sandbox.md— the shared project sandbox: one container for a human and a whole agent team, the identity mount, the generated image, adoption, and whichdevcontainer.jsonfields are honoured.docs/relay.md— the agent mesh: roles, teams/tiles, therelayCLI, and the MCP coordination tools.