bosun-tmux 0.3.2

Tmux-native orchestrator for AI agent sessions
Documentation

Bosun

Bosun Screenshot

Tmux-native orchestrator for AI agent sessions. Written in Rust with ratatui.

Bosun lists, previews, creates, and manages tmux sessions running AI coding agents (Claude Code, Codex, or a plain shell) from a single terminal UI. It was built as a from-scratch reimagining of agent-deck — same workflow, new architecture, designed around a few rules that keep it simple and robust:

  • Tmux is the source of truth. Bosun receives push notifications from tmux via control mode (tmux -C). No shared database state to race on; multi-instance coexistence is trivial because every bosun reads the same tmux server.
  • Actor pattern, single-writer app state. One task owns tmux I/O, one task owns AppState. No nested mutexes, no Arc<Mutex<...>> scattered across the render path.
  • Dedicated tmux socket. Bosun runs its sessions on tmux -L bosun by default so it never touches your other tmux state, and so Claude Code's macOS Keychain auth lineage flows through bosun's process tree correctly.
  • Per-session status bar. Bosun writes its status line with set-option -t <session>, never globally, so non-bosun sessions on the same server are untouched.
  • Opencode aesthetic. Borderless, subtly shaded panels, bold accents, modal dialogs with left accent bars and drop shadow. Fifteen built-in themes — ten dark (opencode, tokyonight, dracula, catppuccin-mocha, one-dark-pro, ayu-mirage, nord, gruvbox-dark, rose-pine, github-dark) and five light (github-light, one-light, solarized-light, ayu-light, quiet-light) — switched live with t.

Features

  • Live session list with smoothed status detection ( running · waiting · idle · error) and pane preview
  • Create new bosun-managed sessions from a modal form: name, path, agent choice, and agent-specific options (Claude --continue / --resume / skip-permissions, Codex --yolo)
  • Filesystem tab-completion in the path field (shell-style LCP matching against live directory contents)
  • Recent sessions picker (Ctrl+R from the new-session modal) backed by SQLite, with live substring filter and delete-from-list
  • Session lifecycle: attach (Enter), rename (r), restart (R), kill (d)
  • Fifteen built-in themes (10 dark + 5 light) plus user themes from $XDG_CONFIG_HOME/bosun/themes/*.toml; live preview picker on t
  • Config file at $XDG_CONFIG_HOME/bosun/config.toml with theme, session_prefix, tmux_socket knobs (env vars still override)
  • One-key detach: Ctrl+Q inside any attach returns you to bosun without touching your tmux prefix or leaving stray bindings behind

Requirements

  • Rust 1.80 or newer
  • tmux 3.x (tested against 3.6)
  • macOS or Linux (Windows is not supported)

Installation

Homebrew (recommended)

brew install yetidevworks/bosun/bosun

From crates.io

cargo install bosun-tmux

(The crate is published as bosun-tmux because bosun is reserved on crates.io; the installed binary is still bosun.)

From source

git clone https://github.com/yetidevworks/bosun
cd bosun
cargo install --path .

Pre-built binaries

Download from GitHub Releases.

Key bindings

Main list

Key Action
/ / k / j Move selection
Shift+↑ / Shift+↓ / K / J Reorder selected session
Enter Attach to selected session
n New session
r Rename selected session
R Restart selected session (kill + recreate with same spec)
d Kill selected session (with confirm)
t Theme picker (arrows live-preview, Enter applies + persists)
Ctrl+R Force immediate refresh
q / Ctrl+C Quit

Inside a bosun-managed attach

Key Action
Ctrl+Q Detach back to bosun

The Ctrl+Q binding is installed on the root key-table only for the duration of the attach and removed on return. It never touches your tmux prefix (C-b by default, C-a for a lot of us), so muscle memory keeps working.

New-session modal

Key Action
Tab / Shift-Tab Next / previous field
Ctrl+R Open recents picker and pre-fill from a past session
Tab (in path field) Filesystem completion — 1 match commits, N matches extend to longest common prefix
/ (in path field) Navigate filesystem dropdown (scrollable)
Esc (in path field) Dismiss dropdown so Tab advances
Space (on checkbox) Toggle option
Enter Create session
Esc Cancel

Themes

Fifteen themes ship built in (ten dark, five light). Press t on the main list to open the picker — arrow keys live-preview the whole UI including the modal itself, Enter applies and writes the choice to config.toml, Esc reverts.

Dark:

  • opencode (default)
  • tokyonight
  • dracula
  • catppuccin-mocha
  • one-dark-pro
  • ayu-mirage
  • nord
  • gruvbox-dark
  • rose-pine
  • github-dark

Light:

  • github-light
  • one-light
  • solarized-light
  • ayu-light
  • quiet-light

Custom themes

Drop a <name>.toml into $XDG_CONFIG_HOME/bosun/themes/ (on macOS: ~/Library/Application Support/dev.yetidevworks.bosun/themes/) and it shows up in the picker alongside the built-ins. User themes override built-ins of the same name. A theme is a set of hex colors for 13 semantic slots:

name = "my-theme"

bg             = "#0b0d12"   # deepest background
panel          = "#11141b"   # session list row bg
panel_alt      = "#131722"   # status bar + modal bg
selection_bg   = "#1e2433"   # selected row / focused field
text           = "#e6e9ef"
text_muted     = "#7c8495"
accent         = "#7c5cff"   # primary accent, selection marker, modal bars
shadow         = "#05070b"   # modal drop shadow
dim_fg         = "#3c4254"   # dim-background foreground behind modals
status_running = "#62d98c"
status_waiting = "#f4c169"
status_idle    = "#7c8495"
status_error   = "#ff5d6b"

See themes/opencode.toml in the repo for the authoritative reference.

Configuration

Bosun reads (in order of precedence):

  1. Built-in defaults
  2. $XDG_CONFIG_HOME/bosun/config.toml
  3. Environment variables

Example config.toml:

theme          = "tokyonight"
session_prefix = "bosun-"        # bosun only manages sessions with this prefix
tmux_socket    = "bosun"         # dedicated tmux -L socket; "default" uses your shared socket
divider_x      = 50              # saved automatically when you drag the list/preview divider

Environment overrides:

Var Equivalent
BOSUN_THEME theme
BOSUN_PREFIX session_prefix (empty string = show all sessions)
BOSUN_TMUX_SOCKET tmux_socket (empty or default = shared socket)
BOSUN_LOG Tracing filter, e.g. BOSUN_LOG=info

How Ctrl-Q detach works

Just before each attach bosun installs a temporary root-table binding:

tmux bind-key -T root C-q detach-client
tmux attach-session -t <name>    # blocks until you detach
tmux unbind-key -T root C-q      # on return

Per-attach install/uninstall (no refcount) keeps the return path under 50 ms. A panic hook ensures the binding is cleaned up even if bosun dies unexpectedly — that path is exercised by a dedicated integration test.

Why a dedicated tmux socket

By default bosun runs on tmux -L bosun, which starts a separate tmux server owned by the bosun process. Two reasons:

  1. macOS Keychain lineage. Claude Code stores its auth tokens in the user's Keychain. macOS gates Keychain access by process tree. Bosun's tmux server is a child of bosun, which is a child of your login shell, so Claude sessions started inside bosun see your cached credentials. Sessions on a random long-lived tmux server started months ago by some other tool don't have that lineage and fail to authenticate.
  2. Isolation. Bosun never touches your other tmux sessions, bindings, or status bar. If you want the opposite — bosun managing your main tmux server — set BOSUN_TMUX_SOCKET=default.

Development

cargo run
BOSUN_LOG=info cargo run                       # tracing to stderr
cargo test                                     # unit + snapshot tests
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo test --features tmux-it                  # integration tests that spawn a real tmux

Snapshot tests use insta. After an intentional UI change:

cargo install cargo-insta     # once
cargo insta accept            # accept all new snapshots

Layout

src/
  main.rs                    entry point, panic hook, terminal setup
  app.rs                     AppState, central event loop, attach orchestration
  config.rs                  config.toml loader + env overlay + write_theme
  events.rs                  Command / AppMsg types
  store/                     SQLite-backed recents
  tmux/
    client.rs                tokio::process wrapper (all tmux I/O lives here)
    parse.rs                 pure parsers for tmux CLI output
    attach.rs                Ctrl-Q keytable + panic safety
    status_bar.rs            per-session status line management
    detector/                status detection (Claude, Codex, generic fallback)
    session.rs
  actors/
    tmux_actor.rs            owns TmuxClient, handles Commands
    control_client.rs        tmux -C push notifications
    input_actor.rs           crossterm event stream -> AppMsg
  ui/
    mod.rs                   draw(frame, state, theme)
    theme.rs                 Theme struct + built-in loader + user dir scan
    layout.rs
    session_list.rs          2-line rows (name + agent·path)
    preview.rs               live pane preview from capture-pane
    statusbar.rs
    modal/
      mod.rs                 modal stack + ModalResult enum
      new_session.rs
      recents.rs
      rename.rs
      confirm.rs
      theme.rs               theme picker with live preview
themes/                      15 built-in theme .toml files (embedded via include_str!)
tests/
  snapshot_session_list.rs
  integration_*.rs           real-tmux integration tests (feature = tmux-it)

License

MIT © 2026 Andy Miller