rune is a project-local task runner written in Go. It lets you define and run project-specific tasks from a Runefile, with support for arguments, boolean flags, task namespaces (db:migrate), and per-task help menus.
Like just, rune is a command runner rather than a build system — tasks run sequentially without file dependency graphs or .PHONY boilerplate.
rune build --race # pass boolean flags directly
rune db:migrate # run tasks organized by namespace
rune --dry-run release # inspect the execution plan without running commands
rune compose up --build # forward passthrough arguments to underlying toolsNote
rune searches for a Runefile starting from the current directory and traversing parent directories, so you can invoke tasks from any subdirectory within your project.
- Namespaced Tasks — Organize related tasks under namespaces (e.g.
rune db:migrate,rune db:seed). - Command-line Arguments, Options & Enums — Accept positional arguments, short/long flags (
-w|--watch?), valued options (--output="dist"), and enum choices (--env=[staging,production]). - Per-Task Help Menus — Auto-generated
--helpfor tasks and namespaces, with doc-comments for parameters. - Dependency Execution — Run prerequisite tasks with cycle detection and deduplication.
- Dependency Tree Inspection — Inspect execution graphs in Unicode box-drawing format with
--tree. - Private Tasks — Hide internal or helper tasks from discovery menus using
#[private]. - Interactive Safety Prompts — Guard destructive tasks with
#[confirm: ...]prompts before running. - Dry-run Mode — Preview the resolved execution order with
--dry-run. - Environment Integration — Automatically loads
.envfiles beside yourRunefileand sets task context variables. - Shell Completion — Tab completion scripts for Bash, Zsh, and Fish.
Install the latest pre-compiled binary into /usr/local/bin (or ~/.local/bin):
curl -fsSL https://raw.githubusercontent.com/octopyid/rune/main/install.sh | shbrew install octopyid/tap/runeDownload standalone binaries for Linux and macOS (amd64, arm64) directly from GitHub Releases.
go install github.com/octopyid/rune/cmd/rune@latestgit clone https://github.com/octopyid/rune.git
cd rune
go build -o ./bin/rune ./cmd/rune- Go 1.22 or newer (if building from source or installing via
go install)
1. Create a Runefile at your project root:
#[Build the application binary]
build target="dev" --race?:
go build {{race}} -o ./bin/app ./...
#[Run unit and integration tests]
test: build
go test ./...
#[Reset the database schema]
#[confirm: This will permanently delete all database data.]
db:fresh:
dropdb --if-exists app_dev
createdb app_dev
#[Forward arbitrary commands to Docker Compose]
compose *args:
docker compose {{args}}
2. Run your tasks:
rune # list all available tasks
rune build # run with defaults
rune build production --race # with arguments and flags
rune build --help # auto-generated per-task help
rune --dry-run release # simulate without executingTip
Use rune <namespace> or rune <namespace> --help to discover tasks grouped under a namespace, for example rune db or rune db --help.
task: # Run a simple command
task arg: # Required positional argument
task env="dev": # Positional argument with default value
task --race?: # Optional boolean flag
task *args: # Forward arbitrary trailing arguments
task: dep1 dep2 # Prerequisites executed before task
#[Description] # Task summary displayed in help menu
#[confirm: Are you sure?] # Prompt user before executing task
#[dir: path/to/dir] # Execute task in specific working directory
#[env: KEY=VAL; KEY2=V2] # Task-scoped environment variables
#[private] # Hide internal task from listing and autocompletion
# arg: Description # Document argument in help menu
# --flag: Description # Document flag in help menu
A Runefile consists of metadata attributes, task signatures, and indented command bodies.
#[Task description]
#[confirm: Confirmation prompt]
task_name [arguments...] [--flags...] [*passthrough] : [dependencies...]
command 1
command 2
| Element | Description |
|---|---|
#[...] |
Metadata — attaches description or safety prompt to the next task |
# |
Comment — ignored entirely |
| Signature | Non-indented line ending with : (or : dep1 dep2) |
| Commands | Lines indented with 4 spaces or a tab |
{{var}} |
Interpolation — expands to the resolved argument or flag value |
greet name:
echo "Hello, {{name}}!"
rune greet Supian
# Output: Hello, Supian!If a required argument is omitted, Rune displays an actionable error:
✗ Missing argument: name
Usage:
rune greet <name>
Assign a default value using = (supports quoted or unquoted values):
build target="dev":
echo "Building for target: {{target}}"
rune build # Building for target: dev
rune build production # Building for target: productionNote: Required arguments must always precede default arguments in the signature.
Declare task CLI options using boolean flags or valued options. Single-character short aliases can be paired with long option names using pipe syntax (-<short>|--<long>).
| Signature Syntax | Parameter Type | Status | Default | CLI Invocation | Value in {{var}} |
Validation / Error |
|---|---|---|---|---|---|---|
target |
Positional Argument | Required | - | rune task app.go |
"app.go" |
Error if omitted |
target="dev" |
Positional Argument | Optional | "dev" |
rune taskrune task prod |
"dev""prod" |
- |
action=[up,down]="up" |
Positional Enum | Optional | "up" |
rune taskrune task down |
"up""down" |
Error if value not in choices |
action=[up,down] |
Positional Enum | Required | - | rune task up |
"up" |
Error if omitted or not in choices |
-w|--watch? or --watch? |
Boolean Flag | Optional | false |
rune taskrune task -wrune task --watch |
"" (omitted)"--watch""--watch" |
- |
-t|--token= or --token= |
String Option | Required | - | rune task -t sec123rune task --token=sec123 |
"sec123""sec123" |
Error if omitted or passed without value |
-o|--output="dist" |
String Option | Optional | "dist" |
rune taskrune task -o binrune task --output=bin |
"dist""bin""bin" |
Error if passed without value |
-t|--tag=? |
String Option | Optional | "" |
rune taskrune task -t v1.0 |
"""v1.0" |
Error if passed without value |
-e|--env=[stg,prod] |
Enum Option | Required | - | rune task -e stgrune task --env=prod |
"stg""prod" |
Error if omitted or not in choices |
-m|--mode=[a,b]="a" |
Enum Option | Optional | "a" |
rune taskrune task -m b |
"a""b" |
Error if not in choices |
*args |
Passthrough Args | Optional | [] |
rune task --extra "val" |
Passthrough list | - |
Options declared without = are boolean switches (true/false):
build -w|--watch? target="dev":
go build {{watch}} ./...
rune build # watch is false (expands to nothing)
rune build -w # watch is true (expands to --watch)
rune build --watch # watch is true (expands to --watch)
rune build -w=false # watch is falseOptions declared with = accept string values. Both space and = syntax are supported:
build -o|--output="dist" target="main.go":
go build -o {{output}}/app {{target}}
rune build # output defaults to "dist"
rune build -o bin # output is "bin"
rune build --output=bin # output is "bin"Constrain options or positional arguments to allowed values with [choice1,choice2]. Rune automatically validates inputs and rejects invalid values before running commands:
deploy -e|--env=[staging,production] -m|--mode=[rolling,canary]="rolling":
./deploy.sh --target={{env}} --strategy={{mode}}
# Valid invocations
rune deploy -e staging
rune deploy --env=production --mode=canary
# Invalid enum value fails fast with a clear error:
rune deploy -e local
# ✗ Invalid value "local" for option -e, --env=VALUE
# Allowed choices: staging, productionPositional parameters also support enum choices (e.g. db:migrate action=[up,down,status]="up":).
If you mistype an option, Rune suggests the closest candidate:
rune build --rce
# ✗ Unknown option: --rce
# Did you mean: --raceWhen an underlying tool accepts arbitrary arguments, declare an explicit passthrough parameter using *<name>:
compose *args:
docker compose {{args}}
rune compose exec api sh -c "echo 'Health check'" --user=rootDocument task parameters and options by placing a contiguous comment block directly before the task header:
- Options with short aliases: Use
# -s|--option: Description,# --option: Description, or# -s: Description(all formats bind to the same option and share the description across--helpand autocomplete). - Positional arguments: Use
# <arg>: Description. - Passthrough arguments: Use
# *<args>: Descriptionor# <args>: Description.
#[Deploy application to cloud infrastructure]
# -e|--env: Target cloud environment
# -o|--output: Build output folder
# -w|--watch: Watch file changes
# target: Entrypoint package
deploy -e|--env=[staging,production] -o|--output="dist" -w|--watch? target="./cmd/app":
./deploy.sh
Running rune deploy --help automatically renders these descriptions, choices, defaults, and required markers:
Arguments:
target Entrypoint package [default: "./cmd/app"]
Options:
-e, --env=VALUE Target cloud environment [choices: staging, production] (required)
-o, --output=VALUE Build output folder [default: "dist"]
-w, --watch Watch file changes
-h, --help Display help for the given command
-v, --version Display this application version
Note: Comments that do not match declared parameter names (such as developer notes
# NOTE: ...or# TODO: ...), comments separated by blank lines, and comments inside the task body are completely ignored.
Set a task-specific working directory with #[dir: <path>]:
#[dir: frontend]
build:web:
npm run build
#[dir: backend]
build:api:
go build -o ../bin/api ./...
- Paths are relative to the directory containing the
Runefile(or absolute if specified). - Rune configures the process working directory directly without injecting shell
cdcommands. - Rune validates that the directory exists before executing the task.
- Dependencies retain their own working directory configuration.
Declare task-scoped environment variables using #[env: ...]. Both single-line and semicolon-separated formats are supported, and multiple #[env] attributes accumulate:
#[env: GOOS=linux]
#[env: CGO_ENABLED=0]
build:linux:
go build -o ./bin/app-linux ./...
Equivalently in a single attribute:
#[env: CGO_ENABLED=0; GOOS=linux]
build:linux:
go build -o ./bin/app-linux ./...
- Task-scoped variables override inherited process environment variables and
.envvalues for that task. - Dependencies maintain their own isolated task environments.
Hide internal helper or prerequisite tasks from public discovery menus (rune, rune list, and shell completions) using #[private]:
#[private]
ensure:certs:
./scripts/generate-certs.sh
deploy: ensure:certs
./scripts/deploy.sh
- Private tasks do not appear in
runeorrune --helpcommand listings. - Private tasks remain fully executable when invoked directly by name (
rune ensure:certs). - Private tasks can be freely referenced as dependencies by other tasks.
- If all tasks in a namespace are private, the namespace itself is hidden from the root command list.
rune connects child processes directly to the terminal's standard streams (stdin, stdout, stderr):
- Interactive Commands — Programs such as
python,psql,ssh, and text editors receive interactive terminal input directly. - Signal Forwarding — Common POSIX signals (
SIGINT,SIGTERM,SIGHUP) are forwarded to the running command process. - Exit Code Preservation — Propagates the exact exit code of the executed command.
- Unix Pipes — Tasks can participate in standard Unix pipes from your shell:
cat dump.sql | rune db:import
rune compose ps | grep runningNote
rune executes each command directly via os/exec without an implicit shell. If you need shell features such as pipes (|) or logical operators (&&), run them through sh -c "...".
Group related tasks using colon notation (namespace:task):
db:migrate:
migrate -path ./migrations -database "$DATABASE_URL" up
db:seed:
go run ./cmd/seed
db:fresh:
dropdb --if-exists app_dev
createdb app_dev
Namespaces act like command groups:
rune db # discover tasks inside the 'db' namespace
rune db --helpNamespace:
db
Usage:
rune db:<task> [arguments] [flags]
Available Tasks:
fresh Reset the database schema
migrate Run database migrations
seed Seed the database
Specify task dependencies on the signature line after the colon:
build:
go build ./...
test: build
go test ./...
release: build test
./release.sh
| Behavior | Description |
|---|---|
| Deterministic Ordering | Dependencies execute before their dependent task |
| Deduplication | A task runs once even if multiple tasks depend on it |
| Cycle Detection | Circular dependencies are detected before any command runs |
| Fail-Fast | Non-zero exit code halts execution immediately |
Protect dangerous actions with #[confirm: message]:
#[Reset the database schema]
#[confirm: This will permanently delete all database data.]
db:fresh:
dropdb --if-exists app_dev
createdb app_dev
WARN This will permanently delete all database data.
Continue?
❯ Yes No
| Key | Action |
|---|---|
← / →, Tab, h / l |
Toggle selection |
Enter or Space |
Execute selected choice |
y |
Confirm immediately |
n, Esc, Ctrl+C |
Cancel |
Confirmation occurs before any task in the dependency graph executes. In non-interactive environments (CI, pipes), Rune falls back to line-based Continue? [y/N] input.
To skip confirmation entirely:
rune --yes db:fresh # or -ySimulate execution and inspect the resolved command sequence without running anything:
rune --dry-run release[dry-run] Execution plan for 'release':
1. build
$ go build ./...
2. test
$ go test ./...
3. release
$ ./release.sh
Inspect the hierarchical dependency tree of a task or the entire Runefile without executing any commands:
rune release --tree
# or: rune --tree releaserelease
├── build (dir: backend)
└── test
├── setup:certs [private]
└── build (dir: backend) (deduped)
- Box-Drawing Tree: Uses standard Unicode box-drawing characters (
├──,└──,│). - Deduplication (
(deduped)): Tasks already expanded earlier in the tree are marked as(deduped)to avoid redundant sub-branches. - Context Badges: Highlights task attributes inline such as
[private]and(dir: <path>). - Project-Wide Overview: Run
rune --treewithout a task name to visualize dependency trees for all tasks in yourRunefile:
rune --tree| Variable | Description |
|---|---|
Auto .env loading |
Loaded automatically if .env exists beside the Runefile |
RUNE_TASK |
Name of the active task (e.g. build) |
RUNE_ARG_<NAME> & <NAME> |
Value of resolved positional arguments |
RUNE_FLAG_<NAME> |
Set to 1 when the flag is enabled |
Child processes also inherit the full system environment (os.Environ()).
Display each command before executing it:
rune build --verbose$ go build -o ./bin/app ./...
- Does not alter command arguments, environment, or execution flow.
- Command arguments with spaces or quotes are cleanly escaped.
Display execution duration for tasks:
rune test --timeOutput for tasks with dependencies:
[1/3] test:unit 0.82s
[2/3] test:feature 1.45s
[3/3] test:e2e 3.21s
✔ Total: 5.48s
Combine with --verbose:
rune build --verbose --time$ go build -o ./bin/app ./...
✔ build (0.82s)
Total: 0.82s
Rune includes built-in console UI components for use directly from the shell or inside your Runefile task recipes.
| Command | Badge | Color | Exit Code | Purpose |
|---|---|---|---|---|
rune info <msg> |
INFO |
Blue bg, white bold | 0 |
Informational status updates |
rune warn <msg> |
WARN |
Yellow bg, black bold | 0 |
Warnings or cautionary alerts |
rune done <msg> |
DONE |
Green bg, white bold | 0 |
Successful completions (alias: rune success) |
rune fail <msg> |
FAIL |
Red bg, white bold | 1 |
Task or validation failure alerts |
rune error <msg> |
ERROR |
Red bg, white bold | 1 |
System error or fatal exception alerts |
Example usage in a Runefile:
#[Deploy application to production]
#[confirm: Are you sure you want to deploy to production?]
deploy:
rune info "Preparing deployment assets..."
npm run build
rune info "Syncing files to remote server..."
rsync -avz ./dist/ user@example.com:/var/www/
rune done "Deployment finished successfully!"
#[Verify system dependencies]
check:
sh -c "test -f .env || (rune error 'Missing .env configuration file!' && exit 1)"
sh -c "git diff --quiet || (rune fail 'Working directory has unstaged changes!' && exit 1)"
rune done "All checks passed."
Output:
INFO Preparing deployment assets...
DONE Deployment finished successfully!
Task Precedence: If your
Runefiledefines a custom task namedinfo,warn,error,fail, ordone, your custom task takes precedence over the built-in component.
Rune provides tab completion for Bash, Zsh, and Fish.
Add to your ~/.zshrc:
source <(rune completion zsh)Add to your ~/.bashrc:
source <(rune completion bash)Add to your ~/.config/fish/config.fish:
rune completion fish | source| Input | Completion |
|---|---|
rune bu<TAB> |
build |
rune d<TAB> |
db |
rune build --<TAB> |
--race |
rune in<TAB> |
info, warn, done, error, help, list |
rune --<TAB> |
--dry-run, --help, --yes, etc. |
To enable syntax highlighting for Runefile in VS Code, add the file association to your settings.json:
"files.associations": {
"Runefile": "makefile"
}rune is focused on running project-specific tasks. It is deliberately minimal and does not attempt to be:
- A build system or CI/CD engine
- A process supervisor or daemon manager
- A package manager or environment orchestrator
- A full scripting language or workflow engine
Its goal is simply to make defining, finding, and running tasks straightforward and dependable.
Rune is a local CLI tool. It does not collect telemetry, phone home, or require network access to operate.
Reporting a Vulnerability: If you discover a security issue, please follow the responsible disclosure process described in SECURITY.md. Do not open a public GitHub issue for security vulnerabilities.
Contributions are welcome — bug reports, feature discussions, and pull requests alike. If you are new to the codebase, look for issues labeled good first issue as a starting point.
Before submitting a pull request, please read CONTRIBUTING.md. It covers the code style, commit conventions, and the PR review process.
Rune is licensed under the MIT License. See LICENSE for the full license text.