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.rs dispatches all subcommands. AppContext lives in src/ctx.rs. Config schema in src/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:

FileScope
bess.yamlWorkspace-level defaults and profiles
bess-service.yamlPer-service overrides

Resolution works through ordered blocks, each containing one or more VarSource entries. Three adapter types load values:

AdapterReads from
Dotenv.env files
TomlTOML files
EnvProcess 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 with r! and the key remains unresolved after all blocks, it appears in required_missing on ResolvedConfig.

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=0 and SSH BatchMode=yes on all remote commands
  • SSH connectivity probing before recommending SSH protocol
  • SSH/HTTPS automatic fallback on clone failure
  • GlabError::NotInstalled / NotAuthenticated — no silent failures
  • wrap_future shows 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 guard
  • TelemetryError — error types

Commands

CommandModuleDescription
statusstatus.rsWorkspace health overview
upup.rsStart services (profile, filter, force-rebuild)
downdown.rsStop all running services
buildbuild.rsBuild/pull service images
profileprofile.rsList / switch / show profiles
configconfig.rsInspect / validate / generate config
initinit.rsBootstrap workspace (clone all repos, infra, migrations)
pullpull.rsGit pull across repos (override-aware)
getget.rsClone a single repository
overrideoverride_cmd.rsOverride repo checkout (branch/tag, clear, force)
mockmock.rsManage WireMock stubs
migratemigrate.rsRun Flyway migrations
volumevolume.rsManage Docker volumes
releaserelease.rsRelease 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

AreaGap
workspace/projects/ProjectDef trait exists but per-project structs are largely empty shells
CommandsNo unit tests for individual command delegates
UISimplified element system; no ratatui live mode
mando up --profileApp service startup by profile not yet implemented
Status commandDoesn’t show container health/state