diff --git a/CLAUDE.md b/CLAUDE.md index c9b8fa4..76d7fc1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,6 +28,7 @@ crates/ ├── maestro-daemon/ # `maestro daemon` — tmux wrapper + WS endpoints (NAP-212, NAP-214) ├── maestro-registry/ # `maestro registry` — phone book + HTTP API (NAP-213, NAP-215) ├── maestro-web/ # standalone `maestro-web` browser UI (NAP-241, NAP-249) +├── maestro-tui/ # `maestro tui` — ratatui session manager (NAP-275 epic) ├── maestro-installer/ # `maestro-install` wizard: cargo install + systemd (NAP-225) └── maestro-e2e/ # cross-process integration tests (NAP-217) web/ # browser SPA (NAP-218) diff --git a/crates/maestro-tui/src/lib.rs b/crates/maestro-tui/src/lib.rs index 4e8cf40..27ec96a 100644 --- a/crates/maestro-tui/src/lib.rs +++ b/crates/maestro-tui/src/lib.rs @@ -22,6 +22,7 @@ pub mod state; pub mod tabs; pub mod terminal; +use std::path::PathBuf; use std::time::Duration; use ratatui::backend::CrosstermBackend; @@ -38,6 +39,10 @@ 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, + /// Explicit `--config ` override from the umbrella binary's global + /// flag. When `None`, [`maestro_config::resolve_config_path`] picks the + /// default (`$MAESTRO_CONFIG` or `~/.config/maestro/config.toml`). + pub config_path: Option, } #[derive(Debug, thiserror::Error)] @@ -62,8 +67,13 @@ pub async fn run(cfg: RunConfig) -> Result<(), TuiError> { // Health check — fail fast if no daemon is reachable. health_check(&base_url).await?; - // Load runtime config from disk if present; fall back to defaults. - let cfg_file = match maestro_config::resolve_config_path() { + // Load runtime config: explicit `--config ` wins, then + // `$MAESTRO_CONFIG`, then the platform default. Missing file is fine — + // we fall back to shipped defaults. + let cfg_path = cfg.config_path.clone().map(Ok).unwrap_or_else(|| { + maestro_config::resolve_config_path().map_err(|e| TuiError::Config(e.to_string())) + }); + let cfg_file = match cfg_path { Ok(path) => maestro_config::MaestroConfigFile::load_or_default(&path) .map_err(|e| TuiError::Config(e.to_string()))?, Err(_) => maestro_config::MaestroConfigFile::default(), diff --git a/crates/maestro/src/main.rs b/crates/maestro/src/main.rs index 34174e6..ca0ab42 100644 --- a/crates/maestro/src/main.rs +++ b/crates/maestro/src/main.rs @@ -167,7 +167,11 @@ async fn dispatch(cli: Cli) -> Result<(), Box> { 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? + maestro_tui::run(maestro_tui::RunConfig { + daemon_url, + config_path: cli.config, + }) + .await? } } Ok(()) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index f631dca..8efb48f 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -52,8 +52,8 @@ The binary lands in `~/.cargo/bin/maestro` — make sure that's on your `$PATH`. ## Thin builds for single-role hosts -The default install pulls in everything. Registry-only and daemon-only hosts -can opt out of unused dependencies: +The default install pulls in everything (`cli` + `daemon` + `registry` + `tui`). +Single-role hosts can opt out of unused dependencies: ```sh # Registry server — no tmux/PTY/jwt-verifier code: @@ -63,10 +63,52 @@ cargo install --git https://git.naps.pt/naps62/maestro \ # Daemon-only host — no embedded web assets, no registry HTTP surface: cargo install --git https://git.naps.pt/naps62/maestro \ --no-default-features --features daemon --locked maestro + +# CLI-only client (no local daemon, no TUI): +cargo install --git https://git.naps.pt/naps62/maestro \ + --no-default-features --features cli --locked maestro ``` The wizard always installs the default-feature build. +## The terminal UI: `maestro tui` + +`maestro tui` is the keyboard-and-mouse driven terminal UI client. It ships +in the default feature set, so a vanilla `cargo install maestro` builds it. +Drop the `tui` feature to skip it on hosts that don't need the UI surface +(`--no-default-features --features cli,daemon`). + +It talks only to the **local** `maestro daemon` over `http://127.0.0.1:7070` +by default; peer daemons reach you transparently via the catalog mirror. +Override the daemon URL with `--daemon-url` or `$MAESTRO_DAEMON_URL`. + +The `[tui]` section in `~/.config/maestro/config.toml` (or whatever path you +pass via the global `--config` flag) controls the keymap. Shipped defaults +cover every v1 action — only override what you want to change: + +```toml +[tui] +leader = "ctrl-a" # one byte that escapes Insert mode +chord_timeout_ms = 1000 # how long to hold a partial chord like `g` + +[tui.keymap.normal] +"i" = "EnterInsert" +"s" = "OpenSpawnModal" +"x" = "KillHighlighted" +"?" = "ShowHelp" +"ctrl-t" = "NewTab" +"g t" = "NextTab" +"ctrl-w h" = "FocusSidebar" +"enter" = "AttachHighlighted" + +[tui.keymap.insert] +# (leader is added automatically as LeaveInsert; only override here when you +# want extra Insert-mode bindings) +``` + +The full action vocabulary lives in +`docs/superpowers/specs/2026-05-28-maestro-tui-design.md` §3.8. + ## Adding the browser UI (optional) `maestro-web` is a separate binary that serves the SPA bundle and reverse-