Introduction
wsx is a terminal UI that runs Claude Code, Pi, Hermes, Codex, or oh-my-pi sessions in their own git worktrees. Use it to launch, watch, and steer many agent sessions in parallel from one dashboard, including several agents working together in one workspace.
Key features
- Parallel sessions in worktrees: each workspace gets its own branch, worktree, and agent session. See Dashboard.
- Multiple coding agents: pick Claude, Pi, Hermes, Codex, or oh-my-pi per workspace. See Coding agents.
- Multi-agent workspaces: run several agents in one worktree and have them message each other. See Multi-agent workspaces.
- Attention alerts: status glyphs, a terminal bell, and a live activity line show which sessions need you. See Status indicators.
- Agent status and recaps: agents report their status and a goal/state/next recap with
wsx statusandwsx recap. See Status and recap. - Project manager digest: one pane summarizes what every workspace is for and what's next. See Project manager digest.
- Configurable detail bar: up to four containers of built-in or custom modules. See Detail bar.
- Tags and hiding: label workspaces and hide them by tag or PR state. See Tags and hiding.
- Split panes and saved layouts: watch other workspaces while attached. See Updates panel and split panes.
- Pinned commands and prompt tags: fire saved prompts with
Ctrl-x <digit>and insert XML-wrapped prompt sections. See Pinned commands and Prompt tags. - Bundled agent skills:
wsx,agent-review, andhandoff. See Agent skills. - Editor, terminal, and diff hooks: open a workspace in your tools with one key. See Editor, terminal, and diff.
- Editor-hosted agents: give an agent in your editor the workspace's context with
wsx context. See Editor-hosted agents. - Related repos: let agents read sibling repos without editing them. See Related repos.
- Remote and shared workspaces: attach over ssh, or run agents in tmux on this or another host. See Remote access and Shared workspaces.
- Desktop widgets: a waybar module, a KDE Plasma applet, and a macOS menubar plugin. See Desktop widgets.
- Less setup per workspace: auto-rename branches from your first prompt, and run per-repo setup and archive scripts. See Auto-rename and Setup and archive scripts.
Where to go next
- New to wsx: Installation, then Quick start.
- Looking up a key: Keybindings.
- Looking up a command or setting: CLI reference and Settings.
Getting started
Install wsx, add a repository, and open your first workspace.
- Installation: Homebrew, cargo-binstall, Nix, prebuilt binaries, or source.
- Quick start: a first session, and wiring up your editor, terminal, and diff tool.
Installation
Every method installs the same wsx binary. Pick one.
Prerequisites:
giton your PATH.- The GitHub CLI (
gh), optional, for pull request numbers and review marks on the dashboard.
Homebrew
macOS and Linux, Intel and ARM:
brew tap bakedbean/workspacex https://github.com/bakedbean/workspacex
brew install bakedbean/workspacex/wsx
Use the fully qualified name. A bare brew install wsx stops with a tap trust error.
Upgrade:
brew update && brew upgrade bakedbean/workspacex/wsx
cargo-binstall
cargo-binstall downloads the prebuilt binary, with no Rust build:
cargo binstall --git https://github.com/bakedbean/workspacex wsx
Nix
Run once without installing:
nix run github:bakedbean/workspacex
Install into your profile (Nix before 2.34 uses install instead of add):
nix profile add github:bakedbean/workspacex
Add to a NixOS or home-manager flake:
{
inputs.wsx.url = "github:bakedbean/workspacex";
# package: inputs.wsx.packages.${pkgs.stdenv.hostPlatform.system}.default
}
Prebuilt binaries
Download a tarball from the
releases page, verify it
against its .sha256 file, and install it:
f=wsx-<version>-<target>.tar.gz
echo "$(cat "$f.sha256") $f" | shasum -a 256 -c -
tar xzf "$f"
sudo install -m 755 wsx-<version>-<target>/wsx /usr/local/bin/wsx
| Platform | Target |
|---|---|
| macOS, Apple silicon | aarch64-apple-darwin |
| macOS, Intel | x86_64-apple-darwin |
| Linux, ARM64 | aarch64-unknown-linux-gnu |
| Linux, x86-64 | x86_64-unknown-linux-gnu |
The Linux binaries need glibc 2.35 or later.
From source
Requires Rust 1.88 or later.
git clone https://github.com/bakedbean/workspacex
cd workspacex
cargo install --path .
Quick start
This walkthrough takes you from install to a running agent session, then connects wsx to your editor, terminal, and diff tool.
-
Install wsx. On macOS or Linux with Homebrew:
brew tap bakedbean/workspacex https://github.com/bakedbean/workspacex brew install bakedbean/workspacex/wsxFor other methods, see Installation.
-
Register a repository:
wsx repo add /path/to/your/repo -
Launch the dashboard:
wsx -
Press
nto create a workspace: a new branch and worktree with its own agent session. PressN(Shift-N) instead to start the agent in permissive mode, which skips permission prompts. -
Press
enterto attach. The agent (Claude Code by default) runs inside the worktree. -
Press
Ctrl-x dto detach back to the dashboard. The session keeps running. -
Tell wsx which tools the
[e]editor,[t]terminal, and[v]diff keys launch. For example, Neovim in Alacritty:wsx config set editor_cmd "alacritty --working-directory={path} -e nvim" wsx config set terminal_cmd "alacritty --working-directory={path}" wsx config set diff_cmd "alacritty --working-directory={path} -e nvim -c 'DiffviewOpen {base}...HEAD'"{path}is the worktree and{base}is the diff base ref. Wrap terminal editors likenvimin a terminal command; GUI editors likecodework directly. See Editor, terminal, and diff for placeholders and fallbacks.
Next steps
- Dashboard and Keybindings
- Attached view
- Coding agents, to switch from Claude Code
- Settings
Dashboard
The dashboard is wsx's home screen: one row per workspace, grouped by repo, showing each agent's status, PR, and changes. Select a row and press enter to attach to its agent.
Keybindings
Every key in the wsx TUI, grouped by where you press it. Capital letters mean Shift plus the letter.
Dashboard
Moving around
| Key | Action |
|---|---|
j / k (or arrows) | Move the selection |
h / l | Fold / unfold the repo; l on a workspace attaches |
z z | Toggle the repo's fold |
z a (or z r) | Unfold every repo |
z M (or z m) | Fold every repo |
/ | Filter the rows; esc clears |
esc | Clear a status filter set by clicking the header tally |
o | Sort by recency or by status (saved) |
G | Group by repo or by attention |
Tab | Focus the detail-bar reply, then the digest |
q | Quit; stops direct sessions (shared ones keep running) |
q asks first if a workspace is still being created or archived.
Workspaces
| Key | Action |
|---|---|
enter / i | Attach; on a repo header, create a workspace there |
n | Create a workspace in the selected repo |
N | Create one in permissive mode (skips agent permission prompts) |
S | Create a tmux-shared workspace |
T | Toggle the workspace between shared and direct |
d | Archive |
e / t / v / g | Open in editor, terminal, diff, or lazygit |
? | Workspace actions card |
# | Edit tags |
C | Pick the row's name color |
K | Processes under the worktree |
Ctrl-x 1–9 | Send a pinned command to the workspace |
Ctrl-x a | Agents panel |
Repos and panes
| Key | Action |
|---|---|
K / J on a repo | Move the repo up / down (saved) |
s | Repo settings |
, | Global settings |
F | Hide rules for the repo |
H | Remote hosts |
p | Toggle the digest |
r (digest open) | Refresh git and PR data |
enter / l on a hidden group | Show its workspaces |
h on a hidden group | Collapse it |
Detail-bar reply
| Key | Action |
|---|---|
Tab | Focus the reply input |
enter | Send to the agent (starts it if needed) |
esc | Discard and return to the list |
Switching panels
Panels open in a drawer at the bottom of the screen.
- A panel's key opens it and replaces the open panel. Pressing the open panel's key again closes it. A key with nothing to open (
?on a repo header) leaves the panel alone. - While a panel takes text (tag editor, name-color filter, name fields, a
/filter), keys type. In the process listKkills; in the updates panelssplits. Ctrl-xchords that open panels work from any panel. Progress cards for remote work ignore panel keys until they finish or you pressesc.
Dialogs
| Key | New workspace | Confirm (archive, quit, share) |
|---|---|---|
enter | Create (blank name generates one) | — |
y / n | — | Confirm / cancel |
esc | Cancel | Cancel |
↑ / ↓ | Switch between name and tags | — |
Tab | Cycle the agent | — |
Ctrl-s | Toggle shared | — |
Workspace actions card (?)
| Key | Action |
|---|---|
r | Rename the workspace and its branch |
o | Open the setup log |
x | Cancel a create in progress |
C / # | Name color / tags |
j / k | Move the selection, card stays open |
e t v g enter | Act on the workspace and close |
? / esc | Close |
Setup log viewer (? then o)
Shows the setup script's output: live while the workspace is being created, the saved log afterwards. Stderr lines are marked !. Only the last 2000 lines show.
| Key | Action |
|---|---|
j / k (or arrows) | Scroll one line |
PageUp / PageDown | Scroll ten lines |
g / Home | Jump to the start |
G / End | Jump to the end |
esc / enter | Close; setup keeps running |
Panels opened from the dashboard
| Panel | Keys |
|---|---|
Tag editor (#) | type + enter add; arrows + space toggle; esc close |
Hide rules (F) | j/k move; space toggle; esc close |
Name color (C) | arrows move; type to filter; enter apply; Delete clear; esc close |
Repo settings (s) | j/k move; enter edit; d clear the repo's value; esc close |
Global settings (,) | j/k move; enter toggle, cycle, or edit; d reset to default; esc close |
Processes (K) | arrows move; r run a command; k SIGTERM; K SIGKILL; esc close |
Remote hosts (H) | j/k move; enter open; esc close |
| Remote workspace list | enter attach or share; n/N new; r refresh; esc close |
Digest (p, focused)
| Key | Action |
|---|---|
j / k (or arrows) | Move the selection |
enter | Attach to the workspace |
/ | Filter by name |
r | Refresh git and PR data |
esc | Clear the filter, or return focus |
Tab | Return focus to the list |
p / q | Close the digest |
Attached view
Keys go to the agent, except chords after Ctrl-x. Pressing Ctrl-x alone shows a menu; pick with arrows and enter, or press the letter. wsx drops Ctrl-z and Ctrl-d so a slip can't suspend or exit the agent.
| Key | Action |
|---|---|
Ctrl-x d | Close the pane; detach if it's the last one |
Ctrl-x D | Save the split layout, then detach |
Ctrl-x arrows | Move focus between split panes |
Ctrl-x u | Updates panel |
Ctrl-x a | Agents panel |
Ctrl-x e / t / v / g | Editor, terminal, diff, lazygit |
Ctrl-x k | Processes |
Ctrl-x < | Prompt tag picker |
Ctrl-x # | Workspace tags editor |
Ctrl-x R | Workspace recap (goal / state / next); j/k scroll; esc closes |
Ctrl-x 1–9 | Send a pinned command |
Ctrl-x x | Send a literal Ctrl-x to the agent |
Ctrl-x esc | Dismiss the menu |
Ctrl-x q w r y i o p s h j | Switch the pane to another agent (multi-agent workspaces) |
A workspace attached from a remote host takes only Ctrl-x d, Ctrl-x <, Ctrl-x #, Ctrl-x x, and Ctrl-x 1–9; Ctrl-x alone shows a menu of those.
Panels opened from the attached view
| Panel | Keys |
|---|---|
Updates (Ctrl-x u) | j/k move; enter/l attach, or expand a hidden group; h fold a hidden group; v side-by-side split; s stacked split; o/G sort and group; / filter; esc close |
Agents (Ctrl-x a) | j/k move; enter add the selected kind; a add one of each; x remove the newest; esc close |
Prompt tag (Ctrl-x <) | arrows move; 1–9 pick; type to filter or name a new tag; enter write the body; Ctrl-d delete the tag; esc close |
| Prompt tag body | Ctrl-s insert; esc back |
Status indicators
Each dashboard row fits a workspace's agent status, PR, processes, and changes on one line, so you can see which workspace needs you without attaching.
Row columns
Left to right:
| Column | Shows |
|---|---|
| Agent strip | One colored bar per agent, primary rightmost |
| Status | A colored bar and a status glyph (see below) |
| Branch | ⎇ colored by PR state, the name, tags, and badges |
| PR | ⏺ #241 open plus a review mark |
| Processes | ● 2p for two tracked processes, · for none |
| Diff | +N −N lines committed on the branch since its base |
| Message | Status word, then the agent's recap or its latest activity |
| Age | Time since the last activity |
The name is bold, and warn-colored for a workspace created in permissive mode unless you picked a name color with C. In the message column, ▸ means the agent reported its status with wsx status and └ means wsx inferred it.
Status glyphs
| Glyph | Word | Meaning |
|---|---|---|
? | asking | The agent asked a question or waits on a permission prompt |
! | stalled | No output for 60 seconds mid-turn |
| spinner | thinking | The agent is working |
| spinner | waiting | The session is running but quiet for over 30 seconds |
✓ | done | The agent finished its turn |
· | idle | No session, or a session you haven't prompted yet |
The dashboard's top line tallies how many workspaces hold each status, listing only the ones in use: ? 2 question ⠋ 3 thinking · 4 idle. The tally counts every workspace, including ones a filter hides. A theme whose header leaves out $statuses shows the tally on its own row below the header instead, with every status listed, zeros dimmed. That row isn't clickable.
Filtering by status
Click a status in the header tally to show only the workspaces in that status. Clicking ? 2 question lists just those two rows. While the filter is on, that status is highlighted and the others are dimmed. If the last workspace leaves that status, its item stays in the tally at 0, so you can still see what's filtering the list and click to clear it. The filter lasts until you clear it or quit wsx.
- Click the same status again, or press
esc, to clear the filter. - Click a different status to switch to it. Only one status filters at a time.
- A status filter combines with a
/filter: both must match. With both on, the firstescclears the typed filter and the second clears the status. - Like a
/filter, a status filter shows rows that hide rules would otherwise hide, so the list matches the tally.
In a workspace with several agents, a peer's bar in the agent strip turns into a spinner while that agent prints output. A + stands in for peers that don't fit.
Branch badges
| Badge | Meaning |
|---|---|
| spinner | Being created or archived |
⚙! | Setup script failed; the workspace still works. ? then o shows the log |
⚙? | Setup was cancelled |
✗ | The worktree was never created. Archive it with d |
✉! | A message to the agent couldn't be delivered |
◇ | tmux-shared: green while its session runs, red when it doesn't |
| columns icon | A saved split layout (nerd fonts only) |
PR chip
The chip and the ⎇ glyph take the PR state's color: no PR, draft, open, conflicts, merged, or closed. Open PRs also carry a review mark:
| Mark | Meaning |
|---|---|
✓ | Approved |
✗ | Changes requested; a number counts unresolved threads |
◌ | Review required |
PR data needs gh, installed and authenticated.
Alerts
When an agent asks a question, waits on a permission prompt, finishes, or stalls, wsx rings the terminal bell. The workspace also moves to the front of the attached view's workspace strip until you attach to it.
- Each state has its own bell pattern, set with the
notification_bell_*settings. wsx config set notifications offturns off the bell and the flag.- Workspaces that were already waiting when wsx started don't ring.
- While wsx runs, the bell doesn't ask the window manager for urgency, so on Hyprland it won't switch your desktop workspace.
See Settings for the allowed values.
Detail bar
The detail bar runs along the bottom of the dashboard and shows more about the selected workspace: its recap, recent chat, processes, and changed files. It ends in a reply input, so you can answer an agent without attaching.
Layout
The bar holds 1–4 equal-width columns, called containers. Each container stacks one or more modules:
| Module | Shows |
|---|---|
session_summary | The recap (goal, state, next), tool counts, status, model |
recent_chat | The latest messages between you and the agent |
processes | Tracked processes in the worktree |
recent_files | Files the agent edited recently, with line counts |
session_summary falls back to the session's first prompt when the agent hasn't written a recap. Scroll a container with the mouse wheel.
Replying
Press Tab to focus the reply input, type, and press enter to send it to the agent. wsx starts the agent first if it isn't running. esc discards the draft.
Configuring
The detail_bar_config setting holds a JSON value. Every field is optional, and missing fields take the defaults:
{
"visible": true,
"height": { "percent": 30, "min_rows": 8, "max_rows": 18 },
"containers": [["session_summary"], ["recent_chat"], ["processes", "recent_files"]],
"bottom_rule": true
}
| Field | Default | Effect |
|---|---|---|
visible | true | false hides the bar and its reply input |
height.percent | 30 | Height as a percent of the terminal, 5–80 |
height.min_rows | 8 | Minimum height in rows, 4–40 |
height.max_rows | 18 | Maximum height in rows, 4–60 |
containers | as above | Up to 4 columns of module IDs; [] leaves a column empty |
bottom_rule | true | Draw a rule under the reply row; false saves a row |
When every container is empty ([[], [], []]), the bar shrinks to its header and reply input. An unknown module ID shows a [unknown: <id>] placeholder.
Set or edit the global value:
wsx config edit detail_bar_config # open in $EDITOR
wsx config set detail_bar_config "" # back to the defaults
Examples:
# Taller bar on a big monitor
wsx config set detail_bar_config '{"height": {"percent": 45, "max_rows": 24}}'
# One full-width chat column
wsx config set detail_bar_config '{"containers": [["recent_chat"]]}'
# Hide the bar
wsx config set detail_bar_config '{"visible": false}'
wsx config set rejects malformed JSON and keeps the old value. Out-of-range heights are clamped.
Per-repo override
To change the bar for one repo, press s on the dashboard, select detail_bar_config, and press enter. $EDITOR opens on the repo's override. Write only the fields you want to change, for example { "visible": false }. Press d on the row to clear the override.
visible, height, and bottom_rule override field by field. containers replaces the global list.
Project manager digest
The digest is a pane below the workspace list with a card for every ready workspace, grouped by repo. Use it to review what each agent is doing and what's left without attaching to each one.
Press p to open it. It opens focused and needs no setup.
Within each repo, cards are ordered blocked first, then waiting, then the rest, oldest activity first.
What a card shows
- Header: workspace name, branch, agent, and the agent's reported status, such as
[blocked 4s], with its message. With several agents, it shows the most urgent status. - Recap: the agent's
goal:,state:, andnext:lines. A workspace without a recap showsno recap yet. - Facts: git counts (
↑ahead ↓behind ~modified ?untracked), the PR chip,active <age> ago, andrecap stalewhen the agent has been active since its last recap.
Agents write recaps and statuses with wsx recap and wsx status. See Status and recap.
Keys
| Key | Action |
|---|---|
p | Open or close the digest |
j / k (or arrows) | Move the selection |
enter | Attach to the workspace |
/ | Filter cards by workspace name |
esc | Clear the filter, or return focus to the list |
Tab | Cycle focus: list, reply input, digest |
r | Refresh git and PR data |
q | Close the digest |
q closes the digest only while the digest has focus. With the list focused, q quits wsx and stops its direct (non-shared) sessions.
Tags and hiding
Tags are short labels on workspaces, like parked or spike. Use them, and each PR's state, to search the dashboard and to hide workspaces you're not working on.
Tagging workspaces
Press # on a workspace, or Ctrl-x # while attached to one, to open the tag editor:
| Key | Action |
|---|---|
type + enter | Add the tag |
arrows + space | Toggle a listed tag |
esc | Close |
Typing filters the list, which offers every tag used in wsx. Changes save as you make them.
Attached to a workspace on a shared host, Ctrl-x # edits its tags on that host. The editor offers the tags used by that host's listed workspaces. Each change shows at once and runs wsx workspace tag there over ssh, one at a time in the order you made them; they finish even if you close the editor or detach. If a change fails, the editor shows the host's actual tags and why it failed.
To tag a workspace as you create it, press ↓ in the new-workspace form and type its tags, separated by spaces or commas. An invalid tag keeps the form open and says which one. Creating a workspace with the cursor on a hidden tag group, or on a workspace inside one, fills in that group's tag; edit the field to change it.
Tag rules:
- Lowercase
a-z,0-9,-, and_, starting with a letter or digit. - Up to 32 characters.
From a shell or an agent:
wsx workspace tag add parked
wsx workspace tag rm parked --workspace backend/old-spike
wsx workspace tag list --all
See wsx workspace tag for the full syntax.
Tags show dimmed after the branch name: ⎇ wsx/my-slug #parked #ui. They use only the space left in the branch column, and tags that don't fit collapse to a count like +2. To make room, widen the column with the dashboard_branch_width setting. Attached to a workspace, local or remote, its tags show after its name in the top bar; when the bar is too narrow they drop as a group rather than folding to a count.
Workspace tags are unrelated to prompt tags.
Hiding workspaces
Press F on a repo header, or any row in the repo, to choose what that repo hides. Move with j/k and toggle with space. wsx saves the choice per repo.
Hidden workspaces collapse into one dimmed row per rule:
▸ ⏺ merged · 4 hidden
▸ #parked · 2 hidden
Press enter or l on that row to show its workspaces, and h to collapse it. An expanded group and its workspaces show at full brightness. Expanded rows stay open until wsx restarts.
By PR state
The states are no PR, draft, open, conflicted, merged, and closed. Each hides every matching workspace, except no PR, which keeps a workspace visible when any of these is true:
- It has commits ahead of its base branch, or uncommitted changes.
- An agent session is running, or its agent reports working, waiting, or blocked.
- It was created, or its agent was active, in the last 3 days.
A workspace whose PR state hasn't loaded yet stays visible.
By tag
The F picker lists every tag in use above the PR states. A tag rule hides every workspace with that tag, even one with work in progress. A workspace matching a tag rule and a PR rule is grouped under the tag. A rule for a tag no longer in use stays in the picker with a count of 0, so you can turn it off.
What still counts hidden workspaces
- A
/search covers every workspace, hidden or not. - Status bar, Waybar, and menu bar counts include hidden workspaces.
- The updates panel (
Ctrl-x u) shows the same group rows as the dashboard. Expanding a group in either place expands it in both. - The attached view's top bar leaves them out, except workspaces carrying a tag listed in the
top_bar_show_tagssetting. Those stay in the bar unless a PR rule hides them. - The by-attention view (
G) lists them in a HIDDEN section.
If the selected workspace becomes hidden, for example when its PR merges, the selection moves to its group's row.
Searching
Press / and type. The filter matches branch names, repo names, tags, status words, and recaps, ignoring case. Prefix a tag with # to match only that exact tag:
/parked anything containing "parked"
/#parked tagged exactly "parked"
esc clears the filter.
To narrow the list by status, click a status in the header tally. See Filtering by status.
Process tracking
wsx lists the long-running processes working inside each workspace's worktree, such as dev servers and watchers. Use it to find and stop what a workspace left running, or to start a command there without opening a terminal.
A workspace with tracked processes shows a count like ● 2p on its dashboard row. Press K on the row, Ctrl-x k while attached, or click the count to open the list:
PID COMMAND CWD
12345 npm /home/user/wt/fix-bug
12389 pytest /home/user/wt/fix-bug/tests
[↑/↓] move [r] run [k] term [K] kill [esc] close
| Key | Action |
|---|---|
↑ / ↓ | Move the selection |
k | Send SIGTERM to the process |
K | Send SIGKILL to the process |
r | Run a command in the worktree |
esc | Close |
j and k don't move the selection here, because k stops a process.
Running a command
r runs a command with sh -c in the worktree, in the background. Output goes to a log file under ~/.local/state/wsx/logs/, and wsx shows the path. The command keeps running after you quit wsx, and it appears in the list so you can stop it with K.
What's listed
- Processes whose working directory is inside the worktree.
- Not shells and editors (bash, zsh, nvim, code, and so on).
- Not helpers that Claude Code and editors start, such as MCP and language servers, unless they listen on a TCP port. A dev server started from inside Claude Code still shows.
Archiving stops processes
Archiving a workspace, with d or wsx workspace archive, stops every listed process before it removes the worktree. Each gets SIGTERM, then SIGKILL after two seconds. --keep-worktree skips this. A process wsx can't signal, such as one owned by another user, is reported and the archive continues.
Requirements
Process tracking needs lsof. Without it, the count stays at 0 and the list is empty.
Mouse and text selection
wsx captures the mouse so the wheel scrolls the agent's history and parts of the screen are clickable. As a result, a plain click-and-drag no longer selects text.
Selecting text
Hold Shift while you drag. Most terminals (Alacritty, Kitty, WezTerm, iTerm2, GNOME Terminal) pass a Shift-drag through to their own selection. On macOS, some terminals use Option instead. In iTerm2 you can also right-click and choose Bypass mouse reporting.
Scrolling
- The wheel scrolls the attached pane's history. If the agent handles the mouse itself, the wheel goes to the agent; hold
Shiftto scroll wsx's history instead. - On the dashboard, the wheel over a detail bar container scrolls that container.
Clickable targets
| Click | Does |
|---|---|
| A key hint in the footer | Presses that key |
A status in the header tally (? 2 question) | Filters the list to that status; click it again, or press esc, to clear |
A row's PR chip (⏺ #123 open) | Opens the PR in your browser |
| A repo header's PR link | Opens your open PRs in that repo on GitHub |
A row's process count (● 2p) | Opens the process list |
| A pinned-command chip | Sends that pinned command |
| A prompt-tag chip | Opens that prompt tag's body box |
| A workspace in the attached view's strip | Attaches to it |
The strip's +N more | Opens the updates panel |
| An agent pill | Switches the pane to that agent |
A $hosts chip | Opens that host's shared workspaces |
| The footer activity graph | Picks the graph's time window |
The repo PR link shows as PR, or as a pull-request icon with nerd_fonts on. It turns green when one of the repo's workspaces has an open PR. It appears only for repos whose origin is on github.com.
Opening PRs needs gh, installed and authenticated.
Attached view
Press Enter on a dashboard row to attach to a workspace's agent full-screen. Commands in the attached view start with the Ctrl-x leader. Press Ctrl-x d to detach back to the dashboard. The agent keeps running.
- Updates panel and split panes: switch workspaces and tile several in one view.
- Pinned commands: fire saved prompts from a chip row.
- Prompt tags: wrap prompt sections in XML tags.
To send a literal Ctrl-x to the agent, press Ctrl-x x. Keybindings lists every attached-view key.
Updates panel and split panes
While you're attached to one workspace, wsx keeps the others in view. Use the top-bar strip and the updates panel to jump between workspaces, and split the view to watch several agents at once.
Workspace strip
The top bar lists every other workspace after the focused one's label, as <glyph> <repo>/<name> (<age>):
- The glyph and color match the workspace's dashboard row.
- Workspaces that need attention come first. The rest follow the dashboard's order.
- Workspaces the dashboard hides are left out, even ones that need attention. To keep a hidden tag in the strip, list it in the
top_bar_show_tagssetting. A workspace with a listed tag ignores every tag rule, but PR rules still hide it. - Click an entry to switch to that workspace.
- When the strip runs out of room it ends in
… +N more. Click it to open the updates panel.
Updates panel
Press Ctrl-x u to open the updates panel, a compact dashboard in the bottom drawer. It lists every visible workspace in the dashboard's order and grouping, with nothing folded. Hidden workspaces fold into the dashboard's group rows: press l on a group to list its workspaces, h to fold it again, or enter to toggle it. A / search covers hidden workspaces too. Each row shows the workspace's state, latest event, PR chip, and +N −N diff. It updates live.
| Key | Action |
|---|---|
↑ / ↓, k / j | Move the selection (wraps at the ends) |
Enter or l | Switch the current pane to the selected workspace |
l / h | Expand / fold a hidden group (Enter toggles it) |
v | Open the selected workspace in a vertical split (side by side) |
s | Open the selected workspace in a horizontal split (stacked) |
o | Cycle the sort mode (recency ↔ status), same as the dashboard |
G | Toggle grouping (by repo ↔ by attention) |
/ | Filter by workspace name, repo, or status text |
Esc | Clear the filter, or close the panel |
While a filter is active, letter keys type into the filter. Use the arrow keys and Enter to pick a row.
Split panes
You can tile several workspaces in the attached view, vim-style. Any pane can split again, so you can build any tree of vertical and horizontal splits. Each pane has a one-line title bar, and ● marks the focused pane, which receives your keystrokes.
- Attach to a workspace and press
Ctrl-x u. - Select another workspace and press
vors. Focus moves to the new pane. - Repeat to add more panes.
| Key | Action |
|---|---|
Ctrl-x ←/→/↑/↓ | Move focus to the neighboring pane |
Ctrl-x d | Close the focused pane. Closing the last pane detaches |
Ctrl-x Shift-D | Save the layout and detach to the dashboard |
Other panes keep running when you close one.
Saving a layout
Ctrl-x d doesn't remember the arrangement. Press Ctrl-x Shift-D to save the split tree and the focused pane against the first workspace you attached to, then detach.
- The next time you attach to that workspace, wsx restores the layout and restarts the other panes' sessions.
- Panes whose workspaces no longer exist are dropped. If none remain, you get a single pane.
- A workspace with a saved layout shows a columns glyph next to its branch on the dashboard (Nerd Fonts only).
Pinned commands
Pinned commands put the prompts you send often, such as /pull-request, on a chip row below the agent pane. Fire one with a key instead of typing it.
[1] PR [2] FB [3] /loop /baby… [4] UR
| Key | Action |
|---|---|
Ctrl-x 1 … Ctrl-x 9 | Fire chip 1–9 |
| Click a chip | Fire that chip |
A chip types its command into the agent and presses Enter. Ctrl-x <digit> also works on the dashboard, where it sends the command to the selected workspace's agent and starts its session if needed.
Configure chips
Set the pinned_commands setting, one entry per line:
PR=/pull-request
FB=/feedback
/loop /babysit-prs
review=/agent-review ...
Label=commandshows the label on the chip. A bare line shows the command itself.- Labels longer than 14 columns are truncated.
- Only the first 9 chips get keys. At narrow widths trailing chips are hidden, but their keys still work.
wsx config edit pinned_commands # edit in $EDITOR
wsx config set pinned_commands @./pinned.txt # load from a file
wsx config set pinned_commands "" # clear
wsx config set replaces the whole list. Use wsx config edit to add one chip. A repo's own list, set with wsx repo set-pinned-commands, replaces the global list for that repo. See Settings.
Chips that type without submitting
End a command with ... or … to type it without pressing Enter. Use this for a command that takes an argument you choose each time:
review=/agent-review ...
- Attached, the chip types
/agent-reviewand leaves the cursor there. - On the dashboard, the text goes into the detail bar's reply input. Finish it there and press
Enter. - A command that really ends in
...can't be pinned as a submitting chip.
Some commands ask for missing input on their own. The bundled handoff skill asks what to implement, and agent-review asks which reviewer to spawn.
Prompt tags
Prompt tags wrap a section of your prompt in XML tags, such as <context>…</context> or <task>…</task>, so the model can tell the parts apart. Anthropic's prompting guidance recommends this.
Insert a tag
Press Ctrl-x < or click the <> chip in the footer to open the picker:
name: ▏
1 context ×12
2 task ×7
3 constraints ×2
[↑/↓] move [enter] body [1-9] pick [^d] delete [esc] close
| Key | Action |
|---|---|
| Type | Filter tags by prefix |
Enter | Open the selected tag's body box, or create a new tag |
1–9 | Pick a listed tag (while the name field is empty) |
Ctrl-d | Delete the selected tag |
Esc | Close the picker |
In the body box, type your text. Enter inserts a newline.
| Key | Action |
|---|---|
Ctrl-s | Insert the wrapped text into the agent's prompt |
Esc | Go back to the picker and keep your draft |
Ctrl-s inserts this into the agent's prompt without submitting it:
<context>
…your text…
</context>
Stack several sections, add an instruction, and press Enter yourself.
- Each insert raises the tag's use count. The three most-used tags appear as footer chips. Click one to open its body box.
- If wsx can't confirm the insert, for example because the agent has exited, the body box stays open with your draft.
- Tag names must match
[A-Za-z_][A-Za-z0-9_.-]*.
Configure
The tags and their use counts live in the prompt_tags setting, one name=uses per line:
wsx config edit prompt_tags # edit in $EDITOR
wsx config set prompt_tags "" # clear
Lines that don't parse are dropped.
The footer chips are the $tags bar segment. See Themes to move or restyle them. Ctrl-x < works even if your theme leaves the segment out. If your own ~/.config/wsx/theme.toml sets [attached_bottom].format, add ($tags ) to it and a [tags] table to show the chips:
[attached_bottom]
format = "$keys ($pins )($tags )"
[tags]
format = "[<$label>]()"
separator = " "
more_format = "[ <> ](bold)"
Agents
wsx runs a coding agent in each workspace and keeps track of what it's doing.
- Coding agents: pick claude, pi, hermes, codex, or omp, and what wsx supports for each.
- Multi-agent workspaces: run several agents in one worktree and message between them.
- Status and recap: how agents report progress and where you see it.
- Agent skills: the bundled
wsx,agent-review, andhandoffskills.
Coding agents
wsx runs Claude Code in each workspace by default. You can pick a different coding agent for all new workspaces or for one workspace.
wsx config set coding_agent codex # default for new workspaces
wsx workspace create backend --agent pi # this workspace only
A workspace created from inside another workspace inherits its agent kind. See wsx workspace create.
| Agent | Project | Its own config |
|---|---|---|
claude (default) | Claude Code | ~/.claude.json |
pi | @earendil-works/pi-coding-agent | ~/.pi/ |
hermes | hermes-agent | ~/.hermes/config.yaml |
codex | OpenAI Codex | ~/.codex/config.toml |
omp | oh-my-pi (@oh-my-pi/pi-coding-agent) | ~/.omp/agent/config.yml |
wsx runs the binary of the same name from PATH. Override the path with WSX_<AGENT>_BIN, and the model with the agent's model variables. See Environment variables.
Capabilities
| Capability | claude | pi | hermes | codex | omp |
|---|---|---|---|---|---|
| wsx instructions and auto-rename | ✓ | ✓ | ✓ | ✓ | ✓ |
| Related repos as extra dirs | ✓ | – | – | – | ✓ |
| Automatic status from the harness | ✓ | – | – | ✓ | – |
| Each agent resumes its own session | ✓ | ✓ | – | ✓ | ✓ |
Yolo mode (--yolo) | ✓ | – | ✓ | ✓ | ✓ |
| MCP inheritance | ✓ | – | – | – | – |
| Remote control | ✓ | – | – | – | – |
~/.claude/skills and ~/.claude/commands | ✓ | skills | – | commands | ✓ |
- "wsx instructions" is the process doctrine, the auto-rename prompt, and the repo's custom instructions.
- Agents without automatic status still report through
wsx status set. See Status and recap. - Session resume is covered in Multi-agent workspaces.
claude
- Instructions go in
--append-system-prompt. Related repos go in--add-dir. - wsx adds status hooks through
--settings. - Yolo passes
--dangerously-skip-permissions. - Remote control is on by default. Turn it off with the
remote_controlsetting.
pi
- Instructions go in
--append-system-prompt. - wsx loads a small extension (
pi -e) that reports each session start, so each pi agent resumes its own session. WSX_PI_MODELandWSX_PI_PROVIDERpick the model for new sessions.- pi reads skills from
~/.claude/skills.
hermes
- Hermes has no flag for extra instructions, so wsx writes them into a
<!-- BEGIN wsx-managed -->block at the end of the worktree'sAGENTS.mdon each start.- When wsx creates
AGENTS.md, it copies the repo'sCLAUDE.mdinto it. - An untracked
AGENTS.mdis added to.git/info/exclude. - A tracked
AGENTS.mdshows as modified while the block is present.
- When wsx creates
- On resume, wsx picks the newest Hermes session started since the workspace's first Hermes spawn. If two worktrees start Hermes within a few seconds, it can pick the wrong one.
- Added Hermes agents always start fresh.
WSX_HERMES_MODELoverrides the model in~/.hermes/config.yaml.WSX_HERMES_PROVIDERis passed as--provider, which Hermes's default REPL mode may ignore; set the provider inconfig.yamlto be sure.- The detail bar shows chat and tool counts, but not edited files.
codex
- Requires Codex 0.146.0 or newer.
- Instructions go in
-c developer_instructions=…. Nothing is written to the worktree. Codex readsCLAUDE.mdwhen the repo has noAGENTS.md. - Instructions are set only on a fresh start. A resumed session keeps the ones it started with, so edits to custom instructions or related repos don't reach it.
- Status comes from Codex's
notifyafter each turn. - Your
~/.claude/commands/*.mdare mirrored into a Codex plugin on each start, so commands such as/pull-requestwork in Codex. - wsx turns off Codex's startup update prompt, which would block input. Update Codex yourself, for example with
npm install -g @openai/codex. - Yolo passes
--dangerously-bypass-approvals-and-sandbox. - The detail bar doesn't show edited files.
omp
omp and pi are separate harnesses with different binaries. You can install both.
- Instructions go in
--append-system-prompt. Related repos go in--add-dir. - Yolo passes
--approval-mode yolo. Otherwise omp uses yourtools.approvalMode. WSX_OMP_MODELsets the model. It acceptsprovider/id.- On each start wsx passes
--config <state dir>/omp-config.yml. This overlay turns on omp's~/.claudeskills, commands and Claude plugins, which omp 18 turns off by default.- It overrides your own settings for those keys in wsx sessions, and replaces your
enabledProviderslist. - It enables every skill and command under
~/.claude, not only wsx's. - Your
~/.omp/agent/config.ymlis never edited.
- It overrides your own settings for those keys in wsx sessions, and replaces your
- Pinned chips that name a bundled skill are sent as
/skill:<name>. See Agent skills.
Multi-agent workspaces
A workspace can run several agents, of the same kind or different kinds. They share one worktree and branch, each has its own session, and they can message each other. Use this to run a reviewer next to the agent doing the work, or to try claude and codex on the same problem.
Every workspace starts with one primary agent, chosen by --agent or the coding_agent setting. See Coding agents. The primary lives as long as the workspace.
Agents write to the same files. Hand work off with messages rather than editing the same paths at once.
Labels
Each agent's label is its kind, plus #N for the second and later agents of that kind: claude, claude#2, codex. Labels appear in the footer, in wsx agent list, and in message banners. The label primary always means the workspace's primary agent.
wsx sets two variables in each agent's session, which the wsx agent commands read:
| Variable | Value |
|---|---|
WSX_WORKSPACE_ID | The agent's workspace |
WSX_AGENT_INSTANCE_ID | This agent instance |
Without WSX_WORKSPACE_ID, wsx finds the workspace from the current directory, so the commands also work from a shell in the worktree.
Add and remove agents
Press Ctrl-x a on the dashboard or in the attached view to open the agents panel. It lists the workspace's agents and a picker of the five kinds.
| Key | Action |
|---|---|
↑ / ↓, k / j | Move through the picker |
Enter | Add the selected kind |
a | Add one agent of every kind |
x | Remove the most recently added non-primary agent |
Esc | Close the panel |
A new agent starts at once with the workspace's context. When you remove an agent you're viewing, the pane switches to the primary.
From the CLI:
wsx agent add codex # prints: added codex
wsx agent remove claude#2 # can't remove the primary
Removing an agent discards messages still queued for it. See wsx agent add and wsx agent remove.
Switch focus between agents
With two or more agents, the attached view's chip row shows an agent pill for each, with a switch key:
^x menu 1 pr 2 fb ──────── ● claude q ○ codex w ○ pi r opus 4.8 45k/200k
- Press
Ctrl-xand the pill's key, or click the pill, to show that agent in the focused pane. - Keys come from
q w r y i o p s h j, primary first. Agents past the tenth have no key but stay clickable. ●marks the agent in the focused pane. A spinner replaces the dot while an agent works.- The model and token count follow the agent in the focused pane.
- On a narrow terminal the pills hide as a group. Their keys still work.
Messages
Agents send each other messages with wsx agent send. The target is a label, primary, or a numeric instance id:
wsx agent send codex "review the parser changes"
wsx agent send primary --file brief.md
wsx agent send --workspace backend/add-widgets primary "start on the API"
send prints the message id, for example queued message #561 to codex (2747 bytes). The dashboard delivers the message into the target's session with a banner that says how to answer:
[message #561 from claude#2; reply with: wsx agent reply 561 <message>]
…message body…
A sender in another workspace shows as <repo>/<slug> <label>. A message from a plain shell has the banner [message #561].
- Delivery waits until the target is idle at its prompt. If the target isn't running, wsx starts it.
- Only a running dashboard delivers messages. Without one,
sendwarns and the message waits in the queue. sendrefuses a target whose binary isn't installed.- A message that fails to deliver several times shows a red
✉!badge on the dashboard row. Restart wsx to retry. - Delivery is at least once. A wsx crash mid-delivery can repeat a message.
To answer, read mail, or wait for a reply:
wsx agent reply 561 "tests pass" # reply to message #561
wsx agent messages # your inbox
wsx agent wait --after 561 --done codex # block until codex replies or reports done
wsx agent whoami # your label, instance id, and workspace
wsx agent wait only reads a message. The dashboard still delivers it later, so the agent sees the same [message #561 …] banner again and should treat it as a duplicate.
The wsx agent reference covers every flag: send, reply, messages, wait, and whoami.
List agents
wsx agent list
1 claude (primary) working — "running the test suite" (model, 3m ago)
2 claude#2 done (hook, 10m ago)
4 codex
The number is the instance id. Each agent's own status follows its label. See Per-agent status and wsx agent list.
Sessions survive a restart
When you quit and restart wsx, each agent resumes its own conversation, even when several agents of one kind share the worktree.
| Agent | Resumes with |
|---|---|
claude | claude --resume <id>, with the id from wsx's status hooks. Follows /clear. |
codex | codex resume <id>, after the agent's first turn ends |
pi | pi --session-id <id>. Follows /new, /resume, and forks. |
omp | omp --resume=<file>. Follows /new. |
hermes | The primary resumes its newest session. Added agents start fresh. |
- Until wsx has an agent's session id, the primary uses its harness's "continue latest" and an added agent starts fresh.
- If an agent's recorded session no longer exists on disk, the agent starts fresh.
- Changing a workspace's agent kind forgets its sessions.
- wsx looks only in each harness's default session directory. A custom
CODEX_HOMEorCLAUDE_CONFIG_DIRisn't supported.
Inspect other workspaces
The read commands take --workspace <repo>/<slug>, so an agent can check on a workspace it handed work to:
wsx status show --workspace backend/add-widgets
wsx recap show --workspace backend/add-widgets
wsx agent list --workspace backend/add-widgets
wsx context show --workspace backend/add-widgets
For every workspace at once, use wsx workspace list --json.
Status and recap
Agents tell wsx what they're doing so you can see at a glance which workspaces need you. The status is a state word and a one-line message. The recap is the task's goal, current state, and next step.
Status states
| State | Meaning |
|---|---|
working | The agent is doing substantive work |
waiting | The agent is parked on something external, such as CI |
blocked | The agent needs a decision or answer from you |
done | The task is finished |
busy | The turn ended, but background work is still running (hooks only) |
Set status
An agent sets its own status:
wsx status set blocked --message "need your call on the schema"
wsx status set also accepts the recap flags, so one call can update both:
wsx status set working --message "fixing tests" --state "2 tests failing" --next "fix regex"
| Command | Effect |
|---|---|
wsx status show | Print the workspace's status and each agent's (bare wsx status too) |
wsx status clear | From an agent, clear its own status. From a shell, clear every agent's |
See wsx status set and wsx status show for all flags.
Automatic status from hooks
Claude and Codex report status without the agent's help. wsx wires their lifecycle events to wsx status from-hook (Claude) or Codex's notify.
| Event | Status |
|---|---|
| You submit a prompt (Claude) | working |
Permission prompt, AskUserQuestion, or plan approval (Claude) | blocked |
| Claude's idle notification | waiting |
Turn ends with a question (ends in ?) | blocked |
| Turn ends while subagents or shell tasks run (Claude) | busy |
| Turn ends otherwise | done |
A status the agent sets itself holds through the end of that turn:
- The turn-end hook keeps an agent's own
done,blocked, orwaitingand its message. The next turn that ends without a newwsx status setreplaces it. - The idle notification never overrides an agent's own status.
- New work (
workingorbusy) replaces it and drops the old message. - If the agent said
workingand the turn ends, the hook's result replaces it.
A background monitor, such as the watcher Claude keeps after publishing an artifact, doesn't count as background work. A turn that ends with only a monitor running reads as done.
pi, Hermes, and omp have no status hooks. They rely on wsx status set, and the dashboard reads their session logs.
Per-agent status
Each agent's status is recorded separately, keyed by its WSX_AGENT_INSTANCE_ID. A status set from a plain shell or an editor-hosted agent goes to the primary. wsx agent list and wsx status show print each agent's status:
status: blocked — "need your call on the schema" (model, 1m ago)
agents:
claude (primary): working — "implementing" (model, 4m ago)
claude#2: blocked — "need your call on the schema" (model, 1m ago)
The workspace status comes from its agents. The state that most needs you wins, in this order: blocked, working, busy, waiting, done. The most recent breaks ties. So one agent finishing can't mark the workspace done while another still works.
- The dashboard and project manager digest combine the workspace status with the primary agent's session. A peer's older status gives way to newer activity in the primary's transcript.
- On the dashboard, a reported status holds until the agent's session log shows newer activity. Then wsx reads the log instead.
busyholds until the next hook. - A status never expires. An agent whose session died keeps its last status until it reports again, is cleared, or is removed.
- Removing an agent drops its status.
Recap
The recap is three one-line fields, each with a short form for narrow views:
| Field | Short form | Holds |
|---|---|---|
goal | --goal-short | What the task is for (short form ≤40 chars) |
state | --state-short | Where the work stands (short form ≤24 chars) |
next | --next-short | The next step (short form ≤24 chars) |
wsx recap set --goal "Audit V2 invoices auto-issued today for the CV-04964 drift bug" \
--goal-short "Audit V2 invoices, CV-04964"
wsx recap set --state "tests failing" --next "debug the regex"
wsx recap set updates only the fields you pass. Write short forms in telegraphic style: identifiers and ticket numbers, no articles. wsx recap show prints the recap, and wsx recap clear deletes it. See wsx recap set.
Where they show up
| Place | Shows |
|---|---|
| Dashboard row | Status word, then the recap's short forms |
| Detail bar SESSION SUMMARY | Full recap, then status |
| Project manager digest | Full recap and status for every workspace |
| Waybar, Plasma, and menu bar | Workspace status. The menu bar also shows recaps |
When a recap field has no short form, the dashboard row shows the full field with articles removed.
What agents are told
wsx's process doctrine, which it adds to every agent's instructions, tells agents to:
- set status as they go:
workingwhen they start,blockedwhen they need you,waitingwhen parked,donewhen finished; - set the recap goal once they understand the task, and update state and next whenever they set status or end a turn unfinished;
- keep the short forms filled in.
Change or turn off the doctrine with the process_doctrine and process_doctrine_extra settings. See Settings.
Agent skills
wsx bundles skills that teach agents to drive wsx: creating workspaces, messaging peers, and handing work off. Install them once per machine.
wsx setup install-skill
This writes every bundled skill to each agent's skills directory:
| Agent | Directory | When |
|---|---|---|
| claude, pi, omp | ~/.claude/skills/ | Always |
| codex | ~/.codex/skills/ | codex is on PATH, WSX_CODEX_BIN is set, or ~/.codex exists |
| hermes | ~/.hermes/skills/ | hermes is on PATH, WSX_HERMES_BIN is set, or ~/.hermes exists |
Re-run it after upgrading wsx. It reports already up to date for unchanged skills and overwrites any copy that differs, including your local edits. omp reads ~/.claude/skills through a config overlay wsx passes. See Coding agents.
Bundled skills
| Skill | Use it to |
|---|---|
wsx | Drive the wsx CLI: workspaces, naming, peer agents, and work across related repos |
agent-review | Start a peer agent that reviews the current branch against main and reports back to you |
handoff | Continue a finished workspace's feature in a fresh workspace |
agent-review takes the reviewer kind (claude, pi, hermes, codex, or omp) and asks if you omit it. It adds the reviewer with wsx agent add and gives it the branch diff. The reviewer runs the repo's own review skill when it has one (any *review* skill under .claude/skills/, .agents/skills/, or .github/skills/, such as pr-review), else its harness's built-in, then replies with its findings.
handoff takes what the new workspace should implement, or asks for it:
/handoff add a --json flag
It creates a workspace in the same repo and briefs its agent with a summary of this session: decisions, rejected approaches, gotchas, file pointers, and follow-ups. Reply defer to have the new agent wait for your request. The old agent marks itself done and doesn't archive its workspace.
Pinning skills as chips
Add skills to your pinned commands with wsx config edit pinned_commands:
agent-review=/agent-review ...
handoff=/handoff
- The
...makes theagent-reviewchip type/agent-reviewand wait for you to add the reviewer kind. - The
handoffchip runs with no request, so the agent asks what to implement. - In omp, wsx sends a chip naming a bundled skill as
/skill:<name>, because omp's own/handoffwould catch it. Pin the plain/handoffform. Typing/handoffyourself in omp still runs omp's built-in command.
Configuration
Configure wsx with global and per-repo settings, setup and archive scripts, themes, and auto-rename.
Settings
Global settings live in the wsx state database. Read and change them with wsx config. Per-repo values override some of them.
wsx config list
wsx config get <key>
wsx config set <key> <value>
wsx config edit <key> # opens $EDITOR (default: vi)
Value sources:
- A literal:
wsx config set branch_prefix bakedbean - A file, prefixed with
@:wsx config set custom_instructions @./instructions.md - An empty string clears the key and restores the default:
wsx config set custom_instructions "" wsx config edit <key>opens the current value in$EDITOR. Use it for multi-line values.
config set rejects unknown keys. On/off keys accept on/true/1/yes and off/false/0/no. See wsx config for the full syntax.
Global settings drawer
Press , on the dashboard to browse every key below without leaving wsx. The footer describes the selected key and lists its allowed values.
- Unset keys are dimmed and show their default.
↑/↓selects a key.Enterflips an on/off key, steps a fixed-choice key (theme,coding_agent, bells, sort mode, usage window) to its next value, or opens any other key in$EDITOR.dclears the key, restoring its default.Esccloses the drawer.
Values are validated the same way as wsx config set. Dashboard sort, pin, and top-bar tag settings apply at once; keys marked "Restart wsx to apply" still need a restart.
General
| Key | Default | Effect |
|---|---|---|
branch_prefix | unset | Names new branches <prefix>/<workspace>. Unset, the branch is the bare workspace name. |
coding_agent | claude | Agent for new workspaces: claude, pi, hermes, codex or omp. See Coding agents. |
Agent sessions
| Key | Default | Effect |
|---|---|---|
custom_instructions | unset | Text appended to the agent's system prompt on every spawn. |
process_doctrine | built-in text | Replaces the working doctrine injected into every session. off, none or disabled turns it off; an empty value restores the default. See What agents are told. |
process_doctrine_extra | unset | Extra - ... bullet lines appended to the doctrine. |
mcp_mirror | on | Copy the source repo's MCP servers into each worktree. See MCP server inheritance. |
Editor and tools
| Key | Default | Effect |
|---|---|---|
editor_cmd | $VISUAL, then $EDITOR | Command for e. The worktree path is appended unless the command contains {path}. |
terminal_cmd | $TERMINAL | Command for t, run in the worktree. {cmd} makes it usable for desktop-widget jumps. |
diff_cmd | unset | Command for v. Accepts {path} and {base}. |
lazygit_cmd | unset | Command for g, run in the worktree. It must open its own window, for example wezterm start -- lazygit. |
See Editor, terminal, and diff for examples.
Notifications
| Key | Default | Effect |
|---|---|---|
notifications | on | Master switch for the terminal bell and the ! attention marker. |
notification_bell_question | double | Bell when the agent asks you a question. |
notification_bell_complete | single | Bell when the agent finishes a turn. |
notification_bell_permission | single | Bell when the agent waits on a permission prompt. |
notification_bell_stalled | triple | Bell when the agent stalls. |
Bell values are off, single, double or triple. off silences the bell but keeps the ! marker. See Status indicators.
Dashboard
| Key | Default | Effect |
|---|---|---|
dashboard_sort_mode | recency | Workspace order within a repo: recency or status. o toggles it. |
dashboard_blocked_pin_max_age_secs | 86400 | How long a workspace blocked on you stays pinned to the top in recency mode. |
top_bar_show_tags | unset | Tags, separated by commas or spaces, that keep a workspace in the attached view's top bar when a tag hide rule hides it from the dashboard. A workspace with any listed tag ignores every tag rule; PR hide rules still apply. Names are lowercased, and invalid ones are ignored. After wsx config set, restart wsx to apply; the , drawer applies it at once. |
dashboard_branch_width | 28 | Width of the branch column, clamped to 10–80. |
dashboard_pr_width | 16 | Width of the PR column, clamped to 8–24. |
detail_bar_config | built-in | JSON for the detail pane. See Detail bar for the schema. |
usage_graph_window | 24h | Window of the $usage activity graph: 24h, 1w or 1mo. Shown only when a theme places $usage. |
pinned_commands | unset | Label=command lines shown as chips. See Pinned commands. |
prompt_tags | unset | name=uses lines, maintained by the prompt-tag picker. See Prompt tags. |
Appearance
| Key | Default | Effect |
|---|---|---|
theme | wsx | Base palette: wsx, default (terminal ANSI colors), dracula, jellybeans or nord. Restart wsx to apply. |
bar_theme | off | Read bar layout and styling from ~/.config/wsx/theme.toml. Applies within a second. See Themes. |
nerd_fonts | on | Use Nerd Font glyphs on the dashboard. |
Remote
| Key | Default | Effect |
|---|---|---|
remote_control | on | Start claude with --remote-control, so claude.ai/code and the mobile app can reach it. See Remote access. |
remote_control_sandbox | off | Also pass --sandbox when remote_control is on. |
remotes | unset | name=command lines run by wsx remote <name>. See Remote access. |
shared_hosts | unset | name=ssh-destination lines for browsing and creating workspaces on other machines. See Shared workspaces. |
Per-repo settings
Each repo can override or add these values:
| Setting | CLI | Notes |
|---|---|---|
| Name | wsx repo set-name | |
| Branch prefix | wsx repo set-prefix | Overrides branch_prefix. |
| Base branch | wsx repo set-base-branch | |
| Custom instructions | wsx repo set-instructions | Added to the global custom_instructions. |
| Setup and archive scripts | wsx repo set-setup, set-archive | See Setup and archive scripts. |
| Pinned commands | wsx repo set-pinned-commands | Replaces the global pinned_commands. |
| Related repos | wsx repo set-related-repos | See Related repos. |
| Detail bar | drawer only | Merged over the global detail_bar_config. |
You can also edit them in the repo settings drawer: press s on any dashboard row.
- An unset field shows the global value marked
(inherited), or(unset). ↑/↓selects a field.Enteropens the repo value in$EDITORand saves it when the editor exits.dclears the repo value, which restores inheritance.Esccloses the drawer.
Use a terminal editor that blocks until you quit (vim, helix, nano). A GUI editor that returns immediately saves nothing.
See wsx repo for the full syntax.
Setup and archive scripts
Each repo can have a setup script, which runs when a workspace is created, and an archive script, which runs when a workspace is removed. Use them to install dependencies or clean up build output.
wsx repo set-setup <repo> 'bun install'
wsx repo set-archive <repo> 'rm -rf node_modules'
wsx repo set-setup <repo> @./scripts/setup.sh # from a file
wsx repo edit-setup <repo> # in $EDITOR
wsx repo set-setup <repo> "" # clear
You can also edit both scripts in the repo settings drawer (s on the dashboard). See Per-repo settings and wsx repo.
How scripts run
- wsx runs each script as
$SHELL -ilc "<script>", so your shell profile loads first and tools likemise,direnvandasdfwork. - If
$SHELLis unset or a POSIX-only shell (sh,dash,ash), wsx uses/bin/bash. - The working directory is the worktree.
- Two extra environment variables are set:
| Variable | Value |
|---|---|
WSX_REPO_ROOT | The source repo |
WSX_WORKTREE | The workspace's worktree |
Setup failures and logs
A failed setup doesn't block the workspace. Its dashboard row shows a ⚙! badge, and a cancelled setup shows ⚙?.
Setup output goes to ~/.local/state/wsx/logs/setup-<repo>-<workspace>.log. Each run overwrites it, and a rename moves it. wsx doesn't keep archive script output.
To read the log in the TUI, or to cancel a setup that's still running, open the workspace card with ? on the dashboard. o opens the log and x cancels the setup. See Keybindings.
The archive script runs before wsx removes the worktree, and you can't cancel it.
Themes
wsx has two layers of theming. The theme setting picks a base palette for the whole UI. The optional bar theme file, theme.toml, controls what the dashboard and attached-view bars show and how they look, using a subset of Starship's format grammar.
Quick start
wsx theme init # write the bundled default to ~/.config/wsx/theme.toml
$EDITOR "$(wsx theme path)"
wsx theme check # print every error; exit 1 on any
wsx config set bar_theme on
bar_themeis off by default. While it's off, wsx draws its stock bars and never reads the file. Thewsx themecommands still work, so you can prepare a file first.- Switching
bar_themeeither way, and saving the file, take effect within a second without a restart. - If a save has errors, the bars keep their last good look and the dashboard footer shows the first error for five seconds. An invalid file at startup falls back to the bundled default.
- The file is optional, and anything you leave out keeps the bundled default.
- The path is
$XDG_CONFIG_HOME/wsx/theme.tomlwhenXDG_CONFIG_HOMEis an absolute path, and~/.config/wsx/theme.tomlotherwise. wsx theme check <path>validates another file before you install it.
See wsx theme for the command syntax.
Base palette
wsx config set theme nord # wsx (default), default, dracula, jellybeans, nord
The base palette colors repo headers, the selected row, status dots, modals and markdown. default uses your terminal's ANSI colors. Restart wsx after changing it. The bar theme file can use the palette's colors as theme tokens, but can't change them.
Example themes
Ready-made files live in the repo under docs/examples/:
| File | Look | Pair with theme |
|---|---|---|
theme-starship.toml | Powerline blocks in stepped greys with orange accents. | wsx |
theme-rose-pine.toml | Rosé Pine in an airline layout. | wsx |
theme-rose-pine-moon.toml | Rosé Pine Moon, same layout. | wsx |
theme-nord.toml | Nord, same layout. | nord |
theme-nord0.toml | Nord one step darker, blending into a Nord terminal. | nord |
theme-jellybeans.toml | Jellybeans, same layout. | jellybeans |
theme-orange.toml | Dark orange, from a vim-airline theme. | jellybeans |
cp docs/examples/theme-starship.toml ~/.config/wsx/theme.toml
wsx config set bar_theme on
They need a Nerd Font and a truecolor terminal. The powerline caps (U+E0B0, U+E0B2) are private-use characters, so copy the files as bytes (cp, scp, git). Pasting through a chat or editor can strip them: wsx theme check still passes, but the blocks lose their edges.
The examples place $statuses, which wsx builds before the segment existed don't recognize: wsx theme check fails, and the running app keeps its last good theme. Use the examples from the release you run.
To switch between several themes, make theme.toml a symlink and re-point it:
mkdir -p ~/.config/wsx/themes
cp docs/examples/theme-*.toml ~/.config/wsx/themes/
ln -sfn themes/theme-nord.toml ~/.config/wsx/theme.toml
The running app reloads within a second. If a switch doesn't show, touch the new target.
Bars
The bundled default:
[dashboard_header]
format = "$brand( $filter) ($statuses)"
right_format = "$counts"
fill = " "
[dashboard_footer]
format = "$keys"
right_format = "$funnel"
fill = " "
[attached_top]
format = "($agent_bar )$workspace( $workspace_tags)( $attention)"
[attached_bottom]
format = "$keys ($pins )($tags )"
right_format = "( ($agents )($model_tokens )($procs )($diff )$pr)"
fill = "─"
fill_style = "fg:dim"
[dashboard_detail]
format = "($pins )"
fill = "─"
fill_style = "fg:dim"
[dashboard_detail_header]
format = "$agent_bar $workspace $branch( $pr)( $diff)( $procs) $status"
fill = " "
[dashboard_detail_reply]
format = "($pins )$prompt "
right_format = "$keys"
fill = " "
| Bar | Where |
|---|---|
dashboard_header | The dashboard's top line: wordmark, filter echo, status tally, counts. Only the tally's items are clickable. When its formats don't place $statuses, or [statuses] sets disabled, the tally gets its own row below it instead. That depends only on placement: a priority that lets $statuses drop on a narrow terminal doesn't bring the row back. |
dashboard_footer | The dashboard's bottom line: key hints and the funnel module. |
attached_top | The attached view's top bar. |
attached_bottom | The attached view's bottom bar (chip row). |
dashboard_detail | The detail pane's pinned-command row. Drawn only when the reply row leaves out $pins or this bar places a module. |
dashboard_detail_header | The detail pane's top row. |
dashboard_detail_reply | The detail pane's reply row, laid out like a shell prompt. |
Each bar takes these keys:
| Key | Meaning |
|---|---|
format | The left side, clipped at the right edge if too long. |
right_format | Flush right. |
style | Base style for literal text, segment content and the fill. |
fill | Character repeated across the unused gap. |
fill_style | Style for the fill, merged over style. |
When both sides are non-empty, at least one blank column separates them.
Overflow
When a bar is too narrow, segments with a priority below 100 drop, lowest first, from either side, until the bar fits. Segments at the default priority of 100 never drop. If it still doesn't fit, wsx omits the right side and clips the left.
The bundled priorities:
| Bar | Priorities |
|---|---|
attached_bottom | model_tokens 10, agents 20, procs 30, diff 40, tags 40, pr 50 |
dashboard_footer | hosts 45, version 50, tokens 55, funnel 60, usage 60 |
dashboard_header | sort 30 (when placed), counts 50 |
The reply row
[dashboard_detail_reply]'s format is the prompt before your draft and right_format is a right prompt after it. The draft fills the gap and always keeps at least 12 cells. To make room, the right side drops first, then the prompt's droppable segments.
$keys holds the ↵ send / Esc cancel hint and shows only while the row has focus (Tab from a selected workspace).
When $pins is on the reply row, on either side, the separate pinned-command row folds away and the chips stay clickable on the reply row. To get the separate row back, leave $pins out: format = "$prompt ". The example themes place the chips on a block:
[dashboard_detail_reply]
format = "([ $pins ](bg:soot fg:orange)[\ue0b0](fg:soot) )$prompt "
right_format = "([\ue0b2](fg:ash)[ $keys ](bg:ash fg:orange))"
Unplaced segments
The bundled default registers some segments without placing them:
$version, the running wsx version:right_format = "$version( $funnel)".$hosts, one clickable chip per shared host:right_format = "$hosts( $funnel)". A chip opens that host's workspace list, likeH.$usage, the activity sparkline;usage_graph_windowsets its range.$tokens, a module showing context size per agent kind.
A theme that sets its own [attached_bottom].format must add $tags itself to show the prompt-tag chips.
Differences from the stock bars
With bar_theme on:
- The attached view drops the dim
─rule under its top bar, so the pane gains a row. - A workspace with no PR leaves one blank cell at the chip row's right edge.
- A narrow dashboard footer drops the version, then the funnel, instead of overflowing.
- The dashboard header's filter echo is capped at 24 characters and never drops. A long filter drops the counts first.
Grammar
| Syntax | Meaning |
|---|---|
$name, ${name} | Insert a segment (inside a segment's format, one of its variables). |
[text](style) | Style a run. Inner runs inherit the outer background unless they set their own, so powerline blocks compose. |
( … ) | Render only if a $name inside produced output. Put separators inside the group so they vanish with the segment. |
$$, \[, \(, \x | Literal $, [, (, or x. |
In TOML double-quoted strings, write a backslash as \\. Single-quoted literal strings take format escapes as-is: '\[$workspace\]'. Use double quotes when you need \ue0b0 to decode to a glyph.
Bare parentheses are a conditional group, so escape a literal pair: \($age\). An unescaped ($age) drops the parentheses.
Styles and colors
A style is space-separated tokens:
fg:<color>,bg:<color>, or a bare<color>(foreground).bold,dimmed,italic,underline.none, accepted for Starship compatibility. It does nothing and doesn't reset anything.$style, the segment's resolved style.
A color is one of:
#rrggbb, or a 0–255 index.- An ANSI name:
red,bright-blue,white. - A
[palette]name. - A theme token:
dim row_dim path code bg_alt bg_soft ok warn err attention merged header_fg selected_fg selected_bg question stalled waiting thinking complete idle brand wordmark agent_claude agent_pi agent_hermes agent_codex agent_omp. Theagent_*tokens are each agent's fixed identity color.
Palette names shadow theme tokens, which shadow ANSI names. This only affects the bar theme. fg:dim is a color, while dimmed is a text modifier.
[palette]
rust = "#d75f00"
Segments
Each segment has its own table, such as [workspace], with these keys:
| Key | Meaning |
|---|---|
format | The segment's layout, using its variables. |
style | Merged over the segment's state-derived style and exposed as $style. |
symbol | Substituted for $symbol. |
disabled | Hide the segment. |
priority | Overflow survival; higher lasts longer. Default 100 never drops. |
palette | A sub-table of colors for this segment only. See Recoloring one segment. |
separator | Multi-item segments: a format drawn between items. It takes no variables. |
styles | Multi-item segments: per-position styles. See Grading items by position. |
more_format | attention: the tail when entries don't fit. tags: the manager chip. Its one variable is $count. |
more_style | attention only: the tail's $style. |
symbols | agent_bar only: one glyph per agent kind. |
| Segment | Variables | Notes |
|---|---|---|
brand | $symbol $name $mark $view | The wordmark. $view is dashboard, not placed by default. Dashboard header only. |
group | $label $tabs | The group: mode tabs; the active one is highlighted. Not placed by default. Dashboard header only. |
sort | $label $tabs | The sort: mode tabs. Not placed by default. Dashboard header only. |
statuses | $glyph $count $label | One item per status with a nonzero count, most urgent first, counting filtered-out workspaces too. $style is the status color, reversed on the status the list is filtered to and dimmed on the rest. Dashboard header only. Clickable. Older wsx builds reject a theme that uses it. |
filter | $needle | The live filter, capped at 24 characters. Dashboard header only. |
counts | $repos $workspaces | Repo and workspace counts. Dashboard header only. |
keys | $key $label | One pill per key hint. Clickable. |
version | $version | |
usage | $label $spark | The activity sparkline. Clickable. |
agent_bar | $symbol | The agent's glyph in its identity color. |
workspace | $repo $name | $style carries the PR-lifecycle tint (green open, purple merged, red closed). $repo is absent in the detail pane. |
workspace_tags | $tag | One item per workspace tag, dimmed. Attached view only; empty over a remote attach. A custom attached_top must place it to show tags. |
branch | $symbol $branch | The git branch. symbol defaults to ⎇. |
prompt | $symbol $agent | The reply row's prompt character. symbol defaults to ❯. Bold in the agent's color while the row has focus. |
status | $glyph $label $ago | The workspace's status as its row shows it. $ago is how long it has held. Detail header only. |
attention | $glyph $repo $name $age | One item per workspace needing attention. Overflow folds into more_format. Clickable. |
pins | $index $label | One chip per pinned command. Clickable. |
hosts | $index $name $dest | One chip per shared host; $name is truncated to 14 columns. Dashboard footer only. Clickable. |
tags | $index $label | The three most-used prompt tags, then the manager chip. Attached view only. Clickable. |
agents | $symbol $icon $label $key | One pill per agent when there are two or more. $symbol shows which agent is focused, or a spinner while it works. Clickable. |
model_tokens | $model $tokens | $style turns warn near the context limit. |
procs | $symbol $count | Hidden at zero. Clickable. |
diff | $added $removed | Hidden when clean. |
pr | $symbol $number $label $mark | $style carries the lifecycle tint; $mark_style styles the review verdict. Clickable, except over a remote attach. |
Where segments produce output:
| Bar | Segments |
|---|---|
| Attached bars | All except the dashboard-header segments, branch, prompt, status and hosts. Either bar, either side. |
dashboard_footer | keys, version, usage, hosts |
dashboard_header | brand, group, sort, statuses, filter, counts |
dashboard_detail | pins |
dashboard_detail_header | agent_bar, workspace, branch, pr, diff, procs, status |
dashboard_detail_reply | prompt, agent_bar, workspace, branch, pins, keys |
Other segments render empty there, and any segment renders empty when its data is absent. Modules work in every bar.
pr, procs, usage, attention and tags may appear only once per scope. The scopes are: the two attached bars together, the dashboard footer, the dashboard header, and each of the three detail-pane rows. wsx theme check rejects a duplicate.
Gotchas when overriding a segment:
stylereaches the output only through$style. The stock formats ofkeys,pins,version,usageanddiffdon't use$style, sostylealone does nothing on them.- Emptying
symbolleaves the space after$symbolin the stock format. Remove it fromformattoo. - An item whose
formatrenders empty is dropped, with no separator or click target. [attention] format = "$items"from older themes is rejected. Delete the table or use the item variables.- A built-in segment name, such as
hosts, can't be a module name.
Overriding a segment's style
style merges over the segment's state-derived style attribute by attribute. style = "bg:second" keeps the PR's lifecycle foreground, while style = "fg:rust" replaces it.
[workspace]
format = "[($repo/)$name]($style)"
style = "fg:rust bold"
An explicit style on an inner run still overrides $style. For the PR review mark, use [$mark]($mark_style) to keep its verdict color.
Agent glyphs
[agent_bar.symbols] sets a glyph per agent kind, tried before symbol:
[agent_bar]
symbol = "\ue0b0" # kinds without an entry
[agent_bar.symbols]
claude = "\uec82"
codex = "\uec81"
Keys must be claude, pi, hermes, codex or omp. Entries merge over the bundled default. An empty entry (pi = "") shows no glyph. The same glyphs appear as $icon in agents pills and as $icon_<kind> in modules.
Grading items by position
A multi-item segment's styles is a list of styles. The first rendered item's $style is patched by styles[0], the second by styles[1], and items past the end take the last entry. A background-only grade keeps the item's state color in the foreground.
For powerline caps between graded blocks, a multi-item segment's formats can use six extra colors:
| Color | Meaning |
|---|---|
item_fg, item_bg | This item's final $style colors. |
prev_fg, prev_bg | The previous rendered item's colors. |
next_fg, next_bg | The next rendered item's colors. |
separator sees prev_* and next_*, and more_format sees prev_*. A missing color, such as the last item's next_bg, sets nothing, so the bar's style shows through. That lets the last block's wedge blend into the bar.
attention's more_style gives the fold tail its own colors, so the last entry's next_* points into the tail.
[attention]
styles = ["bg:charcoal fg:orange", "bg:slate fg:cream", "bg:grey fg:cream"]
more_style = "bg:orange fg:black"
format = "[ $glyph $repo/$name \\($age\\) ]($style)[\ue0b0](fg:item_bg bg:next_bg)"
separator = ""
more_format = "[ +$count more ]($style)[\ue0b0](fg:item_bg)"
Rules wsx theme check enforces:
stylesandmore_stylecan't use the six neighbor colors.stylesand the six colors are rejected on single-item segments.- The six names can't be
[palette]entries.
Recoloring one segment
A [<segment>.palette] table overrides color names inside that segment only, including the tokens behind its state-derived $style. Use it to darken lifecycle tints on a light block:
[pr.palette]
ok = "#008700" # open
merged = "#870087"
err = "#870000" # closed
warn = "#878700" # conflict
[workspace.palette]
ok = "#008700"
header_fg = "black" # no PR
- Values may name global
[palette]entries, but not each other. - A name defined only in a segment palette is unknown elsewhere.
- It changes colors only, not attributes or which token a state uses.
The tokens each state uses:
| Segment | Tokens |
|---|---|
pr, workspace | ok open, merged, err closed, warn conflict |
workspace | header_fg without a PR or on a draft |
pr | dim on a draft |
attention $glyph, statuses | The six status tokens |
group, sort | selected_fg, selected_bg, path |
Modules
A module is a segment you compose from fleet-wide variables. Declare it as [module.<name>] and place it as $<name>:
[module.funnel]
format = "([$working working](fg:ok) )([$blocked blocked](fg:err) )([$mergeable ready](fg:merged))"
priority = 60
[dashboard_footer]
right_format = "$funnel"
- A module takes
format,style,priorityanddisabled. It has no click target. - A count renders empty at zero, so wrap each item in
( … )to drop it with its label.$workspacesand$reposalways render. tokens_*values render abbreviated (77k,1.2M) and are empty at zero.- The bundled default defines
funnel(placed) andtokens(not placed). Set only the fields you want to change.
[dashboard_footer]
right_format = "$tokens( $funnel)( $usage)"
| Variable | Counts |
|---|---|
working waiting blocked done | workspaces whose last wsx status set is that state |
busy | workspaces parked on background work |
unreported | workspaces with no reported status |
alerts | workspaces with an unacknowledged attention alert |
awaiting stalled active idle | workspaces by live transcript classification |
live_agents | workspaces with a live primary session |
pr_none pr_draft pr_open pr_conflicted pr_merged pr_closed | workspaces by PR lifecycle |
review_required changes_requested approved | PRs by review verdict |
unresolved | unresolved review threads |
mergeable | PRs that are open and approved |
dirty | workspaces with modified or untracked files |
msgs_queued | agent-to-agent messages not yet delivered |
workspaces repos | totals (always rendered) |
tokens_total | sum of the latest context size across every agent, primary and peers |
tokens_claude tokens_pi tokens_hermes tokens_codex tokens_omp | the same, per agent kind (hermes reports none) |
icon_claude icon_pi icon_hermes icon_codex icon_omp | the kind's glyph from [agent_bar.symbols]; like literal text, it never keeps a ( … ) group alive alone |
A powerline example
The rounded caps \ue0b6 and \ue0b4 need a Nerd Font. Conditional groups hide whole blocks, caps included, when the segment inside is empty.
[palette]
first = "#121212"
second = "#3a3a3a"
rust = "#d75f00"
[attached_top]
format = "[\ue0b6](fg:first)[ $workspace ](bg:first)[\ue0b4](fg:first)( [\ue0b6](fg:second)[ $attention ](bg:second)[\ue0b4](fg:second))"
[attached_bottom]
right_format = "([\ue0b6](fg:second)[ $agents ](bg:second)[\ue0b4](fg:second))( [\ue0b6](fg:first)[ $pr ](bg:first)[\ue0b4](fg:first))"
[workspace]
format = "[($repo/)$name]($style)"
style = "fg:rust bold"
[pr]
style = "fg:rust"
Auto-rename
After your first prompt in a new workspace, wsx renames the workspace and its git branch to match the task. Set the WSX_RENAME_MODE environment variable before starting wsx to choose how.
| Mode | Behavior |
|---|---|
claude (default) | The coding agent picks a slug from your first message and renames the branch. Costs a few tokens per new session. |
local | wsx slugifies the text of your first prompt. Costs no tokens. |
off | No auto-rename. |
WSX_RENAME_MODE=local wsx
Auto-rename only applies to a workspace that still has its generated <adjective>-<plant> name. To rename a workspace by hand, press r on its card (? on the dashboard).
Integrations
wsx hands work off to the tools around it: your editor, terminal and difftool, agents running inside your editor, other repos, other machines, and your desktop bar.
- Editor, terminal, and diff: open a workspace in your own tools with
e,t,vandg. - Editor-hosted agents: give an agent in your editor the workspace's context.
- Related repos: let a workspace's agent read sibling repos.
- MCP server inheritance: reuse the source repo's MCP servers in every workspace.
- Remote access: reach wsx and its agents from another machine or your phone.
- Shared workspaces: run agents in tmux so they outlive wsx and can be attached from anywhere.
- Desktop widgets: show workspaces in Waybar, a KDE Plasma panel, or the macOS menu bar.
Editor, terminal, and diff
Open the selected workspace in your own tools without leaving wsx. Each key works on a dashboard row and, after Ctrl-x, in the attached view. The tool starts detached, so wsx keeps running.
| Key | Opens | Setting | Fallback |
|---|---|---|---|
e | Your editor on the worktree | editor_cmd | $VISUAL, then $EDITOR |
t | A terminal in the worktree | terminal_cmd | $TERMINAL |
v | Your difftool on the branch | diff_cmd | none |
g | lazygit or another git UI in the worktree | lazygit_cmd | none |
If no command resolves, wsx shows an error that names the setting to set. See Settings for each key.
Placeholders
| Placeholder | Replaced with | Used by |
|---|---|---|
{path} | The worktree path | All four |
{base} | The upstream base ref, such as origin/main | diff_cmd |
{cmd} | The wsx command a desktop jump runs | terminal_cmd, from desktop widgets |
If editor_cmd or diff_cmd contains no placeholder, wsx appends the worktree path as the last argument. terminal_cmd and lazygit_cmd start with the worktree as their working directory instead.
{base} comes from origin/HEAD and falls back to main. If it's wrong, run git remote set-head origin --auto in the repo.
Examples
TUI editors have no terminal of their own, so wrap them in one:
wsx config set editor_cmd "xdg-terminal-exec --dir={path} nvim"
GUI editors work directly:
wsx config set editor_cmd "code"
A diff in a terminal pager. Use three dots so the diff starts at the merge base and a stale local main doesn't add noise:
wsx config set diff_cmd "alacritty -e sh -c 'cd {path} && git diff {base}...HEAD | delta'"
lazygit needs a terminal window, because wsx owns its own:
wsx config set lazygit_cmd "wezterm start -- lazygit"
If your editor hosts its own AI agent, see Editor-hosted agents to give it the workspace's context.
Editor-hosted agents
An AI agent inside your editor (magenta.nvim, Cursor, a VS Code extension) doesn't know what wsx knows about the workspace. wsx context writes that knowledge to one markdown file your editor can hand to its agent, and the agent reports back to the workspace's primary agent with wsx agent send.
The contract
-
Write the digest. Run
wsx context writefrom the worktree. It prints the file's path and exits 0. Outside a wsx worktree it exits non-zero, so an integration can be installed globally and stay inert elsewhere.wsx context show # print the digest wsx context write # write it to a file and print the path -
Refresh it. Your editor decides when to rerun
wsx context write. Good triggers are editor start, window focus, and opening the agent's chat panel. Guard against overlapping runs on rapid focus events. -
Hand it to the agent. Add the path through the editor's context mechanism. If the agent snapshots files when they're added, re-add the file after each refresh.
-
Report back. The digest tells the agent to report with
wsx agent send <primary> "<summary>", run from the worktree. The message arrives at the primary agent without a sender label.
The file lives at $XDG_STATE_HOME/wsx/context/<repo>/<workspace>.md and is readable only by you. It can quote the primary agent's last message, so don't commit or share it. The path follows the workspace name, so it changes after a rename.
See wsx context and wsx agent send.
What the digest contains
- Repo, workspace, branch, base ref and worktree path.
- Attached agents, with the primary marked.
- The last status the primary agent pushed.
- The recap: goal, state and next step.
- Up to 20 commits ahead of the base, and a count of uncommitted changes.
- The primary agent's last message, up to 2,000 characters.
- External instructions for the editor-hosted agent: check
git statusandgit diffbefore editing, keep edits small, leave branches, status and recap to the primary agent, and report back withwsx agent send.
Sections with no data are left out. wsx-spawned agents are told that an editor-hosted agent may share their worktree and that its messages arrive unlabeled.
neovim with magenta.nvim
magenta.nvim re-reads its context files before each request, so a digest that wsx keeps fresh stays live. Add this to your neovim config:
local worktrees = vim.fn.expand("~/.local/state/wsx/worktrees/")
local added_path, in_flight = nil, false
local function magenta_visible()
for _, win in ipairs(vim.api.nvim_list_wins()) do
local name = vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(win))
if name:find("Magenta Input", 1, true) then return true end
end
return false
end
local function wsx_context()
if in_flight or not vim.startswith(vim.fn.getcwd(), worktrees) then return end
in_flight = true
vim.system({ "wsx", "context", "write" }, { text = true }, function(out)
in_flight = false
if out.code ~= 0 then return end
local path = vim.trim(out.stdout)
if path == "" or path == added_path then return end
vim.schedule(function()
if magenta_visible() then
vim.cmd("Magenta context-files " .. vim.fn.fnameescape(path))
added_path = path
end
end)
end)
end
vim.api.nvim_create_autocmd({ "VimEnter", "FocusGained" }, { callback = wsx_context })
vim.api.nvim_create_autocmd("BufWinEnter", {
callback = function(ev)
if vim.api.nvim_buf_get_name(ev.buf):find("Magenta Input", 1, true) then
added_path = nil
wsx_context()
end
end,
})
vim.api.nvim_create_user_command("WsxContext", function()
added_path = nil
wsx_context()
end, {})
- The digest refreshes on start and on focus, and attaches when you open the magenta sidebar or start a new thread.
- The path is added only while the sidebar is open, because
:Magenta context-filesopens it otherwise. :WsxContextforces a refresh and re-add.- If you set
$XDG_STATE_HOME, changeworktreesto match.
Other editors
Any agent that can read a file can use the digest. Run wsx context write on the editor's start and focus events, ignore a non-zero exit, and pass the printed path to the agent: through a context-file API, or a rules file that tells the agent to read that path. The External instructions block handles the rest.
Related repos
Declare related repos when your work spans several repos that need to know about each other, such as a backend, a frontend and a marketing site. A workspace's agent can then read the related repos' source.
wsx repo set-related-repos backend frontend,marketing
wsx repo edit-related-repos backend # edit the list in $EDITOR
The value is a comma-separated list of registered repo names. When a workspace in backend starts its agent, wsx:
- passes each related repo's source path with
--add-dir(Claude Code and oh-my-pi); - tells the agent to treat those paths as read-only;
- tells the agent that when a related repo needs changes, it should create a workspace there with
wsx workspace create, hand the task to that workspace's agent withwsx agent send, and tell you which workspace owns it.
Each repo gets its own branch and pull request. Merge them in dependency order.
The read-only rule is an instruction, not a lock. Installing the wsx skill (see Agent skills) reinforces it. Unknown repo names in the list are skipped.
See wsx repo set-related-repos.
MCP server inheritance
Claude Code keys its MCP server config on the directory it starts in, so an agent in a worktree wouldn't see the MCP servers you added in the source repo. wsx copies the source repo's mcpServers from ~/.claude.json into the worktree's entry each time a workspace's agent starts.
- Servers you add to the source repo with
claude mcp addreach a workspace the next time its agent starts. - Archiving a workspace removes its entry from
~/.claude.json. - MCP configs often hold API tokens. Copying puts the same secret under a second key in the same file.
To turn it off, set mcp_mirror to false (see Settings). wsx then never reads or writes ~/.claude.json, and you can still add servers per workspace with claude mcp add while attached.
wsx config set mcp_mirror false
Remote access
Reach wsx from another machine or your phone. Run the whole dashboard in tmux and attach over ssh, drive a single Claude Code session from claude.ai, or attach to one agent with shared workspaces.
| You want | Use |
|---|---|
| The full dashboard from another computer | tmux and ssh |
| One reconnect command per machine | Named remotes |
| A Claude Code session from your phone or browser | Remote control |
| One agent from another machine, or from the dashboard's host picker | Shared workspaces |
tmux and ssh
On the host, start wsx inside tmux, or reattach if it's already running:
tmux new -As wsx 'wsx'
From any other machine:
ssh desktop -t tmux attach -t wsx
Workspaces and their agents keep running while you're detached.
- wsx's leader key is
Ctrl-x, so it doesn't collide with tmux'sCtrl-bprefix. - On a flaky network, use mosh:
mosh desktop -- tmux attach -t wsx. - Tailscale or another VPN makes the host reachable by name without port forwarding.
Named remotes
Save a reconnect command under a short name, then run it with wsx remote:
wsx remote # list saved names
wsx remote desktop # run the saved command
wsx config edit remotes # edit the list in $EDITOR
The remotes setting holds one name=command per line:
desktop=ssh -t desktop 'tmux attach -t wsx'
gpu=ssh gpu-box -t 'tmux -u attach -t main || tmux -u new -s main'
- There is no
wsx remote add. Add a line in the editor, and keep the existing lines unless you mean to drop them. - Only the first
=splits name from command, so the command can contain=. - The command runs with
sh -cand replaces the wsx process. When it exits, you're back at your shell. - A duplicate name uses its last value.
See wsx remote.
Remote control
wsx starts Claude Code with --remote-control, so each Claude session prints a URL and a QR code you can open on claude.ai/code or the Claude mobile apps. It applies to Claude Code agents only.
| Setting | Effect |
|---|---|
remote_control | Set to false to keep sessions local-only. |
remote_control_sandbox | Set to true to run remote-issued commands in Claude Code's --sandbox. |
- The relay uses your claude.ai account. Signed out or offline, the local session still works.
- Session state goes through Anthropic's relay, and anyone who can see your screen can see the session URL.
See Settings.
Shared workspaces
A shared workspace runs its agents inside tmux instead of as children of wsx. Quitting wsx or dropping your ssh connection doesn't stop them, and you can attach to an agent from another terminal or another machine.
Requires tmux 3.2 or later.
Create or convert
wsx workspace create <repo> --shared
wsx workspace share <repo> <slug>
wsx workspace unshare <repo> <slug>
On the dashboard:
| Key | Action |
|---|---|
S | New shared workspace. In the new-workspace form, Ctrl-s toggles shared. |
T | Toggle the selected workspace between shared and direct, after a confirmation. |
A running agent can't move into or out of tmux, so it must restart:
Trestarts running agents right away and resumes their conversations. Stopped agents start in tmux.shareandunsharechange only the flag. Running agents keep their old mode until they restart.share --restartasks the dashboard on that host to do whatTdoes, then waits and prints the tmux session names. It needs wsx running on the host, and fails without changes if no dashboard answers within a few seconds.
See wsx workspace share.
On the dashboard
A shared workspace shows a badge left of its branch: a network icon with nerd fonts, ◇ without.
| Badge color | Meaning |
|---|---|
| Green | The tmux session is alive, even if this wsx isn't attached to it. |
| Red | No live session backs it, so nobody can attach. |
After you restart wsx, a shared workspace's agent is still running. Press Enter on the row to reattach with its history intact.
Attach from another terminal
Each agent runs in a tmux session you can attach to directly:
tmux attach -t wsx-<repo>-<workspace>
Extra agents get wsx-<repo>-<workspace>-<agent><n>, for example wsx-myrepo-fix-bug-codex2. Characters other than letters, digits, _ and - become -. The name is fixed when the session starts, so renaming the workspace doesn't change it.
List shared workspaces and their sessions:
wsx shared list # repo, workspace, session, alive/(dead)
wsx shared list --all # also every ready workspace that isn't shared
wsx shared list --json
See wsx shared list.
Browse another machine
List the hosts in the shared_hosts setting, one name=ssh-destination per line:
wsx config edit shared_hosts
mini=eben@ebenmini.local
lab=user@lab.example.com
Press H on the dashboard to pick a host. For one-click access, add the $hosts segment to your dashboard footer (see Themes).
wsx lists the host's workspaces: live agents first (agent, branch with PR number and tags, repo/workspace), then dimmed rows marked not shared or stopped.
| Key | Action |
|---|---|
j / k | Move. |
Enter | Attach to a live agent. On a not shared or stopped row, share and restart it on the host after a confirmation, then attach. |
n / N | Create a workspace on the host, normal or yolo. |
r | Refresh the list. |
Esc | Close. |
- Sharing from the list restarts the agent, which cuts off what it was doing. Its conversation resumes.
Ctrl-x ddetaches from a remote agent and returns to the dashboard. The agent keeps running on the host.
Create a workspace on the host
n opens a form preset to the selected row's repo:
| Key | Action |
|---|---|
↑ / ↓ | Change repo. |
| Type | Set the name. Leave it empty for a generated name. |
Tab | Cycle the agent, starting from the host's default. |
Ctrl-y | Toggle yolo. |
Enter | Create it on the host as shared, run its setup script, start its agents, and attach. |
- If the workspace can't be created (name taken, unknown repo), the form returns with the host's reason.
- If it's created but its agents don't start, it appears in the list with the reason. Press
Enteron it once the host is fixed. - If its setup script fails, wsx doesn't attach. The list shows the exit code and the setup log path on the host.
- Creating gives up after 15 minutes. The host may still finish, so press
rbefore you retry.
Names that would need shell quoting can't be shared or created from the list. A host running an older wsx shows only its live shared sessions, with no sharing or creating.
Requirements for remote hosts
- Key-based ssh access. The background list fetch can't answer a password prompt.
wsxandtmuxon the PATH of a loginshon the host. Check withssh <host> "sh -lc 'which wsx tmux'". On macOS, PATH set only in~/.zshrcor~/.zprofile(including Homebrew's) is invisible tosh -l, so add it to~/.profiletoo.- The wsx dashboard running on the host to share or create from the list. Attaching to an already-shared workspace doesn't need it.
- Locally, only
ssh.
Scrollback
Reattaching shows only the session's current screen. wsx's scrollback starts fresh with each attach. tmux keeps its own scrollback, which you can reach with tmux copy mode (Ctrl-b [).
Desktop widgets
A desktop widget shows your live workspace count in the system bar, colored by the most urgent status, and jumps to any workspace in one click. wsx ships one for waybar, one for KDE Plasma 6, and one for the macOS menubar.
Every widget:
- shows a branch icon and the count of workspaces across every repo;
- takes its color from the most urgent status: blocked, done, waiting, or working;
- shows a per-workspace tooltip on hover;
- jumps to a workspace by opening it in the running TUI, attached, or by launching a new TUI in a terminal.
The setup command bakes in the path to wsx. Re-run it after you move or
reinstall wsx somewhere else.
Waybar (Linux)
wsx setup waybar
Setup writes wsx.jsonc and wsx.css into ~/.config/waybar/ and adds the
custom/wsx module to config.jsonc, saving a timestamped backup first. If
it doesn't recognize your config's layout, it prints the lines to paste
instead.
Click the module to open a workspace picker. It uses
walker by default. Set
WSX_WAYBAR_MENU to use another menu command.
When walker and elephant are installed, setup also adds an elephant menu and
a wsx walker theme, so the picker shows PR numbers, review marks, and diff
counts:
- The theme takes its colors from your current walker theme. Re-run
wsx setup waybarafter you switch walker themes. - Setup restarts elephant for you. Restart walker yourself to load the theme.
To remove it, delete "custom/wsx" and the wsx include entry from
config.jsonc, or restore the backup.
See wsx setup waybar and
wsx waybar.
KDE Plasma applet (Linux)
Requires KDE Plasma 6.
wsx setup plasma
Setup installs the applet into
~/.local/share/plasma/plasmoids/io.github.bakedbean.wsx. To add it to a
panel, right-click the panel, choose Add or Manage Widgets, and search for
"wsx".
Click the applet to list every repo's workspaces with their status glyph, status message, and PR number. Pick one to jump to it and raise its window. If the jump fails, the popup shows the error.
-
After you re-run setup with the applet already on a panel, restart plasmashell to load the new version:
kquitapp6 plasmashell && kstart plasmashell -
A terminal that serves several windows from one process can raise the wrong window of that terminal.
To remove it:
kpackagetool6 --type Plasma/Applet --remove io.github.bakedbean.wsx
See wsx setup plasma and
wsx desktop.
macOS menubar (SwiftBar)
Requires SwiftBar. Launch SwiftBar once and pick a plugin folder before you run setup.
brew install swiftbar
wsx setup menubar
Setup installs a wsx-menubar.10s.sh plugin into SwiftBar's plugin folder.
The dropdown lists every repo and its workspaces, with status glyph, dirty marker, diff stats, and PR number. Each workspace has a submenu:
| Item | Action |
|---|---|
| Jump | Open the workspace in the TUI. |
| Open PR | Open the pull request in your browser. |
| Copy worktree path | Copy the path to the clipboard. |
| Reveal in Finder | Show the worktree in Finder. |
A Project Manager submenu lists each workspace's recap (goal, state, and next step), blocked and waiting workspaces first. It mirrors the dashboard's project manager digest. Click a line to jump to that workspace.
To remove it, delete wsx-menubar.10s.sh from the plugin folder.
See wsx setup menubar and
wsx menubar.
Which terminal a jump opens
With no TUI running, a jump launches one in the first of these that applies:
| Order | Linux | macOS |
|---|---|---|
| 1 | terminal_cmd, if it contains {cmd} | terminal_cmd, if it contains {cmd} |
| 2 | $TERMINAL | iTerm2 |
| 3 | konsole, in a Plasma session | Terminal |
| 4 | alacritty |
terminal_cmd is also the dashboard's [t] terminal, which doesn't fill in
{cmd}. If you use [t], leave {cmd} out of terminal_cmd and let the
jump pick a terminal from the rest of the list. See
Editor, terminal, and diff.
CLI reference
Every wsx command and flag, grouped by command. Run wsx with no arguments to open the dashboard. wsx --help lists the command groups, wsx <group> --help lists a group's commands, and wsx --version prints the version.
wsx # open the dashboard
wsx --select <repo>/<slug> # open the dashboard with a workspace selected
wsx <group> --help
wsx --version
Conventions
| Convention | Meaning |
|---|---|
<value-or-@file> | A literal value, or @path to read the value from a file. |
"" as a value | Clears the setting. Per-repo values then fall back to the global setting. |
| Current workspace | Commands without --workspace act on $WSX_WORKSPACE_ID, else the worktree containing the current directory. |
--workspace <repo>/<slug> | Targets another workspace instead of the current one. |
--json | Prints machine-readable output. Timestamps are epoch milliseconds. |
wsx sets $WSX_WORKSPACE_ID and $WSX_AGENT_INSTANCE_ID in every agent session it spawns, so commands run by an agent know their workspace and sender. See Environment variables.
Command groups
| Group | Purpose | Page |
|---|---|---|
wsx workspace | Create, list, rename, share, tag and archive workspaces | wsx workspace |
wsx repo | Register repositories and set per-repo settings | wsx repo |
wsx agent | Add, remove and message agents | wsx agent |
wsx status | Report and read agent status | wsx status, recap, context |
wsx recap | Maintain the workspace recap | wsx status, recap, context |
wsx context | Print a context digest for editor-hosted agents | wsx status, recap, context |
wsx config | Get and set global settings | Other commands |
wsx theme | Validate and scaffold the bar theme file | Other commands |
wsx remote | Run saved remote shortcuts | Other commands |
wsx shared | List tmux-shared workspaces | Other commands |
wsx setup | Install the agent skills and desktop widgets | Other commands |
wsx waybar | Linux waybar module and workspace jumper | Other commands |
wsx desktop | Desktop-neutral status and jump for panel widgets | Other commands |
wsx menubar | macOS SwiftBar module and workspace jumper | Other commands |
wsx workspace
Commands that create, inspect and retire workspaces. Each workspace is a git worktree on its own branch, with one or more agents.
wsx workspace create
wsx workspace create <repo> [--name <slug>] [--yolo] [--shared] [--start [--json]] [--agent <kind>] [--prompt <text>]
Creates a branch and worktree in <repo>, like n on the dashboard.
| Flag | Effect |
|---|---|
--name <slug> | Workspace name. The branch is <branch_prefix>/<slug>. Default: a generated name like merry-birch. |
--yolo | Skip permission prompts in the agent session. |
--shared | Run the workspace's agents under tmux. See Shared workspaces. |
--start | Have the running dashboard start the agents in tmux, wait until they're live, and print the sessions to attach to. Implies --shared. |
--json | With --start only: print the workspace record on stdout, even when the start fails. |
--agent <kind> | claude, pi, hermes, codex or omp. Overrides the coding_agent setting. See Coding agents. |
--prompt <text> | Queue a first task for the primary agent, like an immediate wsx agent send. |
- Run from inside a workspace,
createinherits that workspace's yolo mode and agent kind unless you pass--yoloor--agent. It prints what it inherited. - wsx refuses a slug that git can't use in a branch name, or whose branch already exists or clashes with an existing branch path (
wsx/fooblockswsx/foo/bar). - A failing setup script prints a warning and its log path. The workspace is still created.
--startand--promptneed a running dashboard. Without one,--startfails after creating the workspace, and a--promptstays queued until a dashboard starts.
Starting work from a phone over SSH, with a dashboard already running in tmux on the host:
wsx workspace create backend --prompt "Fix the flaky input PTY tests"
Without --name, the agent renames the workspace and branch from the prompt. See Auto-rename.
wsx workspace list
wsx workspace list [<repo>] [--json]
Lists workspaces as tab-separated repo slug branch worktree_path rows, optionally for one repo.
--json prints one object per workspace with everything its dashboard row shows:
{
"repo": "backend",
"slug": "add-widgets",
"branch": "eg/add-widgets",
"path": "/home/me/.local/state/wsx/worktrees/backend/add-widgets",
"status": {"state": "working", "message": "running tests", "source": "model", "reported_at": 1790342366562},
"recap": {"goal_short": "POST /widgets", "state_short": "tests failing", "next_short": "fix validation"},
"agents": [{"id": 12, "label": "claude", "kind": "claude", "primary": true, "status": {"state": "working", "...": "..."}}],
"pr": {"state": "open", "number": 241, "url": "https://github.com/acme/backend/pull/241", "review": "approved", "unresolved": 0, "fetched_at": 1790342836000},
"tags": ["spike"]
}
statusis the workspace's combined status. See Status and recap.pris the last PR state the dashboard or a desktop widget cached.listmakes no network calls.pr.stateis one ofno_pr,draft,open,conflicted,mergedorclosed.- A field that was never set is
null.
wsx workspace path
wsx workspace path <repo> <slug>
Prints the workspace's worktree path, for cd "$(wsx workspace path backend my-slug)".
wsx workspace rename
wsx workspace rename <repo> <old-slug> <new-slug>
Renames the workspace and its git branch together. Use this instead of git branch -m, which leaves wsx out of sync.
- The worktree directory doesn't move. It keeps the old slug, and the command prints the unchanged path.
wsx workspace archive
wsx workspace archive <repo> <slug> [--keep-worktree] [--force-delete-branch]
Retires a workspace, like the dashboard's archive action. In order, it:
- stops the workspace's tmux agent sessions;
- stops tracked processes under the worktree (
SIGTERM, thenSIGKILLafter two seconds), unless--keep-worktree; - runs the repo's archive script;
- force-removes the worktree, unless
--keep-worktree. Uncommitted changes are lost. - deletes the branch, then unregisters the workspace.
| Flag | Effect |
|---|---|
--keep-worktree | Keep the worktree directory and leave its processes running. |
--force-delete-branch | Delete the branch even if it isn't merged. Without it, an unmerged branch is kept. |
wsx workspace share
wsx workspace share <repo> <slug> [--restart [--json]]
Switches a workspace to tmux-backed agent sessions, so other terminals or machines can attach. See Shared workspaces.
| Flag | Effect |
|---|---|
--restart | Have the running dashboard restart the agents in tmux now, and print the sessions to attach to. |
--json | With --restart only: print the workspace record as JSON. |
- Without
--restart, running sessions keep their current backend until they restart.
wsx workspace unshare
wsx workspace unshare <repo> <slug>
Switches a workspace back to direct (non-tmux) agent sessions. Running sessions change on their next restart.
wsx workspace tag
wsx workspace tag add|rm <tag>... [--workspace <repo>/<slug>]
wsx workspace tag list [--workspace <repo>/<slug> | --all] [--json]
Adds, removes or lists workspace tags on the current workspace, then prints its tags.
| Flag | Effect |
|---|---|
--workspace <repo>/<slug> | Act on another workspace. |
--all | list only: print tag<TAB>count for every tag in use. |
--json | list only: print an array of tags, or {"tag", "workspaces"} objects with --all. |
- Tags are lowercased. Each must be 1–32 characters of
a-z 0-9 - _, starting with a letter or digit. If any tag is invalid, nothing is written.
wsx repo
Commands that register git repositories with wsx and set their per-repo settings. <name> is the repo's wsx name, as shown by wsx repo list. Values marked <value-or-@file> accept @path to read from a file and "" to clear. The edit-* commands open the current value in $EDITOR.
wsx repo add
wsx repo add <path> [--name <name>] [--prefix <prefix>]
Registers the git working tree at <path>.
| Flag | Effect |
|---|---|
--name <name> | Name on the dashboard and in commands. Default: the directory name. |
--prefix <prefix> | Branch prefix for this repo. Usually omit it and set the global branch_prefix instead. |
wsx repo list
wsx repo list [--json]
Lists registered repos and their paths. --json prints {name, path, branch_prefix} objects.
wsx repo remove
wsx repo remove <name>
Unregisters a repo and all its workspaces. Nothing is deleted on disk: the repo and its worktrees stay where they are.
wsx repo set-name
wsx repo set-name <name> <new-name>
Renames the repo in wsx. Later commands, such as wsx workspace create, use the new name.
wsx repo set-path
wsx repo set-path <name> <path>
Points the repo at a source checkout that moved, keeping its name, settings and workspaces. Use it instead of remove and add.
- New workspaces branch from the new path. Existing worktrees came from the old checkout, so recreate them if the old path is gone.
wsx repo set-prefix
wsx repo set-prefix <name> <prefix>
Sets the repo's branch prefix. "" clears it, falling back to the global branch_prefix.
wsx repo set-base-branch
wsx repo set-base-branch <name> <ref>
Sets the ref new workspaces branch from, such as origin/main. wsx fetches it before each create. The dashboard's diff column also compares against it.
""clears it. New workspaces then branch from the source checkout's currentHEAD.
wsx repo set-instructions
wsx repo set-instructions <name> <value-or-@file>
Sets instructions added to the agent's system prompt in this repo's workspaces, after the global custom_instructions.
wsx repo set-setup
wsx repo set-setup <name> <value-or-@file>
wsx repo edit-setup <name>
Sets the script that runs in each new worktree. See Setup and archive scripts.
wsx repo set-archive
wsx repo set-archive <name> <value-or-@file>
wsx repo edit-archive <name>
Sets the script that runs before a worktree is archived. See Setup and archive scripts.
wsx repo set-pinned-commands
wsx repo set-pinned-commands <name> <value-or-@file>
wsx repo edit-pinned-commands <name>
Overrides the global pinned_commands for this repo. "" falls back to the global value. See Pinned commands.
wsx repo set-related-repos
wsx repo set-related-repos <name> <value-or-@file>
wsx repo edit-related-repos <name>
Sets a comma-separated list of other registered repos, such as frontend,marketing, that agents in this repo can also read. Unknown names are skipped. See Related repos.
wsx agent
Commands that add agents to a workspace and pass messages between them. See Multi-agent workspaces for how agents share a worktree.
An agent's label is its kind, with #N for the second and later agents of that kind: claude, claude#2, codex. primary always names the workspace's primary agent. An all-digit target is an instance id, which is unique across workspaces. send, reply, messages and wait identify you by $WSX_AGENT_INSTANCE_ID, which wsx sets in each agent session.
wsx agent list
wsx agent list [--workspace <repo>/<slug>] [--json]
Lists the workspace's agents with their instance ids and each agent's last status.
1 claude (primary) working — "running the test suite" (model, 3m ago)
2 claude#2 done (hook, 10m ago)
4 codex
--json prints {id, label, kind, primary, status} objects. status is {state, message, source, reported_at} or null.
wsx agent add
wsx agent add <kind>
Adds an agent of <kind> (claude, pi, hermes, codex or omp) to the current workspace and prints its label. The dashboard starts it.
wsx agent remove
wsx agent remove <label>
Removes an agent from the current workspace.
- You can't remove the primary agent. Archive the workspace instead.
- Messages still queued for the agent are discarded. The command reports how many.
wsx agent send
wsx agent send [--workspace <repo>/<slug>] [--file <path>|-] <label|instance-id> [<message...>|-]
Queues a message for an agent and prints its id, for example queued message #561 to claude#2 (2747 bytes).
| Flag | Effect |
|---|---|
--workspace <repo>/<slug> | Look up the label in another workspace. |
--file <path> | Read the body from a file. --file - or a body of - reads stdin. |
- The running dashboard delivers the message when the target is idle, starting the agent first if needed. With no dashboard running,
sendwarns and the message waits. - The recipient sees a banner with the message id, the sender and the reply command.
- Flags can go before or after the target. From the first message word on, everything is body text. To send text containing
--fileor--workspace, quote the whole message. - An empty body is refused. So is a target whose agent binary isn't installed.
wsx agent reply
wsx agent reply [--file <path>|-] [<msg-id>] [<message...>|-]
Queues a message to the sender of <msg-id> (561 or #561), wherever it lives. Without an id it answers the latest message you received.
- The first word counts as an id only when a body follows, so
wsx agent reply 42sends "42". Prefer the explicit id from the banner. - Messages sent from a shell or an editor agent have no sender to reply to.
wsx agent messages
wsx agent messages [--sent|--all] [--undelivered] [--limit <n>] [--id <msg-id>] [--json]
Lists your inbox as tab-separated rows, oldest first.
ID FROM TO BYTES CREATED DELIVERED
561 claude#2 claude 2747 2026-09-25T14:03:11Z 2026-09-25T14:03:14Z
562 workspacex/parent-task claude claude 812 2026-09-25T14:10:40Z queued
| Flag | Effect |
|---|---|
--sent | List messages you sent. |
--all | List every message in the current workspace. Works from a plain shell. |
--undelivered | Only queued and dropped messages. |
--limit <n> | Show the newest n. Default: 20. |
--id <msg-id> | Print one message's headers and full body. |
--json | Print {id, from_id, from, to_id, to, workspace, bytes, body, created_at, delivered_at, state, drop_reason} objects. |
DELIVEREDreadsdroppedwhen wsx gave up on a message, for example because the agent's binary is missing.--idshows the reason.
wsx agent wait
wsx agent wait [--from <sender>] [--after <msg-id>] [--timeout <secs>] [--done <agent>]
Blocks until a message for you arrives, then prints it like messages --id.
| Flag | Effect |
|---|---|
--from <sender> | Only messages from this agent: a label, primary, an instance id, or <repo>/<slug> <label>. |
--after <msg-id> | Only messages newer than this id. Without it, already-queued messages count. |
--timeout <secs> | Give up after this long. Default: 110. 0 waits forever. |
--done <agent> | Also return when this agent reports done or blocked after the wait (or the --after message) began. Works from a plain shell. |
waitdoesn't consume the message. The dashboard still injects it into your session, so you see it twice. Chain waits with--after <last id>.- For a request and reply:
senda task, thenwait --after <its id> --done <peer>.
wsx agent whoami
wsx agent whoami
Prints your agent label, instance id, workspace and whether you're the primary.
wsx status, recap, context
Commands an agent uses to report what it's doing, and that you or another agent use to read it back. See Status and recap for how the dashboard shows them.
wsx status set
wsx status set <working|waiting|blocked|done> [--message <text>] [--goal <text>] [--state <text>] [--next <text>] [--goal-short <text>] [--state-short <text>] [--next-short <text>]
Sets the calling agent's status in the current workspace.
| Flag | Effect |
|---|---|
--message <text>, -m <text> | A short note shown with the status. |
--goal, --state, --next and their -short forms | Update the recap in the same call. See wsx recap set. |
- From a shell without
$WSX_AGENT_INSTANCE_ID, the status is recorded against the primary agent. done,blockedandwaitinghold until the agent starts new work.workingholds until the turn ends. See Status and recap for the rules.
wsx status show
wsx status [show] [--workspace <repo>/<slug>] [--json]
Prints the workspace's combined status, then each agent's. Bare wsx status does the same.
status: blocked — "need your call on the schema" (model, 1m ago)
agents:
claude (primary): working — "implementing" (model, 4m ago)
claude#2: blocked — "need your call on the schema" (model, 1m ago)
--json prints {repo, slug, status, agents}, with the same shapes as wsx agent list --json.
wsx status clear
wsx status clear
Clears status in the current workspace. Run by an agent, it clears only that agent's status. Run from a shell, it clears every agent's.
wsx status from-hook
wsx status from-hook [--agent <kind>]
Reads an agent hook's JSON event on stdin and updates the status. wsx installs this as the agents' status hook, so you don't run it yourself. It always exits 0 and prints nothing. Codex uses wsx status from-notify the same way.
wsx recap set
wsx recap set [--goal <text>] [--state <text>] [--next <text>] [--goal-short <text>] [--state-short <text>] [--next-short <text>]
Updates the current workspace's recap. Pass at least one flag. Fields you omit keep their value.
| Flag | Field |
|---|---|
--goal | What the workspace is for. |
--state | Where the work stands. |
--next | The next step. |
--goal-short, --state-short, --next-short | Keyword versions for the dashboard row: identifiers, ticket and PR numbers, no filler. |
wsx recap show
wsx recap [show] [--workspace <repo>/<slug>] [--json]
Prints the recap. Bare wsx recap does the same.
wsx recap clear
wsx recap clear
Deletes the current workspace's recap.
wsx context show
wsx context show [--workspace <repo>/<slug>]
Prints a markdown digest of the workspace for an editor-hosted agent: branch, agents, status, recap, recent commits, the primary agent's last message, and instructions for the external agent. See Editor-hosted agents.
wsx context write
wsx context write
Writes the current workspace's digest to $XDG_STATE_HOME/wsx/context/<repo>/<slug>.md (default ~/.local/state) and prints the path.
- Outside a wsx worktree it exits non-zero, so a global editor hook stays inert elsewhere.
- The file can quote the agent's last message. Don't commit or share it.
- The path follows the workspace name. After a rename, the old file stays behind.
Other commands
Settings, themes, remote shortcuts, shared workspaces, setup helpers and desktop widgets.
wsx config get
wsx config get <key>
Prints a global setting's value, or (unset). See Settings for every key.
wsx config set
wsx config set <key> <value-or-@file>
Sets a global setting. @path reads the value from a file, and "" clears the setting back to its default. An unknown key is an error.
wsx config list
wsx config list
Lists every setting that has a value.
wsx config edit
wsx config edit <key>
Opens a setting's value in $EDITOR and saves it when you exit. Use it for multi-line values such as remotes and pinned_commands.
wsx theme check
wsx theme check [<path>]
Validates the bar theme file and prints every error. Without <path> it checks ~/.config/wsx/theme.toml, and a missing file there passes. See Themes.
- It also warns when the
bar_themesetting is off, since the file then has no effect.
wsx theme path
wsx theme path
Prints where wsx looks for theme.toml.
wsx theme init
wsx theme init
Writes the bundled default theme.toml to that path. It refuses to overwrite an existing file.
wsx remote
wsx remote [<name>]
Without a name, lists your saved remote shortcuts. With a name, replaces the wsx process with sh -c <command>. See Remote access.
- Shortcuts live in the
remotessetting as onename=commandline each. Add one withwsx config edit remotes. There is nowsx remote add.
wsx shared list
wsx shared list [--json] [--all]
Lists tmux-shared workspaces as tab-separated repo slug session alive rows, one per agent. See Shared workspaces.
| Flag | Effect |
|---|---|
--all | Also list every ready workspace that isn't shared. |
--json | Print records with each workspace's PR status. Needs gh for the PR status. |
wsx setup install-skill
wsx setup install-skill
Installs or updates the bundled wsx, agent-review and handoff skills for Claude Code, and for Codex and Hermes when they're installed. See Agent skills.
wsx setup waybar
wsx setup waybar
Installs the waybar module into ~/.config/waybar, plus the elephant menu and walker theme when those are installed. Linux only. See Desktop widgets.
wsx setup plasma
wsx setup plasma
Installs the KDE Plasma 6 panel applet. Linux only. See Desktop widgets.
wsx setup menubar
wsx setup menubar
Installs the SwiftBar plugin. macOS only. See Desktop widgets.
wsx waybar status
wsx waybar status
Prints the JSON for the waybar custom/wsx module. Linux only, like every wsx waybar command.
wsx waybar menu
wsx waybar menu
Opens a workspace picker and jumps to the one you choose. It uses walker unless WSX_WAYBAR_MENU names another menu command.
wsx waybar jump
wsx waybar jump <repo> <slug>
Selects the workspace in a running dashboard, or opens a new one in a terminal (terminal_cmd) with it selected.
wsx waybar menu-entries
wsx waybar menu-entries [--json]
Prints the workspace entries for the elephant menu as JSON.
wsx waybar refresh-prs
wsx waybar refresh-prs
Refreshes the cached PR state for every workspace. Needs gh.
wsx desktop status
wsx desktop status
Prints a JSON summary for panel widgets: count, class (the most urgent state), tooltip, and rows of {repo, slug, state, message, pr_number}.
wsx desktop jump
wsx desktop jump <repo> <slug>
Selects the workspace in a running dashboard and raises its window, or opens a new one. Linux only.
wsx menubar plugin
wsx menubar plugin
Prints the SwiftBar plugin document. macOS only, like every wsx menubar command.
wsx menubar jump
wsx menubar jump <repo> <slug>
Selects the workspace in a running dashboard, or opens a new one in a terminal.
wsx menubar copy-path
wsx menubar copy-path <repo> <slug>
Copies the workspace's worktree path to the clipboard.
wsx menubar refresh
wsx menubar refresh
Refreshes the cached git and PR indicators for every workspace.
Reference
Lookup tables for the environment wsx reads and the files it keeps.
Environment variables
Set these in the shell that starts wsx. wsx passes its environment on to the agents it starts.
Agent binaries and models
| Variable | Purpose |
|---|---|
WSX_CLAUDE_BIN | Path to claude. Default: claude on PATH. |
WSX_PI_BIN | Path to pi. Default: pi on PATH. |
WSX_PI_MODEL | Model pattern passed to pi as --model. |
WSX_PI_PROVIDER | Limits pi to one provider's models. |
WSX_HERMES_BIN | Path to hermes. Default: hermes on PATH. |
WSX_HERMES_MODEL | Model for Hermes, overriding ~/.hermes/config.yaml. |
WSX_HERMES_PROVIDER | Provider passed to Hermes as --provider. Hermes's default REPL mode may ignore it in favour of ~/.hermes/config.yaml. |
WSX_CODEX_BIN | Path to codex. Default: codex on PATH. |
WSX_CODEX_MODEL | Model passed to Codex as -m. |
WSX_OMP_BIN | Path to omp (oh-my-pi). Default: omp on PATH. |
WSX_OMP_MODEL | Model passed to omp as --model. |
See Coding agents.
Other tools
| Variable | Purpose |
|---|---|
WSX_TMUX_BIN | Path to tmux, for shared workspaces. |
WSX_SSH_BIN | Path to ssh, for browsing remote hosts. |
WSX_WAYBAR_MENU | Menu command for the Waybar workspace picker. Default: walker --dmenu. |
VISUAL, EDITOR | Editor for e when editor_cmd is unset. |
EDITOR | Editor for wsx config edit, the edit-* commands, and repo settings. Default: vi. |
TERMINAL | Terminal for t when terminal_cmd is unset. |
SHELL | Shell for setup and archive scripts. Falls back to bash for sh, dash and ash. |
Behavior and diagnostics
| Variable | Purpose |
|---|---|
WSX_RENAME_MODE | Auto-rename mode: claude (default), local, or off. See Auto-rename. |
RUST_LOG | Log filter. Default: info. Use wsx=debug for verbose logs. |
WSX_INPUT_TRACE | Set to 1 to log every key and mouse event to wsx.log. |
Paths
| Variable | Purpose |
|---|---|
XDG_STATE_HOME | Base of the state directory. Default: ~/.local/state. |
XDG_CONFIG_HOME | Base of the config directory. Default: ~/.config. |
XDG_RUNTIME_DIR | Base of the directory for the dashboard's control sockets. |
Set by wsx
wsx sets these for the processes it starts.
| Variable | Set for | Purpose |
|---|---|---|
WSX_WORKSPACE_ID | Agent sessions | The workspace the session belongs to. |
WSX_AGENT_INSTANCE_ID | Agent sessions | The agent instance, used to sign and receive messages. |
WSX_REPO_ROOT | Setup and archive scripts | The source repo's path. |
WSX_WORKTREE | Setup and archive scripts | The workspace's worktree path. |
See Multi-agent workspaces and Setup and archive scripts.
Storage and configuration files
wsx keeps its state under $XDG_STATE_HOME/wsx/ (default ~/.local/state/wsx/) and its config under $XDG_CONFIG_HOME/wsx/ (default ~/.config/wsx/).
wsx's own files
| Path | Contents |
|---|---|
~/.config/wsx/theme.toml | Optional bar theme, reloaded while wsx runs. See Themes. |
~/.local/state/wsx/state.db | SQLite database of repos, workspaces, agents and settings. |
~/.local/state/wsx/worktrees/<repo>/<workspace>/ | Workspace worktrees. |
~/.local/state/wsx/context/<repo>/<workspace>.md | Context digest from wsx context write. See Editor-hosted agents. |
~/.local/state/wsx/logs/wsx.log.<date> | The dashboard's log, one file per day. |
~/.local/state/wsx/logs/setup-<repo>-<workspace>.log | The last setup script run for a workspace. |
~/.local/state/wsx/logs/ws<id>-<time>.log | Output of a command run in the background. |
~/.local/state/wsx/omp-config.yml | Config overlay passed to oh-my-pi. |
~/.local/state/wsx/pi-session-report.ts | Extension passed to pi so it reports its session. |
$XDG_RUNTIME_DIR/wsx/tui-<pid>.sock | Control socket for each running dashboard. Without XDG_RUNTIME_DIR, under ~/.local/state/wsx/run/. |
Files wsx reads or writes elsewhere
| Path | Use |
|---|---|
~/.claude.json | Claude Code config. wsx copies MCP servers into worktree entries. See MCP server inheritance. |
~/.claude/projects/<encoded-cwd>/<session>.jsonl | Claude Code transcripts, read for status, resume and recaps. |
~/.claude/skills/, ~/.codex/skills/, ~/.hermes/skills/ | Where wsx setup install-skill installs skills. See Agent skills. |
~/plugins/wsx-claude-commands/, ~/.agents/plugins/marketplace.json | Your ~/.claude/commands mirrored as a Codex plugin when a Codex agent starts. |
Development
Notes for people working on wsx itself.
- Testing: run the test suite and the checks CI runs.
- Releasing: cut a release and publish its packages.
Testing
Run the same checks CI runs before you open a pull request:
cargo fmt --all --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo test --doc --all-features
The suite runs without any coding agent installed. Tests point WSX_CLAUDE_BIN and the other WSX_*_BIN variables at stand-ins such as cat.
Toolchain
rust-toolchain.toml pins the Rust version, so rustup installs the same rustfmt and clippy CI uses. Formatting can differ between Rust releases, so use the pinned toolchain for cargo fmt.
The Nix dev shell (nix develop) provides cargo, clippy, rustfmt, git and gh from nixpkgs and ignores rust-toolchain.toml. If cargo fmt --check passes there but fails in CI, run it with rustup's toolchain instead.
End-to-end checks
harness/ runs a locally built wsx against a throwaway sandbox in /tmp/wsx-test:
bash harness/smoke.sh # prints SMOKE PASS
harness/harness.sh up # build wsx and provision the sandbox
harness/harness.sh down # remove it
See harness/README.md for snapshots and state queries.
Releasing
A git tag drives each release. Merging a version bump to main creates the tag, and the rest is automatic.
Cut a release
- In a pull request, set the new version in
Cargo.toml, runcargo checkto updateCargo.lock, and set the same version innix/package.nix. - Merge the pull request.
The Tag release workflow (tag-release.yml) runs on each push to main that touches Cargo.toml. If no v<version> tag exists, it tags the merge commit and starts the Release workflow. A Cargo.toml change that keeps the version does nothing.
Tag by hand
If Tag release didn't run or failed, tag the merge commit and push the tag. A tag you push starts Release directly:
git tag v0.2.0 <merge-commit>
git push origin v0.2.0
The tag must match the version in Cargo.toml, or Release stops.
Rebuild an existing tag
If the tag exists but the release is missing or broken, run the Release workflow from the Actions tab with the tag name. It creates the release or replaces its assets.
What Release does
| Job | Does |
|---|---|
test | Runs the CI checks on Linux and macOS. Nothing publishes if they fail. |
build | Builds four targets and packages each as wsx-<version>-<target>.tar.gz with a .sha256. |
release | Creates the GitHub release and uploads the tarballs. |
homebrew | Opens a pull request that updates Formula/wsx.rb. |
| Runner | Target |
|---|---|
macos-latest | aarch64-apple-darwin |
macos-latest | x86_64-apple-darwin |
ubuntu-22.04 | x86_64-unknown-linux-gnu |
ubuntu-22.04-arm | aarch64-unknown-linux-gnu |
Linux builds use Ubuntu 22.04 so the binaries run on glibc 2.35 and later. The build fails if a binary's architecture doesn't match its target.
Update the formula by hand
Download the release tarballs and their .sha256 files into a directory, then:
scripts/update-homebrew-formula.sh 0.2.0 ./dist
The script changes only the version, URLs and checksums.
Nix
Bump version in nix/package.nix in the same commit as Cargo.toml. If the sessionx revision in Cargo.toml changes, refresh its hash and copy the hash field into outputHashes in nix/package.nix:
nix-prefetch-git --url https://github.com/bakedbean/sessionx --rev <rev>
crates.io
wsx isn't on crates.io. Cargo.toml sets publish = false, and the sessionx git dependency must be published first. After both change, cargo binstall wsx works without --git.