A lightweight task runner for defining and executing shell commands with a clean, readable syntax. Define functions in a Runfile (or ~/.runfile) and call them from the command line to streamline your development workflow.
It hits a common sweet spot — lightweight, readable, and shell-native for quick CLI automation without the overhead of heavier task systems.
- Familiar Syntax: Low onboarding cost for anyone comfortable with a shell.
- Block Functions (
{}): Clean multi-statement definitions without messy shell escaping. - Nested Names: Organize commands logically (e.g.,
db:up,db:down). - REPL: An interactive mode for rapid iterative development.
- Dual Scope: Global (
~/.runfile) and project-specific (./Runfile) logic.
brew tap nihilok/tap
brew install devrun
# OR
cargo install devrun# Via Scoop
scoop bucket add nihilok https://github.com/nihilok/scoop-bucket
scoop install devrun
# OR via Cargo
cargo install devrunrun --install-completion # Auto-detects bash/zsh/fishIf you are coming from other tools, here is how run compares and how to translate your existing workflows.
| Feature | make |
just |
run |
|---|---|---|---|
| Primary Goal | Build system / Dependencies | Command runner | Shell-native task orchestrator |
| Syntax | Tab-indented Makefile | Custom DSL | Bash-like functions |
| Namespacing | None (flat) | Limited | Native (using : to space) |
| Interactive | No | No | Yes (REPL mode) |
In make, you use target: dependencies \n \t command. In run, you focus on the action: build() cargo build. No more tab-indentation errors or .PHONY declarations.
In just, recipes are defined like recipe: \n command. In run, you get native shell blocks: task() { cmd1; cmd2; }. Additionally, run handles nested namespaces more gracefully: run docker build maps directly to docker:build().
Create a Runfile in your project root:
# Simple one-liners
build() cargo build --release
test() cargo test
# Multi-statement blocks
ci() {
echo "Running CI pipeline..."
cargo fmt -- --check
cargo clippy
cargo test
echo "All checks passed!"
}
# Namespaced commands with arguments
docker:shell() docker compose exec $1 bash
git:commit() git add . && git commit -m "$1" && echo "${2:-Done}"
Execute commands:
run build
run docker shell web
run git commit "Initial commit"- The "Colon" Shortcut: If you define
web:deploy(), you can run it asrun web:deployORrun web deploy. The latter makes your CLI feel like a first-class tool. - Default Arguments: Use
${1:-default_value}to make arguments optional. - Global Quality of Life: Put your most-used utility commands in
~/.runfile. They will be available in every directory. - The REPL for Debugging: If you are building a complex chain of commands, just type
runto enter the REPL. Test your functions without restarting the process. - Shell Overrides: Use the
RUN_SHELLenvironment variable to switch engines (e.g.,RUN_SHELL=zsh run task).
We welcome contributions! Here is what is currently on the horizon for run:
- Task Dependencies: Internal function calling (e.g.,
deploy()automatically triggersbuild()). .envSupport: Automatic loading of environment variables from a local.envfile.- Watch Mode: A built-in
--watchflag to trigger functions on file system changes. - Private Functions: Support for "hidden" tasks (e.g.,
_setup()) that don't appear in the--listview.
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.
MIT