flagstaff
API reference

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;
ParameterType
meaningstring

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;
ParameterType
namestring

Returns BorderStyle

lookupSpinner

A spinner by name, or a refusal that lists the names that exist.

function lookupSpinner(name: string): SpinnerDef;
ParameterType
namestring

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;
ParameterType
pluginunknown

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;
ParameterType
pluginunknown

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';

On this page