feat(maestro): scaffold maestro-tui crate + maestro tui subcommand (NAP-276)
ci / rust (push) Successful in 15m43s

New `crates/maestro-tui/` library behind a `tui` cargo feature on the umbrella
binary (default-on). Ships a clap-derive `TuiArgs` and a `run()` stub that
returns `NotImplemented` — exercises the full dispatch path so phases 2+ can
fill in the catalog, session liveness, widgets, and event loop without
touching scaffolding.

Acceptance:
- `cargo build -p maestro --features tui` succeeds
- `maestro tui` prints "maestro: not implemented" and exits 1
- `cargo test -p maestro-tui` passes (2 clap-parse tests)
- `cargo fmt --check` + `cargo clippy --all-targets --features tui -- -D warnings` clean

Phase 1 of the Maestro TUI epic (NAP-275); plan in
`docs/superpowers/plans/2026-05-28-maestro-tui.md`.
This commit is contained in:
2026-05-28 19:24:34 +00:00
parent d0f8997a78
commit c99f23c705
7 changed files with 115 additions and 3 deletions
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "maestro-tui"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
publish.workspace = true
description = "Terminal UI client over the local maestro daemon's `/api/*` surface."
[dependencies]
clap.workspace = true
thiserror.workspace = true
+40
View File
@@ -0,0 +1,40 @@
//! Clap-derive subcommand definition for `maestro tui`.
#[derive(Debug, clap::Args)]
pub struct TuiArgs {
/// Override the local daemon's HTTP base URL (default `http://127.0.0.1:7070`).
#[arg(long, env = "MAESTRO_DAEMON_URL")]
pub daemon_url: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Debug, Parser)]
struct Harness {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Debug, clap::Subcommand)]
enum Cmd {
Tui(TuiArgs),
}
#[test]
fn parses_with_daemon_url() {
let h = Harness::try_parse_from(["maestro", "tui", "--daemon-url", "http://10.0.0.1:7070"])
.unwrap();
let Cmd::Tui(args) = h.cmd;
assert_eq!(args.daemon_url.as_deref(), Some("http://10.0.0.1:7070"));
}
#[test]
fn parses_without_daemon_url() {
let h = Harness::try_parse_from(["maestro", "tui"]).unwrap();
let Cmd::Tui(args) = h.cmd;
assert!(args.daemon_url.is_none());
}
}
+32
View File
@@ -0,0 +1,32 @@
//! `maestro tui` — terminal UI client over the local daemon's `/api/*` surface.
//!
//! Design: `docs/superpowers/specs/2026-05-28-maestro-tui-design.md`
//! Implementation: `docs/superpowers/plans/2026-05-28-maestro-tui.md`
//!
//! Phase 1 (NAP-276) scaffolds the crate and a `Tui` subcommand stub. The real
//! event loop, rendering, and `/api/*` plumbing land in NAP-277 onward.
#![deny(missing_debug_implementations)]
pub mod cli;
/// Inputs for [`run`]. Built by the umbrella binary from the parsed
/// `TuiArgs` plus any global flags.
#[derive(Debug, Clone)]
pub struct RunConfig {
/// HTTP base URL of the local daemon, e.g. `http://127.0.0.1:7070`.
/// No trailing slash.
pub daemon_url: String,
}
#[derive(Debug, thiserror::Error)]
pub enum TuiError {
#[error("not implemented")]
NotImplemented,
}
/// Entry point. Phase-1 stub — returns `NotImplemented` so the subcommand
/// dispatch path is exercisable end-to-end before the renderer lands.
pub async fn run(_cfg: RunConfig) -> Result<(), TuiError> {
Err(TuiError::NotImplemented)
}
+3 -1
View File
@@ -12,10 +12,11 @@ name = "maestro"
path = "src/main.rs"
[features]
default = ["cli", "daemon", "registry"]
default = ["cli", "daemon", "registry", "tui"]
cli = ["dep:maestro-cli"]
daemon = ["dep:maestro-daemon"]
registry = ["dep:maestro-registry"]
tui = ["dep:maestro-tui"]
[dependencies]
clap.workspace = true
@@ -24,3 +25,4 @@ tracing-subscriber.workspace = true
maestro-cli = { path = "../maestro-cli", optional = true }
maestro-daemon = { path = "../maestro-daemon", optional = true }
maestro-registry = { path = "../maestro-registry", optional = true }
maestro-tui = { path = "../maestro-tui", optional = true }
+18 -2
View File
@@ -7,8 +7,13 @@
//! cargo install maestro # default: cli + daemon + registry
//! ```
#[cfg(not(any(feature = "cli", feature = "daemon", feature = "registry")))]
compile_error!("at least one of the `cli`, `daemon`, `registry` features must be enabled");
#[cfg(not(any(
feature = "cli",
feature = "daemon",
feature = "registry",
feature = "tui"
)))]
compile_error!("at least one of the `cli`, `daemon`, `registry`, `tui` features must be enabled");
use std::path::PathBuf;
use std::process::ExitCode;
@@ -73,6 +78,10 @@ enum Command {
/// Always-on registry: presence index + session catalog.
#[cfg(feature = "registry")]
Registry,
/// Open the terminal UI.
#[cfg(feature = "tui")]
Tui(maestro_tui::cli::TuiArgs),
}
fn main() -> ExitCode {
@@ -153,6 +162,13 @@ async fn dispatch(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
Command::Daemon { cmd } => maestro_daemon::cli::dispatch(cli.config, cmd).await?,
#[cfg(feature = "registry")]
Command::Registry => maestro_registry::run(cli.config).await?,
#[cfg(feature = "tui")]
Command::Tui(args) => {
let daemon_url = args
.daemon_url
.unwrap_or_else(|| "http://127.0.0.1:7070".to_string());
maestro_tui::run(maestro_tui::RunConfig { daemon_url }).await?
}
}
Ok(())
}