xeq
Run sequences of shell commands with a single word.
Define your commands once in a TOML file. Run them from anywhere, on any OS, without rewriting them every time.
Table of Contents
- Why xeq?
- How does xeq compare?
- Installation
- Quick Start
- Commands
- TOML File Format
- Features
- Real-World Examples
- How It Works
- Example Files
- Contributing
- License
Why xeq?
Every project has a setup ritual — install dependencies, build, run tests, configure things. You either memorize the steps, paste them from a notes file, or write a shell script that only works on your machine.
xeq gives you a better option:
- Write your commands in a
xeq.tomlfile - Commit it to your repo
- Anyone on the team runs the exact same steps with one command — on Linux, macOS, or Windows
How does xeq compare?
| Feature | xeq | just | make |
|---|---|---|---|
| Config format | TOML | Custom syntax | Makefile syntax |
| Cross-platform | ✅ | ✅ | ⚠️ poor on Windows |
| Parallel execution | ✅ built-in | ❌ | ❌ |
| Nested scripts | ✅ (xeq://) |
✅ | ⚠️ |
| Variables | ✅ global + local | ✅ | ✅ |
| Argument passing | ✅ named + positional | ✅ named + defaults | ⚠️ |
| Flag toggle mechanic | ✅ | ❌ | ❌ |
| Multi-language recipes | ❌ | ✅ | ❌ |
.env loading |
❌ | ✅ | ❌ |
| Learning curve | None (TOML) | Low (new syntax) | High |
Installation
macOS / Linux
|
Windows (PowerShell)
iwr https://raw.githubusercontent.com/opmr0/xeq/main/install.ps1 -UseBasicParsing | iex
Via cargo
Quick Start
1. Create a xeq.toml in your project root:
[]
= [
"npm install",
"npm run build"
]
[]
= ["npm run dev"]
2. Point xeq at the file (one time only):
If there is a
xeq.tomlin your current directory, xeq finds it automatically — no config step needed.
3. Run any script by name:
That's it. From now on, anyone who clones the repo can run xeq run setup and get the exact same result.
Commands
xeq config [path]
Saves the path to your TOML file globally. You only need to do this once, or when you move the file.
xeq run <script> [flags]
Runs a script by name. Commands execute one at a time in order. If any command fails, xeq stops — unless you pass --continue-on-err.
Available flags:
| Flag | Short | Description |
|---|---|---|
--continue-on-err |
-C |
Keep going even if a command fails |
--quiet |
-q |
Hide xeq's own log messages, only show command output |
--clear |
-c |
Clear the terminal before each command runs |
--parallel |
-p |
Run all commands at the same time instead of one by one |
--allow-recursion |
Let a script call itself (disabled by default to prevent infinite loops) | |
--args <values...> |
-a |
Pass arguments into the script — positional or key=value |
--global |
-g |
Use the globally saved path instead of the local xeq.toml |
xeq init
Creates a starter xeq.toml in the current directory with a sample script. Will not overwrite an existing file.
xeq list
Shows all scripts in your TOML file — their names, descriptions, and commands.
build --- Format, lint and release
runs:
cargo fmt
cargo clippy
cargo build --release
TOML File Format
A xeq.toml file contains named scripts. Each script needs at least a run array:
[]
= "What this script does"
= ["quiet"]
= [
"command one",
"command two",
"command three"
]
- Script names are case-sensitive —
Buildandbuildare different scripts descriptionis optional and only shows inxeq listoptionsare optional — see Script Options- You can define as many scripts as you want in a single file
Note:
cdmust be on its own line. Usingcdwith&∨is not supported and will cause an error.
Features
Script Options
Bake default behavior into a script so you don't have to pass flags every time:
[]
= ["quiet", "parallel"]
= ["cargo build", "cargo test"]
Now xeq run build always runs quietly and in parallel — no flags needed.
Available options: quiet, clear, parallel, continue_on_err, allow_recursion
Invalid options cause a parse error before any commands run.
Toggling: CLI flags toggle script options. If a script has quiet set and you pass --quiet, it turns quiet off for that run. This lets you override defaults without editing the file.
Nested Scripts
A script can call other scripts using the xeq:// prefix. This lets you build bigger workflows out of smaller reusable pieces:
[]
= ["npm install"]
[]
= ["npm run build"]
[]
= [
"xeq://install", # runs install first
"xeq://build", # then build
"npm run deploy" # then this
]
Running xeq run deploy automatically runs install and build first, in order.
Circular dependency protection: If a script tries to call itself (directly or through a chain), xeq exits with an error. Add
allow_recursiontooptionsif you intentionally need recursive behavior.
Local Configuration
xeq automatically detects a xeq.toml in your current directory. If one exists, it uses that. If not, it falls back to the globally saved path.
This means in most projects you never need to run xeq config at all — just put the file in the project root.
To force xeq to use the global path, pass --global:
Variables
Use a [vars] block to define reusable values at the top of your file. Reference them in commands with {{@varname}}:
[]
= "myapp:latest"
= "development"
[]
= ["docker build -t {{@image}} ."]
[]
= ["APP_ENV={{@env}} npm start"]
Both scripts share the same values. Change them in one place and every script picks up the update.
Local variables let a specific script use a different value without affecting others:
[]
= "myapp:latest" # default for all scripts
[]
= "myapp:build" # only applies to this script
= ["docker build -t {{@image}} ."]
[]
= ["docker push {{@image}}"] # still uses "myapp:latest"
Override at runtime using a named --arg — useful for one-off runs without editing the file:
Resolution order — most specific wins:
--args (runtime) -> local vars (per script) -> global vars (file-level)
Using
{{@varname}}with no value defined at any level causes a clear error before the command runs.
Arguments
For values that change every run, use positional placeholders {{1}}, {{2}}, etc. and pass them with --args:
[]
= [
"npm create vite@latest {{1}} -- --template {{2}}",
"cd {{1}}",
"npm install"
]
# {{1}} = my-app
# {{2}} = react
You can mix named and positional args in a single call:
If a script uses placeholders but no
--argsare given, xeq passes the raw{{1}}to the shell.
Parallel Execution
Run all commands in a script at the same time instead of one by one:
[]
= ["parallel"]
= [
"cargo test",
"cargo clippy",
"cargo fmt --check"
]
Scripts with
cdcommands orxeq://calls cannot run in parallel — xeq will exit with a clear error if you try.
How It Works
- xeq stores your TOML file path using the system config directory
- Commands run through
sh -con Linux/macOS andcmd /Con Windows cdcommands update the working directory for all subsequent commands in that script- Variables resolve in order:
--args→ local vars → global vars - On failure, xeq exits with the same exit code as the failed command
- Script names are case-sensitive:
Buildandbuildare different scripts
Example Files
The examples/ folder has ready-to-use TOML files for common workflows:
| File | What it does |
|---|---|
react-tailwind.toml |
Scaffold a React + Tailwind CSS project |
nextjs.toml |
Set up a Next.js project with TypeScript |
rust-project.toml |
Format, lint, test, and release a Rust project |
docker-app.toml |
Start, stop, rebuild, and tail logs for Docker Compose |
git-workflow.toml |
Sync, push, and stash/pop git operations |
scripts-nesting.toml |
Example of calling scripts from within scripts |
Contributing
Contributions are welcome — whether it's a bug fix, a new feature, or an improvement to the docs. Open an issue.
Getting started:
Before submitting a PR:
- Run
cargo fmtto format your code - Run
cargo clippyand fix any warnings - Run
cargo testand make sure all tests pass - If you're adding a new feature, add tests for it
Project structure:
src/
main.rs # CLI parsing and command dispatch
config.rs # Path saving/loading and TOML reading
runner.rs # Script execution logic
types.rs # Shared types (Script, Scripts, Config, SavedPath)
macros.rs # log! and err! macros
examples/ # Ready-to-use TOML files
If you're unsure whether a change fits the project, open an issue first.
License
MIT — LICENSE