rmd

module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT

README

rmd

Read a markdown file with browser-grade typography - one command, one self-contained page, no process left running.

Platform Go Build License

rmd <file.md> renders a markdown file into a single self-contained HTML page, serves it once on a random loopback port, opens it in your default browser, and exits as soon as the page confirms it loaded. Every asset - fonts, CSS, JS, local images, Mermaid, KaTeX - is inlined into that one HTML document, so the tab keeps working (themes, font sizes, diagrams) long after the process is gone.

rmd notes.md   # render, open in the browser, exit
rmd -version   # print version

Quick install via the personal Homebrew tap (other methods under Install):

brew install dsbasko/tap/rmd   # install
brew upgrade rmd               # update to the latest release

Contents


Why

Reading .md in a terminal or an IDE is a compromise: monospaced everything, no real typography, no themes. The usual fixes are worse in their own way - a dedicated viewer app you have to install and keep updated, or a grip-style preview server that stays resident for as long as the tab is open.

rmd is the third option: browser typography with a one-shot process. It does its job - render, serve once, open - and exits. The page it leaves behind is fully autonomous: close the laptop, go offline, reopen the tab tomorrow - the theme switcher and every diagram still work, because nothing on that page references the network or the dead server.

How it works

  1. Parse. goldmark with GFM, footnotes, and auto heading ids turns the markdown into HTML. Along the way, AST transformers inline local ![]() images as data:<mime>;base64,…, convert ```mermaid fences into <pre class="mermaid"> blocks, and capture math ($$…$$, \(…\), \[…\]) at parse time, before CommonMark can mangle the LaTeX. Code blocks get chroma classes with a palette generated per theme.
  2. Assemble. html/template + go:embed build one self-contained page: CSS, JS, woff2 fonts, mermaid.min.js, and katex.min.js are embedded into the binary and inlined into the HTML. Mermaid and KaTeX are included only when the document actually uses them. There is not a single external URL in the output.
  3. Serve and open. An HTTP server starts on 127.0.0.1:<random port>, the URL is printed to stdout, and the browser opens it (open / xdg-open / cmd depending on the platform).
  4. Exit. On window.load the page sends navigator.sendBeacon('/done'); the server shuts down and the process exits 0.

Two safety valves keep it from hanging around:

  • no GET / within 60 s → exit 1 (the URL is already on stdout, so you can still open it by hand);
  • page served but no beacon within 15 s → exit 0 (the content was delivered in full; the server has nothing left to do).

Install

Homebrew (easiest):

brew install dsbasko/tap/rmd   # install
brew upgrade rmd               # update to the latest release

Installs from the personal tap dsbasko/homebrew-tap (not homebrew-core), building from source. The build needs no network access beyond fetching the tarball: all page assets are committed to the repository and embedded via go:embed.

Quick (go install):

go install github.com/dsbasko/rmd/cmd/rmd@latest

Drops the binary in $(go env GOPATH)/bin.

From a clone:

git clone https://github.com/dsbasko/rmd
cd rmd
make install   # go install into $GOBIN
make build     # or: local binary in ./bin/rmd

Usage

rmd notes.md      # render and open in the browser
rmd -version      # print version and exit

What happens on a run:

  1. the markdown is parsed into a self-contained HTML page;
  2. a local server starts on 127.0.0.1:<random>, its URL is printed to stdout;
  3. the page opens in the default browser (a failed open is a warning, not fatal - the URL is already printed);
  4. after window.load the browser sends navigator.sendBeacon('/done'), the server shuts down, the process exits 0.

The tab stays fully functional after the process ends - theme and size switching work offline.

Themes and sizes

Settings are not persisted: every run starts from the system theme (prefers-color-scheme) and size m. The toolbar in the top-right corner controls both; choices live until the tab is closed (no localStorage).

  • Themes: ☀ light · ☾ dark · ¶ paper. With no choice made, the system light/dark preference applies; paper is manual-only.
  • Sizes: xs / s / m / l / xl → --prose-font-size 13 / 14.5 / 16 / 18 / 20 px (code font ~2 px smaller), with the content column width scaling along. Default is m.

Rendering features

  • GFM: tables, blockquotes (including nested), task-list checkboxes, strikethrough, autolinks, footnotes.
  • Code highlighting: chroma with CSS classes; palettes generated per theme (github for light, github-dark for dark, solarized-light for paper).
  • Mermaid: ```mermaid fences render client-side; diagram theme follows the page theme.
  • KaTeX: $$…$$, \(…\), \[…\]. Single $…$ is deliberately not supported, so "from $10 to $20" never turns into math.
  • Images: local paths (relative or absolute) are inlined as data:<mime>;base64,…; http(s):// and data: sources are left as is; a missing file produces a warning on stderr and the node is left untouched.
  • Fonts: Roboto Slab (prose) and JetBrains Mono (code) are embedded with Latin and Cyrillic subsets - nothing needs to be installed in the OS.

Exit codes

Code Meaning
0 Clean exit: beacon received, beacon timeout after serving, or Ctrl+C.
1 Error: file missing / not a regular file, render failure, or no GET within 60 s.
2 Usage: no positional argument or an unknown flag.

Known limitations

  • Raw HTML <img src="…"> and url() inside user HTML are not inlined - only markdown ![]() nodes are processed.
  • GitHub alerts, TOC generation, and multi-file navigation are not supported - one file per run.
  • No theme/size CLI flags - the on-page toolbar is the only control.

Building from source

Go 1.26+, no cgo, no network needed for the build - every embedded asset is committed to the repository.

make build     # ./bin/rmd with -ldflags "-s -w -X main.version=…"
make test      # go test ./...
make lint      # golangci-lint run
make install   # go install into $GOBIN
make clean     # remove ./bin

Rough layout:

cmd/rmd/            CLI entry point; run(args, stdout, stderr) int is the testable wrapper
internal/render/    goldmark → Result{HTML, Title, HasMermaid, HasKatex, Warnings}
internal/page/      Build(Params) []byte - html/template + go:embed → self-contained HTML
internal/server/    one-shot loopback server: Start() → url; Wait(ctx) handles shutdown
internal/browser/   Open(url) - open / xdg-open / cmd behind a mockable platform runner
scripts/            vendor-assets.sh - re-vendors the pinned embedded assets

Updating embedded assets (fonts, Mermaid, KaTeX) goes through scripts/vendor-assets.sh - versions are pinned in its header, and the result is committed to the repository.

License

Released under the MIT License. See LICENSE for the full text.

© 2026 Dmitriy Basenko.

Directories

Path Synopsis
cmd
rmd command
Command rmd renders a markdown file into a single self-contained HTML page, serves it on a random loopback port, opens it in the browser and exits once the page has loaded.
Command rmd renders a markdown file into a single self-contained HTML page, serves it on a random loopback port, opens it in the browser and exits once the page has loaded.
internal
browser
Package browser opens a URL in the platform's default web browser.
Package browser opens a URL in the platform's default web browser.
page
Package page assembles a render.Result into a single self-contained HTML document.
Package page assembles a render.Result into a single self-contained HTML document.
render
Package render converts a markdown source into a self-contained HTML fragment together with metadata (title, feature flags, warnings) used by the page builder.
Package render converts a markdown source into a self-contained HTML fragment together with metadata (title, feature flags, warnings) used by the page builder.
server
Package server serves a single self-contained HTML page on a random loopback port and shuts down once the browser reports the page has loaded (via a POST /done beacon) or a timeout elapses.
Package server serves a single self-contained HTML page on a random loopback port and shuts down once the browser reports the page has loaded (via a POST /done beacon) or a timeout elapses.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL