mando-cli v2
Unified CLI for the Alpiq BESS workspace. On 2026-03-31 the previous v1 and v3 attempts were consolidated under a single v2 architecture. On 2026-04-09 the src/v2/ hierarchy was flattened to src/ root (no more v2 prefix). The result is 11 top-level modules, 13 commands, 96 passing tests, and 0 compiler errors.
For Agents
Entry point:
src/commands/mod.rsdispatches all subcommands. AppContext lives insrc/ctx.rs. Config schema insrc/config/schema.rs. Known gaps listed at the bottom of this document.
Module Structure
src/
├── ctx.rs # AppContext, CommandDelegate trait, CommandError
├── opts.rs # CliOpts, CliBuffer (spinner + render), CliContext
├── lib.rs # re-exports all 11 submodules
├── commands/ # 13 command implementations + dispatch fn
├── config/ # bess.yaml / bess-service.yaml resolution
│ ├── resolver.rs # ConfigResolver, ResolvedConfig, interpolation
│ ├── schema.rs # ServiceConfiguration, VarSource, Adapter enum
│ ├── defaults/ # default config values
│ └── adapter/ # dotenv, toml, env adapters
├── runtime/ # Docker lifecycle, build profiles, container specs
│ ├── artifact.rs # BuiltArtifact (hash, path, attributes)
│ ├── build_profile.rs # BuildProfile, ServiceBuildDef
│ ├── docker_client.rs # DockerClient (bollard) — build/pull/lifecycle
│ └── infra.rs # ContainerSpec for postgres, wiremock
├── network/ # external dependency mocking
│ ├── manager.rs # NetworkDependencyManager
│ └── backend.rs # MockBackend trait, MockStub, MockRequest
├── db/ # database migration tooling
│ ├── flyway.rs # Flyway migration wrapper
│ ├── runner.rs # MigrationRunner trait, MigrationConfig
│ └── mod.rs
├── system/ # OS-level primitives
│ ├── cmd.rs # Cmd builder, async execution, RawOutput
│ ├── handler.rs # BinaryHandler trait
│ ├── output.rs # structured command output (text, json, kv, columns)
│ ├── path.rs # path utilities
│ └── vfs.rs # virtual filesystem, FsError
├── ui/ # terminal rendering
│ ├── element.rs # Element enum (Header, StatusRow, KV, etc.)
│ └── render.rs # render_elements() to stdout
├── telemetry/ # observability
│ ├── tracing_setup.rs # init_tracing, TracingGuard
│ └── error.rs # TelemetryError
└── workspace/ # project discovery and git operations
├── project.rs # Project enum, MandoWorkspaceProject
├── state.rs # ProjectStateManager, OverrideEntry
├── bootstrap.rs # workspace on_load hook
├── git/ # GitHandler, GlabHandler, SSH probe
│ ├── git_handler.rs # git ops with timeouts, safe env
│ ├── glab_handler.rs # GitLab auth, SSH/HTTPS detection
│ ├── git.rs # GitRepository wrapper
│ └── mod.rs
└── projects/ # per-project context wrappers
└── ctx.rs # ProjectDef trait, ProjectError
Key Design Decisions
AppContext (no DI container)
v1 used a custom polymorphic IoC container (polym_ioc). v2 drops all of that. A CLI process lives for seconds, not minutes --- the overhead of a DI framework is not justified.
pub struct AppContext {
pub cli: Arc<CliContext>, // opts + render buffer
pub state: Arc<ProjectStateManager>,
pub projects: Option<MandoWorkspaceProject>,
pub workspace_state: WorkspaceState,
}AppContext is constructed once at startup and passed by reference to every command. CliContext bundles CliOpts (flags) and CliBuffer (spinner helper + element renderer).
CommandDelegate trait
Clap owns the CLI definition (in src/cli.rs). Each command struct implements CommandDelegate:
#[async_trait]
pub trait CommandDelegate: Send + Sync {
async fn execute(&self, ctx: &AppContext) -> Result<(), CommandError>;
}The dispatch() function in commands/mod.rs maps each Commands enum variant to its delegate.
Config: block-based resolution
Two YAML files drive configuration:
| File | Scope |
|---|---|
bess.yaml | Workspace-level defaults and profiles |
bess-service.yaml | Per-service overrides |
Resolution works through ordered blocks, each containing one or more VarSource entries. Three adapter types load values:
| Adapter | Reads from |
|---|---|
Dotenv | .env files |
Toml | TOML files |
Env | Process environment |
Key rules:
- First-set-wins (top-down): blocks are processed in order; if a key is already set, later blocks cannot override it.
${VAR}interpolation: after all blocks resolve, values containing${VAR}are expanded against the full resolved map. Circular references are detected and error.r!= required: if a key’s description starts withr!and the key remains unresolved after all blocks, it appears inrequired_missingonResolvedConfig.
Git Handler Layer (rewritten 2026-04-09)
See mando-cli-git-handler-rewrite-2026-04-09 for full details.
- All remote operations have 120s timeouts via
remote_cmd()+run_with_timeout() GIT_TERMINAL_PROMPT=0andSSH BatchMode=yeson all remote commands- SSH connectivity probing before recommending SSH protocol
- SSH/HTTPS automatic fallback on clone failure
GlabError::NotInstalled/NotAuthenticated— no silent failureswrap_futureshows✘with error message on failure (was always showing✔)
NetworkDependencyManager with MockBackend trait
Manages external API mocking via a pluggable backend system. NetworkDependencyManager holds backends and fans out operations.
Telemetry: local OTLP + diagnostics
Two active subsystems:
init_tracing/TracingGuard— tracing-subscriber with RAII guardTelemetryError— error types
Commands
| Command | Module | Description |
|---|---|---|
status | status.rs | Workspace health overview |
up | up.rs | Start services (profile, filter, force-rebuild) |
down | down.rs | Stop all running services |
build | build.rs | Build/pull service images |
profile | profile.rs | List / switch / show profiles |
config | config.rs | Inspect / validate / generate config |
init | init.rs | Bootstrap workspace (clone all repos, infra, migrations) |
pull | pull.rs | Git pull across repos (override-aware) |
get | get.rs | Clone a single repository |
override | override_cmd.rs | Override repo checkout (branch/tag, clear, force) |
mock | mock.rs | Manage WireMock stubs |
migrate | migrate.rs | Run Flyway migrations |
volume | volume.rs | Manage Docker volumes |
release | release.rs | Release tooling (debug-only) |
Test Status (2026-04-09)
- 96 tests passing across 4 test suites
- 0 compiler errors
- Coverage:
system/(cmd, handler, output),workspace/git/(git_handler, glab_handler), integration tests (full git workflows, clone/pull roundtrips, checkout, worktrees, SSH probe)
Known Gaps
| Area | Gap |
|---|---|
workspace/projects/ | ProjectDef trait exists but per-project structs are largely empty shells |
| Commands | No unit tests for individual command delegates |
| UI | Simplified element system; no ratatui live mode |
mando up --profile | App service startup by profile not yet implemented |
| Status command | Doesn’t show container health/state |
Related
- mando-cli — v1 YAML recipe runner (historical)
- mando-cli-v3 — abandoned v3 attempt (historical)
- mando-cli-git-handler-rewrite-2026-04-09 — git handler rewrite details
- mando-cli-cleanup-2026-03-31 — dead code cleanup session
- mando-cli-v2-state-flow — state integration session
- mando-cli-docker-lifecycle — Docker container lifecycle
- mando-cli-mock-command-2026-04-28 — WireMock command rollout
- mando-cli-mock-down-idempotent-2026-05-06 —
mock down+ healthcheck fix (2026-05-06 session) - mando-cli-status-readonly-2026-05-06 —
statuscommand made read-only and bounded (2026-05-06 session) - mando-cli-build-context-filter-2026-05-06 — yaml-driven
context_includes(2026-05-06 session) - mando-cli-build-variants-shelved-2026-05-06 — SHELVED: profile-driven build variants (2026-05-06 design)
- Mando — the service being managed
- Alpiq BESS — project overview