<!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" type="button" data-cmd="cargo install {{cratesName}}">
						<span class="label">cargo · crates.io</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">curl -fsSL …/install.sh | sh</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>
			</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>&lt;TAB&gt;</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">&lt;TAB&gt;</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 &lt;TAB&gt;</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 · 6</h3>
						<ul>
							<li>turbo</li>
							<li>nx</li>
							<li>make</li>
							<li>just</li>
							<li>go-task</li>
							<li>mise</li>
						</ul>
					</div>
					<div>
						<h3>Task sources · 6</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>
						</ul>
					</div>
				</div>
				<p class="meta">nx and mise are 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) — 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>
