@cleocode/animations
pnpm add @cleocode/animations18 braille loaders
Frame-cycled spinners indexed by their generic name. Ported verbatim from
unicode-animations (MIT, Gunnar Gray).
Each entry is { frames: string[], interval: number } — pure data, no I/O.
9 workshop names
Aliases drawn from CLEO canon (CLEO-VISION.md · NEXUS-CORE-ASPECTS.md).
Same Spinner objects as the generic registry — references, not copies — so renaming
a generic spinner automatically updates the canon view.
3 woven gauges
Fixed-width segmented bars. Tapestry uses coarse Unicode
blocks (░▒▓█) for a woven cloth feel. Cascade uses
1/8 gradient steps (▏▎▍▌▋▊▉█) for a smooth waterfall edge.
Refinery uses braille block-fill stages, evoking the BRAIN
memory promotion pipeline filling level-by-level.
4 canon flares
Sparks are finite frame sequences. They play once and decay back to empty —
not loops. awaken at first dream / cleo init; sweep
on BRAIN integrity sweep complete; cascade on release-shipped /
task-complete; weave on playbook stage transitions and CANT
directive acceptance. (On this page they auto-replay every ~1.4s so you can
see the motion.)
AnimateContext
Every primitive in this package routes its output through an
AnimateContext. The context is pure data — no I/O, no
timers — derived from the LAFS FlagResolution plus environment
signals. When silent, primitives return no-op handles so callers don't branch
on output mode.
| Signal | Source | Effect | Reason code |
|---|---|---|---|
format !== 'human' | LAFS flags | Disable animations (machine output) | format-json |
quiet === true | LAFS flags | Disable animations (script-friendly) | quiet |
!isTTY | process.stdout.isTTY | Disable animations (piped/redirected) | no-tty |
NO_COLOR set | process.env.NO_COLOR | Disable animations (no-color.org) | no-color |
Construct it once at command entry, thread it everywhere downstream:
import { resolveOutputFormat } from '@cleocode/lafs'; import { createAnimateContext } from '@cleocode/animations'; const flags = resolveOutputFormat({ humanFlag: true }); const ctx = createAnimateContext({ flagResolution: flags }); if (ctx.enabled) /* render spinner */; else console.log(`silent: ${ctx.reason}`); // → "enabled" | "format-json" | "quiet" | "no-tty" | "no-color"
How to render
Lint rule: any process.stdout.write(...) of a string starting with
\r outside @cleocode/animations is a contract violation.
Always route through createSpinnerHandle — it owns the timer, the cursor,
the exit handler, and the LAFS gate.
import { resolveOutputFormat } from '@cleocode/lafs'; import { createAnimateContext, createSpinnerHandle } from '@cleocode/animations'; const ctx = createAnimateContext({ flagResolution: resolveOutputFormat({ humanFlag: true }), }); const spinner = createSpinnerHandle(ctx, 'looming', 'Weaving tasks…'); spinner.start(); try { await doWork(); spinner.stop('✔ Tapestry complete.'); } catch (err) { spinner.stop(); throw err; }
const spinner = createSpinnerHandle(ctx, 'weaving', 'Connecting to database…'); spinner.start(); const db = await connect(); spinner.update(`Running ${migrations.length} migrations…`); await db.migrate(migrations); spinner.stop('✔ Database ready.');
import { renderProgressBar } from '@cleocode/animations'; function tick(done, total) { const ratio = done / total; const bar = renderProgressBar('refinery', ratio, 36); // (progress bars are not yet routed through a handle — // gate manually with ctx.enabled until ProgressBarHandle ships) if (ctx.enabled) process.stdout.write(`\r\x1B[2K ${bar} ${done}/${total}`); }
import { sparks, sparkDurationMs } from '@cleocode/animations'; async function playSpark(name) { if (!ctx.enabled) return; const { frames, interval } = sparks[name]; for (const f of frames) { process.stdout.write(`\r\x1B[2K ${f}`); await new Promise(r => setTimeout(r, interval)); } process.stdout.write('\n'); } await shipRelease(); await playSpark('cascade'); // total: ~980ms via sparkDurationMs('cascade')
Exports from @cleocode/animations
| Export | Type / Shape |
|---|---|
spinners | Record<BrailleSpinnerName, Spinner> — 18 entries |
canonSpinners | Record<CanonSpinnerName, Spinner> — 9 aliases |
CANON_TO_GENERIC | Record<CanonSpinnerName, BrailleSpinnerName> |
resolveSpinner(name) | (string) => Spinner | undefined |
gridToBraille(grid) | (boolean[][]) => string |
makeGrid(rows, cols) | (number, number) => boolean[][] |
Spinner | TS interface: { frames: readonly string[]; interval: number } |
BrailleSpinnerName · CanonSpinnerName | TS string-literal unions |
| Export | Type / Shape |
|---|---|
createAnimateContext(input) | (AnimateContextInput) => AnimateContext |
SILENT_CONTEXT | Frozen AnimateContext — always disabled |
AnimateContext | { enabled, reason, inputs } |
AnimateContextInput | { flagResolution, isTTY?, noColor? } |
FlagResolutionLike | { format: 'json'|'human'; quiet: boolean } |
\r owner)| Export | Type / Shape |
|---|---|
createSpinnerHandle(ctx, name, label, options?) | (AnimateContext, name, string, SpinnerHandleOptions?) => SpinnerHandle |
SpinnerHandle | { start(); stop(finalLine?); update(label); enabled: boolean } |
SpinnerHandleOptions | { delayMs?: number } — defaults to 150 |
| Export | Type / Shape |
|---|---|
progressBars | Record<ProgressBarStyle, ProgressBarRenderer> |
renderProgressBar(style, ratio, width) | (style, number, number) => string |
ProgressBarStyle | 'tapestry' | 'cascade' | 'refinery' |
sparks | Record<SparkName, Spark> |
sparkDurationMs(name) | (SparkName) => number |
SparkName | 'awaken' | 'sweep' | 'cascade' | 'weave' |
| Canon name | Generic name | Cleo lore role |
|---|---|---|
looming | helix | Twin strands weaving — task on the LOOM |
weaving | braillewave | Pattern threading across columns |
heartbeat | breathe | Organic in-out pulse — Hearth presence |
awakening | pulse | Radial bloom — first dream / cleo init |
sweeping | scan | Left→right beam — BRAIN integrity Sweep |
watching | orbit | Circular sentinel — sentient daemon tick |
cascade | cascade | Diagonal fall — command-success accent |
tapestry | waverows | Multi-row sinusoidal — wave of tasks shipping |
refinery | columns | Filling stages — memory promotion pipeline |