burgee vs the alternatives
Measured against commander, yargs, oclif, cac and citty — every capability row marked built is in the package today, with its tests.
This page is maintained by hand, and that is the reason to distrust it most. Four of
its rows disagreed with the suite that now measures them — an installed size 5.4x under,
an import cost that flattered us by 12 ms, a claim that burgee landed level with cac
against a measured 1.45x, and a compatibility count of 1,361 that has been
1,360 since the oracle's baseline was set. Benchmarks is generated from npm run bench and is the
authority; this page exists to put six frameworks in one table, which that one does not
do, and every cell here now says where it comes from.
- ⚙️ measured by
npm run benchon this repo, all four packages in one run on one machine, at the date below. - 📖 surveyed in
competitor-landscape.md§2 on a different machine at a different time. Never compare a ⚙️ against a 📖 — that is a comparison between two machines.
Measured
⚙️ rows: 2026-10-19 (66e4611), a GitHub Actions runner (AMD EPYC 7763, 4 cores), linux/x64, Node
24.13, 52 interleaved spawns per variant. Milliseconds are a property of that machine, so the deltas below are for reading,
never for gating; the machine-independent figure is the ratio on
Benchmarks.
| burgee | commander | yargs | @oclif/core | cac | citty | |
|---|---|---|---|---|---|---|
| Downloads / week | new | 508.2M | 259.3M | 10.9M | 49.4M | 30.5M |
| Runtime dependencies | 5, none outside the burgee family | 0 | 6 | 18 | 0 | 0 |
| Installed size ⚙️ | 1607 KB | 203 KB | 515 KB | 912 KB 📖 | 40 KB | 52 KB 📖 |
| Full CLI run over bare node ⚙️ | +19.1 ms | +15.3 ms | +77.1 ms | +131 ms 📖 | +4.1 ms | +6 ms 📖 |
Three things in that table got worse when they were measured rather than asserted, and they stay in it.
Installed size. burgee installs at 1607 KB. It is our largest number, it is our least flattering one, and it has now been wrong on this page twice in opposite directions.
First it said 104 KB, a guess at the tarball rather than a measurement. That was corrected
to 561 KB on 2026-09-09 — the real npm pack unpacked size, which is what lands in a
user's node_modules — and then the package grew to 2.3x that figure while the cell sat
still, because the cell was read from a published measurement rather than from a fresh
install. Every check on this page compared the page against that file and stayed green.
Fixed 2026-09-16: the row is now measured against the tree by CI on every run, so it cannot
fall behind again. It is checked there and not on a developer's laptop for a reason worth
stating, since it is the same class of mistake: the walk starts at a workspace link, so
locally it counts a developer's dev dependencies too and reads several hundred KB more for the
same commit. The cell is whatever npm i burgee puts on disk on the runner, and nothing else.
85 KB of it arrived on 2026-09-17 and is worth naming. burgee/commander now spawns
sub-binaries through bellpull instead of node:child_process, so burgee installs bellpull's
whole package — 86,113 bytes. What it buys is a Windows subcommand that actually works: upstream
commander sends every Windows spawn through node, which is a workaround for spawn not
searching PATHEXT, and it is simply wrong for a subcommand that is a .cmd, a .bat, or has
a shebang that is not node. The check on this row is what surfaced the cost, which is the
argument for the check.
24 KB more arrived on 2026-09-30: seniority/yaml (24,226 bytes), the YAML parser that
seniority's cosmiconfig drop-in needs to read the YAML files cosmiconfig reads. burgee never
loads it — no module on burgee's path imports it, and its bundled column did not move — but it
is on disk.
About 90 KB left on 2026-10-11. Every family package published a declaration file for
every module, and a TypeScript consumer can only open the ones an exports entry reaches.
burgee alone shipped 35 that none does, 76,504 bytes of them (migrate.d.ts was the largest).
The build now prunes them by the closure the TypeScript compiler computes from the entry
points, and a check fails if any shipped declaration imports one that was not shipped
(D-20261011).
It buys no dependency outside the burgee family and seven drop-in front ends. It is still 1607 KB, and the bundled column on Benchmarks — what an application actually grows by — is the number to judge us on. That one is over its own ceiling too, and says so.
Speed. This page previously ran two rows, an import cost and a full run, with burgee's
cells in both unsourced. The suite times one thing, a full CLI run as a process, so there
is one row. burgee does not land level with cac: it starts at 1.47x cac's cold
start against a published target of at or below cac, and that target is recorded as not
met. It read 1.32x on 2026-09-09 and 1.56x on 2026-10-08, and CI runs since 2026-10-06 have
read 1.47x to 1.56x: burgee's start-up got slower in that month, and this page says so rather
than keeping the older figure. Since D-157 the gate is what is reachable: a downward-only ratchet at 1.55x
cac, set from CI's own spread and only ever lowered, which it still meets. The burgee/yargs
drop-in starts at 0.64x yargs (0.82x on 2026-09-09). The burgee/commander drop-in starts at
1.07x commander (1.18x on 2026-10-08, 1.08x on 2026-09-09).
These are the figures from the CI runner, which is where the suite now publishes from: the
same commit measured eleven times there put burgee ÷ cac inside 2.3%, where a developer
laptop under load spread the same ratio from 1.351 to 1.957 in a single day. The absolute
milliseconds are a property of that machine and are not comparable with the two 📖 columns,
which come from a survey on somebody else's. The ratios are what the gates and bands
watch, because a ratio taken between two spawns in the same run cancels the machine out.
Moving to that machine did not uniformly flatter us. burgee ÷ cac improved from 1.45x to
1.32x and commander went from a small loss to a level result; burgee/yargs ÷ yargs moved
against us, from 0.69x to 0.82x. The machine was chosen for its variance, before these
numbers were read.
burgee's speed comes from node:util.parseArgs being in the standard library, not from a
faster language: burgee is TypeScript, exactly like commander and yargs. On this run it buys
a lead over yargs, and neither parity with commander nor a lead over cac.
@oclif/core's 131 ms is a surveyed number, not one this suite reproduces, and it is
carried here rather than dropped because eighteen runtime dependencies against none outside the burgee family is the
comparison this table exists for. Treat it as the survey's, not as ours.
Capabilities, with evidence
burgee against commander, yargs and meow, the three frameworks compat-oracle grades its drop-in paths against.
Every mark links to its evidence — a test in this repository for burgee, and for the others the
source file at the version the oracle grades, or that framework's own test suite — and
scripts/capabilities-lock.test.ts fails the build when a cited test or quoted source stops
saying what the cell claims. A row is here only when every cell of it can be proved.
✓ yes · ◐ partial, with what is missing · ✗ no · — does not apply. Every mark links to its evidence: our test or grade, or the incumbent’s source at the version compat-oracle grades.
Agents and automation
--jsonenvelopeEvery command answers
--jsonwith one{ ok, data }or{ ok, error }document on stdout, from the same declaration as its text output.burgee- burgee: yes
commander- commander: no
yargs- yargs: no
--schema(the CLI as data)Any program prints its commands, arguments, options, choices, defaults and effects as JSON, without running a handler.
burgee- burgee: yes
commander- commander: no
yargs- yargs: no
meow- meow: no
--mcp(an MCP server)Any program serves its commands as MCP tools over stdio, so an agent can call them without a wrapper.
burgee- burgee: yes
commander- commander: no
yargs- yargs: no
meow- meow: no
Agent detection
A program run by a coding agent knows which agent it is and turns interaction off, even when a terminal is attached.
burgee- burgee: yes
meow- meow: no
Exit-code contract
Usage, config, auth, cancellation and runtime failures each exit with their own fixed code, so a script can branch on the code instead of the message.
burgee- burgee: yes
Shell and terminal
Completions for bash, zsh, fish and PowerShell
Every program prints a completion script for each of the four shells, generated from the same declaration as its help.
burgee- burgee: yes
commander- commander: no
meow- meow: no
Cleanup on Ctrl+C
A cleanup a command registered runs when the user presses Ctrl+C, and the process then ends by the signal, which a shell reports as 130.
burgee- burgee: yes
No help screen on a runtime error
A command that fails prints its error, not the usage text, so the message is not buried under help it did not ask for.
burgee- burgee: yes
Configuration
Config, env and flag layers with the winning source named
Values resolve flag over env over config file over package.json over default, and
config explainor--explain <option>says which layer won.burgee- burgee: yes
--versionfrom package.json--versionprints the version from the package.json that owns the entry file, unless the program declares its own.burgee- burgee: yes
yargs- yargs: yes
meow- meow: yes
Compatibility
Passes commander's own test suite
burgee/commanderruns commander's own tests at v15.0.0, unedited apart from the import, so a commander program keeps working after the import changes.commander- commander: yes
Passes yargs' own test suite
burgee/yargsruns yargs' own tests at v18.2.0, unedited apart from the import, so a yargs program keeps working after the import changes.Passes meow's own test suite
burgee/meowruns meow's own tests at v14.1.0, unedited apart from the import, and the count it passes is published.
Capabilities across six frameworks
This table is older and hand-maintained, and covers oclif, cac and citty, which the table above does not grade. Where the two disagree, the one above is the one with evidence: this table said commander dumps its help on a runtime error, and commander's own source says help after an error is off by default.
| burgee | commander | yargs | oclif | cac | citty | |
|---|---|---|---|---|---|---|
| One file, no build step | ✅ (enforced by test) | ✅ | ✅ | ❌ scaffold | ✅ | ✅ |
| Plugins that add commands | ✅ built | ❌ (RFC #2505, closed unlanded) | ❌ | ✅ | ❌ | ❌ |
| Plugins on commander syntax | ✅ built | — | — | — | — | — |
--json envelope | ✅ built | ❌ | ❌ | partial | ❌ | ❌ |
| Exit-code contract | ✅ built | ❌ | ❌ | partial | ❌ | ❌ |
| No help dumped on a runtime error | ✅ built | ✅ | ❌ | ❌ | ❌ | ❌ |
| Weight budget per entry point | ✅ built | ❌ | ❌ | ❌ | ❌ | ❌ |
| commander compatibility | ✅ 1,360 / 1,360 of commander's own tests | — | ❌ | ❌ | ❌ | ❌ |
| yargs compatibility | ✅ 816 / 816 of yargs' own tests | ❌ | — | ❌ | ❌ | ❌ |
--schema (the CLI as data) | ✅ built | ❌ | ❌ | manifest | ❌ | ❌ |
--mcp (an MCP server) | ✅ built | ❌ | ❌ | ❌ | ❌ | ❌ |
| Shell completions | ✅ built | ❌ | partial | ✅ | ❌ | partial |
| Types derived from one schema | ✅ built | partial | partial | ✅ | partial | ✅ |
✅ built · 🔨 in progress
Plugins means a package that adds commands to a program it did not write, which is what
oclif's plugins and burgee's do. citty 0.2.2 has a plugins option, but each one is a pair
of setup and cleanup hooks run around a command and adds no command, so its cell stays
❌.
The three that matter
Plugins. commander has no plugin system — its RFC has been open for years — and yargs has none either. burgee has one, and it works on a program written in commander's syntax: change one import and you gain a capability commander cannot sell you at any price.
Nothing you rely on breaks. Compatibility is graded by commander's own 1,360 tests and yargs' 816, run in our CI, published as a pass rate that only goes up. Not the word "compatible" — a number you can check. (This paragraph said 1,215 and the table said 1,361; both were wrong, and the count the oracle's baseline actually holds is 1,360. It is on Benchmarks, where it is read from the oracle rather than retyped.)
One declaration, every surface. Help, --json, --schema, --mcp, completions and
types are all projections of the same manifest, so they cannot drift, and adding the next
one is an emitter rather than a feature.
What we do not have
Users. commander and yargs do 767M downloads a week between them; burgee is days old. Every number above is real and none of it matters until someone installs it. The compatibility burn-down is published from the first commit precisely so that gap is visible rather than glossed.
Reproducing
The ⚙️ rows come from the suite, not from a shell loop, because a shell loop measures whatever else the machine was doing:
git clone https://github.com/ofri-peretz/burgee && cd burgee
npm install && npx turbo run build
npm run bench -- --checkThat writes benchmarks/results/cli-benchmarks/<date>.json, which carries every number on
Benchmarks along with the resolved version and path of every package it
measured. The 📖 rows are not reproduced by it; they are
competitor-landscape.md
§2's, taken on a different machine.
Supply chain and continuity
What installing each burgee package puts on disk, how releases are published and signed, the licence, and what a fork inherits. Every statement links the check that holds it.
Gallery
Every component and every registered plugin, rendered by running them — the static projection beside the animation, in all five modes.