# lectio `lectio` is a self-contained Go rewrite of the Python `daily-reading` tool (`ewangelia.py`). It **computes** the day's Catholic liturgical calendar and readings entirely offline — Ordinary Form (modern) or Extraordinary Form (1962) — and shows the text across public-domain scripture corpora with correct psalm versification. There is no network dependency at all: the calendar and the lectionary citations are embedded in the binary. Only the Latin Vulgate corpus is embedded by default, so the shipped binary carries no third-party scripture text; the other translations are opt-in (see [Scripture corpora](#scripture-corpora)). Three binaries share the same config and reading pipeline: - **`lectio`** — the CLI (daily readings, version compare, passage lookup, calendar export) - **`lectio-ui`** — a colored, scrollable Bubble Tea terminal reader - **`lectio-web`** — a local HTMX browser UI (bundled, no CDN — works offline) ## Install ```sh make build # ./lectio, ./lectio-ui, ./lectio-web in the current directory make install # builds and installs all three to $PREFIX/bin (default ~/.local/bin) make uninstall # removes them again ``` `PREFIX` can be overridden, e.g. `make install PREFIX=/usr/local`. Cross-compiled binaries for linux/darwin (amd64+arm64) and windows/amd64 land in `dist/` via `make cross`. Requires Go 1.24+ to build. No other dependencies (`go.mod` only pulls in Bubble Tea/Lipgloss for the TUI and go-toml for config parsing). ## `lectio` — the CLI ``` lectio [today] [--all] [--raw] [--width N] [--refresh] lectio date D [--all] [--raw] [--width N] [--refresh] D = YYYY-MM-DD lectio show VERSION [--date D] [--all] [--raw] [--width N] [--refresh] lectio compare LIST [--date D] [--all] [--width N] [--refresh] [--raw] lectio update [--days N] [--from D] lectio --version | -v lectio help | -h | lectio -h ``` Versions (public-domain): `vul` (Wulgata, embedded by default), plus `wuj` (Wujek), `grb` (Grecki), `drb` (Douay-Rheims) — opt-in (see [Scripture corpora](#scripture-corpora)). Flags: - `--lectionary new|traditional` — override config `lectionary` for this run - `--ui-lang pl|en` — override the interface/citation language for this run - `--all` — show every reading part (1st/2nd reading, psalm, acclamation, gospel); default is the gospel only, unless config `all = true` - `--raw` — omit the banner/headings (for piping) - `--width N` — wrap width; `0` detects the terminal width, else falls back to 80 (120 for `compare`) Exit codes: `0` ok, `1` runtime error, `2` usage error. ### Subcommands `today` (also the default with no subcommand) — the gospel (or `--all`) for today, in the config's `default_version`: ```sh lectio lectio today --all --width 100 ``` `date D` — same, for an arbitrary date: ```sh lectio date 2026-12-25 --all ``` `show VERSION` — one version's text, defaulting to today: ```sh lectio show wuj --date 2026-12-25 ``` `compare LIST` — several versions side by side (LIST is comma-separated; falls back to config `versions` if omitted): ```sh lectio compare wuj,drb --date 2026-04-05 ``` ### Calendar API — JSON & iCal Emit the computed liturgical calendar (day identity + reading citations) as JSON or iCal, offline. Honours the config's form (OF/EF) and custom calendar layers. ```sh lectio 2026-01-06 --format json # one day, JSON lectio --format ical --year 2026 > lectio-2026.ics # a year, iCal feed lectio --format json --from 2026-01-01 --to 2026-01-31 # a range lectio --format ical --year 2026 --form new # override the form ``` `lectio-web` also serves the same data for `webcal://` subscription: - `GET /api/calendar.json?date=…` or `?from=&to=` or `?year=` (+ optional `&form=old|new`) - `GET /calendar.ics?year=…` or `?from=&to=` — served as `text/calendar` - `GET /source` — plain-text source offer (see [License](#license)) The web endpoints validate every input and cap a request's span at ~5 years (1830 days); JSON carries a stable `"schema": "lectio.calendar/1"`. Both feeds also carry `source` / `license` (JSON) and `X-LECTIO-SOURCE` / `X-LECTIO-LICENSE` (iCal), so a subscriber who never opens the HTML UI still gets the AGPL source offer. If you fork lectio and run it as a service, set `config.SourceURL` to your own repository. ## `lectio-ui` — the terminal reader ```sh lectio-ui ``` A full-day, scrollable, colored view built on Bubble Tea. Key bindings: | Key | Action | |---|---| | `tab` / `shift+tab` | cycle to next/previous version | | `←` / `→` | previous/next day | | `j`/`↓`, `k`/`↑` | scroll down/up one line | | `space` / `b` | page down/up | | `g` / `G` | jump to top/bottom | | `q` / `ctrl+c` | quit | Colors are a fixed light/dark-adaptive palette (Catppuccin-style, not configurable per instance): the heading is accented, the citation line is faint, verse numbers are muted, verse text is the default foreground, and the header/footer bars use an inverted background. Colors degrade automatically to plain text when `NO_COLOR` is set or the terminal doesn't support them. ## `lectio-web` — the browser UI ```sh lectio-web ``` Starts a local HTTP server, prints its URL, and best-effort opens it in your default browser (failure to open — headless box, no browser — is silent; just visit the printed URL). The port comes from config `web_port`: `0` (the default) tries `1099` first, then falls back to any free OS-assigned port if `1099` is taken; a non-zero value binds that port exactly and fails if it's already in use. All static assets (htmx, base CSS, theme CSS) are embedded in the binary and served locally — there is no CDN, so the page works fully offline once running. Page controls: - **Date navigation** — a date picker plus `←`/`→` buttons - **Lekcjonarz (lectionary)** — new vs traditional, same as `--lectionary` - **Version checkboxes** — wuj/vul/grb/drb, independent of any one visitor's config - **Zakres (all-parts toggle)** — gospel-only vs every part, same as `--all` - **Układ (display mode)** — horizontal / vertical / interlinear, see below - **Motyw (theme picker)** — swaps the theme stylesheet client-side instantly, no server round trip ### Display modes (`web_display`) - **horizontal** (default) — each version's text stacked in its own block under a shared heading - **vertical** — the same per-version text laid out as side-by-side columns, like the CLI's `compare` - **interlinear** — every version interleaved verse-by-verse by chapter:verse ## Configuration Config file: `~/.config/lectio/config.toml` (XDG-aware; auto-seeded with the built-in defaults on first run), or the path in `$LECTIO_CONFIG` if set. An invalid file falls back to defaults with a warning on stderr. ```toml schema_version = 1 lectionary = "new" # "new" (Ordinary Form) or "traditional" (Extraordinary Form, 1962) versions = ["wuj", "vul", "grb", "drb"] # compare set + TUI cycle order; default = corpora available in the build (vul only unless you add more) default_version = "vul" # TUI start / `lectio show` default width = 0 # CLI wrap width; 0 = detect terminal all = false # default to all parts (true) or just the gospel (false) reading_version = "" # force a reading corpus (e.g. "wuj", "drb"); blank = auto/Latin web_theme = "transfiguration" # built-in order/season theme or a user theme in ~/.config/lectio/themes/ web_port = 0 # lectio-web port; 0 = try 1099, then any free port web_display = "horizontal" # lectio-web layout: "horizontal" (stacked), "vertical" (columns), "interlinear" (verse-by-verse) # Which parts to show. Both tables are commented out -> every part is shown. # Uncomment a table and set a part to false to hide it; parts you don't list # stay shown. (Parsed as a map: a part is hidden only if explicitly false.) # # [parts.new] # pierwsze_czytanie = true # 1. czytanie (1st reading) # psalm = true # Psalm # drugie_czytanie = true # 2. czytanie (2nd reading, on feasts) # aklamacja = true # Aklamacja (acclamation) # ewangelia = true # Ewangelia (gospel) # # [parts.traditional] # introitus = true # Introit # oratio = true # Collect # lectio = true # Epistle # graduale = true # Gradual / Alleluia / Tract # evangelium = true # Gospel # offertorium = true # Offertory # secreta = true # Secret # communio = true # Communion # postcommunio = true # Postcommunion ``` CLI flags (`--lectionary`, `--ui-lang`, `--all`, …) and `lectio-web` query parameters override the corresponding config values for that run/request only; the file itself is never rewritten. ## Themes `lectio-web` ships 14 built-in, colour-only themes (they set colours — with all spacing, borders and layout fixed in `base.css`). Each theme now also sets a distinct typeface — serif variants (plain, old-style, classic or Iowan), humanist sans, or plain sans — so themes are told apart by face as well as palette, on top of which a monospace reading face can be turned on for any theme (see `web_mono` and the "mono" top-bar toggle below): Religious orders: `transfiguration` (dark, gold/olive, serif — the default), `desert_fathers` (light, sand/umber, Iowan serif), `benedictines` (dark, black/gold, old-style serif), `franciscans` (dark, brown/terracotta, humanist sans), `memento_mori` (dark, bare monospace — no role colours, just plain text), `dominicans` (black & white, classic serif). Liturgical seasons and feasts: `advent` (dark violet, classic serif), `nativity` (light white/gold, old-style serif), `lent` (dark ashen violet, serif), `easter` (light radiant gold, Iowan serif), `pentecost` (red/white/gold, humanist sans), `ordinary` (light green, sans), `epiphany` (dark indigo/gold, serif), `marian` (light blue/white/gold, old-style serif). Set the default with `web_theme` in config, or pick one live from the "motyw" dropdown in the browser UI. To write your own theme, drop a CSS file defining the required role variables/classes (`--bg`, `--fg`, `.heading`, `.citation`, `.vnum`, `.refrain`, link colours, border colours — see `docs/THEMES.md` for the full list and a template) at: ``` ~/.config/lectio/themes/.css ``` Restart (or reload) `lectio-web`; `` appears in the theme list and overrides any built-in theme of the same name. ## Offline by design Everything the engine needs ships in the binary — there is no cache, no harvest step, and no network access. For any date, `lectio` computes the liturgical day (season, precedence, calendar of saints) and resolves its reading citations from the embedded lectionary tables, then renders the verse text from a scripture corpus — the embedded Latin Vulgate, or another corpus you compiled in or dropped into the corpora dir (see [Scripture corpora](#scripture-corpora)). Reading citations are stored in English-canonical form; the Psalms are renumbered per corpus (Hebrew→Vulgate for `wuj`/`vul`/`grb`, kept modern for `drb`), and the citation shown in the heading is formatted in your sigla dialect (`sigla_style` / `ui_language`). The reading text comes from the configured `reading_version` (else the corpus that matches your language, else the complete Latin Vulgate). Where the chosen corpus lacks a passage, the `-L`/liturgy view falls back to the Latin Vulgate, which carries the full canon. > The modern Polish text (Biblia Tysiąclecia) is copyrighted and cannot be > embedded; the public-domain Polish corpus is Wujek (`wuj`), which is > ~16th-century and incomplete. Add `wuj` (build with `make build-full`, or drop > `wuj.tsv`/`wuj.ini` into your corpora dir — see [Scripture corpora](#scripture-corpora)) > and set `reading_version = "wuj"` for Polish text, or keep the default Latin > Vulgate for a complete, consistent reading. ## Lectionaries Both are computed offline from the embedded calendar engine and lectionary data — no external service: - **new** (default) — the Ordinary Form (post-1969) calendar and lectionary, with the 3-year Sunday cycle (A/B/C) and 2-year weekday cycle (I/II). - **traditional** — the Extraordinary Form (1962 Roman Missal): the temporal and sanctoral of the day with its epistle and gospel. Switch per run with `--lectionary new|traditional`, or set `lectionary` in config for a permanent default. ## Scripture corpora Only the Latin **Vulgate** (`vul`) — public domain, lectio's own hand-curation — is embedded in the default binary. The other translations (`wuj`, `drb`, `grb`) are kept out of the shipped binary so it carries no third-party scripture text, and they are **not covered by lectio's licence** (see [License](#license)). Add them in either of two ways: - **Compile them in** — `make build-full` (or `go build -tags fullbible ...`) embeds `wuj`, `drb` and `grb` alongside the Vulgate. Do this only where you may redistribute those corpora. - **Drop them in at runtime** — place `.tsv` and `.ini` in your corpora dir (`~/.config/lectio/corpora/`). The repo ships the files under `internal/bible/corpora_optional/`; copy the ones you want. A corpus found there is used by all three binaries without a rebuild and overrides an embedded corpus of the same code. Add your own translation the same way: a tab-separated `.tsv` (`book⇥abbr⇥booknum⇥chapter⇥verse⇥text`) plus a `.ini` sidecar (`lang`, `name`, `psalm_system`); validate with `lectio --corpus-check `. A version code lectio can't find renders as `(not in )` — it never fails the day. ## Localising to your language `ui_language` accepts **any** code, and *everything visible* is translatable from plain-text files — no rebuild. Set `ui_language = ` and provide the files below; each overrides the English baseline key by key, so a partial translation just leaves the rest in English. The reading text stays in its source language (that comes from the corpus, not the interface). - **Interface chrome** (button labels, keybar, menu words, column headers) lives in `.ini` in your UI dir (`~/.config/lectio/ui/`). English and Polish ship built in; use the shipped `internal/i18n/lang/en.ini` as the full key list to translate (e.g. `bookmarks = Zakładki`, `banner_gospel = Évangile`). Only the built-in `en`/`pl` need no file. - **Day names** (e.g. "3rd Sunday in Ordinary Time") are generated. English is built in and Polish ships with lectio: `ui_language = pl` → "3. Niedziela Okresu Zwykłego". To add or customise a language, drop a `.ini` into your names dir (`~/.config/lectio/names/`) and set `ui_language = `. It overrides the English baseline key by key — translate only what you want; the rest stays English. Use the shipped `internal/naming/lang/pl.ini` as a template: it maps weekdays, months, season names, ordinals and a few format templates, so word order and grammatical case can follow the target language. - **Saint names** live in the calendar data (`name.`). English and Latin are complete; Polish is partial. To supply or override a saint's name in your language, add a calendar layer (`~/.config/lectio/calendars/.ini`) with `name. = …` entries and enable it via `use`. A name lectio lacks in your language falls back to English, then Latin, then the generated day name. ## License Copyright (C) 2026 Łukasz Kasprzak lectio is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Because this is the **Affero** GPL, section 13 applies to `lectio-web`: if you run a modified version and let people reach it over a network, you must offer those users the source of your modified version. The AGPL covers lectio's own code and data. It does **not** cover the bible corpora: the embedded Latin Vulgate and the opt-in `wuj`/`drb`/`grb` are separate public-domain scripture texts with their own provenance, redistributed under their own terms, not lectio's. See `NOTICE`. Full text in `LICENSE`; third-party data attributions in `NOTICE`.