⚠️ MASSIVE WARNING ⚠️
This is 100% AI-generated code. Every single line was written by Codex CLI, Gemini CLI, and Claude Code — the human has not written a single line of Rust. That said, it works well for daily use. No guarantee it won't eat your epub, delete your database, or crash your terminal. You're on your own. PRs welcome.
Rust reimplementation of the awesome CLI ebook reader epy.
The goal is to keep the reading experience and keybindings familiar while improving performance, robustness, and portability by using Rust and a fully self-contained SQLite implementation.
A clean reading experience in repy, showing Marcus Aurelius's Meditations with hyphenation, footnote markers, and progress tracking.
Status
Functional for daily use! Core reading features are complete: TUI navigation, search, bookmarks, library management, two-phase cursor/selection modes, image viewing, link/footnote handling, dictionary lookup, Wikipedia lookup, and TTS (text-to-speech) all work. Text is intelligently wrapped and hyphenated. Reading state and preferences are persisted per-book.
Not yet implemented: export functionality, advanced search features (history, fuzzy, incremental), mouse support, and additional ebook formats beyond EPUB.
See to-do.md for detailed feature status and roadmap.
Installation
Download Binaries
You can download pre-built binaries for Linux, Windows, and macOS from the GitHub Releases page.
- Linux: Download
repy-linux-x86_64(compatible with most modern distributions). - Windows: Download
repy-windows-x86_64.exe. - macOS: Download
repy-macos-universal(works natively on both Intel and Apple Silicon Macs).
After downloading, rename the file to repy (or repy.exe on Windows) and make it executable:
# Linux/macOS chmod +x repy-*-* mv repy-*-* /usr/local/bin/repy
Build from source
If you prefer to build it yourself, you need Rust and Cargo installed.
# Clone this repository git clone https://github.com/newptcai/repy.git cd repy # Build and install cargo install --path .
The bundled rusqlite feature is enabled, so no system-wide libsqlite3
installation is required; SQLite is compiled and linked as part of the build.
Usage
Opening a book
To open any EPUB file (doesn't need to be in your library):
Starting without arguments
If there is a reading history, repy reopens the last-read book at the last saved
position. Otherwise, it starts in the reader UI without a book loaded.
Other options
repy -c FILE # Use a specific configuration file repy -v # Increase verbosity (for debugging) repy --debug # Enable debug output
Note: -r (history) and --dump options are defined but not yet implemented.
Search
Search functionality supports regular expressions.
- Start Search: Press
/to open the search input. - Navigation:
Enter: Jump to the selected result (or the first one if freshly searching).n: Jump to the next search hit.p/N: Jump to the previous search hit.
- Clear Highlights: There is no dedicated key to clear highlights. A workaround is to press
/to start a new search (which clears existing highlights) and thenEscto cancel. - Current Hit: All matching text is highlighted in yellow. When navigating with
n,p, orN, the view jumps to the line containing the match, but the "current" hit is not visually distinguished from other matches on the screen.
Keybindings
Press ? in the TUI to see the help window at any time (Help (?)).
Navigation
k/Up— Line Upj/Down— Line Downh/Left— Page Upl/Right— Page DownSpace— Page DownCtrl+u— Half Page UpCtrl+d— Half Page DownL— Next ChapterH— Previous Chapterg— Chapter StartG— Chapter EndHome— Book StartEnd— Book End
Jump History
Ctrl+o— Jump BackCtrl+i/Tab— Jump Forward
Display
+/-— Increase/Decrease Width=— Reset WidthT— Toggle Top Barc— Cycle Color Theme
Windows & Tools
/— Search!— Text-to-Speech (Toggle)v— Cursor Modet— Table of Contentsm— Bookmarks (ato add,dto delete,Enterto jump)u— Links on Pageo— Images on Pagei— Metadatar— Library (History)j/kto select an entryEnterto open the selected bookdto delete the selected history entry
s— SettingsEnter: Activate (toggle boolean, input for dictionary client)r: Reset to default- Dictionary command templates use
%qas the query placeholder
q— Quit / Close Window
Cursor & Selection Modes
The text-selection flow is two-phase:
- Press
vin the reader to enter Cursor Mode (-- CURSOR MODE --appears in the header). - In cursor mode, move with
hjklor word motionswbe. - Press
vagain to set an anchor and enter Selection Mode. - In selection mode, move with
hjkland word motionswbeto expand/shrink the character-level selection (selection can cross page boundaries). - Press
yto copy the selected text to clipboard. - Press
dto run dictionary lookup on the selection. By default it triessdcv,dict, andwkdict. You can configure a custom command template in Settings (s). - Press
pto run Wikipedia lookup on the selection; the popup shows a link to the page plus the summary (10s timeout). - Press
Escto leave selection mode back to cursor mode; pressEscagain to return to reader mode.
Text-to-Speech (TTS)
Press ! to toggle reading aloud from the current paragraph.
- Engine Support: Defaults to
purr. Cycle through built-in presets by pressingEnteron the TTS Engine row in Settings (s):purr— KittenTTS local neural TTS (default); requires purredge-tts— Microsoft Edge neural TTS; requires edge-tts andmpvorffplaytrans— Google Translate TTS; requires translate-shell
- Custom engine: set
preferred_tts_engineinconfiguration.jsonto a command template:{}is replaced with the spoken text;{output}is replaced with a temp audio file path- If
{output}is present, repy expects the command to write audio to that path, then plays it via mpv/ffplay (with prefetch, same as edge-tts). Example:"preferred_tts_engine": "mytts --text \"{}\" --wav \"{output}\""
- If only
{}is used, the command is expected to speak the text directly (inline). Example:"preferred_tts_engine": "myengine --speed 1.5 \"{}\""
- A bare command name with no placeholders receives the text as its sole positional argument. Example:
"preferred_tts_engine": "myengine"
- 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.
Configuration
The configuration file is automatically created on first run with sensible defaults.
Color Themes
repy supports three built-in color themes:
- Default: Uses terminal colors
- Dark: Gruvbox Dark theme
- Light: Gruvbox Light theme
Press c in the reader to cycle through themes. The selected theme is saved in configuration.json under Settings.color_theme.
Location
The config file location follows this priority order:
- XDG_CONFIG_HOME:
$XDG_CONFIG_HOME/repy/configuration.json - Legacy XDG:
~/.config/repy/configuration.json(if the directory exists) - Legacy home:
~/.repy/configuration.json(fallback) - Windows:
%USERPROFILE%\.repy\configuration.json
If you can't find the config file, run repy -vv to see debug output that will
show you exactly which path is being used.
Configuration options
The configuration is JSON with two sections: Setting and Keymap.
Example configuration.json:
{
"Setting": {
"default_viewer": "auto",
"dictionary_client": "sdcv",
"show_progress_indicator": true,
"page_scroll_animation": true,
"mouse_support": false,
"start_with_double_spread": false,
"seamless_between_chapters": true,
"color_theme": "Default",
"preferred_tts_engine": "purr",
"tts_engine_args": []
},
"Keymap": {
"scroll_up": "k",
"scroll_down": "j",
"page_up": "h",
"page_down": "l",
"quit": "q",
"help": "?"
}
}You can modify any setting or keybinding by editing this file. Changes take effect on next restart.
Database and Reading State
repy stores reading history, last positions, and bookmarks in a SQLite database.
The database file (states.db) is located in the same directory as your config file.
Database schema
-
reading_states— Current position for each bookfilepath,content_index,padding,row,rel_pctg
-
library— Metadata and reading progressfilepath,last_read,title,author,reading_progress
-
bookmarks— Named bookmarks per bookid,filepath,name, plus position fields
When you quit (q from the reader window), repy saves your current position
and updates the library entry. When you open a book, it restores your last position
and any stored bookmarks.
Contributing
This project is still evolving. Bug reports, small focused patches, and feedback on
feature parity with epy are very welcome.