maestro-cli/src/config.rs was two pass-throughs, a newtype error, a two-field projection of MaestroConfigFile, and one four-line or_else with a single caller. `resolve_daemon_url` moves onto MaestroConfigFile, next to the schema it reads, and the module goes; its tests move with it. `[web].dev_proxy_target` is deleted end to end — schema, env var, normalisation, three tests, README line, and the startup warning that admitted the server ignores it. MaestroConfigFile::save had no callers outside its own tests. Deleting it also removes every skip_serializing_if, the five is_empty impls, and the Serialize derives that existed only to make its output tidy. BroadcastTuning was a struct that was never instantiated: all three uses were `::DEFAULT.field` in const position and there is no [broadcast] section. Now two plain consts. $MAESTRO_CONFIG was honoured twice, by resolve_config_path and by clap's `env =`. Every binary already gets it through clap, so resolve_config_path and CONFIG_PATH_ENV go and the TUI falls back to default_config_path like everything else. Also collapses three layers of 1:1 forwarding: the umbrella's cli_dispatch shim, and four near-identical 8-line functions in cli/dispatch.rs that differed only in which run_* they called. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
81 lines
2.8 KiB
Rust
81 lines
2.8 KiB
Rust
//! 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<PathBuf>,
|
|
daemon_url_override: Option<String>,
|
|
) -> 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<String>) -> Result<DaemonClient, CliError> {
|
|
let cfg = MaestroConfigFile::load(config_path)?;
|
|
Ok(DaemonClient::new(cfg.resolve_daemon_url(override_url)))
|
|
}
|