Getting started
Install paratext, emit a hyperlink, a notification, a title and an image, and see what a supporting terminal gets and what a pipe gets instead.
paratext writes the escape sequences that go around the text — hyperlinks, inline images, the window title, the clipboard, notifications — and, for every one of them, says what to print instead when the terminal cannot show it. No terminal answers "do you do OSC 1337", so that second answer is not an extra: it is what a pipe, a log and an agent get, and it is the default whenever support is absent or unknown.
Install
npm install paratextIt is ESM with a default condition, so require('paratext') also works from CommonJS on
Node 20.19+ and 22.13+. It depends on nothing
(package-shape-lock.test.ts
holds it at zero).
A first program
emit(runtime, name, fields) renders a capability for a runtime — the environment and whether
stdout is a terminal. Here the same four calls go to a pipe and to iTerm2:
import { emit } from 'paratext';
const pipe = { env: {}, isTTY: { stdout: false } };
const iterm = { env: { TERM_PROGRAM: 'iTerm.app' }, isTTY: { stdout: true } };
for (const [name, runtime] of [['pipe', pipe], ['iTerm2', iterm]]) {
console.log(name);
console.log(' ', JSON.stringify(emit(runtime, 'link', { text: 'Docs', url: 'https://paratext.interlace.tools' })));
console.log(' ', JSON.stringify(emit(runtime, 'notify', { title: 'Build finished', body: '3 warnings' })));
console.log(' ', JSON.stringify(emit(runtime, 'title', { text: 'building' })));
console.log(' ', JSON.stringify(emit(runtime, 'image', { base64: 'iVBORw0KGgo=', caption: '[chart: build times]' })));
}pipe
"Docs (https://paratext.interlace.tools)"
"Build finished: 3 warnings"
""
"[chart: build times]"
iTerm2
"\u001b]8;;https://paratext.interlace.tools\u0007Docs\u001b]8;;\u0007"
"\u001b]9;Build finished: 3 warnings\u0007"
"\u001b]0;building\u0007"
"\u001b]1337;File=inline=1:iVBORw0KGgo=\u0007"On the pipe, the link keeps its address, the notification becomes a line, the image becomes
its caption, and the window title — chrome, not content — becomes nothing. On iTerm2 each is
the sequence iTerm2 understands. JSON.stringify only makes the escape bytes visible here.
For the real process, pass processRuntime():
import { emit, processRuntime } from 'paratext';
console.log(emit(processRuntime(), 'link', { text: 'Docs', url: 'https://paratext.interlace.tools' }));Docs (https://paratext.interlace.tools)Every output block on this site is checked: tests/examples.test.ts writes each titled file,
runs the command in the block's title, and compares.
The seven built-ins
| name | sequence | fields | on a pipe |
|---|---|---|---|
link | OSC 8 | text, url | text (url) |
image | OSC 1337 | base64, caption, optional width, height, preserveAspectRatio, size | the caption |
notify | OSC 9 | title, optional body | title: body |
title | OSC 0 | text | nothing |
clipboard | OSC 52 | text | nothing |
cwd | OSC 50 and OSC 9;9 | path | nothing |
bell | BEL | — | nothing |
A capability the package never heard of is a plain object away: Capabilities as data.
Where next
- Guides: the static projection, detecting support, the registry and plugins, the ansi-escapes surface, and images.
- Why paratext: what it does that ansi-escapes, terminal-link and term-img do not, cell by cell, with the evidence.
- Coming from ansi-escapes and the other two: change one import.
- API reference: every export of every entry point.
paratext
Everything around your terminal output that is not the output: hyperlinks, images, window title, clipboard, notifications and the bell — each with a static fallback for terminals that cannot do it. Drop-in paths for ansi-escapes, terminal-link and term-img; term-img's takes image bytes, not file paths. Zero dependencies.
The static projection
Every capability carries a fallback template, and emit() returns it whenever support is absent or unknown — so a pipe gets Docs (url), a caption or a line, never OSC bytes.