feat(maestro): scaffold maestro-tui crate + maestro tui subcommand (NAP-276)
ci / rust (push) Successful in 15m43s
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:
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user