Multimodal Dynamic Launcher - Shell script orchestrator
Project description
Mudyla - Multimodal Dynamic Launcher
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
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
--continueflag - 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 contextbuild-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 valuestring: String valuebool: 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 mudylaorpipx 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:
- Run all tests on Linux and Windows
- Build distribution packages (wheel and sdist)
- Publish to PyPI using trusted publishing (no tokens needed)
- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2e24aeeec2ff17b6caf667d347a254f266f3aa1f5820ad326ff0123aefc4158
|
|
| MD5 |
aab923d1e79240d004d3396d3ede57a6
|
|
| BLAKE2b-256 |
48cf8ebcdf6ae0643fa4ecbfe7004871b7127f8821459d417010b4fc55330c1e
|
Provenance
The following attestation bundles were made for mudyla-0.2.0.tar.gz:
Publisher:
ci.yml on 7mind/mudyla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mudyla-0.2.0.tar.gz -
Subject digest:
c2e24aeeec2ff17b6caf667d347a254f266f3aa1f5820ad326ff0123aefc4158 - Sigstore transparency entry: 721416774
- Sigstore integration time:
-
Permalink:
7mind/mudyla@05b6e6ea96be10e89fe3b1b212d5ef62f2b094a3 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/7mind
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@05b6e6ea96be10e89fe3b1b212d5ef62f2b094a3 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cbc3ec43aaf434e89d8d380785dea1f92c510a065dfb223c8dab6021990a95eb
|
|
| MD5 |
ad6b01672675b6b7c9a989e46e703499
|
|
| BLAKE2b-256 |
eb63d3d98d69aef18d1bec785350c380b50328e83b167e71592e7aa7bf4772ed
|
Provenance
The following attestation bundles were made for mudyla-0.2.0-py3-none-any.whl:
Publisher:
ci.yml on 7mind/mudyla
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mudyla-0.2.0-py3-none-any.whl -
Subject digest:
cbc3ec43aaf434e89d8d380785dea1f92c510a065dfb223c8dab6021990a95eb - Sigstore transparency entry: 721416784
- Sigstore integration time:
-
Permalink:
7mind/mudyla@05b6e6ea96be10e89fe3b1b212d5ef62f2b094a3 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/7mind
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@05b6e6ea96be10e89fe3b1b212d5ef62f2b094a3 -
Trigger Event:
push
-
Statement type: