npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dvdrw

v1.0.8

Published

Create beautiful animated SVGs from terminal recordings

Readme

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 dvdrw
import dvd from 'dvdrw';

const { svg } = await dvd(`
  Type "echo hello world"
  Enter
  Sleep 800ms
`, { theme: 'dracula', template: 'macos' });

Contents


Why the library?

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, optional onProgress callback, 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.


Inputs

dvd(input, options) accepts four input shapes. Pick whichever matches the data you already have.

1. CD script string

The fastest path. Same syntax as dvd-cli, just inlined.

const { svg } = await dvd(`
  Type "npm install dvdrw"
  Sleep 400ms
  Enter
  Sleep 800ms
`, { theme: 'dracula', template: 'macos', title: 'quick-start' });

2. Programmatic steps

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.
const steps = [
  { 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 } = await dvd(steps, { theme: 'tokyoNight', template: 'macos', title: 'test runner' });

Full source: examples/02-programmatic-steps.ts

3. Raw terminal output

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.

import dvd from 'dvdrw';
import { spawnSync } from 'node:child_process';

const r = spawnSync('myscript.sh', { encoding: 'buffer' });
const raw = r.stdout.toString('binary');

const { svg } = await dvd({ raw, totalDuration: 2400 }, {
  theme: 'catppuccinMocha',
  template: 'macos',
  title: 'spinner capture',
});

Full source: examples/03-raw-output.ts

4. Pre-parsed script

If you've already parsed a CD script (e.g., for validation or transformation), pass the AST directly:

import dvd, { parseCDScript } from 'dvdrw';

const script = parseCDScript(scriptText);
// ...mutate, validate, splice frames...
const { svg } = await dvd({ script }, { theme: 'nord' });

Themes

37 built-in themes, all exported from themes. Pass by name or as a full Theme object.

await dvd(script, { theme: 'tokyoNight' });

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:

import dvd, { type Theme } from 'dvdrw';

const retroGreen: Theme = {
  name: 'retro',
  background: '#0a0a0a',
  foreground: '#00ff00',
  cursor: '#00ff00',
  // ...the 16 ANSI colors
};

await dvd(script, { theme: retroGreen });

Source: examples/04-themes-gallery.ts


Templates

Window chrome — macos / windows / minimal.

Source: examples/09-templates.ts


Loop styles

Animations loop by default. Choose how the loop behaves at the boundary:

await dvd(script, {
  loopStyle: 'reverse',  // 'loop' | 'reverse' | 'rewind' | 'fade'
  loopPause: 600,
  rewindSpeed: 6,        // for 'rewind'
  fadeDuration: 1200,    // for 'fade'
});

Source: examples/05-loop-styles.ts


Branded output

Gradient backgrounds, watermarks, custom borders — for docs sites and landing pages where the SVG carries product weight.

const { svg } = await dvd(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


Progress tracking

Wire dvd into your build pipeline or TUI. The onProgress callback fires for every step the executor runs.

await dvd(script, {
  onProgress: (current, total, description) => {
    const pct = 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:

const result = await dvd(script);
console.log(result.metadata); // { duration: 2118, frameCount: 33, fps: 15.6 }

Source: examples/07-progress.ts


Low-level API

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';

const fontSize = 16;
const theme = themes.draculaPro;

let state = createGridState(44, 9);
state = processInput(state, '\x1b[1;38;5;213m  dvdrw\x1b[0m · terminal recordings as svg');

const spans = 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


Rendering modes: filmstrip vs SMIL

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 |

await dvd(script, { smil: true });

Options reference

const { svg, frames, frameData, metadata } = await dvd(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 string
result.metadata.duration    // total ms
result.metadata.frameCount  // number of frames
result.metadata.fps         // effective fps
result.frames               // TerminalFrame[]
result.frameData            // FrameData[] — raw row data, useful for custom emitters

Steps reference

When 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.


Comparison

| | 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 |


Examples

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.ts

Related

  • dvd-cli — the CLI front-end (.cd scripts, pipe mode, rec / render sub-commands)
  • shellfie — terminal screenshots in code
  • shellfie-cli — terminal screenshots CLI
  • shellfied — terminal screenshots web service

License

MIT