@abhi-arya1/wt
v0.0.9
Published
Git worktree sandboxes, locally or on remote hosts over SSH.
Readme
wt
Run git worktree sandboxes, locally or on remote hosts over SSH, with minimal setup.
wt clones your repo into a bare mirror, then spins up isolated worktrees you can enter, run commands in, and throw away when you're done. Works on your machine or any box you can SSH into, with SSH keys, agents, environments, and more included.
wt
├── host
│ ├── add [name]
│ ├── ls
│ ├── check <name>
│ ├── rm <name>
│ ├── map <name> <localPort> <hostPort>
│ └── unmap <name> <localPort>
├── up [name]
├── local [name]
├── rename <old> <new>
├── enter <name>
├── run <name> <cmd>
├── sessions
├── gc
├── doctor
├── bootstrap
├── ls
├── rm <name>
└── status <name>Install
# with any package manager, but bun is recommended
bun install -g @abhi-arya1/wtRequires Bun (v1.3.3+). You can also build a standalone binary with bun build --compile if you prefer — no runtime needed, but the binary will be larger.
From source
git clone https://github.com/abhi-arya1/wt.git && cd wt
bun install
bun run build
bun linkOr just run it directly during development:
bun run dev -- <command>Quick start
Local sandbox
You're in a git repo. You want an isolated copy to mess around in without touching your working tree.
wt local my-experiment --enter
# you're now in a worktree at .wt/sandboxes/my-experiment
# type `exit` to leave the sandbox shell
# or just let it pick the name from your current branch
wt local --enter
# sandbox named after your current branchRemote sandbox
You have a server you can SSH into. Register it as a host, then spin up sandboxes there.
wt host add prod-box --ssh [email protected] --root /srv/wt
wt up my-feature --host prod-box --enterClean up
wt rm my-experiment # remove a specific sandbox
wt gc # remove all sandboxes older than 7 days
wt gc --older-than 1d # more aggressive
wt gc --dry-run # see what would get deletedAgent Usage
If you are using an agent to make sandboxes for multiple agents to work within them, you can install wt then give agents the SKILL.md file. This will allow them to understand usage of wt and create sandboxes on your behalf.
Guides
Setting up a remote host from scratch
Make sure the remote box has
gitinstalled and your SSH key is authorized.Add the host:
wt host add myserver --ssh [email protected] --root /home/me/wt-sandboxesThis registers the host and runs a connectivity check. If it passes, you're good.
- Check what's installed on the remote:
wt bootstrap --host myserver --tmux --agents claude,opencodeThis tells you what's present and what's missing. It doesn't install anything -- just reports.
- Verify everything works:
wt doctor --host myserver- Create a sandbox from any local git repo:
cd ~/projects/my-app
wt up test-sandbox --host myserver --enterYou're now in a shell on myserver inside a worktree of your repo. .env files from your local directory get copied over automatically.
- Run commands without entering:
wt run test-sandbox -- make build
wt run test-sandbox -- bun test- Use tmux for persistent sessions:
wt enter test-sandbox --tmux
# detach with ctrl-b d, reattach later with the same commandRunning multiple sandboxes for parallel work
Say you need to test three branches at once.
wt up --branch feature/auth
wt up --branch feature/payments
wt up --branch fix/header-bug
wt ls
# NAME HOST REF CREATED
# auth local a1b2c3d4 2/8/2026
# payments local e5f6g7h8 2/8/2026
# header-bug local i9j0k1l2 2/8/2026
wt run auth -- bun test
wt run payments -- bun test
wt run header-bug -- bun test
# rename one if you want
wt rename auth login-revamp
# done, clean up
wt rm login-revamp
wt rm payments
wt rm header-bugSSH keys and identity files
Use -i to point at a specific key when adding a host, and -p for a non-standard port:
wt host add mybox --ssh [email protected] --root /srv/wt -i ~/.ssh/id_mybox -p 2222These are stored in config and used for every SSH and SCP operation to that host. If you don't pass -i, wt uses whatever OpenSSH picks from your agent or default key.
Private repos on remote hosts
When you run wt up --host myserver, wt tells the remote to git clone --bare --mirror <origin>. That means the remote host needs access to your git remote (e.g. GitHub). For private repos, you have two options:
Option 1: SSH agent forwarding (recommended)
Add ForwardAgent yes for the host in your ~/.ssh/config:
Host myserver
HostName 10.0.0.5
User deploy
ForwardAgent yesNow your local SSH keys are available on the remote when wt runs git commands. No keys need to be deployed on the server.
Option 2: Deploy a key on the remote
Add an SSH key on the remote host and register it as a deploy key with your git provider (e.g. GitHub deploy keys). This works without agent forwarding but means the remote has standing access to the repo.
Hosts behind firewalls and jump hosts
wt doesn't have explicit jump host flags, but it passes the SSH target directly to OpenSSH, so anything in your ~/.ssh/config is honored. To reach a host behind a bastion:
Host bastion
HostName bastion.example.com
User ops
Host internal-box
HostName 10.0.1.50
User deploy
ProxyJump bastion
ForwardAgent yesThen register it using the alias:
wt host add internal-box --ssh internal-box --root /srv/wtwt will connect through the bastion transparently. The same applies to ProxyCommand, custom ControlMaster settings, or any other OpenSSH config directives.
Pushing and creating PRs from a sandbox
Sandboxes are regular git worktrees — standard git commands work as expected. Push branches and create PRs the same way you normally would:
wt enter my-sandbox
git checkout -b my-feature
# make changes
git add .
git commit -m "my changes"
git push -u origin my-feature
# create a PR with the GitHub CLI
gh pr create --title "My feature" --body "Description of changes"You can also do this without entering the sandbox:
wt run my-sandbox -- git push -u origin my-feature
wt run my-sandbox -- gh pr create --title "My feature" --body "Description"Note: Because wt uses git clone --bare --mirror, the mirror fetches all remote refs including GitHub's internal PR refs (refs/pull/*/head). A plain git push with no arguments may try to push these back and produce harmless remote rejected errors. To avoid this, push specific branches explicitly (git push origin my-branch) or set git config --global push.default current.
Using with tmux sessions
Every sandbox can have a tmux session tied to it. Sessions are named wt-<sandboxId>.
wt enter my-sandbox --tmux # creates or reattaches to tmux session
wt sessions # list all wt-managed tmux sessions
wt sessions --host prod-box # list sessions on a remote hostCommand reference
wt up [name]
Create a sandbox worktree on a host. If name is omitted, it defaults to the branch name from -b / --ref, or the current branch. When -b is not provided, the sandbox stays on the same branch you're currently on.
| Flag | Description |
|---|---|
| -H, --host <name> | Target host (defaults to configured default, falls back to local) |
| -b, --branch <name> | Create or use a branch with this name |
| -r, --ref <ref> | Git ref to check out (branch, tag, or sha that must exist) |
| -e, --enter | Enter the sandbox after creating it |
| --tmux | Use tmux when entering (implies --enter) |
| --json | JSON output |
wt local [name]
Shorthand for wt up [name] on the local host. Same options minus --host.
| Flag | Description |
|---|---|
| -b, --branch <name> | Create or use a branch with this name |
| -r, --ref <ref> | Git ref to check out (branch, tag, or sha that must exist) |
| -e, --enter | Enter the sandbox after creating it |
| --tmux | Use tmux when entering (implies --enter) |
| --json | JSON output |
wt rename <old> <new>
Rename a sandbox.
| Flag | Description |
|---|---|
| --json | JSON output |
wt enter <name>
Open a shell inside a sandbox.
| Flag | Description |
|---|---|
| --tmux | Use a tmux session instead of a plain shell |
| --json | Print sandbox record as JSON without entering |
wt run <name> <cmd...>
Run a command inside a sandbox. Streams output by default.
| Flag | Description |
|---|---|
| --json | Capture stdout/stderr and return as JSON (must come before --) |
| --quiet | Suppress non-error output (must come before --) |
Note: flags for wt run itself go before --. Everything after -- is passed to the command.
wt run my-sandbox --json -- git log --oneline -5wt ls
List all sandboxes.
| Flag | Description |
|---|---|
| -H, --host <name> | Filter by host |
| --json | JSON output |
wt rm <name>
Remove a sandbox. Deletes the worktree directory, metadata, and config entry. Prunes the mirror's worktree references.
| Flag | Description |
|---|---|
| --json | JSON output |
wt status <name>
Show sandbox details: host, ref, path, age, whether the directory exists, and whether a tmux session is active.
| Flag | Description |
|---|---|
| --json | JSON output |
wt sessions
List active wt-* tmux sessions.
| Flag | Description |
|---|---|
| -H, --host <name> | Target host |
| --json | JSON output |
wt gc
Garbage-collect stale sandboxes. A sandbox is stale if it's older than the threshold or its directory no longer exists.
| Flag | Description |
|---|---|
| -H, --host <name> | Target host (omit for all hosts) |
| --older-than <dur> | Age threshold, e.g. 7d, 24h, 1w (default: 7d) |
| --dry-run | Preview what would be deleted |
| --json | JSON output |
wt doctor
Check that git, bun/node, and tmux are available on a host.
| Flag | Description |
|---|---|
| -H, --host <name> | Target host |
| --json | JSON output |
wt bootstrap
Check host readiness. Reports what's installed and what's missing. Does not install anything.
| Flag | Description |
|---|---|
| -H, --host <name> | Target host |
| --tmux | Include tmux in checks |
| --agents <list> | Comma-separated agent CLIs to check (e.g. claude,opencode) |
| --json | JSON output |
wt host add [name]
Register or update a remote host.
| Flag | Description |
|---|---|
| -s, --ssh <target> | SSH target (alias, user@host, or ssh://user@host:port) |
| -r, --root <path> | Remote base directory (absolute path) |
| -d, --default | Set as default host |
| -p, --port <n> | SSH port |
| -i, --identity <path> | Path to SSH identity file |
| -t, --connect-timeout <s> | Connection timeout in seconds (default: 10) |
| -l, --labels <k=v,...> | Comma-separated key=value labels |
| --no-check | Skip connectivity check |
| --json | JSON output |
wt host ls
List all configured hosts.
| Flag | Description |
|---|---|
| --json | JSON output |
wt host check <name>
Test SSH connectivity and capabilities of a host.
| Flag | Description |
|---|---|
| --json | JSON output |
wt host rm <name>
Remove a host.
| Flag | Description |
|---|---|
| -y, --yes | Skip confirmation prompt |
| --json | JSON output |
wt host map <name> <localPort> <hostPort>
Add a port mapping to a host. When you SSH into the host (via wt enter, wt up --enter, etc.), the mapping is applied as -L localPort:localhost:hostPort, forwarding traffic from your local port to the remote port.
| Flag | Description |
|---|---|
| --json | JSON output |
# Forward local port 3000 to remote port 8080
wt host map myserver 3000 8080
# Now when you enter a sandbox, localhost:3000 reaches the remote's port 8080
wt enter my-sandbox
curl localhost:3000 # hits remote:8080Mappings are stored in config and persist across restarts. If a mapping for the same local port already exists, it is updated.
wt host unmap <name> <localPort>
Remove a port mapping from a host.
| Flag | Description |
|---|---|
| --json | JSON output |
wt host unmap myserver 3000How it works
wt creates a bare mirror of your repo, then uses git worktree add to spin up isolated checkouts. Each sandbox gets its own directory and metadata file.
.wt/ # local root (inside your repo)
mirrors/
<repoId>.git/ # bare mirror
sandboxes/
<name>/ # worktree checkout
meta/
<sandboxId>.json # sandbox metadataRemote hosts use the same layout under the configured root path (e.g. /srv/wt). All remote operations go over SSH.
Config lives at ~/.config/wt/config.json and stores hosts and sandbox records. Every structured command supports --json for scripting.
JSON output
Every command that produces structured output supports --json. Errors in JSON mode return:
{
"ok": false,
"error": "what went wrong"
}This makes it straightforward to compose wt with other tools, scripts, or agents.
Future Plans
Unsure if this will be added, but I'd like to add:
- Cloud provider abstractions (AWS, Fly.io, etc.)
- Automatic download for sandbox dependencies
- Better observability, error handling, cleanup, and devex
