@getsolaris/copse 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1003 @@
1
+ <p align="center">
2
+ <img src="./banner.png" alt="copse" />
3
+ </p>
4
+
5
+ # 🌲 copse
6
+
7
+ **English** | [Korean](./README.ko.md)
8
+
9
+ > Git worktree manager with a beautiful TUI
10
+
11
+ Manage git worktrees with ease. Create, switch, and clean up worktrees with config-driven automation, monorepo support, and built-in health checks.
12
+
13
+ ## Features
14
+
15
+ - **TUI mode** — interactive terminal UI (`cop`)
16
+ - **CLI mode** — scriptable commands (`cop add`, `cop list`, etc.)
17
+ - **Config-driven** — per-repo hooks, file copying, symlinks
18
+ - **Monorepo support** — auto-detect packages, per-package hooks, focus tracking
19
+ - **Health checks** — `cop doctor` diagnoses worktree issues
20
+ - **Centralized worktrees** — all worktrees under `~/.copse/worktrees/` by default
21
+ - **Smart cleanup** — auto-detect and remove merged worktrees
22
+ - **Themes** — 9 built-in color themes (OpenCode, Tokyo Night, Dracula, Nord, Catppuccin, GitHub Dark, One Dark, Monokai, GitHub Light)
23
+ - **Templates** — reusable worktree presets (`cop add --template review`)
24
+ - **Cross-worktree exec** — run commands across all worktrees (`cop exec "bun test"`)
25
+ - **GitHub PR integration** — create worktrees from PRs (`cop add --pr 123`)
26
+ - **Fuzzy branch picker** — interactive branch selection in TUI with type-ahead filtering
27
+ - **Lifecycle management** — auto-detect stale/merged worktrees, configurable limits
28
+ - **Shared dependencies** — save disk with hardlink/symlink strategies for `node_modules`
29
+ - **Worktree diff** — compare changes between worktrees (`cop diff feature/a feature/b`)
30
+ - **Pin protection** — protect worktrees from auto-cleanup (`cop pin`)
31
+ - **Activity log** — track create/delete/switch/rename/archive/import events (`cop log`)
32
+ - **Archive** — preserve worktree changes as patches before removal (`cop archive`)
33
+ - **Branch rename** — rename worktree branches with metadata migration (`cop rename`)
34
+ - **Clone & init** — clone repos with cop config initialization (`cop clone`)
35
+ - **Import worktrees** — adopt manually-created worktrees (`cop import`)
36
+ - **Detail view** — expanded worktree info with commits, diff stats, upstream status (TUI)
37
+ - **Bulk actions** — multi-select and batch operations on worktrees (TUI)
38
+ - **Toast notifications** — non-blocking operation feedback (TUI)
39
+ - **Shell completions** — tab completion for bash/zsh/fish (`cop shell-init --completions`)
40
+ - **Config profiles** — switch between configuration sets (`cop config --profiles`)
41
+ - **Tmux sessions** — auto-create/kill tmux sessions per worktree with layout templates (`cop session`)
42
+ - **Workspaces** — auto-discover git repos under parent directories with per-workspace defaults (`workspaces` config)
43
+ - **AI agent init** — create config by default or install cop skill for Claude Code, Codex, OpenCode (`cop init`, `cop init --skill`)
44
+
45
+ ## Requirements
46
+
47
+ - [Bun](https://bun.sh) runtime
48
+ - git 2.17+
49
+ - macOS or Linux
50
+ - [gh CLI](https://cli.github.com) (optional, for `--pr` flag)
51
+ - [tmux](https://github.com/tmux/tmux) (optional, for `cop session`)
52
+
53
+ ## Installation
54
+
55
+ ### Homebrew (macOS/Linux)
56
+
57
+ ```bash
58
+ brew install getsolaris/tap/copse
59
+ ```
60
+
61
+ ### curl (one-liner)
62
+
63
+ ```bash
64
+ curl -fsSL https://raw.githubusercontent.com/getsolaris/copse/main/install.sh | bash
65
+ ```
66
+
67
+ ### npm / bun
68
+
69
+ ```bash
70
+ bun install -g @getsolaris/copse
71
+ # or
72
+ npm install -g @getsolaris/copse
73
+ ```
74
+
75
+ ## Local Development
76
+
77
+ For local contributor testing, run the repo directly with Bun:
78
+
79
+ ```bash
80
+ bun install
81
+ bun run src/index.ts
82
+ bun run src/index.ts <cmd>
83
+ bun run typecheck
84
+ bun test
85
+ bun run build
86
+ ```
87
+
88
+ Prefer 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.
89
+
90
+ ## Quick Start
91
+
92
+ ```bash
93
+ # Launch TUI
94
+ cop
95
+
96
+ # List worktrees
97
+ cop list
98
+
99
+ # Create a new worktree
100
+ cop add feature/my-feature
101
+
102
+ # Create with monorepo focus
103
+ cop add feature/my-feature --focus apps/web,apps/api
104
+
105
+ # Create from a GitHub PR
106
+ cop add --pr 123
107
+
108
+ # Use a template
109
+ cop add feature/login --template review
110
+
111
+ # Pin a worktree to protect from cleanup
112
+ cop pin feature/important --reason "active sprint"
113
+
114
+ # View activity log
115
+ cop log
116
+
117
+ # Archive worktree changes before removing
118
+ cop archive feature/done --yes
119
+
120
+ # Rename a worktree branch
121
+ cop rename old-name new-name
122
+
123
+ # Clone and initialize cop
124
+ cop clone https://github.com/user/repo.git
125
+
126
+ # Import an existing worktree
127
+ cop import /path/to/worktree
128
+
129
+ # Open/attach tmux session for a worktree
130
+ cop session feature/my-feature
131
+
132
+ # Create worktree with tmux session
133
+ cop add feature/new --session
134
+
135
+ # Run command across all worktrees
136
+ cop exec "bun test"
137
+
138
+ # Compare two worktrees
139
+ cop diff feature/a feature/b --stat
140
+
141
+ # Check worktree health
142
+ cop doctor
143
+
144
+ # Switch to a worktree (requires shell integration)
145
+ cop switch feature/my-feature
146
+
147
+ # Remove a worktree
148
+ cop remove feature/my-feature --yes
149
+
150
+ # Clean up merged worktrees
151
+ cop clean --dry-run
152
+
153
+ # Initialize config file
154
+ cop init
155
+
156
+ # Generate AI agent skill file
157
+ cop init --skill claude-code
158
+ ```
159
+
160
+ ## TUI Usage
161
+
162
+ Launch with `cop` (no arguments).
163
+
164
+ ### Keyboard Shortcuts
165
+
166
+ | Key | Action |
167
+ | --------- | ---------------------- |
168
+ | `j` / `k` | Navigate worktree list |
169
+ | `a` | Add worktree |
170
+ | `d` | Delete worktree |
171
+ | `o` | Open in editor (focus-aware) |
172
+ | `h` | Doctor (health check) |
173
+ | `Enter` | Open detail view |
174
+ | `Escape` | Close detail view / picker |
175
+ | `Space` | Toggle worktree selection |
176
+ | `Ctrl+A` | Select all worktrees |
177
+ | `x` | Bulk actions menu |
178
+ | `r` | Refresh list |
179
+ | `Ctrl+P` | Command palette |
180
+ | `?` | Help |
181
+ | `q` | Quit |
182
+
183
+ #### `o` — Focus-aware editor open
184
+
185
+ Pressing `o` opens the selected worktree in `$VISUAL` / `$EDITOR`:
186
+
187
+ - **No focus paths set** → opens the worktree root.
188
+ - **Exactly 1 focus path** → opens `<worktree>/<focus>` directly.
189
+ - **2+ focus paths** → shows a picker so you can choose which focus path (or the worktree root) to open.
190
+
191
+ The picker supports `j`/`k` or `↑`/`↓` to navigate, `Enter` to open, and `Esc` to cancel.
192
+
193
+ ### Command Palette (`Ctrl+P`)
194
+
195
+ Searchable command menu with:
196
+
197
+ - Add / Delete / Refresh worktrees
198
+ - Run Doctor
199
+ - Open Config
200
+ - Switch theme
201
+ - Quit
202
+
203
+ Type to filter, `↑↓` to navigate, `Enter` to execute, `Esc` to close.
204
+
205
+ ### Worktree Creation Flow
206
+
207
+ 1. Press `a` to open the Create view
208
+ 2. Start typing a branch name — matching branches appear as you type
209
+ 3. Use `↑↓` to select from suggestions, or keep typing for a new branch
210
+ 4. Press `Tab` to switch to the Focus field (optional)
211
+ 5. Type focus paths (e.g. `apps/web,apps/api`)
212
+ 6. Press `Enter` to preview
213
+ 7. Press `Enter` to confirm
214
+
215
+ The fuzzy branch picker shows local and remote branches sorted by last commit date, filtered in real-time as you type.
216
+
217
+ After creation, the configured `copyFiles`, `linkFiles`, `postCreate` hooks, and monorepo hooks run automatically.
218
+
219
+ ### Doctor View
220
+
221
+ Press `h` to open the Doctor tab. Shows health check results:
222
+
223
+ - ✓ Git version check
224
+ - ✓ Config validation
225
+ - ✓ Stale worktree detection
226
+ - ✓ Orphaned directory detection
227
+ - ✓ Lock status check
228
+ - ✓ Dirty worktree detection
229
+
230
+ Press `r` to recheck, `Esc` to go back.
231
+
232
+ ### Config View
233
+
234
+ Open with `Ctrl+P` → `Open Config`. The Config tab renders the full contents of `~/.config/copse/config.json`, including:
235
+
236
+ - Top-level: `version`, `theme`, `activeProfile`
237
+ - `defaults` (including `postRemove`, `autoUpstream`, `sharedDeps`)
238
+ - All per-repo overrides
239
+ - The full `monorepo` tree — `autoDetect`, `extraPatterns`, and every `hooks[]` entry with its `glob` / `copyFiles` / `linkFiles` / `postCreate` / `postRemove`
240
+ - `templates`, `lifecycle`, `sessions`, `profiles`
241
+
242
+ Most scalar and string-array fields are editable inline. The counter in the header shows the current position (`1/20`).
243
+
244
+ | Key | Action |
245
+ | --- | --- |
246
+ | `j` / `k` | Navigate editable fields |
247
+ | `g` / `G` | Jump to first / last field |
248
+ | `Enter` | Edit the selected field |
249
+ | `Tab` | Cycle through preset values (in edit mode) |
250
+ | `Space` / `←→` | Toggle a boolean / cycle a theme (in edit mode) |
251
+ | `Enter` | Commit the edit (saves to disk + reloads) |
252
+ | `Esc` | Cancel the edit |
253
+ | `e` | Open the config file in `$EDITOR` |
254
+ | `r` | Reload the file from disk |
255
+ | `i` | Initialize the config file if missing |
256
+
257
+ Inline editing supports five kinds of fields:
258
+
259
+ - **Strings** — plain text input (e.g. `worktreeDir`, hook `glob`, `sessions.prefix`). Press `Tab` to cycle through common presets.
260
+ - **String arrays** — JSON input like `[".env", ".env.local"]`. Empty input is treated as `[]`. Press `Tab` to cycle through presets including `[]` as the first option.
261
+ - **Booleans** — toggle with `Space`, `Tab`, or `←→`, commit with `Enter`.
262
+ - **Themes** — cycle with `Tab` or `←→`, commit with `Enter`. The new theme is applied live.
263
+ - **Enums** — fields with a fixed set of valid values (e.g. `sharedDeps.strategy` = `hardlink` / `symlink` / `copy`). Cycle with `Tab` or `←→`, commit with `Enter`.
264
+
265
+ 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.
266
+
267
+ 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`.
268
+
269
+ ## CLI Commands
270
+
271
+ | Command | Description |
272
+ | ------------------------ | ------------------------------------ |
273
+ | `cop` | Launch TUI |
274
+ | `cop list` | List all worktrees (with focus info) |
275
+ | `cop add <branch>` | Create worktree |
276
+ | `cop remove <branch>` | Remove worktree |
277
+ | `cop switch <branch>` | Switch to worktree |
278
+ | `cop clean` | Remove merged worktrees |
279
+ | `cop doctor` | Check worktree health |
280
+ | `cop config` | Manage configuration |
281
+ | `cop exec <command>` | Run command in each worktree |
282
+ | `cop diff <ref1> [ref2]` | Diff between worktrees/branches |
283
+ | `cop pin <branch>` | Pin/unpin worktree (protect from cleanup) |
284
+ | `cop log` | Show worktree activity log |
285
+ | `cop archive <branch>` | Archive changes and optionally remove |
286
+ | `cop rename <old> <new>` | Rename worktree branch |
287
+ | `cop clone <url>` | Clone repo and initialize cop |
288
+ | `cop import <path>` | Adopt worktree with cop metadata |
289
+ | `cop session [branch]` | Manage tmux sessions for worktrees |
290
+ | `cop open [branch]` | Open a worktree in your editor (focus-aware) |
291
+ | `cop init` | Initialize config or install AI agent skills |
292
+
293
+ ### `cop add`
294
+
295
+ ```bash
296
+ cop add feature/login # Create branch if needed + worktree
297
+ cop add feature/login --base main # New branches start from main
298
+ cop add existing-branch # Worktree for existing branch
299
+
300
+ # Monorepo: create with focus packages
301
+ cop add feature/login --focus apps/web,apps/api
302
+ cop add feature/login --focus apps/web --focus apps/api
303
+
304
+ # Use a template
305
+ cop add feature/login --template review
306
+
307
+ # Create from a GitHub PR (requires gh CLI)
308
+ cop add --pr 123
309
+ cop add --pr 456 --template review
310
+ ```
311
+
312
+ ### `cop doctor`
313
+
314
+ ```bash
315
+ cop doctor # Human-readable output
316
+ cop doctor --json # JSON output for scripting
317
+ ```
318
+
319
+ Exit code: `0` if healthy, `1` if any warnings or errors.
320
+
321
+ ```
322
+ copse doctor
323
+
324
+ ✓ Git version: 2.39.0 (>= 2.17 required)
325
+ ✓ Configuration: valid
326
+ ✓ Stale worktrees: none
327
+ ✓ Orphaned directories: none
328
+ ✓ Worktree locks: all clear
329
+ ✓ Dirty worktrees: none
330
+
331
+ All checks passed.
332
+ ```
333
+
334
+ ### `cop list`
335
+
336
+ ```bash
337
+ cop list # Table with Focus column
338
+ cop list --json # JSON with focus array
339
+ cop list --porcelain # Machine-readable
340
+ ```
341
+
342
+ Output includes a `Focus` column showing monorepo focus paths per worktree.
343
+
344
+ ### `cop remove`
345
+
346
+ ```bash
347
+ cop remove feature/login # Remove by branch name
348
+ cop remove feature/login --force # Force remove (dirty worktree)
349
+ cop remove feature/login --yes # Skip confirmation
350
+ ```
351
+
352
+ ### `cop clean`
353
+
354
+ ```bash
355
+ cop clean --dry-run # Preview what would be removed
356
+ cop clean # Remove all merged worktrees
357
+ cop clean --stale # Also show stale worktrees (uses lifecycle config)
358
+ ```
359
+
360
+ ### `cop exec`
361
+
362
+ Run a shell command in every non-main worktree.
363
+
364
+ ```bash
365
+ cop exec "bun test" # Run in all worktrees (sequential)
366
+ cop exec "bun test" --parallel # Run in parallel
367
+ cop exec "git pull" --all # Across all configured repos
368
+ cop exec "bun install" --dirty # Only dirty worktrees
369
+ cop exec "git rebase main" --behind # Only worktrees behind upstream
370
+ cop exec "bun test" --json # JSON output
371
+ ```
372
+
373
+ | Flag | Description |
374
+ | ------------------- | ------------------------------------- |
375
+ | `--parallel` / `-p` | Run commands in parallel |
376
+ | `--all` / `-a` | Include all configured repos |
377
+ | `--dirty` | Only run in dirty worktrees |
378
+ | `--clean` | Only run in clean worktrees |
379
+ | `--behind` | Only run in worktrees behind upstream |
380
+ | `--json` / `-j` | Output results as JSON |
381
+
382
+ Environment variables available in commands: `COP_BRANCH`, `COP_WORKTREE_PATH`, `COP_REPO_PATH`.
383
+
384
+ ### `cop diff`
385
+
386
+ Show diff between two worktree branches.
387
+
388
+ ```bash
389
+ cop diff feature/a feature/b # Full diff
390
+ cop diff feature/a feature/b --stat # Diffstat summary
391
+ cop diff feature/a --name-only # Changed file names only
392
+ cop diff feature/a # Compare against current HEAD
393
+ ```
394
+
395
+ ### `cop pin`
396
+
397
+ ```bash
398
+ cop pin feature/auth --reason "active sprint" # Pin with reason
399
+ cop pin --list # List pinned worktrees
400
+ cop pin --list --json # JSON output
401
+ cop unpin feature/auth # Unpin
402
+ ```
403
+
404
+ Pinned worktrees are excluded from `cop clean` and lifecycle auto-cleanup.
405
+
406
+ ### `cop log`
407
+
408
+ ```bash
409
+ cop log # Show last 20 events
410
+ cop log --limit 50 # Show last 50 events
411
+ cop log --json # JSON output
412
+ cop log --clear # Clear activity log
413
+ ```
414
+
415
+ Events are color-coded: create (green), delete (red), switch (blue), rename (yellow), archive (magenta), import (cyan).
416
+
417
+ ### `cop archive`
418
+
419
+ ```bash
420
+ cop archive feature/done --yes # Archive and remove
421
+ cop archive feature/wip --keep # Archive without removing
422
+ cop archive --list # List all archives
423
+ cop archive --list --json # JSON output
424
+ ```
425
+
426
+ Archives are stored as patch files in `~/.copse/archives/`.
427
+
428
+ ### `cop rename`
429
+
430
+ ```bash
431
+ cop rename old-branch new-branch # Rename branch
432
+ cop rename old-branch new-branch --move-path # Also move worktree directory
433
+ ```
434
+
435
+ ### `cop clone`
436
+
437
+ ```bash
438
+ cop clone https://github.com/user/repo.git # Clone and init
439
+ cop clone https://github.com/user/repo.git ./my-dir # Custom target path
440
+ cop clone https://github.com/user/repo.git --template review # Apply template
441
+ cop clone https://github.com/user/repo.git --no-init-config # Skip config init
442
+ ```
443
+
444
+ ### `cop import`
445
+
446
+ ```bash
447
+ cop import /path/to/worktree # Adopt worktree
448
+ cop import /path/to/worktree --focus apps/web,apps/api # With focus
449
+ cop import /path/to/worktree --pin # Pin immediately
450
+ ```
451
+
452
+ ### `cop session`
453
+
454
+ Manage tmux sessions for worktrees. Requires tmux.
455
+
456
+ ```bash
457
+ cop session feature/auth # Open/attach session (create if needed)
458
+ cop session feature/auth --layout api # Use named layout from config
459
+ cop session --list # List active cop sessions
460
+ cop session --list --json # JSON output
461
+ cop session feature/auth --kill # Kill session for worktree
462
+ cop session --kill-all # Kill all cop sessions
463
+ ```
464
+
465
+ Sessions are auto-created/killed when `sessions.autoCreate` / `sessions.autoKill` are enabled in config.
466
+
467
+ ```bash
468
+ # Create worktree with tmux session
469
+ cop add feature/login --session
470
+ cop add feature/login --session --layout api
471
+ ```
472
+
473
+ When `sessions.enabled` is `true` and you're inside tmux, `cop switch` automatically switches to the target worktree's tmux session.
474
+
475
+ ### `cop open`
476
+
477
+ 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`).
478
+
479
+ ```bash
480
+ cop open # Open the current worktree
481
+ cop open feature/auth # Open a specific worktree
482
+ cop open feature/auth -e nvim # Override editor
483
+
484
+ # Focus-aware behavior (when the worktree was created with --focus)
485
+ cop open feature/auth # 1 focus path → opens that focus
486
+ # 2+ focus paths → errors with hint
487
+ cop open feature/auth --focus apps/web # Pick a specific focus path
488
+ cop open feature/auth -f apps/api # Same with the short alias
489
+ cop open feature/auth --root # Force the worktree root, ignore focus
490
+
491
+ cop open --list-editors # List detected editors
492
+ ```
493
+
494
+ | Flag | Alias | Description |
495
+ | ---- | ----- | ----------- |
496
+ | `--editor` | `-e` | Editor command to use (overrides `$VISUAL`/`$EDITOR`) |
497
+ | `--focus` | `-f` | Open a specific focus path (must match a focus entry on the worktree) |
498
+ | `--root` | | Force the worktree root, ignoring any focus paths |
499
+ | `--list-editors` | | List detected editors |
500
+
501
+ **Focus resolution rules:**
502
+
503
+ - 0 focus paths set → opens the worktree root.
504
+ - 1 focus path set → opens `<worktree>/<focus>` automatically.
505
+ - 2+ focus paths set → errors out and asks for `--focus <path>` or `--root` (the TUI shows an interactive picker instead).
506
+
507
+ ### `cop init`
508
+
509
+ Initialize cop config by default, or install cop skill for AI coding agents so they can use cop commands.
510
+
511
+ ```bash
512
+ cop init # → ~/.config/copse/config.json
513
+ cop init --skill claude-code # → ~/.claude/skills/cop/
514
+ cop init --skill codex # → ~/.agents/skills/cop/
515
+ cop init --skill opencode # → ~/.config/opencode/skill/cop/
516
+ ```
517
+
518
+ | Platform | Skill Path |
519
+ |----------|-----------|
520
+ | `claude-code` | `~/.claude/skills/cop/` |
521
+ | `codex` | `~/.agents/skills/cop/` |
522
+ | `opencode` | `~/.config/opencode/skill/cop/` |
523
+
524
+ Each skill directory contains:
525
+ - `SKILL.md` — overview and common workflows
526
+ - `references/` — detailed per-command documentation (21 files)
527
+
528
+ Without `--skill`, the command reuses the normal config initializer and creates only `config.json`.
529
+ The command is idempotent — running it again updates the skill directory.
530
+
531
+ #### Auto-init on first run
532
+
533
+ You don't have to run `cop init` manually. The first time you run any `cop` command (including launching the TUI), if `~/.config/copse/config.json` does not exist, cop creates it with the default template and prints a one-line notice to stderr:
534
+
535
+ ```
536
+ cop: created default config at /Users/you/.config/copse/config.json
537
+ ```
538
+
539
+ The notice is suppressed when stdout is not a TTY (so pipes, scripts, and CI stay quiet) and when you run `cop init` explicitly (to avoid duplicate messages with init's own success line). Auto-init is fully idempotent — subsequent runs do nothing.
540
+
541
+ ## Configuration
542
+
543
+ Config file: `~/.config/copse/config.json`
544
+
545
+ Initialize with: `cop config --init` (or just run any `cop` command — see [Auto-init on first run](#auto-init-on-first-run))
546
+
547
+ ### Full Example
548
+
549
+ ```json
550
+ {
551
+ "$schema": "https://raw.githubusercontent.com/getsolaris/copse/main/schema.json",
552
+ "version": 1,
553
+ "theme": "dracula",
554
+ "defaults": {
555
+ "worktreeDir": "~/.copse/worktrees/{repo}-{branch}",
556
+ "copyFiles": [".env"],
557
+ "linkFiles": ["node_modules"],
558
+ "postCreate": ["bun install"],
559
+ "postRemove": [],
560
+ "sharedDeps": {
561
+ "strategy": "hardlink",
562
+ "paths": ["node_modules"],
563
+ "invalidateOn": ["package.json", "bun.lockb"]
564
+ }
565
+ },
566
+ "templates": {
567
+ "review": {
568
+ "copyFiles": [".env.local"],
569
+ "postCreate": ["bun install", "bun run build"],
570
+ "autoUpstream": true
571
+ },
572
+ "hotfix": {
573
+ "base": "main",
574
+ "copyFiles": [".env.production"],
575
+ "postCreate": ["bun install"]
576
+ },
577
+ "experiment": {
578
+ "worktreeDir": "~/tmp/experiments/{branch}",
579
+ "postRemove": []
580
+ }
581
+ },
582
+ "lifecycle": {
583
+ "autoCleanMerged": true,
584
+ "staleAfterDays": 14,
585
+ "maxWorktrees": 10
586
+ },
587
+ "sessions": {
588
+ "enabled": true,
589
+ "autoCreate": false,
590
+ "autoKill": true,
591
+ "prefix": "cop",
592
+ "defaultLayout": "dev",
593
+ "layouts": {
594
+ "dev": {
595
+ "windows": [
596
+ { "name": "editor", "command": "$EDITOR ." },
597
+ { "name": "dev", "command": "bun dev" },
598
+ { "name": "test", "command": "bun test --watch" }
599
+ ]
600
+ },
601
+ "minimal": {
602
+ "windows": [
603
+ { "name": "shell" }
604
+ ]
605
+ }
606
+ }
607
+ },
608
+ "workspaces": [
609
+ {
610
+ "path": "~/Desktop/work",
611
+ "depth": 1,
612
+ "exclude": ["node_modules", ".cache", "archived"],
613
+ "defaults": {
614
+ "copyFiles": [".env", ".env.local"],
615
+ "linkFiles": ["node_modules"],
616
+ "postCreate": ["bun install"],
617
+ "autoUpstream": true
618
+ }
619
+ }
620
+ ],
621
+ "repos": [
622
+ {
623
+ "path": "/Users/me/dev/frontend",
624
+ "copyFiles": [".env", ".env.local"],
625
+ "linkFiles": ["node_modules", ".next"],
626
+ "postCreate": ["bun install", "bun run build"]
627
+ },
628
+ {
629
+ "path": "/Users/me/dev/backend",
630
+ "copyFiles": [".env"],
631
+ "postCreate": ["pip install -r requirements.txt"]
632
+ },
633
+ {
634
+ "path": "/Users/me/dev/monorepo",
635
+ "copyFiles": [".env"],
636
+ "postCreate": ["pnpm install"],
637
+ "monorepo": {
638
+ "autoDetect": true,
639
+ "extraPatterns": ["apps/*/*"],
640
+ "hooks": [
641
+ {
642
+ "glob": "apps/web",
643
+ "copyFiles": [".env"],
644
+ "postCreate": ["cd {packagePath} && pnpm install"]
645
+ },
646
+ {
647
+ "glob": "apps/api",
648
+ "copyFiles": [".env"],
649
+ "linkFiles": ["node_modules"],
650
+ "postCreate": ["cd {packagePath} && pnpm install && pnpm build"]
651
+ }
652
+ ]
653
+ }
654
+ }
655
+ ]
656
+ }
657
+ ```
658
+
659
+ ### Config Fields
660
+
661
+ #### `defaults`
662
+
663
+ All repos inherit these unless overridden.
664
+
665
+ | Field | Type | Default | Description |
666
+ | ------------- | ---------- | ---------------------------------- | --------------------------------------- |
667
+ | `worktreeDir` | `string` | `~/.copse/worktrees/{repo}-{branch}` | Worktree directory pattern |
668
+ | `copyFiles` | `string[]` | `[]` | Files to copy from main repo |
669
+ | `linkFiles` | `string[]` | `[]` | Files/dirs to symlink (saves disk) |
670
+ | `postCreate` | `string[]` | `[]` | Commands to run after worktree creation |
671
+ | `postRemove` | `string[]` | `[]` | Commands to run before worktree removal |
672
+
673
+ #### `repos[]`
674
+
675
+ Per-repo overrides. Each entry requires `path`.
676
+
677
+ | Field | Type | Required | Description |
678
+ | ------------- | ---------- | -------- | ----------------------------------- |
679
+ | `path` | `string` | Yes | Absolute path to the repository |
680
+ | `worktreeDir` | `string` | No | Override default worktree directory |
681
+ | `copyFiles` | `string[]` | No | Override default copy files |
682
+ | `linkFiles` | `string[]` | No | Override default link files |
683
+ | `postCreate` | `string[]` | No | Override default post-create hooks |
684
+ | `postRemove` | `string[]` | No | Override default post-remove hooks |
685
+ | `monorepo` | `object` | No | Monorepo support config |
686
+
687
+ #### `workspaces[]`
688
+
689
+ 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.
690
+
691
+ ```json
692
+ {
693
+ "workspaces": [
694
+ {
695
+ "path": "~/Desktop/work",
696
+ "depth": 1,
697
+ "exclude": ["node_modules", ".cache", "archived"],
698
+ "defaults": {
699
+ "copyFiles": [".env", ".env.local"],
700
+ "linkFiles": ["node_modules"],
701
+ "postCreate": ["bun install"],
702
+ "autoUpstream": true
703
+ }
704
+ }
705
+ ]
706
+ }
707
+ ```
708
+
709
+ | Field | Type | Required | Default | Description |
710
+ | ---------- | ---------- | -------- | ------- | ------------------------------------------------------------------------------ |
711
+ | `path` | `string` | Yes | — | Parent directory to scan. Supports `~` expansion. |
712
+ | `depth` | `integer` | No | `1` | Scan depth (1–3). `1` means immediate children only. |
713
+ | `exclude` | `string[]` | No | `[]` | Glob patterns matched against directory names to skip (e.g. `node_modules`). |
714
+ | `defaults` | `object` | No | — | Per-repo defaults applied to every discovered repo. Same fields as `defaults`. |
715
+
716
+ **Discovery rules:**
717
+
718
+ - A directory is a repo only if it contains a `.git` **directory** (not a file). Linked worktrees (`.git` as file) and submodules are skipped.
719
+ - Discovered repos do NOT have their children scanned (no recursion into repos).
720
+ - Symbolic links are not followed.
721
+ - Discovery runs on every `loadConfig()` call. There is no caching.
722
+
723
+ **Precedence (highest → lowest):**
724
+
725
+ 1. Explicit `repos[]` entry with the same resolved path — wins entirely.
726
+ 2. `workspaces[].defaults` — repo-level override layer.
727
+ 3. Top-level `defaults`.
728
+ 4. Built-in defaults.
729
+
730
+ **`workspaces[].defaults` does NOT support `monorepo`.** If you need monorepo hooks for a discovered repo, add an explicit `repos[]` entry for it.
731
+
732
+ **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.
733
+
734
+ #### `monorepo`
735
+
736
+ Universal monorepo support. Auto-detects packages from workspace config files and supports per-package hooks.
737
+
738
+ ```json
739
+ {
740
+ "monorepo": {
741
+ "autoDetect": true,
742
+ "extraPatterns": ["apps/*/*"],
743
+ "hooks": [
744
+ {
745
+ "glob": "apps/mobile/*",
746
+ "copyFiles": [".env"],
747
+ "linkFiles": ["node_modules"],
748
+ "postCreate": ["cd {packagePath} && pnpm install"]
749
+ }
750
+ ]
751
+ }
752
+ }
753
+ ```
754
+
755
+ | Field | Type | Default | Description |
756
+ | --------------- | ---------- | ------- | ----------------------------------------- |
757
+ | `autoDetect` | `boolean` | `true` | Auto-detect monorepo tools |
758
+ | `extraPatterns` | `string[]` | `[]` | Extra glob patterns for package discovery |
759
+ | `hooks` | `array` | `[]` | Per-package hook definitions |
760
+
761
+ **Auto-detection** supports: pnpm workspaces, Turborepo, Nx, Lerna, npm/yarn workspaces.
762
+
763
+ **`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/*/*"]`.
764
+
765
+ #### `monorepo.hooks[]`
766
+
767
+ Per-package hooks matched by glob pattern against focus paths.
768
+
769
+ | Field | Type | Required | Description |
770
+ | ------------ | ---------- | -------- | ------------------------------------------------------------------------------ |
771
+ | `glob` | `string` | Yes | Glob to match focus paths (e.g. `apps/*`, `apps/mobile/*`) |
772
+ | `copyFiles` | `string[]` | No | Files to copy within the matched package directory |
773
+ | `linkFiles` | `string[]` | No | Files/dirs to symlink within the matched package directory |
774
+ | `postCreate` | `string[]` | No | Commands to run after creation. Supports `{packagePath}`, `{repo}`, `{branch}` |
775
+ | `postRemove` | `string[]` | No | Commands to run before removal |
776
+
777
+ Hooks execute in declaration order, after the repo-level `postCreate`/`postRemove`.
778
+
779
+ **`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`.
780
+
781
+ #### `templates`
782
+
783
+ Named presets for worktree creation. Each template can override any default field.
784
+
785
+ ```json
786
+ {
787
+ "templates": {
788
+ "review": {
789
+ "copyFiles": [".env.local"],
790
+ "postCreate": ["bun install", "bun run build"],
791
+ "autoUpstream": true
792
+ },
793
+ "hotfix": {
794
+ "base": "main",
795
+ "copyFiles": [".env.production"],
796
+ "postCreate": ["bun install"]
797
+ }
798
+ }
799
+ }
800
+ ```
801
+
802
+ | Field | Type | Description |
803
+ | -------------- | ---------- | ---------------------------------- |
804
+ | `worktreeDir` | `string` | Override worktree directory |
805
+ | `copyFiles` | `string[]` | Override files to copy |
806
+ | `linkFiles` | `string[]` | Override files to symlink |
807
+ | `postCreate` | `string[]` | Override post-create hooks |
808
+ | `postRemove` | `string[]` | Override post-remove hooks |
809
+ | `autoUpstream` | `boolean` | Override upstream tracking |
810
+ | `base` | `string` | Default base branch for newly created branches |
811
+
812
+ Usage: `cop add feature/login --template review`
813
+
814
+ Template values override the resolved repo config. The `base` field sets a default for `--base` if not explicitly provided.
815
+
816
+ #### `lifecycle`
817
+
818
+ Automatic worktree lifecycle management. Used by `cop clean --stale`.
819
+
820
+ ```json
821
+ {
822
+ "lifecycle": {
823
+ "autoCleanMerged": true,
824
+ "staleAfterDays": 14,
825
+ "maxWorktrees": 10
826
+ }
827
+ }
828
+ ```
829
+
830
+ | Field | Type | Default | Description |
831
+ | ----------------- | --------- | ------- | ------------------------------------------- |
832
+ | `autoCleanMerged` | `boolean` | `false` | Flag merged worktrees for cleanup |
833
+ | `staleAfterDays` | `number` | — | Days of inactivity before flagging as stale |
834
+ | `maxWorktrees` | `number` | — | Warn when exceeding this count |
835
+
836
+ #### Config Profiles
837
+
838
+ Switch between different configuration sets.
839
+
840
+ ```bash
841
+ cop config --profiles # List profiles
842
+ cop config --profile work --activate # Activate profile
843
+ cop config --profile personal --delete # Delete profile
844
+ ```
845
+
846
+ #### `sessions`
847
+
848
+ Tmux session management for worktrees.
849
+
850
+ ```json
851
+ {
852
+ "sessions": {
853
+ "enabled": true,
854
+ "autoCreate": true,
855
+ "autoKill": true,
856
+ "prefix": "cop",
857
+ "defaultLayout": "dev",
858
+ "layouts": {
859
+ "dev": {
860
+ "windows": [
861
+ { "name": "editor", "command": "$EDITOR ." },
862
+ { "name": "dev", "command": "bun dev" },
863
+ { "name": "test", "command": "bun test --watch" }
864
+ ]
865
+ }
866
+ }
867
+ }
868
+ }
869
+ ```
870
+
871
+ | Field | Type | Default | Description |
872
+ | --------------- | --------- | ------- | -------------------------------------------------- |
873
+ | `enabled` | `boolean` | `false` | Enable session integration (auto-switch in tmux) |
874
+ | `autoCreate` | `boolean` | `false` | Auto-create tmux session on `cop add` |
875
+ | `autoKill` | `boolean` | `false` | Auto-kill tmux session on `cop remove` |
876
+ | `prefix` | `string` | `"cop"` | Prefix for tmux session names |
877
+ | `defaultLayout` | `string` | — | Default layout name for new sessions |
878
+ | `layouts` | `object` | `{}` | Named layouts with window definitions |
879
+
880
+ **Layout windows:**
881
+
882
+ | Field | Type | Required | Description |
883
+ | --------- | -------- | -------- | ------------------------------ |
884
+ | `name` | `string` | Yes | Window name |
885
+ | `command` | `string` | No | Command to run in the window |
886
+
887
+ Session naming: branch `feat/auth-token` → tmux session `cop_feat-auth-token`.
888
+
889
+ #### `sharedDeps`
890
+
891
+ Share dependencies between main repo and worktrees to save disk space. Can be set in `defaults` or per-repo.
892
+
893
+ ```json
894
+ {
895
+ "defaults": {
896
+ "sharedDeps": {
897
+ "strategy": "hardlink",
898
+ "paths": ["node_modules"],
899
+ "invalidateOn": ["package.json", "bun.lockb"]
900
+ }
901
+ }
902
+ }
903
+ ```
904
+
905
+ | Field | Type | Default | Description |
906
+ | -------------- | ---------- | ----------- | ------------------------------------------ |
907
+ | `strategy` | `string` | `"symlink"` | `"hardlink"`, `"symlink"`, or `"copy"` |
908
+ | `paths` | `string[]` | `[]` | Directories/files to share |
909
+ | `invalidateOn` | `string[]` | `[]` | Files that trigger re-sharing when changed |
910
+
911
+ **Strategies:**
912
+
913
+ - `hardlink` — Create hard links for each file (saves disk, each worktree can modify independently for files that get rewritten)
914
+ - `symlink` — Create a symlink to the source directory (most disk savings, shared state)
915
+ - `copy` — Fall back to regular copy
916
+
917
+ ### `--focus` Flag
918
+
919
+ Track which monorepo packages a worktree is working on.
920
+
921
+ ```bash
922
+ cop add feature/login --focus apps/web,apps/api
923
+ ```
924
+
925
+ - Supports comma-separated, space-separated, or multiple `--focus` flags
926
+ - Focus metadata is stored in git internals (not in the worktree root)
927
+ - `cop list` shows focus paths per worktree
928
+ - Monorepo hooks only fire for matching focus paths
929
+ - Focus is optional — omitting it creates a normal worktree
930
+
931
+ ### Template Variables
932
+
933
+ Available in `worktreeDir` and monorepo hook commands:
934
+
935
+ | Variable | Description | Example |
936
+ | --------------- | ------------------------------------------ | -------------- |
937
+ | `{repo}` | Repository directory name | `my-app` |
938
+ | `{branch}` | Branch name (`/` replaced with `-`) | `feature-auth` |
939
+ | `{packagePath}` | Matched package path (monorepo hooks only) | `apps/web` |
940
+ | `~` | Home directory (only at path start) | `/Users/me` |
941
+
942
+ ### Priority
943
+
944
+ Per-repo settings completely replace defaults (no merging):
945
+
946
+ ```
947
+ repos[].copyFiles exists? → use repos[].copyFiles
948
+ repos[].copyFiles missing? → use defaults.copyFiles
949
+ defaults.copyFiles missing? → use [] (empty)
950
+ ```
951
+
952
+ ### Themes
953
+
954
+ Set via config or command palette (`Ctrl+P`):
955
+
956
+ ```json
957
+ { "theme": "tokyo-night" }
958
+ ```
959
+
960
+ Available: `opencode`, `tokyo-night`, `dracula`, `nord`, `catppuccin`, `github-dark`, `one-dark`, `monokai`, `github-light`
961
+
962
+ ## Shell Integration
963
+
964
+ Use `cop shell-init` to install shell integration for `cop switch`.
965
+
966
+ ### Completions
967
+
968
+ ```bash
969
+ # Add completions (bash)
970
+ eval "$(cop shell-init --completions bash)"
971
+
972
+ # Add completions (zsh)
973
+ eval "$(cop shell-init --completions zsh)"
974
+
975
+ # Add completions (fish)
976
+ cop shell-init --completions fish | source
977
+ ```
978
+
979
+ ### Examples
980
+
981
+ ```bash
982
+ # zsh
983
+ echo 'eval "$(cop shell-init zsh)"' >> ~/.zshrc
984
+ source ~/.zshrc
985
+
986
+ # bash
987
+ echo 'eval "$(cop shell-init bash)"' >> ~/.bashrc
988
+ source ~/.bashrc
989
+
990
+ # fish
991
+ cop shell-init fish >> ~/.config/fish/config.fish
992
+ source ~/.config/fish/config.fish
993
+ ```
994
+
995
+ You can also preview the generated wrapper before saving it:
996
+
997
+ ```bash
998
+ cop shell-init zsh
999
+ ```
1000
+
1001
+ ## License
1002
+
1003
+ MIT © getsolaris