<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>runner — universal project task runner</title>
<meta name="description" content="One CLI across npm, yarn, pnpm, bun, cargo, deno, uv, poetry, and more. Auto-detects toolchain.">
<meta name="theme-color" content="#f4ede1">
<meta property="og:title" content="runner — universal project task runner">
<meta property="og:description" content="One CLI for every toolchain. npm install -g {{npmName}}">
<link rel="canonical" href="https://runner.kjanat.dev/">
<link rel="icon" href="assets/icon.svg" type="image/svg+xml">
<link rel="stylesheet" href="styles/base.css">
<link rel="stylesheet" href="styles/index.css">
</head>
<body>
<main>
<p id="copy-status" class="visually-hidden" role="status" aria-live="polite" aria-atomic="true"></p>
<header>
<h1 class="wordmark">runner</h1>
<p class="tagline">
Universal project task runner. Auto-detects toolchain, provides unified CLI.
</p>
<p class="meta">
v{{version}} ·
<a href="{{repo}}">{{repoShort}}</a> · {{license}}
</p>
</header>
<hr class="rule">
<section aria-labelledby="install-tag">
<span class="section-tag" id="install-tag">install</span>
<div class="install">
<button class="copy pri" type="button" data-cmd="npm install -g {{npmName}}">
<span class="label">npm · primary</span>
<span class="cmd">npm install -g {{npmName}}</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
<button class="copy pri" type="button" data-cmd="cargo binstall {{cratesName}}">
<span class="label">cargo · binstall · primary</span>
<span class="cmd">cargo binstall {{cratesName}}</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
<button class="copy" type="button" data-cmd="cargo install {{cratesName}}">
<span class="label">cargo · build from source</span>
<span class="cmd">cargo install {{cratesName}}</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
<button class="copy" type="button" data-cmd="curl -fsSL https://raw.githubusercontent.com/kjanat/runner/master/install.sh | sh">
<span class="label">linux installer</span>
<span class="cmd cmd-split"><span>curl -fsSL </span><span class="cmd-shrink"
>https://raw.githubusercontent.com/kjanat/runner/master/install.sh</span><span> | sh</span></span>
<span class="toast" aria-hidden="true">copied</span>
</button>
</div>
<p class="meta">
The npm package is a façade — installs only the <a href="{{repo}}/tree/master/npm">prebuilt binary</a> for your platform via
<code>optionalDependencies</code>. No postinstall, no network at install time.
</p>
<p class="meta">
Using <code>cargo binstall</code>? Run <code>cargo install cargo-binstall</code> once first. After that, every <code>cargo binstall
<crate></code>
pulls a prebuilt binary from GitHub releases instead of compiling.
</p>
</section>
<hr class="rule">
<section aria-labelledby="demo-tag">
<span class="section-tag" id="demo-tag">what it looks like</span>
<pre class="term"><span class="prompt"></span><span>runner</span>
<a class="bold link" href="{{repo}}/">runner</a> <a class="link" href="{{repo}}/releases/tag/v{{version}}">{{version}}</a>
<span class="dim">Package Managers</span> bun, pnpm
<span class="dim">Task Runners</span> turbo, just
<span class="dim">Node</span> 22 (.nvmrc), current v22.11.0 <span class="green">(ok)</span>
<span class="dim">Monorepo</span> <span class="green">yes</span>
<span class="bold link">package.json</span> build <span class="dim">compile the thing</span>
<span class="bold link">package.json</span> test <span class="dim">run tests</span>
<span class="bold link">package.json</span> dev <span class="dim">start dev server</span>
<span class="bold link">justfile</span> ci, fmt, release
<span class="bold">justfile (aliases)</span> b <span class="arrow">→</span> <span class="dim">build</span>, t <span class="arrow">→</span> <span class="dim">test</span>
<span class="prompt"></span><span>run test</span>
<span class="dim">› bun test</span><span class="cursor"> </span></pre>
</section>
<hr class="rule">
<section aria-labelledby="completion-tag">
<span class="section-tag" id="completion-tag">tab-fucking completion</span>
<p class="tagline">
Drop one line in your shell rc. Now <code><TAB></code> hits the binary and asks <em>this</em> project what tasks it knows about — grouped by
source, with descriptions. Same line registers both <code>runner</code> and <code>run</code>.
</p>
<div class="install">
<button class="copy pri" type="button" data-cmd='eval "$(runner completions)"'>
<span class="label">bash · zsh · fish · auto-detects $SHELL</span>
<span class="cmd">eval "$(runner completions)"</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
<button class="copy" type="button" data-cmd="runner completions powershell | Out-String | Invoke-Expression">
<span class="label">powershell</span>
<span class="cmd">runner completions powershell | Out-String | Invoke-Expression</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
</div>
<pre class="term"><span class="prompt"></span><span>cd ~/some-project</span>
<span class="prompt"></span><span>runner </span><span class="dim"><TAB></span>
<span class="dim">-- package.json --</span>
build <span class="dim">compile the thing</span>
test <span class="dim">run tests</span>
dev <span class="dim">start dev server</span>
<span class="dim">-- justfile --</span>
ci <span class="dim">lint + tests</span>
fmt <span class="dim">format</span>
release <span class="dim">cut a release</span>
<span class="dim">-- justfile (aliases) --</span>
b <span class="dim">→ build</span>
t <span class="dim">→ test</span>
<span class="dim">-- Commands --</span>
list <span class="dim">list tasks</span>
info <span class="dim">show detected project</span>
clean <span class="dim">clean build artefacts</span><span class="cursor"> </span></pre>
<div class="meta">
<p>Path-typed flags like <code>--dir <TAB></code> delegate to the shell's own file completer.</p>
<p><code>~/</code>, globs, and <code>cdpath</code> all work.</p>
</div>
</section>
<hr class="rule">
<section aria-labelledby="matrix-tag">
<span class="section-tag" id="matrix-tag">it speaks</span>
<div class="matrix">
<div>
<h3>Package managers · 12</h3>
<ul>
<li>npm</li>
<li>yarn</li>
<li>pnpm</li>
<li>bun</li>
<li>cargo</li>
<li>deno</li>
<li>uv</li>
<li>poetry</li>
<li>pipenv</li>
<li>go</li>
<li>bundler</li>
<li>composer</li>
</ul>
</div>
<div class="single">
<h3>Task runners · 7</h3>
<ul>
<li>turbo</li>
<li>nx</li>
<li>make</li>
<li>just</li>
<li>go-task</li>
<li>mise</li>
<li>bacon</li>
</ul>
</div>
<div>
<h3>Task sources · 8</h3>
<ul>
<li>package.json</li>
<li>turbo.json(c)</li>
<li>Makefile</li>
<li>justfile</li>
<li>Taskfile</li>
<li>deno.json(c)</li>
<li>bacon.toml</li>
<li>mise.toml</li>
</ul>
</div>
</div>
<p class="meta">nx is detection-only for now.</p>
</section>
<hr class="rule">
<section aria-labelledby="why-tag">
<span class="section-tag" id="why-tag">why it's not shit</span>
<ol class="why">
<li>Auto-detection picks the right tool from your lockfiles. No flags, no config.</li>
<li>One CLI across npm, yarn, pnpm, bun, cargo, deno, uv, poetry, pipenv, go, bundler, composer.</li>
<li>
Aggregates tasks from package.json, turbo.json(c), Makefile, justfile, Taskfile, deno.json(c), bacon.toml — qualified syntax (<code>run
package.json:test</code>) when names collide.
</li>
<li>Monorepo-aware: turbo, nx, pnpm, npm/yarn workspaces, Cargo workspaces.</li>
<li>
No task by that name? Falls through to the package manager's exec primitive — <code>npx</code>, <code>bunx</code>, <code>pnpm exec</code>, <code>uv
run</code>, etc.
</li>
</ol>
</section>
<footer>
{{license}} · v{{version}} · by <a href="mailto:{{authorEmail}}">{{authorName}}</a> · <a href="{{repo}}">source</a> · <a
href="{{repo}}/blob/master/CHANGELOG.md"
>changelog</a> · <a href="https://crates.io/crates/{{cratesName}}">crates.io</a> · <a href="https://npm.im/{{npmName}}">npm</a>
</footer>
</main>
<script type="module" src="app/copy.ts"></script>
</body>
</html>