Metadata-Version: 2.5
Name: servo-reader
Version: 0.4.0
Summary: Ultra-lightweight terminal web reader: render any site as clean markdown using the Servo engine.
Author: Parker Brown
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: browser,lynx,markdown,reader,servo,terminal,tui
Requires-Python: >=3.10
Requires-Dist: servo-agent>=0.2.0
Provides-Extra: dev
Requires-Dist: pillow>=10.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: images
Requires-Dist: pillow>=10.0; extra == 'images'
Description-Content-Type: text/markdown

# servo-reader (`sr`)

**Read any website in your terminal as clean, rendered markdown — using the real
[Servo](https://servo.org) browser engine.**

Lynx-grade weight, modern-engine fidelity. Unlike `lynx`/`w3m`, JS-rendered sites
work (a real layout + JS engine loads them); unlike a full browser, the output is
calm wrapped markdown in your terminal — no pixels, no Chromium.

**Tiered fetch** keeps it fast: most pages are read over a plain HTTP GET in
**under a second with no engine at all** — Servo is only spun up when a page comes
back thin / JS-gated. So the giant backend is the *fallback*, not the floor.

```
sr --links https://servo.org/about/
sr https://news.ycombinator.com
sr en.wikipedia.org/wiki/Servo_(software)
sr --raw servo.org/about/ | glow -     # pipe raw markdown elsewhere
sr --width 100 --no-pager some.blog/post
```

## How it works

```
URL ─▶ Tier 1: HTTP GET ──── rich content? ──yes──┐
        │                                         │
        └─ thin / JS-gated / failed?              │
            └▶ Tier 2: Servo (headless servoshell + WebDriver)
                        navigate, settle, post-JS DOM ─┤
                                                       ▼
                          servo_agent.distill (trafilatura / markdownify) ─▶ markdown
                            └─ servo_reader.render (zero-dep ANSI + OSC-8 links) ─▶ terminal
```

`--http` forces the cheap path (instant, no engine); `--servo` forces the engine;
default `auto` does Tier 1 → Tier 2. The footer shows which tier won (`via http` /
`via servo`).

It stands on [`servo-agent`](https://github.com/parker-brown-family/servo-agent)
for the engine plumbing and the distiller, and adds a tiny terminal markdown
renderer (no third-party deps — that is the whole point of a *light* reader).

## Install / run

This is a [uv](https://docs.astral.sh/uv/) project. It is not published on PyPI.
`servo-agent` is pulled straight from GitHub, so a fresh clone builds standalone:

```bash
git clone https://github.com/parker-brown-family/servo-reader
cd servo-reader
uv sync
uv run sr --links https://servo.org/about/
```

To put `sr` on your `PATH` instead, install it as a uv tool:

```bash
uv tool install git+https://github.com/parker-brown-family/servo-reader
```

> Co-developing against a local `servo-agent` checkout? Override the source in
> `pyproject.toml` with `servo-agent = { path = "../servo-agent", editable = true }`.

### The Servo engine

Pages that come back thin over HTTP are rendered with Servo, which needs a built
`servoshell` binary (Servo's shell). servo-reader looks for one in this order:

1. `$SERVOSHELL`, the path to a binary
2. `servoshell` on `PATH`
3. a built `servo/` checkout beside servo-reader
   (`servo/target/debug/servoshell` or `servo/target/release/servoshell`)

```bash
export SERVOSHELL=/path/to/servo/target/debug/servoshell   # a local build, or
export SERVO_WEBDRIVER=host:port                            # an already-running engine
```

Using `$SERVO_WEBDRIVER` against a warm engine skips the per-call browser launch
and is **much** faster than cold-starting a debug `servoshell` each time. See
[Servo's build docs](https://book.servo.org/hacking/building-servo.html) to
produce the binary.

Most reads never touch the engine. Without one, a page that comes back thin over
HTTP still prints its HTTP text, with a note on stderr that Servo rendering was
skipped, and `--servo` exits with an error. Pass `--http` to stay on the cheap
path always.

## Options

| Flag | Meaning |
|------|---------|
| `-L`, `--links` | append a numbered index of the page's links |
| `-l N`, `--follow N` | follow link `N` from the last page read |
| `-b`, `--back` / `-f`, `--forward` | move through visit history |
| `--history` | list recent pages and exit |
| `--images auto\|kitty\|sixel\|off` | inline images (default `auto`-detect; needs `[images]` extra) |
| `--http` | force the cheap HTTP fetch (instant, no engine) |
| `--servo` | force the Servo engine (renders JS) |
| `--engine auto\|http\|servo` | fetch strategy (default `auto`: HTTP → Servo fallback) |
| `--raw` | emit raw markdown, no ANSI (great for piping) |
| `--no-color` | layout without color |
| `--width N` | wrap width (default: terminal, capped at 100) |
| `--max-chars N` | hard cap on extracted text (default 100k) |
| `--no-settle` | skip the post-load quiet wait (faster, static pages) |
| `--no-pager` | never page through `less` |
| `--timeout S` | navigation timeout |

## Browsing — follow links

Every read remembers the page's links. List them with `--links`, then jump:

```bash
sr --links en.wikipedia.org/wiki/Servo_(software)   # numbered link index
sr -l 4                                              # follow link 4
sr -l 12                                             # …and keep going (the new
                                                     #   page's links are saved too)
```

Reads are also recorded in a **back/forward history** with browser semantics:

```bash
sr --history    # list visited pages (▶ marks where you are)
sr --back       # previous page
sr --forward    # forward again (a new read from a back-position re-branches)
```

State lives in `$XDG_STATE_HOME/servo-reader/` (`last.json` links, `history.json`
stack) — small files, no daemon required for navigation.

## Images (kitty / sixel)

Optionally render a page's figures inline:

```bash
uv sync --extra images    # in a clone; adds Pillow, the only extra dependency
# or, as a uv tool:
uv tool install 'servo-reader[images] @ git+https://github.com/parker-brown-family/servo-reader'
sr --images auto en.wikipedia.org/wiki/Servo_(software)
```

`auto` detects the terminal's graphics protocol and **stays off when it can't be
sure** — so it never garbles a terminal that can't decode graphics. Supported:
**kitty / WezTerm / Ghostty** (kitty protocol) and **foot / xterm-with-sixel /
Black Box** (sixel). Force one with `--images kitty|sixel`.

Images are fetched only from `http`/`https` hosts with public addresses, so an
image served from `localhost` or a private network shows the placeholder instead
(see [SECURITY.md](SECURITY.md)).

> Note: terminals on the **alacritty backend — including terminal-delight —
> support neither protocol**, so images won't display there (you'll see a
> `🖼 alt-text` placeholder instead). Use a kitty/sixel terminal for the image lane.

## Warm engine (skip the cold start)

Engine-tier reads cold-start a `servoshell` (~2s). Run one persistent engine and
every read attaches to it automatically:

```bash
sr-engine start     # launch a headless servoshell, register its WebDriver port
sr-engine status    # running?  where?
sr ...              # engine reads now attach to the warm one (no env export needed)
sr-engine stop
```

`sr` discovers the daemon via the same state dir; you can still override with
`$SERVO_WEBDRIVER` to point at any engine. (Measured: an engine read drops from
~2.4s cold to ~1.3s warm.)

## Status

The package version is 0.4.0, which is what `sr --version` prints. Nothing has
been tagged or released, so install from a clone or from the git URL above. It
covers the tiered fetch, link following with back/forward history, the
warm-engine daemon, and inline images. Pillow, for the image lane, is the only
optional dependency.
