Linux-only multi-agent terminal with split panes, git worktree workspaces, session restore, and prompt-aware notifications.
Run several coding agents in one desktop window. ForkTTY keeps their terminals and workspace state separated, can place agents in isolated git worktrees, and surfaces unread prompts when a background workspace needs attention.
Status: Early development (v0.1.2). ForkTTY is usable on Linux, but the runtime surface is still changing. There are no macOS or Windows builds.
- Linux desktop app built with Tauri v2, Rust, React 19, TypeScript, and Vite.
- xterm.js terminals using
@xterm/xterm5.5 with Fit, Search, and Canvas addons. - Split panes powered by
react-resizable-panels, with horizontal/vertical splits, drag resize, andAlt+Arrownavigation. - Workspace sidebar with branch, directory, worktree status, unread counts, notification previews, drag reorder, and optional collapsed/right-side layouts.
- Git worktree workspaces using native
git2operations, configurable nested/sibling/outer-nested layouts, and optional.forktty/setup/.forktty/teardownhooks. - Session persistence for workspace order, active workspace, pane tree, focused pane, names, working directories, branches, and worktree metadata. PTYs and scrollback are not persisted.
- Session restore hardening: malformed or unsupported session files are ignored and quarantined; pane trees are validated before restore.
- Prompt-aware notifications from OSC 133, Claude-style prompt patterns, and OSC 9/99/777 terminal notification sequences.
- Noise controls for notifications: switch/restore suppression, short-window dedupe, and no repeated prompt notifications while a workspace is already unread.
- Welcome/onboarding and polished dialogs: first-run welcome overlay, safer focus defaults for destructive modals, and improved empty/loading/error states in settings, branch picker, command palette, notification panel, and pane spawn failures.
- Ghostty theme compatibility for local colors, font family, font size, and palettes, with explicit ForkTTY config taking precedence.
- Local socket API over a user-private Unix domain socket for workspace, surface, notification, worktree, and metadata automation.
- System tray integration with unread-count tooltip and click-to-focus behavior when the desktop environment supports it.
- Privacy-first defaults: no telemetry, no update checks, no external network calls. See PRIVACY.md.
- Linux with WebKitGTK 4.1 and AppIndicator/Ayatana libraries.
- Rust 1.88+
- Node.js 20+
Debian / Ubuntu:
sudo apt install libwebkit2gtk-4.1-dev build-essential \
libssl-dev libayatana-appindicator3-dev librsvg2-devFedora:
sudo dnf install webkit2gtk4.1-devel gtk3-devel libappindicator-gtk3-devel librsvg2-develgit clone https://github.com/Lucenx9/forktty.git
cd forktty
npm install
npm run tauri:devnpm run tauri:build
sudo dpkg -i src-tauri/target/release/bundle/deb/ForkTTY_*.debThe AppImage is emitted under src-tauri/target/release/bundle/appimage/ when the AppImage bundle is produced:
chmod +x src-tauri/target/release/bundle/appimage/ForkTTY_*.AppImage
src-tauri/target/release/bundle/appimage/ForkTTY_*.AppImageThe packaging script normalizes AppImage root symlinks, rejects unsafe icon values containing / or .., and patches the AppImage runtime environment for common WebKitGTK/GPU issues.
- Release page: https://github.com/Lucenx9/forktty/releases/latest
- Debian / Ubuntu / Linux Mint: download
ForkTTY_*.deb, then install withsudo dpkg -i. - Other Linux distributions: download
ForkTTY_*.AppImage,chmod +xit, then run it directly. - Prefer the native
.debon Debian-family systems; use the AppImage as the portable fallback.
# .deb example for the current 0.1.2 release
curl -LO https://github.com/Lucenx9/forktty/releases/latest/download/ForkTTY_0.1.2_amd64.deb
sudo dpkg -i ForkTTY_0.1.2_amd64.deb# AppImage example for the current 0.1.2 release
curl -LO https://github.com/Lucenx9/forktty/releases/latest/download/ForkTTY_0.1.2_amd64.AppImage
chmod +x ForkTTY_0.1.2_amd64.AppImage
./ForkTTY_0.1.2_amd64.AppImage| Action | Shortcut |
|---|---|
| New workspace | Ctrl+N |
| New worktree workspace / branch picker | Ctrl+Shift+N |
| Close workspace | Ctrl+Shift+W |
| Jump to workspace 1-9 | Ctrl+1..Ctrl+9 |
| Split right | Ctrl+D |
| Split down | Ctrl+Shift+D |
| Navigate panes | Alt+Arrow |
| Close pane | Ctrl+W |
| Find in terminal | Ctrl+F |
| Copy selection | Ctrl+Shift+C |
| Command palette | Ctrl+Shift+P |
| Notification panel | Ctrl+Shift+I |
| Jump to unread workspace | Ctrl+Shift+U |
| Settings | Ctrl+, |
| Zoom in / out / reset | Ctrl+= / Ctrl+- / Ctrl+0 |
These shortcuts intentionally override some terminal defaults while the main app surface is focused. Text inputs, modals, the branch picker, and command palette block workspace-mutating shortcuts.
Config file: ~/.config/forktty/config.toml. All fields are optional.
[general]
# "auto" reads Ghostty colors/fonts when available; "builtin" uses the fallback theme.
theme_source = "auto"
# Must be an absolute path to an executable file. Default: $SHELL, then /bin/bash.
shell = "/bin/bash"
# Worktree placement: "nested" (.worktrees/), "sibling", or "outer-nested".
worktree_layout = "nested"
# Empty disables custom commands.
# If set, the first token must be an absolute path to an executable file.
# Additional static arguments are currently supported through shell_words parsing.
notification_command = ""
[appearance]
font_family = ""
font_size = 14
sidebar_position = "left" # "left" or "right"
[notifications]
desktop = true
sound = trueConfig loading is bounded to regular files of at most 1 MiB for ForkTTY's TOML config. Invalid saved settings are rejected when written from the app. Loaded config values are normalized where possible; spawned shells still require a valid executable path.
Ghostty users: ForkTTY reads ~/.config/ghostty/config and theme files under ~/.config/ghostty/themes/ for compatible colors, font family, font size, and palette entries. Explicit [appearance] values override Ghostty-derived values.
notification_command runs in addition to desktop notifications when ForkTTY dispatches a notification.
- The command is split with
shell_words; ForkTTY does not usesh -c. - The first token must be an absolute path to an executable regular file.
- Additional static arguments are passed as argv if present.
- Pipes, redirection, variable expansion, globs, and shell operators are not interpreted.
- The notification payload is passed through environment variables:
FORKTTY_NOTIFICATION_TITLEFORKTTY_NOTIFICATION_BODY
Example:
[general]
notification_command = "/usr/bin/notify-send --app-name ForkTTY"A stricter future policy that disallows extra notification_command arguments is tracked as a follow-up, not current behavior.
ForkTTY saves workspace layout through a debounced store subscription and attempts a final best-effort save on window unload. On startup it restores the workspace list, active workspace, pane tree, focused pane index, working directories, branch labels, and worktree metadata.
Restore does not preserve PTY processes or scrollback. New PTYs are spawned for restored panes. Corrupt or structurally invalid session files are renamed to session.json.bad-* and ignored so the app can start with a clean workspace.
Prompt notifications are suppressed briefly during restore and workspace switches to avoid false positives from shell redraws and terminal resizing.
Worktree workspaces are optional. Ctrl+Shift+N opens the branch picker, which can create a new branch from HEAD or attach a worktree to an existing branch. Layout is controlled by general.worktree_layout.
Socket-driven worktree.* operations validate caller-provided cwd values against repositories already open in the frontend. Subdirectories and linked worktrees in the same open repository are accepted; unrelated repositories are rejected. Removing the last worktree-backed workspace creates a replacement plain workspace rooted at the repository fallback path before closing the worktree workspace.
Frontend (React 19 + TypeScript + Vite)
- @xterm/xterm 5.5 with Fit, Search, and Canvas addons
- react-resizable-panels recursive pane layout
- Zustand workspace/config/metadata stores
Tauri v2 IPC
- invoke commands for control paths
- Channels for PTY output streaming
- local event bridge for socket API requests
Backend (Rust)
- portable-pty for PTY lifecycle
- git2 for repository and worktree operations
- output_scanner for OSC/prompt detection
- notify-rust for desktop notifications
- tokio + serde_json for the Unix socket API
- Linux-only local desktop threat model; the current user is the primary trust boundary.
- Unix socket defaults to
$XDG_RUNTIME_DIR/forktty.sock, validates private parent permissions, uses owner-only socket permissions, and enforces a 1 MiB request limit. - Shell path and
notification_commandprogram path must be absolute executable files. - Custom notification commands use argv execution, not
sh -c; title/body are delivered via environment variables. - Worktree names and paths are validated; hook execution is restricted to
.forktty/setupand.forktty/teardowninside verified worktrees. - AppImage packaging rejects unsafe icon path traversal values and refuses absolute root symlinks.
- CSP restricts the WebView to local app content.
See SECURITY.md for reporting instructions, residual risks, and the full local trust model.
- Linux only. There are no supported macOS or Windows builds.
- Dark theme only. CSS has limited system-preference handling, but no light-mode toggle.
- PTYs and scrollback are not persisted across restart.
- Session saves are debounced and the final
beforeunloadsave is best effort. - No idle-time notification detector; only prompt/OSC notification triggers are active.
- No end-to-end PTY backpressure. The frontend bounds pending output and may drop buffered bytes under sustained overload.
- Full Tauri GUI smoke tests and routine manual runtime QA are still backlog items.
- Documentation screenshots may lag small UI polish changes.
ForkTTY is in active early development. Keep changes small, verify the relevant surface, and avoid documenting behavior that is not implemented in the current code.
Useful commands:
npm run tauri:dev # Dev mode
npm run build # TypeScript + Vite production build
npm run lint # ESLint for src/
npm run test # Vitest suite
npm run tauri:build # Production Tauri build via packaging script
npm run tauri:info # Tauri environment info
cargo fmt --manifest-path src-tauri/Cargo.toml --check
cargo clippy --manifest-path src-tauri/Cargo.toml -- -D warnings
cargo test --manifest-path src-tauri/Cargo.toml
npx prettier --check src/See SPEC.md for technical details and ROADMAP.md for implemented, planned, and known backlog work.
Built from scratch for Linux, inspired by cmux (macOS-only, Swift/AppKit).
GNU Affero General Public License v3.0 (AGPL-3.0-only)
