use std::path::PathBuf;
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
#[derive(Parser, Debug)]
#[command(name = "kabu")]
#[command(about = "Enhance git worktree and jj workspace with automated setup")]
#[command(version = VERSION_STRING)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu add ../new-worktree
kabu list
kabu remove ../new-worktree
kabu config validate
WHAT THIS COMMAND DOES:
- Detects git/jj automatically
- Runs selected subcommand
- Uses .kabu/config.yaml or .kabu/config.toml (YAML wins)
DISCOVERY MAP:
- Worktree/workspace lifecycle: kabu add, kabu list, kabu remove
- Shell navigation and integration: kabu path, kabu cd, kabu init, kabu completions
- Configuration and schema: kabu config, kabu config schema
- Hook trust management: kabu trust, kabu untrust
SAFETY NOTES:
- hooks do not run until trusted (kabu trust)
- remove warns before risky deletion unless --force is used
SEE ALSO:
kabu <command> --help
kabu man")]
pub(crate) struct Cli {
#[command(subcommand)]
pub command: Command,
}
const VERSION_STRING: &str = env!("KABU_VERSION_LABEL");
#[derive(Subcommand, Debug)]
pub(crate) enum Command {
Add(AddArgs),
#[command(visible_alias = "rm")]
Remove(RemoveArgs),
#[command(visible_alias = "ls")]
List(ListArgs),
Path(PathArgs),
Cd,
Config(ConfigArgs),
Trust(TrustArgs),
Untrust(UntrustArgs),
Completions {
shell: Shell,
},
Init(InitArgs),
Man,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu config new
kabu config validate
kabu config get auto_cd.after_remove
kabu config schema > schema.json
WHAT THIS COMMAND DOES:
- Creates, validates, and inspects .kabu config files
- Supports YAML and TOML (YAML has higher priority)
- Validates strict keys (unknown keys fail)
CONFIG KEYS (TOP LEVEL):
on_conflict: abort | skip | overwrite | backup
auto_cd:
after_add: true | false
after_remove: main | select
worktree:
path_template: string
branch_template: string
mkdir:
- path: string
description: string (optional)
link:
- source: string
target: string (optional; default = source)
skip_tracked: bool (optional; default = false)
on_conflict: abort | skip | overwrite | backup (optional)
description: string (optional)
copy:
- source: string
target: string (optional; default = source)
on_conflict: abort | skip | overwrite | backup (optional)
description: string (optional)
hooks:
hook_shell: string (optional)
pre_add/post_add/pre_remove/post_remove:
- command: string (required)
description: string (optional)
on_setup_failure:
remove_worktree: bool (optional; default = false)
delete_branch: bool (optional; default = false)
ui:
add_default_mode: existing | new
show_key_hints: bool
colors: accent/border/disabled/error/footer/header/label/muted/preview/
search/selection_bg/selection_fg/text/title/warning
TEMPLATE VARIABLES:
worktree.path_template:
{{branch}}, {{repository}}
worktree.branch_template:
{{commitish}}, {{repository}}, {{strftime(...)}} (e.g. {{strftime(%Y%m%d)}})
hooks.command:
{{worktree_path}}, {{worktree_name}}, {{branch}}, {{repo_root}}, {{vcs_type}}
jj-specific: {{change_id}}, {{commit_id}}
aliases: {{workspace_path}}, {{workspace_name}}, {{bookmark}}
HOOK ENVIRONMENT VARIABLES:
KABU_WORKTREE_PATH, KABU_WORKTREE_NAME, KABU_BRANCH, KABU_REPO_ROOT,
KABU_VCS_TYPE, KABU_CHANGE_ID, KABU_COMMIT_ID
CONFLICT RESOLUTION PRIORITY:
CLI --on-conflict > per-entry on_conflict > top-level on_conflict > prompt/default behavior
EXECUTION ORDER:
add: pre_add -> VCS add -> mkdir -> link -> copy -> post_add
remove: pre_remove -> VCS remove -> post_remove
SAFETY NOTES:
- Hooks require trust before execution (kabu trust)
- Template placeholders use double braces: {{branch}}
- Paths in mkdir/link/copy must be relative; unknown keys fail validation
SEE ALSO:
kabu config <subcommand> --help
kabu trust --help
kabu man")]
pub(crate) struct ConfigArgs {
#[command(subcommand)]
pub command: Option<ConfigCommand>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum ConfigFormatArg {
#[default]
Yaml,
Toml,
}
impl std::str::FromStr for ConfigFormatArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"yaml" | "yml" => Ok(Self::Yaml),
"toml" => Ok(Self::Toml),
_ => Err(format!("Invalid format: {s}. Valid values: yaml, toml")),
}
}
}
#[derive(Subcommand, Debug)]
pub(crate) enum ConfigCommand {
Validate,
Schema,
New {
#[arg(short, long, verbatim_doc_comment)]
global: bool,
#[arg(short, long, default_value = "yaml")]
format: ConfigFormatArg,
#[arg(short, long)]
path: Option<std::path::PathBuf>,
#[arg(short = 'O', long = "override")]
override_existing: bool,
#[arg(long)]
with_gitignore: bool,
#[arg(long, conflicts_with = "with_gitignore")]
without_gitignore: bool,
},
Get {
key: String,
},
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu add ../new-worktree
kabu add -b feature-x ../feature-x
kabu add --interactive
kabu add --dry-run ../preview
WHAT THIS COMMAND DOES:
- Creates a git worktree / jj workspace
- Runs setup in fixed order: mkdir -> link -> copy
- Can skip setup with --no-setup
VCS MAPPING:
- git: git worktree add
- jj: jj workspace add
- colocated (git+jj): jj workspace add is used
ARGUMENT RULES:
- [PATH] is required unless --interactive, or path_template + branch (-b/-B/COMMITISH)
- [COMMITISH] is optional revision/branch input
- -b/-B creates or resets branch/bookmark depending on VCS
CONFLICT MODES:
abort stop on first conflict
skip keep existing target and continue
overwrite replace existing target
backup rename existing target with .bak then continue
ENVIRONMENT VARIABLES:
KABU_ON_CONFLICT default for --on-conflict
KABUHOOK_SHELL shell override for hook execution
SAFETY NOTES:
- --dry-run previews actions without changes
- --no-setup runs only VCS add
- hooks run only after trust (kabu trust)
- operation order is fixed and independent of YAML order
SEE ALSO:
kabu remove --help
kabu config --help")]
pub(crate) struct AddArgs {
pub path: Option<PathBuf>,
pub commitish: Option<String>,
#[arg(short, long, help_heading = "kabu Options")]
pub interactive: bool,
#[arg(
long,
value_name = "MODE",
help_heading = "kabu Options",
env = "KABU_ON_CONFLICT"
)]
pub on_conflict: Option<OnConflictArg>,
#[arg(long, help_heading = "kabu Options")]
pub dry_run: bool,
#[arg(long, help_heading = "kabu Options")]
pub no_setup: bool,
#[cfg(windows)]
#[arg(
long,
value_name = "SHELL",
help_heading = "kabu Options",
value_parser = [
"pwsh",
"powershell",
"bash",
"git-bash",
"gitbash",
"cmd",
"cmd.exe",
"wsl"
]
)]
pub hook_shell: Option<String>,
#[arg(
short = 'b',
value_name = "name",
help_heading = "git worktree Options"
)]
pub new_branch: Option<String>,
#[arg(
short = 'B',
value_name = "name",
help_heading = "git worktree Options"
)]
pub new_branch_force: Option<String>,
#[arg(short, long, help_heading = "git worktree Options")]
pub force: bool,
#[arg(short, long, help_heading = "git worktree Options")]
pub detach: bool,
#[arg(long, help_heading = "git worktree Options")]
pub no_checkout: bool,
#[arg(long, help_heading = "git worktree Options")]
pub lock: bool,
#[arg(long, help_heading = "git worktree Options")]
pub track: bool,
#[arg(long, help_heading = "git worktree Options")]
pub no_track: bool,
#[arg(long, help_heading = "git worktree Options")]
pub guess_remote: bool,
#[arg(long, help_heading = "git worktree Options")]
pub no_guess_remote: bool,
#[arg(short, long, help_heading = "Shared Options")]
pub quiet: bool,
#[arg(
long,
value_name = "WHEN",
default_value = "auto",
conflicts_with = "no_color",
help_heading = "Shared Options"
)]
pub color: clap::ColorChoice,
#[arg(long, help_heading = "Shared Options")]
pub no_color: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu remove ../old-worktree
kabu remove --current
kabu remove --interactive
kabu remove --dry-run ../preview
WHAT THIS COMMAND DOES:
- Removes git worktree / forgets jj workspace
- Runs pre_remove and post_remove hooks if trusted
- Supports interactive and current-target modes
VCS MAPPING:
- git: git worktree remove
- jj: jj workspace forget
- colocated (git+jj): jj workspace forget is used
TARGET SELECTION RULES:
- [PATHS]... accepts multiple targets
- --interactive selects targets from UI
- --current targets the worktree/workspace containing cwd
SAFETY NOTES:
- warns on dirty or unpushed state by default
- --force bypasses checks and confirmation
- main worktree/workspace cannot be removed
SEE ALSO:
kabu list --help
kabu trust --help")]
pub(crate) struct RemoveArgs {
pub paths: Vec<PathBuf>,
#[arg(short, long, help_heading = "kabu Options")]
pub interactive: bool,
#[arg(short = 'c', long, help_heading = "kabu Options")]
pub current: bool,
#[arg(long, help_heading = "kabu Options")]
pub dry_run: bool,
#[cfg(windows)]
#[arg(
long,
value_name = "SHELL",
help_heading = "kabu Options",
value_parser = [
"pwsh",
"powershell",
"bash",
"git-bash",
"gitbash",
"cmd",
"cmd.exe",
"wsl"
]
)]
pub hook_shell: Option<String>,
#[arg(short, long, help_heading = "git worktree Options")]
pub force: bool,
#[arg(short, long, help_heading = "Shared Options")]
pub quiet: bool,
#[arg(
long,
value_name = "WHEN",
default_value = "auto",
conflicts_with = "no_color",
help_heading = "Shared Options"
)]
pub color: clap::ColorChoice,
#[arg(long, help_heading = "Shared Options")]
pub no_color: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu list
kabu ls --header
kabu list --path-only
WHAT THIS COMMAND DOES:
- Lists worktrees/workspaces from git or jj
- Shows branch/bookmark and commit/change information
- Marks dirty entries with '*'
OUTPUT COLUMNS:
PATH worktree/workspace path
BRANCH git branch or jj bookmark/workspace label
COMMIT short commit hash (git) or change id (jj)
STATUS '*' when modified/deleted/untracked files exist
MODES:
--path-only print paths only (script-friendly)
--header include header row
SEE ALSO:
kabu path --help
kabu remove --help")]
pub(crate) struct ListArgs {
#[arg(short, long)]
pub path_only: bool,
#[arg(long)]
pub header: bool,
#[arg(
long,
value_name = "WHEN",
default_value = "auto",
conflicts_with = "no_color",
help_heading = "Shared Options"
)]
pub color: clap::ColorChoice,
#[arg(long, help_heading = "Shared Options")]
pub no_color: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu trust
kabu trust --show
kabu trust --check
kabu trust --yes
WHAT THIS COMMAND DOES:
- Reviews and trusts hooks for a repository
- Stores trust with a config snapshot hash
- Requires re-trust when hooks/config change
HOOK EXECUTION MODEL:
pre_add before add (repo root)
post_add after add + setup (new worktree/workspace)
pre_remove before remove (target worktree/workspace)
post_remove after remove (repo root)
WINDOWS SHELL RESOLUTION:
pwsh -> powershell -> git-bash -> cmd
override: --hook-shell (add/remove) or KABUHOOK_SHELL
TRUST STORAGE:
Default location: ~/.local/share/kabu/trusted/
Repository identity + config snapshot are hashed for verification
SAFETY NOTES:
- trusting allows all configured hooks to run
- use --show to review commands before trusting
- --check exits 0 when trusted, 1 when trust is required
SEE ALSO:
kabu untrust --help
kabu config --help")]
pub(crate) struct TrustArgs {
pub path: Option<PathBuf>,
#[arg(short = 'y', long)]
pub yes: bool,
#[arg(long)]
pub show: bool,
#[arg(long, conflicts_with = "show")]
pub check: bool,
#[arg(
long,
value_name = "WHEN",
default_value = "auto",
conflicts_with = "no_color"
)]
pub color: clap::ColorChoice,
#[arg(long)]
pub no_color: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
eval \"$(kabu init bash)\"
eval \"$(kabu init zsh)\"
kabu init fish | source
kabu init bash --print-full-init
WHAT THIS COMMAND DOES:
- Generates shell integration script
- Enables completion, kabu cd, and trust warnings
SUPPORTED SHELLS:
bash, zsh, fish, powershell, elvish
INSTALL PATTERNS:
bash: eval \"$(kabu init bash)\"
zsh: eval \"$(kabu init zsh)\"
fish: kabu init fish | source
powershell: Invoke-Expression (& kabu init powershell | Out-String)
elvish: eval (kabu init elvish | slurp)
TROUBLESHOOTING:
1) reload shell session
2) confirm binary is in PATH
3) confirm init snippet is in shell rc/profile
SAFETY NOTES:
- kabu cd requires shell integration
- without integration, use: cd \"$(kabu path)\"
SEE ALSO:
kabu path --help
kabu completions --help")]
pub(crate) struct InitArgs {
pub shell: Shell,
#[arg(long)]
pub print_full_init: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu path
kabu path --main
cd \"$(kabu path)\"
WHAT THIS COMMAND DOES:
- Prints selected worktree/workspace path
- --main returns the main worktree/workspace path
SELECTION BEHAVIOR:
default interactive selection UI
--main no interaction, prints main path directly
USAGE NOTES:
- Intended for command substitution in shells
- Works without shell integration unlike kabu cd
SEE ALSO:
kabu cd
kabu list --help")]
pub(crate) struct PathArgs {
#[arg(long)]
pub main: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "\
QUICK EXAMPLES:
kabu untrust
kabu untrust --list
kabu untrust /path/to/repo
WHAT THIS COMMAND DOES:
- Revokes trust for one repository or lists trusted repositories
MODES:
default revoke trust for current (or given) repository
--list print all trusted repository entries
SEE ALSO:
kabu trust --help")]
pub(crate) struct UntrustArgs {
pub path: Option<PathBuf>,
#[arg(long)]
pub list: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OnConflictArg {
Abort,
Skip,
Overwrite,
Backup,
}
impl std::str::FromStr for OnConflictArg {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"abort" => Ok(Self::Abort),
"skip" => Ok(Self::Skip),
"overwrite" => Ok(Self::Overwrite),
"backup" => Ok(Self::Backup),
_ => Err(format!(
"Invalid on-conflict mode: {s}. Valid values: abort, skip, overwrite, backup"
)),
}
}
}
pub(crate) fn parse() -> Cli {
Cli::parse()
}
pub(crate) fn build() -> clap::Command {
Cli::command()
}