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

svg-terminal

v1.2.3

Published

Generate animated SVG terminals for GitHub READMEs from a declarative YAML config. 48 built-in blocks, 20 themes, zero runtime deps in the output.

Readme

svg-terminal

CI CodeQL npm version npm downloads Node 22+ MIT license

Generate animated SVG terminals from a declarative YAML config. The output is a single self-contained SVG that works inside GitHub's sandbox — no script, no external assets.

svg-terminal demo

Demo above is the actual SVG this library produces. Source: examples/demo.yml. Regenerate with npm run demo.

Try in 60 seconds

npx svg-terminal init                       # writes terminal.yml
npx svg-terminal generate                   # writes terminal.svg
npx svg-terminal blocks                     # lists all 48 blocks

Or as a GitHub Action — refresh your profile README on a schedule:

- uses: williamzujkowski/svg-terminal@v1
  with:
    config: terminal.yml
    output: terminal.svg
    commit: true

See the full GitHub Action section below, the block catalog (48 blocks, one preview each), and the 20-theme gallery.

What's in the box

  • Declarative YAML config — write blocks, pick a theme, run the CLI
  • 48 built-in blocks — across identity, retro / fake-system, status, ASCII art, single- and multi-line animation, and humor categories. Browse the block catalog for previews of each
  • 20 built-in themes — 12 classics (dracula, nord, monokai, the amber/green-phosphor/cyberpunk CRT trio, solarized-dark, win95, catppuccin, tokyo-night, gruvbox, high-contrast) plus 8 sharp, modern, WCAG-AAA OKLCH additions (modus-vivendi, oxocarbon, rose-pine, everforest, kanagawa, flexoki, github-light, dayfox)
  • Frame animationBlockResult.animation = { frames, fps, loop } powers the 10 animated blocks (spinners, clock, dice, progress bar, etc.). Frames may be single- or multi-line as of #69 (jumping-jack is the reference multi-line block)
  • Dynamic-block cache — the 5 cacheable blocks (weather, github-stats, github-languages, quote, fun-fact) write to .svg-terminal-cache.json. Pair with --frozen-cache for offline CI builds
  • Reduced-motion respected@media (prefers-reduced-motion) clamps the CSS fade-ins AND (since v0.17) the frame cycle. SMIL-driven typing reveal, cursor walk, and scroll-on-overflow remain animated; pair with --static for full stillness
  • Schema-validated, XSS-safe — strict zod schema on every config field; user-controllable values are escaped at SVG emit sites. See SECURITY.md
  • No runtime deps in the output — SMIL + CSS animation, inline, GitHub-sandbox-safe
  • CLI + librarynpx svg-terminal generate, or import { generate } from 'svg-terminal'. Requires Node 22+

Quick Start

npx svg-terminal init                       # Creates terminal.yml
npx svg-terminal generate                   # Generates terminal.svg
npx svg-terminal generate --watch           # Rebuild on every save
npx svg-terminal blocks <name>              # Inspect a block's config schema

CLI reference

Commands

| Command | Description | |---------|-------------| | generate | Generate the SVG from a config file | | init | Create a starter terminal.yml (refuses to overwrite without --force) | | themes | List available themes | | blocks [<name>] | List block types, or print one block's config schema | | cache check | Verify dynamic-block cache freshness (exit 1 on stale/missing) |

generate flags

| Flag | Description | |------|-------------| | --config <path> | Config file path (default terminal.yml) | | --output <path> | Output file path (default terminal.svg) | | --static | Non-animated final-frame snapshot | | --minify | Strip inter-element whitespace | | --strict | Promote soft warnings (unknown block-config keys, over-tall animated bands) to hard errors | | --watch | Re-generate on config-file change | | --timings | Print per-phase wall-clock timings to stderr | | --explain | Print the resolved config + block list as JSON to stderr | | --no-cache / --refresh-cache / --frozen-cache | Cache behavior shortcuts (off / re-fetch all / cached-only) | | --cache-mode <m> | Explicit cache mode: normal | refresh | frozen | off | | --version | Print the version |

Configuration

Edit terminal.yml:

theme: dracula

window:
  title: "dev@my-machine:~"

terminal:
  prompt: "dev@box:~$ "

blocks:
  - block: neofetch
    config:
      username: dev
      hostname: my-machine
      role: Full-Stack Developer
      languages: TypeScript, Rust, Go

  - block: fortune
    config:
      fortunes:
        - "The best code is no code at all."
        - "Talk is cheap. Show me the code."

  - block: custom
    config:
      command: echo "Hello!"
      lines:
        - "[[fg:green]]Welcome to my terminal![[/fg]]"

svg-terminal init writes a fully-commented starter terminal.yml. The top-level keys:

| Key | Purpose | |-----|---------| | theme | Theme name, an inline theme object, or random (daily rotation) | | blocks | The ordered list of blocks to render (each { block, config?, command?, color?, typing?, pause? }) | | window | Chrome + frame: width, height/autoHeight (+ minHeight/maxHeight), title, style (macos|win95|floating|minimal|none), titleBarHeight, borderRadius | | terminal | Text rendering: fontFamily, fontSize, lineHeight, prompt, padding/paddingTop | | effects | textGlow, scanlines, vignette, shadow (booleans) | | animation | Timing: loop, defaultTypingDuration, outputLineStagger, commandOutputPause, outputEndPause, defaultSequencePause, scrollDelay | | chrome | Title-bar/button styling: titleFontFamily, titleFontSize, buttonRadius, buttonSpacing, dimOpacity | | accessibility | { describe } — emit the <title>/<desc> screen-reader content (default true) | | accessibilityLabel | Override the auto-generated aria-label | | variables | Arbitrary values exposed to blocks (surfaced by --explain) | | maxDuration | Hard cap (seconds) on the animation timeline (default 90) | | scrollDuration | Per-scroll transition duration in ms (default 100) | | fetchTimeout | Network timeout (ms) for dynamic blocks (default 10000) | | cachePath / cacheTTL | On-disk cache file location + entry TTL for cacheable blocks |

Themes

| Theme | Description | |-------|-------------| | dracula | Dark purple/green theme (default) | | nord | Arctic blue/frost palette | | monokai | Classic warm dark theme | | amber | Vintage amber CRT (pairs well with effects.textGlow: true) | | green-phosphor | Classic green-on-black phosphor (pair with glow) | | cyberpunk | Neon magenta/cyan on near-black | | solarized-dark | Ethan Schoonover's solarized dark palette (lifted prompt/comment for WCAG AA) | | win95 | Authentic Windows 95 chrome — auto-switches window.style: win95 | | catppuccin | Catppuccin Mocha — soothing pastel dark theme | | tokyo-night | Tokyo Night (storm variant) — popular for Vim/Neovim | | gruvbox | Gruvbox Dark medium — retro warm contrast | | high-contrast | WCAG AAA pure-black-on-white palette — accessibility / slides / projector |

OKLCH WCAG-AAA additions — sharp, modern, scanline-free; AAA body text (≥ 7:1), derived from the 400+ OKLCH scheme collection:

| Theme | Description | |-------|-------------| | modus-vivendi | Maximally-legible neutral dark (Protesilaos) — 21:1 body text | | oxocarbon | IBM Carbon — cool high-contrast modern dark | | rose-pine | Muted rosé & iris, soho dusk | | everforest | Warm forest green, cozy low-glare | | kanagawa | Hokusai ink-wash — indigo, sand, wave-crest | | flexoki | Warm paper-and-ink reading palette (Steph Ango) | | github-light | Clean professional light theme | | dayfox | Soft warm-cream light theme |

Special value: theme: random rotates through all themes deterministically by day of year — gives you a different look every day without committing to one.

Each is the same 2-block config (motd + neofetch) rendered against the named theme. Source in examples/gallery/_template.yml.

| | | |---|---| | dracula | nord | | monokai | amber | | green-phosphor | cyberpunk | | solarized-dark | win95 | | catppuccin | tokyo-night | | gruvbox | high-contrast | | modus-vivendi | oxocarbon | | rose-pine | everforest | | kanagawa | flexoki | | github-light | dayfox |

Blocks

Run svg-terminal blocks to list all 48 (cacheable ones marked *), or svg-terminal blocks <name> to print one block's config schema directly without grepping the source.

| Block | Description | |-------|-------------| | neofetch | System-info display with configurable fields | | fortune | Random quote/fortune in ASCII box | | custom | Arbitrary text with [[fg:color]] markup | | motd | Welcome banner / message of the day | | dad-joke | Q&A joke in an ASCII box (daily rotation) | | htop | Colorful process/resource monitor display | | profile | Developer profile info card | | goodbye | Farewell message with well-wishes | | npm-install | Humorous npm dependency tree | | blog-post | Blog post title in a box | | national-day | Fun national day celebration | | systemctl | Fake systemd service status | | weather | Live weather from wttr.in (also embeds in MOTD) | | github-stats | Live GitHub user stats (repos, followers) | | github-languages | Top languages across a user's public repos, with percentage bars | | quote | Random quote from dummyjson.com | | fun-fact | Random fun fact from uselessfacts.jsph.pl | | vim-exit | The eternal "how do I quit vim?" meme | | sudo-sandwich | xkcd 149 callback | | rm-rf | Dramatic fake rm -rf / with commentary | | fork-bomb | Mock fork-bomb warning ("turn your laptop fan into a leaf blower") | | kernel-panic | Friendly BSOD spoof in terminal text | | segfault | Fake core dump with corrupted backtrace | | whoami | Username + existential identity bullets | | last-login | last output with awkward 3am login timestamps | | finger | Faux finger(1) card with snarky plan lines | | who | who listing with ghost users (debugger, coffee, sanity) | | uptime | Ridiculous uptime ("up 632 days, that one incident") | | matrix-rain | Single-frame Matrix rain screen with ACCESS GRANTED footer | | cowsay | Speech bubble + ASCII cow (with word-wrap) | | loading-spinner | Braille spinner cycling at configurable fps | | heartbeat | Pulsing heart — emotional hook for a project you love | | spinning-gear | Rotating \|/-\\ gear for DevOps/infra vibes | | blinking-eyes | Kaomoji mascot that blinks every few seconds | | countdown | T-minus N..0..GO! launch stinger (plays once, freezes on GO) | | sparkline | ASCII sparkline (▁▂▄▇▆▅▃▂) from a numeric series | | bbs-login | Retro 1980s BBS welcome banner — pairs with amber / green-phosphor | | build-badge | Terminal-style project status card (tests / lint / coverage) | | license-card | Boxed License / Copyright card | | ascii-clock | HH:MM:SS clock with pulsing colon separators (12h / 24h) | | progress-bar | Fake build progress bar that fills 0% → 100% | | bouncing-dot | Single glyph bouncing left ↔ right | | dice-roll | N d6 dice that tumble and land on a result | | jumping-jack | Multi-line stick figure doing jumping jacks (reference multi-line animation) | | palette-swatch | One-line render of all 16 theme palette colors | | semver-bump | Current semver + bump preview (major/minor/patch) | | ascii-calendar | Current-month calendar grid with today highlighted | | toc | Auto-generated markdown anchor-link table of contents |

Dynamic API Blocks

Blocks marked with "Live" fetch data at build time from free, SFW APIs. They gracefully fall back to static content on failure.

# Weather in MOTD banner
- block: motd
  config:
    title: "MY TERMINAL"
    weather:
      location: NYC       # City name or coordinates
      units: imperial     # imperial, metric, or both

# Standalone weather block
- block: weather
  config:
    location: "Los Angeles"
    units: metric
    compact: false

# GitHub profile stats
- block: github-stats
  config:
    username: your-github-username

# Top languages across a user's public repos
- block: github-languages
  config:
    username: your-github-username
    top: 5          # top N languages (1-10, default 5)
    barWidth: 20    # bar width in chars (5-40, default 20)

# Random fun fact
- block: fun-fact

Set fetchTimeout at the top level to control API timeout (default: 10000ms):

fetchTimeout: 15000  # 15 seconds — generous for slow APIs

Accessibility

Every generated SVG carries role="img", an aria-label summary of the first commands, plus a <title> and <desc> as its first children. The <desc> contains the full final-frame content (every command prefixed with the prompt, every output line with color markup stripped) so screen-reader users can read more than the 5-command summary.

Opt out if your terminal output is sensitive and you don't want it duplicated as plain text inside the SVG payload:

accessibility:
  describe: false   # default true — emit <desc> with full content

Reduced-motion caveat. The SVG emits an inline @media (prefers-reduced-motion: reduce) rule, which applies to CSS animations — the fade-ins and the frame cycle (single- and multi-line) honor it (migrated SMIL → CSS in v0.17). The remaining SMIL holdouts — typing reveal, cursor walk, and scroll-on-overflow — don't read the same CSS media query, so users who set the OS-level reduced-motion preference still see those animate. If that's a problem for your audience, generate with --static — same content, no motion at all.

Caching API responses

Dynamic blocks cache their responses in .svg-terminal-cache.json next to your config file (24h TTL by default). Commit that file alongside the YAML and CI builds become deterministic — no upstream hits, no diff churn from quote-of-the-minute drift.

# Top-level overrides
cacheTTL: 86400         # seconds (default 86400 = 24h)
cachePath: ".cache.json" # relative to the config file (must stay inside)

CLI control:

svg-terminal generate                       # use cache if fresh, fetch + write back when stale
svg-terminal generate --refresh-cache       # ignore cache entries, re-fetch everything
svg-terminal generate --frozen-cache        # serve only cached values, never fetch (CI offline)
svg-terminal generate --no-cache            # bypass cache entirely (don't read, don't write)

Reproducibility note: committed cache + a generous cacheTTL makes CI reproducible; pair with --frozen-cache to make it truly offline (the build fails loudly if any block lacks a cached entry, rather than silently reaching for the network).

Privacy note: the cache file stores the raw API payloads. If a block fetches data you'd rather not commit (e.g. a private GitHub profile), either skip that block in versioned configs or add .svg-terminal-cache.json to .gitignore.

Custom Blocks

Custom blocks declare a strict zod configSchema so typos throw BlockConfigError at config-load time instead of silently falling back to defaults. Skipping the schema is allowed but discouraged — see CONTRIBUTING.md.

import { z } from 'zod';
import { registerBlock, generate } from 'svg-terminal';

registerBlock({
  name: 'my-block',
  configSchema: z.object({
    greeting: z.string().optional(),
  }).strict(),
  render(context, config) {
    const greeting = (config.greeting as string) ?? 'hello';
    return {
      command: 'my-command',
      lines: [`[[fg:green]]${greeting}, world[[/fg]]`],
    };
  },
});

Programmatic API

// ESM. Run as `node --experimental-vm-modules` or save as .mjs / set "type":"module".
import { generate, generateStatic } from 'svg-terminal';

async function main() {
  const svg = await generate({
    theme: 'nord',
    blocks: [
      { block: 'neofetch', config: { username: 'dev' } },
      { block: 'custom', config: { command: 'date', lines: ['2026-05-25'] } },
    ]
  }, {
    // All fields optional. configPath anchors cachePath resolution if you
    // want the cache; cacheMode is one of 'normal' | 'refresh' | 'frozen' | 'off';
    // now lets you pin context.now for reproducible test/demo output.
    configPath: '/abs/path/to/config.yml',
    cacheMode: 'frozen',
    now: new Date('2026-05-25T13:37:00Z'),
  });
}
main();

generateStatic returns the same content as a non-animated SVG — useful for accessibility fallbacks and social-preview cards.

inspectCache(userConfig, configPath) returns {filePath, results} where each result reports per-cacheable-block status (OK / STALE / MISS) with age in seconds — useful for building your own "is the cache hot?" CI gates without invoking the svg-terminal cache check subcommand. userConfig is the parsed config object; configPath anchors cache-path resolution.

GitHub Action

Complete .github/workflows/refresh-svg.yml for a weekly README refresh:

name: Refresh SVG terminal
on:
  schedule:
    - cron: '0 12 * * MON'   # Mondays at 12:00 UTC
  workflow_dispatch:          # also run on demand

jobs:
  refresh:
    runs-on: ubuntu-latest
    permissions:
      contents: write         # required for commit: true
    steps:
      - uses: actions/checkout@v4
      - uses: williamzujkowski/svg-terminal@v1
        with:
          config: terminal.yml
          output: terminal.svg
          commit: true

For maximally reproducible CI, commit .svg-terminal-cache.json alongside terminal.yml and set cache-mode: frozen — every build will serve cached payloads with zero network calls. The build fails loudly if a cacheable block is missing an entry:

- uses: williamzujkowski/svg-terminal@v1
  with:
    config: terminal.yml
    output: terminal.svg
    cache-mode: frozen   # normal | refresh | frozen | off
    static: false        # set true to skip animation
    minify: false        # set true to strip inter-element whitespace
    commit: true
    commit-message: chore(readme): refresh terminal svg

The action commits as github-actions[bot]; the commit input only runs git add output + git commit + git push against the current branch. Skip commit: true and add your own commit step if you need signed commits or a custom author.

Inputs

| Input | Default | Description | |-------|---------|-------------| | config | terminal.yml | Path to the YAML config file | | output | terminal.svg | Output SVG file path | | cache-mode | normal | Dynamic-block cache behavior: normal | refresh | frozen | off | | static | false | Generate a non-animated SVG (final-frame snapshot) | | minify | false | Strip inter-element whitespace from the output | | commit | false | Auto-commit the generated SVG (needs permissions: contents: write) | | commit-message | chore: update terminal SVG [skip ci] | Commit message when commit: true |

Outputs

| Output | Description | |--------|-------------| | svg-path | Path to the generated SVG file (echoes output) | | svg-bytes | Byte count of the generated SVG (integer string) | | svg-size | Alias of svg-bytes — same value | | svg-sha256 | Hex SHA-256 of the generated SVG, for cache-busting downstream | | svg-changed | true | false — did this run modify a pre-existing SVG? false on the first run (no prior file) and when bytes are unchanged. Gate commits on this. |

- uses: williamzujkowski/svg-terminal@v1
  id: term
  with: { config: terminal.yml, output: terminal.svg }
- run: echo "size=${{ steps.term.outputs.svg-bytes }} changed=${{ steps.term.outputs.svg-changed }}"

Text Markup

Blocks support inline color markup:

[[fg:green]]green text[[/fg]]
[[bg:blue]][[fg:white]]white on blue[[/fg]][[/bg]]
[[fg:cyan]][[bold]]bold cyan[[/bold]][[/fg]]
[[dim]]dimmed text[[/dim]]

Tags: [[fg:color]] (foreground), [[bg:color]] (background), [[bold]], [[dim]] — each closed by its matching [[/fg]] / [[/bg]] / [[/bold]] / [[/dim]], and nestable.

Available colors: red, green, yellow, blue, magenta, cyan, white, orange, purple, pink, comment, plus bright_* variants. Colors resolve against the active theme palette; a raw hex value also works.

ASCII Boxes

The box generator supports multiple styles:

import { createBox } from 'svg-terminal';

createBox({ style: 'rounded', width: 40, lines: ['Hello!'] });
// ╭──────────────────────────────────────╮
// │ Hello!                               │
// ╰──────────────────────────────────────╯

Styles: double (╔═╗), rounded (╭─╮), single (┌─┐), heavy (┏━┓), dashed (┌╌┐)

Contributing

See CONTRIBUTING.md for the local dev loop, the block/theme contribution recipes, and PR conventions.

License

MIT