@ogpoyraz/wtx
v0.8.3
Published
Multi-repo git worktree manager
Maintainers
Readme
wtx
Multi-repo git worktree manager built for parallel development — human or AI.
Create isolated worktrees across every repo you maintain, with working dependencies, synced env files, deterministic ports, and one-command hand-off to coding agents like Claude Code, Codex, OpenCode, or Cursor.
$ wtx create feat/auth --agent claude
my-api
◌ Fetching origin/main...
✓ Worktree created → ~/Repos/my-api-wt/feat/auth
✓ Synced .env
✓ Safely linked 214 packages
✓ Done — agent spawned in tmux session wtx-my-api-feat-authWhy not plain git worktree?
git worktree add gives you a directory — no dependencies, no .env, no port-conflict solution, no cleanup discipline. Every wrapper solves one slice.
| | git worktree | single-repo managers | wtx |
|---|---|---|---|
| Multi-repo in one command | – | – | ✓ |
| Working deps after create | manual | hooks only | adapters + safe links |
| .env sync from main | manual | some | ✓ |
| Parallel dev-server ports | collide | – | deterministic {port} |
| Safe removal (dirty guards) | none | varies | boundary-checked |
| Spawn coding agents | – | some | ✓ tmux-aware |
| MCP server for agents | – | – | ✓ |
Installation
npm install -g @ogpoyraz/wtxShell integration
Add to ~/.zshrc, ~/.bashrc, or your fish config:
eval "$(wtx init zsh)" # or bash, or fishThis enables wtx cd <repo> <branch> to change directories in your current shell.
First run
Run any command without a config and wtx walks you through setup: it scans common dev directories for git repos, lets you pick which ones to manage, and writes ~/.config/wtx/config.json. Prefer manual? wtx config init starts the same wizard; non-interactive shells print exact steps instead.
Quick Start
# create a worktree (deps + sync_files handled automatically)
wtx create ogp/my-feature --repo my-frontend
# create a dependent worktree from another branch
wtx create ogp/my-ui --repo my-frontend --base ogp/my-api
# hand it to a coding agent
wtx create ogp/my-feature --repo my-frontend --agent claude --prompt "add OAuth login"
# list everything across all repos
wtx ls
# rebase onto the recorded base (main for independent branches)
wtx rebase ogp/my-feature
# remove when merged — refuses dirty worktrees unless forced
wtx remove ogp/my-featureIf origin already has a branch with that name owned by someone else, wtx warns and creates your own branch from base instead of tracking theirs. Use --track to adopt theirs, or --local when local and remote diverged.
Commands
| Command | Args | Flags | Description |
|---|---|---|---|
| create | <branch> | --repo, --base, --open, --ide, --track, --local, --agent <name>, --prompt <text>, --deps <strategy> | Create worktree(s), sync files, prepare deps, optionally spawn an agent |
| pull | <pr-link> | --repo | Fetch a GitHub PR and create its worktree |
| pull-branch | [branch] | --repo | Fast-forward pull a worktree's branch (auto-detects repo from cwd) |
| remove | <branch> | --repo, --force, --yes | Remove worktree(s), clean empty dirs |
| rename | <old-branch> <new-branch> | --repo | Rename the branch and move the checkout to the matching new directory |
| prune | | --repo, --force, --yes | Remove worktrees whose PR has merged |
| open | <branch> | --repo, --ide | Open worktree in IDE |
| rebase | <branch> | --repo, --onto <ref> | Rebase worktree onto its recorded base, or an explicit ref |
| fetch | | --repo | Fetch main for each repo |
| sync | <branch> | --repo | Re-copy sync files, run post-sync hooks |
| deps | [branch] | --repo, --install, --symlink, --json | Inspect or switch dependency strategy; omit the branch and --install runs in the main checkout |
| ls | | --repo, --pr, --json | List all worktrees with clean/dirty state |
| status | <branch> | --repo, --base <ref>, --json | Ahead/behind against base, dirty files, rebase state, deps strategy |
| stack | <branch> | --repo, --json | Show recorded parent and descendant branches |
| prs | | --repo, --json, --all | Pull request status across worktrees |
| exec | <branch> <command...> | --repo | Run a command inside a worktree (WTX_PORT injected) |
| terminal | | | Interactive worktree dashboard (requires Bun) |
| mcp | | | Run MCP server exposing worktree tools over stdio |
| cd | <repo> <branch> | | cd into worktree (requires shell integration) |
| history | | --limit, --json, --source | Show recent action history (~/.local/state/wtx/history.jsonl) |
| config init/show/set/add-repo/remove-repo | | | Manage ~/.config/wtx/config.json |
| skill list/show/path | | | Agent skill files for opencode/cursor/claude |
| init | <bash\|zsh\|fish> | | Output shell wrapper for eval |
Global flags: --verbose, --dry-run, -q/--quiet, -v/--version, -h/--help.
--repo accepts comma-separated values or repeats. Omit it to target all configured repos, or run inside a managed repo to auto-scope. Destructive commands (remove, prune) ask for confirmation on interactive terminals; scripts pass --yes or set WTX_YES=1.
Configuration
Config lives at ~/.config/wtx/config.json.
{
"version": 1,
"root": "~/Repos",
"postfix": "-wt",
"ide": "cursor",
"default_main_branch": "main",
"user": "ogp",
"ports": { "min": 4100, "max": 4999 },
"agents": {
"claude": { "command": "claude" },
"reviewer": { "command": "opencode --model ollama/qwen3" }
},
"repos": {
"my-frontend": {
"main_branch": "auto",
"sync_files": [".env", ".env.local"],
"post_create": ["wtx deps"],
"post_sync": ["wtx deps"],
"deps": { "manager": "auto", "strategy": "auto" }
},
"my-api": {
"main_branch": "auto",
"sync_files": [".env"],
"post_create": ["wtx deps", "docker compose up -d db"]
},
"my-go-service": {
"main_branch": "main"
},
"forked-lib": {
"check_prs": true,
"forge_provider": "github",
"pr_lookup_repo": "upstream-owner/lib",
"install_script": "pnpm install --frozen-lockfile"
}
}
}Fields
| Field | Default | Description |
|---|---|---|
| root | required | Base directory where repos live. ~ expanded. |
| postfix | "-wt" | Worktree directory suffix (<repo><postfix>/<branch>) |
| ide | "cursor" | Default IDE for wtx open |
| default_main_branch | "main" | Fallback when auto-detection fails |
| user | null | Your forge handle; enables ownership detection |
| ports.min / ports.max | 4100 / 4999 | Range used by {port} isolation |
| agents.<name>.command | – | Shell command template for --agent; {wt}, {branch}, {repo} expanded |
| repos.<name>.main_branch | "auto" | Auto-detects via git symbolic-ref |
| repos.<name>.fetch_main_on_create | true | Fetch before creating so branches start fresh |
| repos.<name>.sync_files | [] | Files or folders copied from main checkout on create and sync — directories are copied recursively (build/) |
| repos.<name>.post_create / post_sync | [] | Hook commands; failures fail the command with a rerun hint |
| repos.<name>.install_script | null | Command run inside a worktree (or the main checkout via wtx deps --install) for dependency installs ({wt}, {branch}, {main} expanded); when unset, the detected package manager performs a real install |
| repos.<name>.deps.manager | "auto" | Force a manager: npm bun pnpm yarn go python cargo |
| repos.<name>.deps.strategy | "auto" | auto link symlink install off — see below |
| repos.<name>.check_prs | true | Set false to skip all PR lookups for this repo's branches (no gh calls) |
| repos.<name>.forge_provider | "auto" | "auto" enables GitHub only when the origin remote is github.com; "github" forces GitHub even for other hosts (GitHub Enterprise) |
| repos.<name>.pr_lookup_repo | null | owner/repo override for where PRs are looked up — use when origin points at a fork or mirror |
PR lookup keys explained
PR visibility (wtx prs, the TUI badges, ownership tags, prune) is read-only via the gh CLI. Three per-repo keys tune it:
check_prs— master switch.falsedisables everyghquery for this repo; everything else keeps working.forge_provider— which forge hosts the project."auto"only activates GitHub when your origin remote isgithub.com; set"github"to force it for GitHub Enterprise or custom domains.pr_lookup_repo— where to look PRs up, asowner/repo. Normally derived from origin; override when origin points at your fork but PRs live in the upstream repo.
Legacy configs using pr, forge, and pr_repo keep working — they are migrated automatically on load and rewritten in place on the next config save.
Template variables
In post_create, post_sync, and agent commands:
| Variable | Expands to |
|---|---|
| {root} {repo} {branch} {postfix} | identity fields |
| {main} / {wt} | absolute main checkout / worktree paths |
| {port} | deterministic collision-free port for this worktree |
Hooks also receive WTX_PORT as an environment variable, as does wtx exec. Ports hash repo+branch into the configured range and probe collisions against every active worktree — two branches can run dev servers simultaneously without conflict, and the same branch always gets the same port.
Dependency syncing
wtx deps keeps worktree dependencies working without duplicating installs. Each ecosystem gets its own adapter; detection is lockfile-based and overridable per repo via deps.manager.
Supported today: npm, bun, pnpm, yarn (Node) · Go (go mod download) · Python (uv sync; venvs are never symlinked — shebang paths make that unsafe) · Cargo (build cache shared through worktree-local .cargo/config.toml pointing at main's target/).
Strategies
| deps.strategy | Behavior |
|---|---|
| auto | Definitions match main → safe link; differ → install; only some workspaces changed → targeted install |
| link | Force safe linking |
| symlink | Legacy whole-directory symlink (⚠ installs inside the worktree mutate main's node_modules) |
| install | Always a real install in the worktree |
| off | Leave dependencies alone |
Per-repo install_script overrides the install command entirely — useful for non-standard setups (pnpm install --frozen-lockfile, bootstrap scripts, codegen steps). It runs inside the worktree (or the main checkout when installing via wtx deps --install with no branch) with the same {wt} / {branch} / {main} template expansion as hooks.
Safe linking (the auto default)
When manifests match main, wtx creates a real node_modules directory in the worktree containing per-package symlinks into main's tree (scoped packages traversed, .bin linked). Package-manager operations inside the worktree replace links, never main's files — an npm install in one branch cannot damage another checkout. If replacing an existing directory fails mid-way, the original is restored rather than left missing.
Monorepos: workspaces globs (including **) and pnpm-workspace.yaml are parsed; when only some workspaces changed, installs target just those (-w / --filter). Yarn falls back to a full install.
Inspect any worktree with wtx deps <branch>, or add --json for machine output. States include symlinked, linked-packages, installed, broken, external, and shared-target — broken and external states come with repair instructions.
AI agents
Spawn an agent into a fresh worktree
wtx create feat/auth --repo my-api --agent claude --prompt "implement OAuth login"The full pipeline runs first — create, sync files, prepare dependencies — then the agent launches inside the worktree. With tmux installed it runs in a detached named session (attach hint printed); otherwise directly. If post-create hooks fail, the agent is not spawned: fix with wtx sync instead of burning agent tokens on a broken tree. Define custom agents under agents.<name> in config.
MCP server
Expose worktrees to MCP-capable clients (Claude Code, OpenCode, Cursor):
{
"mcpServers": {
"wtx": { "command": "wtx", "args": ["mcp"] }
}
}Tools: list_worktrees, worktree_status, create_worktree, remove_worktree, rebase_worktree. All operations are scoped to configured repos; remove_worktree requires confirm: true, plus force: true for dirty worktrees.
Skill files
wtx skill show opencode > ~/.config/opencode/commands/wtx.md
wtx skill show cursor > .cursor/rules/wtx.mdc
wtx skill show claude >> CLAUDE.mdPull request workflow
Read-only PR visibility and lifecycle automation via the GitHub CLI — wtx stores no tokens.
$ wtx prs
my-frontend
#42 ogp/my-feature CONFLICTED · awaiting review checks 3/3 ✓ 2d ago
#43 ogp/fix-token IN_REVIEW checks 2/3 ✓ 5h ago @alice
⚠ 2 open PRs across 1 repo — 1 needs attentionEvery PR gets one ranked display state (CONFLICTED, CI_FAILING, CHANGES_REQUESTED, ...). Merged and closed PRs surface in wtx ls --pr and wtx status, so stale worktrees are recognizable at a glance.
wtx pull https://github.com/OGPoyraz/wtx/pull/11 # PR → worktree in one command
wtx prune # remove worktrees whose PR mergedLookups degrade gracefully: if gh is missing or unauthenticated you get a warning and everything else keeps working. Fork workflows are covered by check_prs / forge_provider / pr_lookup_repo.
Stacked branches
Worktrees remain sibling directories on disk, while their branches can form a dependency stack:
wtx create feat/api
wtx create feat/ui --base feat/api
# Open the PRs with these bases:
# feat/api -> main
# feat/ui -> feat/api--base accepts a normal Git ref and uses committed history only; uncommitted files in the parent worktree are not copied. The default remains the configured main branch. wtx stack feat/ui shows the recorded local relationship, and wtx rebase feat/ui rebases onto the recorded parent instead of flattening the stack onto main. After the parent merges, retarget the child PR to main before rebasing it onto main.
Stack metadata is stored under the repository's common Git directory and is not committed to the project. If a parent branch moves, wtx status <child> and the terminal dashboard mark the base as moved. Removing a parent with recorded children is refused unless --force is supplied.
Terminal dashboard
wtx terminal opens a full-screen dashboard across all configured repos (requires the Bun runtime).
| Key | Action |
|---|---|
| / | Fuzzy-filter entries by branch, repo, PR, owner |
| Space | Multi-select for batch operations |
| R / D / s | Batch rebase / remove / sync selection |
| p | Pull latest changes for selected branch(es) |
| i | Install dependencies in selected worktree(s) |
| n | Create worktree — pick a dependency strategy (auto / install / symlink) |
| m | Rename worktree (branch + directory move) |
| b | Rebase selected |
| a | Spawn coding agent in selected worktree |
| H | Action history (recent actions across CLI and dashboard) |
| o | Open in IDE |
| c | Edit configuration |
| ? / q | Help / quit |
Actions run in the background — navigation never locks. Progress shows inline: fetching, pulling, installing deps, renaming, etc. next to the repo or branch being worked on, and a new worktree appears immediately as a (creating) row. Repositories render alphabetically and stay sorted across create/delete operations. While data loads, repos appear immediately with a refreshing… indicator. One operation runs per repo at a time; conflicting actions are rejected with a toast until it finishes. On failure a log modal opens with the captured output; press any key to dismiss. Destructive ones confirm first.
If a rebase hits conflicts, the worktree is restored: the in-progress rebase is aborted automatically, the branch stays on its pre-rebase commits, and a "Rebase failed — manual merge needed" log explains what happened.
Scripting
Machine-readable output where it matters: wtx ls --json, wtx status <branch> --json, wtx deps <branch> --json, wtx prs --json. Combine with -q to suppress progress lines, and preview anything destructive with --dry-run — including planned deletions and hook commands.
Action history
Mutating commands (create, remove, rebase, sync, pull, ...) are recorded to ~/.local/state/wtx/history.jsonl — whether you ran them from your shell or from inside wtx terminal (entries are tagged with their source). Inspect with wtx history --limit 50 --json --source terminal, or press H in the dashboard. The file rotates automatically at ~5 MB, keeping the newest entries.
License
MIT
