Skip to content

Repository files navigation

📖 Sage

A blazingly fast, asynchronous terminal-based web novel reader built in Rust.

Read, cache, and export web novels from NovelBuddy and NovelFire — entirely from your terminal.

ReleaseRustPlatformLicense

Sage TUI web novel reader demo


What is Sage?

Sage is a high-performance TUI (terminal user interface) application for reading web novels. It connects to online novel platforms, fetches chapter catalogs, stores everything in a local SQLite database, and renders formatted text in a fully customizable terminal reader.

Built with ratatui and crossterm, Sage runs on Linux, macOS, and Windows without requiring a browser or GUI.

Key Capabilities

FeatureDescription
Cloudflare BypassJA3 fingerprint impersonation via primp with Chrome V144 — requests appear as legitimate browser traffic
Offline ReadingBackground-download entire novels into SQLite; read without internet
Multi-SourceBuilt-in providers for NovelBuddy and NovelFire via an extensible NovelProvider trait
EPUB ExportExport downloaded novels to .epub for Kindle, Kobo, or any e-reader
Storage Managerncdu-style interface to visualize per-novel disk usage and clear caches
Premium ReaderAdjustable text width, margins, spacing, color schemes (Sepia, Paper, Soft Dark), and alignment
ThemesTokyo Night, Dracula, Catppuccin Mocha, Solarized Dark

Installation

Download Prebuilt Binaries (Recommended)

Grab the latest release for your platform from the Releases page:

PlatformDownload
Linux x86_64sage-linux-x86_64.tar.gz
macOS x86_64sage-macos-x86_64.tar.gz
macOS Apple Siliconsage-macos-aarch64.tar.gz
Windows x86_64sage-windows-x86_64.zip
# Example: install on Linux
curl -LO https://github.com/musprodev/sage/releases/latest/download/sage-linux-x86_64.tar.gz
tar xzf sage-linux-x86_64.tar.gz
sudo mv sage /usr/local/bin/

Build from Source on Fedora Linux

Install the required system packages and the Rust toolchain:

# Install system dependencies
sudo dnf install sqlite-devel openssl-devel gcc pkg-config perl-FindBin
# Install Rust (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source"$HOME/.cargo/env"
# Clone, build, and install
git clone https://github.com/musprodev/sage.git
cd sage
cargo build --release
sudo cp target/release/sage /usr/local/bin/

Build from Source (Generic)

Ensure you have the Rust toolchain installed (1.86+):

git clone https://github.com/musprodev/sage.git
cd sage
cargo build --release

The binary is output to target/release/sage. Move it to your PATH:

sudo mv target/release/sage /usr/local/bin/

Quick Start

Launch Sage:

sage

Search for a novel with /, select it with Enter to add to your library, then press Enter again to load chapters. Press Enter on a chapter to read it.

Download an entire novel for offline reading with D, or export to EPUB with E.


Architecture

Sage is structured as a single Rust binary crate with the following modules:

src/
├── main.rs # Entry point, terminal setup, keyboard dispatch
├── app.rs # Application state, event handling, task spawning
├── ui.rs # TUI rendering (ratatui layouts, widgets, theming)
├── scraper.rs # NovelProvider trait + NovelBuddy/NovelFire impls
├── db.rs # SQLite persistence (rusqlite, WAL mode)
├── downloader.rs # Background download manager (semaphore, rate-limit)
├── exporter.rs # EPUB export via epub-builder
├── reader_settings.rs # Reader customization enums and cycling logic
├── theme.rs # 4 terminal color themes
├── models.rs # Novel, Chapter, Progress domain structs
├── config.rs # JSON config (~/.config/sage/)
└── error.rs # SageError enum (thiserror)

How Cloudflare Bypass Works

Sage uses the primp HTTP client configured with Impersonate::ChromeV144. This generates TLS connections with a JA3 fingerprint identical to Chrome 144, causing Cloudflare's bot detection to classify requests as legitimate browser traffic. No headless browser is needed — requests operate at raw HTTP speed with persistent cookie storage.

let client = Client::builder().impersonate(Impersonate::ChromeV144).cookie_store(true).timeout(std::time::Duration::from_secs(30)).build().expect("failed to build primp client");

SQLite Caching

All data is stored at ~/.local/share/sage/sage.db with three tables:

  • novels — metadata (title, author, cover, source URL, description)
  • chapters — content + download status, foreign-keyed to novels with ON DELETE CASCADE
  • progress — per-novel reading position (chapter ID + scroll offset)

WAL journal mode is enabled for concurrent read performance. Downloaded chapter text is stored directly in the content column, enabling fully offline reading.


Keybindings

Global

KeyAction
TabNavigate between UI panes
EscGo back or exit current view
tCycle global UI theme (Tokyo Night → Dracula → Catppuccin → Solarized)
Ctrl-CForce quit
qQuit application

Library View

KeyAction
j / kNavigate novels
/Search for novels online
EnterOpen selected novel's chapter list
d / DDownload all chapters for offline reading
e / EExport novel to EPUB
m / MOpen Storage Manager
DelRemove novel from library

Reading View

KeyAction
j / kScroll line by line
d / uScroll page down / up
g / GJump to top / bottom
p / nNext / previous chapter
S / sToggle reader settings panel

Reader Settings (press S or s while reading)

KeySettingValues
wText WidthNarrow (60) → Medium (80) → Wide (100) → Full
mMarginsCompact → Normal → Wide
lLine SpacingSingle → Relaxed → Double
pParagraph SpacingCompact → Normal → Relaxed
cColor SchemeDefault → Sepia → Paper → Soft Dark
aAlignmentLeft → Center

Storage Manager

KeyAction
j / kNavigate downloaded novels
DelClear downloaded content to reclaim disk space
c / CConfigure custom export directory path
m / M / EscReturn to Library

Supported Sources

SourceBase URLCatalog FetchStatus
NovelBuddynovelbuddy.comREST API (api.novelbuddy.com)✅ Active
NovelFirenovelfire.netHTML scraping (CSS selectors)✅ Active

Additional providers can be added by implementing the NovelProvider async trait:

#[async_trait]pubtraitNovelProvider:Send + Sync{fnsource_id(&self) -> &'staticstr;fnbase_url(&self) -> &'staticstr;asyncfnsearch(&self,query:&str) -> Result<Vec<Novel>,SageError>;asyncfnfetch_chapters(&self,novel_url:&str) -> Result<Vec<Chapter>,SageError>;asyncfnfetch_chapter_content(&self,chapter_url:&str) -> Result<String,SageError>;}

Contributing

Contributions, feature requests, and bug reports are welcome!

  • Open an issue for bugs or feature requests
  • Submit a Pull Request to add novel providers, improve the UI, or fix bugs
  • Run tests before submitting: cargo test

License

This project is licensed under the MIT License.

Releases

Packages

Used by

Contributors

Languages