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 9 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, 9 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 all 9 check commands 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
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
yargs — Middleware on one program (.middleware()), and commands loaded from modules (.commandDir())
meow — None
cac — Events on one program (cli.on())
cittysetup and cleanup on each command
hooksburgee lifecycle hooks. preRun opens around a command, and exactly one of postRun or onError closes.
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-core — Custom prompts built with createPrompt
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
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
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
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

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 cover the run of a command and nothing around it. A plugin can act at preRun, postRun, onError — not before arguments are parsed, not inside help, not in config resolution, not at shutdown. 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