Generate animated SVG terminal recordings from code.
dvdrw is the Node library behind dvd-cli — call it from your own code, your build pipeline, or your service. Output is a single self-contained animated SVG. No ffmpeg, no headless browser, no shelling out.
npm install dvdrwimportdvdfrom'dvdrw';const{ svg }=awaitdvd(` Type "echo hello world" Enter Sleep 800ms`,{theme: 'dracula',template: 'macos'});- Why the library?
- Inputs
- Themes
- Templates
- Loop styles
- Branded output
- Progress tracking
- Low-level API
- Rendering modes
- Options reference
- Steps reference
- Comparison
- Related
The CLI is great when you have a .cd script in a file. The library is for everything else:
- Programmatic content — render the output of a real test run, a real deploy, or a real benchmark, with frames built from runtime data.
- Raw stdout capture — feed any ANSI byte stream straight in (
{ raw }). Spinners, progress bars,chartscii,lolcat, anything that animates on the terminal. - Build-pipeline integration — fully
async, optionalonProgresscallback, no temp files, no subprocesses by default. - Embeddable — drop into a docs generator, a service, an Electron app, a serverless function. The output is a string.
- Composable — the parser, terminal emulator, coalescer, emitter and animator are all exported and usable independently.
If you want a single command on the CLI that takes a .cd file and writes a .svg, use dvd-cli. If you want to call into the engine, you're in the right place.
dvd(input, options) accepts four input shapes. Pick whichever matches the data you already have.
The fastest path. Same syntax as dvd-cli, just inlined.
const{ svg }=awaitdvd(` Type "npm install dvdrw" Sleep 400ms Enter Sleep 800ms`,{theme: 'dracula',template: 'macos',title: 'quick-start'});When the content of your animation is computed at runtime — a generated test report, a stream of deploy events, a templated demo — skip the script and pass an array.
// Each Type+Enter is sent through a real shell, so wrap any styled output// in `echo -e "..."` rather than typing raw ANSI as a command.conststeps=[{type: 'Type',text: 'echo -e "\\x1b[2m$\\x1b[0m npm test"'},{type: 'Key',key: 'Enter'},{type: 'Sleep',duration: 600},
...tests.flatMap((t)=>[{type: 'Type',text: `echo -e "\\x1b[32m ✓\\x1b[0m ${t.name} \\x1b[2m(${t.ms}ms)\\x1b[0m"`},{type: 'Key',key: 'Enter'},{type: 'Sleep',duration: 200},]),];const{ svg }=awaitdvd(steps,{theme: 'tokyoNight',template: 'macos',title: 'test runner'});Full source:
examples/02-programmatic-steps.ts
Capture stdout from any command and hand the bytes over. dvd auto-detects the animation pattern (cursor reset, terminal reset, clear-line, cursor-up) and splits into frames automatically.
importdvdfrom'dvdrw';import{spawnSync}from'node:child_process';constr=spawnSync('myscript.sh',{encoding: 'buffer'});constraw=r.stdout.toString('binary');const{ svg }=awaitdvd({ raw,totalDuration: 2400},{theme: 'catppuccinMocha',template: 'macos',title: 'spinner capture',});Full source:
examples/03-raw-output.ts
If you've already parsed a CD script (e.g., for validation or transformation), pass the AST directly:
importdvd,{parseCDScript}from'dvdrw';constscript=parseCDScript(scriptText);// ...mutate, validate, splice frames...const{ svg }=awaitdvd({ script },{theme: 'nord'});37 built-in themes, all exported from themes. Pass by name or as a full Theme object.
awaitdvd(script,{theme: 'tokyoNight'});| dracula | tokyoNight |
| catppuccinMocha | nord |
| gruvboxDark | monokai |
| oneDark | synthwave84 |
The full list: a11yDark, base16Dark, base16Light, blackboard, catppuccinMocha, cobalt, dark, dracula, draculaPro, duotoneDark, githubDark, githubLight, gruvboxDark, gruvboxLight, hopscotch, lucario, material, monokai, night3024, nord, oceanicNext, oneDark, oneLight, pandaSyntax, paraisoDark, seti, shadesOfPurple, solarizedDark, solarizedLight, synthwave84, terminal, tokyoNight, twilight, verminal, vscode, yeti, zenburn.
For custom palettes, pass a Theme object directly:
importdvd,{typeTheme}from'dvdrw';constretroGreen: Theme={name: 'retro',background: '#0a0a0a',foreground: '#00ff00',cursor: '#00ff00',// ...the 16 ANSI colors};awaitdvd(script,{theme: retroGreen});Source:
examples/04-themes-gallery.ts
Window chrome — macos / windows / minimal.
| macos | windows | minimal |
Source:
examples/09-templates.ts
Animations loop by default. Choose how the loop behaves at the boundary:
awaitdvd(script,{loopStyle: 'reverse',// 'loop' | 'reverse' | 'rewind' | 'fade'loopPause: 600,rewindSpeed: 6,// for 'rewind'fadeDuration: 1200,// for 'fade'});| loop — restart from frame 0 | reverse — play forward then back |
| rewind — fast reverse like rewinding tape | fade — fade to black, fade back in |
Source:
examples/05-loop-styles.ts
Gradient backgrounds, watermarks, custom borders — for docs sites and landing pages where the SVG carries product weight.
const{ svg }=awaitdvd(script,{theme: 'tokyoNight',template: 'macos',background: 'gradient(#7c5fff, #ff6ec7:diagonal)',backgroundPadding: 48,borderRadius: 12,watermark: 'made with dvd',});Backgrounds accept solid colors (#1a1a2e) or gradients in the form gradient(<color>, <color>[:vertical|horizontal|diagonal]) with as many stops as you need.
Source:
examples/06-branding.ts
Wire dvd into your build pipeline or TUI. The onProgress callback fires for every step the executor runs.
awaitdvd(script,{onProgress: (current,total,description)=>{constpct=Math.round((current/total)*100);process.stdout.write(`\r[${pct}%] ${description??''}`);},});The returned result also carries metadata — frame count, total duration, effective FPS — for logging and CI annotations:
constresult=awaitdvd(script);console.log(result.metadata);// { duration: 2118, frameCount: 33, fps: 15.6 }Source:
examples/07-progress.ts
Skip the executor when you want to render arbitrary terminal state directly — for static badges, dashboards, CI annotations, or content that doesn't fit the script model.
import{coalesce,createGridState,emit,processInput,themes}from'dvdrw';constfontSize=16;consttheme=themes.draculaPro;letstate=createGridState(44,9);state=processInput(state,'\x1b[1;38;5;213m dvdrw\x1b[0m · terminal recordings as svg');constspans=coalesce(state,theme);const{ svg }=emit(spans,state.cursor,false,{
theme,template: 'minimal',width: 460,height: 240,
fontSize,lineHeight: fontSize*1.4,charWidth: fontSize*0.6,padding: 16,});The exposed building blocks:
| Module | Symbols |
|---|---|
| Terminal emulator | createGridState, processInput, applyCommand, applyCommands, parseInput |
| Text processing | coalesce |
| Emission | emit, emitAnimated, emitFilmstripAnimated |
| Animation | createAnimatedSVG, createFilmstripSVG, optimizeSvg |
| Raw output | processRawOutput, detectAnimationType, splitIntoFrames |
| Cast files | parseCastFile, RecordingPlayer, generateFramesFromRecording |
| Script parsing | parseCDScript, CDParseError |
| Executor | CDExecutor |
Source:
examples/08-low-level-api.ts
Two animation engines are available. The default (filmstrip) is what you almost always want.
| Filmstrip (default) | SMIL (smil: true) | |
|---|---|---|
| Engine | CSS @keyframes over a deduped row pool | Native SVG <animate> per frame |
| File size | Smaller — scales with unique rows, not total frames | Larger — scales with total frames |
| Best for | README embeds, docs, long recordings, mostly-static content | Short animations, high-FPS smoothness on iOS Safari / 120Hz |
awaitdvd(script,{smil: true});const{ svg, frames, frameData, metadata }=awaitdvd(input,{// Window chrome
theme,// Theme name (string) or Theme object — default 'dark'
template,// 'macos' | 'windows' | 'minimal' — default 'macos'
title,// window title text
watermark,// string or SVG markup// Dimensions
width, height,// omit for auto-sizing from script content
fontSize,// default 14
lineHeight,// multiplier — default 1.4
letterSpacing,
fontFamily,
padding,// default 16// Borders
borderRadius,// default 8
borderColor, borderWidth,// Background (outside the terminal window)
background,// '#hex' or 'gradient(#a, #b[:horizontal|vertical|diagonal])'
backgroundPadding,
backgroundRadius,// Header / footer
headerHeight, headerBackground, headerBorder,
headerBorderColor, headerBorderWidth,
footerHeight, footerBackground, footerBorder,
footerBorderColor, footerBorderWidth,// Cursor
cursorStyle,// 'block' | 'bar' | 'underline'
cursorColor,
cursorBlink,// default false// Animation
fps,
loop,// default true
loopStyle,// 'loop' | 'reverse' | 'rewind' | 'fade'
loopPause,// ms between cycles
pauseAtEnd,// ms hold on last frame — default 1000
fadeDuration,// ms for 'fade' style — default 1500
rewindSpeed,// multiplier for 'rewind' — default 5
playbackSpeed,// 1 = normal, 2 = 2x, 0.5 = half speed// Renderer
smil,// false = filmstrip (default), true = SMIL
optimize,// SVGO post-pass — default true
customGlyphs,// box-drawing as geometric shapes — default true// Callbacks
onFrame,// (frame: TerminalFrame) => void
onProgress,// (current, total, description?) => void});The result:
result.svg// animated SVG stringresult.metadata.duration// total msresult.metadata.frameCount// number of framesresult.metadata.fps// effective fpsresult.frames// TerminalFrame[]result.frameData// FrameData[] — raw row data, useful for custom emittersWhen using the programmatic-steps input, each entry conforms to one of these shapes:
| Type | Fields | Example |
|---|---|---|
Type | text, optional speed (ms/char) | { type: 'Type', text: 'hello', speed: 50 } |
Key | key | { type: 'Key', key: 'Enter' } |
Sleep | duration (ms) | { type: 'Sleep', duration: 1000 } |
Shortcut | key + modifier flags | { type: 'Shortcut', ctrl: true, key: 'c' } |
Screenshot | path | { type: 'Screenshot', path: 'frame.svg' } |
Copy / Paste | text (Copy only) | { type: 'Copy', text: 'hi' } |
Set | setting, value | { type: 'Set', setting: 'Theme', value: 'dracula' } |
Env | key, value | { type: 'Env', key: 'NODE_ENV', value: 'prod' } |
Keys: Enter, Backspace, Tab, Space, Left, Right, Up, Down.
| dvdrw | VHS | asciinema | |
|---|---|---|---|
| Output | SVG | GIF / MP4 | asciicast |
| Native API | TypeScript lib | CLI | JSON + player |
| Dependencies | none | ffmpeg, ttyd | player embed |
| Scalable | yes | no | yes |
| GitHub README | perfect | works | embed only |
| Editable | yes (XML) | no | yes (JSON) |
| Offline | yes | yes | no |
| Loop styles | 4 modes | basic | basic |
| Programmatic | yes | limited | yes |
All runnable. Each writes its SVG into examples/svgs/.
npx ts-node -P tsconfig.dev.json examples/01-quick-start.ts
npx ts-node -P tsconfig.dev.json examples/02-programmatic-steps.ts
npx ts-node -P tsconfig.dev.json examples/03-raw-output.ts
npx ts-node -P tsconfig.dev.json examples/04-themes-gallery.ts
npx ts-node -P tsconfig.dev.json examples/05-loop-styles.ts
npx ts-node -P tsconfig.dev.json examples/06-branding.ts
npx ts-node -P tsconfig.dev.json examples/07-progress.ts
npx ts-node -P tsconfig.dev.json examples/08-low-level-api.ts
npx ts-node -P tsconfig.dev.json examples/09-templates.ts
# or render them all in one go:
npx ts-node -P tsconfig.dev.json examples/10-render-all.tsdvd-cli— the CLI front-end (.cdscripts, pipe mode,rec/rendersub-commands)shellfie— terminal screenshots in codeshellfie-cli— terminal screenshots CLIshellfied— terminal screenshots web service
MIT