Mando CLI Recipe Guide

Practical guide for reading, maintaining, and creating new mando-cli recipe YAML files. Covers every step type, template variables, conditional execution, and common authoring patterns.

What Are Recipes?

Recipes are YAML files in recipes/ that define interactive CLI workflows. They’re run via cargo run -- run <path> (e.g., cargo run -- run debug/memray-container). Each recipe is a linear sequence of steps — choices, prompts, checks, commands — that guide the user through a task.

Recipes are not code generation templates. They’re interactive scripts with a UI layer.

Recipe File Structure

name: Human-readable recipe name
description: One-line description shown in menus
 
steps:
  - <step_type>:
      name: Step label (shown to user)
      command: Shell command to run
      when: "optional condition"
      ignore_errors: true  # optional

Every recipe starts with name and description (used by the menu system), followed by a steps list. Steps execute top-to-bottom.

Step Types Reference

Step TypePurposeKey FieldsExample Use
checkVerify a precondition (fails recipe if command exits non-zero)name, commandCheck containers are running
choicePresent options to user, store selection in a variablename (becomes var name), message, options (array of label/value)Select target service
promptFree-text input from username, message, type (text), defaultEnter endpoint path
captureRun command silently, store stdout in a variablename (becomes var name), commandAuto-detect port number
runExecute a shell command, showing outputname, command, when, ignore_errors, cwdRun docker exec

Template Variables

Variables are set by choice, prompt, and capture steps. They’re referenced as {{variable_name}} in any subsequent step field. Variables are string-replaced before execution and remain in scope from the step that sets them until the end of the recipe.

- choice:
    name: target
    message: "Which service?"
    options:
      - label: "Forecast"
        value: local-forecast-1
 
- run:
    name: "Profile {{target}}"
    command: docker exec {{target}} python3 -c "print('hello')"

In this example, {{target}} resolves to local-forecast-1 if the user picks “Forecast”.

Conditional Execution (when)

The when field accepts simple string comparisons. If the condition evaluates to false, the step is silently skipped. If when is omitted, the step always runs.

  • Simple comparison: "{{var}} == value"
  • Multiple conditions: "{{var1}} == true && {{var2}} == false"
  • Negation: "{{var}} != value"

when conditions are string comparisons

when: "{{trace_allocators}} == True" compares the literal string "True". There is no boolean logic — true and True are different strings. Match the exact value produced by the step that sets the variable.

The ignore_errors Field

When true, the recipe continues even if the command exits non-zero. Use for:

  • open commands (may fail in headless environments)
  • Optional cleanup steps
  • Steps that are “nice to have” but not critical

Without it, a non-zero exit code stops the recipe immediately.

How to Create a New Recipe

  1. Create a YAML file in the appropriate recipes/ subdirectory
  2. Add name and description at the top
  3. Start with a check step for preconditions (e.g., are containers running?)
  4. Add choice/prompt steps to gather user input
  5. Use capture steps for computed values (port detection, timestamps, etc.)
  6. Add run steps for the actual work
  7. Use when conditions to branch on user choices

Keep it linear

Recipes execute top-to-bottom. There are no loops, no goto, no subroutines. Use when conditions to skip steps you don’t need. This keeps recipes predictable and debuggable.

Common Patterns

Port auto-detection

- capture:
    name: service_port
    command: |
      case "{{container_target}}" in
        local-forecast-1) echo 5000 ;;
        local-optimization-1) echo 6000 ;;
        *) echo 5000 ;;
      esac

Boolean normalization (for Python True/False)

- capture:
    name: trace_allocators
    command: |
      case "{{trace_allocators}}" in
        true|True|yes|1) echo "True" ;;
        *) echo "False" ;;
      esac

Timestamp-based run IDs

- capture:
    name: run_id
    command: echo "my-run-$(date +%Y%m%d-%H%M%S)"

Docker exec with env vars

- run:
    name: "Run script in container"
    command: |
      docker exec \
        -e VAR1={{value1}} \
        -e VAR2={{value2}} \
        {{container_target}} \
        python3 /tmp/my-script.py

Copy files in/out of containers

- run:
    name: "Copy script into container"
    command: docker cp ./test-data/script.py {{container}}:/tmp/script.py
 
- run:
    name: "Copy results out"
    command: docker cp {{container}}:/tmp/results/. ./runs/{{run_id}}/

Recipe Directory Structure

recipes/
├── debug/
│   ├── memray-container.yaml    — profile running containers (8 modes)
│   ├── memray-local.yaml        — profile local Python code
│   ├── memray-install.yaml      — install memray + debug symbols
│   ├── memray-sequence.yaml     — multi-request leak detection
│   └── memray-view.yaml         — browse saved profiles
├── (other recipe groups...)

Run a recipe: cargo run -- run debug/memray-container Or use the menu: cargo run -- run menu

Maintainability Tips

Readability

  • Use comment separators (# ═══════════) between logical sections in long recipes
  • Give every step a descriptive name — it’s what the user sees during execution
  • Group related steps together with comments

Repetition

memray-local.yaml repeats 4 near-identical blocks for each target x mode combination. This is a known trade-off — YAML doesn’t support functions or loops, so repetition is unavoidable. When editing, make sure to update all copies of repeated blocks.

Testing recipes

Run with -e flags to skip interactive prompts:

cargo run -- run debug/memray-container \
  -e container_target=local-forecast-1 \
  -e profile_mode=snapshot \
  -e trace_allocators=False