Install and run

← Contents

The desktop app

To use reader without a terminal, download the installer for your computer from the latest release: the .dmg for a Mac (arm64 for Apple silicon, x64 for Intel), the .exe for Windows, the .AppImage for Linux. It asks for a folder of documents the first time, and opens the one you had last after that. File → Open Folder… (⌘O) opens another, each in its window, and File → Open Recent the ones before.

It isn’t signed yet, so the first time, a Mac says it can’t check it: open System Settings → Privacy & Security and choose Open Anyway. Windows says it protected your PC: choose More info, then Run anyway.

Install

Node 22.13+.

$ git clone https://github.com/jzheaux/reader && cd reader
$ npm install
$ npm link          # puts `reader` on your PATH

Or run it in Docker, without Node: see Run it in Docker.

Run

$ reader ~/Documents/notes

Opens http://127.0.0.1:8734/. Ctrl-C stops it. Nothing leaves your machine.

Run it again on another directory and that one gets the next free ports (8736, and 8737 for its preview; then 8738, …), so each directory has its own tab. --port asks for one port exactly.

Options

$ reader [dir] [--port 8734] [--frame-port 8735] [--no-browser] [--open <file>] [--day-starts 04:00]
         [--host <address>] [--allow-host <name>]... [--tailscale]
$ reader export <file>... [--out <file>] [--no-draw]
$ reader --version

dir defaults to the current directory. By default the server listens only on 127.0.0.1 and rejects requests addressed to any other host name; --host and --allow-host open it to other devices (below). It rejects any path that would leave dir, including through symlinks.

The page’s preview is served apart from the app, so that what a document draws can’t reach your files: at frames.localhost on the app’s port, where the browser can have that name, and else on a second port, --frame-port, by default the one after the app’s (or any free one, if that’s taken). Safari, and a browser on another device, use the second port, so a firewall or a container that lets the first through has to let the second through too.

reader export saves documents as HTML; see Export and print.

reader --version prints which reader this is, as the foot of the file list shows it, linked to its release notes: v0.0.2, or, run from a checkout that has moved past that release, v0.0.2+abc1234, linked to that commit.

From another device

To use a directory from another device, say from a home server on a tailnet, listen elsewhere and name what that machine is reached by:

$ reader /data --no-browser --host 0.0.0.0 --allow-host my-nas

--host is the address to listen on. --allow-host is a name or address a browser may ask for it by, and can be repeated; loopback names always work, as does a --host address other than 0.0.0.0 or ::, and nothing else does unless named. There is no sign-in: whoever can reach the machine can read and write dir, so the network is the boundary. Keep it to one you trust, such as a tailnet, or put a proxy that signs people in in front of it.

If this machine is on a tailnet, --tailscale does that for you without listening anywhere but 127.0.0.1:

$ reader --tailscale

It serves both ports, the app’s and the preview’s, on the tailnet with tailscale serve, at the same numbers, answers to the tailnet’s names for this machine, and prints the address to open there, like http://my-mac.tail1234.ts.net:8734/. When reader stops it takes them off the tailnet again, and a port already served to reader’s, as by an earlier tailscale serve of your own, it leaves alone. It needs the tailscale command and the machine connected, and as above, anyone on the tailnet can read and write dir.