xeq
xeq runs sequences of commands from a single TOML file, making repetitive tasks fast and consistent.
Every project has a setup ritual. Ten commands, always in the same order, every time. Write them once in a xeq.toml, commit it, and anyone on any OS runs the exact same steps with one command.
Table of Contents
- Demo
- Installation
- Quick Start
- Comparison
- Commands
- TOML Format
- Features
- CI Usage
- Examples
- How It Works
- Contributing
- License
Demo

Installation
macOS / Linux
|
Windows (Powershell)
iwr https://raw.githubusercontent.com/opmr0/xeq/main/install.ps1 -UseBasicParsing | iex
Via cargo (Rust package manager)
Quick Start
1. Create a xeq.toml in your project root:
[]
= [
"npm install",
"npm run build"
]
[]
= ["npm run dev"]
2. Run any script by name:
xeq finds xeq.toml in the current directory automatically. Use xeq init to generate a starter file.
Comparison
| Feature | xeq | Makefile | npm scripts | just |
|---|---|---|---|---|
| File type | TOML | Makefile | package.json | Justfile |
| Cross-platform | Yes | Mostly Linux/macOS | Yes | Yes |
| Validation command | Yes (xeq validate) |
No | No | No |
| Variables | Yes | Yes | Limited | Yes |
| Args support | Yes | Limited | Limited | Yes |
| Nested scripts | Yes (xeq:) |
Yes | No | Yes |
| Parallel execution | Yes | Yes (-j) |
No | No |
| .env support | Yes (auto) | No | No | Yes (opt-in) |
| Init templates | Yes (30+) | No | No | No |
Commands
xeq run <script> [flags]
Runs a script by name. Commands execute in order. If any command fails, xeq stops unless you pass --continue-on-err.
If no script name is given, xeq runs the default script defined in your config:
= "dev"
[]
= ["npm run dev"]
| Flag | Short | Description |
|---|---|---|
--continue-on-err |
-C |
Keep going even if a command fails |
--quiet |
-q |
Hide xeq's own log messages |
--clear |
-c |
Clear the terminal before each command |
--parallel [threads] |
-p |
Run all commands in parallel (default: logical CPU count) |
--args <values...> |
-a |
Pass arguments into the script, positional or key=value |
--global |
-g |
Use the globally saved xeq.toml instead of the local one |
--dry-run |
-d |
Preview commands without executing them |
--no-events |
-e |
Disable events for this run |
--allow-empty-args |
-A |
Skip errors for missing variables or arguments |
--no-env |
Skip loading the .env file |
|
--allow-recursion |
Let a script call itself |
Exit codes: xeq exits 0 on success and non-zero if any command fails. Use this in CI pipelines or shell scripts to halt on failure.
xeq init [template]
Creates a starter xeq.toml in the current directory. Will not overwrite an existing file.
Available templates: android, ansible, astro, aws, bun, deno, django, docker, dotnet, elixir, expo, fastapi, flutter, git, go, hugo, java-gradle, java-maven, kubernetes, laravel, monorepo, nestjs, nextjs, node, python, rails, react, rust, svelte, tauri
xeq validate
Checks all scripts for errors without running anything.
| Check | Flag |
|---|---|
| Circular dependencies | static |
Missing xeq: targets |
static |
default script existence |
static |
continue_on_err + events conflict |
static |
parallel_threads <= 1 |
static |
parallel + cd/xeq: conflict |
static |
dir existence |
static |
Undefined {{@var}} |
static |
dir cd permission |
--runtime |
Unset {{$VAR}} env vars |
--runtime |
| Command existence | --runtime |
Add --runtime to also validate environment variables and system commands:
xeq list
Shows all scripts in your xeq.toml, names, descriptions, and commands.
xeq config [path]
Saves a xeq.toml path globally so you can run it from anywhere.
xeq toml
Prints the full TOML format reference.
Aliases
| Command | Alias |
|---|---|
xeq run |
xeq r |
xeq config |
xeq c |
xeq init |
xeq i |
xeq validate |
xeq v |
TOML Format
Each script needs at minimum a run array:
[]
= "What this script does"
= "./my_app"
= 4
= ["quiet"]
= [
"command one",
"command two"
]
run- required, commands to execute in orderdescription- optional, shown inxeq listdir- optional, working directory (absolute or relative to where xeq is invoked, not wherexeq.tomllives)parallel_threads- optional, enables parallel execution with a set thread countoptions- optional, baked-in flags. see Script Options
Script names are case-sensitive. Build and build are different scripts.
Features
1. Script Options
Bake default behavior into a script so you don't have to pass flags every time:
[]
= ["quiet", "continue_on_err"]
= ["cargo build", "cargo test"]
Available options: quiet, clear, continue_on_err, allow_recursion, allow_empty_vars
CLI flags toggle script options. If quiet is baked in and you pass --quiet, it turns quiet off for that run.
2. Variables
Define reusable values in a [vars] block and reference them with {{@varname}}:
[]
= "myapp:latest"
= "development"
[]
= ["docker build -t {{@image}} ."]
[]
= ["APP_ENV={{@env}} npm start"]
Local variables override global ones for a specific script:
[]
= "myapp:latest"
[]
= "myapp:build"
= ["docker build -t {{@image}} ."] # uses "myapp:build"
[]
= ["docker push {{@image}}"] # uses "myapp:latest"
Override at runtime with --args:
Fallback values use | to provide a default if a variable isn't set:
[]
= ["docker build -t {{@image | myapp:latest}} ."]
Resolution order: --args -> local vars -> global vars -> fallback
If a variable isn't defined and no fallback is set, xeq exits with an error. Pass
--allow-empty-varsor addallow_empty_varsto options to skip this.
3. Arguments
Use positional placeholders {{1}}, {{2}} for values that change every run:
[]
= [
"npm create vite@latest {{1}} -- --template {{2}}",
"cd {{1}}",
"npm install"
]
# {{1}} = my-app
# {{2}} = react
Mix named and positional args:
Missing arguments cause an error. Use
--allow-empty-varsto skip this.
4. Environment Variables
Reference environment variables with {{$VARNAME}}:
[]
= ["deploy --token {{$API_TOKEN}} --env {{$DEPLOY_ENV}}"]
xeq loads a .env file from the directory where xeq is invoked automatically:
# .env
API_TOKEN=abc123
DEPLOY_ENV=production
Pass --no-env to skip loading .env.
Missing env vars cause an error. Use
--allow-empty-varsto skip this.
5. Nested Scripts
Call other scripts from within a script using the xeq: prefix:
[]
= ["npm install"]
[]
= ["npm run build"]
[]
= [
"xeq:install",
"xeq:build",
"npm run deploy"
]
Running xeq run deploy runs install and build first, in order.
xeq detects circular dependencies and exits. Add
allow_recursionto options if you intentionally need a script to call itself.
6. Parallel Execution
Run all commands in a script at the same time:
[]
= 4
= [
"cargo test",
"cargo clippy",
"cargo fmt --check"
]
Use -p to toggle parallel mode from the CLI, or -p <threads> to override the thread count:
Scripts with
cdcommands orxeq:calls cannot run in parallel.xeq validatecatches this. Ifparallel_threadsis 1 or less, xeq falls back to sequential execution.
7. Global Configuration
Save a xeq.toml globally to run scripts from any directory:
8. Events
Run additional commands when a script succeeds or fails:
[]
= ["cargo test", "cargo build"]
= ["echo build passed"]
= ["echo build failed"]
Events cannot be combined with
continue_on_err.
9. Custom Shells
Set a shell at the file level to run all commands with:
= "zsh"
[]
= ["cargo build", "cargo test"]
Available shells: sh, bash, zsh, fish, cmd, powershell
Defaults to sh on Linux/macOS and cmd on Windows.
CI Usage
xeq works well in CI pipelines. Use xeq validate --runtime as a pre-flight check to catch missing commands or unset env vars before anything runs.
GitHub Actions example:
- name: Validate config
run: xeq validate --runtime
- name: Build
run: xeq run build
- name: Test
run: xeq run test
xeq exits non-zero on any failure, so your pipeline halts automatically without extra configuration.
Examples
The examples/ folder has ready-to-use TOML files for common workflows.
| File | Description | Features Used |
|---|---|---|
react-tailwind.toml |
Scaffold and run a React + Tailwind project | variables, cd operators |
nextjs.toml |
Next.js project setup and pipeline | nested scripts, variables |
rust-project.toml |
Rust checks, build and publish | parallel, nested scripts |
docker.toml |
Docker image and container management | variables, nested scripts |
git-workflow.toml |
Common git operations | variables, arguments |
nested-scripts.toml |
CI pipeline from reusable pieces | nested scripts |
env-vars.toml |
Deploy and notify using env vars | {{$VAR}}, nested scripts |
python-project.toml |
Virtualenv, checks and PyPI publish | parallel, nested scripts, variables |
database.toml |
Migrations, seed, dump and restore | env vars, arguments, nested scripts |
monorepo.toml |
Multi-package frontend workspace | parallel, variables, nested scripts |
aws-deploy.toml |
ECR push and ECS deploy pipeline | env vars, nested scripts |
go-project.toml |
Go build, test and cross-compile | parallel, nested scripts, variables |
arguments.toml |
Positional and named arg patterns | arguments, variables |
script-options.toml |
Flag toggle mechanic demonstrations | options, parallel, quiet, continue_on_err |
How It Works
- xeq reads
xeq.tomlfrom the current directory, or a globally saved path with--global - Commands run through
sh -con Linux/macOS andcmd /Con Windows by default cdcommands update the working directory for all subsequent commands in that scriptdiris resolved relative to where xeq is invoked, not wherexeq.tomllives- Variables resolve in order:
--args-> local vars -> global vars -> fallback .envis loaded from the invocation directory automatically before any script runs- Script names are case-sensitive
Contributing
See CONTRIBUTING.md.
License
MIT - LICENSE