//! Public dispatch for the `maestro` CLI subcommands. //! //! Every command talks to the local daemon (`[cli].daemon_url`, default //! `http://127.0.0.1:7070`): `list` reads its session catalog, `spawn` / `kill` //! hit its session endpoints, and `attach` opens `WS /api/attach/:id`. use std::path::{Path, PathBuf}; use maestro_protocol::DaemonClient; use crate::attach::{parse_detach_key, run_attach_url}; use crate::cli::Command; use crate::sessions::{run_kill, run_list, run_spawn}; use maestro_config::{default_config_path, MaestroConfigFile}; #[derive(Debug, thiserror::Error)] #[allow(clippy::result_large_err)] pub enum CliError { #[error("config: {0}")] Config(#[from] maestro_config::ConfigError), #[error("{0}")] Client(#[from] maestro_protocol::ClientError), #[error("{0}")] Attach(#[from] crate::attach::AttachError), #[error("{0}")] Io(#[from] std::io::Error), } /// Dispatch a parsed top-level subcommand. The umbrella binary passes the /// global flags (`--config`, `--daemon-url`) in alongside the variant. #[allow(clippy::result_large_err)] pub async fn dispatch( command: Command, config: Option, daemon_url_override: Option, ) -> Result<(), CliError> { let config_path = match config { Some(p) => p, None => default_config_path()?, }; let daemon = client(&config_path, daemon_url_override)?; match command { Command::List(args) => { let mut stdout = std::io::stdout().lock(); run_list(&daemon, &args, &mut stdout).await?; } Command::Spawn(args) => { let attach_after = args.attach; let session = run_spawn(&daemon, args).await?; println!("spawned {}", session.id); if attach_after { // `spawn --attach` has no detach-key flag; use the attach // default (Ctrl-q). run_attach_url(&daemon.attach_url(session.id)?, CTRL_Q).await?; } } Command::Attach(args) => { let detach = parse_detach_key(&args.detach_key)?; run_attach_url(&daemon.attach_url(args.session_id)?, detach).await?; } Command::Kill(args) => { run_kill(&daemon, &args).await?; println!("killed {}", args.session_id); } } Ok(()) } /// Default detach byte, matching `maestro attach --detach-key`'s `^q`. const CTRL_Q: u8 = 0x11; /// HTTP client pointed at the daemon's API. That surface needs no auth — /// reaching `bind_addr` is the authorization. #[allow(clippy::result_large_err)] fn client(config_path: &Path, override_url: Option) -> Result { let cfg = MaestroConfigFile::load(config_path)?; Ok(DaemonClient::new(cfg.resolve_daemon_url(override_url))) }