tsr
tsr is a lightweight, polyglot, repo-aware task runner. It is a command runner, not a build system: it provides one unified interface over the native runners already in your repo (npm, bun, cargo, go, uv, …), adds a task dependency graph and opt-in parallelism, and delegates caching to specialist tools (Turbo, Nx) rather than reimplementing it.
tsr dev # run the 'dev' task tsr ci # run the 'ci' task tsr test -- --watch
Binary:
tsrConfig file:
tasks.toml(also the workspace-root anchor)Written in: Rust, single static binary
Design principles
Lightweight — a thin unifying layer, not a replacement for native runners.
Delegate, don't reimplement — execution is handed to native runners; caching is handed to Turbo/Nx.
Polyglot — one entry point across every ecosystem in the repo.
Predictable by default — sequential execution unless parallelism is explicitly requested; fail fast.
Hand-edit-safe — the config stays valid and legible when edited by hand, and unknown keys survive a round-trip.
Explicitly out of scope
Content-hash caching, remote caching, and inputs/outputs tracking are never implemented in tsr — they are ceded to delegated backends (Turbo, Nx). Adding them would contradict the "lightweight, delegate" principle.
Run your first task
From an empty repo to a running task in four steps.
Install
tsr is a single static binary. Install it using the official installer or build from source:
curl -fsSL https://raw.githubusercontent.com/Open-Tech-Foundation/tsr/main/install.sh | bashirm https://raw.githubusercontent.com/Open-Tech-Foundation/tsr/main/install.ps1 | iexcargo build --releaseScaffold a config
From your repo root, generate a commented starter tasks.toml showcasing all three task forms. It refuses to overwrite an existing file, so it is always safe to run:
tsr --init
Define a task
Open tasks.toml and add a task — or author it interactively with tsr --config:
[tasks.dev] run = "vite" dir = "apps/web"
Run it
tsr dev
Because run = "vite" has no shell metacharacters, tsr splits it and spawns vite directly — no npm run / Node startup tax.
The location of tasks.toml defines your workspace root. tsr finds it by walking up from the current directory to the nearest tasks.toml, so you can run tasks from anywhere in the repo.
tasks.toml is optional. In a repo that only has a package.json (or Cargo.toml, go.mod, pyproject.toml), tsr dev just runs npm run dev / cargo dev / … by auto-detecting the ecosystem — no config to write. Add a tasks.toml when you want a dependency graph, monorepo fan-out, or delegate.