Clone a repo and tasks just work - no dependency installs, no global tools, no configuration. A truly zero-setup task runner for Windows, macOS, and Linux.
- Zero setup for contributors - Clone the repo, run tasks immediately
- Cross-platform - First-class support for Windows, macOS, and Linux
- Self-contained - All binaries and wrappers checked into the repo
- Simple - Easy to understand, easy to use
- Composable - Tasks can delegate to other tasks, enabling monorepo workflows
| Attribute | Value |
|---|---|
| Name | rnr (pronounced "runner") |
| Language | Rust |
| Task file | rnr.yaml |
| License | TBD |
When a repo is initialized with rnr, it contains:
project/
├── .rnr/
│ ├── config.yaml # Platform configuration
│ └── bin/
│ ├── rnr-linux-amd64 # Linux x86_64 binary
│ ├── rnr-macos-amd64 # macOS x86_64 binary
│ ├── rnr-macos-arm64 # macOS ARM64 binary
│ ├── rnr-windows-amd64.exe # Windows x86_64 binary
│ └── rnr-windows-arm64.exe # Windows ARM64 binary
├── rnr # Shell wrapper script (Unix)
├── rnr.cmd # Batch wrapper script (Windows)
├── rnr.yaml # Task definitions
└── ... (rest of project)
Note: Only the selected platforms are included. Most projects only need 2-3 platforms.
.rnr/config.yaml tracks which platforms are configured:
version: "0.1.0"
platforms:
- linux-amd64
- macos-arm64
- windows-amd64rnr (Unix shell script):
#!/bin/sh
set -e
# Detect OS
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
EXT=""
case "$OS" in
linux*) OS="linux" ;;
darwin*) OS="macos" ;;
mingw*|msys*|cygwin*) OS="windows"; EXT=".exe" ;;
*) echo "Error: Unsupported OS: $OS" >&2; exit 1 ;;
esac
# Detect architecture
ARCH=$(uname -m)
case "$ARCH" in
x86_64|amd64) ARCH="amd64" ;;
arm64|aarch64) ARCH="arm64" ;;
*) echo "Error: Unsupported architecture: $ARCH" >&2; exit 1 ;;
esac
BINARY="$(dirname "$0")/.rnr/bin/rnr-${OS}-${ARCH}${EXT}"
if [ ! -f "$BINARY" ]; then
echo "Error: rnr is not configured for ${OS}-${ARCH}." >&2
echo "Run 'rnr init --add-platform ${OS}-${ARCH}' to add support." >&2
exit 1
fi
exec "$BINARY" "$@"rnr.cmd (Windows batch script):
@echo off
setlocal
:: Detect architecture
if "%PROCESSOR_ARCHITECTURE%"=="ARM64" (
set "ARCH=arm64"
) else (
set "ARCH=amd64"
)
set "BINARY=%~dp0.rnr\bin\rnr-windows-%ARCH%.exe"
if not exist "%BINARY%" (
echo Error: rnr is not configured for windows-%ARCH%. >&2
echo Run 'rnr init --add-platform windows-%ARCH%' to add support. >&2
exit /b 1
)
"%BINARY%" %*# macOS/Linux
curl -sSL https://rnr.dev/rnr -o rnr && chmod +x rnr && ./rnr init
# Windows (PowerShell)
irm https://rnr.dev/rnr.exe -OutFile rnr.exe; .\rnr.exe initWhen running rnr init, an interactive prompt lets you choose which platforms to support:
Initializing rnr...
Which platforms should this project support?
(Current platform is pre-selected)
[x] linux-amd64 (760 KB)
[ ] macos-amd64 (662 KB)
[x] macos-arm64 (608 KB) <- current
[x] windows-amd64 (584 KB)
[ ] windows-arm64 (528 KB)
Selected: 1.95 MB total
[Enter] Confirm [Space] Toggle [a] All [n] None [Esc] Cancel
For CI/CD or scripting, use flags:
# Specify exact platforms
rnr init --platforms linux-amd64,macos-arm64,windows-amd64
# Include all platforms
rnr init --all-platforms
# Current platform only
rnr init --current-platform-onlyThe init command:
- Detects current platform and pre-selects it
- Shows interactive platform selection (unless flags provided)
- Creates
.rnr/bin/directory - Downloads selected platform binaries from GitHub Releases
- Creates
.rnr/config.yamlwith selected platforms - Creates wrapper scripts (
rnr,rnr.cmd) - Creates starter
rnr.yamlif one doesn't exist - Cleans up the initial downloaded binary
# Add a platform
rnr init --add-platform windows-arm64
# Remove a platform
rnr init --remove-platform linux-amd64
# Show current platforms
rnr init --show-platformsgit clone <repo>
./rnr build # macOS/Linux
rnr build # Windows./rnr upgrade # Updates binaries in .rnr/bin/Always run from project root. Tasks can operate on subdirectories via the dir: property or namespaced task names.
./rnr build # Run 'build' task
./rnr web:build # Run namespaced 'web:build' task
./rnr api:test # Run namespaced 'api:test' taskFor simple commands, use the shorthand form:
build: cargo build --release
lint: npm run lint
test: cargo testtask-name:
description: Human-readable description for --list
dir: <working directory, relative to project root>
env:
KEY: value
cmd: <shell command>
# OR
task: <another rnr task name>
# OR
steps:
- cmd: <command>
- task: <task>
- parallel:
- cmd: <command>
- task: <task>| Property | Type | Description |
|---|---|---|
description |
string | Human-readable description shown in --list |
dir |
string | Working directory (relative to project root) |
env |
map | Environment variables for this task |
cmd |
string | Shell command to execute |
task |
string | Another rnr task to run (can be in a subdirectory's rnr.yaml) |
steps |
array | Sequential list of commands/tasks |
parallel |
array | Steps to run in parallel (used within steps) |
# Simple commands (shorthand)
lint: npm run lint
format: npm run format
# Task with description
test:
description: Run all tests
cmd: cargo test
# Task with working directory
build-api:
description: Build the API service
dir: services/api
cmd: cargo build --release
# Task with environment variables
build-web:
description: Build web frontend for production
dir: services/web
env:
NODE_ENV: production
cmd: npm run build
# Delegate to nested task file
api:test:
dir: services/api
task: test # runs 'test' from services/api/rnr.yaml
# Sequential steps
ci:
description: Run full CI pipeline
steps:
- task: lint
- task: test
- task: build-all
# Parallel execution
build-all:
description: Build all services in parallel
steps:
- cmd: echo "Starting builds..."
- parallel:
- task: build-api
- task: build-web
- cmd: echo "All builds complete"
# Complex workflow
deploy:
description: Deploy to production
steps:
- task: ci
- parallel:
- dir: services/api
cmd: ./deploy.sh
- dir: services/web
cmd: ./deploy.sh
- cmd: echo "Deployment complete"Subdirectories can have their own rnr.yaml for organization:
project/
├── rnr.yaml # Root task file
└── services/
├── api/
│ └── rnr.yaml # API-specific tasks
└── web/
└── rnr.yaml # Web-specific tasks
services/api/rnr.yaml:
build: cargo build --release
test: cargo test
clean: cargo cleanRoot rnr.yaml delegates:
api:build:
dir: services/api
task: build
api:test:
dir: services/api
task: test| Command | Description |
|---|---|
rnr init |
Initialize the current directory with rnr |
rnr upgrade |
Update rnr binaries to latest version |
rnr --list |
List all available tasks with descriptions |
rnr --help |
Show help information |
rnr --version |
Show rnr version |
rnr <task> |
Run the specified task |
Creates the full rnr setup in the current directory:
rnr init [OPTIONS]
Options:
--platforms <list> Comma-separated list of platforms (non-interactive)
--all-platforms Include all available platforms
--current-platform-only Only include the current platform
--add-platform <name> Add a platform to existing setup
--remove-platform <name> Remove a platform from existing setup
--show-platforms Show currently configured platforms
Creates:
.rnr/bin/with selected platform binaries.rnr/config.yamltracking configured platformsrnrandrnr.cmdwrapper scripts- Starter
rnr.yamlwith example tasks (if not exists)
Downloads the latest rnr binaries for configured platforms and replaces those in .rnr/bin/. Reads .rnr/config.yaml to know which platforms to update.
Displays all available tasks with their descriptions:
$ ./rnr --list
Available tasks:
build-all Build all services in parallel
build-api Build the API service
build-web Build web frontend for production
ci Run full CI pipeline
deploy Deploy to production
format
lint
test Run all tests
- Parse
rnr.yamltask files - Run shell commands (
cmd) - Set working directory (
dir) - Environment variables (
env) - Task descriptions (
description) - Sequential steps (
steps) - Parallel execution (
parallel) - Delegate to other tasks (
task) - Delegate to nested task files (
dir+task) - Shorthand syntax (
task: command)
-
rnr init- Initialize repo with platform selection -
rnr upgrade- Update binaries for configured platforms -
rnr --list- List tasks -
rnr --help- Show help -
rnr --version- Show version
- Build for Linux (x86_64)
- Build for macOS (x86_64, arm64)
- Build for Windows (x86_64, arm64)
- Shell wrapper script generation
- Batch wrapper script generation
- Host binaries on GitHub Releases
- Init downloads selected platform binaries
- Upgrade fetches latest binaries for configured platforms
- Platform selection (interactive and non-interactive)
Run prerequisite tasks before a task executes:
deploy:
depends: [build, test]
cmd: ./deploy.shRun tasks conditionally based on environment or conditions:
deploy:
if: ${{ env.CI == 'true' }}
cmd: ./deploy.sh
test:
when:
- file_exists: package.json
cmd: npm testAutomatically re-run tasks when files change:
dev:
watch:
paths: [src/**/*.rs]
ignore: [target/]
cmd: cargo buildUse variables in task definitions:
vars:
version: "1.0.0"
build:
cmd: cargo build --version ${{ vars.version }}Import tasks from other files:
include:
- .rnr/common.yaml
- .rnr/deploy.yamlAutomatically load .env files:
build:
dotenv: .env.production
cmd: npm run buildOr global setting:
config:
dotenv: true # Auto-load .env if present
tasks:
build:
cmd: npm run buildSkip tasks if inputs haven't changed:
build:
inputs:
- src/**/*.rs
- Cargo.toml
outputs:
- target/release/myapp
cmd: cargo build --releasePass arguments to tasks:
greet:
args:
- name: name
required: true
cmd: echo "Hello, ${{ args.name }}"./rnr greet --name=WorldRun commands before/after tasks:
build:
before: echo "Starting build"
cmd: cargo build
after: echo "Build complete"Run a default task when no task is specified:
default: build
build:
cmd: cargo build./rnr # Runs 'build'When run without arguments, show interactive task picker:
./rnr
# Shows interactive list of tasks to choose from- Compiles to small, static binaries
- Cross-compilation is well-supported
- No runtime dependencies
- Memory safe
- Strong ecosystem for CLI tools (clap, serde, etc.)
Current binary sizes per platform:
| Platform | Size |
|---|---|
| Linux x86_64 | ~760 KB |
| macOS x86_64 | ~662 KB |
| macOS ARM64 | ~608 KB |
| Windows x86_64 | ~584 KB |
| Windows ARM64 | ~528 KB |
| All platforms | ~3.1 MB |
Size optimizations applied:
opt-level = "z"for size optimization- LTO (Link-Time Optimization)
- Strip symbols
panic = "abort"
Future size reduction options:
- Replace
reqwestwithureq(smaller HTTP client) - Remove
tokioif not needed for async - Use smaller YAML parser
- Unix: Execute commands via
sh -c "<command>" - Windows: Execute commands via
cmd /c "<command>"or PowerShell
- Use Rust async or threads for parallel task execution
- Capture and interleave output appropriately
- Handle failures (fail-fast vs. continue)
- Clear error messages with context
- Exit codes: 0 for success, non-zero for failure
- Propagate exit codes from executed commands
-
Task name restrictions - What characters are allowed in task names? Alphanumeric + hyphen + colon?
-
Parallel failure behavior - Fail fast or wait for all? Configurable?
-
Output handling - How to handle output from parallel tasks? Interleave, buffer, or prefix?
-
Shell selection - Allow users to specify shell? (bash, zsh, fish, PowerShell)
-
Binary hosting - Where to host binaries? GitHub Releases? Custom CDN?
-
Versioning - How to handle version pinning? Lock file?
| Tool | Requires Install | Cross-Platform | Checked into Repo |
|---|---|---|---|
| npm scripts | Node.js | Yes | No (needs Node) |
| Make | Make | Partial (Windows pain) | No |
| Just | Just | Yes | No |
| Task (taskfile.dev) | Task | Yes | No |
| rnr | Nothing | Yes | Yes |