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

remark-speech

v0.10.0

Published

Remark plugin to add direct speech with metadata for speaker element in markdown

Readme

remark-speech

remark-speech is a remark plugin that adds direct-speech syntax with speaker metadata to Markdown. It includes parsing and serialization support so speech blocks can be read from and written back to Markdown consistently.

Sample

npm install remark-speech remark-parse remark-stringify unified
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { remarkDirectSpeech } from "remark-speech";

const input = `

[[Fay]]"Es ist *Zeit*", säuselte eine helle Stimme, um sich
selbst dazu zu bringen, sich zu rühren.
`;

const file = await unified()
  .use(remarkParse)
  .use(remarkDirectSpeech)
  .use(remarkStringify)
  .process(input);

console.log(String(file));

Input and output keep the same direct-speech block syntax, while the plugin adds AST support for working with speaker metadata.

Block speech

The above is used for "normal" direct speech that is used in most texts. The plugin also supports block speech, which is used for longer speeches or monologues. These can contain multiple paragraphs and generally all block elements. The syntax for block speech is similar to the block quote syntax and is as follows:

Dann holte er aus und begann zu sprechen:
| [[Johnathan]]
| Veränderung ist wie der Wind: Manchmal spürt man sie kaum, und doch bewegt sie ganze
| Bäume. Sie kommt leise, aber ihre Wirkung ist unaufhaltsam. Wer sich ihr widersetzt,
| wird irgendwann gebrochen. Wer sie annimmt, lernt zu fliegen. Die Frage ist nicht, ob
| wir uns ändern können, sondern ob wir den Mut haben, uns zu ändern – bevor das Leben es
| für uns tut.
|
| Ein einzelnes Licht kann die Dunkelheit erhellen, aber viele Lichter zusammen schaffen
| eine Sonne. Gemeinschaft bedeutet nicht, dass wir alle gleich sind, sondern dass
| wir uns gegenseitig stärken – in Schwäche, in Zweifel, in Freude. Denn niemand von
| uns ist so stark wie wir alle zusammen.
|
| …

Sein Redefluss sollte noch viele Stunden ungebrochen weitergehen,
und doch war es nur ein Anfang.

The marker is configurable, depending on the context it may be more appropriate to use a different marker than | for block speech. The default is |, but if your markdown formatter complains you could go to >.

Configuration

Both inline and block speech support a requireSpeaker option.

Inline options

import { remarkDirectSpeech } from "remark-speech";

unified().use(remarkDirectSpeech, {
  spoken: {
    quote: '"',
    closingQuote: '"',
    requireSpeaker: "optional", // "always" | "never" | "optional"
  },
});

Block options

import { remarkDirectSpeechBlock } from "remark-speech";

unified().use(remarkDirectSpeechBlock, {
  spoken: {
    marker: "|",
    requireSpeaker: "optional", // "always" | "never" | "optional"
  },
});

requireSpeaker behavior

  • always: a speaker in [[...]] is required.
  • never: a speaker in [[...]] is not allowed.
  • optional: both forms are allowed.

If requireSpeaker is omitted, the default is optional.

When a node has an empty speaker, serialization omits [[...]].

Error Reporting

The plugin validates speaker rules during processing and emits parser-visible diagnostics on the VFile.

  • Violations are added to file.messages.
  • Messages are marked as fatal (message.fatal = true) so consuming programs can treat them as errors.
  • This is structured diagnostics, not log-only warnings.

Example:

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import { remarkDirectSpeech } from "remark-speech";

const file = unified()
  .use(remarkParse)
  .use(remarkDirectSpeech, {
    spoken: {
      quote: '"',
      requireSpeaker: "always",
    },
  })
  .use(remarkStringify)
  .processSync('"No speaker"');

if (file.messages.length > 0) {
  for (const message of file.messages) {
    console.error(String(message));
  }
}