mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Setup command handler
//!
//! Orchestrates environment setup and prerequisites checking.

use crate::context::CliContext;
use anyhow::Result;
use std::process::Command;

/// Arguments for setup command
#[derive(Debug, Clone)]
pub struct SetupArgs {
    pub check_only: bool,
    pub install_deps: bool,
}

/// Handle setup command
///
/// Checks and installs required dependencies for Mecha10 development.
///
/// # Arguments
///
/// * `ctx` - CLI execution context
/// * `args` - Setup command arguments
pub async fn handle_setup(ctx: &mut CliContext, args: &SetupArgs) -> Result<()> {
    println!();
    println!("🔧 Mecha10 Environment Setup");
    println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    println!();

    let mut all_ok = true;

    // Check Rust installation
    println!("Checking prerequisites...");
    println!();

    println!("1. Rust toolchain:");
    let rust_ok = check_rust()?;
    if rust_ok {
        println!("   ✅ Rust installed");
    } else {
        println!("   ❌ Rust not found");
        all_ok = false;
    }

    // Check Cargo
    println!("2. Cargo:");
    let cargo_ok = check_cargo()?;
    if cargo_ok {
        println!("   ✅ Cargo installed");
    } else {
        println!("   ❌ Cargo not found");
        all_ok = false;
    }

    // Check Docker
    println!("3. Docker:");
    let docker = ctx.docker();
    let docker_installed = docker.check_installation().is_ok();
    let docker_daemon = docker.check_daemon().is_ok();
    let docker_ok = docker_installed && docker_daemon;

    if docker_ok {
        println!("   ✅ Docker installed and running");
    } else if docker_installed {
        println!("   ⚠️  Docker installed but daemon not running");
        println!("      Start Docker Desktop or run: sudo systemctl start docker");
        all_ok = false;
    } else {
        println!("   ⚠️  Docker not available");
        println!("      Required for control plane services (Redis, PostgreSQL)");
        all_ok = false;
    }

    // Check Redis CLI (optional)
    println!("4. Redis CLI (optional):");
    let redis_cli_ok = check_redis_cli()?;
    if redis_cli_ok {
        println!("   ✅ redis-cli installed");
    } else {
        println!("   ⬜ redis-cli not found (optional)");
    }

    // Check pnpm (optional for web dashboard)
    println!("5. pnpm (optional):");
    let pnpm_ok = check_pnpm()?;
    if pnpm_ok {
        println!("   ✅ pnpm installed");
    } else {
        println!("   ⬜ pnpm not found (needed for dashboard development)");
    }

    // Check Python (required for AI model quantization)
    println!("6. Python 3:");
    let python_ok = check_python()?;
    if python_ok {
        println!("   ✅ Python 3 installed");
    } else {
        println!("   ⚠️  Python 3 not found (needed for AI model quantization)");
        all_ok = false;
    }

    println!();

    // Setup Python virtual environment and install dependencies if not in check-only mode
    if !args.check_only && python_ok {
        println!("7. Python Virtual Environment:");
        match setup_venv().await {
            Ok(true) => println!("   ✅ Virtual environment ready (.venv)"),
            Ok(false) => println!("   ✅ Using existing virtual environment (.venv)"),
            Err(e) => {
                println!("   ⚠️  Failed to create virtual environment: {}", e);
                println!("      Create manually with: python3 -m venv .venv");
            }
        }
        println!();

        println!("8. Python Dependencies:");
        match install_python_deps(ctx).await {
            Ok(true) => {
                println!("   ✅ Python dependencies installed in venv");
                println!();
                println!("   📝 To activate the virtual environment:");
                println!("      source .venv/bin/activate");
            }
            Ok(false) => println!("   ⬜ No requirements.txt found (skipping)"),
            Err(e) => {
                println!("   ⚠️  Failed to install Python dependencies: {}", e);
                println!("      Install manually with:");
                println!("        source .venv/bin/activate");
                println!("        pip install -r requirements.txt");
            }
        }
        println!();
    }

    // Check/download AI models if not in check-only mode
    if !args.check_only {
        println!("9. AI Models:");
        let models_result = check_and_download_models(ctx).await;
        match models_result {
            Ok(downloaded) => {
                if downloaded > 0 {
                    println!("   ✅ Downloaded {} recommended models", downloaded);
                } else {
                    println!("   ✅ Recommended models already installed");
                }
            }
            Err(e) => {
                println!("   ⚠️  Failed to download models: {}", e);
                println!("      You can download models later with: mecha10 models pull --all");
            }
        }
        println!();
    }

    // Summary
    if all_ok {
        println!("✨ Environment setup complete!");
        println!();
        println!("You're ready to use Mecha10:");
        println!("  • Create a project: mecha10 init");
        println!("  • Download AI models: mecha10 models pull --all");
        println!("  • Start services: mecha10 infrastructure start");
        println!("  • Start development: mecha10 dev");
    } else {
        println!("⚠️  Some prerequisites are missing");
        println!();

        if !rust_ok || !cargo_ok {
            println!("Install Rust:");
            println!("  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh");
            println!();
        }

        if !docker_ok {
            println!("Install Docker:");
            println!("  https://docs.docker.com/get-docker/");
            println!();
        }

        if args.install_deps {
            println!("Attempting to install optional dependencies...");
            install_optional_deps().await?;
        } else {
            println!("Run with --install-deps to install optional dependencies");
        }

        println!();

        if args.check_only {
            return Err(anyhow::anyhow!("Prerequisites check failed"));
        }
    }

    println!();
    Ok(())
}

/// Check if Rust is installed
fn check_rust() -> Result<bool> {
    let output = Command::new("rustc").arg("--version").output();
    Ok(output.map(|o| o.status.success()).unwrap_or(false))
}

/// Check if Cargo is installed
fn check_cargo() -> Result<bool> {
    let output = Command::new("cargo").arg("--version").output();
    Ok(output.map(|o| o.status.success()).unwrap_or(false))
}

/// Check if redis-cli is installed
fn check_redis_cli() -> Result<bool> {
    let output = Command::new("redis-cli").arg("--version").output();
    Ok(output.map(|o| o.status.success()).unwrap_or(false))
}

/// Check if pnpm is installed
fn check_pnpm() -> Result<bool> {
    let output = Command::new("pnpm").arg("--version").output();
    Ok(output.map(|o| o.status.success()).unwrap_or(false))
}

/// Check if Python 3 is installed
fn check_python() -> Result<bool> {
    // Try python3 first, then python
    for candidate in &["python3", "python"] {
        if let Ok(output) = Command::new(candidate).arg("--version").output() {
            if output.status.success() {
                // Verify it's Python 3.x
                let version = String::from_utf8_lossy(&output.stdout);
                if version.contains("Python 3.") {
                    return Ok(true);
                }
            }
        }
    }
    Ok(false)
}

/// Install optional dependencies
async fn install_optional_deps() -> Result<()> {
    // Check if npm is available
    let npm_available = Command::new("npm")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false);

    if npm_available {
        println!("Installing pnpm via npm...");
        let output = Command::new("npm").args(["install", "-g", "pnpm"]).output()?;

        if output.status.success() {
            println!("✅ pnpm installed");
        } else {
            println!("❌ Failed to install pnpm");
        }
    } else {
        println!("⚠️  npm not available, cannot install pnpm");
        println!("   Install Node.js: https://nodejs.org/");
    }

    Ok(())
}

/// Setup Python virtual environment
async fn setup_venv() -> Result<bool> {
    let venv_path = std::path::Path::new(".venv");

    // Check if venv already exists
    if venv_path.exists() {
        return Ok(false); // Already exists
    }

    // Find Python executable
    let python = find_python_executable()?;

    println!("   Creating virtual environment...");

    // Create venv
    let output = tokio::process::Command::new(&python)
        .args(["-m", "venv", ".venv"])
        .output()
        .await?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("venv creation failed: {}", stderr);
    }

    Ok(true) // Created new venv
}

/// Install Python dependencies from requirements.txt using venv
async fn install_python_deps(_ctx: &CliContext) -> Result<bool> {
    // Check if requirements.txt exists in the current directory
    let requirements_path = std::path::Path::new("requirements.txt");
    if !requirements_path.exists() {
        return Ok(false);
    }

    // Use the venv's Python executable if it exists, otherwise fall back to system Python
    let python = if std::path::Path::new(".venv/bin/python").exists() {
        ".venv/bin/python"
    } else if std::path::Path::new(".venv/Scripts/python.exe").exists() {
        ".venv/Scripts/python.exe" // Windows
    } else {
        &find_python_executable()?
    };

    println!("   Installing from requirements.txt...");

    // Run pip install -r requirements.txt
    let output = tokio::process::Command::new(python)
        .args(["-m", "pip", "install", "-r", "requirements.txt"])
        .output()
        .await?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        anyhow::bail!("pip install failed: {}", stderr);
    }

    Ok(true)
}

/// Find Python 3 executable
fn find_python_executable() -> Result<String> {
    for candidate in &["python3", "python"] {
        if let Ok(output) = Command::new(candidate).arg("--version").output() {
            if output.status.success() {
                let version = String::from_utf8_lossy(&output.stdout);
                if version.contains("Python 3.") {
                    return Ok(candidate.to_string());
                }
            }
        }
    }
    anyhow::bail!("Python 3 not found. Install with: brew install python3 (macOS) or apt install python3 (Linux)")
}

/// Check and download recommended AI models
async fn check_and_download_models(_ctx: &CliContext) -> Result<usize> {
    // Note: Model auto-download during setup has been removed.
    // Users should explicitly pull models they need using: mecha10 models pull <name>
    //
    // This keeps setup fast and gives users control over which models to download.
    // The model catalog no longer has a "recommended" field - models are recommended
    // by being referenced in node configurations.

    Ok(0)
}