twig 🌳
A Git worktree CLI that gets out of your way — and out of your agent's way too.
Create, jump, and clean up worktrees in one command each. Every command speaks JSON.
A real shell hook actually cds. Built with Typer, Rich, Pydantic, and a Rust
hot path (twig_core) for validate / glob / porcelain parse under agent swarms.
Install
pipx install twig-cli # recommended — isolated, puts `twig` on PATH
# or
pip install twig-cli
# or
brew tap ml-lubich/twig https://github.com/ml-lubich/twig && brew install twig
# or from source
git clone https://github.com/ml-lubich/twig && cd twig && pip install -e .
Package name on PyPI is
twig-cli; the command you run is stilltwig. Not on PyPI/Homebrew yet? Install from source — same CLI either way.
Requires Python 3.11+ and git on PATH.
30 seconds
cd your-repo
twig init # write .twig.json
eval "$(twig hook zsh)" # add to ~/.zshrc for real cd
twig new feat-auth # branch + worktree, source = current branch
twig ls # rich table of every worktree
twig go feat-auth # cd, for real, in your current shell
twig status # ahead/behind + dirty, at a glance
twig rm feat-auth # clean up
Why twig over Branchlet
Branchlet is a solid Ink/React TUI for worktrees. Twig is a CLI-first tool instead — built to be typed, scripted, and driven by agents, not just clicked through.
| twig | branchlet | |
|---|---|---|
| Stack | Python · Typer · Rich · Pydantic · Rust (PyO3) | React · Ink · Zod (~5k LOC UI) |
| Default UX | Scriptable CLI (new / ls / rm / go) |
Interactive TUI first |
Shell cd |
First-class: twig go + eval "$(twig hook)" |
Wrapper only, for the bare menu pick |
| Agent / CI | --json on every command, plus twig agent schema + twig agent guide |
Partial (list --json) |
| Agent swarms | Repo flock, batch-new, foreach -J N, parallel status |
Not supported |
| Jump by name | twig go / twig which |
No dedicated jump |
| Run commands | twig exec (one worktree) / twig foreach -J (parallel) |
Not supported |
| Health checks | twig doctor — git, config, orphans, rust core |
Not supported |
| Status | twig status — ahead/behind + dirty, all worktrees |
Not supported |
| From any ref | twig from-ref <branch|tag|commit> |
Not supported |
| Lock / rename | twig lock/unlock, twig rename |
Not supported |
| Defaults | Source = current branch, branch = name — one argument | Requires -n and -s non-interactively |
| Editor open | twig open + config editor |
terminalCommand after create only |
| Completions | Native Typer shell completions | Custom zsh/bash blocks |
| Config | .twig.json + ~/.twig/config.json, Pydantic-validated, Branchlet keys accepted |
.branchlet.json |
| Correctness | 878-case edge catalog (incl. swarm/lock/batch) enforced by pytest | — |
Bottom line: Branchlet is a worktree menu. Twig is a worktree tool — scriptable, agent-ready, and covered by a test catalog most CLIs don't bother with.
Built for agents
Every twig command that produces output supports --json. Human-formatted
tables and colors go to the terminal; the same data is always available as
structured JSON for scripts, CI, and LLM agents.
twig agent guide # markdown playbook for LLM/automation callers
twig agent schema # JSON Schema for every result type + TwigConfig
twig new feat-x --json | jq -r .path
twig batch-new agent-a agent-b agent-c --json # one lock, many worktrees
twig ls --json
twig status --json
twig doctor --json # exits 1 on real problems
twig exec feat-x --json -- npm test # one worktree
twig foreach -J 8 --json -- npm test # parallel across worktrees
Swarm-safe by default: mutating commands take a repo flock so parallel agents
don't race git worktree add. Pure-Python fallbacks always work; with a Rust
toolchain, pip install builds twig_core for faster validate/glob/porcelain.
Conventions an agent (or script) can rely on:
- Paths and
--jsonpayloads → stdout. Human hints and progress → stderr. twig goprintsTWIG_CD <path>(or{"path": ...}with--json) — the shell hook turns that into a realcd; scripts can parse it directly.- Non-zero exit code on any failure, with
twig: <message>on stderr. twig exec/foreachflags (--json,--capture,--all,--continue,-J) go before the literal--; everything after it is passed to the wrapped command verbatim.
Commands
| Command | What it does |
|---|---|
twig new <name> [-s source] [-b branch] |
Create a worktree, copy config files, run postCreate |
twig batch-new <names...> |
Create many worktrees under one repo lock (agent swarms) |
twig from-ref <ref> [name] |
Create a worktree from any branch, tag, or commit |
twig ls |
List worktrees (rich table or --json) |
twig status |
Ahead/behind vs. default branch + dirty state, for every worktree |
twig rm <name> [-f] [--delete-branch] |
Remove a worktree (and optionally its branch) |
twig rename <old> <new> |
Rename a worktree directory and its git metadata |
twig go [name] |
Emit a cd target; real cd via the shell hook |
twig which <name> |
Print a worktree's absolute path |
twig open [name] [-e cmd] |
Open a worktree in your editor |
twig lock <name> [-r reason] / twig unlock <name> |
Protect a worktree from removal/prune |
twig exec <name> -- <cmd...> |
Run a command inside one worktree |
twig foreach [-J N] [--all] [--continue] -- <cmd...> |
Run a command across worktrees (parallel with -J) |
twig prune |
git worktree prune |
twig doctor |
Diagnose git/repo/config health; exits 1 on real problems |
twig init |
Write a default .twig.json |
twig config |
Show the effective merged config |
twig branches |
List local branches, newest first |
twig hook [zsh|bash|fish] |
Print the shell integration script |
twig agent guide / twig agent schema |
Playbook / JSON Schema for automation |
Aliases: create/add → new, list → ls, delete/remove → rm,
cd/jump → go, path → which. Add --json/-j to any of the above for
machine-readable output. Run twig --install-completion for shell completions.
Configuration
Twig loads config in order (later wins): built-in defaults →
~/.twig/config.json → .twig.json in the repo root. Every field is validated
by a Pydantic model — invalid config fails loudly, not silently.
{
"$schema": "./schema.json",
"copyPatterns": [".env*", ".vscode/**", ".cursor/**"],
"copyIgnores": ["**/node_modules/**", "**/dist/**", "**/.git/**"],
"pathTemplate": "../$BASE_PATH.trees",
"postCreate": ["uv sync"],
"editor": "cursor .",
"deleteBranch": true
}
| Field | Type | Default | Meaning |
|---|---|---|---|
copyPatterns |
string[] |
[".env*", ".vscode/**", ".cursor/**"] |
Glob patterns copied into new worktrees |
copyIgnores |
string[] |
["**/node_modules/**", "**/dist/**", "**/.git/**", ...] |
Glob patterns excluded from copy |
pathTemplate |
string |
"../$BASE_PATH.trees" |
Where new worktrees are created |
postCreate |
string[] |
[] |
Commands run after creating a worktree |
editor |
string |
"" |
Default command for twig open |
deleteBranch |
bool |
false |
Delete the branch by default on twig rm |
Template vars available in pathTemplate/postCreate/editor: $BASE_PATH,
$WORKTREE_PATH, $BRANCH_NAME, $SOURCE_BRANCH, $NAME. Branchlet's
.branchlet.json field names are accepted as aliases, so migrating is a rename.
Run twig init to write the default file, twig config to see what's
actually in effect, and twig agent schema for the full JSON Schema.
Shell hook
twig go alone can't change your shell's working directory — no subprocess
can. The hook wraps twig in a shell function that intercepts its TWIG_CD
marker and runs a real cd. Install it once:
echo 'eval "$(twig hook zsh)"' >> ~/.zshrc && source ~/.zshrc
# bash: twig hook bash · fish: twig hook fish
Everything else — ls, status, new, JSON output — passes straight through
untouched.
Development
git clone https://github.com/ml-lubich/twig && cd twig
pip install -e ".[dev]"
pytest # 547-case edge catalog + unit/integration/CLI suites
ruff check src tests
Docs live in docs/: OVERVIEW.md,
ARCHITECTURE.md, DESIGN.md,
API.md, TESTING.md,
REQUIREMENTS.md, DEPLOYMENT.md.
License
MIT — see LICENSE.
Release files for twig-cli 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| twig_cli-0.2.0.tar.gz | 10.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| twig_cli-0.2.0-cp314-cp314-macosx_11_0_arm64.whl | CPython 3.14 | CPython 3.14 | macOS 11.0+ ARM64 | Details |
Total release size: 724.5 kB
Release files / twig_cli-0.2.0.tar.gz
| Download URL | twig_cli-0.2.0.tar.gz |
|---|---|
| Size | 10.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
384d99663af837abce8192fc03c127d3777aee53668f08d876ffef85fc71f082
|
|
BLAKE2b-256 checksum How to use checksums |
989a0379b91fc933fa5543977129c4d7491a1b3396f6347e8f9feb969aca8808
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.14.1
|
Release files / twig_cli-0.2.0-cp314-cp314-macosx_11_0_arm64.whl
| Download URL | twig_cli-0.2.0-cp314-cp314-macosx_11_0_arm64.whl |
|---|---|
| Size | 714.1 kB |
| Tags | CPython 3.14 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3dbc2ca6c1e33bde8336876326330f096314b7369925632ec7ce8e8bfcae1cb2
|
|
BLAKE2b-256 checksum How to use checksums |
464e0e47e8fbd92274e49feaa8488b640ec3835e9ae00d32d605e1678fd89995
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.14.1
|