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`.
141 lines
4.6 KiB
Rust
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(),
|
|
))
|
|
}
|