Index of every page on this site: llms.txt
All skills
harlan-zw avatar

/mdream

@9c10169
by Harlan Wiltonharlan-zw/mdream1k stars
65
Run it yourself

Convert HTML to Markdown, plain text, or safe HTML with mdream, the Rust engine for Node, edge, and the browser. Use when a task mentions mdream, htmlToMarkdown, streamHtmlToMarkdown, the minimal preset, frontmatter or extraction callbacks, tagOverrides, clean options, the mdream CLI, or errors such as "Custom hook plugins require @mdream/js" or ERR_PACKAGE_PATH_NOT_EXPORTED for mdream/plugins.

  • 2 files
  • 9.1 KB
  • MIT
  • Updated last month
  • GitHub
Use this Skill: https://skilld.dev/gh/harlan-zw/mdream/mdream

Nothing lands on disk. Nothing to clean up.

Fork this Skill

Edit a local copy. It keeps the author and licence.

SKILL.md

≈101 tokens for metadata: the name and description. ≈1.5k when used: this file. ≈701 more on demand in 1 file.

Description uses 5.1% of example budget

Before choosing a Skill, your Agent reads its name and description. All available Skills share that space.

  • A shorter description leaves more room for other Skills. This entry exceeds our 1% size suggestion.

In our Claude Code example, all Skill names and descriptions share 8,000 characters. This Skill uses ≈406 characters, or 5.1%.

The 1% threshold is a size suggestion. Longer descriptions can still fit.

Your model, settings, and other Skills decide how much text your Agent can read.

Example settings and source

The example uses a 200k-token context and default Claude Code settings. The count includes the name, description, separators, and when_to_use when present. Codex also counts local file paths.

Skit's source and limits: Codex 0.160.1, Claude Code 2.1.292.

mdream

This Skill covers v2. For an earlier release, read the Skill at its release tag. The package wraps a Rust converter: NAPI in Node, WASM on edge runtimes and in the browser. @mdream/js is a separate pure JS engine. It has hook plugins, the splitter, llms.txt generation, and content negotiation. Its option shape differs; see Traps.

Setup

import { htmlToMarkdown } from 'mdream'

const markdown = htmlToMarkdown('<main><h1>Docs</h1></main>', {
  origin: 'https://example.com',
  minimal: true,
})
  • htmlToMarkdown is synchronous in Node and on edge runtimes. It returns a string.
  • Set origin to make relative href and src values absolute. Without it, they stay relative.
  • If a bundler fails on the native binding, mark mdream as external. In Next.js, add it to serverExternalPackages. @mdream/vite does this for you.

Automatic behaviour

Default output keeps everything: navigation, forms, footers, and text hidden with CSS classes. The <title> text never appears in the body. Read it with frontmatter or extraction.

minimal: true turns on these options. Each one is off without it.

Option Effect under minimal
frontmatter YAML frontmatter from <title> and <meta>.
isolateMain Keeps <main>, else the content from the first heading to the first <footer>.
tailwind font-bold becomes **bold**. hidden and absolute content is dropped.
filter Excludes form, fieldset, object, embed, footer, aside, iframe, input, textarea, select, button, nav.
clean All cleanup: tracking parameters, # links, images without alt, and more.

To turn one off, pass it as false: { minimal: true, frontmatter: false, clean: false }. A filter under minimal adds to the excludes above: { minimal: true, filter: { exclude: ['h1'] } } drops h1 and every default tag. filter: false keeps forms and nav.

Common tasks

Read metadata and matched elements in one pass:

import { htmlToMarkdown } from 'mdream'

const links: string[] = []
let title = ''
const markdown = htmlToMarkdown('<html><head><title>T</title></head><body><a href="/a">A</a></body></html>', {
  frontmatter: (fm) => { title = fm.title ?? '' },
  extraction: {
    'a[href]': (el) => { links.push(el.attributes.href ?? '') },
  },
})

Stream a large response with the same options. Callbacks run once, after the last chunk is read:

import { streamHtmlToMarkdown } from 'mdream'

const response = await fetch('https://example.com')
let markdown = ''
let title = ''
for await (const chunk of streamHtmlToMarkdown(response.body, {
  origin: 'https://example.com',
  minimal: true,
  frontmatter: (fm) => { title = fm.title ?? '' },
})) {
  markdown += chunk
}

Render a custom element with Markdown semantics. Unknown tags output only their text.

import { htmlToMarkdown } from 'mdream'

htmlToMarkdown('<x-heading>Title</x-heading><callout>Read this</callout>', {
  tagOverrides: {
    'x-heading': 'h2',
    'callout': { enter: '> **Note:** ', exit: '', spacing: [2, 2] },
  },
})
// ## Title\n\n> **Note:** Read this

Other output: format: 'text' gives plain text, format: 'html' gives allowlisted semantic HTML. wrapWidth: 80 wraps prose only, never code, tables, or headings.

CLI: curl -s URL | mdream --origin URL --preset minimal. It streams stdin to stdout. Flags: --format, --text, --wrap-width.

Traps

  • Unknown options throw. A key outside the Config list throws a TypeError that names the fix. Pass { minimal: true }, not preset. Pass clean: { urls: true }, not cleanUrls. Pass plugin options at the top level, not in a plugins object. A key set to undefined does not throw. The CLI exits with code 1 on an unknown flag.
  • Hook plugins are not in this package. An array in plugins throws Custom hook plugins require @mdream/js. Use @mdream/js with plugins: [createPlugin({...})] from @mdream/js/plugins.
  • @mdream/js takes plugins as an array. It throws a TypeError on minimal, format, frontmatter, isolateMain, tailwind, filter, or extraction at the top level. Use withMinimalPreset() from @mdream/js/preset/minimal, or { plugins: [frontmatterPlugin()] } from @mdream/js/plugins. For text or HTML output, use @mdream/js/text or @mdream/js/html.
  • Emphasis is *, not _. Headings are ATX, bullets are -, rules are ---. Only tagOverrides changes a delimiter, for example em: { enter: '_', exit: '_', isInline: true }.
  • Filter selectors also drop inline position: absolute and position: fixed elements.
  • Browser bundles import mdream/browser. In a browser bundle, the root mdream entry throws a TypeError when called. Every entry takes the same options and produces the same string. mdream/browser and the CDN script return Promise<string>, so await them. Read references/runtimes.md before you use mdream outside Node or a Cloudflare Worker.

Version limits

Code from mdream 0.x fails on 1.x and 2.x. The subpaths mdream/plugins, mdream/preset/minimal, mdream/splitter, mdream/llms-txt, and mdream/negotiate throw ERR_PACKAGE_PATH_NOT_EXPORTED.

// Old (0.x):
// import { htmlToMarkdown } from 'mdream'
// import { withMinimalPreset } from 'mdream/preset/minimal'
// htmlToMarkdown(html, withMinimalPreset({ origin }))

import { htmlToMarkdown } from 'mdream'

// New:
htmlToMarkdown('<p>x</p>', { minimal: true, origin: 'https://example.com' })

The splitter, llms.txt generation, and shouldServeMarkdown moved to @mdream/js/splitter, @mdream/js/llms-txt, and @mdream/js/negotiate.

Config

Options: origin, minimal, clean (true or per rule), frontmatter, isolateMain, tailwind, filter, extraction, tagOverrides, wrapWidth, format. Types: MdreamOptions in the package README.

Source: SKILL.md on GitHub

skilld matched fixed text patterns in SKILL.md and file names. Patterns miss obfuscated code.

skilld run checks every file with the same patterns. It asks for approval before it loads a Skill with a behavior marked Needs approval.

No third-party reports yet.

Signed by skilld at 9c10169. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated last month

README badge

README badge for harlan-zw/mdream