devrig 0.30.2

Local development orchestrator
Documentation
use clap::{CommandFactory, Parser};
use clap_complete::aot::generate;
use devrig::cli::{Cli, Commands};
use devrig::commands;
use devrig::config::resolve::resolve_config;
use devrig::orchestrator::Orchestrator;

#[tokio::main]
async fn main() {
    // Initialize tracing subscriber with env-filter support.
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .with_target(false)
        .init();

    let cli = Cli::parse();

    let result = match cli.command {
        Commands::Start {
            services,
            #[cfg(debug_assertions)]
            dev,
        } => {
            let dev_mode = { #[cfg(debug_assertions)] { dev } #[cfg(not(debug_assertions))] { false } };
            run_start(cli.global.config_file, services, dev_mode).await
        }
        Commands::Stop { all, .. } if all => run_stop_all().await,
        Commands::Stop { .. } => run_stop(cli.global.config_file).await,
        Commands::Delete { all } if all => run_delete_all().await,
        Commands::Delete { .. } => run_delete(cli.global.config_file).await,
        Commands::Ps { all } => commands::ps::run(cli.global.config_file.as_deref(), all),
        Commands::Init => commands::init::run(),
        Commands::Doctor => commands::doctor::run(),
        Commands::Env { service } => {
            commands::env::run(cli.global.config_file.as_deref(), &service)
        }
        Commands::Exec { docker, command } => {
            commands::exec::run(cli.global.config_file.as_deref(), &docker, command).await
        }
        Commands::Reset { docker } => {
            commands::reset::run(cli.global.config_file.as_deref(), &docker)
        }
        Commands::Validate => commands::validate::run(cli.global.config_file.as_deref()),
        Commands::Logs {
            services,
            follow: _,
            tail,
            since,
            grep,
            exclude,
            level,
            format,
            output,
            timestamps,
        } => commands::logs::run(
            cli.global.config_file.as_deref(),
            services,
            tail,
            since,
            grep,
            exclude,
            level,
            format,
            output,
            timestamps,
        ),
        Commands::Completions { shell } => {
            generate(shell, &mut Cli::command(), "devrig", &mut std::io::stdout());
            Ok(())
        }
        Commands::Cluster { command } => match command {
            devrig::cli::ClusterCommands::Create => {
                commands::cluster::run_create(cli.global.config_file.as_deref()).await
            }
            devrig::cli::ClusterCommands::Delete => {
                commands::cluster::run_delete(cli.global.config_file.as_deref()).await
            }
            devrig::cli::ClusterCommands::Kubeconfig => {
                commands::cluster::run_kubeconfig(cli.global.config_file.as_deref())
            }
            devrig::cli::ClusterCommands::Rebuild { images, no_apply } => {
                commands::cluster::run_rebuild_images(
                    images,
                    no_apply,
                    cli.global.config_file.as_deref(),
                )
                .await
            }
        },
        Commands::Kubectl { args } => {
            commands::cluster::run_kubectl(cli.global.config_file.as_deref(), args).await
        }
        Commands::Update => commands::update::run(),
        Commands::Skill { command } => match command {
            devrig::cli::SkillCommands::Install { global } => {
                commands::skill::run_install(global, cli.global.config_file.as_deref()).await
            }
            devrig::cli::SkillCommands::Reference => commands::skill::run_reference(),
        },
        Commands::Query { command } => match command {
            devrig::cli::QueryCommands::Traces {
                service,
                status,
                min_duration,
                last: _,
                limit,
                format,
            } => {
                commands::query::run_traces(
                    cli.global.config_file.as_deref(),
                    service,
                    status,
                    min_duration,
                    limit,
                    format,
                )
                .await
            }
            devrig::cli::QueryCommands::Trace { trace_id, format } => {
                commands::query::run_trace_detail(
                    cli.global.config_file.as_deref(),
                    trace_id,
                    format,
                )
                .await
            }
            devrig::cli::QueryCommands::Logs {
                service,
                level,
                search,
                trace_id,
                last: _,
                limit,
                format,
            } => {
                commands::query::run_logs(
                    cli.global.config_file.as_deref(),
                    service,
                    level,
                    search,
                    trace_id,
                    limit,
                    format,
                )
                .await
            }
            devrig::cli::QueryCommands::Metrics {
                name,
                service,
                last: _,
                limit,
                format,
            } => {
                commands::query::run_metrics(
                    cli.global.config_file.as_deref(),
                    name,
                    service,
                    limit,
                    format,
                )
                .await
            }
            devrig::cli::QueryCommands::Status { format } => {
                commands::query::run_status(cli.global.config_file.as_deref(), format).await
            }
            devrig::cli::QueryCommands::Related { trace_id, format } => {
                commands::query::run_related(cli.global.config_file.as_deref(), trace_id, format)
                    .await
            }
        },
    };

    if let Err(e) = result {
        eprintln!("Error: {:#}", e);
        std::process::exit(1);
    }
}

async fn run_start(
    config_file: Option<std::path::PathBuf>,
    services: Vec<String>,
    dev_mode: bool,
) -> anyhow::Result<()> {
    let config_path = resolve_config(config_file.as_deref())?;
    let mut orchestrator = Orchestrator::from_config(config_path)?;
    orchestrator.start(services, dev_mode).await
}

async fn run_stop(config_file: Option<std::path::PathBuf>) -> anyhow::Result<()> {
    let config_path = resolve_config(config_file.as_deref())?;
    let orchestrator = Orchestrator::from_config(config_path)?;
    orchestrator.stop().await
}

async fn run_stop_all() -> anyhow::Result<()> {
    use devrig::orchestrator::registry::InstanceRegistry;

    let mut registry = InstanceRegistry::load();
    registry.cleanup();
    let _ = registry.save();

    let instances = registry.list().to_vec();
    if instances.is_empty() {
        eprintln!("No running devrig instances found.");
        return Ok(());
    }

    for entry in &instances {
        let config_path = std::path::PathBuf::from(&entry.config_path);
        if !config_path.exists() {
            eprintln!("  {} — config not found, skipping", entry.slug);
            continue;
        }
        eprint!("  Stopping {} ... ", entry.slug);
        match Orchestrator::from_config(config_path) {
            Ok(o) => match o.stop().await {
                Ok(()) => eprintln!("done"),
                Err(e) => eprintln!("error: {:#}", e),
            },
            Err(e) => eprintln!("error: {:#}", e),
        }
    }
    Ok(())
}

async fn run_delete(config_file: Option<std::path::PathBuf>) -> anyhow::Result<()> {
    let config_path = resolve_config(config_file.as_deref())?;
    let orchestrator = Orchestrator::from_config(config_path)?;
    orchestrator.delete().await
}

async fn run_delete_all() -> anyhow::Result<()> {
    use devrig::orchestrator::registry::InstanceRegistry;

    let mut registry = InstanceRegistry::load();
    registry.cleanup();
    let _ = registry.save();

    let instances = registry.list().to_vec();
    if instances.is_empty() {
        eprintln!("No running devrig instances found.");
        return Ok(());
    }

    for entry in &instances {
        let config_path = std::path::PathBuf::from(&entry.config_path);
        if !config_path.exists() {
            eprintln!("  {} — config not found, skipping", entry.slug);
            continue;
        }
        eprint!("  Deleting {} ... ", entry.slug);
        match Orchestrator::from_config(config_path) {
            Ok(o) => match o.delete().await {
                Ok(()) => eprintln!("done"),
                Err(e) => eprintln!("error: {:#}", e),
            },
            Err(e) => eprintln!("error: {:#}", e),
        }
    }
    Ok(())
}