A left-side TUI for WezTerm that makes it easy to juggle a dozen Claude Code sessions at once.
Every tab is just ✳ Claude Code. You have no idea which tab is which
project. This fixes that.
- Sidebar pane: one keystroke opens a thin column on the left of the
current wezterm window that lists every tab, labeled by project name,
git branch, dirty flag, and the category you filed the project under
(e.g.
~/project/work/foo→work). - Live Claude state: detects whether each tab's Claude Code is currently running a turn, waiting for input, or asking for a permission / choice, by reading the pane's text buffer.
- Notification badges: when a background Claude tab finishes a task
(running → waiting), it gets a
★badge so you know without switching. Optionally firesnotify-sendfor a desktop alert. - Smart tab titles: tabs running Claude Code get auto-renamed from
✳ Claude Codeto1 joyfitso you can tell them apart even without the sidebar open. - Fuzzy search: press
/to filter tabs by project name. - New tab launcher: press
nto pick a project directory and launch a new Claude Code session. - Category grouping: press
gto toggle grouped-by-category view with dividers. - Emacs-style navigation:
C-n/C-p,j/k,1-9, or mouse click to jump to a tab. Enter activates the selection.
Requires Go 1.24+.
go install github.com/Andyyyy64/wezterm-claude-sidebar@latest...or from a clone:
git clone https://github.com/Andyyyy64/wezterm-claude-sidebar
cd wezterm-claude-sidebar
go build -o wcs .
sudo install wcs /usr/local/bin/wcs # or anywhere on $PATHCopy lua/sidebar.lua into ~/.config/wezterm/ (or somewhere on
WezTerm's Lua package path) and add to your wezterm.lua:
local wezterm = require("wezterm")
local sidebar = require("sidebar")
local config = wezterm.config_builder()
config.leader = { key = "x", mods = "CTRL", timeout_milliseconds = 1000 }
sidebar.apply(config, {
binary = "wcs", -- or absolute path
toggle_key = { key = "c", mods = "LEADER|CTRL" }, -- C-x C-c, this tab only
-- window-wide toggle: opens a mirror in every tab, so switching tabs
-- reads as one fixed sidebar (all mirrors share the single daemon)
window_toggle_key = { key = "P", mods = "CMD|SHIFT" },
width_cells = 44,
tab_title = true, -- also enhance tab titles
refresh_ms = 3000, -- daemon poll interval, ms
})
return configIf you run WezTerm on Windows but Claude Code inside WSL, install wcs
inside the WSL distro, not on Windows. The sidebar shells out to
wezterm.exe cli ... via interop, so it needs to talk to the Windows
binary from inside WSL. Export WEZTERM_BIN=wezterm.exe if PATH
resolution misses it.
The cursor runs through the spaces and the agents as one list: pressing up on the first agent moves onto the last space, and down off the last space comes back to the first agent.
| Key | Action |
|---|---|
C-n / j / ↓ |
cursor down |
C-p / k / ↑ |
cursor up |
Home |
jump to the first space |
G / End |
jump to the last agent |
Enter / o / Space |
on an agent: activate its tab · on a space: filter the agents to it |
1-9 |
jump directly to tab N |
| mouse click | activate clicked tab / filter clicked space |
| Key | Action |
|---|---|
/ |
fuzzy search — filter tabs by project name |
n |
new tab launcher — pick a project, start Claude |
g |
toggle category grouping view |
d |
close the selected tab (with y/n confirmation) |
r / C-r |
refresh now |
q / C-g / Esc |
close the sidebar |
wcs [flags]
-all-windows show tabs from every wezterm window, not just this one
-refresh int refresh interval, ms (default 1500)
-no-claude-detect skip reading pane text for state detection
-host-pane int wezterm pane id of the pane that opened the sidebar
-host-tab int wezterm tab id of the tab the sidebar was opened from
-daemon run the shared sidebar daemon (autostarted; see below)
-dump print the collected tab list once and exit (no TUI)
Every sidebar pane is a thin viewer: it draws frames pushed to it over
a unix socket (~/.cache/wcs/wcs.sock or the platform equivalent) and
forwards its keys, clicks, and resizes back. A single daemon process
owns the actual sidebar — the polling, the item list, the cursor — and
renders a frame per viewer size. That is what makes N open sidebars read
as one UI: every tab shows the same list and the same arrow, and a click
in any of them moves them all. It also means only one process ever polls
wezterm, no matter how many sidebars are open.
The first viewer that finds no daemon listening starts one (from its own
binary, passing its refresh/detect flags along); the daemon exits when
the last viewer is gone for a while, and retires itself when a viewer
from a newer install connects. Quitting (q) closes only the sidebar it
was pressed in.
The daemon shells out to wezterm cli list --format json to enumerate panes,
groups them by (window_id, tab_id), and for each tab probes all panes
to pick the best "face" (a Claude Code pane beats a plain shell, and
among Claude panes the most urgent state wins). Each tab is enriched
with:
- Project info —
git rev-parse --show-toplevelto find the repo root, thenrev-parse --abbrev-ref HEADandstatus --porcelain. Results are cached for a few seconds to keep polling cheap. - Category — the first path component under
~/project(configurable viaWCS_PROJECT_ROOT). - Claude state —
wezterm cli get-textof the pane, then a heuristic search for Claude Code's footer markers (bypass permissions,esc to interrupt,? for shortcuts).
All per-tab enrichment runs in parallel goroutines to minimize wall-clock time (~110ms without Claude detection, ~210ms with).
Every poll costs subprocesses (one cli list, one cli list-clients,
one cli get-text per real pane, plus cached git probes). Only the
daemon polls — viewers sit idle on a socket read — and its rate adapts
instead of burning -refresh around the clock:
- when the focused tab has no sidebar in it (nobody is looking at a mirror), polls run at 2× the interval, stretching to 8× while nothing changes at all; visible but idle backs off to 4× — any state change, focus move, keypress, or click snaps the rate back;
- while a turn is running the base rate always holds;
- desktop notifications fire once per event, since exactly one process is watching;
- the once-a-second elapsed-counter tick only runs while something is actually running.
Clicking or pressing Enter runs wezterm cli activate-tab --tab-id N.
MIT