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/src/main.rs
T
Miguel Palhas c856333e4a
ci / rust (push) Successful in 2m40s
docs(maestro): describe the single-host architecture
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.
2026-08-22 10:26:49 +01:00

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"),
}
}