burgee

Plugins

Every layer takes plugins the same way — a plain object, validated against one published schema, checked by the package's own command before it ships. One object can extend all nine.

Generated by npm run plugins:page from the packages themselves. Do not edit by hand.

Every package in the family takes plugins, and all 10 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 package 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 others, so one object can extend any subset of the family that is installed, including all of it.

One object, 10 layers

A company's CLI conventions, in one file: brand colours, a spinner, a terminal quirk, a terminal capability, a config source, where its tools live, what to flush on exit, a prompt of its own, and a command every one of its CLIs gets.

examples/plugins/acme.mjs
// One plugin object, nine layers. A company's CLI conventions in a single file: brand
// colours, a spinner, a terminal quirk, a config source, where its tools live, what to
// flush on exit, a prompt of its own, a terminal capability and a command every CLI
// gets. Each package reads its own key and ignores the rest, so the same object
// registers into any subset of the family that is installed.
//
// Check it against any layer: `npx roundel check examples/plugins/acme.mjs`, and the
// same for flagstaff, linegauge, paratext, seniority, bellpull, closeout, caique and
// `npx burgee check`. `scripts/plugin-example-lock.test.ts` runs all nine.
export default {
  name: 'acme',
  contract: 1,

  // roundel — the brand, as semantic tokens every styled line reads.
  tokens: { ok: '#1a7f37', error: '#cf222e', hint: '#0969da' },

  // flagstaff — a spinner, with the line a pipe or an agent prints instead.
  spinners: { acme: { frames: ['◐', '◓', '◑', '◒'], interval: 100, static: '◐' } },

  // linegauge — the Nerd Font glyphs this company's prompt draws two columns wide.
  widths: { powerline: { ranges: [[0xe0a0, 0xe0a3], [0xe0b0, 0xe0b3]], columns: 2, why: 'Nerd Font powerline glyphs render wide in the company terminal profile' } },

  // paratext — a terminal capability, with what prints when the terminal lacks it.
  capabilities: { beep: { name: 'beep', osc: 'BEL', when: { tty: true }, encode: '\u0007', fallback: '' } },

  // seniority — a config layer between the flag (0) and the declared default (40).
  sources: { vault: { rank: 25, read: () => undefined, location: 'vault://acme/cli' } },

  // bellpull — where the company's pinned toolchain lives, searched before PATH.
  resolvers: { asdf: { rank: -10, paths: ['{ASDF_DATA_DIR}/shims'], when: { envAny: ['ASDF_DATA_DIR'] } } },

  // closeout — flush telemetry before the process exits, whatever ended it.
  handlers: [{ name: 'flush-telemetry', phase: 'flush', run() {} }],

  // caique — a prompt kind of the company's own, with its non-interactive projection.
  widgets: { rating: { static: () => 'rate 1-5', sample: { running: {}, done: {} } } },

  // burgee — a command every company CLI gets, declared exactly as a program's own.
  commands: [{ path: ['doctor'], description: 'Check this machine against the company toolchain', options: {}, effects: 'read_only', run: () => ({ ok: true }) }],
};

This is a committed file, not an illustration: scripts/plugin-example-lock.test.ts runs 9 check commands and flagstaff's validator against it, and the published schema over it, on every CI run.

What each layer takes

PackageKeyWhat a plugin contributesCheck itWhat the incumbent offers
bellpullresolversbellpull resolvers by name: extra directories searched for an executable, before PATH (negative rank) or after it (positive). Two resolvers with one name shadow; two names both apply, in rank order.npx bellpull check ./plugin.mjscross-spawn — None
which — None
burgeecommandsburgee commands this plugin contributes. Each is read by exactly the code a program's own command is, and a burgee plugin must declare contract.npx burgee check ./plugin.mjscommander — Lifecycle hooks on one program (.hook('preAction')); no plugin shareable across programs — the plugin RFC, #2505, is open
extra-typings — commander's, unchanged: its JavaScript is commander's, and what it adds is types
yargs — Middleware on one program (.middleware()), and commands loaded from modules (.commandDir())
meow — None
cac — Events on one program (cli.on())
citty — setup and cleanup on each command
hooksburgee lifecycle hooks. parse rewrites argv before a command is resolved; preRun opens around a command, and exactly one of postRun or onError closes; shutdown fires once as the program leaves, by any path.
localesburgee locale packs (localised-human-surfaces R4): a BCP 47 language tag, as Intl.Locale writes it, mapped to catalog ids and their translations. A message is a string, or for a message whose English carries a count placeholder, one string per plural form Intl.PluralRules selects for the locale. burgee/locale-pack-schema.json lists every id with its English; burgee check renders a pack or refuses it.
formatsburgee output formats (agent-headroom RF): a name a caller types as --format=<name>, mapped to how a success's data is rendered under it. burgee registers agent and tsv the same way; text, json, json-pretty and every name already registered are taken. A format renders data only, never a failure. --format lists every name; burgee check checks them.
caiquewidgetscaique prompt widgets by kind. The six built-in kinds — text, confirm, select, multiselect, password, path — cannot be replaced: a plugin adds kinds of its own.npx caique check ./plugin.mjsclack — Custom prompts built on @clack/core
inquirer-prompts — A theme per prompt; new prompts are built on @inquirer/core's createPrompt
inquirer-core — Custom prompts built with createPrompt
localesburgee locale packs (localised-human-surfaces R4): a BCP 47 language tag, as Intl.Locale writes it, mapped to catalog ids and their translations. A message is a string, or for a message whose English carries a count placeholder, one string per plural form Intl.PluralRules selects for the locale. burgee/locale-pack-schema.json lists every id with its English; burgee check renders a pack or refuses it.
closeouthandlerscloseout exit handlers. The phase decides the order they run in, not their position in this list.npx closeout check ./plugin.mjsrestore-cursor — None
exit-hook — None — it is the hook
signal-exit — None — it is the hook
onetime — None
cli-cursor — None — the stream is an argument, per call
controlroomkeymapscontrolroom keymaps by name: key spec to action name, and the label each action shows in the hint line. default replaces the built-in keymap.npx controlroom check ./plugin.mjsink — Components are the extension: any React component renders (@inkjs/ui, ink-spinner); no registry, no theme
inkjs-ui — A theme object passed to ThemeProvider, per tree (extendTheme)
panescontrolroom panes by name: the flagstaff component that draws each one, and its label. A tab bar is a pane over flagstaff's tab-bar.
flagstafftokensA roundel theme: semantic token name to a hex colour, contrast-checked when flown.npx flagstaff check ./plugin.mjsora — A custom spinner object, per instance (spinner: { frames, interval })
log-update — None
boxen — A custom border object, per call (borderStyle)
cli-table3 — Custom border characters, per table (chars)
glyphsSymbols by meaning: ok, fail, warn, info, running. A plugin that ships glyphs changes every built-in that draws one.
spinnersSpinner styles by name, in cli-spinners' shape plus the static projection.
bordersBorder styles a box can be drawn with, by name.
componentsComponents by name: static(state) returns the text a pipe, an agent or a screen reader gets; frame(t, state) is the optional animated form.
linegaugewidthslinegauge width overrides by name — the measurement section of a plugin, read the way flagstaff reads spinners.npx linegauge check ./plugin.mjsstring-width — An option per call (ambiguousIsNarrow); no per-code-point override
strip-ansi — None
wrap-ansi — Options per call
slice-ansi — None
cli-truncate — Options per call
widest-line — None
paratextcapabilitiesparatext capabilities by name — the OSC section of a plugin, read the way flagstaff reads spinners.npx paratext check ./plugin.mjsansi-escapes — None
terminal-link — A fallback function, per call
term-img — A fallback function, per call
roundeltokensA roundel theme: semantic token name to a hex colour, contrast-checked when flown.npx roundel check ./plugin.mjschalk — None — styles are code; there is no theme to register
colorette — None — createColors({ useColor }) per instance, and nothing to register
kleur — None — one enabled switch on the module object, and nothing to register
picocolors — None — createColors(enabled) per instance, and nothing to register
supports-color — Options per call (createSupportsColor(stream, { sniffFlags })); nothing to register
yoctocolors — None — 61 plain functions, and nothing to register
senioritysourcesseniority configuration sources by name, ranked against the built-in layers: a flag is 0 and a declared default is 40, and a plugin source sits strictly between.npx seniority check ./plugin.mjsrc — A custom parse function, per call
cosmiconfig — Custom loaders per file extension and searchPlaces, per explorer
lilconfig — Custom loaders per file extension, per call
dotenv — None
find-up — A matcher function, per call, in place of a name

The schema for every key above ships as <package>/schema.json. An editor, a validator or a model can read it without running anything.

What a plugin cannot do yet

  • burgee's hooks stop at help and config. A plugin can act at parse, preRun, postRun, onError, shutdown — rewriting argv before a command is resolved, around the run, and once as the program leaves — but not inside help rendering and not in config resolution. Contributing commands has no such limit.
  • flagstaff's built-in components are not contributions. A plugin adds spinners, borders and components of its own; it cannot replace progress, box or table.
  • The schema cannot say "function". static, run, read and handler are required and described; that each is a function is checked by the package, not by the schema.
  • Whether a model writes a working plugin in one turn is unmeasured. Each package has an eval case that asks exactly that, proven to tell a working plugin from a broken one; none has run against a model yet.

On this page