@getsolaris/copse
v1.2.1
Published
Git worktree manager with a beautiful TUI
Maintainers
Readme
🌲 copse
English | Korean
Git worktree manager with a beautiful TUI
Manage git worktrees with ease. Create, switch, and clean up worktrees with config-driven automation, monorepo support, and built-in health checks.
Why "copse"?
A copse is a small group of trees growing closely together. Git worktrees are branches checked out as separate working directories — each one a tree. When you manage multiple worktrees for a single repo, you're tending a little grove. That's a copse.
Features
- TUI mode — interactive terminal UI (
copse) - CLI mode — scriptable commands (
copse add,copse list, etc.) - Config-driven — per-repo hooks, file copying, symlinks
- Monorepo support — auto-detect packages, per-package hooks, focus tracking
- Health checks —
copse doctordiagnoses worktree issues - Centralized worktrees — all worktrees under
~/.copse/worktrees/by default - Smart cleanup — auto-detect and remove merged worktrees
- Themes — 9 built-in color themes (OpenCode, Tokyo Night, Dracula, Nord, Catppuccin, GitHub Dark, One Dark, Monokai, GitHub Light)
- Templates — reusable worktree presets (
copse add --template review) - Cross-worktree exec — run commands across all worktrees (
copse exec "bun test") - GitHub PR integration — create worktrees from PRs (
copse add --pr 123) - Fuzzy branch picker — interactive branch selection in TUI with type-ahead filtering
- Lifecycle management — auto-detect stale/merged worktrees, configurable limits
- Shared dependencies — save disk with hardlink/symlink strategies for
node_modules - Worktree diff — compare changes between worktrees (
copse diff feature/a feature/b) - Pin protection — protect worktrees from auto-cleanup (
copse pin) - Activity log — track create/delete/switch/rename/archive/import events (
copse log) - Archive — preserve worktree changes as patches before removal (
copse archive) - Branch rename — rename worktree branches with metadata migration (
copse rename) - Clone & init — clone repos with copse config initialization (
copse clone) - Import worktrees — adopt manually-created worktrees (
copse import) - Detail view — expanded worktree info with commits, diff stats, upstream status (TUI)
- Bulk actions — multi-select and batch operations on worktrees (TUI)
- Toast notifications — non-blocking operation feedback (TUI)
- Shell completions — tab completion for bash/zsh/fish (
copse shell-init --completions) - Config profiles — switch between configuration sets (
copse config --profiles) - Tmux sessions — auto-create/kill tmux sessions per worktree with layout templates (
copse session) - Workspaces — auto-discover git repos under parent directories with per-workspace defaults (
workspacesconfig) - AI agent init — create config by default or install copse skill for Claude Code, Codex, OpenCode (
copse init,copse init --skill)
Requirements
- Bun runtime
- git 2.17+
- macOS or Linux
- gh CLI (optional, for
--prflag) - tmux (optional, for
copse session)
Installation
Homebrew (macOS/Linux)
brew install getsolaris/tap/copsecurl (one-liner)
curl -fsSL https://raw.githubusercontent.com/getsolaris/copse/main/install.sh | bashnpm / bun
bun install -g @getsolaris/copse
# or
npm install -g @getsolaris/copseLocal Development
For local contributor testing, run the repo directly with Bun:
bun install
bun run src/index.ts
bun run src/index.ts <cmd>
bun run typecheck
bun test
bun run buildPrefer targeted tests first when you change covered code, then run the full checks before opening a PR. If you change CLI or TUI behavior, manually run the affected flows locally as well.
Quick Start
# Launch TUI
copse
# List worktrees
copse list
# Create a new worktree
copse add feature/my-feature
# Create with monorepo focus
copse add feature/my-feature --focus apps/web,apps/api
# Create from a GitHub PR
copse add --pr 123
# Use a template
copse add feature/login --template review
# Pin a worktree to protect from cleanup
copse pin feature/important --reason "active sprint"
# View activity log
copse log
# Archive worktree changes before removing
copse archive feature/done --yes
# Rename a worktree branch
copse rename old-name new-name
# Clone and initialize copse
copse clone https://github.com/user/repo.git
# Import an existing worktree
copse import /path/to/worktree
# Open/attach tmux session for a worktree
copse session feature/my-feature
# Create worktree with tmux session
copse add feature/new --session
# Run command across all worktrees
copse exec "bun test"
# Compare two worktrees
copse diff feature/a feature/b --stat
# Check worktree health
copse doctor
# Switch to a worktree (requires shell integration)
copse switch feature/my-feature
# Remove a worktree
copse remove feature/my-feature --yes
# Clean up merged worktrees
copse clean --dry-run
# Initialize config file
copse init
# Generate AI agent skill file
copse init --skill claude-codeTUI Usage
Launch with copse (no arguments).
Keyboard Shortcuts
| Key | Action |
| --------- | ---------------------- |
| j / k | Navigate worktree list |
| a | Add worktree |
| d | Delete worktree |
| o | Open in editor (focus-aware) |
| h | Doctor (health check) |
| Enter | Open detail view |
| Escape | Close detail view / picker |
| Space | Toggle worktree selection |
| Ctrl+A | Select all worktrees |
| x | Bulk actions menu |
| r | Refresh list |
| Ctrl+P | Command palette |
| ? | Help |
| q | Quit |
o — Focus-aware editor open
Pressing o opens the selected worktree in $VISUAL / $EDITOR:
- No focus paths set → opens the worktree root.
- Exactly 1 focus path → opens
<worktree>/<focus>directly. - 2+ focus paths → shows a picker so you can choose which focus path (or the worktree root) to open.
The picker supports j/k or ↑/↓ to navigate, Enter to open, and Esc to cancel.
Command Palette (Ctrl+P)
Searchable command menu with:
- Add / Delete / Refresh worktrees
- Run Doctor
- Open Config
- Switch theme
- Quit
Type to filter, ↑↓ to navigate, Enter to execute, Esc to close.
Worktree Creation Flow
- Press
ato open the Create view - Start typing a branch name — matching branches appear as you type
- Use
↑↓to select from suggestions, or keep typing for a new branch - Press
Tabto switch to the Focus field (optional) - Type focus paths (e.g.
apps/web,apps/api) - Press
Enterto preview - Press
Enterto confirm
The fuzzy branch picker shows local and remote branches sorted by last commit date, filtered in real-time as you type.
After creation, the configured copyFiles, linkFiles, postCreate hooks, and monorepo hooks run automatically.
Doctor View
Press h to open the Doctor tab. Shows health check results:
- ✓ Git version check
- ✓ Config validation
- ✓ Stale worktree detection
- ✓ Orphaned directory detection
- ✓ Lock status check
- ✓ Dirty worktree detection
Press r to recheck, Esc to go back.
Config View
Open with Ctrl+P → Open Config. The Config tab renders the full contents of ~/.config/copse/config.json, including:
- Top-level:
version,theme,activeProfile defaults(includingpostRemove,autoUpstream,sharedDeps)- All per-repo overrides
- The full
monorepotree —autoDetect,extraPatterns, and everyhooks[]entry with itsglob/copyFiles/linkFiles/postCreate/postRemove templates,lifecycle,sessions,profiles
Most scalar and string-array fields are editable inline. The counter in the header shows the current position (1/20).
| Key | Action |
| --- | --- |
| j / k | Navigate editable fields |
| g / G | Jump to first / last field |
| Enter | Edit the selected field |
| Tab | Cycle through preset values (in edit mode) |
| Space / ←→ | Toggle a boolean / cycle a theme (in edit mode) |
| Enter | Commit the edit (saves to disk + reloads) |
| Esc | Cancel the edit |
| e | Open the config file in $EDITOR |
| r | Reload the file from disk |
| i | Initialize the config file if missing |
Inline editing supports five kinds of fields:
- Strings — plain text input (e.g.
worktreeDir, hookglob,sessions.prefix). PressTabto cycle through common presets. - String arrays — JSON input like
[".env", ".env.local"]. Empty input is treated as[]. PressTabto cycle through presets including[]as the first option. - Booleans — toggle with
Space,Tab, or←→, commit withEnter. - Themes — cycle with
Tabor←→, commit withEnter. The new theme is applied live. - Enums — fields with a fixed set of valid values (e.g.
sharedDeps.strategy=hardlink/symlink/copy). Cycle withTabor←→, commit withEnter.
The footer shows the current preset position when applicable, e.g. Tab:preset (2/4). The cycle starts at the position matching the current value, so the first Tab always advances to a new value.
Every commit runs validateConfig before writing. Invalid input surfaces as an inline error and the edit stays open so you can fix it. For more complex fields (sessions.layouts, templates, profiles), press e to open $EDITOR.
CLI Commands
| Command | Description |
| ------------------------ | ------------------------------------ |
| copse | Launch TUI |
| copse list | List all worktrees (with focus info) |
| copse add <branch> | Create worktree |
| copse remove <branch> | Remove worktree |
| copse switch <branch> | Switch to worktree |
| copse clean | Remove merged worktrees |
| copse doctor | Check worktree health |
| copse config | Manage configuration |
| copse exec <command> | Run command in each worktree |
| copse diff <ref1> [ref2] | Diff between worktrees/branches |
| copse pin <branch> | Pin/unpin worktree (protect from cleanup) |
| copse log | Show worktree activity log |
| copse archive <branch> | Archive changes and optionally remove |
| copse rename <old> <new> | Rename worktree branch |
| copse clone <url> | Clone repo and initialize copse |
| copse import <path> | Adopt worktree with copse metadata |
| copse session [branch] | Manage tmux sessions for worktrees |
| copse open [branch] | Open a worktree in your editor (focus-aware) |
| copse init | Initialize config or install AI agent skills |
copse add
copse add feature/login # Create branch if needed + worktree
copse add feature/login --base main # New branches start from main
copse add feature/login --base origin/main # Auto-fetches origin/main first (see below)
copse add feature/login --base origin/main --no-fetch # Skip auto-fetch
copse add existing-branch # Worktree for existing branch
# Monorepo: create with focus packages
copse add feature/login --focus apps/web,apps/api
copse add feature/login --focus apps/web --focus apps/api
# Use a template
copse add feature/login --template review
# Create from a GitHub PR (requires gh CLI)
copse add --pr 123
copse add --pr 456 --template reviewRemote-aware base + auto-fetch
When --base (or defaults.base in config) matches <remote>/<branch> and <remote> is a
known git remote, copse add runs git fetch <remote> <branch> before creating the worktree.
This prevents accidentally branching from a stale local copy of a remote-tracking ref.
--base main,--base develop,--base HEAD,--base <sha>→ no fetch (local ref).--base origin/main,--base upstream/release/v2→ fetch that specific branch, then branch from it.--base fork/mainwhenforkis not a configured remote → treated as a local ref (no fetch).- Fetch failures (offline, auth errors) print a warning and continue with the local copy.
- Auto-fetch is skipped when the target branch already exists locally (
--baseonly matters for new branches).
Pass --no-fetch to skip the auto-fetch unconditionally. Set defaults.base to make
remote-aware base the default for every copse add:
{
"version": 1,
"defaults": {
"base": "origin/main"
}
}Base resolution order (highest wins): --base flag → template.base → per-repo
repos[].base → defaults.base → git HEAD.
copse doctor
copse doctor # Human-readable output
copse doctor --json # JSON output for scriptingExit code: 0 if healthy, 1 if any warnings or errors.
copse doctor
✓ Git version: 2.39.0 (>= 2.17 required)
✓ Configuration: valid
✓ Stale worktrees: none
✓ Orphaned directories: none
✓ Worktree locks: all clear
✓ Dirty worktrees: none
All checks passed.copse list
copse list # Table with Focus column
copse list --json # JSON with focus array
copse list --porcelain # Machine-readableOutput includes a Focus column showing monorepo focus paths per worktree.
copse remove
copse remove feature/login # Remove by branch name
copse remove feature/login --force # Force remove (dirty worktree)
copse remove feature/login --yes # Skip confirmationcopse clean
copse clean --dry-run # Preview what would be removed
copse clean # Remove all merged worktrees
copse clean --stale # Also show stale worktrees (uses lifecycle config)copse exec
Run a shell command in every non-main worktree.
copse exec "bun test" # Run in all worktrees (sequential)
copse exec "bun test" --parallel # Run in parallel
copse exec "git pull" --all # Across all configured repos
copse exec "bun install" --dirty # Only dirty worktrees
copse exec "git rebase main" --behind # Only worktrees behind upstream
copse exec "bun test" --json # JSON output| Flag | Description |
| ------------------- | ------------------------------------- |
| --parallel / -p | Run commands in parallel |
| --all / -a | Include all configured repos |
| --dirty | Only run in dirty worktrees |
| --clean | Only run in clean worktrees |
| --behind | Only run in worktrees behind upstream |
| --json / -j | Output results as JSON |
Environment variables available in commands: COPSE_BRANCH, COPSE_WORKTREE_PATH, COPSE_REPO_PATH.
copse diff
Show diff between two worktree branches.
copse diff feature/a feature/b # Full diff
copse diff feature/a feature/b --stat # Diffstat summary
copse diff feature/a --name-only # Changed file names only
copse diff feature/a # Compare against current HEADcopse pin
copse pin feature/auth --reason "active sprint" # Pin with reason
copse pin --list # List pinned worktrees
copse pin --list --json # JSON output
copse unpin feature/auth # UnpinPinned worktrees are excluded from copse clean and lifecycle auto-cleanup.
copse log
copse log # Show last 20 events
copse log --limit 50 # Show last 50 events
copse log --json # JSON output
copse log --clear # Clear activity logEvents are color-coded: create (green), delete (red), switch (blue), rename (yellow), archive (magenta), import (cyan).
copse archive
copse archive feature/done --yes # Archive and remove
copse archive feature/wip --keep # Archive without removing
copse archive --list # List all archives
copse archive --list --json # JSON outputArchives are stored as patch files in ~/.copse/archives/.
copse rename
copse rename old-branch new-branch # Rename branch
copse rename old-branch new-branch --move-path # Also move worktree directorycopse clone
copse clone https://github.com/user/repo.git # Clone and init
copse clone https://github.com/user/repo.git ./my-dir # Custom target path
copse clone https://github.com/user/repo.git --template review # Apply template
copse clone https://github.com/user/repo.git --no-init-config # Skip config initcopse import
copse import /path/to/worktree # Adopt worktree
copse import /path/to/worktree --focus apps/web,apps/api # With focus
copse import /path/to/worktree --pin # Pin immediatelycopse session
Manage tmux sessions for worktrees. Requires tmux.
copse session feature/auth # Open/attach session (create if needed)
copse session feature/auth --layout api # Use named layout from config
copse session --list # List active copse sessions
copse session --list --json # JSON output
copse session feature/auth --kill # Kill session for worktree
copse session --kill-all # Kill all copse sessionsSessions are auto-created/killed when sessions.autoCreate / sessions.autoKill are enabled in config.
# Create worktree with tmux session
copse add feature/login --session
copse add feature/login --session --layout apiWhen sessions.enabled is true and you're inside tmux, copse switch automatically switches to the target worktree's tmux session.
copse open
Open a worktree in your editor or IDE. Auto-detects $VISUAL / $EDITOR and falls back to a known list (code, cursor, vim, nvim, emacs, nano, subl, zed, idea, webstorm).
copse open # Open the current worktree
copse open feature/auth # Open a specific worktree
copse open feature/auth -e nvim # Override editor
# Focus-aware behavior (when the worktree was created with --focus)
copse open feature/auth # 1 focus path → opens that focus
# 2+ focus paths → errors with hint
copse open feature/auth --focus apps/web # Pick a specific focus path
copse open feature/auth -f apps/api # Same with the short alias
copse open feature/auth --root # Force the worktree root, ignore focus
copse open --list-editors # List detected editors| Flag | Alias | Description |
| ---- | ----- | ----------- |
| --editor | -e | Editor command to use (overrides $VISUAL/$EDITOR) |
| --focus | -f | Open a specific focus path (must match a focus entry on the worktree) |
| --root | | Force the worktree root, ignoring any focus paths |
| --list-editors | | List detected editors |
Focus resolution rules:
- 0 focus paths set → opens the worktree root.
- 1 focus path set → opens
<worktree>/<focus>automatically. - 2+ focus paths set → errors out and asks for
--focus <path>or--root(the TUI shows an interactive picker instead).
copse init
Initialize copse config by default, or install copse skill for AI coding agents so they can use copse commands.
copse init # → ~/.config/copse/config.json
copse init --skill claude-code # → ~/.claude/skills/copse/
copse init --skill codex # → ~/.agents/skills/copse/
copse init --skill opencode # → ~/.config/opencode/skill/copse/| Platform | Skill Path |
|----------|-----------|
| claude-code | ~/.claude/skills/copse/ |
| codex | ~/.agents/skills/copse/ |
| opencode | ~/.config/opencode/skill/copse/ |
Each skill directory contains:
SKILL.md— overview and common workflowsreferences/— detailed per-command documentation (21 files)
Without --skill, the command reuses the normal config initializer and creates only config.json.
The command is idempotent — running it again updates the skill directory.
Auto-init on first run
You don't have to run copse init manually. The first time you run any copse command (including launching the TUI), if ~/.config/copse/config.json does not exist, copse creates it with the default template and prints a one-line notice to stderr:
copse: created default config at /Users/you/.config/copse/config.jsonThe notice is suppressed when stdout is not a TTY (so pipes, scripts, and CI stay quiet) and when you run copse init explicitly (to avoid duplicate messages with init's own success line). Auto-init is fully idempotent — subsequent runs do nothing.
Configuration
Config file: ~/.config/copse/config.json
Initialize with: copse config --init (or just run any copse command — see Auto-init on first run)
Full Example
{
"$schema": "https://raw.githubusercontent.com/getsolaris/copse/main/schema.json",
"version": 1,
"theme": "dracula",
"defaults": {
"worktreeDir": "~/.copse/worktrees/{repo}-{branch}",
"copyFiles": [".env"],
"linkFiles": ["node_modules"],
"postCreate": ["bun install"],
"postRemove": [],
"sharedDeps": {
"strategy": "hardlink",
"paths": ["node_modules"],
"invalidateOn": ["package.json", "bun.lockb"]
}
},
"templates": {
"review": {
"copyFiles": [".env.local"],
"postCreate": ["bun install", "bun run build"],
"autoUpstream": true
},
"hotfix": {
"base": "main",
"copyFiles": [".env.production"],
"postCreate": ["bun install"]
},
"experiment": {
"worktreeDir": "~/tmp/experiments/{branch}",
"postRemove": []
}
},
"lifecycle": {
"autoCleanMerged": true,
"staleAfterDays": 14,
"maxWorktrees": 10
},
"sessions": {
"enabled": true,
"autoCreate": false,
"autoKill": true,
"prefix": "copse",
"defaultLayout": "dev",
"layouts": {
"dev": {
"windows": [
{ "name": "editor", "command": "$EDITOR ." },
{ "name": "dev", "command": "bun dev" },
{ "name": "test", "command": "bun test --watch" }
]
},
"minimal": {
"windows": [
{ "name": "shell" }
]
}
}
},
"workspaces": [
{
"path": "~/Desktop/work",
"depth": 1,
"exclude": ["node_modules", ".cache", "archived"],
"defaults": {
"copyFiles": [".env", ".env.local"],
"linkFiles": ["node_modules"],
"postCreate": ["bun install"],
"autoUpstream": true
}
}
],
"repos": [
{
"path": "/Users/me/dev/frontend",
"copyFiles": [".env", ".env.local"],
"linkFiles": ["node_modules", ".next"],
"postCreate": ["bun install", "bun run build"]
},
{
"path": "/Users/me/dev/backend",
"copyFiles": [".env"],
"postCreate": ["pip install -r requirements.txt"]
},
{
"path": "/Users/me/dev/monorepo",
"copyFiles": [".env"],
"postCreate": ["pnpm install"],
"monorepo": {
"autoDetect": true,
"extraPatterns": ["apps/*/*"],
"hooks": [
{
"glob": "apps/web",
"copyFiles": [".env"],
"postCreate": ["cd {packagePath} && pnpm install"]
},
{
"glob": "apps/api",
"copyFiles": [".env"],
"linkFiles": ["node_modules"],
"postCreate": ["cd {packagePath} && pnpm install && pnpm build"]
}
]
}
}
]
}Config Fields
defaults
All repos inherit these unless overridden.
| Field | Type | Default | Description |
| ------------- | ---------- | ---------------------------------- | --------------------------------------- |
| worktreeDir | string | ~/.copse/worktrees/{repo}-{branch} | Worktree directory pattern |
| copyFiles | string[] | [] | Files to copy from main repo |
| linkFiles | string[] | [] | Files/dirs to symlink (saves disk) |
| postCreate | string[] | [] | Commands to run after worktree creation |
| postRemove | string[] | [] | Commands to run before worktree removal |
| base | string | — | Default base ref for new branches. If it matches <remote>/<branch> of a known remote, copse add auto-runs git fetch <remote> <branch> first |
repos[]
Per-repo overrides. Each entry requires path.
| Field | Type | Required | Description |
| ------------- | ---------- | -------- | ----------------------------------- |
| path | string | Yes | Absolute path to the repository |
| worktreeDir | string | No | Override default worktree directory |
| copyFiles | string[] | No | Override default copy files |
| linkFiles | string[] | No | Override default link files |
| postCreate | string[] | No | Override default post-create hooks |
| postRemove | string[] | No | Override default post-remove hooks |
| base | string | No | Override default base ref |
| monorepo | object | No | Monorepo support config |
workspaces[]
Auto-discover git repositories under parent directories. Each discovered repo is merged into repos[] at load time with the workspace's defaults as its override layer.
{
"workspaces": [
{
"path": "~/Desktop/work",
"depth": 1,
"exclude": ["node_modules", ".cache", "archived"],
"defaults": {
"copyFiles": [".env", ".env.local"],
"linkFiles": ["node_modules"],
"postCreate": ["bun install"],
"autoUpstream": true
}
}
]
}| Field | Type | Required | Default | Description |
| ---------- | ---------- | -------- | ------- | ------------------------------------------------------------------------------ |
| path | string | Yes | — | Parent directory to scan. Supports ~ expansion. |
| depth | integer | No | 1 | Scan depth (1–3). 1 means immediate children only. |
| exclude | string[] | No | [] | Glob patterns matched against directory names to skip (e.g. node_modules). |
| defaults | object | No | — | Per-repo defaults applied to every discovered repo. Same fields as defaults. |
Discovery rules:
- A directory is a repo only if it contains a
.gitdirectory (not a file). Linked worktrees (.gitas file) and submodules are skipped. - Discovered repos do NOT have their children scanned (no recursion into repos).
- Symbolic links are not followed.
- Discovery runs on every
loadConfig()call. There is no caching.
Precedence (highest → lowest):
- Explicit
repos[]entry with the same resolved path — wins entirely. workspaces[].defaults— repo-level override layer.- Top-level
defaults. - Built-in defaults.
workspaces[].defaults does NOT support monorepo. If you need monorepo hooks for a discovered repo, add an explicit repos[] entry for it.
TUI display: The Config view (Ctrl+P → Open Config) shows the file as authored. Workspace-discovered repos appear under their own Workspaces (N) section, not under Repos (N). The Repos count therefore reflects only your explicit repos[] entries, even when workspace discovery is adding more repos at runtime. Editing any field via the Config view writes back the raw, user-authored shape — auto-discovered repos are never serialized into repos[] on disk.
monorepo
Universal monorepo support. Auto-detects packages from workspace config files and supports per-package hooks.
{
"monorepo": {
"autoDetect": true,
"extraPatterns": ["apps/*/*"],
"hooks": [
{
"glob": "apps/mobile/*",
"copyFiles": [".env"],
"linkFiles": ["node_modules"],
"postCreate": ["cd {packagePath} && pnpm install"]
}
]
}
}| Field | Type | Default | Description |
| --------------- | ---------- | ------- | ----------------------------------------- |
| autoDetect | boolean | true | Auto-detect monorepo tools |
| extraPatterns | string[] | [] | Extra glob patterns for package discovery |
| hooks | array | [] | Per-package hook definitions |
Auto-detection supports: pnpm workspaces, Turborepo, Nx, Lerna, npm/yarn workspaces.
extraPatterns catches packages not covered by auto-detection. For example, if your pnpm-workspace.yaml only covers packages/* but you also have apps at apps/frontend/dashboard, use extraPatterns: ["apps/*/*"].
monorepo.hooks[]
Per-package hooks matched by glob pattern against focus paths.
| Field | Type | Required | Description |
| ------------ | ---------- | -------- | ------------------------------------------------------------------------------ |
| glob | string | Yes | Glob to match focus paths (e.g. apps/*, apps/mobile/*) |
| copyFiles | string[] | No | Files to copy within the matched package directory |
| linkFiles | string[] | No | Files/dirs to symlink within the matched package directory |
| postCreate | string[] | No | Commands to run after creation. Supports {packagePath}, {repo}, {branch} |
| postRemove | string[] | No | Commands to run before removal |
Hooks execute in declaration order, after the repo-level postCreate/postRemove.
copyFiles/linkFiles in hooks operate on the package subdirectory, not the repo root. For example, with glob: "apps/mobile/*" and copyFiles: [".env"], the .env file is copied from <main-repo>/apps/mobile/ios/.env to <worktree>/apps/mobile/ios/.env.
templates
Named presets for worktree creation. Each template can override any default field.
{
"templates": {
"review": {
"copyFiles": [".env.local"],
"postCreate": ["bun install", "bun run build"],
"autoUpstream": true
},
"hotfix": {
"base": "main",
"copyFiles": [".env.production"],
"postCreate": ["bun install"]
}
}
}| Field | Type | Description |
| -------------- | ---------- | ---------------------------------- |
| worktreeDir | string | Override worktree directory |
| copyFiles | string[] | Override files to copy |
| linkFiles | string[] | Override files to symlink |
| postCreate | string[] | Override post-create hooks |
| postRemove | string[] | Override post-remove hooks |
| autoUpstream | boolean | Override upstream tracking |
| base | string | Default base branch for newly created branches |
Usage: copse add feature/login --template review
Template values override the resolved repo config. The base field sets a default for --base if not explicitly provided.
lifecycle
Automatic worktree lifecycle management. Used by copse clean --stale.
{
"lifecycle": {
"autoCleanMerged": true,
"staleAfterDays": 14,
"maxWorktrees": 10
}
}| Field | Type | Default | Description |
| ----------------- | --------- | ------- | ------------------------------------------- |
| autoCleanMerged | boolean | false | Flag merged worktrees for cleanup |
| staleAfterDays | number | — | Days of inactivity before flagging as stale |
| maxWorktrees | number | — | Warn when exceeding this count |
Config Profiles
Switch between different configuration sets.
copse config --profiles # List profiles
copse config --profile work --activate # Activate profile
copse config --profile personal --delete # Delete profilesessions
Tmux session management for worktrees.
{
"sessions": {
"enabled": true,
"autoCreate": true,
"autoKill": true,
"prefix": "copse",
"defaultLayout": "dev",
"layouts": {
"dev": {
"windows": [
{ "name": "editor", "command": "$EDITOR ." },
{ "name": "dev", "command": "bun dev" },
{ "name": "test", "command": "bun test --watch" }
]
}
}
}
}| Field | Type | Default | Description |
| --------------- | --------- | ------- | -------------------------------------------------- |
| enabled | boolean | false | Enable session integration (auto-switch in tmux) |
| autoCreate | boolean | false | Auto-create tmux session on copse add |
| autoKill | boolean | false | Auto-kill tmux session on copse remove |
| prefix | string | "copse" | Prefix for tmux session names |
| defaultLayout | string | — | Default layout name for new sessions |
| layouts | object | {} | Named layouts with window definitions |
Layout windows:
| Field | Type | Required | Description |
| --------- | -------- | -------- | ------------------------------ |
| name | string | Yes | Window name |
| command | string | No | Command to run in the window |
Session naming: branch feat/auth-token → tmux session copse_feat-auth-token.
sharedDeps
Share dependencies between main repo and worktrees to save disk space. Can be set in defaults or per-repo.
{
"defaults": {
"sharedDeps": {
"strategy": "hardlink",
"paths": ["node_modules"],
"invalidateOn": ["package.json", "bun.lockb"]
}
}
}| Field | Type | Default | Description |
| -------------- | ---------- | ----------- | ------------------------------------------ |
| strategy | string | "symlink" | "hardlink", "symlink", or "copy" |
| paths | string[] | [] | Directories/files to share |
| invalidateOn | string[] | [] | Files that trigger re-sharing when changed |
Strategies:
hardlink— Create hard links for each file (saves disk, each worktree can modify independently for files that get rewritten)symlink— Create a symlink to the source directory (most disk savings, shared state)copy— Fall back to regular copy
--focus Flag
Track which monorepo packages a worktree is working on.
copse add feature/login --focus apps/web,apps/api- Supports comma-separated, space-separated, or multiple
--focusflags - Focus metadata is stored in git internals (not in the worktree root)
copse listshows focus paths per worktree- Monorepo hooks only fire for matching focus paths
- Focus is optional — omitting it creates a normal worktree
Template Variables
Available in worktreeDir and monorepo hook commands:
| Variable | Description | Example |
| --------------- | ------------------------------------------ | -------------- |
| {repo} | Repository directory name | my-app |
| {branch} | Branch name (/ replaced with -) | feature-auth |
| {packagePath} | Matched package path (monorepo hooks only) | apps/web |
| ~ | Home directory (only at path start) | /Users/me |
Priority
Per-repo settings completely replace defaults (no merging):
repos[].copyFiles exists? → use repos[].copyFiles
repos[].copyFiles missing? → use defaults.copyFiles
defaults.copyFiles missing? → use [] (empty)Themes
Set via config or command palette (Ctrl+P):
{ "theme": "tokyo-night" }Available: opencode, tokyo-night, dracula, nord, catppuccin, github-dark, one-dark, monokai, github-light
Shell Integration
Use copse shell-init to install shell integration for copse switch.
Completions
# Add completions (bash)
eval "$(copse shell-init --completions bash)"
# Add completions (zsh)
eval "$(copse shell-init --completions zsh)"
# Add completions (fish)
copse shell-init --completions fish | sourceExamples
# zsh
echo 'eval "$(copse shell-init zsh)"' >> ~/.zshrc
source ~/.zshrc
# bash
echo 'eval "$(copse shell-init bash)"' >> ~/.bashrc
source ~/.bashrc
# fish
copse shell-init fish >> ~/.config/fish/config.fish
source ~/.config/fish/config.fishYou can also preview the generated wrapper before saving it:
copse shell-init zshLicense
MIT © getsolaris
