shipit 1.4.8

Shipit is an open source command line interface for managing merge requests, changelogs, tags, and releases using a plan and apply interface. Built with coding agent integration in mind.
Documentation
use clap::builder::styling::{AnsiColor, Effects, Styles};
use clap::{Args, Parser, Subcommand};

fn styles() -> Styles {
    Styles::styled()
        .header(AnsiColor::Green.on_default() | Effects::BOLD)
        .usage(AnsiColor::Green.on_default() | Effects::BOLD)
        .literal(AnsiColor::Cyan.on_default() | Effects::BOLD)
        .placeholder(AnsiColor::Cyan.on_default())
        .error(AnsiColor::Red.on_default() | Effects::BOLD)
        .valid(AnsiColor::Cyan.on_default() | Effects::BOLD)
        .invalid(AnsiColor::Yellow.on_default() | Effects::BOLD)
}

#[derive(Args, Debug, Default)]
pub struct B2bPlanArgs {
    #[arg(help = "Source branch to open the merge/pull request from")]
    pub source: String,
    #[arg(help = "Target branch to merge into")]
    pub target: String,
    #[arg(
        short = 'c',
        long,
        help = "Categorize commits by conventional commit type and structure the description accordingly"
    )]
    pub conventional_commits: bool,
    #[arg(
        long,
        help = "Path to the git repository [default: present working directory]"
    )]
    pub dir: Option<String>,
    #[arg(long, default_value = "origin", help = "Name of the git remote to use")]
    pub remote: String,
    #[arg(
        long,
        help = "Title to use for the merge/pull request [default: 'Release Candidate vX.Y.Z']"
    )]
    pub title: Option<String>,
    #[arg(long, help = "Description to use for the merge/pull request")]
    pub description: Option<String>,
    #[arg(long, help = "Only include merge commits in the discovered commits")]
    pub only_merges: bool,
    #[arg(
        long,
        help = "Do not append the 'generated by Shipit' line to the created description"
    )]
    pub no_sign: bool,
    #[arg(
        short = 'y',
        long,
        help = "Automatically approve all shipit command prompts"
    )]
    pub yes: bool,
    #[arg(
        long,
        help = "Emit the plan as YAML to stdout (useful for piping to yq)"
    )]
    pub yaml: bool,
    #[arg(
        long,
        help = "Continue even if the working directory has uncommitted changes"
    )]
    pub allow_dirty: bool,
}

#[derive(Args, Debug)]
pub struct B2bApplyArgs {
    #[arg(help = "Name of the plan file in .shipit/plans/ to apply")]
    pub plan: String,
    #[arg(
        long,
        help = "Path to the git repository [default: present working directory]"
    )]
    pub dir: Option<String>,
    #[arg(long, default_value = "origin", help = "Name of the git remote to use")]
    pub remote: String,
    #[arg(
        long,
        help = "Continue even if the working directory has uncommitted changes"
    )]
    pub allow_dirty: bool,
    #[arg(
        short = 'y',
        long,
        help = "Automatically approve all shipit command prompts"
    )]
    pub yes: bool,
}

#[derive(Subcommand, Debug)]
pub enum B2bSubcommand {
    /// Generate a plan file for a merge/pull request without creating it
    Plan(B2bPlanArgs),
    /// Open a merge/pull request using a plan file from .shipit/plans/
    Apply(B2bApplyArgs),
}

#[derive(Args, Debug)]
pub struct B2bArgs {
    #[command(subcommand)]
    pub command: B2bSubcommand,
}

#[derive(Args, Debug, Default)]
pub struct B2tPlanArgs {
    #[arg(help = "Branch to create the tag on")]
    pub branch: String,
    #[arg(help = "Name of the tag to create")]
    pub tag: Option<String>,
    #[arg(
        short = 'c',
        long,
        help = "Categorize commits by conventional commit type and structure the notes accordingly"
    )]
    pub conventional_commits: bool,
    #[arg(
        long,
        help = "Path to the git repository [default: present working directory]"
    )]
    pub dir: Option<String>,
    #[arg(long, default_value = "origin", help = "Name of the git remote to use")]
    pub remote: String,
    #[arg(long, help = "Description to use for the tag notes")]
    pub description: Option<String>,
    #[arg(long, help = "Only include merge commits in the discovered commits")]
    pub only_merges: bool,
    #[arg(
        long,
        help = "The most recent tag to compare against [default: most recent tag on the branch]"
    )]
    pub latest_tag: Option<String>,
    #[arg(
        long,
        help = "Do not append the 'generated by Shipit' line to the tag notes"
    )]
    pub no_sign: bool,
    #[arg(
        short = 'y',
        long,
        help = "Automatically approve all shipit command prompts"
    )]
    pub yes: bool,
    #[arg(
        long,
        help = "Emit the plan as YAML to stdout (useful for piping to yq)"
    )]
    pub yaml: bool,
    #[arg(
        long,
        help = "Continue even if the working directory has uncommitted changes"
    )]
    pub allow_dirty: bool,
}

#[derive(Args, Debug)]
pub struct B2tApplyArgs {
    #[arg(help = "Name of the plan file in .shipit/plans/ to apply")]
    pub plan: String,
    #[arg(
        long,
        help = "Path to the git repository [default: present working directory]"
    )]
    pub dir: Option<String>,
    #[arg(long, default_value = "origin", help = "Name of the git remote to use")]
    pub remote: String,
    #[arg(
        long,
        help = "Continue even if the working directory has uncommitted changes"
    )]
    pub allow_dirty: bool,
    #[arg(
        short = 'y',
        long,
        help = "Automatically approve all shipit command prompts"
    )]
    pub yes: bool,
}

#[derive(Subcommand, Debug)]
pub enum B2tSubcommand {
    /// Generate a plan file for a tag without creating it
    Plan(B2tPlanArgs),
    /// Create and push a tag using a plan file from .shipit/plans/
    Apply(B2tApplyArgs),
}

#[derive(Args, Debug)]
pub struct B2tArgs {
    #[command(subcommand)]
    pub command: B2tSubcommand,
}

#[derive(Parser)]
#[command(styles = styles())]
#[command(version)]
#[command(subcommand_required = false)]
#[command(arg_required_else_help = true)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Option<Commands>,
    #[arg(
        short = 'v',
        long,
        action = clap::ArgAction::Count,
        global = true,
        help = "Increase log verbosity (-v for info, -vv for debug)"
    )]
    pub verbose: u8,
    #[arg(long, hide = true)]
    pub markdown_help: bool,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Open or apply a merge/pull request
    #[command(visible_alias = "t2b")]
    B2b(B2bArgs),
    /// Plan or apply an annotated tag on a branch
    B2t(B2tArgs),
    /// Write the default config to the platform config directory (overwrites existing config)
    Init(InitArgs),
}

#[derive(Args, Debug, Default)]
pub struct InitArgs {
    #[arg(
        long,
        help = "Directory to write the config file to [default: present working directory]"
    )]
    pub dir: Option<String>,
    #[arg(long, help = "Platform personal access token")]
    pub platform_token: Option<String>,
    #[arg(long, help = "Platform domain")]
    pub platform_domain: Option<String>,
    #[arg(long, help = "Git remote to infer the platform domain from [default: origin]")]
    pub remote: Option<String>,
    #[arg(long, help = "Only create or update the CLAUDE.md agent guide; skip all other setup")]
    pub guide_only: bool,
}