Skip to main content

git_worktree_manager/
cli.rs

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