@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.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +233 -0
  3. package/dist/bin/claude-worktree.d.ts +3 -0
  4. package/dist/bin/claude-worktree.d.ts.map +1 -0
  5. package/dist/bin/claude-worktree.js +14 -0
  6. package/dist/bin/claude-worktree.js.map +1 -0
  7. package/dist/src/cli.d.ts +17 -0
  8. package/dist/src/cli.d.ts.map +1 -0
  9. package/dist/src/cli.js +360 -0
  10. package/dist/src/cli.js.map +1 -0
  11. package/dist/src/commands/clean.d.ts +3 -0
  12. package/dist/src/commands/clean.d.ts.map +1 -0
  13. package/dist/src/commands/clean.js +190 -0
  14. package/dist/src/commands/clean.js.map +1 -0
  15. package/dist/src/commands/create.d.ts +14 -0
  16. package/dist/src/commands/create.d.ts.map +1 -0
  17. package/dist/src/commands/create.js +458 -0
  18. package/dist/src/commands/create.js.map +1 -0
  19. package/dist/src/commands/hooks.d.ts +8 -0
  20. package/dist/src/commands/hooks.d.ts.map +1 -0
  21. package/dist/src/commands/hooks.js +28 -0
  22. package/dist/src/commands/hooks.js.map +1 -0
  23. package/dist/src/commands/list.d.ts +17 -0
  24. package/dist/src/commands/list.d.ts.map +1 -0
  25. package/dist/src/commands/list.js +228 -0
  26. package/dist/src/commands/list.js.map +1 -0
  27. package/dist/src/commands/rollback.d.ts +3 -0
  28. package/dist/src/commands/rollback.d.ts.map +1 -0
  29. package/dist/src/commands/rollback.js +99 -0
  30. package/dist/src/commands/rollback.js.map +1 -0
  31. package/dist/src/commands/run-in-pane.d.ts +4 -0
  32. package/dist/src/commands/run-in-pane.d.ts.map +1 -0
  33. package/dist/src/commands/run-in-pane.js +120 -0
  34. package/dist/src/commands/run-in-pane.js.map +1 -0
  35. package/dist/src/core/cache.d.ts +10 -0
  36. package/dist/src/core/cache.d.ts.map +1 -0
  37. package/dist/src/core/cache.js +66 -0
  38. package/dist/src/core/cache.js.map +1 -0
  39. package/dist/src/core/config.d.ts +11 -0
  40. package/dist/src/core/config.d.ts.map +1 -0
  41. package/dist/src/core/config.js +181 -0
  42. package/dist/src/core/config.js.map +1 -0
  43. package/dist/src/core/errors.d.ts +9 -0
  44. package/dist/src/core/errors.d.ts.map +1 -0
  45. package/dist/src/core/errors.js +20 -0
  46. package/dist/src/core/errors.js.map +1 -0
  47. package/dist/src/core/exec.d.ts +73 -0
  48. package/dist/src/core/exec.d.ts.map +1 -0
  49. package/dist/src/core/exec.js +138 -0
  50. package/dist/src/core/exec.js.map +1 -0
  51. package/dist/src/core/git.d.ts +28 -0
  52. package/dist/src/core/git.d.ts.map +1 -0
  53. package/dist/src/core/git.js +291 -0
  54. package/dist/src/core/git.js.map +1 -0
  55. package/dist/src/core/session.d.ts +9 -0
  56. package/dist/src/core/session.d.ts.map +1 -0
  57. package/dist/src/core/session.js +87 -0
  58. package/dist/src/core/session.js.map +1 -0
  59. package/dist/src/core/slot.d.ts +7 -0
  60. package/dist/src/core/slot.d.ts.map +1 -0
  61. package/dist/src/core/slot.js +73 -0
  62. package/dist/src/core/slot.js.map +1 -0
  63. package/dist/src/external/claude.d.ts +3 -0
  64. package/dist/src/external/claude.d.ts.map +1 -0
  65. package/dist/src/external/claude.js +71 -0
  66. package/dist/src/external/claude.js.map +1 -0
  67. package/dist/src/external/wezterm.d.ts +11 -0
  68. package/dist/src/external/wezterm.d.ts.map +1 -0
  69. package/dist/src/external/wezterm.js +87 -0
  70. package/dist/src/external/wezterm.js.map +1 -0
  71. package/dist/src/index.d.ts +24 -0
  72. package/dist/src/index.d.ts.map +1 -0
  73. package/dist/src/index.js +44 -0
  74. package/dist/src/index.js.map +1 -0
  75. package/dist/src/options.d.ts +3 -0
  76. package/dist/src/options.d.ts.map +1 -0
  77. package/dist/src/options.js +54 -0
  78. package/dist/src/options.js.map +1 -0
  79. package/dist/src/types.d.ts +273 -0
  80. package/dist/src/types.d.ts.map +1 -0
  81. package/dist/src/types.js +5 -0
  82. package/dist/src/types.js.map +1 -0
  83. package/dist/src/ui/color.d.ts +12 -0
  84. package/dist/src/ui/color.d.ts.map +1 -0
  85. package/dist/src/ui/color.js +40 -0
  86. package/dist/src/ui/color.js.map +1 -0
  87. package/dist/src/ui/icons.d.ts +22 -0
  88. package/dist/src/ui/icons.d.ts.map +1 -0
  89. package/dist/src/ui/icons.js +26 -0
  90. package/dist/src/ui/icons.js.map +1 -0
  91. package/dist/src/ui/logger.d.ts +14 -0
  92. package/dist/src/ui/logger.d.ts.map +1 -0
  93. package/dist/src/ui/logger.js +35 -0
  94. package/dist/src/ui/logger.js.map +1 -0
  95. package/dist/src/ui/prompt.d.ts +4 -0
  96. package/dist/src/ui/prompt.d.ts.map +1 -0
  97. package/dist/src/ui/prompt.js +57 -0
  98. package/dist/src/ui/prompt.js.map +1 -0
  99. package/dist/src/ui/spinner.d.ts +26 -0
  100. package/dist/src/ui/spinner.d.ts.map +1 -0
  101. package/dist/src/ui/spinner.js +319 -0
  102. package/dist/src/ui/spinner.js.map +1 -0
  103. 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
+ [![npm version](https://img.shields.io/npm/v/@hytfjwr/claude-worktree.svg)](https://www.npmjs.com/package/@hytfjwr/claude-worktree)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=claude-worktree.d.ts.map
@@ -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"}
@@ -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