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

markdansi

v0.2.1

Published

Tiny dependency-light markdown to ANSI converter.

Downloads

205,531

Readme

🎨 Markdansi: Wraps, colors, links—no baggage.

npm license MIT node >=22 tests vitest

Tiny, dependency-light Markdown → ANSI renderer and CLI for modern Node (>=22). Focuses on readable terminal output with sensible wrapping, GFM support (tables, task lists, strikethrough), optional OSC‑8 hyperlinks, and zero built‑in syntax highlighting (pluggable hook). Written in TypeScript, ships ESM.

Published on npm as markdansi.

Install

Grab it from npm; no native deps, so install is instant on Node 22+.

bun add markdansi
# or
pnpm add markdansi
# or
npm install markdansi

CLI

Quick one-shot renderer: pipe Markdown in, ANSI comes out. Flags let you pick width, wrap, colors, links, and table/list styling.

markdansi [--in FILE] [--out FILE] [--width N] [--no-wrap] [--no-color] [--no-links] [--theme default|dim|bright]
[--list-indent N] [--quote-prefix STR]
  • Input: stdin if --in not given (use --in - for stdin explicitly).
  • Output: stdout unless --out provided.
  • Wrapping: on by default; --no-wrap disables hard wrapping.
  • Links: OSC‑8 when supported; --no-links disables.
  • Lists/quotes: --list-indent sets spaces per nesting level (default 2); --quote-prefix sets blockquote prefix (default ).

Library

Use the renderer directly in Node/TS for customizable theming, optional syntax highlighting hooks, and OSC‑8 link control.

ESM / CommonJS

Markdansi ships ESM ("type":"module"). If you’re in CommonJS (or a tool like tsx running your script as CJS), prefer dynamic import:

const { render } = await import('markdansi');
console.log(render('# hello'));

Streaming (recommended: hybrid blocks)

If you’re streaming Markdown (LLM output), keep scrollback safe by emitting completed fragments only and writing them once (append-only; no in-place redraw).

Hybrid mode streams regular lines as they complete, but buffers multi-line constructs that need context:

  • Fenced code blocks (``` / ~~~) — flushed only after the closing fence
  • Tables — flushed only after the header separator row + until the table ends
import { createMarkdownStreamer, render } from 'markdansi';

const streamer = createMarkdownStreamer({
  render: (md) => render(md, { width: process.stdout.columns ?? 80 }),
  spacing: 'single', // collapse consecutive blank lines
});

process.stdin.setEncoding('utf8');
process.stdin.on('data', (delta) => {
  const chunk = streamer.push(delta);
  if (chunk) process.stdout.write(chunk);
});
process.stdin.on('end', () => {
  const tail = streamer.finish();
  if (tail) process.stdout.write(tail);
});
import { render, createRenderer, strip, themes } from 'markdansi';

const ansi = render('# Hello **world**', { width: 60 });

const renderNoWrap = createRenderer({ wrap: false });
const out = renderNoWrap('A very long line...');

// Plain text (no ANSI/OSC)
const plain = strip('link to [x](https://example.com)');

// Custom theme and highlighter hook
const custom = createRenderer({
  theme: {
    ...themes.default,
    code: { color: 'cyan', dim: true }, // fallback used for inline/block
    inlineCode: { color: 'red' },
    blockCode: { color: 'green' },
  },
  highlighter: (code, lang) => code.toUpperCase(),
});
console.log(custom('`inline`\n\n```\nblock code\n```'));

// Example: real syntax highlighting with Shiki (TS + Swift)
import { bundledLanguages, bundledThemes, createHighlighter } from 'shiki';

const shiki = await createHighlighter({
  themes: [bundledThemes['github-dark']],
  langs: [bundledLanguages.typescript, bundledLanguages.swift],
});

const highlighted = createRenderer({
  highlighter: (code, lang) => {
    if (!lang) return code;
    const normalized = lang.toLowerCase();
    if (!['ts', 'typescript', 'swift'].includes(normalized)) return code;
    const { tokens } = shiki.codeToTokens(code, {
      lang: normalized === 'swift' ? 'swift' : 'ts',
      theme: 'github-dark',
    });
    return tokens
      .map((line) =>
        line
          .map((token) =>
            token.color ? `\u001b[38;2;${parseInt(token.color.slice(1, 3), 16)};${parseInt(
              token.color.slice(3, 5),
              16,
            )};${parseInt(token.color.slice(5, 7), 16)}m${token.content}\u001b[39m` : token.content,
          )
          .join(''),
      )
      .join('\n');
  },
});
console.log(highlighted('```ts\nconst x: number = 1\n```\n```swift\nlet x = 1\n```'));

Options

  • wrap (default true): if false, no hard wrapping anywhere.
  • width: used only when wrap===true; default TTY columns or 80.
  • color (default TTY): false removes all ANSI/OSC.
  • hyperlinks (default auto): enable/disable OSC‑8 links.
  • theme: default | dim | bright | solarized | monochrome | contrast or custom theme object.
  • listIndent: spaces per nesting level (default 2).
  • quotePrefix: blockquote line prefix (default ).
  • tableBorder: unicode (default) | ascii | none.
  • tablePadding: spaces inside cells (default 1); tableDense drops extra separators.
  • tableTruncate: cap cells to column width (default true, ellipsis ).
  • codeBox: draw a box around fenced code (default true); codeGutter shows line numbers; codeWrap wraps code lines by default.
  • highlighter(code, lang): optional hook to recolor code blocks; must not add/remove newlines.

Notes

  • Code blocks wrap to the render width by default; disable with codeWrap=false. If lang is present, a faint [lang] label is shown and boxes use unicode borders.
  • Link/reference definitions that spill their titles onto indented lines are merged back into one line so copied notes don’t turn into boxed code.
  • Tables use unicode borders by default, include padding, respect GFM alignment, and truncate long cells with so layouts stay tidy. Turn off truncation with tableTruncate=false.
  • Tight vs loose lists follow GFM; task items render [ ] / [x].

See docs/spec.md for full behavior details.

Looking for the Swift port? Check out Swiftdansi.

MIT license.