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
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}