use crate::runlock::RunlockCommands; use clap::{Parser, Subcommand, ValueEnum}; pub use crate::cli_sub::{ ConfigCommands, PowerAction, PublishArgs, RegistryCommands, ReposArgs, ReposCommands, SyncImageArgs, TmuxCommands, }; /// 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 { /// Distribute a recipe's image across the cluster nodes (node-to-node, /// one save + parallel fan-out; no serve started). SyncImage(SyncImageArgs), /// 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). /// Mutually exclusive with `--hf-cache` (both pick the HF mount source). #[arg(long, num_args = 0..=1, default_missing_value = "")] mirror: Option, /// Override the host-side HF cache dir for this serve (config /// `hf_cache_dir`, else `~/.cache/huggingface`). `~` expands to the /// node's SSH-user home. Mutually exclusive with `--mirror`. #[arg(long)] hf_cache: Option, /// How hard to run the recipe's `[[staging]]` blocks: `auto` /// (ledger-verified, copy what is new), `skip` (assume staged), /// `force` (re-verify everything). #[arg(long)] stage: 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, /// Skip the post-build registry push (only meaningful when `[registry]` /// is configured and the image needs (re)building). #[arg(long)] no_push: bool, /// Where the image comes from: build from `docker-images/` (default) /// or pull the prebuilt `{base}/{name}:{checksum}` from `[registry]` /// (skips the build; the image must already be pushed). #[arg(long, value_enum, default_value_t = crate::docker::ImageSource::Repo)] image_source: crate::docker::ImageSource, }, /// 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/_