Run Markdown code blocks interactively in your terminal.
upmd runs code blocks directly from Markdown in a real terminal, keeping commands, context, and output together.
- Opens a file, reads from stdin, or lets you browse Markdown files in a directory.
- Renders the document and keeps its code blocks, output, and terminal sessions together.
- Runs one block or a whole document from the TUI, the lightweight CLI, or CI.
- Carries exported shell variables and working-directory changes into the next block. Python, Go, Rust, and TypeScript can opt into experimental state capture.
- Lets you change runners, arguments, environment variables, themes, transparency, and key bindings.
Shell installer (macOS and Linux)
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/rezigned/upmd/releases/latest/download/upmd-installer.sh | shHomebrew (macOS)
brew install rezigned/tap/upmdWindows and manual downloads: Download an archive and checksum from the latest release. Prebuilt binaries are available for macOS (Apple Silicon and Intel), Linux (x86_64 and ARM64), and Windows (x86_64).
From source — requires Rust 1.82 or newer:
git clone https://github.com/rezigned/upmd.git
cd upmd
cargo install --path .# Browse Markdown files under the current directory
upmd
# Browse Markdown files under another directory
upmd ./docs
# Open one file in the TUI
upmd README.md
# Read Markdown from stdin
curl -s https://raw.githubusercontent.com/rezigned/upmd/main/README.md | upmd
# Open the lightweight CLI
upmd --cli README.md
# Execute every block without prompts
upmd --cli --all --yes DEMO.md
# Select a named or numbered block
upmd README.md --block setup
upmd README.md -b 3
# Select and immediately execute one block
upmd README.md --block setup --yesWhen stdin is an interactive terminal and no file is supplied, upmd opens the current-directory file picker. Piped stdin remains Markdown input.
Usage: upmd [OPTIONS] [FILE]
Arguments:
[FILE] Markdown file or directory. Omit to browse the current directory.
Options:
--all Auto-advance through all code blocks
--cli Use the lightweight CLI instead of the full-screen TUI
-d, --working-dir <DIR> Working directory for code execution
-y, --yes Run without confirmation; use with --all or --block
--theme <THEME> Syntax-highlight theme
--capture-state Enable experimental non-shell state capture
-b, --block <NAME|ID> Select a named or numbered code block
--dump-default-config Print every default config key and exit
--transparent Use the terminal's default background
--tick-rate <MS> UI refresh interval in milliseconds
-h, --help Print help
-V, --version Print version
Run upmd --help for the authoritative help generated by the installed version.
Run upmd with a directory to browse its Markdown files. With no arguments, an interactive terminal starts the picker in the current directory; piped stdin is treated as Markdown instead.
The picker searches recursively, filters as you type, and shows a bounded syntax-highlighted preview. An unreadable entry does not stop the scan. In the TUI, press f to reopen the picker beside the active document.
Press ? inside upmd for searchable help. For the full default keymap, see src/apps/config.toml.
| Key | Context | Action |
|---|---|---|
up / k, down / j |
Menu | Move between blocks |
Enter |
Home or CLI | Run selected block |
/ |
Home | Search document text |
ctrl-g |
Home | Filter and jump to a block |
o |
Home | Open full output view |
i |
Home | Enter input mode for the selected running block |
ctrl-o |
Input or output | Return to Home |
z |
Home | Toggle zen mode |
c |
Home | Toggle table of contents |
? |
Home | Open keymap help |
q / ctrl-c |
Home or CLI | Quit |
Press o on the selected block to open its terminal screen and scrollback.
- Wheel, click, release, and drag events are forwarded when the running application enables SGR mouse reporting.
- Otherwise the wheel scrolls local output history and drag selection copies text on release.
yorYcopies completed output.ctrl-vpastes into a running process.Escreturns after execution is complete; while a program is running it remains available to that program.ctrl-oalways returns to Home.
# Interactive CLI navigation
upmd --cli DEMO.md
# Deterministic CI execution
upmd --cli --all --yes DEMO.md
# Execute one named block
upmd --cli DEMO.md --block verify --yesExit status is non-zero when execution cannot start or a selected block fails.
Attributes follow the language token in square brackets:
```bash [name:setup]
echo "setup"
```
```shell [name:portable bin:dash]
echo "using dash"
```name makes a block addressable from --block and the goto interface:
upmd runbook.md --block setup
upmd runbook.md -b setup --yesNumeric IDs are also accepted.
Executable settings resolve in this order:
- Code block attribute, such as
[bin:zsh] - User config, such as
[binaries.bash] - Runner default candidates
Shell languages automatically capture exported environment variables and the final working directory after each successful execution:
```bash [name:prepare]
export API_URL="http://127.0.0.1:8080"
cd /tmp
```
```bash [name:inspect]
printf 'API_URL=%s\n' "$API_URL"
pwd
```Python, Go, Rust, and TypeScript support experimental capture when --capture-state is supplied:
upmd --capture-state DEMO.mdUse e to inspect or edit the environment before running the next block.
| Language | Fence aliases | Default executables |
|---|---|---|
| Bash | bash |
bash |
| POSIX shell | sh, shell |
sh |
| Zsh | zsh |
zsh |
| Fish | fish |
fish |
| Cmd | cmd, bat, batch |
cmd |
| PowerShell | powershell, ps1, pwsh |
powershell, pwsh |
| Python | py, python, python3 |
python3, python |
| JavaScript | js, javascript, node |
node |
| TypeScript | ts, typescript |
node --experimental-strip-types, npx tsx, ts-node |
| Ruby | rb, ruby |
ruby |
| PHP | php |
php |
| C | c |
gcc, clang |
| Go | go, golang |
go |
| Rust | rs, rust |
rustc, cargo rustc -- |
| Zig | zig |
zig |
TypeScript tries Node.js native strip-types (22.6+), npx tsx, then ts-node. Rust uses rustc directly or cargo rustc -- when only Cargo is available. Override any runner with [bin:...] or binaries.<language> in the config.
Compiled runners use isolated temporary workspaces and remove them after execution.
Switch themes with t or choose one at startup:
upmd --theme tokyo-night README.mdCustom .tmTheme files under ~/.config/upmd/themes/ are discovered automatically.
Persistent settings live at ~/.config/upmd/config.toml:
theme = "catppuccin-mocha"
transparent = true
tick_rate = 66
[tui]
inline_max_lines = 20
[cli]
preview_lines = 8Override a runner globally:
[binaries.python]
bin = "python3.12"
extra_args = ["-X", "dev"]
env = { PYTHONUNBUFFERED = "1" }Override component keymaps:
[keymap.home.Execute]
keys = ["x"]
symbol = "x"
help = "run"
[keymap.menu.Next]
keys = ["down", "j"]Print the complete default configuration and every keymap section with:
upmd --dump-default-configSet RUST_LOG to enable file-based tracing. Logs are written to the platform cache directory, for example ~/.cache/upmd/upmd.log on Linux:
RUST_LOG=upmd=debug upmd README.mdThe root crate contains the TUI and CLI frontends. Four workspace crates keep the reusable pieces separate:
upmd-parserparses Markdown and fenced-code attributes.upmd-ptymanages PTY processes, terminal state, and mouse input.upmd-runnerchooses language runners and builds execution plans.upmd-runtimeprovides the component engine used by both frontends.
Both frontends share parsing, runner selection, PTY execution, and input resolution. Runner configuration resolves in this order: block attribute, user config, then built-in default.
MIT

