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