Skip to content

Command reference

This page lists every ws command, subcommand, argument and flag, in the order ws --help shows them. Each entry has a one-line description, its options with defaults, and a short example. For task-oriented help with the scripting commands, see Scripting ws. For the socket messages behind them, see the protocol.

The prefix key is Ctrl-Space: prefix t means press Ctrl-Space, let go, then press t. Every key is in Keys and mouse.

ws [OPTIONS] [COMMAND]...
ws <SUBCOMMAND> [OPTIONS] ...

ws with no subcommand starts or attaches to the background server. Any words that are not a subcommand are a command to run in a pane.

These work with every subcommand, before or after it. ws -L dev attach and ws attach -L dev are the same.

Option Default Description
--local off Run in this terminal instead of the background server. Nothing survives closing the terminal
-L, --socket-name <name> default Use a separate server with this name, like tmux -L. Also read from WS_SOCKET_NAME
-h, --help Print help for ws or any subcommand
-V, --version Print the version (top level only)

A server name may contain ASCII letters, digits, - and _. Any other character becomes _ in the socket file name. Each name has its own socket, its own saved session (~/.local/state/ws/state-<name>.db) and its own panes.

Start the background server if needed, then attach this terminal to it.

Argument Default Description
[COMMAND]... $SHELL Command to run in the pane. Everything after the program name goes to it
  • No server running: ws starts one with the command in its first pane. If a saved session is waiting (after a crash or ws kill-server --keep), it first asks whether to restore it. Without a terminal to ask, it restores.
  • Server running: ws attaches. A command opens in a new tab, in the directory you ran ws from.
  • With --local: ws runs inside this terminal with no server.
  • Inside a pane of the same server: ws refuses, because ws would show itself inside itself. Use prefix t for a new tab, or ws -L <name> to reach a different server.
Terminal window
ws # your shell, or reattach
ws claude --model opus # "--model opus" goes to claude
ws -L dev # a separate server named "dev"
ws --local htop # no server; ends when this terminal closes

To run a program whose name matches a ws subcommand, give its path, for example ws ./new.

Caution: because any word that isn’t a subcommand is a command to run, a mistyped or quoted subcommand doesn’t give a usage error. ws "tab new" or ws tabs list starts a server (or attaches to the running one) and tries to run tab new or tabs in a pane. Type subcommands as separate words, spelled as in this page: ws tab new. See Troubleshooting.

Attach to the running server. Plain ws does too, and starts one if needed.

ws attach fails with no ws server is running; `ws` starts one when nothing is running. Only one terminal is attached at a time; attaching from a second terminal detaches the first, which prints [detached: attached from another terminal].

Terminal window
ws attach
ws attach -L dev

Stop the background server and every pane in it. This also clears the saved session, so the next ws starts fresh. It returns once the server has exited (up to five seconds), so when it says stopped, the panes’ programs are gone.

Terminal window
ws kill-server
ws -L dev kill-server
Option Default Description
--keep off Save the session first and leave it on disk, so the next ws offers to restore it (agents resume their conversations).

Prints Stopped the ws server; its panes were closed., or with --keep, Stopped the ws server and saved the session; `ws` restores it. Fails if no server is running.

Stop the server keeping the session, start a new one that restores it without asking, and attach. Use it to switch to a newly installed build. Refuses to run inside a pane of the server it would restart.

Terminal window
ws restart
ws -L dev restart

A server from before ws restart existed answers that it’s too old; use ws kill-server and ws then. See Restart, keeping the session.

Look at what the server recorded about agent status. These commands read the server’s state file directly, so they work whether or not the server is running.

Hook events, status changes and detections, oldest first.

Option Default Description
--since <duration> 10m How far back: a number with s, m, h or d, such as 90s, 10m, 2h, 1d
--pane <id> all panes Only this pane. Its number is in the inspector title
Terminal window
ws debug events --since 2h --pane 3

A pane’s saved agent identity and its last 20 events.

Argument Description
<PANE> The pane id
Terminal window
ws debug status 3

Run a command in ws and record its raw terminal output to a file, for golden-test fixtures. It always runs in this terminal, never in the server. Avoid resizing the window while recording.

Argument Default Description
<FILE> Where to write the recording
[COMMAND]... $SHELL Command to record
Terminal window
ws record crates/ws-term/tests/fixtures/htop.wsrec htop

This is a development tool for ws itself.

Run a command in an off-screen terminal and save its screen, for documentation. It writes OUT.json, the screen as frame data (rows of styled text runs) that the website draws as a sharp, theme-aware picture, and OUT.svg, the same screen as an image. A setup script can stage what should be on screen first. The screenshots in this guide are made this way, from the real interface.

Argument or option Default Description
<OUT> Where to write, without extension
--size <COLSxROWS> 160x48 The terminal’s size
--setup <command> none A shell command to run once the program has drawn something
--wait-for <text> none Wait until this text is on screen. Repeatable, checked in order
--keys <keys> none Keys to send before capturing, such as "C-Space f", Enter, Esc. A word that isn’t a key name is typed; Click@X,Y and RClick@X,Y click a cell. Repeatable
--crop <NAME=X,Y,WxH> none Also save this part of the screen, as OUT-NAME.json and .svg. Repeatable
--settle <ms> 800 How long to let the screen settle before capturing
--timeout <s> 20 The longest wait for any one step
[COMMAND]... $SHELL What to run
Terminal window
ws capture /tmp/htop --size 120x30 --wait-for "Load average" -- htop

This is a development tool for ws itself.

Report an agent hook event to the ws that owns this pane. It reads the hook’s JSON from stdin.

Argument or option Default Description
<EVENT> Hook event name, such as Stop or UserPromptSubmit
--agent <name> none Agent CLI sending the event, such as claude or codex

Agent hooks call this; you don’t normally run it yourself. ws integrate installs the hooks. Outside ws (no WS_SOCKET or WS_PANE_ID), it does nothing. It always exits 0 and gives up after about a second, so it can never break or stall the agent.

Terminal window
echo '{"session_id":"abc"}' | ws report --agent claude Stop

Open a workstream from a template: a worktree and branch, the template’s panes, and its setup. See Workstreams.

Argument or option Default Description
<TEMPLATE> Template name (see ws templates)
[NAME] pr-<number> with --pr Workstream name, and the new branch’s name. Required unless --pr is given
--branch <branch> a new branch named NAME Check out this existing branch instead. Can’t be combined with --pr
--pr <number> none Check out this pull request (forks too) with gh pr checkout
--project <slug> none Link the workstream to this project

Inside ws, it opens in that ws and prints Opening <name> in ws. With -L <name> it goes to that server instead, even from inside another one (Opening <name> in ws -L <name>), starting it if it isn’t running. Outside, it starts or attaches to the server with the workstream, or runs it in this terminal with --local.

Terminal window
ws new design-system-pr button-focus
ws new design-system-pr --pr 101 --project button-a11y

Workspaces, for scripts. See Scripting ws.

Create a workspace with one shell tab, in the background. Prints nothing.

Argument or option Default Description
<NAME> The workspace’s name
--exists-ok off Succeed if a workspace with this name already exists. Names that differ only in case count as the same
Terminal window
ws workspace new Games --exists-ok

Workspaces and how many tabs each has: one line each, name[ (current)]<TAB>N tabs.

Option Default Description
--json off Print JSON: workspaces with their tabs, projects and panes
Terminal window
ws workspace list --json

Close a workspace and stop everything in it. The only workspace can’t be closed this way; use ws kill-server.

Argument Description
<NAME> The workspace’s name (exact, then ignoring case)
Terminal window
ws workspace close Games

Panes, for scripts. See Scripting ws.

Type text into a pane as a paste, then press Enter with --enter.

Argument or option Default Description
<PANE> The pane id
[TEXT]... empty The text. Words are joined with spaces. Omit it with --enter to just press Enter
--enter off Press Enter after the text
Terminal window
ws pane send 12 --enter cargo test

Close a pane and stop what runs in it. An emptied tab or workspace closes too. The last pane in ws can’t be closed this way.

Argument Description
<PANE> The pane id
Terminal window
ws pane close 12

Tabs, for scripts. See Scripting ws.

Open a tab running a command (default: your shell). Prints the new pane’s id.

Argument or option Default Description
--workspace <name> the workspace on screen Where to open the tab
--name <name> the focused pane’s label The tab’s name
--cwd <dir> the current directory Where the command starts
--project <slug> none Link the tab to this project
--focus off Move the view to the new tab
[COMMAND]... $SHELL What to run. Put it after --
Terminal window
ws tab new --workspace Games --name Snake --cwd ~/code/snake -- nvim plan.md

Close a tab and stop its panes. The last tab in ws can’t be closed this way.

Argument or option Default Description
<TAB> The tab’s name, or its number counting from 1
--workspace <name> the workspace on screen The workspace the tab is in
Terminal window
ws tab close --workspace Games Snake

Tabs as workspace<TAB>tab<TAB>project<TAB>panes<TAB>ports lines. project is - when the tab has none, panes is a comma-separated list, and ports is the tab’s listening TCP ports, comma-separated, or - (dev servers).

Option Default Description
--workspace <name> all workspaces Only this workspace (ignoring case)
--json off Print JSON instead
Terminal window
ws tab list --workspace Games | awk -F'\t' '{ print $2, $4 }'

Split a pane to the right, or below with --down, and run a command there. Prints the new pane’s id.

Argument or option Default Description
--pane <id> $WS_PANE_ID The pane to split. Required outside ws
--down off Split below instead of to the right
--cwd <dir> the current directory Where the new pane starts
--focus off Move the view to the new pane
--size <cells> half The new pane’s rows (with --down) or columns, as its program sees them
[COMMAND]... $SHELL What to run. Put it after --
Terminal window
agent=$(ws split --pane 4 -- claude)
ws split --pane "$agent" --down --size 24 -- cargo run

Projects: a goal plus the files that carry it across agent sessions. See Projects. Project folders live in ~/.local/share/ws/projects/ (or $XDG_DATA_HOME/ws/projects/).

Create a project folder (brief, plan, decisions, log) and open the brief in your editor.

Argument or option Default Description
<NAME> The project’s name. Its slug is made from it
--goal <text> "" The project’s goal
--workspace <name> none The workspace the project belongs to
--no-edit off Don’t open the brief

The editor is WS_EDITOR if set, else nvim if it’s installed, else $VISUAL or $EDITOR, else vi. The brief only opens when stdin is a terminal.

Terminal window
ws project new "Button a11y" --goal "Every button passes WCAG 2.1 AA" --no-edit

Projects, their status, next task and last handoff.

Terminal window
ws project list

One project’s details, open tasks and recent handoffs.

Argument Description
<SLUG> The project’s slug
Terminal window
ws project show button-a11y

Link the tab you run this in to a project. It only works inside a ws pane.

Argument Description
<SLUG> The project’s slug
Terminal window
ws project attach button-a11y

Mark a project done. It’s hidden from the sidebar, and its files are kept.

Argument Description
<SLUG> The project’s slug
Terminal window
ws project archive button-a11y

Rename a project. Its slug (folder and id) stays the same unless you pass --slug.

Argument or option Default Description
<SLUG> The project’s current slug
<NAME> The new name
--slug off Also move its folder to the new name’s slug. Linked tabs in the running ws follow
Terminal window
ws project rename button-a11y "Accessible buttons" --slug

Delete a project and its folder. It asks first.

Argument or option Default Description
<SLUG> The project’s slug
--yes off Don’t ask. Required when there is no terminal to ask on
Terminal window
ws project delete old-spike --yes

Open a tab with an agent continuing the project from its files.

Argument or option Default Description
<SLUG> The project’s slug
--workspace <name> the workspace on screen Put the tab in this workspace
--split off A shell on the left and the agent on the right. Same as --layout split
--layout <layout> agent agent (one pane), split (shell, then agent), or workbench (brief, plan and log in the editor, then the agent above a run pane)
--focus on without --workspace, off with it Move the view to the new tab
--agent <name> claude claude or codex
--model <model> the agent’s default Model to pass to the agent as --model
--task <n> the next open task The task number in plan.md
--dir <dir> the project’s remembered directory, else the current one Where the agent works. Remembered
--run <command> none (a shell) With --layout workbench: what the run pane runs, again on Enter after it exits. Remembered
--run-rows <n> half With --layout workbench: rows the run pane starts with. Remembered

With --workspace, --layout or --split, it builds the tab through the scripting requests and prints <name> in <workspace>: panes <ids>. Without them, it opens one agent tab in the ws you’re in (or starts and attaches ws) and prints Opened a tab continuing <slug>. That form also uses and remembers --dir, but it has no run pane: it ignores --run and --run-rows and prints ws: --run and --run-rows apply to --layout workbench; ignoring them.

Terminal window
ws project continue tetris --workspace Games --layout workbench --dir ~/code/tetris --run "cargo run" --run-rows 24
ws project continue button-a11y --agent codex --task 3

Ask the project’s idle agents to write their handoff entries in the project log.

Argument Description
<SLUG> The project’s slug
Terminal window
ws project handoff button-a11y

Settings. See that page for what’s in each tab.

Command Does
ws settings Open the Settings workspace in the running ws (as prefix S). With no ws running, runs the Keyboard shortcuts page here
ws settings keys The Keyboard shortcuts page, in this terminal (what each Settings tab runs)

Your keys, from ~/.config/ws/keys.toml over the defaults. See Changing keys.

Command Does
ws keys In a terminal: the shortcut editor (see Changing keys). Piped: the same as --list
ws keys --list Every command’s name, description and keys, and the leader
ws keys --check Load keys.toml and report errors and notes
ws keys --print-defaults A complete keys.toml with every default

AI usage recorded from agents’ transcripts: tokens by agent and model, Codex’s rate limit, and the commands, skills and tools agents used. See AI usage.

Command Does
ws usage Today (--week from Monday, --all everything; --json for scripts)
ws usage backfill Read the transcripts of sessions recorded before usage was, once
ws usage clear --all / --since YYYY-MM-DD / --session ID Forget recorded usage (asks first; --yes doesn’t). The transcripts aren’t touched

The agent memory store: facts, decisions, preferences, tasks and handoffs that every agent can share. See Memory. Ids can be given as any unique start or end of 4+ characters; lists show the last 6.

Command Does
ws mem add [TEXT]... Store an item; first line is the title. Reads stdin with no text. Options: --kind (note), --scope (repo here, else global), --title, --tag, --link kind:target (file, pr, commit, url, workstream, project, memory), --pin, --propose
ws mem search <QUERY>... Full-text search, best first. --scope, --kind, --all (any status), --limit (20), --json
ws mem list Newest first, pinned on top. --scope, --kind, --status, --all, --json
ws mem show <ID> Text, provenance, links and history. --json
ws mem edit <ID> Edit in your editor; saving makes a new version. --in-place fixes the text without one
ws mem approve / reject / archive <ID>... Change status
ws mem pin / unpin <ID> Always (or no longer always) include it in agents’ context
ws mem forget [ID]... Delete for good; also --scope, --since YYYY-MM-DD, --session <id>. Asks first unless --yes
ws mem export JSON lines with provenance. --scope, --kind, --all
ws mem import <FILE> Items from an export, as proposed; - reads stdin
ws mem lint Links to missing files, expired tasks, items unused for --days (90)
ws mem summarize [SESSION] Summarize a session now with memory.summarize.command: a summary item and up to five proposed items. Default: the latest session in this pane
ws mem sessions Agent sessions ws recorded, newest first: this repo’s, or --all, --project <slug>, --workstream <name>. --limit (20), --json
ws mem path The database’s path

Scopes are written global, repo, workspace, workstream, project (worked out from the current directory and pane) or kind:key (repo:github.com/org/name, project:button-a11y). With -L <name> (or inside a named server’s pane), ws mem uses that server’s store, memory-<name>.db.

Terminal window
ws mem add "Run bin/setup_db before specs" --kind fact --link file:bin/setup_db
ws mem search setup db --json | jq -r '.[].title'
ws mem export --all > memory-backup.jsonl

Print the context pack for an agent starting here: what memory holds for this directory’s workstream, project, workspace, repo and global scopes, within the token budget.

Option Default Description
--for <AGENT> none The agent it’s for (claude, codex). Prints nothing unless memory.deliver_to includes it, and never fails, since it runs inside agents’ launch commands
--budget <TOKENS> memory.budget (1500) Size cap, about 4 characters per token
--from <SESSION> none Start with what that session (an id from ws mem sessions --json) did and left: the handoff pack
--then <TEXT> none Text to put after the pack, such as a first prompt
--delta <SESSION> none Instead of the pack: only what happened since that session’s turn, for an agent resuming it (with --from, the session being left). What ws handoff uses when it goes back to an agent
--toml off Print it as a TOML string, for codex -c developer_instructions=…; with --for codex, your own developer_instructions come first
--json off The text, the ids used, and how many didn’t fit

Continue a pane’s work in another agent: a new pane beside it runs the agent with a handoff pack (what the pane’s last agent session did and left, plus memory) and a prompt to carry on. See Memory.

Option Default Description
--to <AGENT> required claude, codex, or any [agents.<kind>] in config.toml
--model <MODEL> none Passed with the agent’s model flag
--pane <ID> this pane The pane to take over from
--prompt <TEXT> carry on from the handoff The new agent’s first prompt
--no-ask off Skip asking the old agent for a handoff before switching
--fresh off Start a new session even when an earlier session of that agent could be gone back to
--replace off Close the old pane once the new one is up
--no-focus off Don’t focus the new pane

Prints <new pane><TAB><old agent> → <new agent>, followed by the handoff request’s outcome when ws asked for one. Going back to an earlier session, it says → back to <agent> (resumed), or → back to <agent> in pane <N> when that session is still running there.

Open a review tab for each pull request: a worktree with the PR checked out, .ws/pr.md with its description, comments, reviews and inline threads, and your review agent started as a new session. See Reviewing PRs.

Terminal window
ws review https://github.com/org/repo/pull/12
ws review billing/12 billing/14 org/repo#88
ws review --update billing/12
Argument or option Default Description
<PRS>... required (except with --post or --send) URLs, alias/N or alias#N, org/repo#N, or numbers in the current directory’s repo; separate arguments or comma-separated
--post off Post the review in .ws/review.md: shows it and asks approve, comment, request changes, edit or quit (needs a terminal). PR: the one given, else the review worktree you’re in
--send off Send your comments on this branch’s changes (.ws/review.md at the top of the repo, from :WsComment) to the agent in this tab as one prompt, then move the file to .ws/review-sent-<time>.md. See Reviewing your own agents’ work
--print off With --send: print the prompt and send nothing
--update off Pull new commits, rewrite .ws/pr.md and start the review over in a new tab, replacing an open one
--no-focus off Don’t switch to the first review’s tab

Prints label<TAB>opened|updated|already open<TAB>pane for each PR. Errors are printed per PR on stderr, and the exit status is 1 if any PR didn’t open. Settings are in ~/.config/ws/config.toml under [review].

Send a PR’s unresolved review threads to the agent in this tab, as one “address these” prompt. It shows exactly what it will send and asks first. See Reviewing PRs.

Terminal window
ws pr comments # the PR for the branch checked out here
ws pr comments billing/12
ws pr comments 61 --print # just print the prompt
Argument or option Default Description
[PR] the branch’s PR A URL, alias/N, org/repo#N, or a number in this repo
--yes off Send without asking (otherwise it needs a terminal to ask)
--print off Print the prompt and send nothing; works outside ws

It prints label: sent N unresolved threads to the agent in pane P, or label: no unresolved review comments. Sending must run in a ws pane whose tab has an agent. It never resolves threads or replies on GitHub. The prompt’s first line is [pr_comments] prompt in ~/.config/ws/config.toml.

Finish a workstream: close its tab (stopping its processes) and remove its git worktree. The branch stays, and so does any project linked to the tab.

Terminal window
ws done # the workstream this pane is in
ws done pr-101 # by name
ws done pr-101 --force
Argument or option Default Description
[NAME] this pane’s workstream The workstream’s name. Default: the one whose tab this pane is in. Outside ws, the name is required.
--force off Remove it even with uncommitted changes or commits that aren’t on any remote branch.

Without --force, ws done refuses and says why when the worktree has uncommitted changes, or when its current commit isn’t on any remote branch (neither pushed nor merged):

pr-101 isn't done: 2 uncommitted changes; feat/login has commits that aren't pushed or merged (--force removes it anyway)

On success it prints Done: the tab is closed; the worktree is removed in a moment (the branch stays). Run from inside the workstream, the pane may close before that line appears; that’s still success. It can’t close the last tab in ws. See Finishing a workstream.

Open a file in this workstream’s Neovim and focus it. Outside ws, it runs nvim directly.

Argument Description
<LOCATION> path[:line[:col]]. Relative paths are resolved from the current directory
Terminal window
ws open app/models/user.rb:42

Read a Markdown file, rendered, updating as it changes. See the Markdown viewer.

Argument or option Default Description
<FILE> The Markdown file
-s, --split off Inside ws, open it in a new pane beside this one, on the right, and focus it
--below off With --split: below instead of on the right

Outside ws, --split is ignored and the file opens in this terminal.

Terminal window
ws view README.md
ws view --split --below log.md

The Dashboard page, in this pane: what needs you now (agents, workstreams, review requests), how to get going with your own keys, agent hooks, what ws doctor found, and buttons to start things and go to your workspaces and projects. ws keeps one as the first tab of the Dashboard workspace; see Getting started.

Key or mouse Action
Tab / Shift-Tab, arrows, h / l Move between buttons
Enter, space, a click Run the button: go to the agent’s or workstream’s pane, run the command as if you pressed its key, go to the workspace, or continue the project (ws project continue)
j / k, the wheel Scroll
r Read everything again, ws doctor and review requests included
q Close the page

It asks the server what’s happening every 2 seconds (dashboard), ws doctor once when it opens, and GitHub for review requests every 2 minutes. Outside ws there’s no “Now” section, and buttons say there’s no server.

List workstream templates from ~/.config/ws/templates, with each template’s repo and panes.

Terminal window
ws templates

Check the tools, hooks, config and terminal ws depends on, and say what to fix. Exits 1 if something is broken. See Troubleshooting.

Option Default Description
--fix off Apply the safe fixes first (remove stale sockets, create directories), after asking
--json off Print the checks as a JSON array of {area, name, level, detail, fix} objects, for tools. level is ok, warn or fail; fix is left out when there’s nothing to fix. The exit code is the same

The running server uses ws doctor --json for the ✗ doctor: N badge in its top bar; see the interface.

Terminal window
ws doctor --fix
ws doctor --json | jq -r '.[] | select(.level == "fail") | .name'

Install the hooks that let ws see an agent’s status. See Agents.

Argument or option Default Description
<TARGET> claude: user-level hooks in ~/.claude/settings.json. codex: user-level hooks in ~/.codex/hooks.json (or $CODEX_HOME/hooks.json)
--remove off Remove the hooks instead
--print off Print the resulting settings without writing them
--bin <path> this ws binary The ws binary the hooks should run
--mcp off Register ws mcp (agent memory) as a user-level MCP server named ws, through the agent’s own CLI (claude mcp add --scope user, codex mcp add), instead of installing hooks. With --remove, unregister it; with --print, show the commands

It keeps a backup of the previous file next to it. For Codex, it also makes sure Codex’s hooks feature is on and its daemon_auto_start feature is off, setting features.hooks = true and features.daemon_auto_start = false in Codex’s config.toml if needed. If Codex’s shared daemon is running, it says how to stop it.

Terminal window
ws integrate claude
ws integrate codex --print
ws integrate claude --remove
ws integrate claude --mcp
ws integrate codex --mcp --print

Agent memory as an MCP server on stdin/stdout, for Claude Code, Codex and other MCP clients. Agents start it themselves once it’s registered (ws integrate <agent> --mcp). Its tools: memory_context, memory_search, memory_get, memory_propose, memory_handoff, session_history. See Memory.

Print help for ws or a subcommand. ws help tab new is the same as ws tab new --help.

ws __serve is the background server itself. ws starts it in its own session so closing the terminal doesn’t stop it. It is hidden from help and not meant to be run by hand. Its flags (--cols, --rows, --workstream, --restore) are internal and may change.

Code Meaning
0 Success. ws report always exits 0
1 The command failed. The reason is printed as Error: … on stderr. ws doctor exits 1 when a check fails
2 A usage error, such as an unknown flag or a missing argument
Variable Read by Meaning
WS_SOCKET_NAME every command Same as -L: the server name to use
WS_SOCKET scripting commands, ws open, ws report, ws new, ws project … Set in every pane: the socket of the ws that owns it
WS_PANE_ID ws split, ws view --split, ws open, ws report, ws project attach Set in every pane: its id
WS_WORKSPACE your scripts Set in every pane: its workspace’s name
WS_WORKSTREAM your scripts Set in workstream template panes: the workstream’s name
WS_EDITOR ws project new, workbench layout The editor to use

See Configuration for files ws reads, and the protocol for how panes use these variables.