gwm: git worktree manager (Rust CLI + TUI)
SkillDev toolsManage git worktrees across any repository with the `gwm` Rust binary (CLI + ratatui TUI). Use when the user asks to create / list / remove / bootstrap / switch / link worktrees, run a command across worktrees (`gwm exec`) or reclaim build artifacts (`gwm clean`), materialise a PR into a worktree (`gwm review`), drive a multi-repo workspace (`--workspace`), run the JSON daemon / statusline (`gwm daemon`, `gwm statusline`), list or pin AI-agent sessions per worktree (`gwm agents`, Claude Code / Codex / opencode / Mistral Vibe detection), seed config from a preset (`gwm init --preset`), diagnose with `gwm doctor`, drive tmux/zellij, or wire `gcd` via `gwm shell-init`. Also covers multi-forge (#419): GitHub via `gh` **or** GitLab via `glab`, the `forge` / `[forge_hosts]` keys, and `gwm trust add`. Triggers on `gwm`, `gwq`, `git worktree`, `.gwm.toml`, `forge`, `forge_hosts`, `glab`, `gwm trust add`, GitLab MR, `gwm create`, `gwm list`, `gwm exec`, `gwm clean`, `gwm review`, `gwm daemon`, `gwm statusline`, `gwm agents`, `gwm note`, `gwm bootstrap`, `gwm doctor`, `gwm switch`, `gwm tmux`, `gwm zellij`, `gwm link`, `gwm status`, `gwm shell-init`, `gcd`, `feat/#`, `fix/#`, GitHub issue/PR linking on a worktree.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the gwm: git worktree manager (Rust CLI + TUI) skill
What this skill tells your AI
The instructions your AI receives, as published by kbrdn1/gwm-cli in skills/SKILL.md and read by ahel’s review.
Single-binary Rust tool that manages git worktrees with libgit2, a ratatui TUI, a declarative per-repo bootstrap (.gwm.toml), GitHub issue/PR linking, multiplexer hand-off (tmux / zellij), and a doctor command. Replaces project-specific bash wrappers with one portable binary that works in any git repo.
Source: https://github.com/kbrdn1/gwm-cli, latest stable 1.9.0 (machine contracts frozen since 1.0.0, MSRV 1.95).
1.9.0 is the licensing line, and it changes nothing you call. gwm is dual-licensed MIT OR Apache-2.0: MIT stays in full under LICENSE-MIT, LICENSE-APACHE joins it for its §3 patent grant, and a user picks either without having to say which. Both texts travel in every artefact (release archives, .deb, .rpm, AUR, the published crate). The libraries statically compiled in ship their notices under third-party/: libgit2 (GPLv2 with a linking exception, which is conditional on the notice travelling) and zlib. Behaviour-wise the line is what gwm prints: 165 em dashes rewritten across the binary's string literals and 49 more in gwm --help, so an error message or a completion script no longer carries a character the docs dropped; the Settings panel sizes to its active tab instead of a flat 60% of the frame; and a worktree path is spelled one way everywhere it is printed in full, ~ for $HOME, in the TUI header, the table and the sidebar alike (gwm path --format=json stays absolute).
1.8.0 is the density line, and it changes a default. The TUI is compact unless told otherwise: panes and sidebar sections carry no box rule, each is delimited by a filled one-line header (uppercase title with its bracketed key, counter pinned right on the same line), a muted rule separates the two panes, and the worktrees pane sizes itself to its row count instead of reserving a share of the split. That is two rows and two columns back per section. Focus reads on the header, which takes the focus role and the selection_bg fill, so the border is no longer the focus cue. [tui] layout = "bordered" restores gwm's pre-1.8 frames verbatim and is left untouched by the compact refinements. Two knobs ride along, both applying under either layout: [tui] status_one_line (on by default) folds the sidebar Status block onto one row joined by ·, leaving the path its own row; [tui] dim_unfocused (off) dims the inactive pane's body via the terminal's DIM attribute. A new theme role, section_bg, paints the compact header fill: an indexed colour, not a translucent white, so the mode holds up without truecolor, and distinct from selection_bg in every preset because that difference is what separates a focused header from an unfocused one. Overlays and modals keep their border either way, and their width now comes from one policy (modal_width(term_width, pct, min, max)) rather than the four it used to be, two of which got narrower as the terminal widened.
Per-worktree notes, shipped in 1.7.0 (#515). N opens the selected worktree's note in an editable modal (Esc writes and closes; Ctrl+e hands the same file to $EDITOR: editor_cmd, then $EDITOR, then vi, the handoff o uses in mode = "editor"), and the table carries a binary markdown marker (nf-oct-markdown, captioning its own column since #595) on the rows that have one. Notes are plain Markdown at <main-checkout>/.git/gwm/notes/<branch>.md: greppable with gwm shut down, never committed, and they survive gwm remove, which is why they live in the main checkout rather than in the worktree. gwm note show [slug] prints one and exits 1 when there is none; the --format=json rows carry it in an additive note field. The note is keyed on the branch, so a detached row cannot carry one (and says so), a rename moves the file, and gwm doctor reports a note whose branch is gone (not gwm clean, whose --yes only ever removes directories git already ignores). Presence means non-blank, since vi over an empty buffer writes one byte. N was unbound before, so a .gwm.toml binding a chord starting with N is now a prefix conflict refused at load time.
Security, 1.6.0 (GHSA-fffq-vg6f-gxqm, high). Every version up to and including 1.5.0 expanded lifecycle-hook placeholders into sh -c unescaped, so a branch name carrying ; / | / $ / backticks (all legal in a git ref, and a branch can arrive from someone else's push) ran arbitrary commands as the user, with no trust prompt in the path: the TOFU gate covers the repo's hooks, never the branch name flowing into them. Values are shell-escaped on expansion in 1.6.0, and hooks also get their context as GWM_* environment variables, which need no quoting at all. No backport: upgrade.
Free-form naming, shipped in 1.6.0 (#416 / #418). gwm create --name spike-redis skips the <type> <issue> <desc> triple; the name becomes the branch verbatim, so branch_pattern / path_pattern do not apply and branch-name-derived features (issue auto-linking, gitmoji) stay inactive on it, with gwm link as the way back. The TUI create and rename forms present the fields the repo's own patterns ask for, in pattern order, instead of the canonical triple, and move between the structured and free-form shapes in both directions.
Multi-forge, shipped in 1.5.0 (#419). Issue / PR lookups go through a Forge trait with two backends: GitHub via gh, GitLab via glab. forge = "github" | "gitlab" in .gwm.toml names the backend; [forge_hosts] in the user-level config authorises a self-hosted host; gwm trust add is the per-repo alternative. Worktrees, bootstrap, branch naming and link storage are forge-neutral: only the network layer knows which forge is in play. See Forge selection.
Shipped since 1.0.0: per-worktree notes (N, #515), the rich PR / issue view (I, #420 + its inline review comments #528), container execution on an exec profile (#421), multi-row selection with a batch delete (Space + d, #484), the symfony preset (#392), and a TUI delete that finally runs the remove hooks and records the undo journal (#521 / #531), all in 1.7.0; free-form naming in 1.6.0 (#416 / #418, above) alongside its security fix; multi-forge in 1.5.0 (#419, above); the help overlay (?, complete by construction: every Action must appear, #453) and a trio of TUI polish items in 1.4.0; agent session detection in 1.3.0 (#408): per-worktree AI-agent sessions (Claude Code, Codex, opencode, Mistral Vibe) read from on-disk artefacts, no process scanning, surfaced as gwm agents [attach|detach], a conditional AGENT column, an a overlay, a pinned-only Agents sidebar pane and multi-pins in branch config (gwm-agent-pin); plus the Windows daemon over a named pipe (#439). 1.0.0 shipped: gwm exec / gwm clean (#313), gwm review <PR#> (#308), the JSON API + gwm daemon + gwm statusline (#38 / #309), gwm init --preset (#37), multi-repo --workspace (#36), embedded PTY overlays (#35), a rebindable keymap incl. contextual modal keys + a live Settings panel with a Keys tab (#290 / #219 / #294), a Working Tree pane and a current-PR CI indicator (#300 / #299).
When to use this skill
- User runs or asks about any
gwm <subcommand>:init(incl.--preset),list(incl.--format json),create,remove,path/cd,bootstrap,sync,prune,doctor,types,completions,shell-init,switch(aliass),tmux,zellij,link,unlink,open,status,exec,clean,review,daemon,statusline,agents,new,pr,config,history,undo,trust,labels,milestones,hooks,commit-prefix,aliases,theme,tui keys.trusttakesadd | list | revoke | show. - User wants to fan a command out across worktrees (
gwm exec -- <cmd>) or reclaim build artifacts (gwm clean). - User wants to materialise a GitHub PR into a worktree to review/test it (
gwm review <PR#>, cross-fork, safe-by-default). - User wants a multi-repo workspace (
gwm --workspace <dir>), the JSON API / daemon (--format=json,gwm daemon,gwm statuslinefor shell prompts), or stack presets (gwm init --preset laravel|node|rust|go|python-uv|generic). - User asks which AI agents are working where:
gwm agents(per-worktree sessions + anunmatchedsection),gwm agents attach <wt> <id>/detach <wt> [<id>](multi-pins), the TUIaoverlay (selectj/k, pina, unpind, attach-by-idi), the pinned-onlyAgentssidebar pane, or theGWM_AGENTS_HOMEtest seam. - User opens the TUI by running
gwmalone in a repo, or the picker viagwm switch/gwm s. - User wants to delete several worktrees at once:
Spaceto mark rows in the TUI thend(#484), orgwm remove a b cfrom the shell. - User wants to write down where they were on a worktree:
Nin the TUI opens its note in an editable modal (#515),gwm note showreads it back,gwm doctorreports the ones whose branch is gone. - User asks about the redesigned TUI: PTY overlays (
l/Llazygit,r/Rreview,o/Oshell, #35), the Settings panel (4) and its live-editable Keys tab (#294), the Command Logs modal (3), the Working Tree file-explorer pane (#300), the current-PR CI indicator (#299),[tui.keys.modal.<context>]rebindable overlay keys (#219), and[tui.macro1]/[tui.macro2](h/H). - User mentions
.gwm.toml(per-repo config) or any of its sections:[worktree],[doctor],[tui],[tui.open],[git_tui],[review],[[bootstrap.copy]],[[bootstrap.guard]],[[bootstrap.no_symlink]],[[bootstrap.command]],[bootstrap.fallback.*]. - User asks about composable
whenpredicates (file_exists:,cmd_exists:,env_set:,env_eq:,glob_exists:) and the!/&&/||operators. - User wants to migrate a
tools/worktree-manager.shorgwq-based workflow togwm. - User asks how to set up the AWS RDS guard, the safe
.env.testingfallback, or the no-symlink invariant forvendor//node_modules/. - User asks about the branch convention
<type>/#<issue>-<desc>or its overrides. - User wants to link a worktree to a GitHub issue / PR, refresh GitHub status from inside the TUI, or run
gwm doctorto validate setup before pushing. - User mentions
gcd <pattern>(shell wrapper fromgwm shell-init <shell>). - User wants tmux / zellij hand-off (
gwm tmux <pat>,gwm zellij <pat>with optional--split). - User wants to configure the TUI
l(git_tui) /R(review) launchers, theo(open dispatch: shell/editor/finder), ory(yank path to clipboard) keys. - User asks about the
branch.<n>.gwm-basekey (review base-resolution anchor) or any of thelumen/claude/codex/aider/ghreview presets.
Prerequisites
command -v gwm # required — installed by `cargo install --path .` from the gwm-cli repo
command -v cargo # required at install time (1.95+ — the crate MSRV)
command -v git # required at runtime
command -v gh # OPTIONAL — the GitHub backend: live `gwm status`, TUI state, `R: review` preset
command -v glab # OPTIONAL — the GitLab backend (forge = "gitlab"); $GWM_GLAB overrides it
command -v tmux # OPTIONAL — needed by `gwm tmux`
command -v zellij # OPTIONAL — needed by `gwm zellij` (≥ 0.40 for `--cwd` on new-tab)
command -v lazygit # OPTIONAL — default `[git_tui]` binary backing the TUI `l` key
command -v lumen # OPTIONAL — `[review] tool = "lumen"` preset (default review tool)
command -v claude # OPTIONAL — `[review] tool = "claude"` preset
command -v codex # OPTIONAL — `[review] tool = "codex"` preset
command -v aider # OPTIONAL — `[review] tool = "aider"` preset
command -v pbcopy # OPTIONAL — TUI `y: yank` on macOS (wl-copy/xclip/xsel on Linux, clip on Windows)
gwm vendors libgit2, so no system git2 lib is needed. The binary is self-contained once compiled.
Install (from source)
git clone https://github.com/kbrdn1/gwm-cli.git
cd gwm-cli
cargo install --path . # → ~/.cargo/bin/gwm
gwm --version
No Rust toolchain at hand? cargo binstall gwm-cli pulls the prebuilt binary from the matching GitHub Release (via [package.metadata.binstall]) and drops it in ~/.cargo/bin/ without compiling git2/vendored-libgit2 from source: much faster on first install.
Prebuilt releases (Linux x86_64/aarch64, macOS Intel/Apple Silicon, Windows): https://github.com/kbrdn1/gwm-cli/releases. A Homebrew formula ships under packaging/homebrew/ and a Nix flake.nix is at the repo root.
Environment variables
| Variable | Effect |
|---|---|
GWM_GH / GWM_GLAB | Override the forge CLI binary (path or name). Read once, on the thread that resolves the forge, so the TUI's background fetch never races env mutation. |
GWM_ALLOW_BOOTSTRAP=1 | Same as --allow-bootstrap: skip the TOFU prompt. Also satisfies the forge host gate ; same ledger, same decision. |
GWM_TRUST_LEDGER | Path to the trust ledger (default ~/.config/gwm/trust.toml). |
GWM_NO_GLOBAL_CONFIG=1 | Ignore the user-level ~/.config/gwm/config.toml; repo-only config. |
GWM_HISTORY_FILE | Path to the destructive-op journal backing gwm history / gwm undo. |
GWM_AGENTS_HOME | Override the home scanned for agent session artefacts (test seam). |
Two global flags apply before or after any subcommand: --allow-bootstrap (skip the TOFU prompt: for CI and other non-interactive runs) and --deny-bootstrap (refuse bootstrap even when the ledger says trusted: for a first look at an unfamiliar repo). --workspace <DIR> is global too.
Default conventions
| What | Default | Override |
|---|---|---|
| Branch name | <type>/#<issue>-<desc> | .gwm.toml branch_pattern |
| Worktree dir name | <type>-<issue>-<desc> | .gwm.toml path_pattern |
| Worktree base | ~/cc-worktree/<repo>/ | .gwm.toml base |
| Bootstrap | none (just git worktree add) | .gwm.toml [bootstrap.*] |
| Doctor trunks | ["dev", "main"] | .gwm.toml [doctor] trunks |
| TUI confirm timer | 3 (clamped 0..=5) | .gwm.toml [tui] confirm_countdown_secs |
Branch types: feat, fix, hotfix, docs, test, refactor, chore, perf, ci, build.
Placeholders in patterns: {home}, {repo} (repo name), {repo_path} (main repo's absolute workdir), {repo_parent} (its parent dir), {type}, {issue}, {desc}. Tilde (~/…) is also expanded.
CLI reference
gwm # opens the TUI in the current repo
gwm --workspace <dir> # TUI / list / create across every git repo one level under <dir> (#36)
gwm init [--preset <stack>] [--show] # write .gwm.toml (refuses overwrite); --show prints it instead
gwm init --list-presets # built-in presets: laravel, node/nuxt, rust, go, python-uv, generic
gwm types [--gitmoji] # list supported branch types (--gitmoji adds each type's emoji)
gwm create <type> <issue> <desc> # create branch + worktree + bootstrap
# --skip-hooks on create / review / new / remove / bootstrap skips the [hooks.*] phases
gwm create feat 123 "user-authentication"
gwm create feat 123 foo --no-bootstrap # skip the .gwm.toml stages
gwm create feat 123 foo --reuse-branch # attach to an existing local branch instead of erroring
gwm create feat 123 foo --repo <name> # workspace mode: which child repo gets the worktree
gwm list [--format table|names|json] [--detect-pr] # `json` = stable schema (#38); names = completion
gwm path <pattern> [--format text|json] # print path (fuzzy match) → use $(gwm path auth)
gwm cd <pattern> # alias of `gwm path`
gwm note show [<slug>] # print a worktree's note; exit 1 when there is none (#515)
gwm bootstrap # re-run bootstrap on cwd worktree
gwm bootstrap <pattern> # ...or on a named worktree
gwm sync # fetch + rebase the cwd worktree onto its upstream
gwm sync <pattern> # ...or a fuzzy-matched worktree
gwm sync <pattern> --merge # merge the upstream instead of rebasing
gwm remove <PATTERN>... [--delete-branch] [--dry-run] [--force] # remove (fuzzy); -b drops the branch
# several patterns = one batch (#484): every one is resolved BEFORE anything is
# touched, so an unknown / ambiguous pattern removes nothing at all; duplicates
# collapse; the destructive loop does not stop at the first error and exits non-zero
gwm prune [--dry-run] # clean stale .git/worktrees entries
gwm doctor [--format text|json] # diagnose setup. Exit: 0=green, 1=warn, 2=fail
gwm completions <bash|elvish|fish|powershell|zsh> # emit a shell-completion script on stdout
gwm shell-init <bash|fish|powershell|zsh> # emit a `gcd <pattern>` wrapper to eval/source
gwm switch # interactive picker → prints chosen path to stdout (alias: s)
gwm tmux <pattern> [-p|--split] # open matched worktree in new tmux window (or split)
gwm zellij <pattern> [-p|--split] # open matched worktree in new zellij tab (or pane)
# --- fan-out & disk hygiene (#313) ---
gwm exec [<slug>...] [--jobs N] [--profile P] -- <cmd> # run <cmd> in each worktree; ✓/✗ rollup, non-zero on any fail
# --jobs N runs N in parallel; --profile picks an [exec.profiles.*] entry
# default = all non-main worktrees; slugs before `--`; cmd verbatim after `--`
gwm clean [<slug>...] [--yes] [--profile P] # report (or with --yes reclaim) target/ node_modules/ dist/ build/
# --profile picks a [clean.profiles.*] dir set
# --yes deletes ONLY git-ignored dirs holding no tracked files; never follows symlinks
# --- forge: GitHub (`gh`) or GitLab (`glab`) ---
gwm new <type> <desc> # create issue from a repo template, then its worktree
gwm pr [--draft] [--base <b>] [--render] # render [pr_template] body, then `gh pr create`
gwm review <PR#> [--name <b>] [--bootstrap] # materialise a PR into a worktree (cross-fork; safe-by-default) (#308)
gwm link issue|pr <N> [--worktree PAT] # bind a worktree to a GitHub issue or PR
gwm unlink issue|pr [--worktree PAT] # remove the explicit link
gwm open [issue|pr] [--print-url] [--worktree PAT] # open the linked URL in $BROWSER (--print-url just prints it)
gwm status [--worktree PAT] [--json] # show link + live forge state (needs `gh` / `glab`)
gwm labels list|push [--dry-run] [--prune] # sync the declarative [[labels]] set to origin
gwm milestones list|push [--dry-run] [--prune] # sync the declarative [[milestones]] set to origin
# --- daemon & shell consumers (#38 / #309) ---
gwm daemon [--socket <path>] [--poll-ms <n>] # long-running JSON-RPC 2.0 over a unix socket (list/doctor/path/subscribe)
gwm statusline [--socket <path>] [--watch] # one-line prompt summary off the daemon (degrades to blank when none)
# --- AI-agent sessions (#408) ---
gwm agents [--format table|json] # sessions per worktree (agent, freshness, last activity, id, name)
# + an `unmatched` section for sessions no worktree claimed
gwm agents attach <pat> <session-id> # pin a session to a worktree (pins ACCUMULATE — several per worktree)
gwm agents detach <pat> [<session-id>] # unpin one session, or every pin when the id is omitted
# detection reads on-disk artefacts only (Claude Code, Codex,
# opencode incl. its opencode.db, Mistral Vibe) — no process scanning;
# renames show (Claude live registry, Codex session_index, opencode titles);
# GWM_AGENTS_HOME overrides the scanned home (test seam)
# --- config / convention / introspection ---
gwm config get|set|unset|list|validate|path|edit # git-config-style editing of .gwm.toml (comment-preserving)
gwm history [--all] [--limit N] # recent destructive ops journal (newest first)
gwm undo [--bootstrap] # reverse the last destructive op for this repo
gwm trust add|list|show|revoke # TOFU trust ledger for .gwm.toml — `add` approves the
# current repo without running anything (answers the forge
# host gate, which is non-interactive by design)
gwm aliases # resolved CLI aliases (built-in + repo + user)
gwm commit-prefix [<branch>] [--unicode] [--branch <b>] # Gitmoji + Conventional prefix for the branch
gwm hooks install commit-msg [--force] # opt-in git hook that auto-prepends the commit prefix
gwm theme list|show [<name>] # role-based [theme] presets
gwm tui keys # resolved keymap (defaults + [tui.keys[.modal.*]] overrides)
gwm doctor
Runs a structured set of checks across config, environment, and worktree state. Designed for CI / pre-commit hooks:
| Exit | Meaning |
|---|---|
0 | All checks green |
1 | At least one warning (advisory, e.g. orphan gwm-style branch) |
2 | At least one failure (broken config, prunable worktree, etc.) |
Trunk branches the orphan-branch check treats as merge destinations come from [doctor] trunks = [...] (default ["dev", "main"]). Setting trunks = [] disables the filter (every unclaimed gwm-style branch is flagged).
One of the warnings is an orphan note (#515): a note under .git/gwm/notes/ whose branch no longer exists. This is the only place the note lifecycle is enforced, on purpose. gwm clean does not touch notes: its stated safety property is that --yes only removes directories git already ignores, and prose is neither regenerable nor ignored.
gwm shell-init → gcd <pattern>
The emitted wrapper defines a gcd function that resolves a worktree by fuzzy pattern and cds into it in one keystroke. Install per shell:
# zsh
echo 'eval "$(gwm shell-init zsh)"' >> ~/.zshrc
# bash
echo 'eval "$(gwm shell-init bash)"' >> ~/.bashrc
# fish
gwm shell-init fish | source # also add the eval to ~/.config/fish/config.fish
# powershell
Invoke-Expression (& gwm shell-init powershell | Out-String)
gcd (no arg) launches gwm switch (the picker) and cds into the chosen entry.
Forge selection (GitHub / GitLab)
Two backends implement the same Forge trait: GitHub via gh, GitLab via glab (which says MR where GitHub says PR). Everything else, worktrees, bootstrap, branch naming, the branch.<n>.gwm-* link storage: is forge-neutral.
Which backend. Inferred from the origin host for the vendors' own domains (github.com, ghe.com, gitlab.com). A self-hosted instance lives on an arbitrary domain and cannot be detected from a URL, so name it:
# .gwm.toml
forge = "gitlab" # or "github"
The key always wins over inference, in both directions.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 133
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
gwm- Source
- github.com/kbrdn1/gwm-cli