PluginWorld
Bu

burgee

MCP

A CLI framework built on one declaration: help, --json, --schema, MCP, completions and types are projected from it, with exit codes and fix: lines an agent can act on. No dependency outside the burgee family. Drop-in paths for commander, yargs and meow.

@ofri-peretz · v0.0.0 · MIT · updated today

SECURITY

B

SCORE

65

INSTALLS

▲ 5.9K

PLUG IN

git clone https://github.com/ofri-peretz/burgee.git

See the README to configure this MCP server

README

burgee

Everything a CLI needs that isn't your CLI. Written once, served to humans and agents alike.

npm version npm downloads Quality Gate CodeQL Coverage OpenSSF Scorecard Five dependencies, all in the burgee family: no dependency outside the burgee family Node.js 20.19+ or 22.13+ TypeScript License: MIT GitHub stars

One declaration, six surfaces. Declare a command once and burgee serves it as help, a --json envelope, a versioned --schema, an MCP server, shell completions and TypeScript types. Nothing is written twice, so no surface can drift from another.

Contracts an agent can act on. Exit code 2 means rewrite the command; any other failure means the command was right and the world was not. A mistyped command comes back with a fix: line the agent can run as written, and --explain says which flag, environment variable, config file or default each value came from.

One supply chain. Ten packages, one repository, one release pipeline, and no dependency outside the burgee family in any of them. Six take nothing at all; the other four take only each other.

Proved by the incumbents' own tests. Each drop-in path is graded by the original package's own test suite, run in CI beside a control that runs the real package: commander's 1360 of 1360, yargs' 816 of 816, and 36 more hosts on the compatibility page. The benchmarks below publish every comparison, the rows burgee loses included.

⭐ Star the repo  ·  👀 Watch releases  ·  🐛 Report a bug

📖 Docs: burgee.interlace.tools  ·  llms.txt

Start here  ·  Switch in one command  ·  Measured  ·  The family  ·  Status  ·  Repo map  ·  Research


👋 Start here

npx burgee init my-cli && cd my-cli && npm install

init writes this file as cli.mjs, and a package.json that runs it. Nothing else.

#!/usr/bin/env node
// cli.mjs — the whole CLI
import { defineCommand, run } from 'burgee';

run(defineCommand({
  name: 'greet',
  description: 'Greet someone by name',
  options: { name: { type: 'string', required: true, description: 'who to greet' } },
  effects: 'read_only', // what running it does to the world; required, and what --mcp reads
  run: ({ options }) => ({ greeting: `hello, ${options.name}` }),
}));
$ node cli.mjs --name ada
greeting: hello, ada

$ node cli.mjs --name ada --json
{"ok":true,"data":{"greeting":"hello, ada"},"meta":{"provenance":{"name":{"source":"flag","location":"--name"}}}}

$ node cli.mjs                                    # exit 2, not 1
error: missing required option --name
hint: pass --name <value>
usage: greet [options]
options:
  --name <value>  who to greet (required)

That is the whole thing: one file, one declaration, and a CLI that already speaks to a person, to a script and to an agent.

A burgee is the small swallowtail flag a boat flies to say which club it belongs to — a flag of identity, not of instruction. That is what a command does here: declares itself once, and every surface is that declaration read by a different reader.


🎏 One declaration, every surface

defineCommand()  ──▶  manifest  ──┬──▶  human help
                                  ├──▶  --json      one stable envelope
                                  ├──▶  --schema    versioned, one document per surface
                                  ├──▶  --mcp       an MCP server, generated
                                  ├──▶  completions bash · zsh · fish · pwsh
                                  ├──▶  TypeScript types
                                  └──▶  docs + llms.txt

Surfaces cannot drift, because there is nothing to keep in sync. Adding the next one is an emitter, not a feature.


🤖 What an agent sees

The same command, asked for its own definition. No wrapper, no sidecar file, no scraping --help:

$ node cli.mjs --schema     # trimmed; arguments and examples also come back
{
  "options": {
    "name": { "type": "string", "required": true, "description": "who to greet" }
  },
  "inputSchema": { "type": "object", "properties": { "name": { "type": "string" } } }
}

$ node cli.mjs --mcp        # the same command, served as an MCP server over stdio

Every --schema document validates against burgee/program-schema.json, the JSON Schema the package publishes, so a project can pin its CLI's surface in CI without writing its own idea of the shape.

Six issues across nine years of the commander and yargs trackers ask for machine-readable command structure, and the state of the art today is a regex over --help (tracker-gap-analysis.md). A manifest is not a feature bolted on for agents — it is the thing help was rendered from in the first place.

What that is worth needs no model to measure. Ten tasks per variant, one spawn each, non-TTY with stdin closed — the only environment an agent gets — running the same demo program on each engine:

Variant hangs / 100 exit code correct --json answered
burgee 0 100.0% 100.0%
commander 0 40.0% 25.0%
yargs 0 40.0% 25.0%

Exit code is the one that decides an agent's next move: 2 means rewrite the command, any other non-zero means the command was fine and the world was not. This measures legibility, not tokens. The token and turn halves of B1 run a model against the same program on every push to main. What an agent sees quotes those sessions and lists every run, including the ones where the claim in Measured misses.


🔌 Extend every layer

Every released package in the family takes plugins, and all nine take them the same way. A plugin is a plain object. Each package validates it against one published schema — the same schema.json ships in every package — and each has a check command that shows what a plugin contributes, or refuses it with a code and the fix, before it ships.

Each package reads its own key and ignores the rest, so one object can extend every layer at once: brand colours, a spinner, a terminal quirk, a config source, where the company's tools live, what to flush on exit, a prompt of its own, and a command every one of its CLIs gets.

Package A plugin adds Check it
bellpull resolvers npx bellpull check ./plugin.mjs
burgee commands, hooks, locales npx burgee check ./plugin.mjs
caique widgets, locales npx caique check ./plugin.mjs
closeout handlers npx closeout check ./plugin.mjs
controlroom keymaps, panes npx controlroom check ./plugin.mjs
flagstaff tokens, glyphs, spinners, borders, components npx flagstaff check ./plugin.mjs
linegauge widths npx linegauge check ./plugin.mjs
paratext capabilities npx paratext check ./plugin.mjs
roundel tokens npx roundel check ./plugin.mjs
seniority sources npx seniority check ./plugin.mjs

Where an incumbent has an extension point — commander's .hook(), yargs middleware, cosmiconfig's loaders, inquirer's createPrompt — it lives in one program or one call. A plugin here is written once and shared: across programs, across layers, and across commander, yargs and native syntax alike. The plugins page has the whole nine-layer example, which every released package's check accepts in CI, each incumbent's own extension point beside ours, and what a plugin cannot do yet.


🔁 Switch in one command

npx burgee migrate --dry-run   # what it would change, and nothing written
npx burgee migrate

burgee has drop-in paths for commander and yargs, and the family has them for chalk, ora, string-width, cross-spawn, signal-exit and the other packages graded level with their own suites. burgee migrate rewrites the import specifiers and nothing else, then prints the install command to run next (Migrate). For one commander program, that is one line:

- import { Command } from 'commander';
+ import { Command } from 'burgee/commander';

Your code and your tests are unchanged. Compatibility is not asserted here, it is graded — each host's own suite, vendored unmodified apart from the import specifier, pointed at burgee and run in CI against a control that runs the real host:

Host Front-end burgee rate
commander burgee/commander 1360 / 1360 100%
yargs burgee/yargs 816 / 816 100%

Measured 2026-09-09 (yargs 2026-09-30, at 18.2.0); 36 more hosts are in the full table, generated by npm run compat:page and never hand-edited. Rates are measured against the control's total rather than against registered tests, because a file that fails to import registers as one test instead of its twenty and would flatter a partial implementation.

What changes after the switch is what the program can do. It answers --json, --schema and --mcp, and it takes plugins. commander has no plugin API: its RFC (#2505) was closed for inactivity on 2026-10-09 without one. yargs offers middleware on one program, not a plugin shared across programs. A plugin contributes to the manifest, and the manifest never records which façade filled it, so one plugin works on commander syntax, yargs syntax and native alike.


🧭 It starts as one file

oclif has every capability on this page and does 10.9M downloads a week against commander's 508M. The difference is not features, it is shape. So the shape is locked first, ahead of every other requirement, and Z1 is a test: it installs the published tarball into a temp directory, writes exactly one file, and runs it. The day that needs a second file, a config or a build step, CI goes red.

Everything beyond that first rung — precomputed manifests, plugins, the dev loop, scaffolding — is additive and removable.


📏 Measured

2026-10-08 (ba8a89c), a GitHub Actions runner (AMD EPYC 7763, 4 cores), linux/x64, Node 24, 52 interleaved spawns per variant. † is a published figure taken on another machine, not reproduced here.

burgee commander yargs @oclif/core cac
Runtime dependencies 5, none outside the burgee family 0 6 18 0
Full CLI run over bare node +20.3 ms +14.8 ms +75.0 ms +131 ms † +3.8 ms
Installed size 1690 KB 203 KB 515 KB 912 KB † 40 KB

The speed comes from node:util.parseArgs being in the standard library, not from a faster language: burgee is TypeScript, like both incumbents.

Milliseconds are a property of the machine that produced them, so nothing gates on them. What is gated is the ratio between two spawns interleaved in the same run, which cancels the machine out. Ten of those gates are stated in public — eight are met, three of them as downward-only ratchets rather than the bar they started with; one is not met; and one is not settled yet, because its measure changed:

Claim Gate Measured
the core entry point is under 52 KB bundled core-under-52kb-bundled 24,484 bytes ✅ met
burgee/yargs is lighter in a user's bundle than yargs lighter-than-yargs 0.974× ✅ met
burgee is lighter than cac plus what a cac user installs to match it lighter-than-cac-at-parity 0.251× ✅ met
burgee/commander is lighter than commander plus the same lighter-than-commander-at-parity 0.438× ✅ met
burgee/yargs is lighter than yargs plus the same lighter-than-yargs-at-parity 0.547× ✅ met
burgee starts within 1.55× of cac, the lightest framework in the landscape (ratchet; lowered as it speeds up) cold-start-at-or-below-cac 1.443× ✅ met
burgee/commander stays within 1.42× of commander alone in a user's bundle (ratchet; lowered as it shrinks) lighter-than-commander 1.415× ✅ met
burgee stays within 2.35× of cac alone in a user's bundle (ratchet; lowered as it shrinks) lighter-than-cac 2.343× ✅ met
an agent spends ≥40% fewer tokens over the benchmark tasks (total over every task-run) agent-tokens-40pct 0.699× on the newest CI run (e6cff20); 0.549–0.699 over the 12 runs since B1 installs the demo under its printed name, met on 5 of them ❌ not met
an agent takes ≥30% fewer turns over the benchmark tasks (total over every task-run) agent-turns-30pct 0.677× on the newest CI run (e6cff20); 0.583–0.703 over the same 12 runs, met on 9 of them unmeasured as a record until the next CI run; the figures are its per-task detail, summed

The two agent claims changed measure on 2026-10-09 (D-20261009-b1-totals-and-explain). They compared pooled medians until then, and the median ratio was decided by commander's luck: burgee holds at 3 turns, while commander's median lands at 4 or 6 from run to run, so the same build read 0.5 or 0.75. They now compare the tokens and turns each CLI spends, summed over all 25 task-runs. The figures above are those sums, recomputed from each landed run's per-task detail. On the old measure the newest run read 0.492 tokens and 0.5 turns, both met.

The two ways to ask the bundle question, and why both are here

burgee is 24,484 bundled bytes and cac is 10,452, so the bare row reads 2.343× and it stays on this page. It is also not the choice anyone makes. A program that picks cac and then wants its config file read, its shutdown bounded on every path out, and its cursor handed back on Ctrl-C installs three more packages — and that is what one import of burgee competes with:

the incumbent alone + what you add to match burgee ours
cac 10,452 B 97,692 B 24,484 B
commander 39,084 B 126,335 B 55,293 B
yargs 111,686 B 198,784 B 108,736 B

The additions are cosmiconfig (find and load a config file), exit-hook (run cleanup on every path out, including a signal) and restore-cursor (hand the terminal back), bundled together so a module two of them share is paid for once — which is what a real bundler does.

The rule that stops this being a rigged denominator: a package may only enter a stack where this repository publishes a graded drop-in for it — a compat-oracle row whose pass rate comes from that package's own test suite. seniority/cosmiconfig, closeout/exit-hook and closeout/restore-cursor each have one, which is evidence we do the job rather than our word for it, and parity.test.ts fails if an addition names a package with no active row. It is self-limiting on purpose: we cannot pad a stack with packages we merely resemble.

And the capabilities with nothing to add against — --schema, --mcp, the { ok, data } envelope on every command, agent detection, option relations and Standard Schema — are priced at zero. They are the part of the premium that is real and unpriced, and saying so is worth more than finding a package to charge for them.

The three that are ratchets

cold-start-at-or-below-cac and the two bare weight rows began as ≤ 1 and have a measured floor above it, and it is worth saying plainly rather than leaving as a to-do. cac is 10,452 bytes of parser and help renderer; burgee's 24,484 is that plus coercion, choices, relations, Standard Schema, configuration precedence, signal-bound shutdown, terminal restore and agent detection. Our commander/command.js is 33,487 bundled against commander's 27,226, and the front-end also carries a cross-platform spawn that cannot go lazy without giving up parse()'s synchronous contract and the 1360 / 1360 compat row that rests on it. And import 'cac' is one file in 4.2 ms where import 'burgee' is twenty-one in 20 ms. Closing them means deleting the product, not optimising it: the analysis is D-102, and the shrink that moved every non-default path behind await import() is D-148.

So a bar we will never meet is no longer what these rows publish. Since D-157 each is a downward-only ratchet: a ceiling just above today's measurement, enforced on every benchmark run, that may be lowered by anyone and raised only by a new decision.

Gate Was Ceiling Set from, on 2026-09-27
cold-start-at-or-below-cac ≤ 1 1.55× lowered 2026-10-11 after #898: the 26 CI runs since, mean 1.424, max 1.463; mean + 3 sd, rounded up to 0.05
lighter-than-commander ≤ 1 1.42× lowered 2026-10-19 after commander-front-end-diet: measured 1.416 in CI and locally, plus 80 B for CI's heavier build, rounded up to 0.005 (first set at 1.565)
lighter-than-cac ≤ 1 2.35× measured 2.317, plus 80 B for CI's heavier build, rounded up to 0.05

The ceilings live in claim-ratchets.json with their history, and claim-ratchets-lock.test.ts refuses a step up that does not cite a newer decision. npm run ratchets:propose prints the lower ceiling each can move to from the latest measurement. The size comparison to lead with is still the at-parity rows above; the bare rows stay on the page because a suite that only showed the comparisons we win would be lying by selection.

The agent rows are a different gap. B1 has run once, locally (D-147), and neither row becomes ✅ until the weekly CI job measures it, which waits on a credential only the owner can mint (D-150).

Installed size is our largest number and it is larger than commander's. It buys no dependency outside the burgee family and six drop-in front ends, and it stays on the page either way: not met and unmeasured are different outcomes and neither collapses into the other. Every bundle figure in this section, including the ones that go against us, is written by npm run readme:gates from a fresh bundle of the build, and a push whose build measures anything else is refused (readme-gates-lock.test.ts). The millisecond and installed-size figures come from the measurement published in benchmarks.mdx and pinned in comparison.mdx.


🔐 Supply chain and continuity

npm i burgee installs six packages, all of them from this repository, and installing any family package runs no install script (install-graph-lock). Every release is published through npm trusted publishing with SLSA provenance and no npm token (trusted-publishing-lock). Everything is MIT, and each incumbent's own test suite is vendored in packages/compat-oracle, so a fork keeps the conformance tests that tell it whether it still behaves the same. Supply chain and continuity has the install graph for each package and the check behind every statement. Why it's built this way gives the reasoning behind the family, and what it does not claim.


👥 Who is this for?

You are… What burgee gives you
Shipping your first CLI One file, no dependency outside the burgee family, help and --json for free
Maintaining a commander CLI A one-line import swap, graded against commander's own suite
Building for agents --schema, --mcp and a stable envelope, projected — never hand-written
On a platform team Plugins that work across commander, yargs and native syntax alike

🧩 The family

burgee roundel flagstaff caique
linegauge seniority bellpull closeout paratext controlroom

One family, cut from one ink, with the Interlace mark on every one of them and one light crossing all ten. Only burgee flies the swallowtail: a roundel is rings, a flagstaff is a flag hoisted on a pole, a caique is a parrot, a line gauge is the printer's rule with its ticks cut through it, seniority is rank chevrons, a bellpull is the cord and its pull, a closeout is the double rule an accountant draws under a settled total, and a paratext is a page with its text cut away — only the margins are left — and a control room is a console screen with its panes cut through it. The light sweeps where a page can afford motion and parks itself under prefers-reduced-motion; the favicon and the npm READMEs take the still one. Every mark is burgee/brand output — ten shapes, one declaration — regenerated by npm run brand and drift-checked in CI.

burgee declares; the output stack is what a CLI shows; the foundation is what it stands on. Each package is an independent product with its own README, installs on its own, and declares no dependency outside the burgee family.

Ten packages, one repository, one release pipeline and one supply chain to audit, with a single schema byte-identical in every tarball. Six leaves depend on nothing, and every other dependency points one way, down the layers; the locks that hold each rule are named there. Releases are published through npm trusted publishing, with SLSA provenance and no npm token (trusted-publishing-lock). The case for one codebase is the number of things a user has to trust, not convenience.

Layer Package What the layer owns Migrates from Status
Engine argv, dispatch, manifest burgee one declaration projected to help, --json, --schema, MCP, completions, types commander · yargs · meow released — burgee@1.6.7
Output stack colour roundel one output policy, nine semantic tokens, a contrast-checked theme, and chalk's API over them chalk · picocolors released — roundel@1.2.0
render flagstaff frame loop with a static projection; plugin host for spinners, progress, boxes, tables ora · log-update · boxen · cli-table3 released — flagstaff@1.7.3
prompt caique prompts that are flags first, and never hang inquirer · clack · prompts released — caique@1.4.5
screen controlroom keyboard-driven screens, inline or full-screen, over flagstaff's loop, each with a static projection ink · @inkjs/ui released — controlroom@1.0.6
Foundation text linegauge measure, wrap, truncate and slice styled text without the edge fraying string-width · wrap-ansi · strip-ansi · slice-ansi released — linegauge@1.1.4
config seniority precedence across flag, env, project file, home file and default — with provenance cosmiconfig · lilconfig · dotenv · rc released — seniority@1.1.0
process bellpull run a subprocess; resolve the executable; return a result every caller can read execa · cross-spawn · which released — bellpull@1.0.9
lifecycle closeout exit handlers that run once on every path, terminal restore, bounded deadline signal-exit · exit-hook · restore-cursor released — closeout@1.1.7
terminal paratext hyperlinks, images, window title, clipboard, notifications, bell — each with a static fallback ansi-escapes (OSC half) · terminal-link · term-img released — paratext@1.0.10

All ten are released on npm at 1.x, with their public API under semver (SECURITY.md says which releases are supported). Where an incumbent's own test suite has been vendored, the compat oracle grades the drop-in path against it and publishes the rate — including the ones not yet at 100% — on the compatibility page. Released is not the same as accepted: the four foundation packages began as 0.0.1 name reservations, were built out in waves F1–F4, and their intents under .sdlc/intents/cli-foundation-stack/ are still at draft — the human gate on the design has not run. The bellpull intent carries a kill gate, because a zero-dependency rival already holds the weight pitch in that layer, and its spec says plainly that the package was built before that gate was evaluated. The measurements behind the layers are in candidate-layers.md and replacement-map.md.


📚 Repo map

Path Purpose
packages/burgee/ The framework. burgee is the engine, burgee/commander the compat façade, burgee/testing the in-process harness (T1).
packages/roundel/ roundel — the colours a CLI carries: roundel/policy (outputMode, colorLevel), roundel/tokens (nine semantic tokens), roundel/theme (fly(), contrast-checked), roundel/contrast, and roundel/chalk — chalk 6's API over the tokens, graded by chalk's own suite (58 / 58, against a 58 / 58 control). Intent in .sdlc/intents/roundel/.
packages/flagstaff/ flagstaff — the staff the flag flies from: a frame loop with a static projection for agents, and the plugin host for spinners, progress, boxes and tables. Released; its one dependency is roundel. Intent in .sdlc/intents/flagstaff/.
packages/caique/ caique — the parrot that always answers back: prompts that are flags first and never hang. Released, with graded caique/inquirer and caique/clack paths; intent in .sdlc/intents/caique/.
packages/linegauge/ linegauge — a printer's rule for text: width, wrap, truncate and slice, grapheme-correct over Intl.Segmenter. Released; intent in .sdlc/intents/linegauge/.
packages/seniority/ seniority — which source outranks which, with provenance for every resolved value. Released; intent in .sdlc/intents/seniority/.
packages/bellpull/ bellpull — pull here, work happens there: subprocesses with a structured result and a static projection. Released; intent in .sdlc/intents/bellpull/.
packages/closeout/ closeout — settle and finish: exit handlers that run once, terminal restore, and a deadline so shutdown cannot hang. Released; intent in .sdlc/intents/closeout/.
packages/paratext/ paratext — everything around the output that is not the output: hyperlinks, images, window title, clipboard, notifications and the bell, each with a static fallback. Released; intent in .sdlc/intents/paratext/.
packages/controlroom/ controlroom — where a system is run from: full-screen, keyboard-driven terminal screens with a static projection for every other caller, and controlroom/ink, ink's drop-in, graded by ink's own suite. Released; intent in .sdlc/intents/controlroom/.
packages/compat-oracle/ Internal, never published. Grades compatibility using the hosts' own suites, plus reference drivers that run the real incumbents for byte-for-byte comparison.
examples/ Demo CLIs and the conformance suite that runs every floor case on every host.
apps/docs/ The front-door documentation site (Next.js + fumadocs), deployed at burgee.interlace.tools with llms.txt and a Markdown twin of every page. Every other package has its own site at https://<package>.interlace.tools — apps/docs-<package>/, on the shared chassis apps/docs-chassis/ — named once in .github/vercel-apps.json.
.sdlc/intents/ Stage 1 + 2 artifacts of the AI-native SDLC: intent.md + spec.md per change, and the wave plan.
.sdlc/research/ The evidence everything above rests on.
.sdlc/brand/ What each package is and what its mark has to say (identity model), and the brief a designer would work from.

The research

Every claim in the design traces to one of these, and each records what it could not determine as well as what it found.

  • competitor-landscape.md — the measured map: downloads, cold start, the compatibility bill, and why we implement rather than wrap.
  • tracker-gap-analysis.md — every open item in both trackers. Commander has 8; yargs has 211, 86% predating 2023. Six issues over nine years ask for machine-readable command structure, and the state of the art is regex-scraping --help.
  • agent-requirements.md — what agents need, and three widely-quoted claims that do not survive verification.
  • cli-market-requirements.md — ten production CLIs taken apart, including what the security market requires and where both leading scanners get it wrong.
  • architecture.md — the diagrams.
  • lineage.md — the six projects we follow, each reduced to a commitment.

🚦 Status

What
✅ Working today The engine, the exit-code contract, the JSON envelope, help from the manifest, plugins with hook filters, and a burgee/commander façade that runs a real commander program
🔨 Next The compatibility oracle, and the remaining commander surface
⛔ Not yet true The dev loop, prompts, lazy commands and groups, and an adopter we did not write ourselves

Four locks — shape, process-reference, weight per entry point, and the adoption ladder — are each proven to fail before they passed. A compat façade does not reach 1.0 until its host's own suite passes 100% (C7): both do today, which clears that gate and not the rest — burgee's 1.0 waits on the floor, 114 requirements of which the surfaces, the env/config/schema families and both façades are built. Until then the rate is published rather than the word "compatible" claimed. linegauge and flagstaff have already met every written 1.0 criterion and are 1.0: every requirement in each spec is built (13 of 13 and 12 of 12), and each drop-in path passes its incumbent's own suite in full (D-170).


🛠️ Working on burgee

npm install
npm test           # every lock and unit test, exits non-zero on failure
npm run dev        # docs on http://localhost:3100
npm run compat     # grade burgee against commander's and yargs' own suites
npm run brand      # regenerate every brand surface from one declaration
npm run brand:check # …and fail if any of them was hand-edited since

Dogfooding

This repo runs 12 Interlace ESLint plugins with every rule on at error and zero warnings allowed:

Scope Plugins
Everywhere secure-coding · node-security · conventions · import-next · maintainability · modernization · modularity · operability · reliability
The docs app react-a11y · react-features · browser-security

The rule list is computed from each plugin's own table, so a rule shipped in a plugin release is on here the day it lands — browser-security alone brings 41. Every exception is named in eslint.config.mjs with its reason.

The three scoped to apps/ are scoped for a reason: the engine never runs in a browser, so storage, postMessage and DOM-sink rules have nothing to say about it — but the docs app is a real browser application, and that is exactly the surface they grade.

It earns its keep. During this project those rules caught a prototype-pollution vector in our own option parsing, a barrel import that cost ~5ms of startup, and a façade reaching for process behind the runtime seam.


🤝 Contributing

The most useful thing anyone can send is an adopter we did not write: a real CLI ported over, and a report of what broke. That is the only compatibility evidence that counts, and every failure becomes a case in the conformance suite.

CONTRIBUTING.md has the loop, how a change is shaped before it is written, and what the checks will hold you to. SECURITY.md is the private reporting path — please do not open a public issue for a vulnerability.


📄 License

MIT © Ofri Peretz — see LICENSE. Every published package ships the licence text in its tarball.

Flying the flag for CLIs that humans and agents can both read.

SIMILAR PLUGINS