//! `maestro` — CLI binary entry point. use std::path::Path; use std::process::ExitCode; use clap::Parser; use maestro_cli::attach::{parse_detach_key, run_attach}; use maestro_cli::cli::{Cli, Command}; use maestro_cli::config::{default_config_path, CliConfig, ConfigError}; use maestro_cli::http::RegistryClient; use maestro_cli::sessions::{fetch_connect_info, run_kill, run_list, run_spawn}; use tracing_subscriber::EnvFilter; #[tokio::main(flavor = "current_thread")] async fn main() -> ExitCode { let cli = Cli::parse(); init_tracing(cli.verbose); match dispatch(cli).await { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("maestro: {e}"); ExitCode::FAILURE } } } fn init_tracing(verbose: u8) { let filter = match (std::env::var_os("RUST_LOG"), verbose) { (Some(_), _) => { EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn")) } (None, 0) => EnvFilter::new("warn"), (None, 1) => EnvFilter::new("info"), (None, 2) => EnvFilter::new("debug"), (None, _) => EnvFilter::new("trace"), }; tracing_subscriber::fmt() .with_env_filter(filter) .with_writer(std::io::stderr) .init(); } #[derive(Debug, thiserror::Error)] #[allow(clippy::result_large_err)] enum CliError { #[error("config: {0}")] Config(#[from] ConfigError), #[error("{0}")] Http(#[from] maestro_cli::http::HttpError), #[error("{0}")] Attach(#[from] maestro_cli::attach::AttachError), #[error("{0}")] Io(#[from] std::io::Error), } #[allow(clippy::result_large_err)] async fn dispatch(cli: Cli) -> Result<(), CliError> { let config_path = match cli.config.clone() { Some(p) => p, None => default_config_path()?, }; // `set-registry` is the only command that writes config first and skips // the registry-required path below. if let Command::SetRegistry(args) = &cli.command { let mut cfg = CliConfig::load(&config_path)?; cfg.registry_url = Some(args.url.trim_end_matches('/').to_string()); cfg.save(&config_path)?; println!("registry URL saved to {}", config_path.display()); return Ok(()); } let client = build_client(cli.registry_url.clone(), &config_path)?; match cli.command { Command::List(args) => { let mut stdout = std::io::stdout().lock(); run_list(&client, &args, &mut stdout).await?; } Command::Spawn(args) => { let attach_after = args.attach; let session = run_spawn(&client, args).await?; println!("spawned {}", session.id); if attach_after { let info = fetch_connect_info(&client, session.id).await?; let detach = 0x11; // Ctrl-q run_attach(info, session.id, detach).await?; } } Command::Attach(args) => { let info = fetch_connect_info(&client, args.session_id).await?; let detach = parse_detach_key(&args.detach_key)?; run_attach(info, args.session_id, detach).await?; } Command::Kill(args) => { run_kill(&client, &args).await?; println!("killed {}", args.session_id); } Command::SetRegistry(_) => unreachable!("handled above"), } Ok(()) } #[allow(clippy::result_large_err)] fn build_client( override_url: Option, config_path: &Path, ) -> Result { let cfg = CliConfig::load(config_path)?; let url = override_url .or(cfg.registry_url.clone()) .ok_or(ConfigError::NoRegistryUrl)?; Ok(RegistryClient::new( url, cfg.cookie, config_path.to_path_buf(), )) }