flagstaff/plugin
Every export of flagstaff/plugin, with its signature and doc comment: validate, register, registered, lookupSpinner, lookupBorder, glyph and 2 more, plus 5 types.
import { validate, register, registered, … } from 'flagstaff/plugin';Functions
glyph
A glyph by meaning; the built-ins define every meaning the built-in components use.
function glyph(meaning: string): string;| Parameter | Type |
|---|---|
meaning | string |
Returns string
lookupBorder
A border by name, for box(); every style the built-ins ship is registered like any other.
function lookupBorder(name: string): BorderStyle;| Parameter | Type |
|---|---|
name | string |
Returns BorderStyle
lookupSpinner
A spinner by name, or a refusal that lists the names that exist.
function lookupSpinner(name: string): SpinnerDef;| Parameter | Type |
|---|---|
name | string |
Returns SpinnerDef
register
The only wiring (R4, U9): validate, then keep every key this package understands. A later plugin's entry replaces an earlier one of the same name, so a user overrides a built-in by registering their own — the built-ins go through this same door first.
function register(plugin: unknown): void;| Parameter | Type |
|---|---|
plugin | unknown |
Returns void
registered
A copy of what has been registered — the docs gallery and flagstaff check are
projections of this. A copy rather than the registry itself, because Readonly<T> freezes
the property bindings and not the Maps behind them: handing the live registry out made
set, delete and clear a second door beside register(), through which a spinner
with no static — or a component with no projection at all — could be put in without
ever meeting validate() (#58). U3's refusal has to be structural to mean anything, so
there is one way in. The values are the frozen objects register() stored, so nothing
reached through here writes back.
function registered(): Readonly<Registry>;Returns Readonly<Registry>
validate
Refuse what the schema refuses, and say why. A missing static gets its own code and
fix, because it is the one mistake a plugin author makes on purpose (U3).
function validate(plugin: unknown): asserts plugin is Plugin;| Parameter | Type |
|---|---|
plugin | unknown |
Returns asserts plugin is Plugin
Classes
PluginError
A refused plugin says what is wrong, where, and what to do about it.
class PluginError extends Error {
readonly code: PluginErrorCode;
readonly fix: string;
constructor(code: PluginErrorCode, message: string, fix: string);
}Constants
CONTRACT
const CONTRACT = 1;Interfaces
BorderStyle
A border style, in cli-boxes' shape exactly, so that corpus imports unchanged.
interface BorderStyle {
topLeft: string;
top: string;
topRight: string;
left: string;
right: string;
bottomLeft: string;
bottom: string;
bottomRight: string;
}Component
What the loop hoists: a static projection, and optionally the animated form.
interface Component<S = unknown> {
name: string;
static(state: S): string;
frame?(t: number, state: S): string;
/** Milliseconds between repaints when `frame` is given; `DEFAULT_INTERVAL` otherwise. */
interval?: number;
/**
* The two states this component is *shown* with by `flagstaff check` and the docs gallery.
* The loop never reads it: a component's real state comes from the program. Declared here
* because a grader that invents a state renders the wrong thing and says `ok` (#59).
*/
sample?: {
running: S;
done: S;
};
}Plugin
The whole plugin contract. Keys another package in the family understands are kept for it.
interface Plugin {
name: string;
contract?: typeof CONTRACT;
tokens?: Record<string, `#${string}`>;
glyphs?: Record<string, string>;
spinners?: Record<string, SpinnerDef>;
borders?: Record<string, BorderStyle>;
components?: Record<string, Omit<Component, 'name'>>;
}SpinnerDef
A spinner style, in cli-spinners' shape plus the projection a pipe prints.
interface SpinnerDef {
frames: string[];
interval: number;
static: string;
}Types
PluginErrorCode
The family's whole refusal vocabulary (plugin-contract R8). One union, and every refusal
any surface of this package prints is a member of it — the registry's five, and the two
flagstaff check adds for what only a renderer can discover: a plugin that validates but
contributes nothing renderable, and a component whose static throws on the state it is
shown with.
The last two lived as bare string literals in cli.ts until 2026-09-09, which is exactly
the hole R8 exists to close: a second host could spell E_COMPONENT_THREW its own way and
nothing would notice. scripts/plugin-error-vocabulary-lock.test.ts now reads this
declaration out of the source and refuses any 'E_…' literal in a host that is not in it.
It stays a type rather than a const array on purpose. R3 forbids one layer importing
another, so a runtime list could not be shared with roundel or caique even if it existed —
the lock has to read the source to work across the family, and it does. A const array
would therefore buy nothing and cost ~195 B on ./spinner, which has 82 B of headroom.
type PluginErrorCode = 'E_PLUGIN_SCHEMA' | 'E_NO_STATIC_PROJECTION' | 'E_PLUGIN_CONTRACT' | 'E_UNKNOWN_SPINNER' | 'E_UNKNOWN_BORDER' | 'E_NO_CONTRIBUTION' | 'E_COMPONENT_THREW' | 'E_UNKNOWN_KIND';