use clap::{Args, Parser, Subcommand, ValueEnum}; /// Output format for human-readable reports (`--format`). #[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)] pub enum OutputFormat { Plain, Md, Html, } #[derive(Parser)] #[command(author, version, about = "DGX Spark Launcher for vLLM and SGLang")] pub struct Cli { #[arg(long, short, default_value = "config.toml")] pub config: String, #[arg(long, short, help = "Show commands without executing")] pub dry_run: bool, #[command(subcommand)] pub command: Commands, } #[derive(Subcommand)] pub enum Commands { /// Launch a cluster Serve { #[arg(long, required = true)] recipe: String, #[arg(long, default_value = "auto")] cluster: String, /// Target a specific node within the cluster (default: all). #[arg(long)] node: Option, /// Keep containers after stop (default: auto-remove on stop) #[arg(long)] keep: bool, /// Skip pre-serve hooks (cluster-level + recipe-level). #[arg(long)] skip_before_serve: bool, /// Don't follow logs after serving (default: follow). #[arg(long, action = clap::ArgAction::SetTrue)] no_follow: bool, /// Skip the engine-readiness wait after launch (no /v1/models poll). #[arg(long)] no_wait: bool, /// Power on cluster if switch is off (requires HA config). #[arg(short = 'O', long = "on")] auto_power_on: bool, /// Sync Docker image across nodes before starting. #[arg(long, default_value_t = true)] sync_image: bool, /// Explicit source node for image sync (default: auto-detect). #[arg(long)] sync_image_source: Option, /// Build image on this node if not found (requires docker-images//). #[arg(long)] build_node: Option, /// Force rebuild even if checksum matches. #[arg(long)] force_build: bool, /// Run even on occupied nodes (dangerous for GPU recipes: OOM, /// port conflicts). Skips the occupancy check in cluster selection. #[arg(long)] force: bool, /// Serve single-node on the first free node (overrides recipe `solo`); /// `--no-solo` forces multi-node. #[arg(long)] solo: Option, /// Mount a per-node local HF mirror (bare = ~/.cache/hf-models; /// `--mirror ` = that path; first serve copies per node). #[arg(long, num_args = 0..=1, default_missing_value = "")] mirror: Option, /// Scale the recipe to N nodes (`--fit=N`; bare = whole cluster, TP /// tracks it; incompatible with --solo). #[arg(long, num_args = 0..=1, default_missing_value = "")] fit: Option, /// `sudo rm -rf` the host-side extra_mounts folders on the launch /// nodes before containers start (repo-based mounts untouched). #[arg(long)] clear: bool, }, /// Show container logs Logs { #[arg(long, required = true)] recipe: String, #[arg(long, action = clap::ArgAction::SetTrue)] follow: bool, #[arg(long, default_value = "auto")] cluster: String, /// Target a specific node within the cluster (default: head node if following, all nodes if not). #[arg(long)] node: Option, }, /// Stop a cluster Stop { #[arg(long, required = true)] recipe: String, /// Target specific nodes (repeatable, e.g. --node dgx1 --node dgx2). #[arg(long)] node: Vec, #[arg(long, default_value = "auto")] cluster: String, /// Power off cluster after stopping containers (requires HA config). #[arg(long = "off")] power_off: bool, /// Don't wait until the containers are actually stopped and removed /// (default: wait and verify, up to 60 s per node, so an immediate /// re-serve on the same nodes doesn't race the dying containers). #[arg(long)] no_wait: bool, }, /// Bench a live serve with tool-eval-bench: reports auto-land in /// recipes//reports/_