* fix(windows,scm,daemon): quote paths per shell, wire Checkout to, bound Spawn Five fixes from a whole-codebase audit, in one sweep because they share the paths they touch. Path quoting had two implementations. file_tree::shell_quote_for wrapped the path in quotes and picked the right ones per shell (#593); view::shell_escape_path escaped with backslashes, which is POSIX-only and collides head-on with the Windows path separator, so a dropped file, a pasted path, a staged image path and an accepted completion candidate all lost their separators there. completion::complete_path stripped the same backslashes back off before looking a path up, so inline path completion could never resolve a directory on Windows either. Both now go through one core::shell_quote module, and shell_word_start tracks quoting across the word so a second Tab still finds the word it just inserted. "Checkout to..." was registered, listed in the palette, bindable, and handled by an empty match arm — invoking it did nothing at all. It now opens an inline input row in the SCM panel, the twin of the existing "create branch" one. RemoteTerminal's Spawn read the daemon's reply with no deadline, while Attach in the same file and PaneSession::spawn_over in core both bound theirs. A daemon caught mid-restart accepts the connection and never serves it, and the local route spawns synchronously on the UI thread, so the silence froze the window on "new tab". Two Windows papercuts: client_hostname spawned a console program from a GUI process (a visible console flash) where COMPUTERNAME already has the answer, and completion generators were a silent no-op with no way to tell "produced nothing" from "never ran". Three duplicated implementations merged: proc_name existed twice in the daemon with a different fallback in each, the GUI's control link was the one client socket that skipped transport::tune, and fps.rs and perf.rs were the same windowed meter copied twice. * refactor(completion): stop declaring spec fields nothing reads The Fig spec structs mirrored seven keys the completer never looks at, each held up by its own #[allow(dead_code)]. Serde ignores unknown fields by default, so dropping the declarations parses the same specs and drops the attributes with them. * refactor(daemon): delete the loopback-forward management pipeline Two protocol messages, their kind codes, encode and decode arms, two daemon dispatch arms, two wire structs and two GUI client wrappers all existed to reach SshManager::list_loopback_forwards and close_loopback_forward, which were hardcoded to Vec::new() and false. Nothing called the client wrappers either. The kind codes are left as holes rather than renumbered, the way 13 already is, so the wire format is unchanged for every other message. known-hosts management looks like the same shape but is not: its backend parses the real file, fingerprints keys and rewrites through a 0600 temp file. That one keeps its client half and gains a comment saying it is an interface waiting for a screen. * test(ssh): cover the host-key policy table and both proxy handshakes The host-key decision is lifted out of check_server_key into host_key_action, so what to do about Known/Unknown/Changed/ ChangedAlgorithm/Revoked can be read and tested without a server, a broker or a known_hosts file. Eight tests pin it, including the two subtleties the comments already claimed: verify_host_keys=false still rejects a revoked key, and a new algorithm asks the unknown-host prompt rather than a new variant older peers cannot decode. socks5_connect and http_connect are split into connect + handshake, the handshake generic over the stream, so nine tests drive them from an in-memory duplex: length-prefix framing, the variable-length bound address, auth refusal, reply codes, and the header terminator. * test(cli,daemon): cover server binary resolution and the procargs parser server_exe is split into environment lookup and resolve_server_exe, the latter taking its three sources and an is_exe predicate so seven tests can pin the precedence without touching the filesystem. Holding the sibling to is_file rather than exists fixes a directory named tty7-server shadowing the real binary on PATH. parse_macos_procargs gets six tests over the KERN_PROCARGS2 layout: exec-path skipping, however many bytes of alignment padding follow it, argc bounding argv so the environment stays out, truncation, and a short buffer. * test(ui): cover the host-op pool decisions and the local reconnect schedule The pool's retire condition moves into should_retire with the reason named: a worker must not retire on the timeout alone, because submit counted it as idle and so did not spawn a replacement for the job that landed meanwhile. LocalLink::tick's schedule moves into due(), taking the clock and the link's state as arguments. The first attempt going out immediately, the backoff only applying from the second, and a pending deadline not being pushed further out by later ticks are now pinned. The identical scheduler in remote_workspace had TestAppContext coverage; this one, which every launch depends on, had none. * fix(completion): unquote across the whole word, not just its first character The round-trip test caught two things the first cut got wrong. A quote can open partway into a word — quote_for_shell emits ~/'My Documents' so the shell still expands the tilde — and a single-quoted body is literal all through, so unescaping backslashes inside one took the separators out of 'C:\Users\me'. Scanning with a quote state handles both, and makes the '\'' seam fall out of the state changes rather than needing a case of its own. The GPUI test for accepting a candidate follows the insertion from backslash escaping to quoting. * fix(windows): unbreak the Windows build and quote for PowerShell's own dialect `Instant` was moved behind `#[cfg(unix)]` while the generator cache still uses it unconditionally, so the Windows target stopped compiling. The quoting module treated every shell but cmd.exe as POSIX, including PowerShell. PowerShell does not join a quoted string to the bare word beside it, so the `'\''` seam is not a seam there — `C:\Users\O'Brien` came out as three tokens, and the completion un-quoter turned the apostrophe back into a backslash. Quoting is now a three-way dialect (cmd / PowerShell / POSIX) chosen once and threaded through completion in place of the escapes flag. * test(file-tree): name the shell where the quoting rule is the POSIX one `shell_quote_for(_, None)` answers from the platform, so an assertion about the `'\''` seam has to say which shell it means or it fails on Windows, where the unnamed shell is PowerShell.
tty7
A terminal workbench: persistent sessions, remote work, agents.
Pure Rust · GPU rendering on Zed's gpui · VT core from Alacritty
English · 简体中文
Why
A background server owns your shells and panes — not the window. Everything below follows from that.
- Performance — ~2× the throughput of Alacritty, Ghostty, or Kitty (benchmarks)
- Persistent sessions — quit or reboot; your shells and supported agent sessions keep running, no tmux
- Agent-aware — Claude Code, Codex & co.: status, notifications, and git context for every repo at once
- Scriptable by agents — one agent opens a pane for another, hands off a task, waits, and reads the result, with or without the GUI running
- Editor-grade input — suggestions, completion, highlighting, history search, with no plugin to install
- Remote development — files, repos, panes, and git data stay on the remote machine, over a native SSH stack
- Git beside the terminal — source control, diffs, and worktrees without leaving the window
Install
Native builds for macOS, Windows, and Linux on Releases:
| macOS | …-macos-arm64.dmg · …-x86_64.dmg |
drag into Applications |
| Windows | …-setup.exe · portable ….zip |
|
| Linux | …-x86_64.AppImage |
chmod +x and run — X11/Wayland libraries bundled |
What's inside
| Agent-aware | per-pane detection (19 CLIs) · status dot · notifications · branch + diff · tray icon when input is needed · resume after reboot · tab sidebar grouped by repository |
| CLI + Skills | bundled tty7 CLI · agent skill · run streams a command and exits with its code · split · send · wait --until free · capture |
| Editor-grade input | ghost suggestions from history · explained tab completion · syntax highlighting · multi-line editing · click places the caret · ⌃ R fuzzy history |
| Window | tabs & splits · ⌘ P palette · ⌘ F scrollback search · ⌘ J panel with process tree and listening ports · 13 themes, your own YAML, iTerm2 import · IME |
| Shell integration | injected when a pane starts, nothing to install · prompt marks · working directory · exit codes · command-finished notifications · zsh, bash, fish, PowerShell, WSL, remote panes |
| Remote workspaces | remote files, repos, changes, diffs, worktrees, tabs, and panes · reconnect from any client and continue where you left off |
| SSH | native russh stack: profiles with keychain secrets · SFTP panel · port forwarding · jump hosts · one-time, unprivileged tty7-server install |
| Git | panel follows the focused pane · stage, commit, amend, branch, push, stash · side-by-side or unified diffs · commit graph with cherry-pick, revert, and reset · a new worktree opens its own tab |
Supported agents
Detection is free: brand avatar, branch + diff, tab title.
Status takes one click under Settings → Agents to install that agent's hook,
and brings the status dot, notifications, the tray icon, tty7 wait, and resume
after a reboot. Fork needs both — the agent's own fork command, and the hook
that tells tty7 which session to fork.
The full support matrix, all nineteen
| Agent | Detected | Status · resume | Fork |
|---|---|---|---|
| Claude Code | ✓ | ✓ | ✓ |
| Codex | ✓ | ✓ | ✓ |
| Grok | ✓ | ✓ | ✓ |
| OpenCode | ✓ | ✓ | ✓ |
| Oh My Pi | ✓ | ✓ | ✓ |
| Droid | ✓ | ✓ | ✓ |
| Qwen Code | ✓ | ✓ | ✓ |
| Goose | ✓ | ✓ | ✓ |
| Gemini | ✓ | ✓ | |
| Copilot | ✓ | ✓ | |
| Kimi Code | ✓ | ✓ | |
| Pi | ✓ | ✓ | |
| Aider | ✓ | ||
| Amp | ✓ | ||
| Cursor | ✓ | ||
| Auggie | ✓ | ||
| Hermes | ✓ | ||
| Vibe | ✓ | ||
| Antigravity | ✓ |
None of them are wrapped or proxied — the agent you start is the agent you get,
in a normal PTY, with its own interface. An agent launched through a wrapper
script can be mapped to one by name with agent_commands in config.json.
Documentation
Full documentation lives in docs/ —
keyboard shortcuts ·
config.json ·
CLI reference. The agent-facing CLI interface is also
documented in skills/tty7/SKILL.md.
Install the skill with:
npx skills add l0ng-ai/tty7 # install
npx skills update tty7 # update later
Benchmarks
Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1, five-run averages (2026-07-04):
| tty7 | Alacritty | Ghostty | Kitty | |
|---|---|---|---|---|
Plaintext I/O — 11 MB cat (lower = better) |
95 ms | 239 ms | 179 ms | 185 ms |
| DOOM-fire frame rate (higher = better) | 888 fps | 485 fps | 552 fps | 617 fps |
| Cold-launch memory | 116 MB¹ | 105 MB | 128 MB | 130 MB |
¹ GUI 105 MB + the persistent server 11 MB.
Methodology and one-command reproduction: scripts/bench/.
Built on gpui and alacritty_terminal · Apache-2.0 · Discord · Changelog