cwt 0.2.9

Claude Worktree Manager — TUI for managing git worktrees with Claude Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# cwt — Provider (Claude or Codex) Worktree Manager

A terminal UI for running parallel
provider (Claude or Codex) sessions in
isolated git worktrees. Built in Rust, it uses a local terminal multiplexer for
interactive session management, preferring zellij when available and falling
back to tmux.

> **The worktree is the first-class primitive** — sessions attach to worktrees,
> not the other way around.

```
Worktree (unit of work)
  |-- Branch (auto-created or user-specified)
  |-- Session (provider instance, 0 or 1 active)
  |-- Lifecycle: ephemeral | permanent
  |-- State: idle | running | waiting | done | shipping
```

## Why cwt?

When using a provider (Claude or Codex) on a real codebase, you often want to run multiple tasks
in parallel — fix a bug, add a feature, write tests — without them stepping on
each other. Git worktrees give you cheap, isolated copies of your repo. cwt
manages the lifecycle of those worktrees and the provider sessions inside them,
all from a single TUI.

- **Spin up a worktree in seconds** — auto-named, auto-branched, ready to go
- **Never lose work** — every deletion saves a `.patch` snapshot first
- **Stay organized** — ephemeral worktrees auto-clean; permanent ones stick
  around
- **See everything at once** — two-panel TUI with live session status, diff
  stats, and transcript previews
- **Scale up** — dispatch tasks in bulk, import GitHub issues, broadcast prompts
  across sessions

## Requirements

- **git** (with worktree support)
- **zellij** or **tmux** (interactive mode needs one local terminal multiplexer;
  zellij is preferred when both are installed)
- A provider CLI (`claude` or `codex`)

Optional:

- **gh** ([GitHub CLI]https://cli.github.com/) — for PR creation and CI status
- **podman** or **docker** — for per-worktree containers
- **ssh** — for remote worktrees

## Installation

### Prebuilt binaries with cargo-binstall

```sh
cargo binstall cwt
```

`cargo-binstall` downloads the prebuilt release archive when one is available
for your target. Install either `zellij` or `tmux` separately and make sure it
is on your `PATH`.

### Cargo (from crates.io)

```sh
cargo install cwt
```

`cargo install` does not install a terminal multiplexer for you. Install
`zellij` or `tmux` separately and make sure it is on your `PATH` before running
`cwt`.

### Nix (recommended)

cwt provides a Nix flake with builds for Linux and macOS (x86_64 and aarch64).
The Nix package includes `tmux` and `git` as runtime dependencies and wraps the
binary so they are always on `PATH`. If `zellij` is also available in your
environment, `cwt` will prefer it automatically for local interactive sessions.

```sh
# Run without installing
nix run github:0dragosh/cwt

# Install to your profile
nix profile install github:0dragosh/cwt
```

Add to a flake-based NixOS or home-manager configuration:

```nix
# flake.nix
{
  inputs.cwt.url = "github:0dragosh/cwt";

  # Option 1: use the overlay
  nixpkgs.overlays = [ cwt.overlays.default ];
  # then add pkgs.cwt to your packages

  # Option 2: reference the package directly
  environment.systemPackages = [ cwt.packages.${system}.default ];

  # Option 3: home-manager module (generates ~/.config/cwt/config.toml)
  imports = [ cwt.homeManagerModules.default ];
  programs.cwt = {
    enable = true;
    settings.session.default_permission = "elevated";
  };
}
```

See [docs/nix.md](docs/nix.md) for full home-manager module documentation.

### From source

```sh
git clone https://github.com/0dragosh/cwt.git
cd cwt
cargo build --release
# Binary at target/release/cwt — add it to your PATH
```

Make sure `git` is on your `PATH`, and make sure `zellij` or `tmux` is
installed and on your `PATH`. `cwt` cannot run its interactive workflows
without one of them.

## Quick Start

Interactive mode needs a local terminal multiplexer. If you launch `cwt` from a
regular shell, it will bootstrap into a `zellij` session when `zellij` is
installed, and otherwise fall back to `tmux`.

```sh
# 1. Navigate to any git repo
cd ~/my-project

# 2. Launch the TUI (cwt will bootstrap into zellij or tmux if needed)
cwt

# 3. Press 'n' to create a worktree (Enter for auto-generated name)
# 4. Press 's' to launch a provider session in it
# 5. Press 'Tab' to switch between the worktree list and inspector panels
```

Or use CLI commands directly:

```sh
cwt create my-feature --base main     # Create a worktree
cwt list                               # List all worktrees
cwt delete my-feature                  # Delete (saves a snapshot first)

# Dispatch parallel tasks — one worktree + session per task
cwt dispatch "implement auth" "add tests" "update docs"

# Import GitHub issues as worktrees
cwt import --github --limit 5

# Multi-repo mode
cwt add-repo ~/code/project-a
cwt add-repo ~/code/project-b
cwt forest                             # Launch forest TUI
cwt status                             # CLI summary across repos
```

## Features

### Worktree Management

- **Create** with auto-generated slug names or explicit names, from any base
  branch
- **Two-tier lifecycle**: ephemeral (auto-GC'd) and permanent (never
  auto-deleted)
- **Promote** ephemeral worktrees to permanent with a single keypress
- **Snapshots**: full diff saved as `.patch` before every deletion
- **Restore** previously deleted worktrees from their snapshots
- **Garbage collection**: prune old ephemeral worktrees, skipping those with
  running sessions, uncommitted changes, or unpushed commits
- **Setup scripts**: automatically run a script (e.g., `npm install`) after
  worktree creation

### TUI Interface

- **Two-panel layout**: worktree list (grouped by lifecycle) + inspector
  (details, diff stat, session info)
- **Fuzzy filter**: `/` to search/filter worktrees by name
- **Help overlay**: `?` for a full keybinding reference
- **Mouse support**: click to select, scroll to navigate
- **Status bar**: notification badges for waiting/done sessions

### Terminal Multiplexer Support

### Session Providers

- cwt supports two provider options: `claude` and `codex`
- You can set the default in config with `session.provider = "claude"` or `"codex"`
- You can change the active provider at runtime by pressing `o` in the TUI
- Press `O` to persist the currently selected provider as the default
- Local interactive mode prefers **zellij** and falls back to **tmux**
- Launching `cwt` outside a multiplexer auto-attaches to a `cwt` session in the
  preferred backend
- In zellij, provider sessions and shells open in named tabs; in tmux, they
  open in panes/windows
- **Launch** the provider (Claude or Codex) in the active multiplexer attached
  to any worktree
- **Resume** previous sessions using the active provider's resume flow (`--resume` for Claude)
- **Focus** an existing session tab/pane with a single keypress
- **Open shell** in any worktree directory via the active multiplexer
- Sessions survive TUI exit — closing cwt does not kill running sessions
- Remote sessions still use **tmux** on the remote host today

### Handoff

- **Bidirectional** patch transfer between your main working directory and any
  worktree
- Direction picker: worktree-to-local or local-to-worktree
- Diff preview before applying
- Gitignore gap warnings for untracked files that won't transfer

### Hooks (Real-Time Provider Integration)

- **Unix domain socket** listener for sub-second event delivery
- Worktrees created by the provider (Claude or Codex) outside cwt appear in the list within one
  second
- `cwt hooks install` patches `.claude/settings.json` and writes hook scripts to
  `.cwt/hooks/`

### Forest Mode (Multi-Repo)

- Register multiple repos with `cwt add-repo <path>`
- Three-panel TUI: repos | worktrees | inspector
- Aggregate session counts across all repos
- `cwt status` for a one-line CLI summary

### Agent Orchestration

- **Dispatch** multiple tasks in parallel: `cwt dispatch "task 1" "task 2" ...`
- **Import issues** from GitHub or Linear — creates worktrees and sessions per
  issue
- **Broadcast** a prompt to all running sessions simultaneously

### Ship Pipeline

- **Create PR** from a worktree with auto-generated body from session transcript
- **CI status tracking**: pass/fail/pending via `gh run list`
- **Ship it**: one-keypress macro to push, create PR, and mark as shipping

### Per-Worktree Containers

- Podman or Docker support (prefers Podman for rootless compatibility)
- Auto-detect `Containerfile`, `Dockerfile`, or
  `.devcontainer/devcontainer.json`
- Port management: auto-assign non-conflicting ports per worktree

### Permission Levels

cwt supports three permission tiers for provider sessions (Claude/Codex), giving you
fine-grained control over how much autonomy the provider gets:

| Level | Badge | Behavior |
| ----- | ----- | -------- |
| **Normal** | `N` (gray) | Plain provider command — asks for permission on each tool use (default) |
| **Elevated** | `E` (yellow) | Injects sandbox settings into `.claude/settings.local.json` — the provider runs autonomously within a sandbox |
| **Elevated Unsandboxed** | `U!` (red) | Appends `--dangerously-skip-permissions` — full autonomy, no sandbox |

- Press `m` to cycle through modes at runtime
- Press `M` to save the current mode as the default in your config
- The active level is shown as a badge in the top bar
- Each worktree gets its own `.claude/settings.local.json`, so there are no
  concurrency conflicts between sessions

Provider-specific mode behavior:

- Claude: `Elevated` injects sandbox settings; `Unsandboxed` uses `--dangerously-skip-permissions`.
- Codex: `Unsandboxed` uses `--full-auto`; `Elevated Unsandboxed` uses `--dangerously-bypass-approvals-and-sandbox`.

The elevated (sandboxed) provider mode writes these settings before launch:

```json
{
  "permissions": { "allow": [], "deny": [] },
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "allowUnsandboxedCommands": false
  }
}
```

### Remote Worktrees

- SSH-based remote host management
- Create and manage worktrees on remote machines
- Cross-machine handoff via patches
- Latency-aware polling with network status indicators

## Keybindings

### Worktree Actions

| Key     | Action                               | Context            |
| ------- | ------------------------------------ | ------------------ |
| `n`     | New worktree                         | Global             |
| `s`     | Launch/resume provider session       | Worktree selected  |
| `h`     | Handoff changes (worktree <-> local) | Worktree selected  |
| `p`     | Promote to permanent                 | Ephemeral selected |
| `d`     | Delete (with snapshot)               | Worktree selected  |
| `g`     | Run garbage collection               | Global             |
| `r`     | Restore from snapshot                | Global             |
| `Enter` | Launch/resume provider session       | Worktree selected  |
| `e`     | Open shell in worktree               | Worktree selected  |

### Orchestration

| Key | Action                           | Context |
| --- | -------------------------------- | ------- |
| `t` | Dispatch tasks (multi-worktree)  | Global  |
| `b` | Broadcast prompt to all sessions | Global  |

### Ship Pipeline

| Key | Action                              | Context           |
| --- | ----------------------------------- | ----------------- |
| `P` | Create PR (push + `gh pr create`)   | Worktree selected |
| `S` | Ship it (push + PR + mark shipping) | Worktree selected |
| `c` | Open CI logs in browser             | Worktree selected |

### Permissions

| Key | Action                                       | Context |
| --- | -------------------------------------------- | ------- |
| `m` | Cycle mode (Normal/Unsandboxed/Elevated Unsandboxed) | Global  |
| `M` | Save current mode as default               | Global  |
| `o` | Cycle session provider (Claude/Codex) at runtime | Global  |
| `O` | Save current provider as default           | Global  |

### Navigation

| Key          | Action                       | Context       |
| ------------ | ---------------------------- | ------------- |
| `j` / `Down` | Move down / scroll inspector | Global        |
| `k` / `Up`   | Move up / scroll inspector   | Global        |
| `Tab`        | Switch panel focus (forward) | Global        |
| `Shift+Tab`  | Switch panel focus (back)    | Global        |
| `R`          | Switch to repo panel         | Forest mode   |
| `/`          | Filter/search worktrees      | Worktree list |
| `Esc`        | Clear filter / close dialog  | Global        |
| `?`          | Toggle help overlay          | Global        |
| `q`          | Quit                         | Global        |
| `Ctrl+C`     | Force quit                   | Global        |

## CLI Commands

| Command                             | Description                        |
| ----------------------------------- | ---------------------------------- |
| `cwt`                               | Launch the TUI (default)           |
| `cwt tui`                           | Launch the TUI (explicit)          |
| `cwt list`                          | List all managed worktrees         |
| `cwt create [name] --base <branch>` | Create a new worktree              |
| `cwt create [name] --remote <host>` | Create on a remote host            |
| `cwt delete <name>`                 | Delete a worktree (saves snapshot) |
| `cwt promote <name>`                | Promote ephemeral to permanent     |
| `cwt gc [--execute]`                | Preview/run garbage collection     |
| `cwt hooks install`                 | Install provider hook scripts      |
| `cwt hooks uninstall`               | Remove hook scripts                |
| `cwt hooks status`                  | Show hook and socket status        |
| `cwt dispatch "task" ...`           | Dispatch parallel tasks            |
| `cwt import --github [--limit N]`   | Import GitHub issues as worktrees  |
| `cwt import --linear [--limit N]`   | Import Linear issues as worktrees  |
| `cwt prompt`                        | Print active cwt worktree name     |
| `cwt add-repo <path>`               | Register a repo for forest mode    |
| `cwt forest`                        | Launch forest (multi-repo) TUI     |
| `cwt status`                        | Summary of all repos and sessions  |

### Starship prompt integration

`cwt prompt` prints the current managed worktree name when your shell is inside
that worktree directory, and prints nothing otherwise. This makes it easy to
surface worktree context in [starship](https://starship.rs/) (similar to
worktrunk):

```toml
[custom.cwt]
command = "cwt prompt"
when = "cwt prompt | grep -q ."
format = "[$output]($style) "
style = "bold purple"
```

## Configuration

cwt reads configuration from `.cwt/config.toml` (per-project) and
`~/.config/cwt/config.toml` (global). Forest mode uses
`~/.config/cwt/forest.toml`.

```toml
[worktree]
dir = ".claude/worktrees"        # worktree root (relative to repo root)
max_ephemeral = 15               # GC threshold
auto_name = true                 # generate slug names when no name given

[setup]
script = ""                      # path to setup script (relative to repo root)
timeout_secs = 120               # setup script timeout

[session]
auto_launch = true               # launch session provider on worktree create
provider = "claude"              # "claude" | "codex"
command = ""                     # optional command override (defaults to provider binary)
provider_args = []               # extra args for provider invocation
default_permission = "normal"    # "normal", "elevated", or "elevated_unsandboxed"

# Permission-level overrides (optional — sensible defaults built in)
# [session.permissions.normal]
# extra_args = []
#
# [session.permissions.elevated]
# extra_args = []
# [session.permissions.elevated.settings_override.sandbox]
# enabled = true
# autoAllowBashIfSandboxed = true
#
# [session.permissions.elevated_unsandboxed]
# extra_args = ["--dangerously-skip-permissions"]

[handoff]
method = "patch"                 # "patch" or "cherry-pick"
warn_gitignore = true            # warn about .gitignore gaps

[ui]
theme = "default"                # color theme
show_diff_stat = true            # show file change counts in list

[container]
enabled = false                  # enable container support
runtime = "auto"                 # "podman", "docker", or "auto"
auto_ports = true                # auto-assign ports per worktree

# Remote hosts (one [[remote]] block per host)
[[remote]]
name = "build-server"
host = "build.example.com"
user = "dev"
worktree_dir = "/data/worktrees"
```

## Architecture

```
src/
  main.rs          # CLI parsing, TUI bootstrap, startup checks
  app.rs           # App state, event loop, keybinding dispatch, rendering
  config/          # TOML config loading (project + global fallback)
  state/           # JSON state persistence (.cwt/state.json)
  git/             # Git worktree, branch, and diff operations
  worktree/        # Worktree CRUD, handoff, snapshots, setup, slug generation
  session/         # provider session launcher, tracker, transcript parser
  tmux/            # local multiplexer abstraction (zellij + tmux)
  hooks/           # Unix socket listener, hook events, script installer
  forest/          # Multi-repo config, global index
  orchestration/   # Task dispatch, issue import, broadcast, dashboard
  ship/            # PR creation, CI status, ship pipeline
  env/             # Containers (Podman/Docker), devcontainer, ports, resources
  remote/          # SSH host management, remote sessions, cross-machine sync
  ui/              # ratatui widgets: layout, list, inspector, dialogs, theme
```

## Troubleshooting

**cwt says "zellij or tmux is required"** Install either `zellij` or `tmux`
first, then run `cwt` again. When both are installed locally, `cwt` prefers
`zellij`; otherwise it falls back to `tmux`.

**Worktrees don't appear after a provider creates them** Run
`cwt hooks install` to set up the real-time hook integration. Without hooks, cwt
discovers worktrees on periodic refresh (every few seconds).

**`gh` commands fail (PR creation, CI status)** Make sure the
[GitHub CLI](https://cli.github.com/) is installed and authenticated
(`gh auth login`).

**Sessions show "idle" even though the provider is running** cwt detects session
status by parsing `~/.claude/projects/` transcripts. If the path hash doesn't
match, status won't update. Restarting cwt re-scans the project directory.

**GC skipped a worktree I expected it to prune** GC never prunes worktrees with
running sessions, uncommitted changes, or unpushed commits. Check `cwt list` for
details.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code conventions,
and how to submit changes.

## Releases

Releases are managed automatically by
[release-plz](https://release-plz.ieni.dev/). Pushing conventional commits
(`fix:`, `feat:`, etc.) to `main` triggers a release PR with version bump,
lockfile update, and changelog. Merging the PR publishes to crates.io and
creates a GitHub Release.

## License

[MIT](LICENSE)