git_worktree_manager/cli.rs
1/// CLI definitions using clap derive.
2///
3/// Mirrors the Typer-based CLI in src/git_worktree_manager/cli.py.
4pub mod completions;
5
6use clap::{Parser, Subcommand, ValueHint};
7use std::path::PathBuf;
8
9use crate::operations::config_ops::ConfigKey;
10
11/// Git worktree manager CLI.
12#[derive(Parser, Debug)]
13#[command(
14 name = "gw",
15 version,
16 about = "git worktree manager — AI coding assistant integration",
17 long_about = None,
18 arg_required_else_help = true,
19)]
20pub struct Cli {
21 /// Generate shell completions for the given shell
22 #[arg(long, value_name = "SHELL", value_parser = clap::builder::PossibleValuesParser::new(["bash", "zsh", "fish", "powershell", "elvish"]))]
23 pub generate_completion: Option<String>,
24
25 #[command(subcommand)]
26 pub command: Option<Commands>,
27}
28
29#[derive(Subcommand, Debug)]
30pub enum Commands {
31 /// Create new worktree for feature branch.
32 ///
33 /// Trailing positional args are forwarded verbatim to the underlying AI
34 /// tool (claude/codex/gemini). Use `--` to disambiguate when forwarded
35 /// args start with a hyphen, e.g. `gw new feat-x -- --model opus`.
36 #[command(group(
37 clap::ArgGroup::new("prompt_source")
38 .args(["prompt", "prompt_file"])
39 .multiple(false)
40 .required(false)
41 ))]
42 New {
43 /// Branch name for the new worktree
44 name: String,
45
46 /// Custom worktree path (default: ../<repo>-<branch>)
47 #[arg(long, value_hint = ValueHint::DirPath)]
48 path: Option<String>,
49
50 /// Base branch to create from (default: from config)
51 #[arg(long = "base")]
52 base: Option<String>,
53
54 /// Terminal launch method for THIS invocation. Overrides config
55 /// and CW_LAUNCH_METHOD. Accepts canonical names (e.g.,
56 /// `wezterm-tab`) or aliases (e.g., `w-t`, `w-t-b`). Supports
57 /// `method:session-name` for tmux/zellij. Use `noop`, `none`, or
58 /// `skip` to skip the AI tool launch entirely.
59 #[arg(short = 'T', long = "term", value_name = "METHOD")]
60 term: Option<String>,
61
62 /// Initial prompt to pass to the AI tool. Use `-` to read the
63 /// prompt from standard input (e.g., `--prompt -` with a heredoc
64 /// or pipe).
65 #[arg(long)]
66 prompt: Option<String>,
67
68 /// Read the initial prompt from a file (recommended for multi-line prompts)
69 #[arg(long = "prompt-file", value_hint = ValueHint::FilePath)]
70 prompt_file: Option<PathBuf>,
71
72 /// Disable auto-forwarding of `<TOOL>_*` environment variables
73 /// (e.g. `CLAUDE_*` when ai-tool is claude). Without this flag, gw
74 /// snapshots the parent shell's matching env at invocation time
75 /// and re-injects it inside the spawned terminal so launchers like
76 /// wezterm/iterm/tmux/zellij behave like a normal child process.
77 #[arg(long = "no-env-forward")]
78 no_env_forward: bool,
79
80 /// Extra arguments forwarded verbatim to the AI tool (claude/codex/
81 /// gemini). Mutually exclusive with `--prompt`/`--prompt-file`
82 /// because both ultimately set the AI tool's prompt — use one or
83 /// the other.
84 #[arg(
85 trailing_var_arg = true,
86 allow_hyphen_values = true,
87 value_name = "AI_TOOL_ARGS"
88 )]
89 forward_args: Vec<String>,
90 },
91
92 /// Resume AI work in a worktree.
93 ///
94 /// Trailing positional args are forwarded verbatim to the AI tool. The
95 /// tool's own resume flag (`--continue`/`--resume`) is always injected
96 /// alongside, so `gw resume foo -- --model opus` resumes with opus.
97 Resume {
98 /// Branch name, worktree name, or path to resume (default: current worktree)
99 branch: Option<String>,
100
101 /// Terminal launch method for THIS invocation. Overrides config
102 /// and CW_LAUNCH_METHOD. Accepts canonical names or aliases.
103 /// Supports `method:session-name` for tmux/zellij. Use `noop`,
104 /// `none`, or `skip` to skip the AI tool launch.
105 #[arg(short = 'T', long = "term", value_name = "METHOD")]
106 term: Option<String>,
107
108 /// Disable auto-forwarding of `<TOOL>_*` environment variables.
109 #[arg(long = "no-env-forward")]
110 no_env_forward: bool,
111
112 /// Extra arguments forwarded verbatim to the AI tool (claude/codex/
113 /// gemini). The tool's resume flag is still injected automatically.
114 #[arg(
115 trailing_var_arg = true,
116 allow_hyphen_values = true,
117 value_name = "AI_TOOL_ARGS"
118 )]
119 forward_args: Vec<String>,
120 },
121
122 /// Launch AI tool in an existing worktree (default: current).
123 ///
124 /// Trailing positional args are forwarded verbatim to the AI tool.
125 #[command(group(
126 clap::ArgGroup::new("prompt_source")
127 .args(["prompt", "prompt_file"])
128 .multiple(false)
129 .required(false)
130 ))]
131 Spawn {
132 /// Worktree target — exact worktree name, branch name, or path.
133 /// Default: current worktree.
134 target: Option<String>,
135
136 /// Terminal launch method for THIS invocation. Overrides config
137 /// and CW_LAUNCH_METHOD. Accepts canonical names or aliases.
138 /// Supports `method:session-name` for tmux/zellij. Use `noop`,
139 /// `none`, or `skip` to skip the AI tool launch.
140 #[arg(short = 'T', long = "term", value_name = "METHOD")]
141 term: Option<String>,
142
143 /// Initial prompt to pass to the AI tool. Use `-` to read from stdin.
144 #[arg(long)]
145 prompt: Option<String>,
146
147 /// Read the initial prompt from a file.
148 #[arg(long = "prompt-file", value_hint = ValueHint::FilePath)]
149 prompt_file: Option<PathBuf>,
150
151 /// Disable auto-forwarding of `<TOOL>_*` environment variables.
152 #[arg(long = "no-env-forward")]
153 no_env_forward: bool,
154
155 /// Extra arguments forwarded verbatim to the AI tool.
156 #[arg(
157 trailing_var_arg = true,
158 allow_hyphen_values = true,
159 value_name = "AI_TOOL_ARGS"
160 )]
161 forward_args: Vec<String>,
162 },
163
164 /// Remove one or more worktrees.
165 ///
166 /// With no arguments: removes the current worktree (must be inside one).
167 /// With one or more positional targets: removes each of them; flags apply
168 /// to every target.
169 /// With `-i`: opens a multi-select UI.
170 ///
171 /// Exits 0 on full success, 1 if the user cancelled at the confirmation
172 /// prompt or in the interactive UI, 2 if any target could not be removed
173 /// (not found, busy, or an error).
174 Rm {
175 /// Branch names or paths of worktrees to remove.
176 /// If empty and --interactive is not set, removes the current worktree.
177 #[arg(conflicts_with = "interactive")]
178 targets: Vec<String>,
179
180 /// Interactive multi-select UI (mutually exclusive with positional targets)
181 #[arg(short, long, conflicts_with = "targets")]
182 interactive: bool,
183
184 /// Show what would be removed without removing
185 #[arg(long)]
186 dry_run: bool,
187
188 /// Keep the branch (only remove worktree)
189 #[arg(short = 'k', long)]
190 keep_branch: bool,
191
192 /// Also delete the remote branch
193 #[arg(short = 'r', long)]
194 delete_remote: bool,
195
196 /// Force remove: also bypasses the busy-detection gate (skips the
197 /// "worktree is in use" check and removes anyway)
198 #[arg(short, long, conflicts_with = "no_force")]
199 force: bool,
200
201 /// Don't use --force flag
202 #[arg(long)]
203 no_force: bool,
204 },
205
206 /// List all worktrees (rich, human-readable)
207 List,
208
209 /// Print all worktrees as TSV (for scripts)
210 ///
211 /// Columns: worktree_id, branch, status, age, repo_root, path
212 Ls,
213
214 /// Run diagnostics
215 Doctor {
216 /// Hook-friendly mode: emit a single-line summary and exit 0.
217 #[arg(long)]
218 session_start: bool,
219 /// Suppress informational chatter; keep only the summary.
220 #[arg(long)]
221 quiet: bool,
222 },
223
224 /// Check for updates / upgrade
225 Upgrade {
226 /// Skip the confirmation prompt; required for non-TTY environments.
227 #[arg(short, long)]
228 yes: bool,
229 },
230
231 /// Install Claude Code skill for worktree task delegation
232 #[command(name = "setup-claude")]
233 SetupClaude,
234
235 /// View or edit gw configuration.
236 ///
237 /// Configuration lives in two scopes:
238 /// - `global`: `~/.config/git-worktree-manager/config.json`
239 /// - `repo`: `<repo-root>/.cwconfig.json` (overrides global)
240 ///
241 /// `set` writes to `global` by default; pass `--repo` for a repo
242 /// override. `edit` opens an interactive TUI that lets you toggle
243 /// between scopes with Tab.
244 Config {
245 #[command(subcommand)]
246 action: ConfigAction,
247 },
248
249 /// Interactive shell integration setup
250 ShellSetup,
251
252 /// Run cmd in one specific worktree.
253 Exec {
254 /// Worktree name, branch name, or path.
255 target: String,
256
257 /// Command and args.
258 #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
259 cmd: Vec<String>,
260 },
261
262 /// Run cmd in every worktree in scope.
263 Run {
264 /// Glob filter on branch name or worktree directory basename
265 /// (e.g. 'feat-*'). Matches if either side does.
266 #[arg(long)]
267 only: Option<String>,
268 /// Skip the main worktree.
269 #[arg(long = "no-main")]
270 no_main: bool,
271 /// Parallel worktrees.
272 #[arg(short = 'j', long, default_value = "1")]
273 jobs: usize,
274 /// Continue past per-worktree failures.
275 #[arg(long)]
276 continue_on_error: bool,
277 /// Command and args.
278 #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
279 cmd: Vec<String>,
280 },
281
282 /// Hook helper: read a Claude Code hook payload from stdin (or a file)
283 /// and decide whether to allow or block the inbound tool use. Exits 0
284 /// to allow; non-zero with stderr message to block.
285 Guard {
286 /// Path to read the hook payload from, or "-" for stdin.
287 #[arg(long, value_name = "PATH")]
288 tool_input: String,
289 },
290
291 /// [Internal] Get worktree path for a branch
292 #[command(name = "_path", hide = true)]
293 Path {
294 /// Branch name
295 branch: Option<String>,
296
297 /// List branch names (for tab completion)
298 #[arg(long)]
299 list_branches: bool,
300
301 /// Interactive worktree selection
302 #[arg(short, long)]
303 interactive: bool,
304 },
305
306 /// Generate shell function for gw-cd
307 #[command(name = "_shell-function", hide = true)]
308 ShellFunction {
309 /// Shell type: bash, zsh, fish, or powershell
310 shell: String,
311 },
312
313 /// Refresh update cache (background process)
314 #[command(name = "_update-cache", hide = true)]
315 UpdateCache,
316
317 /// [Internal] Print completion targets for the current repo (one per line)
318 #[command(name = "_complete-targets", hide = true)]
319 CompleteTargets,
320
321 /// [Internal] Execute an AI tool spawn spec file
322 #[command(name = "_spawn-ai", hide = true)]
323 SpawnAi {
324 /// Path to the JSON spawn spec. If omitted, resolves the most recent
325 /// spec for the current worktree from `<git-dir>/gw-spawn-last.json`.
326 #[arg(value_hint = ValueHint::FilePath)]
327 spec: Option<PathBuf>,
328 },
329}
330
331/// Subcommands under `gw config`.
332#[derive(Subcommand, Debug)]
333pub enum ConfigAction {
334 /// List every known key with its current value and scope.
335 List,
336
337 /// Print the resolved value of a single key (repo > global > default).
338 Get {
339 /// Key to look up (e.g. `ai-tool.command`).
340 key: ConfigKey,
341 },
342
343 /// Set a key. Writes to global config by default; `--repo` writes to
344 /// `<repo-root>/.cwconfig.json` as an override.
345 Set {
346 /// Key to set.
347 key: ConfigKey,
348 /// New value. Strings are taken as-is; booleans accept
349 /// true/false/1/0/yes/no/on/off; `ai-tool.args` accepts either
350 /// whitespace-separated tokens or a JSON array literal.
351 value: String,
352 /// Write to repo scope instead of global.
353 #[arg(long)]
354 repo: bool,
355 },
356
357 /// Open an interactive TUI that lets you browse/edit both scopes.
358 Edit,
359}