xbp 10.14.2

XBP is a zero-config build pack that can also interact with proxies, kafka, sockets, synthetic monitors.
Documentation
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(
    name = "xbp",
    version,
    about = "XBP CLI",
    disable_help_subcommand = false
)]
pub struct Cli {
    #[arg(long, global = true)]
    pub debug: bool,
    #[arg(short = 'l', help = "List pm2 processes")]
    pub list: bool,
    #[arg(short = 'p', long = "port", help = "Filter by port number")]
    pub port: Option<u16>,
    #[arg(long, help = "Open logs directory")]
    pub logs: bool,

    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    Ports(PortsCmd),
    #[command(about = "Initialize an XBP project in the current directory")]
    Init,
    Setup,
    Redeploy {
        #[arg(
            help = "Service name to redeploy (optional, uses legacy redeploy.sh if not provided)"
        )]
        service_name: Option<String>,
    },
    RedeployV2(RedeployV2Cmd),
    Config(ConfigCmd),
    Install {
        package: String,
    },
    Logs(LogsCmd),
    List,
    Curl(CurlCmd),
    Services,
    Service {
        #[arg(help = "Command to run: build, install, start, dev, or --help")]
        command: Option<String>,
        #[arg(help = "Service name")]
        service_name: Option<String>,
    },
    #[command(about = "Manage NGINX site configs and upstream mappings")]
    Nginx(NginxCmd),
    Diag(DiagCmd),
    Monitor(MonitorCmd),
    #[command(about = "Capture a PM2 snapshot for later restore")]
    Snapshot,
    #[command(about = "Restore PM2 state from dump or latest snapshot")]
    Resurrect,
    #[command(about = "Stop a PM2 process by name or stop all")]
    Stop {
        #[arg(help = "PM2 process name or 'all' (default: all)")]
        target: Option<String>,
    },
    #[command(about = "Flush PM2 logs globally or for a specific process")]
    Flush {
        #[arg(help = "Optional PM2 process name")]
        target: Option<String>,
    },
    #[command(about = "Run login flow against configured XBP API")]
    Login,
    #[command(about = "Inspect, reconcile, or bump project versions")]
    Version(VersionCmd),
    #[command(about = "Show PM2 environment by name or numeric id")]
    Env {
        #[arg(help = "PM2 process name or id")]
        target: String,
    },
    Tail(TailCmd),
    Start {
        #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
        args: Vec<String>,
    },
    Generate(GenerateCmd),
    Secrets(SecretsCmd),
    #[cfg(feature = "kubernetes")]
    #[command(about = "Experimental Kubernetes cluster manager (feature-gated)")]
    Kubernetes(KubernetesCmd),
    #[cfg(feature = "monitoring")]
    Monitoring(MonitoringCmd),
}

#[derive(Args, Debug)]
pub struct PortsCmd {
    #[arg(short = 'p', long = "port")]
    pub port: Option<u16>,
    #[arg(long = "kill")]
    pub kill: bool,
    #[arg(short = 'n', long = "nginx")]
    pub nginx: bool,
    #[arg(
        long = "full",
        help = "Show reconciled active, NGINX, and XBP project ports"
    )]
    pub full: bool,
}

#[derive(Args, Debug)]
pub struct ConfigCmd {
    #[arg(
        long,
        help = "Show the current project config instead of opening global XBP paths"
    )]
    pub project: bool,
    #[arg(long, help = "Print global XBP paths without opening them")]
    pub no_open: bool,
}

#[derive(Args, Debug)]
pub struct CurlCmd {
    #[arg(help = "URL or domain to fetch, e.g. example.com or https://example.com/api")]
    pub url: Option<String>,
    #[arg(long, help = "Disable the default 15 second timeout")]
    pub no_timeout: bool,
}

#[derive(Args, Debug)]
pub struct VersionCmd {
    #[arg(
        help = "Show versions, bump with major/minor/patch, or set an explicit version like 1.2.3"
    )]
    pub target: Option<String>,
    #[arg(long, help = "Show normalized git tags from `git tag --list`")]
    pub git: bool,
}

#[derive(Args, Debug)]
pub struct RedeployV2Cmd {
    #[arg(short = 'p', long = "password")]
    pub password: Option<String>,
    #[arg(short = 'u', long = "username")]
    pub username: Option<String>,
    #[arg(short = 'h', long = "host")]
    pub host: Option<String>,
    #[arg(short = 'd', long = "project-dir")]
    pub project_dir: Option<String>,
}

#[derive(Args, Debug)]
pub struct LogsCmd {
    #[arg()]
    pub project: Option<String>,
    #[arg(long = "ssh-host", help = "SSH host to stream logs from")]
    pub ssh_host: Option<String>,
    #[arg(long = "ssh-username", help = "SSH username for remote host")]
    pub ssh_username: Option<String>,
    #[arg(long = "ssh-password", help = "SSH password for remote host")]
    pub ssh_password: Option<String>,
}

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

#[derive(Subcommand, Debug)]
pub enum NginxSubCommand {
    #[command(about = "Create and enable an NGINX site for a domain/port")]
    Setup {
        #[arg(short, long, help = "Domain name")]
        domain: String,
        #[arg(short, long, help = "Port to proxy to")]
        port: u16,
    },
    #[command(about = "List discovered NGINX sites with listen/upstream ports")]
    List,
    #[command(about = "Show full NGINX config for one domain or all domains")]
    Show {
        #[arg(help = "Optional domain name to inspect")]
        domain: Option<String>,
    },
    #[command(about = "Open an NGINX site config in your configured editor")]
    Edit {
        #[arg(help = "Domain name to edit")]
        domain: String,
    },
    #[command(about = "Update upstream port for an existing NGINX site")]
    Update {
        #[arg(short, long, help = "Domain name to update")]
        domain: String,
        #[arg(short, long, help = "New port to proxy to")]
        port: u16,
    },
}

#[derive(Args, Debug)]
pub struct DiagCmd {
    #[arg(long, help = "Check Nginx configuration")]
    pub nginx: bool,
    #[arg(long, help = "Check specific ports (comma-separated)")]
    pub ports: Option<String>,
    #[arg(long, help = "Skip internet speed test")]
    pub no_speed_test: bool,
}

#[derive(Args, Debug)]
pub struct MonitorCmd {
    #[command(subcommand)]
    pub command: Option<MonitorSubCommand>,
}

#[derive(Subcommand, Debug)]
pub enum MonitorSubCommand {
    Check,
    Start,
}

#[cfg(feature = "monitoring")]
#[derive(Args, Debug)]
pub struct MonitoringCmd {
    #[command(subcommand)]
    pub command: MonitoringSubCommand,
}

#[cfg(feature = "monitoring")]
#[derive(Subcommand, Debug)]
pub enum MonitoringSubCommand {
    Serve {
        #[arg(
            short,
            long,
            default_value = "prodzilla.yml",
            help = "Monitoring config file"
        )]
        file: String,
    },
    RunOnce {
        #[arg(
            short,
            long,
            default_value = "prodzilla.yml",
            help = "Monitoring config file"
        )]
        file: String,
        #[arg(long, help = "Run probes only")]
        probes_only: bool,
        #[arg(long, help = "Run stories only")]
        stories_only: bool,
    },
    List {
        #[arg(
            short,
            long,
            default_value = "prodzilla.yml",
            help = "Monitoring config file"
        )]
        file: String,
    },
}
#[derive(Args, Debug)]
pub struct TailCmd {
    #[arg(long, help = "Tail Kafka topic instead of log files")]
    pub kafka: bool,
    #[arg(long, help = "Ship logs to Kafka")]
    pub ship: bool,
}

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

#[derive(Subcommand, Debug)]
pub enum GenerateSubCommand {
    Systemd(GenerateSystemdCmd),
}

#[derive(Args, Debug)]
pub struct SecretsCmd {
    #[arg(long, help = "GitHub repository override (owner/repo)")]
    pub repo: Option<String>,
    #[command(subcommand)]
    pub command: Option<SecretsSubCommand>,
}

#[derive(Subcommand, Debug)]
pub enum SecretsSubCommand {
    /// List local env vars from the preferred env file
    List,
    /// Push local env vars to the secrets provider (GitHub)
    Push {
        #[arg(long, help = "Path to env file to push (defaults to .env.local/.env)")]
        file: Option<String>,
        #[arg(long, help = "Force overwrite existing repository variables")]
        force: bool,
    },
    /// Pull secrets from the provider into .env.local
    Pull {
        #[arg(long, help = "Output file path (default: .env.local)")]
        output: Option<String>,
    },
    /// Generate .env.default from source code inspection
    GenerateDefault {
        #[arg(long, help = "Output file path (default: .env.default)")]
        output: Option<String>,
    },
    /// Verify that all required env vars are available locally
    Verify,
    /// Show secrets command usage
    Help,
}

#[derive(Args, Debug)]
pub struct GenerateSystemdCmd {
    #[arg(
        long,
        default_value = "/etc/systemd/system",
        help = "Directory where the systemd units are written"
    )]
    pub output_dir: PathBuf,
    #[arg(long, help = "Only generate the unit for this service name")]
    pub service: Option<String>,
}

#[cfg(feature = "kubernetes")]
#[derive(Args, Debug)]
pub struct KubernetesCmd {
    #[command(subcommand)]
    pub command: KubernetesSubCommand,
}

#[cfg(feature = "kubernetes")]
#[derive(Subcommand, Debug)]
pub enum KubernetesSubCommand {
    /// Validate kubectl, current context, and node readiness
    Check {
        #[arg(long, help = "Kubeconfig context to target")]
        context: Option<String>,
        #[arg(
            long,
            default_value = "default",
            help = "Namespace to probe for workload readiness"
        )]
        namespace: String,
        #[arg(long, help = "Skip live cluster calls (tooling check only)")]
        offline: bool,
    },
    /// Generate Deployment/Service/NetworkPolicy YAML
    Generate {
        #[arg(long, help = "Logical app name (used for resource names)")]
        name: String,
        #[arg(long, help = "Container image reference")]
        image: String,
        #[arg(long, default_value_t = 80, help = "Container port for the service")]
        port: u16,
        #[arg(long, default_value_t = 1, help = "Replica count")]
        replicas: u16,
        #[arg(
            long,
            default_value = "default",
            help = "Namespace for generated resources"
        )]
        namespace: String,
        #[arg(
            long,
            default_value = "k8s/xbp-manifest.yaml",
            help = "Path to write the manifest bundle"
        )]
        output: String,
        #[arg(long, help = "Optional ingress host (creates Ingress when set)")]
        host: Option<String>,
    },
    /// Apply a manifest bundle with kubectl apply -f
    Apply {
        #[arg(long, help = "Path to manifest file")]
        file: String,
        #[arg(long, help = "Override kube context")]
        context: Option<String>,
        #[arg(long, help = "Override namespace")]
        namespace: Option<String>,
        #[arg(long, help = "Use --dry-run=server")]
        dry_run: bool,
    },
    /// Summarize deployments/services/pods in a namespace
    Status {
        #[arg(long, default_value = "default", help = "Namespace to summarize")]
        namespace: String,
        #[arg(long, help = "Override kube context")]
        context: Option<String>,
    },
}