@hytfjwr/claude-worktree 0.1.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/LICENSE +21 -0
- package/README.md +233 -0
- package/dist/bin/claude-worktree.d.ts +3 -0
- package/dist/bin/claude-worktree.d.ts.map +1 -0
- package/dist/bin/claude-worktree.js +14 -0
- package/dist/bin/claude-worktree.js.map +1 -0
- package/dist/src/cli.d.ts +17 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +360 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/commands/clean.d.ts +3 -0
- package/dist/src/commands/clean.d.ts.map +1 -0
- package/dist/src/commands/clean.js +190 -0
- package/dist/src/commands/clean.js.map +1 -0
- package/dist/src/commands/create.d.ts +14 -0
- package/dist/src/commands/create.d.ts.map +1 -0
- package/dist/src/commands/create.js +458 -0
- package/dist/src/commands/create.js.map +1 -0
- package/dist/src/commands/hooks.d.ts +8 -0
- package/dist/src/commands/hooks.d.ts.map +1 -0
- package/dist/src/commands/hooks.js +28 -0
- package/dist/src/commands/hooks.js.map +1 -0
- package/dist/src/commands/list.d.ts +17 -0
- package/dist/src/commands/list.d.ts.map +1 -0
- package/dist/src/commands/list.js +228 -0
- package/dist/src/commands/list.js.map +1 -0
- package/dist/src/commands/rollback.d.ts +3 -0
- package/dist/src/commands/rollback.d.ts.map +1 -0
- package/dist/src/commands/rollback.js +99 -0
- package/dist/src/commands/rollback.js.map +1 -0
- package/dist/src/commands/run-in-pane.d.ts +4 -0
- package/dist/src/commands/run-in-pane.d.ts.map +1 -0
- package/dist/src/commands/run-in-pane.js +120 -0
- package/dist/src/commands/run-in-pane.js.map +1 -0
- package/dist/src/core/cache.d.ts +10 -0
- package/dist/src/core/cache.d.ts.map +1 -0
- package/dist/src/core/cache.js +66 -0
- package/dist/src/core/cache.js.map +1 -0
- package/dist/src/core/config.d.ts +11 -0
- package/dist/src/core/config.d.ts.map +1 -0
- package/dist/src/core/config.js +181 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/errors.d.ts +9 -0
- package/dist/src/core/errors.d.ts.map +1 -0
- package/dist/src/core/errors.js +20 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/exec.d.ts +73 -0
- package/dist/src/core/exec.d.ts.map +1 -0
- package/dist/src/core/exec.js +138 -0
- package/dist/src/core/exec.js.map +1 -0
- package/dist/src/core/git.d.ts +28 -0
- package/dist/src/core/git.d.ts.map +1 -0
- package/dist/src/core/git.js +291 -0
- package/dist/src/core/git.js.map +1 -0
- package/dist/src/core/session.d.ts +9 -0
- package/dist/src/core/session.d.ts.map +1 -0
- package/dist/src/core/session.js +87 -0
- package/dist/src/core/session.js.map +1 -0
- package/dist/src/core/slot.d.ts +7 -0
- package/dist/src/core/slot.d.ts.map +1 -0
- package/dist/src/core/slot.js +73 -0
- package/dist/src/core/slot.js.map +1 -0
- package/dist/src/external/claude.d.ts +3 -0
- package/dist/src/external/claude.d.ts.map +1 -0
- package/dist/src/external/claude.js +71 -0
- package/dist/src/external/claude.js.map +1 -0
- package/dist/src/external/wezterm.d.ts +11 -0
- package/dist/src/external/wezterm.d.ts.map +1 -0
- package/dist/src/external/wezterm.js +87 -0
- package/dist/src/external/wezterm.js.map +1 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +44 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/options.d.ts +3 -0
- package/dist/src/options.d.ts.map +1 -0
- package/dist/src/options.js +54 -0
- package/dist/src/options.js.map +1 -0
- package/dist/src/types.d.ts +273 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/ui/color.d.ts +12 -0
- package/dist/src/ui/color.d.ts.map +1 -0
- package/dist/src/ui/color.js +40 -0
- package/dist/src/ui/color.js.map +1 -0
- package/dist/src/ui/icons.d.ts +22 -0
- package/dist/src/ui/icons.d.ts.map +1 -0
- package/dist/src/ui/icons.js +26 -0
- package/dist/src/ui/icons.js.map +1 -0
- package/dist/src/ui/logger.d.ts +14 -0
- package/dist/src/ui/logger.d.ts.map +1 -0
- package/dist/src/ui/logger.js +35 -0
- package/dist/src/ui/logger.js.map +1 -0
- package/dist/src/ui/prompt.d.ts +4 -0
- package/dist/src/ui/prompt.d.ts.map +1 -0
- package/dist/src/ui/prompt.js +57 -0
- package/dist/src/ui/prompt.js.map +1 -0
- package/dist/src/ui/spinner.d.ts +26 -0
- package/dist/src/ui/spinner.d.ts.map +1 -0
- package/dist/src/ui/spinner.js +319 -0
- package/dist/src/ui/spinner.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 hytfjwr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# claude-worktree
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@hytfjwr/claude-worktree)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A CLI tool for parallel development using WezTerm + git worktree + Claude Code. It creates a git worktree and launches Claude Code. With the `-pane` option, it opens in a new WezTerm pane for parallel development.
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- [Node.js](https://nodejs.org/) (v22+)
|
|
11
|
+
- [Git](https://git-scm.com/)
|
|
12
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
|
13
|
+
- [WezTerm](https://wezfurlong.org/wezterm/) (optional, required only for `-pane`)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @hytfjwr/claude-worktree
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run directly with npx:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @hytfjwr/claude-worktree feature/auth 'Implement authentication feature'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Create a worktree and start Claude Code
|
|
31
|
+
claude-worktree feature/auth 'Implement authentication feature'
|
|
32
|
+
|
|
33
|
+
# Open in a new WezTerm pane for parallel development
|
|
34
|
+
claude-worktree feature/auth 'Implement authentication feature' -pane
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Create Command
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude-worktree <branch-name> <prompt>
|
|
43
|
+
claude-worktree <branch-name> -plan <file-path>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### List Command
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude-worktree list [options]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Clean Command
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
claude-worktree clean [options]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Help
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
claude-worktree -h
|
|
62
|
+
claude-worktree -help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Options
|
|
66
|
+
|
|
67
|
+
- `-p, -pane` - Open in a new WezTerm pane (default: run in current terminal)
|
|
68
|
+
- `-plan <file>` - Read prompt from a file (cannot be used with inline prompt)
|
|
69
|
+
- `-base <branch>` - Base branch for worktree (default: current branch)
|
|
70
|
+
- `-danger` - Skip workspace warning (uses --dangerously-skip-permissions)
|
|
71
|
+
- `-merge` - Auto-merge into base branch and cleanup after task completion
|
|
72
|
+
- `-draft` - Auto-create Draft PR after task completion (cannot be used with -merge)
|
|
73
|
+
- `-v, -verbose` - Show hook execution logs
|
|
74
|
+
- `-h, -help` - Show help
|
|
75
|
+
|
|
76
|
+
### List Options
|
|
77
|
+
|
|
78
|
+
- `-j, -json` - Output as JSON
|
|
79
|
+
- `-s, -status` - Show Claude session status (Running/Done)
|
|
80
|
+
- `-v, -verbose` - Show full paths and details
|
|
81
|
+
|
|
82
|
+
### Clean Options
|
|
83
|
+
|
|
84
|
+
- `-f, -force` - Skip confirmation prompt
|
|
85
|
+
- `-a, -all` - Show all worktrees for manual selection
|
|
86
|
+
- `-n, -dry-run` - Preview targets without deleting
|
|
87
|
+
- `-v, -verbose` - Show hook execution logs
|
|
88
|
+
|
|
89
|
+
### Examples
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Create a worktree and start Claude Code in current terminal
|
|
93
|
+
claude-worktree feature/auth 'Implement authentication feature'
|
|
94
|
+
|
|
95
|
+
# Open in a new WezTerm pane
|
|
96
|
+
claude-worktree feature/auth 'Implement authentication feature' -pane
|
|
97
|
+
|
|
98
|
+
# Short form
|
|
99
|
+
claude-worktree fix/bug-123 'Fix login bug' -p
|
|
100
|
+
|
|
101
|
+
# Read prompt from a plan file
|
|
102
|
+
claude-worktree feature/api -plan ./plan.md
|
|
103
|
+
|
|
104
|
+
# Create worktree from specific base branch
|
|
105
|
+
claude-worktree feature/auth 'Implement authentication feature' -base develop
|
|
106
|
+
|
|
107
|
+
# Skip workspace warning
|
|
108
|
+
claude-worktree feature/auth 'Implement authentication feature' -danger
|
|
109
|
+
|
|
110
|
+
# Auto-merge into base branch after task completion
|
|
111
|
+
claude-worktree feature/auth 'Implement authentication feature' -merge
|
|
112
|
+
|
|
113
|
+
# Auto-create Draft PR after task completion
|
|
114
|
+
claude-worktree feature/auth 'Implement authentication feature' -draft
|
|
115
|
+
|
|
116
|
+
# Draft PR with specific base branch
|
|
117
|
+
claude-worktree feature/auth 'Implement authentication feature' -draft -base main
|
|
118
|
+
|
|
119
|
+
# List worktrees with status
|
|
120
|
+
claude-worktree list
|
|
121
|
+
|
|
122
|
+
# Show Claude session status (Running/Done)
|
|
123
|
+
claude-worktree list -status
|
|
124
|
+
|
|
125
|
+
# List worktrees as JSON
|
|
126
|
+
claude-worktree list -json
|
|
127
|
+
|
|
128
|
+
# Clean up unnecessary worktrees
|
|
129
|
+
claude-worktree clean
|
|
130
|
+
|
|
131
|
+
# Preview worktrees to be deleted
|
|
132
|
+
claude-worktree clean -dry-run
|
|
133
|
+
|
|
134
|
+
# Select from all worktrees manually
|
|
135
|
+
claude-worktree clean -all
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### JSON Output Schema
|
|
139
|
+
|
|
140
|
+
When using `claude-worktree list -json`, the output follows this schema:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"worktrees": [
|
|
145
|
+
{
|
|
146
|
+
"path": "/absolute/path/to/worktree",
|
|
147
|
+
"branch": "feature/auth",
|
|
148
|
+
"isMain": false,
|
|
149
|
+
"isLocked": false,
|
|
150
|
+
"isDirty": false,
|
|
151
|
+
"status": "Active",
|
|
152
|
+
"commit": {
|
|
153
|
+
"hash": "abc1234",
|
|
154
|
+
"message": "Commit message",
|
|
155
|
+
"date": "2025-01-15T10:00:00.000Z"
|
|
156
|
+
},
|
|
157
|
+
"aheadBehind": { "ahead": 2, "behind": 0 },
|
|
158
|
+
"session": {
|
|
159
|
+
"status": "running",
|
|
160
|
+
"elapsedMs": 900000,
|
|
161
|
+
"mode": "pane",
|
|
162
|
+
"paneId": 3
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
| Field | Type | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `path` | `string` | Absolute path to the worktree directory |
|
|
172
|
+
| `branch` | `string \| null` | Branch name, or `null` for detached HEAD |
|
|
173
|
+
| `isMain` | `boolean` | Whether this is the main worktree |
|
|
174
|
+
| `isLocked` | `boolean` | Whether the worktree is locked |
|
|
175
|
+
| `isDirty` | `boolean` | Whether the worktree has uncommitted changes |
|
|
176
|
+
| `status` | `string` | One of: `"Main"`, `"Locked"`, `"Merged"`, `"Dirty"`, `"Active"` |
|
|
177
|
+
| `commit` | `object \| null` | Latest commit info (`hash`, `message`, `date`) |
|
|
178
|
+
| `aheadBehind` | `object \| null` | `{ ahead: number, behind: number }` relative to main branch |
|
|
179
|
+
| `session` | `object \| undefined` | Claude session info (only with `-status` flag) |
|
|
180
|
+
| `session.status` | `string` | `"running"` or `"done"` |
|
|
181
|
+
| `session.elapsedMs` | `number` | Milliseconds since session started |
|
|
182
|
+
| `session.mode` | `string` | `"pane"` or `"terminal"` |
|
|
183
|
+
| `session.paneId` | `number \| undefined` | WezTerm pane ID (pane mode only) |
|
|
184
|
+
|
|
185
|
+
## Hook Configuration
|
|
186
|
+
|
|
187
|
+
You can define project-specific hooks in `.claude-worktree.json` at the repository root:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"maxWorktrees": 5,
|
|
192
|
+
"hookTimeout": 600,
|
|
193
|
+
"postCreate": "cd {path} && docker-compose -p app-{slot} up -d",
|
|
194
|
+
"postCreateTimeout": 300,
|
|
195
|
+
"preClean": "cd {path} && docker-compose down",
|
|
196
|
+
"preCleanTimeout": 120,
|
|
197
|
+
"postClean": "docker volume rm app-{path}-data || true",
|
|
198
|
+
"postCleanTimeout": 60
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Worktree Limit
|
|
203
|
+
|
|
204
|
+
- `maxWorktrees` — Maximum number of concurrent worktrees (excludes main). If set, blocks creation when the limit is reached.
|
|
205
|
+
|
|
206
|
+
### Template Variables
|
|
207
|
+
|
|
208
|
+
- `{path}` — worktree path
|
|
209
|
+
- `{slot}` — auto-assigned slot number (1-9) based on port availability (8881-8889). Slot assignments are persisted to `~/.cache/claude-worktree/slots.json` so that `preClean`/`postClean` hooks can reference the same slot that was assigned during `postCreate`.
|
|
210
|
+
|
|
211
|
+
### Hooks
|
|
212
|
+
|
|
213
|
+
- **postCreate** — Runs after worktree creation (e.g., start Docker containers). If the hook fails, the worktree is automatically rolled back.
|
|
214
|
+
- **preClean** — Runs before worktree deletion (e.g., stop Docker containers). If the hook fails, deletion continues with a warning.
|
|
215
|
+
- **postClean** — Runs after worktree and branch deletion (e.g., Docker volume removal, DNS cleanup). If the hook fails, the operation continues with a warning.
|
|
216
|
+
|
|
217
|
+
### Timeout
|
|
218
|
+
|
|
219
|
+
- `hookTimeout` — Global default timeout in seconds (default: `600`)
|
|
220
|
+
- `postCreateTimeout` — Override timeout for postCreate hook
|
|
221
|
+
- `preCleanTimeout` — Override timeout for preClean hook
|
|
222
|
+
- `postCleanTimeout` — Override timeout for postClean hook
|
|
223
|
+
|
|
224
|
+
Priority: hook-specific value > `hookTimeout` > default (600s)
|
|
225
|
+
|
|
226
|
+
### Environment Variables
|
|
227
|
+
|
|
228
|
+
- `CLAUDE_WORKTREE_CACHE_DIR` — Override the slot cache directory (default: `~/.cache/claude-worktree`)
|
|
229
|
+
- `NO_COLOR` — Disable colored output ([no-color.org](https://no-color.org/)). Colors are also automatically disabled when stdout is not a TTY (e.g., piped output).
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-worktree.d.ts","sourceRoot":"","sources":["../../bin/claude-worktree.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs, run } from "../src/cli.js";
|
|
3
|
+
async function main() {
|
|
4
|
+
try {
|
|
5
|
+
const command = parseArgs(process.argv.slice(2));
|
|
6
|
+
await run(command);
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
main();
|
|
14
|
+
//# sourceMappingURL=claude-worktree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-worktree.js","sourceRoot":"","sources":["../../bin/claude-worktree.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAE/C,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CleanArgs, Command, CreateArgs, ListArgs } from "./types.ts";
|
|
2
|
+
export declare function showHelp(): void;
|
|
3
|
+
export declare function showCreateHelp(): void;
|
|
4
|
+
export declare function showListHelp(): void;
|
|
5
|
+
export declare function showCleanHelp(): void;
|
|
6
|
+
/**
|
|
7
|
+
* Validate a branch name against git's naming rules.
|
|
8
|
+
* Returns an error message if invalid, or null if valid.
|
|
9
|
+
* See: https://git-scm.com/docs/git-check-ref-format
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateBranchName(name: string): string | null;
|
|
12
|
+
export declare function parseCreateArgs(args: string[]): CreateArgs;
|
|
13
|
+
export declare function parseCleanArgs(args: string[]): CleanArgs;
|
|
14
|
+
export declare function parseListArgs(args: string[]): ListArgs;
|
|
15
|
+
export declare function parseArgs(args: string[]): Command;
|
|
16
|
+
export declare function run(command: Command): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3E,wBAAgB,QAAQ,IAAI,IAAI,CAoD/B;AAED,wBAAgB,cAAc,IAAI,IAAI,CA+BrC;AAED,wBAAgB,YAAY,IAAI,IAAI,CAoBnC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAqBpC;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4C9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CA6E1D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAS,CAmBxD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAiBtD;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAkDjD;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BzD"}
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { executeClean } from "./commands/clean.js";
|
|
2
|
+
import { runCreate } from "./commands/create.js";
|
|
3
|
+
import { executeList } from "./commands/list.js";
|
|
4
|
+
import { executeRunInPane, parseRunInPaneArgs } from "./commands/run-in-pane.js";
|
|
5
|
+
import { extractOptions } from "./options.js";
|
|
6
|
+
export function showHelp() {
|
|
7
|
+
console.log(`claude-worktree - CLI for parallel development with WezTerm + git worktree + Claude Code
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
claude-worktree <branch-name> <prompt>
|
|
11
|
+
claude-worktree <branch-name> -plan <file-path>
|
|
12
|
+
claude-worktree list [options]
|
|
13
|
+
claude-worktree clean [options]
|
|
14
|
+
|
|
15
|
+
Commands:
|
|
16
|
+
<branch-name> Create a new worktree with Claude Code
|
|
17
|
+
list List existing worktrees with status
|
|
18
|
+
clean Remove unnecessary worktrees
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
<branch-name> Branch name for the git worktree to create
|
|
22
|
+
<prompt> Prompt to pass to Claude Code
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
-p, -pane Open in a new WezTerm pane (requires WezTerm; default: run in current terminal)
|
|
26
|
+
-plan <file> Read prompt from a plan file (cannot be used with inline prompt)
|
|
27
|
+
-b, -base <branch> Specify base branch (default: current branch)
|
|
28
|
+
-d, -danger Skip workspace warning (uses --dangerously-skip-permissions)
|
|
29
|
+
-m, -merge Auto-merge into base branch and cleanup after task completion
|
|
30
|
+
-draft Auto-create Draft PR after task completion (cannot be used with -merge)
|
|
31
|
+
-v, -verbose Show hook execution logs
|
|
32
|
+
-h, -help Show this help
|
|
33
|
+
|
|
34
|
+
List options:
|
|
35
|
+
-j, -json Output as JSON
|
|
36
|
+
-s, -status Show Claude session status (Running/Done)
|
|
37
|
+
-v, -verbose Show full paths and details
|
|
38
|
+
|
|
39
|
+
Clean options:
|
|
40
|
+
-f, -force Skip confirmation prompt
|
|
41
|
+
-a, -all Show all worktrees for manual selection
|
|
42
|
+
-n, -dry-run Preview targets without deleting
|
|
43
|
+
-v, -verbose Show hook execution logs
|
|
44
|
+
|
|
45
|
+
Examples:
|
|
46
|
+
claude-worktree feature/auth 'Implement authentication feature'
|
|
47
|
+
claude-worktree feature/auth 'Implement authentication feature' -p
|
|
48
|
+
claude-worktree fix/bug-123 'Fix login bug' -pane
|
|
49
|
+
claude-worktree feature/api -plan ./plan.md
|
|
50
|
+
claude-worktree feature/auth 'Implement authentication feature' -danger
|
|
51
|
+
claude-worktree feature/auth 'Implement authentication feature' -merge
|
|
52
|
+
claude-worktree feature/auth 'Implement authentication feature' -draft
|
|
53
|
+
claude-worktree feature/auth 'Implement authentication feature' -draft -base main
|
|
54
|
+
claude-worktree list
|
|
55
|
+
claude-worktree list -json
|
|
56
|
+
claude-worktree clean
|
|
57
|
+
claude-worktree clean -dry-run`);
|
|
58
|
+
}
|
|
59
|
+
export function showCreateHelp() {
|
|
60
|
+
console.log(`claude-worktree <branch-name> - Create a new worktree and launch Claude Code
|
|
61
|
+
|
|
62
|
+
Creates a git worktree for a new branch, then starts a Claude Code session.
|
|
63
|
+
Optionally opens in a new WezTerm pane for parallel development.
|
|
64
|
+
|
|
65
|
+
Usage:
|
|
66
|
+
claude-worktree <branch-name> <prompt>
|
|
67
|
+
claude-worktree <branch-name> -plan <file-path>
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
<branch-name> Branch name for the git worktree to create
|
|
71
|
+
<prompt> Prompt to pass to Claude Code
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
-p, -pane Open in a new WezTerm pane (requires WezTerm; default: run in current terminal)
|
|
75
|
+
-plan <file> Read prompt from a plan file (cannot be used with inline prompt)
|
|
76
|
+
-b, -base <branch> Specify base branch (default: current branch)
|
|
77
|
+
-d, -danger Skip workspace warning (uses --dangerously-skip-permissions)
|
|
78
|
+
-m, -merge Auto-merge into base branch and cleanup after task completion
|
|
79
|
+
-draft Auto-create Draft PR after task completion (cannot be used with -merge)
|
|
80
|
+
-v, -verbose Show hook execution logs
|
|
81
|
+
-h, -help Show this help
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
claude-worktree feature/auth 'Implement authentication feature'
|
|
85
|
+
claude-worktree feature/auth 'Implement auth' -pane
|
|
86
|
+
claude-worktree feature/auth -plan ./plan.md
|
|
87
|
+
claude-worktree feature/auth 'Implement auth' -base develop
|
|
88
|
+
claude-worktree feature/auth 'Implement auth' -merge
|
|
89
|
+
claude-worktree feature/auth 'Implement auth' -draft -base main`);
|
|
90
|
+
}
|
|
91
|
+
export function showListHelp() {
|
|
92
|
+
console.log(`claude-worktree list - List existing worktrees with status
|
|
93
|
+
|
|
94
|
+
Displays all git worktrees managed by claude-worktree, including branch info,
|
|
95
|
+
commit details, and optionally Claude session status.
|
|
96
|
+
|
|
97
|
+
Usage:
|
|
98
|
+
claude-worktree list [options]
|
|
99
|
+
|
|
100
|
+
Options:
|
|
101
|
+
-j, -json Output as JSON (machine-readable format)
|
|
102
|
+
-s, -status Show Claude session status (Running/Done)
|
|
103
|
+
-v, -verbose Show full paths and details
|
|
104
|
+
-h, -help Show this help
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
claude-worktree list
|
|
108
|
+
claude-worktree list -status
|
|
109
|
+
claude-worktree list -json
|
|
110
|
+
claude-worktree list -verbose`);
|
|
111
|
+
}
|
|
112
|
+
export function showCleanHelp() {
|
|
113
|
+
console.log(`claude-worktree clean - Remove unnecessary worktrees
|
|
114
|
+
|
|
115
|
+
Identifies worktrees that can be safely removed (merged branches, deleted remote
|
|
116
|
+
branches) and prompts for confirmation before deleting.
|
|
117
|
+
|
|
118
|
+
Usage:
|
|
119
|
+
claude-worktree clean [options]
|
|
120
|
+
|
|
121
|
+
Options:
|
|
122
|
+
-f, -force Skip confirmation prompt
|
|
123
|
+
-a, -all Show all worktrees for manual selection
|
|
124
|
+
-n, -dry-run Preview targets without deleting
|
|
125
|
+
-v, -verbose Show hook execution logs
|
|
126
|
+
-h, -help Show this help
|
|
127
|
+
|
|
128
|
+
Examples:
|
|
129
|
+
claude-worktree clean
|
|
130
|
+
claude-worktree clean -dry-run
|
|
131
|
+
claude-worktree clean -force
|
|
132
|
+
claude-worktree clean -all`);
|
|
133
|
+
}
|
|
134
|
+
const CREATE_USAGE = "claude-worktree <branch-name> <prompt>\n" + " claude-worktree <branch-name> -plan <file-path>";
|
|
135
|
+
/**
|
|
136
|
+
* Validate a branch name against git's naming rules.
|
|
137
|
+
* Returns an error message if invalid, or null if valid.
|
|
138
|
+
* See: https://git-scm.com/docs/git-check-ref-format
|
|
139
|
+
*/
|
|
140
|
+
export function validateBranchName(name) {
|
|
141
|
+
if (name.startsWith("-")) {
|
|
142
|
+
return `Invalid branch name: "${name}". Branch names cannot start with "-".`;
|
|
143
|
+
}
|
|
144
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
145
|
+
return `Invalid branch name: "${name}". Branch names cannot start or end with ".".`;
|
|
146
|
+
}
|
|
147
|
+
// Check for path components starting with "." (e.g., feature/.hidden)
|
|
148
|
+
const components = name.split("/");
|
|
149
|
+
for (const component of components) {
|
|
150
|
+
if (component.startsWith(".")) {
|
|
151
|
+
return `Invalid branch name: "${name}". Path components cannot start with ".".`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (name.endsWith(".lock")) {
|
|
155
|
+
return `Invalid branch name: "${name}". Branch names cannot end with ".lock".`;
|
|
156
|
+
}
|
|
157
|
+
if (name.includes("..")) {
|
|
158
|
+
return `Invalid branch name: "${name}". Branch names cannot contain "..".`;
|
|
159
|
+
}
|
|
160
|
+
if (name.includes("//")) {
|
|
161
|
+
return `Invalid branch name: "${name}". Branch names cannot contain consecutive slashes.`;
|
|
162
|
+
}
|
|
163
|
+
if (name.endsWith("/")) {
|
|
164
|
+
return `Invalid branch name: "${name}". Branch names cannot end with "/".`;
|
|
165
|
+
}
|
|
166
|
+
if (name.includes("@{")) {
|
|
167
|
+
return `Invalid branch name: "${name}". Branch names cannot contain "@{".`;
|
|
168
|
+
}
|
|
169
|
+
if (name === "@") {
|
|
170
|
+
return `Invalid branch name: "${name}". Branch name cannot be "@".`;
|
|
171
|
+
}
|
|
172
|
+
if (name.includes("\\")) {
|
|
173
|
+
return `Invalid branch name: "${name}". Branch names cannot contain backslashes.`;
|
|
174
|
+
}
|
|
175
|
+
// Check for spaces, control characters (~, ^, :, ?, *, [)
|
|
176
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching git-forbidden control chars
|
|
177
|
+
const invalidCharMatch = name.match(/[\s~^:?*[\x00-\x1f\x7f]/);
|
|
178
|
+
if (invalidCharMatch) {
|
|
179
|
+
const char = invalidCharMatch[0];
|
|
180
|
+
const displayChar = char.trim() === "" ? "whitespace" : `"${char}"`;
|
|
181
|
+
return `Invalid branch name: "${name}". Branch names cannot contain ${displayChar}.`;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
export function parseCreateArgs(args) {
|
|
186
|
+
if (args.length < 1) {
|
|
187
|
+
throw new Error(`Usage:\n ${CREATE_USAGE}\n\n` +
|
|
188
|
+
"Example:\n" +
|
|
189
|
+
" claude-worktree feature/auth 'Implement authentication feature'\n" +
|
|
190
|
+
" claude-worktree feature/auth -plan ./plan.md");
|
|
191
|
+
}
|
|
192
|
+
const branchName = args[0];
|
|
193
|
+
// Validate branch name
|
|
194
|
+
const branchError = validateBranchName(branchName);
|
|
195
|
+
if (branchError) {
|
|
196
|
+
throw new Error(branchError);
|
|
197
|
+
}
|
|
198
|
+
const { booleans, strings, remaining } = extractOptions(args.slice(1), {
|
|
199
|
+
options: {
|
|
200
|
+
pane: { type: "boolean", flag: "-pane", alias: "-p" },
|
|
201
|
+
danger: { type: "boolean", flag: "-danger", alias: "-d" },
|
|
202
|
+
merge: { type: "boolean", flag: "-merge", alias: "-m" },
|
|
203
|
+
draft: { type: "boolean", flag: "-draft" },
|
|
204
|
+
verbose: { type: "boolean", flag: "-verbose", alias: "-v" },
|
|
205
|
+
baseBranch: { type: "string", flag: "-base", alias: "-b", errorMessage: "-base requires a branch name argument" },
|
|
206
|
+
planFile: { type: "string", flag: "-plan", errorMessage: "-plan requires a file path argument" },
|
|
207
|
+
},
|
|
208
|
+
unknownHandling: "error",
|
|
209
|
+
ignoredFlags: ["-h", "-help"],
|
|
210
|
+
unknownErrorPrefix: "Unknown option",
|
|
211
|
+
});
|
|
212
|
+
const { pane, danger, merge, draft, verbose } = booleans;
|
|
213
|
+
const { baseBranch, planFile } = strings;
|
|
214
|
+
// Check for unknown options
|
|
215
|
+
const unknownFlag = remaining.find((arg) => arg.startsWith("-"));
|
|
216
|
+
if (unknownFlag) {
|
|
217
|
+
throw new Error(`Unknown option: ${unknownFlag}`);
|
|
218
|
+
}
|
|
219
|
+
// Mutual exclusivity check for -merge and -draft
|
|
220
|
+
if (merge && draft) {
|
|
221
|
+
throw new Error("Cannot use both -merge and -draft options.\n\n" +
|
|
222
|
+
" -merge Auto-merge into base branch and cleanup after task completion\n" +
|
|
223
|
+
" -draft Auto-create a Draft PR after task completion\n\n" +
|
|
224
|
+
"Example:\n" +
|
|
225
|
+
" claude-worktree feature/auth 'Implement auth' -merge\n" +
|
|
226
|
+
" claude-worktree feature/auth 'Implement auth' -draft -base main");
|
|
227
|
+
}
|
|
228
|
+
const inlinePrompt = remaining.join(" ").trim();
|
|
229
|
+
// Mutual exclusivity check: cannot specify both -plan and inline prompt
|
|
230
|
+
if (planFile && inlinePrompt) {
|
|
231
|
+
throw new Error("Cannot use both -plan and inline prompt. Please use one or the other.");
|
|
232
|
+
}
|
|
233
|
+
// Require either inline prompt or -plan
|
|
234
|
+
if (!inlinePrompt && !planFile) {
|
|
235
|
+
throw new Error(`A prompt or -plan option is required.\n\nUsage:\n ${CREATE_USAGE}`);
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
branchName,
|
|
239
|
+
prompt: inlinePrompt,
|
|
240
|
+
planFile,
|
|
241
|
+
danger,
|
|
242
|
+
merge,
|
|
243
|
+
draft,
|
|
244
|
+
baseBranch,
|
|
245
|
+
pane,
|
|
246
|
+
verbose,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
export function parseCleanArgs(args) {
|
|
250
|
+
const { booleans } = extractOptions(args, {
|
|
251
|
+
options: {
|
|
252
|
+
force: { type: "boolean", flag: "-force", alias: "-f" },
|
|
253
|
+
all: { type: "boolean", flag: "-all", alias: "-a" },
|
|
254
|
+
dryRun: { type: "boolean", flag: "-dry-run", alias: "-n" },
|
|
255
|
+
verbose: { type: "boolean", flag: "-verbose", alias: "-v" },
|
|
256
|
+
},
|
|
257
|
+
unknownHandling: "error",
|
|
258
|
+
ignoredFlags: ["-h", "-help"],
|
|
259
|
+
unknownErrorPrefix: "Unknown option for clean command",
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
force: booleans.force,
|
|
263
|
+
all: booleans.all,
|
|
264
|
+
dryRun: booleans.dryRun,
|
|
265
|
+
verbose: booleans.verbose,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export function parseListArgs(args) {
|
|
269
|
+
const { booleans } = extractOptions(args, {
|
|
270
|
+
options: {
|
|
271
|
+
json: { type: "boolean", flag: "-json", alias: "-j" },
|
|
272
|
+
status: { type: "boolean", flag: "-status", alias: "-s" },
|
|
273
|
+
verbose: { type: "boolean", flag: "-verbose", alias: "-v" },
|
|
274
|
+
},
|
|
275
|
+
unknownHandling: "error",
|
|
276
|
+
ignoredFlags: ["-h", "-help"],
|
|
277
|
+
unknownErrorPrefix: "Unknown option for list command",
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
json: booleans.json,
|
|
281
|
+
verbose: booleans.verbose,
|
|
282
|
+
status: booleans.status,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
export function parseArgs(args) {
|
|
286
|
+
// Internal sub-command: must be checked before help flags
|
|
287
|
+
if (args[0] === "_run-in-pane") {
|
|
288
|
+
if (args.length !== 2) {
|
|
289
|
+
throw new Error("_run-in-pane requires exactly one payload file path argument");
|
|
290
|
+
}
|
|
291
|
+
return { type: "_run-in-pane", payloadPath: args[1] };
|
|
292
|
+
}
|
|
293
|
+
// No args → global help
|
|
294
|
+
if (args.length === 0) {
|
|
295
|
+
return { type: "help" };
|
|
296
|
+
}
|
|
297
|
+
// Per-command help: list -h, clean -h
|
|
298
|
+
if (args[0] === "list") {
|
|
299
|
+
const subArgs = args.slice(1);
|
|
300
|
+
if (subArgs.includes("-h") || subArgs.includes("-help")) {
|
|
301
|
+
return { type: "help", commandHelp: "list" };
|
|
302
|
+
}
|
|
303
|
+
return { type: "list", args: parseListArgs(subArgs) };
|
|
304
|
+
}
|
|
305
|
+
if (args[0] === "clean") {
|
|
306
|
+
const subArgs = args.slice(1);
|
|
307
|
+
if (subArgs.includes("-h") || subArgs.includes("-help")) {
|
|
308
|
+
return { type: "help", commandHelp: "clean" };
|
|
309
|
+
}
|
|
310
|
+
return { type: "clean", args: parseCleanArgs(subArgs) };
|
|
311
|
+
}
|
|
312
|
+
// Global help flags (only when not a sub-command)
|
|
313
|
+
if (args.includes("-h") || args.includes("-help")) {
|
|
314
|
+
// If the first arg looks like a branch name (create command), show create help
|
|
315
|
+
if (args.length >= 1 && !args[0].startsWith("-") && args[0] !== "list" && args[0] !== "clean") {
|
|
316
|
+
return { type: "help", commandHelp: "create" };
|
|
317
|
+
}
|
|
318
|
+
return { type: "help" };
|
|
319
|
+
}
|
|
320
|
+
// Single non-flag argument that isn't a known command → unknown command error
|
|
321
|
+
if (args.length === 1 && !args[0].startsWith("-")) {
|
|
322
|
+
throw new Error(`Unknown command: ${args[0]}\n\n` +
|
|
323
|
+
`Available commands: list, clean\n\n` +
|
|
324
|
+
`To create a worktree:\n ${CREATE_USAGE}`);
|
|
325
|
+
}
|
|
326
|
+
return { type: "create", args: parseCreateArgs(args) };
|
|
327
|
+
}
|
|
328
|
+
export async function run(command) {
|
|
329
|
+
switch (command.type) {
|
|
330
|
+
case "help":
|
|
331
|
+
if (command.commandHelp === "create") {
|
|
332
|
+
showCreateHelp();
|
|
333
|
+
}
|
|
334
|
+
else if (command.commandHelp === "list") {
|
|
335
|
+
showListHelp();
|
|
336
|
+
}
|
|
337
|
+
else if (command.commandHelp === "clean") {
|
|
338
|
+
showCleanHelp();
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
showHelp();
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
case "create":
|
|
345
|
+
await runCreate(command.args);
|
|
346
|
+
break;
|
|
347
|
+
case "list":
|
|
348
|
+
await executeList(command.args);
|
|
349
|
+
break;
|
|
350
|
+
case "clean":
|
|
351
|
+
await executeClean(command.args);
|
|
352
|
+
break;
|
|
353
|
+
case "_run-in-pane": {
|
|
354
|
+
const runInPaneArgs = await parseRunInPaneArgs(command.payloadPath);
|
|
355
|
+
await executeRunInPane(runInPaneArgs);
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
//# sourceMappingURL=cli.js.map
|