zinit 0.3.6

Process supervisor with dependency management
Documentation
//! CLI argument definitions for zinit client.

use std::path::PathBuf;

use clap::{Parser, Subcommand};

/// zinit - Process supervisor CLI
#[derive(Parser)]
#[command(name = "zinit")]
#[command(author, version)]
#[command(about = "Process supervisor with dependency management")]
#[command(
    long_about = "zinit is a process supervisor with dependency management, \
similar to systemd but simpler.\n\n\
Examples:\n  \
zinit list                    # List all services\n  \
zinit status nginx            # Show nginx service status\n  \
zinit start --tree myapp      # Start myapp and its dependencies\n  \
zinit why myapp               # Show why myapp is blocked\n  \
zinit tree                    # Show dependency tree\n  \
zinit tui                     # Launch interactive TUI (if enabled)\n  \
zinit repl                    # Launch interactive REPL (if enabled)"
)]
pub struct Cli {
    /// Socket path (default: auto-detect)
    #[arg(short, long, global = true)]
    pub socket: Option<PathBuf>,

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

#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Commands {
    /// List all services
    #[command(alias = "ls")]
    List,

    /// Show detailed status of a service
    Status {
        /// Service name
        name: String,
    },

    /// Start a service
    Start {
        /// Service name
        name: String,
        /// Also start required dependencies
        #[arg(long)]
        tree: bool,
    },

    /// Stop a service
    Stop {
        /// Service name
        name: String,
    },

    /// Restart a service
    Restart {
        /// Service name
        name: String,
    },

    /// Send a signal to a service
    Kill {
        /// Service name
        name: String,
        /// Signal to send (default: SIGTERM)
        #[arg(short, long)]
        signal: Option<String>,
    },

    /// Show why a service is blocked
    Why {
        /// Service name
        name: String,
    },

    /// Show the dependency tree
    Tree,

    /// Remove a service
    Remove {
        /// Service name
        name: String,
    },

    /// Reload service configurations from disk
    Reload,

    /// Show logs for a service
    Logs {
        /// Service name
        name: String,
        /// Number of lines to show
        #[arg(short = 'n', long, default_value = "100")]
        lines: usize,
        /// Follow log output (tail -f style) - not yet implemented
        #[arg(short, long)]
        follow: bool,
    },

    /// Ping the daemon
    Ping,

    /// Shutdown the daemon (stops services, exits server)
    Shutdown,

    /// Power off the system (signals init)
    Poweroff,

    /// Reboot the system (signals init)
    Reboot,

    /// Add a new service
    #[command(name = "add-service")]
    AddService {
        /// Path to TOML service file (mutually exclusive with --name/--exec)
        #[arg(value_name = "FILE", conflicts_with_all = ["name", "exec"])]
        file: Option<PathBuf>,

        // --- Service definition (alternative to file) ---
        /// Service name (required if no file)
        #[arg(long, requires = "exec")]
        name: Option<String>,

        /// Command to execute (required if no file)
        #[arg(long, requires = "name")]
        exec: Option<String>,

        /// Working directory
        #[arg(long, default_value = "/")]
        dir: String,

        /// Run once and don't restart
        #[arg(long)]
        oneshot: bool,

        /// Environment variable (KEY=VALUE), can repeat
        #[arg(long = "env", short = 'e', value_name = "KEY=VALUE")]
        envs: Vec<String>,

        // --- Dependencies ---
        /// Start after this service (ordering only)
        #[arg(long, value_name = "SERVICE")]
        after: Vec<String>,

        /// Require this service (hard dependency)
        #[arg(long, value_name = "SERVICE")]
        requires: Vec<String>,

        /// Want this service (soft dependency)
        #[arg(long, value_name = "SERVICE")]
        wants: Vec<String>,

        /// Conflict with this service (mutual exclusion)
        #[arg(long, value_name = "SERVICE")]
        conflicts: Vec<String>,

        // --- Lifecycle ---
        /// Restart policy: always, on-failure, never
        #[arg(long, default_value = "on-failure")]
        restart: String,

        /// Initial restart delay in ms
        #[arg(long, default_value = "1000")]
        restart_delay: u64,

        /// Maximum restart delay in ms
        #[arg(long, default_value = "300000")]
        restart_delay_max: u64,

        /// Maximum restart attempts
        #[arg(long, default_value = "10")]
        max_restarts: u32,

        // --- Persistence ---
        /// Save to config directory (survives restarts)
        #[arg(long, conflicts_with = "ephemeral")]
        persist: bool,

        /// Don't save to disk (default, lost on restart)
        #[arg(long, conflicts_with = "persist")]
        ephemeral: bool,
    },

    /// Show detailed debug state of the service graph
    #[command(name = "debug-state")]
    DebugState,

    /// Show process tree for a running service
    #[command(name = "debug-procs")]
    DebugProcs {
        /// Service name
        name: String,
    },

    // --- Interactive modes ---
    /// Launch interactive TUI dashboard
    #[cfg(feature = "tui")]
    Tui,

    /// Launch interactive REPL shell
    #[cfg(feature = "repl")]
    Repl,

    /// Xinet socket activation proxy management
    Xinet {
        #[command(subcommand)]
        command: XinetCommands,
    },
}

/// Xinet subcommands for socket activation proxy management
#[derive(Subcommand)]
pub enum XinetCommands {
    /// Register a new xinet proxy
    Register {
        /// Proxy name
        name: String,

        /// Listen address(es): unix:/path/to/sock or tcp:host:port
        #[arg(short, long, required = true)]
        listen: Vec<String>,

        /// Backend address: unix:/path or tcp:host:port
        #[arg(short, long)]
        backend: String,

        /// Zinit service name to activate on connection
        #[arg(long)]
        service: String,

        /// Timeout waiting for backend socket (seconds)
        #[arg(long, default_value = "30")]
        connect_timeout: u64,

        /// Stop service after idle time (0 = never)
        #[arg(long, default_value = "0")]
        idle_timeout: u64,

        /// Allow only one connection at a time
        #[arg(long)]
        single: bool,
    },

    /// Unregister an xinet proxy
    Unregister {
        /// Proxy name
        name: String,
    },

    /// List all registered proxies
    List,

    /// Show proxy status
    Status {
        /// Proxy name (omit for all)
        name: Option<String>,
    },
}