Skip to main content

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