review.claude.md # Run with Claude
commit.gemini.md "fix auth bug" # Run with Gemini
git diff | explain.claude.md # Pipe through any commandYour markdown files are now executable AI agents.
Markdown files become first-class CLI commands. Write a prompt in markdown, run it like a script. The command is inferred from the filename.
# review.claude.md
---
model: opus
---
Review this code for bugs and suggest improvements.
@./src/**/*.tsreview.claude.md # Runs: claude --model opus <prompt>
review.claude.md --verbose # Pass extra flagsName your file task.COMMAND.md and the command is inferred:
task.claude.md # Runs claude
task.gemini.md # Runs gemini
task.codex.md # Runs codex
task.copilot.md # Runs copilot (print mode by default)Every YAML key becomes a CLI flag passed to the command:
---
model: opus # → --model opus
dangerously-skip-permissions: true # → --dangerously-skip-permissions
mcp-config: ./mcp.json # → --mcp-config ./mcp.json
add-dir: # → --add-dir ./src --add-dir ./tests
- ./src
- ./tests
---The markdown body is passed as the final argument to the command.
mdflow embraces the Unix philosophy:
- No magic mapping - Frontmatter keys pass directly to the command
- Stdin/stdout - Pipe data in and out
- Composable - Chain agents together
- Transparent - See what runs in logs
# Pipe input
git diff | mdflow review.claude.md
# Chain agents
mdflow plan.claude.md | mdflow implement.codex.mdnpm install -g mdflow
# or
bun install && bun link# Run with filename-inferred command
mdflow task.claude.md
mdflow task.gemini.md
# Override command via --command flag
mdflow task.md --command claude
mdflow task.md -c gemini
# Pass additional flags to the command
mdflow task.claude.md --verbose --debugNote: Both
mdflowandmdcommands are available.
Commands are resolved in this priority order:
- CLI flag:
--command claudeor-c claude - Filename pattern:
task.claude.md→claude
If no command can be resolved, you'll get an error with instructions.
Some CLI flags are "hijacked" by mdflow—they're consumed and never passed to the underlying command. This allows generic markdown files without command names to be executed.
Override the command for any markdown file:
# Run a generic .md file with any command
mdflow task.md --command claude
mdflow task.md -c gemini
# Override the filename-inferred command
mdflow task.claude.md --command gemini # Runs gemini, not claudeFrontmatter fields starting with _ (except internal keys like _interactive, _cwd, _subcommand) define template variables:
---
_feature_name: Authentication # Default value
_target_dir: src/features # Default value
---
Build {{ _feature_name }} in {{ _target_dir }}.# Use defaults
mdflow create.claude.md
# Override with CLI flags (consumed by mdflow, not passed to command)
mdflow create.claude.md --_feature_name "Payments" --_target_dir "src/billing"The --_feature_name and --_target_dir flags are consumed by mdflow for template substitution—they won't be passed to the command.
No frontmatter declaration required: You can pass --_varname flags without declaring them in frontmatter. If the variable is used in the body but not provided, you'll be prompted for it:
---
print: true
---
{% if _verbose == "yes" %}Detailed analysis:{% endif %}
Review this code: {{ _target }}mdflow review.claude.md --_verbose yes --_target "./src"CLI positional arguments are available as {{ _1 }}, {{ _2 }}, etc.:
---
print: true
---
Translate "{{ _1 }}" to {{ _2 }}.mdflow translate.claude.md "hello world" "French"
# → Translate "hello world" to French.Use {{ _args }} to get all positional args as a numbered list:
---
print: true
---
Process these items:
{{ _args }}mdflow process.claude.md "apple" "banana" "cherry"
# → Process these items:
# → 1. apple
# → 2. banana
# → 3. cherryWhen you pipe content to mdflow, it's available as the _stdin template variable:
---
model: haiku
---
Summarize this: {{ _stdin }}cat README.md | mdflow summarize.claude.md| Field | Type | Description |
|---|---|---|
_varname |
string | Template variable with default value (use {{ _varname }} in body) |
env |
object | Set process environment variables |
env |
string[] | Pass as --env flags to command |
$1, $2... |
string | Map positional args to flags (e.g., $1: prompt) |
_interactive / _i |
boolean | Enable interactive mode (overrides print-mode defaults) |
_subcommand |
string/string[] | Prepend subcommand(s) to CLI args |
_cwd |
string | Override working directory for inline commands |
| Variable | Description |
|---|---|
{{ _stdin }} |
Content piped to mdflow |
{{ _1 }}, {{ _2 }}... |
Positional CLI arguments |
{{ _args }} |
All positional args as numbered list (1. arg1, 2. arg2, ...) |
Every other frontmatter key is passed directly to the command:
---
model: opus # → --model opus
dangerously-skip-permissions: true # → --dangerously-skip-permissions
mcp-config: ./mcp.json # → --mcp-config ./mcp.json
p: true # → -p (single char = short flag)
---Value conversion:
key: "value"→--key valuekey: true→--keykey: false→ (omitted)key: [a, b]→--key a --key b
All commands run in print mode by default (non-interactive, exit after completion). Use the .i. filename marker, _interactive frontmatter, or CLI flags to enable interactive mode.
task.claude.md # Runs: claude --print "..."
task.copilot.md # Runs: copilot --silent --prompt "..."
task.codex.md # Runs: codex exec "..."
task.gemini.md # Runs: gemini "..." (one-shot)Add .i. before the command name in the filename:
task.i.claude.md # Runs: claude "..." (interactive session)
task.i.copilot.md # Runs: copilot --silent --interactive "..."
task.i.codex.md # Runs: codex "..." (interactive session)
task.i.gemini.md # Runs: gemini --prompt-interactive "..."Or use _interactive (or _i) in frontmatter:
---
_interactive: true # or _interactive: (empty), or _i:
model: opus
---
Review this code with me interactively.Or use CLI flags:
mdflow task.claude.md --_interactive # Enable interactive mode
mdflow task.claude.md -_i # Short formSet default frontmatter per command in ~/.mdflow/config.yaml:
commands:
claude:
model: sonnet # Default model for claude
copilot:
silent: true # Always use --silent for copilotBuilt-in defaults: All commands default to print mode with appropriate flags per CLI tool.
# db.claude.md
---
model: opus
mcp-config: ./postgres-mcp.json
dangerously-skip-permissions: true
---
Analyze the database schema and suggest optimizations.# refactor.gemini.md
---
model: gemini-3-pro-preview
yolo: true
---
Refactor the authentication module to use async/await.# analyze.codex.md
---
model: o3
sandbox: workspace-write
full-auto: true
---
Analyze this codebase and suggest improvements.# task.copilot.md
Explain this code.This runs: copilot --silent --prompt "Explain this code." (print mode)
For interactive mode, use .i. in the filename:
# task.i.copilot.md
Explain this code.This runs: copilot --silent --interactive "Explain this code."
# create-feature.claude.md
---
_feature_name: ""
_target_dir: src/features
model: sonnet
---
Create a new feature called "{{ _feature_name }}" in {{ _target_dir }}.mdflow create-feature.claude.md --_feature_name "Auth"# api-test.claude.md
---
env:
API_URL: https://api.example.com
DEBUG: "true"
---
Test the API at !`echo $API_URL`Inline content from other files or command output directly in your prompts.
Use @ followed by a path to inline file contents:
---
model: claude
---
Follow these coding standards:
@~/.config/coding-standards.md
Now review this code:
@./src/api.ts@~/path- Expands~to home directory@./path- Relative to current markdown file@/path- Absolute path
Imports are recursive—imported files can have their own @ imports.
Use glob patterns to include multiple files at once:
Review all TypeScript files in src:
@./src/**/*.tsGlob imports:
- Respect
.gitignoreautomatically - Include common exclusions (
node_modules,.git, etc.) - Are limited to ~100,000 tokens by default
- Set
MDFLOW_FORCE_CONTEXT=1to override the token limit
Files are formatted as XML with path attributes:
<api path="src/api.ts">
...file content...
</api>
<utils path="src/utils.ts">
...file content...
</utils>Extract specific lines from a file:
@./src/api.ts:10-50This imports only lines 10-50 from the file.
Extract specific TypeScript/JavaScript symbols (interfaces, types, functions, classes, etc.):
@./src/types.ts#UserInterface
@./src/api.ts#fetchUserSupported symbols:
interface Name { ... }type Name = ...function Name(...) { ... }class Name { ... }const/let/var Name = ...enum Name { ... }
Use !`command` to execute a shell command and inline its output:
Current branch: !`git branch --show-current`
Recent commits:
!`git log --oneline -5`
Based on the above, suggest what to work on next.Fetch content from URLs (markdown and JSON only):
@https://raw.githubusercontent.com/user/repo/main/README.mdCaching: Remote URLs are cached locally at ~/.mdflow/cache/ with a 1-hour TTL. Use --no-cache to force a fresh fetch:
mdflow agent.claude.md --no-cachemdflow automatically loads .env files from the markdown file's directory.
Files are loaded in order (later files override earlier):
.env- Base environment.env.local- Local overrides (not committed).env.development/.env.production- Environment-specific.env.development.local/.env.production.local- Environment-specific local
my-agents/
├── .env # API_KEY=default
├── .env.local # API_KEY=my-secret (gitignored)
└── review.claude.md
Environment variables are available:
- In command inlines:
!`echo $API_KEY` - In the spawned command's environment
Usage: mdflow <file.md> [any flags for the command]
mdflow <file.md> --command <cmd>
mdflow --setup
mdflow --logs
mdflow --help
Command resolution:
1. --command flag (e.g., mdflow task.md --command claude)
2. Filename pattern (e.g., task.claude.md → claude)
All frontmatter keys are passed as CLI flags to the command.
Global defaults can be set in ~/.mdflow/config.yaml
mdflow-specific flags (consumed, not passed to command):
--command, -c Specify command to run
--dry-run Preview without executing
--_interactive, -_i Enable interactive mode
--no-cache Force fresh fetch for remote URLs (bypass cache)
--trust Bypass TOFU prompts for remote URLs
Examples:
mdflow task.claude.md -p "print mode"
mdflow task.claude.md --model opus --verbose
mdflow commit.gemini.md
mdflow task.md --command claude
mdflow task.md -c gemini
mdflow task.claude.md -_i # Run in interactive mode
Without a file:
mdflow --setup Configure shell to run .md files directly
mdflow --logs Show log directory
mdflow --help Show this help
| Variable | Description |
|---|---|
MDFLOW_FORCE_CONTEXT |
Set to 1 to disable the 100k token limit for glob imports |
NODE_ENV |
Controls which .env.[NODE_ENV] file is loaded (default: development) |
Make .md files directly executable:
mdflow --setup # One-time setupThen run agents directly:
task.claude.md # Just type the filename
task.claude.md --verbose # With passthrough argsAdd to ~/.zshrc:
alias -s md='mdflow'
export PATH="$HOME/agents:$PATH" # Your agent libraryCreate a directory of agents and add it to PATH:
~/agents/
├── review.claude.md # Code review
├── commit.gemini.md # Commit messages
├── explain.claude.md # Code explainer
├── test.codex.md # Test generator
└── debug.claude.md # Debugging helper
export PATH="$HOME/agents:$PATH"Now use them from anywhere:
review.claude.md # Review current directory
commit.gemini.md "add auth" # Generate commit message
git diff | review.claude.md # Review staged changes- If no frontmatter is present, the file is printed as-is (unless command inferred from filename)
- Template system uses LiquidJS - supports conditionals, loops, and filters
- Logs are always written to
~/.mdflow/logs/<agent-name>/for debugging - Use
--logsto show the log directory - Piped input is available as
{{ _stdin }}template variable - Template variables use
_prefix:_namein frontmatter →{{ _name }}in body →--_nameCLI flag - Remote URLs are cached at
~/.mdflow/cache/with 1-hour TTL (use--no-cacheto bypass) - Imports inside code blocks (``` or `) are ignored by the parser