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.
// 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
| Package | Key | What a plugin contributes | Check it | What the incumbent offers |
|---|---|---|---|---|
| bellpull | resolvers | bellpull 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.mjs | cross-spawn — None which — None |
| burgee | commands | burgee 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.mjs | commander — Lifecycle hooks on one program (.hook('preAction')); no plugin shareable across programs — the plugin RFC, #2505, is openextra-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 |
hooks | burgee 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. | |||
locales | burgee 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. | |||
formats | burgee 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. | |||
| caique | widgets | caique 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.mjs | clack — Custom prompts built on @clack/coreinquirer-prompts — A theme per prompt; new prompts are built on @inquirer/core's createPromptinquirer-core — Custom prompts built with createPrompt |
locales | burgee 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. | |||
| closeout | handlers | closeout exit handlers. The phase decides the order they run in, not their position in this list. | npx closeout check ./plugin.mjs | restore-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 |
| controlroom | keymaps | controlroom 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.mjs | ink — Components are the extension: any React component renders (@inkjs/ui, ink-spinner); no registry, no themeinkjs-ui — A theme object passed to ThemeProvider, per tree (extendTheme) |
panes | controlroom panes by name: the flagstaff component that draws each one, and its label. A tab bar is a pane over flagstaff's tab-bar. | |||
| flagstaff | tokens | A roundel theme: semantic token name to a hex colour, contrast-checked when flown. | npx flagstaff check ./plugin.mjs | ora — 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) |
glyphs | Symbols by meaning: ok, fail, warn, info, running. A plugin that ships glyphs changes every built-in that draws one. | |||
spinners | Spinner styles by name, in cli-spinners' shape plus the static projection. | |||
borders | Border styles a box can be drawn with, by name. | |||
components | Components by name: static(state) returns the text a pipe, an agent or a screen reader gets; frame(t, state) is the optional animated form. | |||
| linegauge | widths | linegauge width overrides by name — the measurement section of a plugin, read the way flagstaff reads spinners. | npx linegauge check ./plugin.mjs | string-width — An option per call (ambiguousIsNarrow); no per-code-point overridestrip-ansi — None wrap-ansi — Options per call slice-ansi — None cli-truncate — Options per call widest-line — None |
| paratext | capabilities | paratext capabilities by name — the OSC section of a plugin, read the way flagstaff reads spinners. | npx paratext check ./plugin.mjs | ansi-escapes — None terminal-link — A fallback function, per callterm-img — A fallback function, per call |
| roundel | tokens | A roundel theme: semantic token name to a hex colour, contrast-checked when flown. | npx roundel check ./plugin.mjs | chalk — None — styles are code; there is no theme to register colorette — None — createColors({ useColor }) per instance, and nothing to registerkleur — None — one enabled switch on the module object, and nothing to registerpicocolors — None — createColors(enabled) per instance, and nothing to registersupports-color — Options per call ( createSupportsColor(stream, { sniffFlags })); nothing to registeryoctocolors — None — 61 plain functions, and nothing to register |
| seniority | sources | seniority 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.mjs | rc — A custom parse function, per callcosmiconfig — Custom loaders per file extension and searchPlaces, per explorerlilconfig — Custom loaders per file extension, per calldotenv — 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,boxortable. - The schema cannot say "function".
static,run,readandhandlerare 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.
Width and text correctness
How linegauge counts the columns a string occupies: grapheme clusters, East Asian Width from Unicode 17 tables, escape sequences that take no columns, and budgets that keep the pathological inputs fast.
Packages
burgee and the 10 packages it is built from: what each migrates from, and the site its docs live on.