Content
# gloss
A lightweight, locally-run web UI for **reading** markdown files and attaching
**comments** that are stored to an adjacent JSON file LLMs can easily find.

The intended loop: an LLM proposes something in a `.md` → you read it here and
leave anchored comments → the LLM reads the comments JSON and revises → repeat.
- **Read-only** rendering (GitHub-flavored markdown). The text never changes, so
comment anchors never drift.
- **Text-offset anchoring** — comments pin to exact character ranges in the
rendered text, plus the literal quoted text as a convenience.
- **Keyboard-first** — the document carries a real text caret even though it's
read-only: arrow keys navigate, `⌥`/`⌘`+arrows jump by word/line, and `⇧`
selects (e.g. `⇧⌥→` selects a word). Press `⌘⇧M` to comment on the selection,
`⌘↵` to save, `Esc` to cancel. The hotkeys are configurable in `src/config.js`.
- **Freeform notes** — a side-by-side scratchpad for notes about the file that
aren't tied to any comment. Markdown shortcuts format as you type
(`**bold**`, `_italic_`, `` `code` ``, `- ` / `1. ` lists, ```` ``` ````
blocks); no toolbar, no chrome. Toggle the right panel between **Comments**
and **Notes** with `⌘⇧.`. Notes are stored as markdown in the same
`*.comments.json` file (see below). On Chromium browsers (Chrome/Brave; not
Arc) the notes can **pop out** into a floating, always-on-top window (Document
Picture-in-Picture) — manually via the `⇱` button, or **automatically when you
switch away from the tab** (Spotify-style, via a silent media session) — so you
can keep jotting while you chase links elsewhere. It folds back inline when you
return, and tears down on close.
- **CLI** — launch on any folder; every `.md` under it is browsable.
## Install
```bash
cd gloss
npm install && npm run setup
```
`npm run setup` is idempotent and does everything in one go: links the **`gls`**
command onto your `PATH`, registers the MCP server (`claude mcp add`, user
scope), and installs the **Stop + PostToolUse hooks** that keep live-edit turns
looping, signal the UI when the agent edits, and correlate each review to its
Claude session. It reports exactly what it changed. (Run it again any time; it
won't duplicate.)
## Use
The command is **`gls`** (with `gloss` kept as an alias). If you skipped
`npm run setup`, link it onto your `PATH` once with `npm link`.
**New to gloss? Take the interactive tour:**
```bash
gls review samples/welcome.md
```
`samples/welcome.md` is a live walkthrough — it teaches you commenting, keyboard
navigation, the notes pad, pop-out notes, and the review loop by having you try
each one right in the UI.
Launch on a folder (defaults to the current directory):
```bash
gls ./docs # or: npm start -- ./docs
```
This opens a browser with a list of every `.md` file under `./docs`. Click one
to read and comment. Flags: `--port N`, `--no-open`.
### `gls review` — blocking single-file review
The CLI twin of the MCP tool (below): open one file, **block** until you click
**Done reviewing** (or close the tab), then print the gathered comments + notes.
```bash
gls review ./proposal.md # human-readable summary
gls review ./proposal.md --json # machine-readable, clean JSON on stdout
```
The `--json` form prints the same result shape the MCP tool returns, so any
script, agent, or CI step that can run a shell command gets the same blocking
review loop — no MCP client required. Flags: `--json`, `--no-open`,
`--timeout MS`. Progress (the URL) goes to stderr so stdout stays pure JSON.
To open a specific file directly:
```
http://localhost:5173/?path=/absolute/path/to/file.md
```
Or just run `npm run dev` and pass `?path=...` yourself.
### `gls list` — manage open sessions
See every open review across all projects and close them out:
```bash
gls list # interactive: ↑/↓ move · x close · r refresh · q quit
gls list --json # machine-readable dump (also used when piped)
```
Each row shows the file, whether the owning process is **●live** or an
**○orphan** (left behind by a crashed session), and its age. Closing a live
review ends it gracefully — exactly like clicking **Done**, so its agent gets a
normal `mode:"done"`; orphans are just reaped. Handy when a review is stuck or
you've lost track of what's running.
## The comments file (LLM-facing contract)
Comments for `notes.md` are written to `notes.comments.json` in the same folder:
```json
{
"version": 1,
"file": "/abs/path/to/notes.md",
"notes": "## freeform notes\n\nmarkdown the reviewer typed, not tied to a comment",
"comments": [
{
"id": "0b3c…",
"anchor": { "start": 142, "end": 168 },
"quote": "the exact text that was highlighted",
"body": "the user's comment",
"resolved": false,
"status": "saved",
"createdAt": "2026-06-15T12:00:00.000Z"
}
]
}
```
`anchor.start`/`end` are character offsets into the rendered plain text;
`quote` is the literal highlighted text. An LLM can grep `*.comments.json`,
read each `quote` + `body`, and revise the source `.md` accordingly. The
optional `notes` field holds freeform markdown the reviewer wrote in the Notes
panel — general feedback that isn't anchored to a specific passage.
Each comment also carries some optional bookkeeping fields you can usually
ignore when just reading feedback: `status` is the live-edit lifecycle —
`"saved"` (the default, a normal batch comment), then `"queued"` → `"working"`
→ `"done"` as a "send now" comment is dispatched to the agent (with a
`queuedAt` timestamp); `editedAt` is set if the reviewer amended the body after
first writing it.
## MCP server (LLM-driven review)
An MCP server exposes two tools so an LLM can request human review of a `.md`
file it wrote, read the comments back, and close them out. It's the same blocking
flow as `gls review` (the shared core lives in `lib/review.js`) — use MCP for an
in-loop agent that awaits the result as a tool call, and the CLI for humans,
scripts, or shell-only agents.
`review_markdown`:
1. spins up the gloss UI scoped to the file's folder,
2. opens the file in your browser,
3. **blocks** until you click **Done reviewing** (or simply close the browser
tab — that ends the session too),
4. returns your comments inline (each with an `id`, `quote` + `body`) plus any
freeform `notes` and the comments-file path,
5. spins the UI back down.
`resolve_comments({ path, ids })` then lets the agent mark the comments it
addressed `resolved: true` (gloss keeps owning the JSON — the agent never
hand-edits it). Resolved comments fold under **Show resolved** and drop out of
the next review's open set. The CLI twin is `gls resolve <file.md> <id…>`.
`npm run setup` already registers this for you (with the correct absolute path).
To do it by hand instead, register it once, globally (available in every
project) — from the gloss repo root:
```bash
claude mcp add --scope user gloss -- node "$(pwd)/mcp/server.js"
```
Scopes: `--scope user` = all projects (recommended) · `--scope project` =
committed `.mcp.json` for a shared repo · `--scope local` = current project only.
Tool input: `{ "path": "/abs/file.md", "timeoutMs"?: number }`
(omit `timeoutMs` to wait indefinitely).
## License
[MIT](LICENSE) © Percival Moser