The terminal as an orchestration surface — for your shells, your plugins, and your AI agents.
copad is a cross-platform terminal emulator built on a shared Rust core with platform-native UIs — GTK4 on Linux, Swift/AppKit on macOS, SwiftUI on iOS. But it's more than an emulator: a workflow runtime (Event Bus, Action Registry, Context Service, Trigger Engine) and a plugin system turn the terminal into one programmable surface, where shells, AI agents, calendars, notes, Slack, and todos all compose and react to one another.
Manual · Install · Just comux · Configuration · Docs
- Built for AI agents —
comuxruns a whole team ofclaude/codexsessions in one terminal: live per-agent status, a desktop ping the moment one needs you, and a restart that brings every agent back mid-conversation — even after a reboot. - Programmable, not just configurable — an event bus and trigger engine let plugins react to each other, while the
coctlCLI and a Unix-socket API drive every pane, tab, and panel from a script. - Truly cross-platform — one Rust core, native UIs on Linux, macOS (Metal-accelerated), and iOS.
- GPU-rendered backgrounds — wallpaper image composited behind the terminal with configurable tint and opacity; random rotation supported
- Tabs + splits — horizontal/vertical splits, drag-to-resize, focus tracking, drag-to-reorder tabs, double-click rename, collapsible icon-only tab bar
- In-terminal search —
Ctrl+Shift+F(Linux) /Cmd+F(macOS), regex with case/whole-word toggle - 10 built-in themes — Catppuccin (Mocha/Latte/Frappé/Macchiato), Dracula, Nord, Tokyo Night, Gruvbox Dark, One Dark, Solarized Dark; hot-reload on config save
- Dynamic font scaling —
Ctrl+=/Ctrl+-/Ctrl+0(Linux) /Cmd+=/Cmd+-/Cmd+0(macOS) - Custom keybindings — bind any chord to a shell command (
spawn:); the spawned command inheritsCOPAD_SOCKET, sospawn:coctl …reaches the binding instance's socket actions
Run a team of AI coding agents in one terminal — see every agent's status at a glance, get pinged the moment one needs you, and detach without stopping any of them.
comux is a standalone, tmux-style multiplexer: a single self-contained binary (no GTK, no daemon) with a persistent server/client split, so the server owns your panes and outlives the terminal that launched it. It's a complete multiplexer — but it's built for the workflow tmux never was: driving several claude / codex sessions at once.
Just want the multiplexer?
comuxneeds no other part of copad. It's a single static binary you can drop onto any machine — including a headless server over SSH — and use anywhere you'd reach for tmux:curl -fsSL https://raw.githubusercontent.com/marshallku/copad/master/install-comux.sh | bash
Made for orchestrating agents
- Live agent status — an always-on sidebar lists every agent pane across every session with a real
status · toolreadout (working/ready/blocked), so you always know who's busy and who's waiting on you - Turn notifications — a native desktop toast fires the instant an agent finishes or blocks for input, even while you're detached;
Ctrl-b !jumps to the blocked one,Ctrl-b aopens a notification center - Detach & resume — close the client and the agents keep running on the server; reattach anytime and pick up exactly where you left off
- Restart-proof — including the AI conversation — kill the server or reboot the machine, and comux restores your whole layout and relaunches each agent mid-conversation via
claude --resume <id>/codex resume <id>. Where tmux-resurrect brings back your shells, comux brings back the live chat - One-command server restart —
comux server restart(alsostart/stop/status) stops the server with a final save and brings a fresh one up with your workspace restored — no manualkill-server-then-reattach dance when you upgrade the binary or need to clear drift - Subscription usage in the status bar — per-window rate-limit utilization (Claude 5h + weekly, Codex weekly) rendered as threshold-colored bars
- Update hint — a background check against GitHub releases shows a
⬆ x.y.zmarker in the status bar when a newer version is out, so remote-script installs know when to update (update_check = falseto disable)
A full multiplexer, too
- Splits, tabs, multi-session workspaces — vim-style pane focus/resize,
Ctrl-bprefix bindings, prefix-lessAlt+1–9, named sessions - git worktree integration —
comux worktree create <branch>spins up a worktree plus a session inside it (tmuxtwtparity) Ctrl-ffuzzy switcher — jump across sessions and agents just by typing- Everything you'd expect — scrollback/copy-mode, mouse (wheel forwarded to mouse-aware apps), shared multi-client, all configurable via
~/.config/copad/mux.toml
- Terminal panel — VTE4 on Linux,
alacritty_terminal+ custom AppKit/CoreText renderer on macOS; PTY handled internally on both platforms - WebView panel — WebKitGTK 6.0 (Linux) / WKWebView (macOS) as a first-class panel; URL toolbar, DevTools toggle, side-by-side with terminals
- Plugin panels — HTML/JS panels loaded from
~/.config/copad/plugins/with an injectedcopadJS bridge for socket calls and event subscriptions - Status bar — Waybar-style 3-zone bar (left/center/right) populated by plugin modules
coctlCLI — full programmatic control over tabs, splits, panels, terminals, webviews, plugins, and the event stream- Unix socket per GUI instance, newline-delimited JSON —
$XDG_RUNTIME_DIR/copad/gui-{PID}.sockon Linux,/tmp/copad-{PID}.sockon macOS (hardened relocation pending); injected asCOPAD_SOCKET, both forms auto-discovered bycoctl - Event stream —
event.subscribefor liveterminal.output,panel.focused,tab.created,webview.navigated, plus all bus events - Terminal agent API —
terminal.read/state/exec/feed/history/contextfor AI agents - Approval workflow —
agent.approveshows a modal and returns the user's choice claude.start— spawn a Claude Code session inside a tmux session in a target worktree
- Event Bus — pub/sub with glob patterns, bounded delivery, drop-newest overflow
- Action Registry — name → handler map; the same registry serves CLI dispatch, plugin RPC, and triggers
- Context Service — active panel, per-panel cwd cache, snapshots; exposed via
context.snapshot - Trigger Engine — declarative
[[triggers]]inconfig.toml(when.event_kindglob + payload match,conditionDSL,action+params, optionalawaitfor chained correlation, orcronfor schedules); fires actions on bus events with{event.*}/{context.*}interpolation; hot-reloads with subscriber reconciliation
plugins/<name>/ — install with ./scripts/install-plugins.sh. Each plugin directory holds the Rust crate (Cargo.toml + src/) and its runtime manifest/assets (plugin.toml, panel.html, triggers.example.toml) together. All plugins implement the service-plugin protocol (newline-JSON over stdio, supervised by copad).
| Plugin | Purpose |
|---|---|
kb |
Grep + filename search and atomic read/append/ensure over ~/docs |
docs |
KB panel — read + navigate ~/docs over dn's incremental indices |
calendar |
Google Calendar event polling with lead-time dedupe |
slack |
Slack Socket Mode — mention/DM/reaction events + chat.postMessage |
discord |
Discord gateway — mention/DM/reaction events + send_message |
jira |
Jira Cloud polling (assigned/comments/transitions) + read/write actions |
llm |
Anthropic Messages API client with JSONL usage log |
todo |
Markdown-checkbox todos in ~/docs/todos/<workspace>/ (vim/git compatible) |
git |
Worktree create/remove + branch / status queries |
claude |
Read-only views over ~/.claude harness artifacts (handoffs, sessions) |
pilot |
Autonomous goal queue — drives detached Claude sessions via csd |
web-bridge |
HTTP+WS cockpit (panes, attention, pilot queue) for browser / phone |
bookmark |
Bookmark capture with dedupe store |
echo |
Reference / E2E plugin |
- Linux — GTK4 + VTE4, full feature set
- macOS — Swift/AppKit +
alacritty_terminal, rendered on a Metal GPU path by default (~5.5× cheaper main-thread render; CoreText kept as thegpu = falsefallback). Full secondary platform: terminal, tabs, splits, search, themes, webview, plugins, status bar, keybindings, background images, AI agent API, daemon-client. Seedocs/macos-app.md. - iOS / mobile —
copad-ios, a SwiftUI + WKWebView native shell around theweb-bridgePWA: attach to a terminal, agent presence/attention, and push, over Tailscale or an SSH tunnel. Seedocs/mobile-access.md.
sudo pacman -S gtk4 vte4 webkitgtk-6.0 gst-plugins-good gst-plugins-badgst-plugins-good/gst-plugins-bad are required by WebKitGTK for media playback.
Install GTK4, libvte-2.91-gtk4, and webkitgtk-6.0 from your distribution's package manager.
Xcode Command Line Tools (Swift 6, macOS 14+) and Rust (for coctl and the FFI staticlib).
xcode-select --install
# https://rustup.rs for Rust# Build all crates
cargo build
# Run the terminal (Linux)
cargo run -p copad-linux
# Generate a default config file
cargo run -p copad-linux -- --init-config
# Control the running terminal via CLI
cargo run -p copad-cli -- <command>For macOS dev iteration: cd copad-macos && ./run.sh (debug bundle, opened in place).
curl -fsSL https://raw.githubusercontent.com/marshallku/copad/master/install.sh | bashOne command for both platforms — it detects the OS and installs the matching release: on Linux the GTK app + coctl/comux/copadd (and starts the copadd systemd --user unit); on macOS (Apple Silicon) Copad.app + coctl/copadd/comux + all first-party plugins + the copadd LaunchAgent (quarantine stripped for the ad-hoc-signed bundle).
Options (pass after bash -s -- when piping): --version vX.Y.Z to pin a release, --system to install system-wide (/usr/local/bin, /Applications; requires sudo), --no-daemon to not enable/start copadd (and stop + disable an existing one).
comux is a self-contained tmux-style multiplexer with no GTK/daemon dependency. To install only it:
curl -fsSL https://raw.githubusercontent.com/marshallku/copad/master/install-comux.sh | bashDownloads a single comux binary (Linux x86_64 / macOS arm64) into ~/.local/bin (--system for /usr/local/bin). The status-bar usage readout needs coctl on PATH; everything else works alone.
On glibc-incompatible Linux hosts — musl-libc distros like Alpine, or a machine whose glibc is older than the release runner's — the installer auto-picks a fully-static comux build (the -musl release asset, statically linked, zero libc dependency). musl-libc systems are detected automatically; on an old-glibc host pass --musl to force it. Build it yourself with cargo zigbuild --release --target x86_64-unknown-linux-musl -p copad-mux (or cargo build --target x86_64-unknown-linux-musl natively on a musl host).
./scripts/install-dev.sh # build + install everything to ~/.local/bin (no sudo)
./scripts/install-dev.sh --system # /usr/local/bin instead of ~/.local/bin (requires sudo)
./scripts/install-dev.sh --restart # also pkill -x copad afterwardsBuilds a release binary, installs the desktop entry, and lays down all first-party plugins via install-plugins.sh.
brew install --cask marshallku/copad/copadInstalls Copad.app to /Applications, coctl + copadd into $(brew --prefix)/bin, and lays out the 14 macOS plugins, shell hooks, and the copadd LaunchAgent (auto-starts at login). Requires Apple Silicon. The tap repo is marshallku/homebrew-copad.
The multiplexer is a separate formula — also available on Linux (statically linked, so any glibc):
brew install marshallku/copad/comuxIt is deliberately not part of the cask (two artifacts cannot both own $(brew --prefix)/bin/comux), so install it alongside the cask if you want both.
Both are published by the release CI: .github/workflows/homebrew.yml renders dist/homebrew/*.rb.tmpl with the release's version + checksums and pushes them to the tap, after verifying the published artifacts really are Developer ID signed and notarized. The tap files are generated — edit the templates here, not the tap.
macOS versions: 14 (Sonoma) and later, including 26 (Tahoe). Every Mach-O in a release (CLIs, copadd, plugin binaries, Copad.app) is Developer ID signed with a hardened runtime and secure timestamp, and Copad.app is notarized + stapled — see docs/macos-signing-notarization.md. That is what makes Tahoe safe: its App Verification policy deletes ad-hoc-signed executables on first launchd spawn, which used to remove copadd and the plugins. Releases cut before the signing secrets landed (v1.0.0 and earlier) are ad-hoc and still break there; v1.0.1 onwards are signed (verified against the published assets).
./scripts/install-macos.sh # ~/Applications + ~/.cargo/bin (no sudo)
./scripts/install-macos.sh --system # /Applications + ~/.cargo/bin (sudo for /Applications)
./scripts/install-macos.sh --launch # open Copad.app after installingBuilds libcopad_ffi.a (Rust staticlib) → links into the SwiftPM release build → stages and atomically installs Copad.app → installs coctl via cargo install --path copad-cli. Use this on Intel Macs (the Homebrew cask is arm64-only) or when iterating on the working tree.
./scripts/install-plugins.sh # install all first-party plugins
./scripts/install-plugins.sh todo git # install just theseRestart copad after installing/updating plugins — discover_plugins() only runs at startup.
coctl update check # check for new versions
coctl update apply # download and install latest (Linux only — macOS users re-run install.sh, brew upgrade --cask, or install-macos.sh)The copadd daemon checks GitHub releases once a day in the background and fires a native desktop notification (plus an update.available bus event) when a newer version ships — so you find out without polling. install.sh installs and starts copadd on both platforms (Linux systemd --user unit / macOS LaunchAgent), so this reaches release installs too; --no-daemon opts out, and COPAD_UPDATE_CHECK=0 disables just the check. comux shows the same signal as a ⬆ x.y.z status-bar hint.
copadd is the background daemon (trigger dispatch, plugin supervision, web-bridge). It runs as a systemd user unit — install-dev.sh and install.sh both install, enable, and start copad-daemon.service for you. The GUI (copad) is separate and is typically launched by your compositor (e.g. exec-once = /home/marshall/.local/bin/copad in hyprland.conf).
systemctl --user status copad-daemon # inspect
journalctl --user -u copad-daemon -f # tail logs
systemctl --user restart copad-daemon # apply a new binary or env overrideWhen does it start on boot?
| Scenario | Daemon starts on boot? |
|---|---|
Display manager autologin (SDDM/GDM with User=…) |
Yes — user session activates systemd --user, which starts the enabled unit |
| Manual login on TTY/greeter | Yes — at login |
| Headless boot, no login yet | No — daemon waits for first user session |
| All sessions logged out | Daemon stops with the last session |
For a single-user desktop with autologin, the default is enough.
Want the daemon up from boot without any login, and surviving all logouts? Enable linger:
sudo loginctl enable-linger $USERWith linger on:
systemd --user@<uid>starts at boot regardless of login state.- The daemon stays alive across logouts.
- SSH / web-bridge / remote-control reach a daemon that is already running, not one that starts on first contact.
PATH note: spawn-style keybindings (e.g. spawn:~/copad-random-bg.sh --next) shell out to coctl. If you installed to ~/.local/bin and your Hyprland/systemd session PATH does not include it, the spawned child cannot find coctl. Fix once with:
mkdir -p ~/.config/environment.d
printf 'PATH=%s/.local/bin:${PATH}\n' "$HOME" > ~/.config/environment.d/10-local-bin.conf
# Re-login (or `systemctl --user import-environment PATH` + restart compositor) to apply.Config file: ~/.config/copad/config.toml (entirely optional — all fields have defaults).
[terminal]
shell = "/bin/zsh"
font_family = "JetBrainsMono Nerd Font Mono"
font_size = 14
[background]
# image = "/path/to/wallpaper.jpg" # static image (rotation replaces it at the first tick)
# rotate_interval = 300 # seconds between random wallpapers from the platform list; 0 = off
tint = 0.85 # tint overlay opacity (0.0–1.0)
opacity = 0.95 # background-image opacity
[tabs]
position = "left" # top, bottom, left, right
collapsed = true # start with tab bar collapsed (icon-only)
width = 200 # tab bar width for vertical positions
[theme]
name = "catppuccin-mocha"
[keybindings]
"ctrl+shift+g" = "spawn:~/scripts/wallpaper.sh --next"
"ctrl+shift+m" = "spawn:~/.local/bin/coctl background toggle"
[security] # macOS only, for now
osc52 = "deny" # or "allow" — gates OSC 52 clipboard writes from the PTYSee docs/config.md for the full reference, and docs/workflow-runtime.md for [[triggers]] declarations.
copad/
├── copad-core/ # Shared Rust library (config, protocol, event bus,
│ # action registry, context, triggers, themes, fs_atomic)
├── copad-ffi/ # Rust staticlib for Swift FFI (macOS bridge)
├── copad-linux/ # GTK4 + VTE4 native terminal app (binary: copad)
├── copad-macos/ # Swift/AppKit + alacritty_terminal app (Copad.app)
├── copad-ios/ # SwiftUI + WKWebView mobile shell over the web-bridge PWA
├── copad-term/ # Rust staticlib wrapping alacritty_terminal for the macOS renderer
├── copad-cli/ # CLI control tool (binary: coctl)
├── copad-daemon/ # Background daemon (binary: copadd) — triggers, plugins, web-bridge
├── copad-mux/ # Standalone terminal multiplexer (binary: comux)
├── plugins/<name>/ # First-party service plugins. Each subdir holds the
│ # Rust crate (Cargo.toml + src/) and its manifest/assets
│ # (plugin.toml, panel.html, triggers.example.toml) together.
│ # Crate names remain `copad-plugin-<name>`.
├── examples/plugins/hello/ # Tutorial plugin: panel + bash command (no Rust crate)
├── scripts/ # install-dev.sh, install-macos.sh, install-plugins.sh
└── docs/ # Project documentation — start at docs/INDEX.md
New here? Read the user manual — install, configuration, and full command references for copad, comux, and coctl, with examples.
The docs below are developer-oriented internals. Start at docs/INDEX.md. Highlights:
architecture.md— crate layout, socket protocol, panel systemworkflow-runtime.md— Event Bus, Action Registry, Context Service, triggersplugins.md— plugin manifest, JS bridge API, service-plugin RPCservice-plugins.md— long-running supervised subprocess designcli.md—coctlreferencelinux-app.md/macos-app.md— platform internalstroubleshooting.md— known issues + fixesroadmap.md— implementation phases
MIT