{"slug": "a-few-thoughts-on-building-a-terminal-epub-reader-with-ai", "title": "A few thoughts on building a terminal ePub reader with AI", "summary": "A developer used AI coding agents to port the terminal e-book reader epy from Python to Rust, creating repy, after the original project stopped being maintained. The project took months instead of hours due to the complexity of rendering EPUB in a terminal, and received minimal attention on GitHub. The author reflects on the devaluation of software in an age of abundant AI-generated content.", "body_md": "I like to do things in the terminal. It has little to do with efficiency. It is more my obsession with fiddling with software, so I don't have to think about all the depressing things happening in the world.\n\nOne terminal program I tried is [epy](https://github.com/wustho/epy),\nan e-book reader.\nI quite liked its speed.\nBut like many other open source projects, it stopped being maintained.\n\nSo last November (2025), when coding agents like Codex CLI and Claude Code got better, I started to port epy from Python to Rust. I thought it was a task for an afternoon. After all, how hard can it be to display text in a terminal?\n\nIt turns out that rendering EPUB correctly in a terminal is a messy business. The format was not designed to be displayed in a terminal at all. And for a reasonable reading experience, you have to solve the problem of navigating the book correctly, which the AI struggled with quite a bit. Instead of a few hours, the agents worked for days, adding more and more features, until I was comfortable sharing the project in February this year (2026).\n\nIt got five up-votes and eight stars on GitHub. Perhaps there is not enough interest in terminal-based EPUB readers. Perhaps there is a general dislike of AI-built software. But had this project been published a few years earlier, I doubt the interest would have been so little.\n\nWith the recent release of Claude Fable 5 and GPT Sol 5.6, I added quite a few more features. Yet I doubt it would get even five up-votes this time.\n\nWith the abundance of software comes the devaluation of software. \"Why would I use this, when I can ask my agent to spit out something similar, but closer to my own likes and dislikes?\" A thing anyone can have made at will is a thing nobody treasures.\n\nBooks may be an analogue, even before authors began publishing hundreds of AI-generated novels. I used to read a lot, sometimes two dozen books a year. I kept an eye on authors I liked and tracked what they were publishing. Then I realized there are already far more good books than I can read in this lifetime. So new books, even great ones, mean nothing to me anymore. It is not that the books got worse. It is that my time was always the scarce thing, and now I can see it.\n\nIf we are really seeing the dawn of AGI, perhaps humans have to reckon with this: when there are far more intellectual artifacts --- games, software, books, music, films --- than anyone could consume in a lifetime, what is the meaning of creating more?\n\nBut maybe the question is older than it looks. Seneca complained two thousand years ago that the abundance of books distracts. There was already more than one life could hold; only the speed is new. And nobody ever wrote to finish writing, or read to finish reading. I did not port epy because the world lacked an e-book reader. I ported it to have something to fiddle with --- an afternoon that became months. The worth of making was never in being consumed. If it keeps my mind off the depressing things happening in the world, that is meaning enough, five up-votes or none.\n\n**This is 100% AI-generated code.** Every single line was written by\n[Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli),\nand [Claude Code](https://claude.ai/claude-code) --- the human has not written a single line of Rust.\nThat said, it works well for daily use. No guarantee it won't eat your epub, delete your database,\nor crash your terminal. You're on your own. PRs welcome.\n\nRust reimplementation of the excellent CLI ebook reader [ epy](https://github.com/wustho/epy).\n\n`repy`\n\nkeeps the keyboard-first reading experience familiar while adding fast\nchapter rendering, inline terminal graphics, persistent annotations, and a\nself-contained SQLite library.\n\n*EPUB artwork rendered directly in the reading flow.*\n\n*Jump through the table of contents, then select, copy, annotate, or look up text without leaving the reader.*\n\n**Functional for daily use!** Core reading features are complete: TUI navigation, search, bookmarks,\nlibrary management, two-phase cursor/selection modes, image viewing, link/footnote handling, dictionary lookup,\nWikipedia lookup, persistent highlights/comments, highlight export, and TTS (text-to-speech) all work. Text is intelligently wrapped and hyphenated.\nReading state and preferences are persisted per-book.\n\n**Supported formats:** EPUB, FictionBook (`.fb2`\n\nand `.fb2.zip`\n\n), MOBI6\n(`.mobi`\n\n), plain text (`.txt`\n\n), Markdown (`.md`\n\n), and comic book archives\n(`.cbz`\n\n--- set `\"inline_images\": \"shown\"`\n\nand use a graphics-capable terminal\nsuch as kitty to see the pages). AZW/AZW3 files are accepted on a best-effort\nbasis; KF8-only content may not be readable by the MOBI6 parser.\n\nSee [to-do.md](/newptcai/repy/blob/main/to-do.md) for detailed feature status and roadmap.\n\nYou can download pre-built binaries for Linux, Windows, and macOS from the [GitHub Releases](https://github.com/newptcai/repy/releases) page.\n\n**Linux**: Download`repy-linux-x86_64`\n\n(compatible with most modern distributions).**Windows**: Download`repy-windows-x86_64.exe`\n\n.**macOS**: Download`repy-macos-universal`\n\n(works natively on both Intel and Apple Silicon Macs).\n\nAfter downloading, rename the file to `repy`\n\n(or `repy.exe`\n\non Windows) and make it executable:\n\n```\n# Linux/macOS\nchmod +x repy-*-*\nmv repy-*-* /usr/local/bin/repy\n```\n\nIf you prefer to build it yourself, you need Rust and Cargo installed.\n\n```\n# Clone this repository\ngit clone https://github.com/newptcai/repy.git\ncd repy\n\n# Build and install\ncargo install --path .\n```\n\nThe bundled `rusqlite`\n\nfeature is enabled, so no system-wide `libsqlite3`\n\ninstallation is required; SQLite is compiled and linked as part of the build.\n\nTo open any EPUB, plain-text, or Markdown file (doesn't need to be in your library):\n\n```\nrepy /path/to/book.epub\nrepy /path/to/notes.md\nrepy\n```\n\nIf there is a reading history, `repy`\n\nreopens the last-read book at the last saved\nposition. Otherwise, it starts in the reader UI without a book loaded.\n\nThe `EBOOK`\n\nargument can be a file path, a reading-history number, or a\npattern matched case-insensitively against the title, author, and path of\nhistory entries (the most recently read match wins):\n\n```\nrepy -r          # Print the reading history with numbers and progress\nrepy 3           # Open the 3rd book in the reading history\nrepy dorian      # Open the most recent history entry matching \"dorian\"\nrepy -d BOOK     # Dump the parsed text of an ebook to stdout (pipe to less/grep)\nrepy -c FILE     # Use a specific configuration file\nrepy -v          # Increase verbosity (for debugging)\nrepy --debug     # Enable debug output\nrepy --export-highlights /path/to/book.epub\n```\n\n`--export-highlights`\n\nwrites all persisted highlights/comments for that EPUB to\nstdout. The default format is JSON (including the book identity); pass\n`--format md`\n\nfor Markdown grouped by chapter, with quotes, notes, and dates:\n\n```\nrepy --export-highlights book.epub --format md > notes.md\n```\n\nSearch functionality supports regular expressions.\n\n**Start Search**: Press`/`\n\nto open the search input.**Incremental**: Matches update live as you type, and the view previews the first match at or after your current position.`Esc`\n\nwhile typing cancels and restores the original position. Invalid partial regexes simply show no matches.**History**:`Up`\n\n/`Down`\n\nwhile typing recall previous queries (persisted across sessions, most recent first, capped at 100).`Down`\n\npast the newest entry restores the query you were typing.**Navigation**:`Enter`\n\n: Confirm the query (recorded in history). Then`j`\n\n/`k`\n\nor`Up`\n\n/`Down`\n\nbrowse results, and a second`Enter`\n\njumps and closes the window.`n`\n\n: Jump to the next search hit.`p`\n\n/`N`\n\n: Jump to the previous search hit.\n\n**Clear Highlights**: There is no dedicated key to clear highlights. A workaround is to press`/`\n\nto start a new search (which clears existing highlights) and then`Esc`\n\nto cancel.**Current Hit**: All matching text is highlighted in yellow; the line containing the current hit is highlighted in orange. A`match N/M`\n\ncounter is shown in the top bar and status messages while navigating with`n`\n\n,`p`\n\n, or`N`\n\n.\n\nPress `?`\n\nin the TUI to see the help window at any time (`Help (?)`\n\n).\n\n`k`\n\n/`Up`\n\n--- Line Up`j`\n\n/`Down`\n\n--- Line Down`h`\n\n/`Left`\n\n--- Page Up`l`\n\n/`Right`\n\n--- Page Down`Space`\n\n--- Page Down`Ctrl+u`\n\n--- Half Page Up`Ctrl+d`\n\n--- Half Page Down`L`\n\n--- Next Chapter`H`\n\n--- Previous Chapter`g`\n\n--- Chapter Start`G`\n\n--- Chapter End`Home`\n\n--- Book Start`End`\n\n--- Book End\n\n`Ctrl+o`\n\n--- Jump Back`Ctrl+i`\n\n/`Tab`\n\n--- Jump Forward\n\n`+`\n\n/`-`\n\n--- Increase/Decrease Width`=`\n\n--- Reset Width`T`\n\n--- Toggle Top Bar`c`\n\n--- Cycle Color Theme\n\n`A`\n\n--- Highlights list`Enter`\n\nin highlights list --- Jump to selected highlight`e`\n\nin highlights list --- Edit comment`d`\n\nin highlights list --- Delete highlight`d`\n\nin cursor mode --- Delete highlight under cursor\n\n`/`\n\n--- Search`!`\n\n--- Text-to-Speech (Toggle)`v`\n\n--- Cursor Mode`t`\n\n--- Table of Contents`m<char>`\n\n--- Set a persistent mark (a-z, A-Z, 0-9)``<char>`\n\n--- Jump to a persistent mark`B`\n\n--- Bookmarks (`a`\n\nto add,`d`\n\nto delete,`Enter`\n\nto jump)`u`\n\n--- Links on Page (`Enter`\n\npreviews internal links;`Enter`\n\nagain jumps)`o`\n\n--- Images on Page`Enter`\n\nshows the selected image in the terminal (kitty, iTerm2, or sixel graphics when the terminal supports them, halfblocks otherwise);`Esc`\n\n/`q`\n\nreturns to the list`o`\n\nopens it with the external viewer instead (`default_viewer`\n\nsetting, then`feh`\n\n, then`xdg-open`\n\n); SVG images always use the external viewer- With\n`\"inline_images\": \"shown\"`\n\n(also toggleable in Settings), images render directly in the reading flow: space is reserved under each placeholder and the image appears once its block is fully on screen\n\n`i`\n\n--- Metadata`s`\n\n--- Settings, including typography controls:- paragraph style cycles through\n`spaced`\n\n,`compact`\n\n, and`indented`\n\n; indented paragraphs use a two-column first-line indent - line spacing cycles through\n`1.0`\n\n,`1.5`\n\n, and`2.0`\n\n- justification expands eligible prose lines while leaving final lines, headings, lists, code, centered text, and CJK-only lines unchanged\n\n- paragraph style cycles through\n`r`\n\n--- Library (reading history merged with books found on disk)`j`\n\n/`k`\n\nto select an entry`Enter`\n\nto open the selected book`c`\n\nto show or hide the selected book's details and cover (off by default)`f`\n\nto cycle among available formats for a Calibre book`R`\n\nto refresh configured library directories`d`\n\nto delete the selected history entry`s`\n\nto cycle the sort order: recent / title / author / series / progress- Books found in\n`library_directories`\n\nbut never opened show as`new`\n\n/`unread`\n\n; history entries whose file has disappeared are marked`[missing]`\n\n- When enabled with\n`c`\n\n, a responsive details panel shows metadata and all available formats; supported graphics terminals also show the cover (Calibre-style`cover.jpg`\n\nfiles are used directly, otherwise the cover is read from the ebook)\n\n`R`\n\n--- Reading Statistics`s`\n\n--- Settings`Enter`\n\n: Activate (toggle boolean, input for dictionary client)`r`\n\n: Reset to default- Dictionary command templates use\n`%q`\n\nas the query placeholder\n\n`q`\n\n--- Quit / Close Window\n\nIn the Table of Contents, Bookmarks, Highlights, and Library windows, press\n`/`\n\nto fuzzy-filter the list. Matches narrow as you type, best match first.\n`Enter`\n\nacts on the selected entry directly, or confirms the filter so\n`j`\n\n/`k`\n\ncan navigate the narrowed list; `Esc`\n\nclears the filter (a second\n`Esc`\n\ncloses the window).\n\nThe text-selection flow is two-phase:\n\n- Press\n`v`\n\nin the reader to enter**Cursor Mode**(`-- CURSOR MODE --`\n\nappears in the header). - In cursor mode, move with\n`h`\n\n`j`\n\n`k`\n\n`l`\n\n, word motions`w`\n\n`b`\n\n`e`\n\n, line motions`^`\n\n(first non-blank) and`$`\n\n(end of line), paragraph motions`[`\n\nand`]`\n\n,`f<char>`\n\n/`F<char>`\n\nto jump to the next / previous occurrence of a literal character on the current line, or`t<char>`\n\n/`T<char>`\n\nto land just before / after it. All motions accept a numeric count prefix (e.g.`5j`\n\n,`3w`\n\n,`2]`\n\n,`3fa`\n\n).- When the cursor is on a highlighted span, press\n`Enter`\n\nto edit that highlight's comment. - Press\n`d`\n\nto delete the highlight under the cursor; if it has a non-empty comment a confirmation popup is shown (`y`\n\ndeletes,`n`\n\n/`Esc`\n\ncancels). - Press\n`C`\n\nto cycle the color of the highlight under the cursor (yellow → green → blue → pink → purple). New highlights use the last color chosen this way. - Rows covered by a highlight show a colored\n`▎`\n\nmargin indicator in a 1-column left gutter (reserved as soon as the book has any highlight).\n\n- When the cursor is on a highlighted span, press\n- Press\n`v`\n\nagain to set an anchor and enter**Selection Mode**. - In selection mode, move with the same motions as cursor mode (\n`h`\n\n`j`\n\n`k`\n\n`l`\n\n,`w`\n\n`b`\n\n`e`\n\n,`^`\n\n`$`\n\n,`[`\n\n`]`\n\n,`f<char>`\n\n/`F<char>`\n\n,`t<char>`\n\n/`T<char>`\n\n, all with optional count prefix) to expand/shrink the character-level selection (selection can cross page boundaries). - Press\n`y`\n\nto copy the selected text to clipboard. - Press\n`a`\n\nto save a highlight for the selection (using the last-used highlight color). - Press\n`c`\n\nto save a highlight and immediately edit its plain-text comment. - Press\n`d`\n\nto run dictionary lookup on the selection. By default it tries`sdcv`\n\n,`dict`\n\n, and`wkdict`\n\n. You can configure a custom command template in Settings (`s`\n\n). - Press\n`p`\n\nto run Wikipedia lookup on the selection; the popup shows a link to the page plus the summary (10s timeout). - Press\n`s`\n\nto search the selection with Ecosia in your browser. - Press\n`Esc`\n\nto leave selection mode back to cursor mode; press`Esc`\n\nagain to return to reader mode.\n\nIn both cursor and selection mode, press `/`\n\nto search within the currently\nvisible screen and jump the cursor to the first match; `n`\n\n/ `N`\n\ncycle through\nmatches. The query is plain text (regex specials are escaped) with smartcase\nmatching, and spaces in the query match across line wraps and soft hyphens, so\n`/example`\n\nwill find `exam-`\n\n/ `ple`\n\neven when the wrapper has split the word\nacross two lines. In selection mode the anchor stays put, so each jump extends\nthe selection.\n\nHighlights are anchored to normalized chapter text with prefix/suffix context, so they survive text-width changes and small whitespace or formatting edits. Cross-chapter highlights are not supported yet.\n\nPress `!`\n\nto toggle reading aloud from the current paragraph.\n\n**Engine Support**: Defaults to`purr`\n\n. Cycle through built-in presets by pressing`Enter`\n\non the**TTS Engine** row in Settings (`s`\n\n):`purr`\n\n--- KittenTTS local neural TTS (default); requires[purr](https://github.com/rany2/purr)`edge-tts`\n\n--- Microsoft Edge neural TTS; requires[edge-tts](https://github.com/rany2/edge-tts)and`mpv`\n\nor`ffplay`\n\n`trans`\n\n--- Google Translate TTS; requires[translate-shell](https://github.com/soimort/translate-shell)\n\n**Custom engine**: set`preferred_tts_engine`\n\nin`configuration.json`\n\nto a command template:`{}`\n\nis replaced with the spoken text;`{output}`\n\nis replaced with a temp audio file path- If\n`{output}`\n\nis present, repy expects the command to write audio to that path, then plays it via mpv/ffplay (with prefetch, same as edge-tts). Example:\n\n```\n\"preferred_tts_engine\": \"mytts --text \\\"{}\\\" --wav \\\"{output}\\\"\"\n```\n\n- If only\n`{}`\n\nis used, the command is expected to speak the text directly (inline). Example:\n\n```\n\"preferred_tts_engine\": \"myengine --speed 1.5 \\\"{}\\\"\"\n```\n\n- A bare command name with no placeholders receives the text as its sole positional argument. Example:\n\n```\n\"preferred_tts_engine\": \"myengine\"\n```\n\n**Visual Feedback**: The paragraph currently being read is underlined in the UI.** Smart Scrolling**: The reader automatically scrolls to keep the active paragraph visible as it progresses through the book.** Granularity**: Text is sent to the TTS engine in manageable chunks (sentence-by-sentence) to ensure responsiveness and proper UI syncing.\n\nThe configuration file is automatically created on first run with sensible defaults.\n\n`repy`\n\nsupports four built-in color themes:\n\n**Default**: Uses terminal colors** Dark**: Gruvbox Dark theme** Light**: Gruvbox Light theme** Sepia**: Warm paper-like palette (classic e-reader sepia mode)\n\nPress `c`\n\nin the reader to cycle through themes. With a book open, the selected\ntheme is saved for that book; otherwise it is saved in `configuration.json`\n\nunder `Settings.color_theme`\n\n.\n\nThe config file location follows this priority order:\n\n**XDG_CONFIG_HOME**:`$XDG_CONFIG_HOME/repy/configuration.json`\n\n**Legacy XDG**:`~/.config/repy/configuration.json`\n\n(if the directory exists)**Legacy home**:`~/.repy/configuration.json`\n\n(fallback)**Windows**:`%USERPROFILE%\\.repy\\configuration.json`\n\nIf you can't find the config file, run `repy -vv`\n\nto see debug output that will\nshow you exactly which path is being used.\n\nThe configuration is JSON with two sections: `Setting`\n\nand `Keymap`\n\n.\n\nExample `configuration.json`\n\n:\n\n```\n{\n  \"Setting\": {\n    \"default_viewer\": \"auto\",\n    \"dictionary_client\": \"sdcv\",\n    \"show_progress_indicator\": true,\n    \"page_scroll_animation\": true,\n    \"mouse_support\": false,\n    \"seamless_between_chapters\": true,\n    \"color_theme\": \"Default\",\n    \"preferred_tts_engine\": \"purr\",\n    \"tts_engine_args\": [],\n    \"library_directories\": [\"~/Calibre\", \"~/Books\"],\n    \"opds_catalogs\": [\n      {\n        \"name\": \"Project Gutenberg\",\n        \"url\": \"https://www.gutenberg.org/ebooks/search.opds/\",\n        \"username\": null,\n        \"password\": null\n      }\n    ],\n    \"opds_download_directory\": null,\n    \"inline_images\": \"placeholder\",\n    \"paragraph_style\": \"spaced\",\n    \"line_spacing\": \"single\",\n    \"justify_text\": false,\n    \"kosync_server\": \"https://sync.koreader.rocks\",\n    \"kosync_username\": \"your-koreader-sync-user\",\n    \"kosync_password\": \"your-password\"\n  },\n  \"Keymap\": {\n    \"scroll_up\": \"k\",\n    \"scroll_down\": \"j\",\n    \"page_up\": \"h\",\n    \"page_down\": \"l\",\n    \"add_highlight\": \"a\",\n    \"add_highlight_comment\": \"c\",\n    \"show_highlights\": \"A\",\n    \"quit\": \"q\",\n    \"help\": \"?\"\n  }\n}\n```\n\nFrom the Library, press `O`\n\nto browse the catalogs in `opds_catalogs`\n\n. `Enter`\n\nopens a catalog, navigation entry, or downloadable publication; `/`\n\nsearches\nwhen the server advertises OpenSearch; `[`\n\nand `]`\n\npage; `f`\n\nchanges format;\n`c`\n\nshows publication details; and `h`\n\n/Backspace returns to the previous feed.\n\nOPDS 1.2 Atom catalogs are supported. Downloads run in the background, are\nvalidated before being saved permanently, and then open in the reader. A null\ndownload directory uses the platform Downloads directory under `repy`\n\n(with an\napp-data fallback). Basic-auth credentials are sent only to the configured\ncatalog origin and passwords are never shown in the UI. Catalog entries remain\nconfiguration-file based. The shared catalog model is version-neutral, so OPDS\n2.0 support can be added as a JSON parser without changing the browser or\ndownload pipeline.\n\nYou can modify any setting or keybinding by editing this file. Changes take effect on next restart.\n\nPull-only.`repy`\n\nfollows the reading position saved by KOReader but never writes its own back to the server. KOReader repositions EPUBs from a CREngine XPointer that`repy`\n\ncannot generate, so pushing would only overwrite KOReader's bookmark and send a KOReader user to the start of the book.`repy`\n\ntherefore reads progress and leaves the server record untouched.\n\n`repy`\n\ncan pull the reading position of an identical ebook file from KOReader's\nprogress-sync service. Register the account in KOReader. The server defaults to\nthe official public service at `https://sync.koreader.rocks`\n\n, so normally you\nonly need to set `kosync_username`\n\nand `kosync_password`\n\n. The password is stored\nas plaintext in `configuration.json`\n\n; on Unix, `repy`\n\nrestricts that file to the\ncurrent user (`0600`\n\n). `repy`\n\nderives the MD5 kosync authentication key in\nmemory.\n\nTo place a pulled position accurately, `repy`\n\nreads the **CREngine XPointer**\nKOReader stores alongside the percentage (e.g.\n`/body/DocFragment[14]/body/p[1]/text().0`\n\n). The `DocFragment`\n\nindex pins the\nexact chapter, and the element path places you within it --- so a \"start of\nchapter 14\" bookmark lands at the start of chapter 14 rather than drifting by a\nparagraph. `repy`\n\nonly *reads* this pointer; it still cannot generate one, which\nis why sync stays pull-only. When the pointer is absent or cannot be resolved\n(e.g. heavily transformed markup), `repy`\n\nfalls back to a width-independent\n**content percentage** --- the fraction of the book's characters before your\ncurrent line. `repy`\n\npulls on open and prompts before jumping when KOReader is\nfurther ahead; the Settings window also offers a **Pull KOReader progress now**\naction. The sync service receives only the KOReader document fingerprint,\npercentage, device label, and timestamp --- not the ebook, filename, highlights,\nor notes.\n\nKOReader identifies a document using sampled bytes from the file. Both devices must therefore use the same unmodified ebook file; reconversion or metadata rewrites can prevent matching.\n\nSet `\"library_directories\"`\n\nto a list of directories to scan for EPUB files\n(`~`\n\nexpands to your home directory):\n\n```\n\"library_directories\": [\"~/Calibre\", \"~/Books\"]\n```\n\nOpening the Library window (`r`\n\n) then shows your reading history merged with\nevery book found in those directories, and refreshes the list with a\nbackground scan. Metadata is cached in the database keyed by file path and\nmodification time, so repeat scans only read new or changed files.\n\nA [Calibre](https://calibre-ebook.com/) library works as-is: point\n`library_directories`\n\nat the Calibre library root. `repy`\n\nreads the root\n`metadata.db`\n\nthrough an immutable, read-only SQLite connection, obtaining\nbooks, formats, authors, series, tags, languages, publishers, comments, and\ncovers without walking every ebook archive. If the database is unavailable or\nits schema is incompatible, `repy`\n\nautomatically falls back to the per-book\n`metadata.opf`\n\nfiles and directory scan. Calibre's database and library files\nare never written.\n\nSet `\"mouse_support\": true`\n\n(or toggle it in the Settings window, where it\napplies immediately) to enable the mouse:\n\n- The wheel scrolls the reading view (3 lines per tick) and moves the selection in list windows and scrollable popups.\n- Left-clicking a line that contains a link follows it; if the line has several links, the links window opens instead.\n\nWhen `mouse_support`\n\nis off (the default), the terminal keeps its native\nmouse behavior, so you can select and copy text the usual way.\n\n`repy`\n\nstores reading history, last positions, jump history, marks, bookmarks, and highlights in a SQLite database.\nThe database file (`states.db`\n\n) is located in the same directory as your config file.\n\n-\n--- Current position for each book`reading_states`\n\n`filepath`\n\n,`content_index`\n\n,`textwidth`\n\n,`row`\n\n,`rel_pctg`\n\n, optional per-book`color_theme`\n\n-\n--- Metadata and reading progress`library`\n\n`filepath`\n\n,`last_read`\n\n,`title`\n\n,`author`\n\n,`reading_progress`\n\n-\n--- Metadata cache for books found in`library_files`\n\n`library_directories`\n\n`filepath`\n\n,`mtime`\n\n,`title`\n\n,`author`\n\n; refreshed by the background scan\n\n-\n--- Named bookmarks per book`bookmarks`\n\n`id`\n\n,`filepath`\n\n,`name`\n\n, plus position fields\n\n-\nand`jump_history`\n\n--- Per-book jump list and Vim-style marks`marks`\n\n- Jump entries are row lists; marks store a one-character name plus position fields\n\n-\n--- Reading statistics keyed by stable book identity`reading_sessions`\n\n`book_id`\n\n, start/end time, duration, rows, and words\n\n-\nand`books`\n\n--- Stable EPUB identity and path aliases`book_aliases`\n\n- Book identity uses metadata plus spine href and content fingerprints, not just file path\n\n-\n--- Persistent highlight anchors and plain-text comments`highlights`\n\n- Stores exact text, prefix/suffix context, approximate normalized offset, color, comment, and resolution status\n\nWhen you quit (`q`\n\nfrom the reader window), `repy`\n\nsaves your current position,\nupdates the library entry, and flushes the active reading-statistics session.\nWhen you open a book, it restores your last position and any stored bookmarks,\nmarks, jump history, highlights, and per-book theme.\n\nThis project is still evolving. Bug reports, small focused patches, and feedback on\nfeature parity with `epy`\n\nare very welcome.", "url": "https://wpnews.pro/news/a-few-thoughts-on-building-a-terminal-epub-reader-with-ai", "canonical_source": "https://github.com/newptcai/repy", "published_at": "2026-07-17 07:17:09+00:00", "updated_at": "2026-07-17 07:51:23.881311+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools"], "entities": ["Codex CLI", "Claude Code", "Gemini CLI", "epy", "repy", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/a-few-thoughts-on-building-a-terminal-epub-reader-with-ai", "markdown": "https://wpnews.pro/news/a-few-thoughts-on-building-a-terminal-epub-reader-with-ai.md", "text": "https://wpnews.pro/news/a-few-thoughts-on-building-a-terminal-epub-reader-with-ai.txt", "jsonld": "https://wpnews.pro/news/a-few-thoughts-on-building-a-terminal-epub-reader-with-ai.jsonld"}}