Skip to content
Projets

yetidevworks/bosun has 48 GitHub stars and 6 forks. Star changes are not measured yet. Last checked 2026-11-10 22:13 UTC. Last push 2026-10-30.

Comment ces chiffres sont mesurés

Tmux-native orchestrator for AI agent sessions. Rust + ratatui.

What you can do

  • Live session list with live status detection (● running · ◐ waiting · ○ idle · ✕ error) refreshed across every managed session at the fast-preview cadence
  • Embedded live preview of the selected session — real PTY in the right pane, focusable in place via single-window mode
  • Tabs (multi-session containers) per sidebar row with a browser- style tab strip, Ctrl+T / + to add, D then a to nuke the whole container, per-tab status glyphs, and survival across tmux restart via @bosun_container_id
  • Sections for organizing sessions, collapsible, persisted in config.toml

Progression mesurée des étoiles

in 24h
Mise à jour retardée
in 7 days
Mise à jour retardée
in 15 days
Mise à jour retardée
in 1 month
Mise à jour retardée

Variations nettes sur des périodes qui se chevauchent, pas des totaux quotidiens. Les mesures absentes restent vides. Les étoiles indiquent l’intérêt, pas la qualité.

README.md

Original

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, Kimi Code, OpenCode, Qwen Code, 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. 28 built-in themes (17 dark and 11 light, listed under Themes), switched live with T.

What's new in 2.0

The 2.0 branch turns bosun from a session picker into a working surface. The preview pane is no longer a snapshot — it's a real embedded terminal you can click into and drive without leaving bosun.

  • Embedded terminal preview. The selected session renders live from a real PTY (portable-pty + vt100 + tui-term), parser-primed on switch so there's no scrollback replay animation. No more 1 fps snapshot polling for the focused row.
  • Single-window focus mode (s to toggle). Press Enter on a session and it opens inside the preview pane instead of going full-screen tmux. The sidebar stays visible the whole time. Ctrl+Q exits focus, same chord as the classic detach. A colored focus border marks whichever pane has the keyboard.
  • Two-way mouse navigation. Click a session row to jump to it and exit focus; click inside the embed to enter focus on it. Drag the divider to resize. Scroll wheel scrolls the sidebar.
  • Live per-session status. The ●◐○✕ glyphs in the sidebar now update at ~5 Hz across every managed session (not just the focused one), driven off the fast preview tick. Claude / Codex detectors recognize the prompt-box UI directly instead of scanning the whole capture for fragile substrings, which kills the "stale Thinking… pegs the glyph to Running" failure mode.
  • Sections. Group sessions into named, optional, collapsible buckets (g to create, Tab to collapse). Persisted in config.toml. Banner fonts cyclable per-section (f on a header) for visual distinction in the preview pane.
  • Editor key (E). With a session highlighted, press E to open the session's path in your configured editor (zed, code, subl, nvim, etc.). Set it once with bosun editor <cmd>.
  • Modify session (m). Open the new-session modal pre-filled from the highlighted session's stored spec — change the name, path, agent, or flags (e.g. add --resume after the fact). Save-only: the running agent keeps its current flags; the next Ctrl+R (restart) picks up the new spec.
  • Tabs inside a sidebar entry. Each sidebar row is now a container that can hold multiple tmux sessions ("tabs"), surfaced as a browser-style strip above the embed with a + button on the right. Single-tab containers behave identically to a session row. Ctrl+T (or click +) opens a slimmed-down new-session modal with the path locked to the container; the new session joins the container and survives a tmux server restart via the @bosun_container_id user option. Tab status glyphs render next to each pill so background tabs surface Running / Waiting state without focus; the sidebar row picks up a small accent dot when any background tab is busy. Shift+→ / Shift+← cycle tabs, Shift+↓ / Shift+↑ cycle sessions — same chord in both sidebar and focused modes. D kills the active tab (drops the container when the last tab goes); on a multi-tab container its prompt adds a to kill every tab at once.
  • Sidebar-order session cycle. Shift+↓ / Shift+↑ walk the next / previous live session in sidebar order (stable, not MRU-shuffled), in both sidebar and focused modes. Sidebar selection follows the embed switch automatically.
  • Input correctness for agents in the embed. DECCKM cursor-key application mode, bracketed paste forwarding (drag-drop an image → Claude sees [Image #N]), SGR 1006 mouse forwarding, modifyOtherKeys for Shift+Enter / Ctrl+Backspace / etc.
  • Self-update. bosun update swaps the binary in place from the latest GitHub release; bosun update --check just reports what would happen. bosun release-notes opens the changelog for the running version.

Features

  • Live session list with live status detection (● running · ◐ waiting · ○ idle · ✕ error) refreshed across every managed session at the fast-preview cadence
  • Embedded live preview of the selected session — real PTY in the right pane, focusable in place via single-window mode
  • Tabs (multi-session containers) per sidebar row with a browser- style tab strip, Ctrl+T / + to add, D then a to nuke the whole container, per-tab status glyphs, and survival across tmux restart via @bosun_container_id
  • Sections for organizing sessions, collapsible, persisted in config.toml
  • Create new bosun-managed sessions from a modal form: name, path, agent choice (Claude Code, Codex, Kimi Code, OpenCode, Qwen Code, plain terminal), and agent-specific options — every agent gets a New/Continue/Resume session radio (Continue reopens the working directory's last session, Resume opens the agent's session picker where one exists) plus its auto-approve flag (Claude skip-permissions, Codex/Kimi/Qwen --yolo, OpenCode --auto). Claude also has a Remote Control checkbox (--remote-control), so the session is reachable from claude.ai/code and the mobile app without typing /rc after it starts
  • Optional git worktree sessions — create a session in a fresh worktree on a new branch, then on kill choose to merge, remove, or keep it
  • 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
  • Quick-switch (/) — type-ahead session picker against name / agent / path
  • Session lifecycle: attach (Enter), rename (R), restart (Ctrl+R), modify (m), kill (D), open in editor (E)
  • In-progress feedback while a session operation is in flight — the affected row shows a ⟳ marker and a killing… / restarting… label; a create (which has no row yet) shows ⟳ creating <name>… in the status bar. Clears the moment the operation lands
  • 28 built-in themes (17 dark + 11 light) plus user themes from $XDG_CONFIG_HOME/bosun/themes/*.toml; live preview picker on T
  • Two-way mouse navigation: click rows to jump, click the embed to focus, drag the divider to resize
  • Config file at $XDG_CONFIG_HOME/bosun/config.toml with theme, session_prefix, tmux_socket, preview_tick_ms, single_window, editor, banner_font knobs (env vars still override)
  • One-key detach: Ctrl+Q inside any attach (full-screen or single-window focus) returns you to bosun without touching your tmux prefix or leaving stray bindings behind
  • Self-update via bosun update; release notes via bosun release-notes

Requirements

  • tmux 3.2 or newer (tested against 3.4 and 3.8) — bosun drives tmux but does not bundle it, and errors out at startup if it can't find it
  • Rust 1.80 or newer (only needed when building from source or installing via cargo)
  • macOS or Linux (Windows is not supported)

Installation

Bosun needs tmux on your PATH. If you don't already have it:

brew install tmux        # macOS
sudo apt install tmux    # Debian/Ubuntu

Homebrew (recommended)

brew install yetidevworks/bosun/bosun

Bosun for Mac

A native Mac app (Apple silicon, macOS 15 or later) runs over the same sessions as the TUI, with real terminals for every tab. It's signed and notarized, and its cask installs the bosun formula with it:

Bosun for Mac

brew install --cask yetidevworks/bosun/bosun-app

See macos/Bosun/README.md for what it does.

From crates.io

cargo install --locked bosun-tmux

[!IMPORTANT] Use --locked. Without it, cargo ignores Bosun's Cargo.lock and resolves dependencies fresh, and the build currently fails with failed to select a version for the requirement zip = "^6.0.0". Bosun's font dependency, retrofont, requires zip 6, and the only zip 6 release (6.0.0) has been yanked. --locked builds with the exact versions Bosun was tested with, which still work. This will go away once retrofont moves to a newer zip.

(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 --locked --path .   # or: make install

Pre-built binaries

Download from GitHub Releases.

CLI subcommands

Bosun runs the TUI when invoked with no arguments. The handful of subcommands all run synchronously and exit before any TUI / tmux machinery starts.

Command Action
bosun Launch the TUI
bosun update Download and install the latest release in place
bosun update --check Report whether an update is available; don't install
bosun release-notes Open the changelog entry for the running version
bosun editor Print the currently configured editor
bosun editor <cmd> Set the editor used by the E key (e.g. bosun editor zed)
bosun editor "" Clear the configured editor
bosun hook <agent> <event> Record an agent's state on its tmux pane. Run by the hooks bosun installs at launch (see Agent status from hooks), not by hand
bosun help / --help / -h Print usage
bosun --version / -V Print version

Key bindings

The full list is available at any time inside bosun with ? or h.

Keys that act on a session, or quit, are shifted (R, D, E, T, Q) and there's no vim-style j/k navigation, so typing into the sidebar when you meant to type into a session can't rename, kill or open anything.

Main list — navigation

Key Action
↑ / ↓ Move selection
Enter Attach (or focus, in single-window mode)
Tab Collapse / expand section (on a section header)
/ Quick-switch — type-ahead session picker
Mouse wheel Scroll session list
Click row Jump selection (and exit focus if currently inside the embed)
Drag divider Resize list / preview split

Main list — sessions

Key Action
n New session
R Rename selected session (on a header: rename the section)
Ctrl+R Restart — kill + recreate with the same spec
m Modify session (name, path, agent, flags) — applies on next Ctrl+R
D Kill active tab (on a header: delete the section; killing the last tab removes the container)
a in the D prompt Kill the whole container — every tab at once (multi-tab containers only)
E Open the session's path in your configured editor
Ctrl+L Redraw the screen and refresh the session list

Killing a session that lives in a git worktree opens a dialog with three choices: m merges the branch and removes the worktree, x removes the worktree but keeps the branch, and Enter (keep) leaves the worktree in place. A dirty (uncommitted) worktree is never removed.

Main list — tabs

Key Action
Ctrl+T Add a tab to the selected container (opens add-tab modal with path locked)
] / [ Cycle next / previous tab within the current container
Shift+→ / Shift+← Same as ] / [ — cycle tab in container
Click tab Switch active tab
Click + Open the add-tab modal

Main list — navigate

Key Action
Shift+↓ / Shift+↑ Cycle next / previous session in sidebar order (skips section headers and dead rows)

Main list — organize

Key Action
Ctrl+Shift+↑ / Ctrl+Shift+↓ / K / J Reorder within section (or move section block)
Ctrl+Shift+→ Move session to the next section
Ctrl+Shift+← Move session to the previous section
1 … 9 Move session to section N
0 Move session to the ungrouped bucket
g New section
f Cycle banner font (on a header: override the section's font)

Main list — settings

Key Action
s or , Settings panel — the config.toml options, editable in place
T Theme picker (↑/↓ live-preview, Enter applies + persists)
? / h Show the help cheat sheet
Q / Ctrl+C Quit

Inside an attached or focused session

Key Action
Ctrl+Q Detach back to bosun (same chord in classic full-screen attach and single-window focus)
Ctrl+V, then a key Send that key to the application once; press Ctrl+V twice to send Ctrl+V
Shift+→ / Shift+← Cycle next / previous tab within the current container
Shift+↓ / Shift+↑ Cycle next / previous live session in sidebar order. Sidebar selection follows automatically.
Click sidebar row Exit focus and jump to that row

Shortcut conflicts with an application

Press Ctrl+V, then the conflicting key to send that key directly to the application once. For example, Ctrl+V followed by Shift+Right navigates a Codex question without switching Bosun tabs. Ctrl+V twice sends a literal Ctrl+V. The footer shows “Send next key to app…” while waiting. In embedded sessions, clicking, pasting, or leaving the session cancels the pending shortcut. This also works in full-screen tmux attachments.

To free the arrows permanently, add this to config.toml and restart Bosun:

[keybindings]
previous_tab = "Ctrl+Shift+Left"
next_tab = "Ctrl+Shift+Right"
previous_session = "Ctrl+Shift+Up"
next_session = "Ctrl+Shift+Down"
send_next_key = "Ctrl+v"

Set an entry to "none" (or "") to disable it. Omitted entries keep their defaults: Shift+Left/Right for tabs, Shift+Up/Down for sessions, and Ctrl+V for send-next-key. Shortcut modifiers match exactly; Ctrl+Shift+arrows are no longer implicit aliases for Shift+arrows. The example above is also useful when a terminal strips Shift from the vertical arrows.

Supported keys are arrows, F1–F12, and letters with Ctrl and/or Alt, using Ctrl+, Alt+ (or Option+), and Shift+ modifiers. Shift with letters is rejected because ordinary terminals cannot reliably distinguish those chords. Ctrl+Q, Ctrl+B, and Ctrl+L (including Alt variants) are reserved. Ctrl+H/I/J/M are rejected because terminals report them as Backspace, Tab, or Enter. Invalid or duplicate bindings produce a log warning and fall back to the default mapping. These settings are file-only and survive changes made in the settings panel. The ? help screen shows the active mapping, including disabled shortcuts. In full-screen attachments, navigation shortcuts retain tmux's recent-session cycling behavior.

Preview pane (mouse)

Action Effect
Click inside the preview while unfocused Enter focus on the selected session
Click on the divider Start a divider drag

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 LCP
↑ / ↓ (in path field) Navigate filesystem dropdown
Esc (in path field) Dismiss dropdown so Tab advances
Space (on a checkbox) Toggle option
Enter Create session
Esc Cancel

The modal has a Create in git worktree checkbox (Space toggles it). When on, it reveals an editable branch field (prefilled from the session name, no / or spaces) and the session is created in a fresh worktree on that branch instead of the plain path.

Recents picker

Key Action
↑ / ↓ Navigate
Type Filter by name / agent / path
Enter Pre-fill the new-session form from the highlighted entry
Ctrl+D Delete the highlighted recent entry
Esc Close

Quick-switch (/)

Key Action
↑ / ↓ Navigate matches
Type Filter
Enter Attach to the highlighted match
Esc Cancel

Settings panel

s (or ,) opens a panel over the options that live in config.toml: theme, banner font, default agent, worktree location, single-window mode, group-in-tab-title, remove-exited-sessions, the live preview and agent status hooks. ↑/↓ pick a row, ←/→ (or space) change it, esc closes. There is no save step — each change applies immediately and is written to config.toml, so the panel and the file always agree.

Install-level settings (session_prefix, tmux_socket, the [agents] binary overrides, the [env] table) stay file-only: they are set once, and a wrong value is better caught by editing the file than by cycling a row.

A row whose setting comes from an environment variable is tagged env and is read-only — the footer names the variable — since anything written to config.toml would be overridden at the next launch anyway.

Theme picker

Key Action
↑ / ↓ / k / j Live-preview next / previous theme
Home / End Jump to first / last theme
Enter Apply + persist to config.toml
Esc Revert

Help dialog

Key Action
↑ / ↓ / k / j Scroll one line
PgUp / PgDn Scroll one page
Home / End Top / bottom
Esc / Enter / ? / h / q Close

Single-window mode

By default Enter on a session does a full-screen tmux attach — the sidebar disappears, you drive the session, Ctrl+Q brings the sidebar back. Familiar from earlier versions and from tmux attach itself.

Single-window mode (toggle with s, persisted to config.toml as single_window = true) keeps the sidebar visible. Enter switches the embedded preview into a writable, focused mode on the selected session and routes your keystrokes to its PTY. Ctrl+Q exits focus back to the sidebar without losing the view. Click the embed to focus in, click the sidebar to focus out — works in either direction.

The colored focus border around whichever pane has the keyboard tells you where typing will land. When you exit focus, the border moves to the sidebar.

Modifying a session

Press m on a live session to open the new-session modal pre-filled from its stored spec — name, path, agent, agent flags. Edit any field, hit Enter to save. Bosun rewrites the per-session @bosun_* tmux user options so the change persists; the recents picker also reflects the new spec on the next open.

The save is non-destructive: the running agent process keeps its existing flags. The next time you press Ctrl+R (restart), the new spec is what gets recreated — same code path Restart already uses. So the common "I forgot to launch Claude with --resume" recovery becomes m → cycle the session-mode field to Resume → Enter → Ctrl+R.

Modifying works the same whether you're focused in the embed or sitting on the row from the sidebar.

Status detection

Bosun classifies each managed session as Running, Waiting, Idle, or Error and renders the glyph in the sidebar. Detection runs on the fast preview tick (default 200 ms) for every managed session, not just the focused one, so a multi-agent dashboard reflects state in near real-time.

Detectors are stacked by priority (Claude > Codex > Kimi > OpenCode > Qwen > generic). Each looks at the bottom region of the visible pane capture — Claude's prompt box, Codex's working line — rather than substring-scanning the whole screen. Older "Thinking…" lines that scrolled past the prompt no longer pin the glyph to Running.

Transitions are smoothed:

  • → Running or → Waiting: instant. High-signal events; the user wants to see "agent woke up" or "agent wants my input" with no delay.
  • → Idle: requires two consecutive matching polls. Filters the brief quiet windows between agent bursts so the Running glyph doesn't flicker off mid-response.

Agent status from hooks

Guessing state from a pane's title and screen is never fully reliable, so agents that support hooks report their own state. Bosun launches them with hooks that run bosun hook <agent> <event>, which writes the state onto the session's tmux pane as @bosun_state (running, waiting, done or idle), @bosun_state_at (epoch ms) an optional @bosun_state_detail such as permission: Bash, and @bosun_background, the number of background agents a turn is still waiting on. The status poll reads those in the same tmux list-sessions call it already makes. A hook takes a few milliseconds, prints nothing and always exits 0, so it can't break the agent.

Claude Code is the agent with hooks today. Bosun never edits ~/.claude/settings.json or a project's .claude/ files: it writes its own settings file with just the hooks (in bosun's data dir, under hooks/) and adds --settings <that file> to the launch command, including on restart and resume. Claude merges hooks from every settings source, so your own hooks keep running. If your extra args already pass --settings, bosun leaves yours alone and that session falls back to detection. The mapping:

Claude Code event Status
SessionStart Idle
UserPromptSubmit, PreToolUse, PostToolUse Running (a tool running after a permission prompt ends the wait)
PermissionRequest, Notification (permission prompt, MCP input request), PreToolUse for AskUserQuestion / ExitPlanMode Waiting, with what it's waiting on: permission: Bash, question (AskUserQuestion, whichever event reports it), plan approval or input requested. A permission notification that doesn't name its tool keeps the detail already written for that wait
Notification (idle_prompt) Running or Waiting settle to Idle; Done is left alone
Stop Done (shown until you look at the session, as before)
Stop with background subagents or workflows still at work Running, with background agent as the detail, until they report back and the turn that follows ends with none left. Background shells and monitors don't count
SessionEnd state cleared

Hook state wins over the detectors, and the detectors stay as a cross-check for what hooks miss. Claude fires no hook when you interrupt it with Esc, so a Running state goes back to the detector once Claude shows its Interrupted line, as does a Running state on a pane that has printed nothing for a minute, unless background agents are still at work (the conversation sits still at its prompt while they run, and Claude's idle notice settles one that never reports back). A Done or Idle report goes back to the detector while Claude's working line (✶ Photosynthesizing… (1m 49s …)) is on screen, which happens when one of your own Stop hooks tells Claude to keep going. Hook state is ignored once the agent has exited to the shell, and a permission prompt that Claude's detector sees on screen beats a hook's Running. Hook-reported changes show at once, without the two-poll smoothing. When a session is waiting, the section overview shows what it's waiting on next to its row.

The other agents keep their detectors for now. Codex's hooks need a trust flag that would also run untrusted project hooks, and its notify setting would replace yours. OpenCode 2.x only reads injected config in a private-server mode that changes how it runs. Qwen Code's only injection point is the admin-level system settings file. Kimi Code reads hooks only from its user config file.

Turn it off with agent_hooks = false in config.toml, BOSUN_AGENT_HOOKS=0, or the "agent status hooks" row in the settings panel. Sessions pick up the change on their next launch or restart. Set BOSUN_HOOK_LOG=/path/to/file in bosun's environment to log every hook invocation when checking which events an agent fires.

Mouse interaction

Bosun captures the mouse in the outer terminal (SGR 1006) and routes events itself. The big interactions:

  • Click a session row → jump selection to it. If you were focused in the embed, the click also exits focus.
  • Click inside the preview while unfocused → enter focus on the selected session. The triggering click isn't passed through into the embed (macOS click-to-focus convention); subsequent clicks under Focused mode are.
  • Drag the divider → resize the list / preview split. The position is persisted to config.toml as divider_x.
  • Scroll wheel over the sidebar → scroll the list.
  • Inside a focused embed → mouse events are forwarded to the inner app via SGR 1006 (when the inner app has mouse tracking enabled), except when you're mid-drag on the divider — that drag completes even when the cursor crosses into the preview pane.

Themes

28 themes ship built in (17 dark, 11 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. The list scrolls on a short terminal.

Dark:

  • opencode (default)
  • bosun (the app icon's navy, cream and mint)
  • tokyonight
  • dracula
  • catppuccin-mocha
  • one-dark-pro
  • ayu-mirage
  • nord
  • gruvbox-dark
  • rose-pine
  • github-dark
  • kanagawa-wave
  • everforest-dark
  • flexoki-dark
  • monokai-pro
  • oxocarbon
  • poimandres

Light:

  • github-light
  • one-light
  • solarized-light
  • ayu-light
  • quiet-light
  • kanagawa-lotus
  • everforest-light
  • flexoki-light
  • catppuccin-latte
  • rose-pine-dawn
  • tokyonight-day

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, focus border
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"

A theme file can also carry an optional [terminal] table. The TUI ignores it; Bosun for Mac paints its terminals with it (see macos/Bosun/README.md). It has background, foreground, cursor, selection_bg and selection_fg as hex colors, a palette of exactly 16 hex colors (ANSI 0-7, then bright 8-15) and an optional ghostty name for reference. A table that doesn't parse is ignored, and the theme still loads without it.

[terminal]
background     = "#0b0d12"
foreground     = "#e6e9ef"
cursor         = "#7c5cff"
selection_bg   = "#1e2433"
selection_fg   = "#e6e9ef"
palette        = [  # 0-7 normal, 8-15 bright
  "#25272d", "#ff5d6b", "#62d98c", "#f4c169", "#6ec8ff", "#b692b5", "#68d0c6", "#e6e9ef",
  "#7c8495", "#ff7a86", "#7ee0a1", "#f6cc84", "#88d2ff", "#c4a6c2", "#83d9d0", "#ebedf2",
]

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"
theme_light       = "github-light"   # Bosun for Mac only: theme while macOS is in Light appearance (unset: `theme`)
theme_dark        = "tokyonight"     # Bosun for Mac only: theme in Dark appearance. The TUI always uses `theme`.
session_prefix    = "bosun-"     # bosun only manages sessions with this prefix
worktree_location = "subdir"     # where "create in worktree" puts new worktrees: "subdir" (<repo>/.worktrees/<branch>) or "sibling" (<repo>-<branch>)
tmux_socket       = "bosun"      # dedicated tmux -L socket; "default" uses your shared socket
divider_x        = 50           # saved automatically when you drag the list/preview divider
preview_tick_ms  = 200          # fast-preview / live-status cadence; 0 disables the fast tick
max_fps          = 60           # ceiling on repaints per second; 0 turns pacing off entirely.
                                # Every byte an embedded session writes is an event, so a pane that
                                # scrolls continuously (a build, an agent streaming an answer) would
                                # otherwise drive the draw loop as fast as bosun can paint. Bursts
                                # collapse into one frame; costs at most ~16ms at 60fps.
single_window    = true         # `s` key persists this; Enter focuses in-place instead of full-screen attach
embed_enabled    = true         # set false to fall back to the polled-snapshot preview
show_group_in_title = false      # prefix grouped sessions as "group/session" in tab pills and terminal title
hide_single_tab_strip = false    # true draws the tab strip only for rows with 2+ tabs; a single tab's
                                # name moves to the status bar. Ctrl+T still adds a tab.
editor           = "zed"        # set via `bosun editor <cmd>`; used by the `E` key
banner_font      = "newsx"      # section banner font; cycled with `f` on a header
default_agent    = "opencode"   # agent preselected for new sessions; defaults to "claude"
remove_dead_sessions = false    # true drops a session's sidebar row when its tmux session ends,
                                # instead of keeping it as an "exited" row that R can restart.
                                # Rows that were already gone when bosun started (tmux server
                                # went away with the machine) are kept either way.
agent_hooks      = true         # agents with hook support report their own status (Claude Code today);
                                # false leaves status to pane detection. Applies on next launch.

# Optional per-agent binary overrides. The value replaces the agent's
# binary in the launch command, so you can point an agent at a wrapper
# script (e.g. one that unsets GIT_EXTERNAL_DIFF before exec'ing the
# real binary) or a differently-named install. Have the wrapper `exec`
# the real binary so status detection still sees the agent process.
[agents]
opencode = "/Users/me/bin/opencode-wrapper"
codex    = "codex-nightly"

# Optional extra environment for every new session's shell, passed to
# tmux as `-e KEY=VALUE`. Strings, numbers and booleans are accepted.
[env]
OPENCODE_CLI_CONFIG_CONTENT = '{"tabs":{"mode":"off"}}'

Detecting a bosun session

Every session bosun creates has BOSUN=1 in its environment, so a shell rc file or a wrapper script can tell it is running inside bosun. Check that instead of looking for bosun in $TMUX: the socket name comes from tmux_socket and differs between installs (default puts bosun on your shared server).

if [ -n "$BOSUN" ]; then
  export OPENCODE_CLI_CONFIG_CONTENT='{"tabs":{"mode":"off"}}'
fi

For a fixed value you don't need the check at all: put it in the [env] table and bosun sets it for you. BOSUN itself is reserved and can't be overridden there. The variables are set when a session is created, so existing sessions pick them up only after they are killed and started again (restart reuses the same shell).

Sections, per-section font overrides, sidebar membership, session history, and recent-sessions metadata are also persisted under [sidebar], [session_history], etc. — bosun writes these automatically, you don't need to hand-edit them.

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_PREVIEW_TICK_MS preview_tick_ms
BOSUN_MAX_FPS max_fps (0 = no repaint pacing)
BOSUN_SINGLE_WINDOW single_window (1 / true to enable)
BOSUN_EMBED embed_enabled (0 / false to disable)
BOSUN_SHOW_GROUP_IN_TITLE show_group_in_title (1 / true / yes / on to enable)
BOSUN_HIDE_SINGLE_TAB_STRIP hide_single_tab_strip (1 / true / yes / on to enable)
BOSUN_DEFAULT_AGENT default_agent
BOSUN_REMOVE_DEAD_SESSIONS remove_dead_sessions (0 / false / off / no to disable)
BOSUN_AGENT_HOOKS agent_hooks (0 / false / off / no to disable)
BOSUN_LOG Tracing filter, e.g. BOSUN_LOG=info

An environment variable wins over config.toml, which makes it a way to run bosun differently for one launch or one machine without editing (or having bosun rewrite) the file:

BOSUN_DEFAULT_AGENT=codex BOSUN_THEME=nord bosun

Any setting pinned this way is tagged env in the settings panel and can't be changed there — the panel would write to config.toml and the variable would just override it again on the next launch.

How Ctrl+Q detach works

For a full-screen tmux attach, bosun installs a temporary root-table binding just before handing the terminal to tmux:

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.

In single-window focus mode the chord works the same way from the user's perspective, but the routing is different: bosun never gives up the terminal, so Ctrl+Q is intercepted in bosun's own key handler and triggers exit_focus() directly.

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.

Troubleshooting

If new sessions close as soon as they start, it's usually because tmux was updated while Bosun's tmux server was still running the old version: quit Bosun and run tmux -L bosun kill-server (it ends the sessions on that server). docs/troubleshooting.md has the checks for this and other problems, and how to get a debug log.

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
make install                                   # release build + replace ~/.local/bin/bosun

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, CLI dispatch
  lib.rs                     module root (re-exports for the binary + tests)
  app.rs                     AppState, central event loop, attach + focus orchestration
  config.rs                  config.toml loader + env overlay + writers
  events.rs                  Command / AppMsg types
  sidebar.rs                 sections + ungrouped membership model
  error.rs
  commands/
    update.rs                self-update via GitHub releases
    release_notes.rs         open changelog for the running version
    editor.rs                get/set the editor used by the `E` key
  store/
    mod.rs / recents.rs      SQLite-backed recents
  tmux/
    client.rs                tokio::process wrapper (all tmux I/O lives here)
    control.rs / control_client.rs   tmux -C push notifications
    parse.rs                 pure parsers for tmux CLI output
    attach.rs                Ctrl-Q keytable + panic safety
    status_bar.rs            per-session status line management
    detector/                live status detection
      mod.rs                 registry + ANSI strip helper
      claude.rs              prompt-box-aware Claude Code detector
      codex.rs               Codex CLI detector
      kimi.rs                Kimi Code detector
      opencode.rs            OpenCode detector
      qwen.rs                Qwen Code detector
      generic.rs             activity-age fallback
    session.rs
  actors/
    tmux_actor.rs            owns TmuxClient, fast-tick status + preview, do_refresh
    input_actor.rs           crossterm event stream -> AppMsg
  ui/
    mod.rs                   draw(frame, state, theme, embed, embed_focused)
    layout.rs                rect math + draggable divider
    theme.rs                 Theme struct + built-in loader + user dir scan
    session_list.rs          2-line rows (name + agent·path), click-to-select
    preview.rs               selected-session preview (embed when live, polled otherwise)
    embed_terminal.rs        portable-pty + vt100 + tui-term embed
    key_encode.rs            key -> bytes (DECCKM, modifyOtherKeys)
    mouse_encode.rs          mouse -> SGR 1006 bytes
    statusbar.rs
    banner.rs                TDF banner font picker
    section_preview.rs       per-section dashboard rendered when a header is selected
    modal/
      mod.rs                 modal stack + ModalResult enum
      new_session.rs / recents.rs / rename.rs / confirm.rs
      section.rs             new/rename section dialog
      quickjump.rs           `/` type-ahead picker
      theme.rs               theme picker with live preview
      help.rs                `?` keyboard cheat sheet
  util/
    hysteresis.rs            status transition smoother
    collision.rs             session-name collision helper
themes/                      28 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

Explorer des projets similaires