Skip to content

Repository files navigation

mdview

A portable, zero-dependency CLI tool that renders Markdown files in a browser window with live reload.

Installation

From a release (prebuilt binary)

macOS / Linux — install the latest release into /usr/local/bin (falls back to ~/.local/bin):

curl -fsSL https://raw.githubusercontent.com/simonerom/mdview/main/install.sh | sh

If the install directory isn't on your PATH, the script detects your shell (zsh, bash, fish, ksh) and prints a ready-to-paste command that adds it permanently to the right startup file.

Pin a version or change the install directory:

curl -fsSL https://raw.githubusercontent.com/simonerom/mdview/main/install.sh | MDVIEW_VERSION=v0.1.0 MDVIEW_INSTALL=~/bin sh

Or download an archive for your platform from the releases page and move the mdview binary somewhere on your PATH. Windows builds are published as .zip.

From source

With the Go toolchain installed (1.21+):

go install github.com/simonerom/mdview@latest

This installs mdview into $(go env GOPATH)/bin — make sure that directory is on your PATH.

To build from a clone instead:

git clone https://github.com/simonerom/mdview.git
cd mdview
go build -o mdview .
sudo install -m 0755 mdview /usr/local/bin/mdview

Check the installed version with:

mdview --version

Usage

mdview file.md              # Open a single file
mdview file1.md file2.md    # Concatenate and view multiple files
cat file.md | mdview        # Read from stdin
mdview --window file.md     # Open a standalone window instead of a tab

mdview exits when you close the page, so it leaves nothing running behind you. It waits a few seconds first, because following a link drops the connection the same way a closed window does — and it never exits before a browser has shown up at all, so opening the printed URL by hand still works.

--window uses Chrome, Brave, Edge or Chromium to open a window with no tabs and no address bar, which is what makes mdview behave like an application: close the window and the process is gone. Safari and Firefox have no equivalent, so it falls back to a normal tab there.

Opening from Finder (macOS)

Finder can only "Open With" an application bundle, never a bare binary. macos-app.sh builds a small one in ~/Applications:

./macos-app.sh

Then set it as the default: Get Info on any .md file → Open with → mdview → Change All…

Each file you open gets its own window and its own process, and closing the window ends it.

Three things in that script are easy to get wrong on your own, and each fails in a way that does not point at its cause:

  • A bundle wrapping a plain shell script never receives the file — macOS delivers it as an Apple Event, which only an AppleScript on open handler picks up.
  • osacompile writes no CFBundleIdentifier and only a legacy wildcard type declaration. "Change All…" binds a default handler to a content type keyed by bundle identifier, so with neither in place Finder quietly settles for remembering that one file — and the setting looks like it will not stick.
  • The binary path is resolved and baked in at build time, because an app launched from Finder does not read your shell profile and will not find mdview on PATH.

Editing

The page opens read-only. Press Edit to swap in a split view — the Monaco editor on the left, the live preview on the right — and Ctrl/Cmd+S or Save to write the file. Monaco is bundled into the binary and only fetched when you press Edit, so viewing stays as fast as it was, and editing works offline.

The Edit button appears only when there is exactly one file to save to. It is hidden for stdin (there is no file) and for multiple files (they are concatenated into one buffer, with no way to split them back apart), and for files that are not writable or not valid UTF-8.

If the file changes on disk while you are editing, mdview says so rather than picking a winner: an untouched buffer is refreshed silently, an edited one gets a prompt, and a save that would clobber a newer version on disk is refused and offered as a choice. Writes go through a temporary file and a rename, keeping permissions, symlink targets and CRLF line endings intact.

Features

  • Editing — Monaco with a live preview, off by default, behind an Edit button
  • Live reload — File watcher + SSE pushes reload events to the browser
  • GitHub-flavored Markdown — Tables, task lists, strikethrough, autolinks
  • Syntax highlighting — Fenced code blocks with language detection
  • Dark/light mode — Respects prefers-color-scheme, with a toggle button
  • Clean typography — GitHub-like CSS embedded in binary
  • Portable — Single binary, cross-compile for macOS/Linux/Windows

How It Works

  1. Reads the Markdown file(s)
  2. Converts to HTML using goldmark with GFM extensions
  3. Starts a local HTTP server on a random port
  4. Opens the default browser
  5. Watches the source file for changes and auto-reloads via SSE
  6. Exits a few seconds after the last page is closed, or on Ctrl+C

The server binds loopback and is unauthenticated, which is not on its own enough once a write endpoint exists: any page in your browser can reach it. Every route checks the Host header — that is what stops DNS rebinding — and the editing endpoints additionally require a per-session token that a cross-origin caller cannot send. The vendored editor lives in monaco/; see monaco/VENDOR.md for what was pruned and how to update it.

Releasing

Releases are built automatically by GitHub Actions. Push a version tag and the release workflow cross-compiles for macOS, Linux and Windows (amd64 + arm64), packages the archives with checksums, and publishes a GitHub release with auto-generated notes:

git tag v0.1.0 && git push origin v0.1.0

The tag name is baked into the binary as its --version string.

Example

Here's a table:

Feature Status
Tables
Task lists
Code highlighting
Dark mode
Live reload

And a task list:

  • Parse Markdown
  • Render HTML
  • Live reload
  • World domination
package main

import "fmt"

func main() {
    fmt.Println("Hello from mdview!")
}

This is a blockquote to test styling.


Built with Go and goldmark.

About

Zero-dependency Go CLI to preview Markdown in the browser with live reload

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages