ANSI / VT100 / xterm parser and serializer — CSI, OSC, SGR, cursor sequences.
Module ansi | Source packages/front/fw/src/io/text/ansi.js | Deps none | Worker-safe yes
Pure module for decoding and producing ANSI / VT100 / xterm sequences. Covers plain text,
CSI sequences (ESC [ ... finalByte), OSC sequences (ESC ] ... BEL/ST),
cursor movements, modes (DEC set/reset) and SGR (colors, attributes).
Paul Williams VT500-style state machine:
ground → escape → csi-entry → csi-param → csi-intermediate → osc-string.
Out of MVP scope: Sixel, Kitty image protocol, full mouse tracking.
Resolve
const ansi = runtime.resolve('ansi');
// Returns: { parser, sgr, cursor, clear, osc }
API
| Method / Property | Signature | Returns |
|---|---|---|
parser() |
() => Parser |
Parser instance (see below) |
sgr(opts?) |
(opts: SgrOptions) => string |
Sequence ESC[<params>m |
cursor.move(pos) |
({row, col}) => string |
ESC[<row>;<col>H |
cursor.up(n?) |
(n?: number) => string |
ESC[<n>A |
cursor.down(n?) |
(n?: number) => string |
ESC[<n>B |
cursor.right(n?) |
(n?: number) => string |
ESC[<n>C |
cursor.left(n?) |
(n?: number) => string |
ESC[<n>D |
cursor.save() |
() => string |
ESC 7 |
cursor.restore() |
() => string |
ESC 8 |
clear.line(mode?) |
('all'|'before'|'after') => string |
ESC[2K / ESC[1K / ESC[0K |
clear.screen(mode?) |
('all'|'before'|'after') => string |
ESC[2J / ESC[1J / ESC[0J |
osc.title(s) |
(s: string) => string |
ESC]0;<s>BEL |
ansi.parser()
Creates a new event-driven parser. Each instance has its own internal state.
interface Parser {
on(event: string, fn: Function): void;
feed(chunk: string | Uint8Array): void;
reset(): void;
}
Emitted events:
| Event | Arguments | Description |
|---|---|---|
'text' |
(s: string) |
Plain text |
'csi' |
(params: number[], intermediates: string, finalByte: string) |
Generic CSI sequence |
'osc' |
(cmd: number, payload: string) |
OSC sequence |
'cursor' |
(op: 'up'|'down'|'left'|'right'|'pos'|'save'|'restore', args?) |
Cursor movement |
'mode' |
(set: boolean, code: number) |
Mode DEC SET / RESET |
'sgr' |
(params: number[]) |
Select Graphic Rendition |
'warning' |
(msg: string) |
Warning (e.g. 'osc-overflow') |
Note: cursor.pos receives args = { row: number, col: number }. Relative movements receive args = { n: number }.
ansi.sgr(opts)
Builds an SGR sequence from named options.
Options sgr
| Option | Type | Description |
|---|---|---|
reset |
boolean |
Code 0 (full reset) |
bold |
boolean |
Code 1 |
dim |
boolean |
Code 2 |
italic |
boolean |
Code 3 |
underline |
boolean |
Code 4 |
blink |
boolean |
Code 5 |
reverse |
boolean |
Code 7 |
strikethrough |
boolean |
Code 9 |
fg |
string | '#RRGGBB' | {r,g,b} |
Foreground color |
bg |
string | '#RRGGBB' | {r,g,b} |
Background color |
Accepted color names for fg/bg: black, red, green, yellow, blue, magenta, cyan, white, brightBlack, brightRed, brightGreen, brightYellow, brightBlue, brightMagenta, brightCyan, brightWhite.
The {r,g,b} form produces a 24-bit truecolor code (ESC[38;2;r;g;bm for FG, ESC[48;2;r;g;bm for BG).
Examples
Colored log
const ansi = runtime.resolve('ansi');
const red = ansi.sgr({ fg: 'red', bold: true });
const reset = ansi.sgr({ reset: true });
process.stdout.write(red + 'ERROR: file not found' + reset + '\n');
Minimal TUI
const ansi = runtime.resolve('ansi');
// Clear the screen, position the cursor, display
process.stdout.write(
ansi.clear.screen('all') +
ansi.cursor.move({ row: 1, col: 1 }) +
ansi.sgr({ fg: 'brightCyan', bold: true }) + 'My TUI' +
ansi.sgr({ reset: true })
);
Parsing the output of a process
const ansi = runtime.resolve('ansi');
const p = ansi.parser();
p.on('text', s => console.log('[text]', s));
p.on('sgr', ps => console.log('[sgr]', ps));
p.on('cursor', (op, args) => console.log('[cursor]', op, args));
p.on('osc', (cmd, payload) => console.log('[osc]', cmd, payload));
// Feed in chunks (e.g. output of a child process)
childProcess.stdout.on('data', chunk => p.feed(chunk));
Truecolor
const ansi = runtime.resolve('ansi');
// 24-bit color via {r,g,b} object
console.log(ansi.sgr({ fg: { r: 255, g: 128, b: 0 } }) + 'Orange' + ansi.sgr({ reset: true }));
// 24-bit color via hex
console.log(ansi.sgr({ bg: '#1e1e2e' }) + 'Catppuccin' + ansi.sgr({ reset: true }));
Worker Usage
const worker = fw.createWorker(
function ({ libs, args }) {
const ansi = libs.ansi;
const lines = [];
const p = ansi.parser();
p.on('text', s => lines.push(s));
p.on('sgr', ps => lines.push('[sgr:' + ps.join(',') + ']'));
p.feed(args[0]);
self.postMessage(lines);
},
{ dependencies: ['ansi'], args: ['\x1b[1mBold text\x1b[0m'] }
);
worker.addEventListener('message', e => console.log(e.data));
Notes
- The OSC buffer is limited to 4096 bytes; overflow emits the
'warning'event with'osc-overflow'and flushes the truncated sequence. cursor.save()/cursor.restore()use DEC sequences (ESC 7/ESC 8), compatible with xterm and most modern terminals.- The parser accepts
stringorUint8Array(decoded UTF-8 viaTextDecoderinternally, streaming-safe). - Each
parser()instance is independent — usereset()to reinitialize state between sessions without creating a new instance. - Private DEC mode sequences (
ESC[?Nh/ESC[?Nl) are normalized: the?is stripped, the numeric code is emitted in the'mode'event.
See also
- str — string manipulation (slug, case, truncate)
- unicode — normalization, collation, grapheme clusters
- Guide worker-safe