This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-cli/src/dispatch.rs
T
naps62-yolo 35482118b1 feat(maestro): unify maestro/maestrod/maestro-registry into single binary (NAP-245)
New `crates/maestro/` umbrella binary hosts the CLI, daemon, and registry
behind Cargo features `cli`, `daemon`, `registry` (default = all three).
Library crates keep their structure; per-binary `main.rs` files are removed
and their dispatch logic is exposed as public functions:

- `maestro_cli::dispatch(command, config, registry_url)`
- `maestro_daemon::cli::dispatch(config, cmd)` (with public `DaemonCmd`/`ConfigureArgs`)
- `maestro_registry::run()`

Subcommand surface:

- `maestro list / spawn / attach / kill / set-registry / policy` (unchanged)
- `maestro daemon [run|configure]` (was `maestrod`)
- `maestro registry` (was `maestro-registry`)

Hard cutover — old binary names are not preserved.

Verified: default-feature build, plus `--no-default-features --features {cli|daemon|registry}`
each build clean with `-D warnings`.
2026-05-27 10:35:41 +00:00

141 lines
4.6 KiB
Rust

//! Public dispatch for the `maestro` CLI subcommands.
//!
//! Extracted from the per-binary `main.rs` (NAP-245) so the umbrella `maestro`
//! binary can dispatch to the same logic.
use std::path::{Path, PathBuf};
use crate::attach::{parse_detach_key, run_attach};
use crate::cli::{AttachArgs, Command, KillArgs, ListArgs, PolicyCmd, SetRegistryArgs, SpawnArgs};
use crate::config::{default_config_path, CliConfig, ConfigError};
use crate::http::RegistryClient;
use crate::policy::{run_reload as run_policy_reload, run_show as run_policy_show};
use crate::sessions::{fetch_connect_info, run_kill, run_list, run_spawn};
#[derive(Debug, thiserror::Error)]
#[allow(clippy::result_large_err)]
pub enum CliError {
#[error("config: {0}")]
Config(#[from] ConfigError),
#[error("{0}")]
Http(#[from] crate::http::HttpError),
#[error("{0}")]
Attach(#[from] crate::attach::AttachError),
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Policy(#[from] crate::policy::PolicyError),
}
/// Dispatch a parsed top-level subcommand. The umbrella binary passes the
/// global flags (`--config`, `--registry-url`) in alongside the variant.
#[allow(clippy::result_large_err)]
pub async fn dispatch(
command: Command,
config: Option<PathBuf>,
registry_url_override: Option<String>,
) -> Result<(), CliError> {
let config_path = match config {
Some(p) => p,
None => default_config_path()?,
};
match command {
Command::SetRegistry(args) => set_registry(&config_path, args),
Command::Policy(sub) => policy(&config_path, sub).await,
Command::List(args) => list(&config_path, registry_url_override, args).await,
Command::Spawn(args) => spawn(&config_path, registry_url_override, args).await,
Command::Attach(args) => attach(&config_path, registry_url_override, args).await,
Command::Kill(args) => kill(&config_path, registry_url_override, args).await,
}
}
#[allow(clippy::result_large_err)]
fn set_registry(config_path: &Path, args: SetRegistryArgs) -> Result<(), CliError> {
let url = args.url.trim_end_matches('/');
CliConfig::save_registry_url(config_path, url)?;
println!("registry URL saved to {}", config_path.display());
Ok(())
}
#[allow(clippy::result_large_err)]
async fn policy(config_path: &Path, sub: PolicyCmd) -> Result<(), CliError> {
let mut stdout = std::io::stdout().lock();
match sub {
PolicyCmd::Reload => run_policy_reload(config_path, &mut stdout).await?,
PolicyCmd::Show { json } => run_policy_show(config_path, json, &mut stdout).await?,
}
Ok(())
}
#[allow(clippy::result_large_err)]
async fn list(
config_path: &Path,
registry_url_override: Option<String>,
args: ListArgs,
) -> Result<(), CliError> {
let client = build_client(registry_url_override, config_path)?;
let mut stdout = std::io::stdout().lock();
run_list(&client, &args, &mut stdout).await?;
Ok(())
}
#[allow(clippy::result_large_err)]
async fn spawn(
config_path: &Path,
registry_url_override: Option<String>,
args: SpawnArgs,
) -> Result<(), CliError> {
let client = build_client(registry_url_override, config_path)?;
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?;
}
Ok(())
}
#[allow(clippy::result_large_err)]
async fn attach(
config_path: &Path,
registry_url_override: Option<String>,
args: AttachArgs,
) -> Result<(), CliError> {
let client = build_client(registry_url_override, config_path)?;
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?;
Ok(())
}
#[allow(clippy::result_large_err)]
async fn kill(
config_path: &Path,
registry_url_override: Option<String>,
args: KillArgs,
) -> Result<(), CliError> {
let client = build_client(registry_url_override, config_path)?;
run_kill(&client, &args).await?;
println!("killed {}", args.session_id);
Ok(())
}
#[allow(clippy::result_large_err)]
fn build_client(
override_url: Option<String>,
config_path: &Path,
) -> Result<RegistryClient, CliError> {
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(),
))
}