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    #[command(group(
31        clap::ArgGroup::new("prompt_source")
32            .args(["prompt", "prompt_file", "prompt_stdin"])
33            .multiple(false)
34            .required(false)
35    ))]
36    New {
37        /// Branch name for the new worktree
38        name: String,
39
40        /// Custom worktree path (default: ../<repo>-<branch>)
41        #[arg(short, long, value_hint = ValueHint::DirPath)]
42        path: Option<String>,
43
44        /// Base branch to create from (default: from config)
45        #[arg(short = 'b', long = "base")]
46        base: Option<String>,
47
48        /// Skip AI tool launch
49        #[arg(long = "no-term")]
50        no_term: bool,
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. Mutually exclusive
56        /// with `--no-term`.
57        #[arg(
58            short = 'T',
59            long = "term",
60            value_name = "METHOD",
61            conflicts_with = "no_term"
62        )]
63        term: Option<String>,
64
65        /// Initial prompt to pass to the AI tool (starts interactive session with task)
66        #[arg(long)]
67        prompt: Option<String>,
68
69        /// Read the initial prompt from a file (recommended for multi-line prompts)
70        #[arg(long = "prompt-file", value_hint = ValueHint::FilePath)]
71        prompt_file: Option<PathBuf>,
72
73        /// Read the initial prompt from standard input
74        #[arg(long = "prompt-stdin")]
75        prompt_stdin: bool,
76    },
77
78    /// Resume AI work in a worktree
79    Resume {
80        /// Branch name, worktree name, or path to resume (default: current worktree)
81        branch: Option<String>,
82
83        /// Terminal launch method for THIS invocation. Overrides config
84        /// and CW_LAUNCH_METHOD. Accepts canonical names or aliases.
85        /// Supports `method:session-name` for tmux/zellij.
86        #[arg(short = 'T', long = "term", value_name = "METHOD")]
87        term: Option<String>,
88    },
89
90    /// Launch AI tool in an existing worktree (default: current).
91    #[command(group(
92        clap::ArgGroup::new("prompt_source")
93            .args(["prompt", "prompt_file", "prompt_stdin"])
94            .multiple(false)
95            .required(false)
96    ))]
97    Spawn {
98        /// Worktree target — exact worktree name, branch name, or path.
99        /// Default: current worktree.
100        target: Option<String>,
101
102        /// Terminal launch method for THIS invocation. Overrides config
103        /// and CW_LAUNCH_METHOD. Accepts canonical names or aliases.
104        /// Supports `method:session-name` for tmux/zellij.
105        #[arg(short = 'T', long = "term", value_name = "METHOD")]
106        term: Option<String>,
107
108        /// Initial prompt to pass to the AI tool.
109        #[arg(long)]
110        prompt: Option<String>,
111
112        /// Read the initial prompt from a file.
113        #[arg(long = "prompt-file", value_hint = ValueHint::FilePath)]
114        prompt_file: Option<PathBuf>,
115
116        /// Read the initial prompt from standard input.
117        #[arg(long = "prompt-stdin")]
118        prompt_stdin: bool,
119    },
120
121    /// Remove one or more worktrees.
122    ///
123    /// With no arguments: removes the current worktree (must be inside one).
124    /// With one or more positional targets: removes each of them; flags apply
125    /// to every target.
126    /// With `-i`: opens a multi-select UI.
127    ///
128    /// Exits 0 on full success, 1 if the user cancelled at the confirmation
129    /// prompt or in the interactive UI, 2 if any target could not be removed
130    /// (not found, busy, or an error).
131    Rm {
132        /// Branch names or paths of worktrees to remove.
133        /// If empty and --interactive is not set, removes the current worktree.
134        #[arg(conflicts_with = "interactive")]
135        targets: Vec<String>,
136
137        /// Interactive multi-select UI (mutually exclusive with positional targets)
138        #[arg(short, long, conflicts_with = "targets")]
139        interactive: bool,
140
141        /// Show what would be removed without removing
142        #[arg(long)]
143        dry_run: bool,
144
145        /// Keep the branch (only remove worktree)
146        #[arg(short = 'k', long)]
147        keep_branch: bool,
148
149        /// Also delete the remote branch
150        #[arg(short = 'r', long)]
151        delete_remote: bool,
152
153        /// Force remove: also bypasses the busy-detection gate (skips the
154        /// "worktree is in use" check and removes anyway)
155        #[arg(short, long, conflicts_with = "no_force")]
156        force: bool,
157
158        /// Don't use --force flag
159        #[arg(long)]
160        no_force: bool,
161    },
162
163    /// List all worktrees (rich, human-readable)
164    List,
165
166    /// Print all worktrees as TSV (for scripts)
167    ///
168    /// Columns: worktree_id, branch, status, age, repo_root, path
169    Ls,
170
171    /// Run diagnostics
172    Doctor {
173        /// Hook-friendly mode: emit a single-line summary and exit 0.
174        #[arg(long)]
175        session_start: bool,
176        /// Suppress informational chatter; keep only the summary.
177        #[arg(long)]
178        quiet: bool,
179    },
180
181    /// Check for updates / upgrade
182    Upgrade {
183        /// Skip the confirmation prompt; required for non-TTY environments.
184        #[arg(short, long)]
185        yes: bool,
186    },
187
188    /// Install Claude Code skill for worktree task delegation
189    #[command(name = "setup-claude")]
190    SetupClaude,
191
192    /// Interactive shell integration setup
193    ShellSetup,
194
195    /// Run cmd in one specific worktree.
196    Exec {
197        /// Worktree name, branch name, or path.
198        target: String,
199
200        /// Command and args.
201        #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
202        cmd: Vec<String>,
203    },
204
205    /// Run cmd in every worktree in scope.
206    Run {
207        /// Glob filter on branch name or worktree directory basename
208        /// (e.g. 'feat-*'). Matches if either side does.
209        #[arg(long)]
210        only: Option<String>,
211        /// Skip the main worktree.
212        #[arg(long = "no-main")]
213        no_main: bool,
214        /// Parallel worktrees.
215        #[arg(short = 'j', long, default_value = "1")]
216        jobs: usize,
217        /// Continue past per-worktree failures.
218        #[arg(long)]
219        continue_on_error: bool,
220        /// Command and args.
221        #[arg(trailing_var_arg = true, allow_hyphen_values = true, required = true)]
222        cmd: Vec<String>,
223    },
224
225    /// Hook helper: read a Claude Code hook payload from stdin (or a file)
226    /// and decide whether to allow or block the inbound tool use. Exits 0
227    /// to allow; non-zero with stderr message to block.
228    Guard {
229        /// Path to read the hook payload from, or "-" for stdin.
230        #[arg(long, value_name = "PATH")]
231        tool_input: String,
232    },
233
234    /// [Internal] Get worktree path for a branch
235    #[command(name = "_path", hide = true)]
236    Path {
237        /// Branch name
238        branch: Option<String>,
239
240        /// List branch names (for tab completion)
241        #[arg(long)]
242        list_branches: bool,
243
244        /// Interactive worktree selection
245        #[arg(short, long)]
246        interactive: bool,
247    },
248
249    /// Generate shell function for gw-cd / cw-cd
250    #[command(name = "_shell-function", hide = true)]
251    ShellFunction {
252        /// Shell type: bash, zsh, fish, or powershell
253        shell: String,
254    },
255
256    /// Refresh update cache (background process)
257    #[command(name = "_update-cache", hide = true)]
258    UpdateCache,
259
260    /// [Internal] Print completion targets for the current repo (one per line)
261    #[command(name = "_complete-targets", hide = true)]
262    CompleteTargets,
263
264    /// [Internal] Execute an AI tool spawn spec file
265    #[command(name = "_spawn-ai", hide = true)]
266    SpawnAi {
267        /// Path to the JSON spawn spec. If omitted, resolves the most recent
268        /// spec for the current worktree from `<git-dir>/gw-spawn-last.json`.
269        #[arg(value_hint = ValueHint::FilePath)]
270        spec: Option<PathBuf>,
271    },
272}