ci / rust (push) Successful in 2m40s
Rewrites CLAUDE.md as the current-architecture reference, drops the registry from INSTALL/PRODUCT/DESIGN and the web README, deletes the registry Dockerfile, and marks the dated design specs superseded where they describe the registry, JWT auth, or cross-host attach. The installer loses its configure step — every config field now has a default, so a daemon starts with no config file at all.
190 lines
5.6 KiB
Rust
190 lines
5.6 KiB
Rust
//! `maestro` — unified binary that hosts the CLI, the per-host daemon, and the
|
|
//! TUI behind feature-gated subcommands (NAP-245).
|
|
//!
|
|
//! ```text
|
|
//! cargo install --no-default-features --features daemon maestro # daemon-only host
|
|
//! cargo install maestro # default: cli + daemon + tui
|
|
//! ```
|
|
|
|
#[cfg(not(any(feature = "cli", feature = "daemon", feature = "tui")))]
|
|
compile_error!("at least one of the `cli`, `daemon`, `tui` features must be enabled");
|
|
|
|
use std::path::PathBuf;
|
|
use std::process::ExitCode;
|
|
|
|
use clap::{Parser, Subcommand};
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(
|
|
name = "maestro",
|
|
version,
|
|
about = "Unified CLI / daemon / TUI for the Maestro terminal-session orchestrator",
|
|
arg_required_else_help = true
|
|
)]
|
|
struct Cli {
|
|
/// Override the daemon HTTP base clients talk to (e.g.
|
|
/// `http://127.0.0.1:7070`). Defaults to `[cli].daemon_url`, then the local
|
|
/// `[daemon].bind_addr`, then `http://127.0.0.1:7070`.
|
|
#[arg(long, global = true, env = "MAESTRO_DAEMON_URL")]
|
|
daemon_url: Option<String>,
|
|
|
|
/// Path to the unified Maestro config file. Default `~/.config/maestro/config.toml`.
|
|
#[arg(long, global = true, env = "MAESTRO_CONFIG")]
|
|
config: Option<PathBuf>,
|
|
|
|
/// Increase log verbosity (CLI subcommands only). Repeat for more: `-v` = debug, `-vv` = trace.
|
|
#[arg(short, long, global = true, action = clap::ArgAction::Count)]
|
|
verbose: u8,
|
|
|
|
#[command(subcommand)]
|
|
command: Command,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum Command {
|
|
/// List all sessions.
|
|
#[cfg(feature = "cli")]
|
|
List(maestro_cli::cli::ListArgs),
|
|
/// Spawn a new session.
|
|
#[cfg(feature = "cli")]
|
|
Spawn(maestro_cli::cli::SpawnArgs),
|
|
/// Attach to a session over WebSocket. Bytes flow through the local TTY.
|
|
#[cfg(feature = "cli")]
|
|
Attach(maestro_cli::cli::AttachArgs),
|
|
/// Kill a session by ID.
|
|
#[cfg(feature = "cli")]
|
|
Kill(maestro_cli::cli::KillArgs),
|
|
/// Per-host daemon: tmux wrapper + session API.
|
|
#[cfg(feature = "daemon")]
|
|
Daemon {
|
|
#[command(subcommand)]
|
|
cmd: Option<maestro_daemon::cli::DaemonCmd>,
|
|
},
|
|
|
|
/// Open the terminal UI.
|
|
#[cfg(feature = "tui")]
|
|
Tui(maestro_tui::cli::TuiArgs),
|
|
}
|
|
|
|
fn main() -> ExitCode {
|
|
let cli = Cli::parse();
|
|
init_tracing(&cli);
|
|
|
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
|
.enable_all()
|
|
.build()
|
|
.expect("build tokio runtime");
|
|
|
|
match rt.block_on(dispatch(cli)) {
|
|
Ok(()) => ExitCode::SUCCESS,
|
|
Err(e) => {
|
|
eprintln!("maestro: {e}");
|
|
ExitCode::FAILURE
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn dispatch(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
|
match cli.command {
|
|
#[cfg(feature = "cli")]
|
|
Command::List(args) => {
|
|
cli_dispatch(
|
|
maestro_cli::cli::Command::List(args),
|
|
cli.config,
|
|
cli.daemon_url.clone(),
|
|
)
|
|
.await?
|
|
}
|
|
#[cfg(feature = "cli")]
|
|
Command::Spawn(args) => {
|
|
cli_dispatch(
|
|
maestro_cli::cli::Command::Spawn(args),
|
|
cli.config,
|
|
cli.daemon_url.clone(),
|
|
)
|
|
.await?
|
|
}
|
|
#[cfg(feature = "cli")]
|
|
Command::Attach(args) => {
|
|
cli_dispatch(
|
|
maestro_cli::cli::Command::Attach(args),
|
|
cli.config,
|
|
cli.daemon_url.clone(),
|
|
)
|
|
.await?
|
|
}
|
|
#[cfg(feature = "cli")]
|
|
Command::Kill(args) => {
|
|
cli_dispatch(
|
|
maestro_cli::cli::Command::Kill(args),
|
|
cli.config,
|
|
cli.daemon_url.clone(),
|
|
)
|
|
.await?
|
|
}
|
|
#[cfg(feature = "daemon")]
|
|
Command::Daemon { cmd } => maestro_daemon::cli::dispatch(cli.config, cmd).await?,
|
|
#[cfg(feature = "tui")]
|
|
Command::Tui(args) => {
|
|
let daemon_url = args
|
|
.daemon_url
|
|
.or(cli.daemon_url)
|
|
.unwrap_or_else(|| "http://127.0.0.1:7070".to_string());
|
|
maestro_tui::run(maestro_tui::RunConfig {
|
|
daemon_url,
|
|
config_path: cli.config,
|
|
})
|
|
.await?
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "cli")]
|
|
#[allow(clippy::result_large_err)]
|
|
async fn cli_dispatch(
|
|
command: maestro_cli::cli::Command,
|
|
config: Option<PathBuf>,
|
|
daemon_url: Option<String>,
|
|
) -> Result<(), maestro_cli::CliError> {
|
|
maestro_cli::dispatch(command, config, daemon_url).await
|
|
}
|
|
|
|
fn init_tracing(cli: &Cli) {
|
|
let filter = if is_cli_subcommand(&cli.command) {
|
|
cli_filter(cli.verbose)
|
|
} else {
|
|
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"))
|
|
};
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(filter)
|
|
.with_writer(std::io::stderr)
|
|
.init();
|
|
}
|
|
|
|
#[cfg(feature = "cli")]
|
|
fn is_cli_subcommand(command: &Command) -> bool {
|
|
matches!(
|
|
command,
|
|
Command::List(_) | Command::Spawn(_) | Command::Attach(_) | Command::Kill(_)
|
|
)
|
|
}
|
|
|
|
#[cfg(not(feature = "cli"))]
|
|
fn is_cli_subcommand(_command: &Command) -> bool {
|
|
false
|
|
}
|
|
|
|
fn cli_filter(verbose: u8) -> EnvFilter {
|
|
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"),
|
|
}
|
|
}
|