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).
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 readerlectio-web— a local HTMX browser UI (bundled, no CDN — works offline)
Install
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 <cmd> -h
Versions (public-domain): vul (Wulgata, embedded by default), plus wuj
(Wujek), grb (Grecki), drb (Douay-Rheims) — opt-in (see
Scripture corpora).
Flags:
--lectionary new|traditional— override configlectionaryfor 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 configall = true--raw— omit the banner/headings (for piping)--width N— wrap width;0detects the terminal width, else falls back to 80 (120 forcompare)
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:
lectio
lectio today --all --width 100
date D — same, for an arbitrary date:
lectio date 2026-12-25 --all
show VERSION — one version's text, defaulting to today:
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):
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.
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 astext/calendarGET /source— plain-text source offer (see 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
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
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.
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/<yourname>.css
Restart (or reload) lectio-web; <yourname> 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).
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. Addwuj(build withmake build-full, or dropwuj.tsv/wuj.iniinto your corpora dir — see Scripture corpora) and setreading_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.
Calendar of saints
The universal sanctorale (saints, ranks, colours, names) is embedded and oracle-validated, so it works out of the box. Because the Church changes it over time, it can be updated or customised without a rebuild, in two tiers — both plain INI:
- Local additions overlay on top. Drop
~/.config/lectio/calendars/<name>.iniand enable it withuse = <name>(comma list, later wins). A layer adds a saint, overrides any field of an existing one (rank, colour, name, readings), or suppresses it (suppress = true). This is the place for diocesan/national propers and one-off rank fixes — you only write the deltas.lectio --cal-dump <name>writes a ready-made template intocalendars/: every universal entry, commented out — uncomment a day and edit it (add-l tradto dump the 1962 form). - Replace the whole base. Drop
~/.config/lectio/sanctorale/of.ini(Ordinary Form) oref.ini(Extraordinary Form) and it is used instead of the embedded calendar — for shipping a full refreshed calendar (regenerate withscripts/gen-sanctoral.go). A missing or malformed file falls back to the embedded default, so the tool stays self-contained and correct.
The temporal cycle and the precedence rules (Table of Liturgical Days) are computed in code — only the sanctorale is data.
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). Add
them in either of two ways:
- Compile them in —
make build-full(orgo build -tags fullbible ...) embedswuj,drbandgrbalongside the Vulgate. Do this only where you may redistribute those corpora. - Drop them in at runtime — place
<code>.tsvand<code>.iniin your corpora dir (~/.config/lectio/corpora/). The repo ships the files underinternal/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 <code>.tsv
(book⇥abbr⇥booknum⇥chapter⇥verse⇥text) plus a <code>.ini sidecar (lang,
name, psalm_system); validate with lectio --corpus-check <path>. A version
code lectio can't find renders as (not in <code>) — 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 = <code> 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
<code>.iniin your UI dir (~/.config/lectio/ui/). English and Polish ship built in; use the shippedinternal/i18n/lang/en.inias the full key list to translate (e.g.bookmarks = Zakładki,banner_gospel = Évangile). Only the built-inen/plneed 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<code>.iniinto your names dir (~/.config/lectio/names/) and setui_language = <code>. It overrides the English baseline key by key — translate only what you want; the rest stays English. Use the shippedinternal/naming/lang/pl.inias 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.<lang>). English, Latin and Polish are complete for the universal calendar. To supply or override a saint's name in another language, add a calendar layer (~/.config/lectio/calendars/<layer>.ini) withname.<code> = …entries and enable it viause. 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 https://www.gnu.org/licenses/.
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.
