Skip to main content

Multimodal Dynamic Launcher - Shell script orchestrator

Project description

Mudyla - Multimodal Dynamic Launcher

CI/CD PyPI Python 3.12+ License: MIT Nix Nix Flake

A script orchestrator: define graphs of Python/Bash/etc actions in Markdown files and run them in parallel under Nix environments.

Totally Claude'd.

Based on some ideas from DIStage Dependency Injection, Grandmaster (our build tool which is currently under development) and ix package manager.

Successor of mobala

If you use Scala and SBT, Mudyla works well with Squish.

An example of a real project using this gloomy tool: Baboon.

Demo

  • Parallel build: asciicast
  • Checkpoint recovery: asciicast
  • Weak dependencies: asciicast

Features

  • Markdown-based action definitions: Define actions in readable Markdown files
  • Multi-language support: Write actions in Bash or Python
  • Dependency graph execution: Automatic dependency resolution and parallel execution
  • Multi-version actions: Different implementations based on axis values (e.g., build-mode)
  • Multi-context execution: Run the same action multiple times with different configurations
  • Per-action parameters: Each action invocation can have different axis values and arguments
  • Type-safe returns: Actions return typed values (int, string, bool, file, directory)
  • Nix integration: All actions run in Nix development environment (optional on Windows)
  • Command-line arguments and flags: Parameterize actions with arguments and flags
  • Environment validation: Validates required environment variables before execution
  • Parallel execution: Run independent actions concurrently for faster builds
  • Checkpoint recovery: Resume from previous runs with --continue flag
  • Rich CLI output: Beautiful tables, execution plans, and progress tracking
  • CI/CD ready: GitHub Actions integration with automated test reporting and PyPI publishing

Installation

Using pip/pipx (easiest)

# Install with pipx (recommended - isolated installation)
pipx install mudyla

# Or install with pip
pip install mudyla

# Run
mdl --help

Using Nix Flakes

# Run directly
nix run github:7mind/mudyla -- :your-action

# Install to profile
nix profile install github:7mind/mudyla

# Development environment (with uv)
nix develop

From Source

git clone https://github.com/7mind/mudyla
cd mudyla

# Option 1: With direnv (automatic)
direnv allow  # Environment loads automatically

# Option 2: Manual
nix develop  # Sets up uv environment manually

Using UV (without Nix)

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone and install
git clone https://github.com/7mind/mudyla
cd mudyla
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
uv pip install -e ".[dev]"

# Run
mdl --help

Quick Start

1. Install Mudyla

# Recommended: Install with pipx (isolated installation)
pipx install mudyla

# Or install with pip
pip install mudyla

# Verify installation
mdl --help

2. Create a Definition File

Create .mdl/defs/actions.md:

# arguments

- `args.output-dir`: Output directory for results
  - type: `directory`
  - default: `"test-output"`

# action: hello-world

```bash
echo "Hello, World!" > "${args.output-dir}/hello.txt"
ret message-file:file=${args.output-dir}/hello.txt
\```

3. Run the Action

mdl :hello-world

That's it! Mudyla will:

  • Resolve dependencies
  • Execute actions in parallel (if independent)
  • Show a rich progress table
  • Output results as JSON

Action Definition Format

Basic Action (Bash)

# action: action-name

```bash
echo "Running action"
ret output-value:string=success
\```

Basic Action (Python)

# action: python-action

```python
# Python actions use the mdl object
mdl.ret("output-value", "success", "string")
mdl.ret("count", 42, "int")
\```

Action with Dependencies

Bash:

# action: dependent-action

```bash
INPUT="${action.previous-action.output-value}"
echo "Using: $INPUT"
ret result:string=done
\```

Python:

# action: python-dependent

```python
# Declare dependency and access outputs
mdl.dep("action.previous-action")
input_value = mdl.actions["previous-action"]["output-value"]
print(f"Using: {input_value}")
mdl.ret("result", "done", "string")
\```

Multi-Version Action

# Axis

- `build-mode`=`{release|development*}`

# action: build

## definition when `build-mode: release`

```bash
echo "Release build"
ret mode:string=release
\```

## definition when `build-mode: development`

```bash
echo "Development build"
ret mode:string=development
\```

Multi-Context Execution

Run the same action multiple times with different configurations using per-action axis values and arguments.

Note: On Windows, context IDs use ASCII symbols (A-Z, 1-8) instead of emojis to avoid encoding issues. On Linux/macOS, colorful emojis are used for better visual distinction.

Multiple contexts for the same action:

# Run build action in both development and release modes
mdl :build --axis build-mode:development :build --axis build-mode:release

# Each context gets its own execution with separate outputs
# Output: build-mode:development#build and build-mode:release#build

Per-action arguments:

# Run the same action with different argument values
mdl :process-file --input=file1.txt :process-file --input=file2.txt

# Each invocation executes independently with its own arguments

Context inheritance:

Dependencies automatically inherit the context from their parent action:

# When running :build with different modes, dependencies also get separate contexts
mdl :build --axis build-mode:release

# If build depends on compile, both will use build-mode:release context
# Output shows: build-mode:release#compile → build-mode:release#build

Context notation:

Multi-context execution uses the format context#action:

  • build-mode:release#build - build action in release context
  • build-mode:development#compile - compile action in development context
  • Rich tables show separate rows for each context

Graph unification:

Duplicate invocations with identical context are automatically unified:

# These two are the same and will only execute once
mdl :build --axis build-mode:release :build --axis build-mode:release

Real-world example: Cross-compilation

# Axis
- `target-arch`=`{x86_64*|aarch64|armv7}`

# action: compile

## definition when `target-arch: x86_64`
```bash
gcc -march=x86-64 -o build/app-x64 src/*.c
ret binary:file=build/app-x64
\```

## definition when `target-arch: aarch64`
```bash
aarch64-linux-gnu-gcc -o build/app-arm64 src/*.c
ret binary:file=build/app-arm64
\```

## definition when `target-arch: armv7`
```bash
arm-linux-gnueabihf-gcc -o build/app-armv7 src/*.c
ret binary:file=build/app-armv7
\```

Run for all architectures:

# Compiles for all three architectures in parallel
mdl :compile --axis target-arch:x86_64 \
    :compile --axis target-arch:aarch64 \
    :compile --axis target-arch:armv7

Python Actions

Mudyla supports Python code blocks alongside Bash. Python actions use the mdl object for interacting with the Mudyla runtime.

Available Python API:

# Return values
mdl.ret(name: str, value: Any, type: str)

# Declare dependencies
mdl.dep(dependency: str)  # e.g., "action.build" or "env.API_KEY"

# Access system variables
mdl.sys["project-root"]  # Project root directory

# Access environment variables
mdl.env.get("VARIABLE_NAME", default)
mdl.env["VARIABLE_NAME"]  # Without default

# Access command-line arguments
mdl.args.get("arg-name", default)
mdl.args["arg-name"]  # Without default

# Access command-line flags
mdl.flags.get("flag-name", False)

# Access outputs from other actions
mdl.actions["action-name"]["output-variable"]

Example: Python action with file operations

# action: process-data

```python
import pathlib
import json

# Access context
project_root = mdl.sys["project-root"]
output_dir = mdl.args.get("output-dir", "output")

# Create output file
output_path = pathlib.Path(project_root) / output_dir / "results.json"
output_path.parent.mkdir(parents=True, exist_ok=True)

# Process data
data = {"status": "success", "count": 42}

with output_path.open("w") as f:
    json.dump(data, f, indent=2)

mdl.ret("output-file", str(output_path), "file")
mdl.ret("count", data["count"], "int")
\```

Example: Mixed Bash and Python workflow

# action: prepare-env

```bash
# Bash action creates directory structure
mkdir -p build/artifacts
echo "Environment prepared"
ret status:string=ready
\```

# action: build-artifacts

```python
# Python action uses the prepared environment
mdl.dep("action.prepare-env")

import pathlib
import shutil

project_root = mdl.sys["project-root"]
build_dir = pathlib.Path(project_root) / "build" / "artifacts"

# Create multiple artifacts
for i in range(3):
    artifact_file = build_dir / f"artifact-{i}.txt"
    artifact_file.write_text(f"Artifact {i} content")

mdl.ret("artifacts-dir", str(build_dir), "directory")
mdl.ret("count", 3, "int")
\```

Expansion Syntax

Bash Actions

Bash actions use ${...} expansion syntax:

  • ${sys.project-root}: Project root directory
  • ${env.VARIABLE_NAME}: Environment variable
  • ${args.argument-name}: Command-line argument
  • ${flags.flag-name}: Command-line flag (1 if present, 0 otherwise)
  • ${action.action-name.variable-name}: Output from another action

Python Actions

Python actions use the mdl object (see Python Actions section for details)

Return Types

  • int: Integer value
  • string: String value
  • bool: Boolean (0 or 1)
  • file: File path (validated for existence)
  • directory: Directory path (validated for existence)

Command-Line Usage

Basic Usage

# Execute goals (runs in parallel by default)
mdl :goal1 :goal2

# With arguments
mdl --arg-name=value :goal

# With flags
mdl --flag-name :goal

# List available actions
mdl --list-actions

Multi-Context Execution

# Run same action with different axis values (multi-context)
mdl :build --axis build-mode:development :build --axis build-mode:release

# Set global axis for all actions
mdl --axis build-mode:release :build :test

# Per-action arguments
mdl :process --input=file1.txt :process --input=file2.txt

# Mix global and per-action settings
mdl --verbose :compile --axis opt-level:O3 :compile --axis opt-level:O0

Execution Control

# Dry run (show plan without executing)
mdl --dry-run :goal

# Sequential execution (disable parallelism)
mdl --seq :goal

# Continue from previous run (checkpoint recovery)
mdl --continue :goal

# Keep run directory for inspection
mdl --keep-run-dir :goal

Output and Logging

# Verbose mode (show commands)
mdl --verbose :goal

# Simple log mode (no rich tables)
mdl --simple-log :goal

# Save output to file
mdl --out results.json :goal

Platform Options

# Run without Nix (Windows or when Nix unavailable)
mdl --without-nix :goal

# GitHub Actions mode (optimized for CI)
mdl --github-actions :goal

Testing

Mudyla uses pytest for comprehensive testing with unit and integration tests.

# Run all tests
./run-tests.sh

# Run only integration tests (tests the built Nix package)
./run-tests.sh integration

# Run only unit tests
./run-tests.sh unit

# Run tests in parallel
./run-tests.sh --parallel

# Generate HTML report
./run-tests.sh --html

# Verbose output
./run-tests.sh --verbose

See TESTING.md for detailed testing documentation, including:

  • Writing new tests
  • Using fixtures and assertions
  • Running specific tests
  • Parallel execution with file locking
  • GitHub Actions integration
  • Debugging and coverage

The test suite includes:

  • Unit tests (20 tests): Test individual components without subprocess calls
  • Integration tests (28 tests): Test the full CLI by running the built Nix package
  • Parallel execution: Tests run concurrently with file locking for isolation
  • GitHub Actions integration: Test results published to Checks tab with JUnit XML reports

CI/CD Pipeline

The project includes a complete CI/CD pipeline:

  • Automated testing: Runs on every push and PR with parallel test execution
  • Cross-platform support: Tests on Linux and Windows
  • Type checking: Optional mypy type checking
  • Test reporting: Results visible in GitHub Checks tab
  • Automated publishing: PyPI releases on version tags using trusted publishing
  • GitHub Releases: Automatic release creation with artifacts

Documentation

Releases

Mudyla uses semantic versioning and automated releases:

  • PyPI: Published automatically on version tags (e.g., v0.1.0)
  • GitHub Releases: Created with distribution artifacts
  • Installation: Always available via pip install mudyla or pipx install mudyla

To create a new release, push a version tag:

git tag v0.1.0
git push origin v0.1.0

The CI/CD pipeline will automatically:

  1. Run all tests on Linux and Windows
  2. Build distribution packages (wheel and sdist)
  3. Publish to PyPI using trusted publishing (no tokens needed)
  4. Create a GitHub Release with artifacts

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mudyla-0.2.0.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mudyla-0.2.0-py3-none-any.whl (12.6 kB view details)

Uploaded Python 3

File details

Details for the file mudyla-0.2.0.tar.gz.

File metadata

  • Download URL: mudyla-0.2.0.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mudyla-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c2e24aeeec2ff17b6caf667d347a254f266f3aa1f5820ad326ff0123aefc4158
MD5 aab923d1e79240d004d3396d3ede57a6
BLAKE2b-256 48cf8ebcdf6ae0643fa4ecbfe7004871b7127f8821459d417010b4fc55330c1e

See more details on using hashes here.

Provenance

The following attestation bundles were made for mudyla-0.2.0.tar.gz:

Publisher: ci.yml on 7mind/mudyla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mudyla-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: mudyla-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 12.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mudyla-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 cbc3ec43aaf434e89d8d380785dea1f92c510a065dfb223c8dab6021990a95eb
MD5 ad6b01672675b6b7c9a989e46e703499
BLAKE2b-256 eb63d3d98d69aef18d1bec785350c380b50328e83b167e71592e7aa7bf4772ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for mudyla-0.2.0-py3-none-any.whl:

Publisher: ci.yml on 7mind/mudyla

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page