mando-cli v2

Polymorphic IoC-based CLI for Alpiq BESS project status, dependency checking, and developer tooling. Built on a custom dependency injection framework (polym_ioc) with async providers, broadcast-based event hooks, and a composable CLI output system.

Status: WIP (2026-03-18)

Core IoC framework, provider system, and event hooks functional. Two contexts implemented (filesystem, binary dependencies). Static CLI output framework being extended with Table, Step, and Panel elements. Ratatui live TUI scaffolded but not yet integrated with provider data.

Architecture

MandoApp (builder)
  ├── .ioc(MandoPolyContainer)     # type-erased HashMap<TypeId, Arc<dyn Any>>
  ├── .with::<MandoFsCtx>()        # filesystem context (project root, structure, state)
  ├── .with::<BinaryDepStateProvider>()  # binary dependency context (glab, etc.)
  ├── .with::<AggregateContext>()   # composite context resolver
  ├── .hook::<AutoHook<_, MandoProjectRootPath>>()  # reactive value watcher
  └── .start()                     # execute all initializers

Core Traits

TraitPurpose
DependencyContainerType-erased storage (resolve<T>, insert<T>)
MandoCliContext<D>Context with init/get/set/resolve against container D
MandoContextualValueProvider<D, O>Async value provider with event emission
ProvidedContextValueMarker with OutEventType + ErrEventType
CacheSafeProvidedContextValueClone + ProvidedContextValue, enables caching
ProviderValueHook<D>Hook trait for reacting to value changes
ContextAggregate<D>Builds composite values from multiple contexts
CliDisplayRenders provider values as Vec<CliElement> for terminal output

Data Flow

Provider resolves value
  → emits to broadcast::Sender<Option<OutEventType>>
  → AutoHook subscribes, deduplicates, forwards on mpsc
  → (future) ratatui TUI reacts to mpsc updates

Provider value implements CliDisplay
  → produces Vec<CliElement>
  → Page builder composes elements
  → render_to_term() outputs styled text via console crate

CLI Output Framework

Four-layer rendering pipeline:

  1. CliDisplay trait — provider values implement this to produce elements
  2. CliElement enum — typed renderable elements
  3. Page builder — fluent composition API
  4. render_to_term() — renders elements to stdout via console crate

Element Types

VariantPurpose
KeyValuelabel: value with style
StatusRowIcon + name + detail (dependency status)
HeaderSection header with decoration
SpacerBlank line
BlockIndented group of child elements
RawPre-formatted string
TableColumnar data with auto-aligned columns
StepSequential check with Pass/Fail/Pending/Skipped state
PanelBordered box with box-drawing chars, contains children

Page Builder

Page::new()
    .header("Mando Status")
    .panel_display(Some("Project"), &status.project_root)
    .spacer()
    .header("Dependencies")
    .steps(vec![
        (StepState::Pass, "glab".into(), Some("ready".into())),
        (StepState::Fail, "docker".into(), Some("not installed".into())),
    ])
    .render();

Styling

ElementStyle enum: Default, Success (green), Warning (yellow), Error (red), Muted (dim), Accent (cyan bold). Applied via console::style().

StepState enum: Pass (✓ green), Fail (✗ red), Pending (⠋ yellow), Skipped (- dim). Controls both icon and color.

Contexts

MandoFsCtx

Filesystem context providing:

  • MandoProjectRootPath — walks up from cwd looking for project markers
  • MandoProjectStructure — project directory layout
  • CliDirState — persisted CLI state from .mando-state.json

BinaryDepStateProvider

Binary dependency checker providing BinaryDepState<B> for any B: BinaryDependency:

  • Runs which/where to check installation
  • Optional auth check (B::auth_check_args())
  • Optional config check (B::config_check_args())
  • Currently implements: Glab (GitLab CLI)

MandoCliStatus (Aggregate)

Composite context that resolves from both MandoFsCtx and BinaryDepStateProvider:

  • project_root: MandoProjectRootPath
  • glab: BinaryDepState<Glab>

Event System

AutoHook<D, Target> watches ProvidedContextValue broadcast channels:

  • Creates broadcast channels if not present in container
  • Spawns tokio tasks for value and error channels
  • Deduplicates value events (only forwards on change)
  • Designed for future ratatui reactive updates

Dual Output Modes

  • Static (current focus): Page builder → render_to_term() for one-shot command output (mando status)
  • Live (future): ratatui alternate screen with event loop, fed by AutoHook channels (mando with no subcommand)

Both share the CliDisplay trait — same provider implementations feed both output paths.

Project Structure

src/
├── main.rs                    # tokio entry, MandoApp bootstrap, CLI/TUI routing
├── cli/
│   ├── commands.rs            # clap Command definition
│   └── router.rs              # subcommand dispatch
├── polym_ioc/
│   ├── container.rs           # DependencyContainer trait + MandoPolyContainer
│   ├── ctx.rs                 # MandoCliContext trait
│   ├── provider.rs            # MandoContextualValueProvider, ProvidedContextValue
│   ├── app.rs                 # MandoApp builder
│   └── aggregate.rs           # ContextResolver, AggregateContext
├── events/
│   └── hook.rs                # ProviderValueHook, AutoHook
├── contexts/
│   ├── status.rs              # MandoCliStatus aggregate
│   ├── fs/                    # filesystem context + providers
│   │   ├── ctx.rs
│   │   └── providers/
│   │       ├── project_rootpath_provider.rs
│   │       ├── mando_project_structure_provider.rs
│   │       └── cli_dir_state_provider.rs
│   └── deps/                  # dependency context + providers
│       ├── ctx.rs
│       └── providers/
│           ├── generic.rs     # BinaryDependency trait, BinaryDepState<B>
│           └── glab_ctx.rs    # Glab impl
└── tui/
    ├── mod.rs                 # MandoTui (ratatui shell)
    ├── draw.rs                # ratatui frame drawing
    ├── display.rs             # CliDisplay, CliElement, ElementStyle, StatusIcon, StepState
    ├── render.rs              # render_to_term, element rendering
    ├── page.rs                # Page fluent builder
    └── impls.rs               # CliDisplay impls for provider types

Dependencies

CratePurpose
tokioAsync runtime (full features)
tracing / tracing-subscriberStructured logging
thiserrorError derives
async-traitAsync trait support
serde / serde_jsonSerialization
anyhowError handling
clapCLI argument parsing
consoleStyled terminal output (static)
indicatifProgress bars
ratatui / crosstermLive TUI (future)
boxenBoxed text rendering
  • mando-cli — v1 YAML recipe runner (separate binary, same directory)
  • Mando — the service being managed
  • Alpiq BESS — project overview