Skip to content

Latest commit

 

History

History
232 lines (205 loc) · 13.1 KB

File metadata and controls

232 lines (205 loc) · 13.1 KB

Sinclair

Repository guidance for agent sessions.

What this is

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

Commands

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 dependency

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.

Architecture

The workspace is layered bottom-up; each crate depends only on those below it.

  • vt — the terminal emulation core. Pure, no I/O: a vte-driven parser feeds a Grid/Screen with 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::spawn runs a child on a pty, feeds its bytes into a vt::Terminal on a reader thread, and emits Events (wakeup, title, bell, exit) over an async-capable flume receiver.
  • cast — asciinema v2 .cast recording: a Recorder writes a header line plus timestamped output events as bytes arrive (output only; UTF-8 split across reads is carried over). Used by terminal for session capture.
  • container — container-backed terminals, pure argv construction with no I/O beyond a $PATH probe: 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 image Recipe that installs the agent CLIs, and discovers containers a project already has by label so an editor's devcontainer is entered rather than duplicated. app drives it; relay uses the same builders so a docker exec assembled on either side cannot drift. See docs/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's settings.json (JSON with comments, parsed by json.rs), with live file watching. kind.rs is the typed key schema; bad values become friendly diagnostics plus the default and never abort the load. jsonedit.rs does comment-preserving single-key writes. Default path $XDG_CONFIG_HOME/sinclair/settings.json or ~/.config/sinclair/settings.json; the legacy key = value config file is still parsed for one-time migration (app's confwrite::migrate).
  • theme — 22 built-in color schemes (builtin/) plus per-color overrides.
  • plugin — parses plugin.toml manifests contributing: [[command]] actions (+ default keybindings), a [panel] block-tree drawer, [[trigger]] event hooks, and [[tool]] MCP tools exposed to agents (mcpbridge merges them into sinclair 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: a wasmtime component-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 the AppHost trait app implements, keeping wasmtime out of app's dependency surface. See docs/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). Optional candle feature (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's auto_update design): GitHub release check, install detection, and in-place installs — macOS mounts the .dmg and 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 a Relaunch decision that app's updateui.rs hands to gpui's restart — Relaunch::Current restarts with no explicit path so gpui reopens the running bundle via NSBundle. 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 report Stages as they run so updateui can 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: agents register, message each other / channels, and wait (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), and cli/ (the relay subcommands). The app never runs the mesh in-process — app/src/relay/ starts/stops the bundled binary as a detached daemon and builds the relay launch command lines the app runs: single agents into splits, and a whole team into its own window (one member per pane) unless relay-team-window is off. Reads no env vars; every parameter comes from settings, passed explicitly. See docs/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 of app — the colors/metrics/mouse/boxdraw policy modules, element::TerminalElement (grid painting, damage-aware frame reuse), the pointer glue, the event bridge, and termview::TermView, a drop-in terminal pane for other gpui apps. gpui sits behind the default ui feature, so default-features = false is a gpui-free headless core. Consumed as a git dependency; see docs/libsinclair.md and crates/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 in main.rs. The grid renderer comes from libsinclair; view/ layers Sinclair's pane behavior (search, hints, copy mode, suggestions, triggers) on top of TerminalElement. The window opens with a transparent native title bar; titlebar.rs draws 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.

Process modes (app/src/main.rs)

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.

Single-instance IPC (app/src/ipc.rs)

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.

Event flow

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.

Working in this repo

  • 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 to main is 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.

Conventions

  • Crates are layered; do not introduce upward dependencies (e.g. vt must not depend on app). Keep terminal emulation logic in vt and gpui concerns in app.
  • The workspace version in the root Cargo.toml drives releases: pushing a Cargo.toml version bump to main tags and publishes a GitHub release with the macOS .dmg and Linux .tar.gz/.deb/.AppImage (x86_64 + aarch64), and updates the Homebrew cask (see .github/workflows/release.yml and docs/release.md).
  • Linux-only code (linux.rs, the #[cfg(target_os = "linux")] blocks in titlebar.rs/main.rs) is not compiled on the macOS dev host; validate it with the Linux Build workflow (.github/workflows/linux.yml, runs on PRs).

Docs

  • 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: the libsinclair crate, TermView, and headless usage.
  • docs/guise.md — the guise component-library migration: how vendor/guise is 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 which devcontainer.json fields are honoured.
  • docs/relay.md — the agent mesh: roles, teams/tiles, the relay CLI, and the MCP coordination tools.