DEV Community

Frank Chan
Frank Chan

Posted on

I built a markdown pager for the terminal because I live in the CLI and nothing else worked

Demo

First post here (new user as well). Figured I'd share something I built instead of lurking forever.

I've been leaning hard into AI for my dev workflow this year. Claude, Cursor, agentic coding, the whole thing. And the more I use AI the more I'm in the terminal. Running agents, reading output, checking docs, piping things around. My entire day is basically a terminal now.

And you know what sucks in the terminal? Reading markdown.

I cat a README and get a wall of ### and ** and raw link syntax. I've done this hundreds of times. Sometimes I'd open a browser tab just to read one file, which felt ridiculous when I'm already staring at a terminal.

I tried the obvious stuff. bat is great for source code but it doesn't render markdown, it just syntax-highlights the raw source. glow actually renders it, but it's not a pager. No search, no page-up/page-down, no mouse. less works but then you're reading plain text.

So I built my own thing.

mdcat

It's a TUI pager. You give it a markdown file, it renders it in your terminal with actual colors and formatting.

npm install -g @dunkinfrunkin/mdcat
mdcat README.md # for terminal
mdcat --web README.md # for web
Enter fullscreen mode Exit fullscreen mode

What it does:

  • One Dark color scheme across headings, code blocks, blockquotes, tables, links
  • Syntax highlighting inside fenced code blocks (not just the markdown source, the actual language inside)
  • Incremental search: / to open, n/N to cycle, gutter markers show all matches on screen. Stole this straight from vim
  • Mouse scroll. Toggle it off with M when you need to select text
  • y copies the visible page to clipboard
  • Clickable links if your terminal supports OSC 8 (iTerm2, kitty, WezTerm, Ghostty)
  • Pipes work: curl -s url | mdcat
  • --web flag if you actually do want a browser

How it works under the hood

This is the part I'm honestly most proud of. The whole thing is raw ANSI escape codes. No blessed, no ink, no terminal UI framework. Nothing.

The TUI (alternate screen, draw loop, keyboard input, mouse events via SGR mode) is about 400 lines of vanilla Node. The renderer walks markdown tokens and maps them to ANSI sequences against a hardcoded One Dark palette.

Some of the gnarlier bits if you're into this kind of thing:

Mouse: SGR extended mouse mode (\x1B[?1000h\x1B[?1006h). Scroll events come in as \x1B[<64;x;yM (down) and \x1B[<65;x;yM (up). I spent way too long debugging this before realizing the regex needs to match uppercase M (press) not lowercase m (release). Felt dumb.

Clipboard: Tries OSC 52 first (\x1B]52;c;base64\x1B\\) which works over SSH and in most modern terminals. Falls back to pbcopy on macOS.

Search: ANSI-strips each line before matching, stores hit line numbers in a Set for O(1) lookup during draw. Gutter is 2 chars: for current match, for other matches on screen.

What went wrong

Terminals are way weirder than I expected.

The ANSI-aware string truncation function (vtrunc) was a nightmare. It has to handle multi-byte characters, nested escape sequences, and still calculate visible width correctly or the whole layout breaks. The first version had off-by-one errors everywhere in the table renderer. I'm talking columns shifting by one character depending on whether a cell had bold text or not.

Also learned the hard way: the default npm bundled with Node 20 doesn't support OIDC for scoped packages. Had to use npm 11+ to publish. Not a terminal issue but it cost me an hour of confusion.

Try it

npm install -g @dunkinfrunkin/mdcat
Enter fullscreen mode Exit fullscreen mode

Site: https://mdcat.frankchan.dev/
GitHub: https://github.com/dunkinfrunkin/mdcat

This is my first open source release. It is a blessing now, especially when I'm deep in an agentic coding session and need to quickly check a README or pipe some markdown output through something readable.

What's your terminal workflow look like these days? Curious if anyone else has gone full CLI-brain from using AI tools.


If you want to follow along with what I'm building, more cool stuff to come:
GitHub | X | LinkedIn

Top comments (2)

Collapse
 
zeke-samori profile image
Zeke

Oh wow, this is a must. Thanks, will try out

Collapse
 
alexis_warner_0c7a04c2efb profile image
Alexis Warner

Don't understand some of what you are saying about how you built it but this is needed since I too am heavily using Claude Code. This is great community contribution, thank you!