burgee

Gallery

Every component and every registered plugin, rendered by running them — the static projection beside the animation, in all five modes.

Generated by npm run gallery:page from what flagstaff has registered. Do not edit by hand.

Every cell on this page is produced by running the component, through the same hoist() a program uses and the same code path as flagstaff check — not by describing it. Escapes are shown as , carriage returns as and newlines as , so what a terminal would swallow is visible.

The five modes

One component, five answers. The tty row is the animation; every other row is the static projection, and it is the artifact — what a pipe, a CI log, an agent and a screen reader all read. Notice that no other row contains an escape sequence.

spinner

The running glyph and the text, then the settled glyph — never a frame.

modewhat it writes
tty␛[?25l⠋ building␛[1G␛[0J⠙ building␛[1G␛[0J⠹ building␛[1G␛[0J⠸ building␛[1G␛[0J✔ built⏎ ␛[?25h
pipe… building⏎ ✔ built⏎
ci… building⏎ ✔ built⏎
json{"event":"spinner","state":{"text":"building"}}⏎ {"event":"spinner","state":{"text":"built","status":"ok"}}⏎
accessible… building⏎ ✔ built⏎

progress

The count and the percentage; a bar of blocks says nothing in a log.

modewhat it writes
tty␛[?25l█████░░░░░░░ 12/30 files␛[1G␛[0J30/30 files · 100%⏎ ␛[?25h
pipe12/30 files · 40%⏎ 30/30 files · 100%⏎
ci12/30 files · 40%⏎ 30/30 files · 100%⏎
json{"event":"progress","state":{"done":12,"total":30,"label":"files"}}⏎ {"event":"progress","state":{"done":30,"total":30,"label":"files"}}⏎
accessible12/30 files · 40%⏎ 30/30 files · 100%⏎

tasks

Only what has settled, so a pipe is not told twice that a step is running.

modewhat it writes
tty␛[?25l✔ install⏎ ⠋ build␛[1G␛[1A␛[0J✔ install⏎ ⠙ build␛[1G␛[1A␛[0J✔ install⏎ ⠹ build␛[1G␛[1A␛[0J✔ install⏎ ⠸ build␛[1G␛[1A␛[0J✔ install⏎ ✔ build⏎ ␛[?25h
pipe✔ install⏎ ✔ build⏎
ci✔ install⏎ ✔ build⏎
json{"event":"tasks","state":{"tasks":[{"title":"install","status":"ok"},{"title":"build","status":"running"}]}}⏎ {"event":"tasks","state":{"tasks":[{"title":"install","status":"ok"},{"title":"build","status":"ok"}]}}⏎
accessible✔ install⏎ ✔ build⏎

box

The title and the text; a border is noise a screen reader reads character by character.

modewhat it writes
tty␛[?25l╭─ dev ──────────────────────────╮⏎ │ starting │⏎ ╰────────────────────────────────╯␛[1G␛[2A␛[0Jdev: ready on :3000⏎ ␛[?25h
pipedev: starting⏎ dev: ready on :3000⏎
cidev: starting⏎ dev: ready on :3000⏎
json{"event":"box","state":{"text":"starting","title":"dev"}}⏎ {"event":"box","state":{"text":"ready on :3000","title":"dev"}}⏎
accessibledev: starting⏎ dev: ready on :3000⏎

table

One line per row of header-and-value pairs, parseable without knowing the drawing.

modewhat it writes
tty␛[?25l┌──────┬───────┐⏎ │ host │ tests │⏎ ├──────┼───────┤⏎ │ ora │ 99 │⏎ └──────┴───────┘␛[1G␛[4A␛[0Jhost: ora, tests: 99⏎ host: log-update, tests: 99⏎ ␛[?25h
pipehost: ora, tests: 99⏎ host: log-update, tests: 99⏎
cihost: ora, tests: 99⏎ host: log-update, tests: 99⏎
json{"event":"table","state":{"rows":[["ora","99"]]}}⏎ {"event":"table","state":{"rows":[["ora","99"],["log-update","99"]]}}⏎
accessiblehost: ora, tests: 99⏎ host: log-update, tests: 99⏎

Spinner styles

What the registry holds right now. static is what every mode but tty prints; a style without one is refused at register().

stylestaticframesinterval
dots 80 ms
line- \ | /130 ms

Two, because two is what the package ships. The corpus everyone already has is one line away and is not bundled — fromCliSpinners(cliSpinners) adds 88 more (dots2, dots3, dots4, dots5, dots6, dots7, …), each with as its static projection unless you say otherwise:

import cliSpinners from 'cli-spinners';
import { fromCliSpinners } from 'flagstaff/import';
import { register } from 'flagstaff/plugin';

register(fromCliSpinners(cliSpinners));

Borders

Drawn by box(), from the same registry. fromCliBoxes(cliBoxes) adds the rest of that corpus the same way.

round
╭────────────────────────────────╮
│ box('…', { border: 'round' })  │
╰────────────────────────────────╯
single
┌────────────────────────────────┐
│ box('…', { border: 'single' }) │
└────────────────────────────────┘
double
╔════════════════════════════════╗
║ box('…', { border: 'double' }) ║
╚════════════════════════════════╝
bold
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ box('…', { border: 'bold' })   ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
classic
+--------------------------------+
| box('…', { border: 'classic'   |
| })                             |
+--------------------------------+
none
 box('…', { border: 'none' })     

Writing one of your own

A plugin is one plain object, validated against schema.json — the same file that ships in the tarball:

export default {
  name: 'pulse',
  contract: 1,
  spinners: {
    pulse: { frames: ['◜', '◝', '◞', '◟'], interval: 90, static: '…' },
  },
};
npx flagstaff check ./pulse.mjs

check prints exactly the table at the top of this page for whatever you wrote, and exits 1 with the code and a fix if the schema refuses it. Leave the static off and it says so:

E_NO_STATIC_PROJECTION: pulse has no static projection
  fix: give it a `static`: the text a pipe, an agent or a screen reader gets instead of the animation

On this page