codex
codex is a terminal-first ebook library and ereader manager, inspired by
Calibre but built for people who live in the shell. The binary is called cdx.
Everything works over SSH — there is no GUI. Every read command speaks JSONL
(--json) so it composes cleanly with jq, fzf, and shell scripts, and every
subcommand has an equivalent screen in the built-in terminal UI (cdx tui).
What it does
- Catalogs — keep one or more independent libraries, each a plain directory
(a
catalog.dbSQLite file plus abooks/tree) that you can put under git. - Import — add EPUB, PDF, MOBI, and AZW3 files; codex extracts metadata and
stores each book under a sanitized
Author_-_Title.extname. - Metadata — edit title/author/tags/rating/series and embed the changes back into the book file.
- Search & groups — substring search with field filters, and a folder-style browse mode grouping by author, tag, series, publisher, language, or rating.
- Kindle sync — detect USB-mounted Kindles and push/pull/sync books, with a
git add -p-style interactive plan. - Duplicates — detect duplicate copies (by content hash or normalized title+author) and assist removal.
- Reader — read EPUB, MOBI/AZW3 (non-DRM), PDF, TXT, and Markdown right in the terminal, with vim-style navigation and saved reading progress.
Where to start
- New here? Read Installation, then Getting started.
- Already installed? Jump to the CLI reference.
Installation
codex ships as a single prebuilt binary. There is no cargo install / crates.io
package — you download the binary for your platform.
Install script (recommended)
curl -fsSL https://codex.daniel.ws/install.sh | sh
The script detects your OS and architecture, downloads the matching binary from
the latest GitHub release,
verifies its SHA-256 checksum, and installs it to ~/.local/bin.
Environment variables:
| Variable | Default | Meaning |
|---|---|---|
CDX_INSTALL_DIR | $HOME/.local/bin | Where to install the cdx binary |
CDX_VERSION | latest release | Install a specific tag (e.g. v1.0.0) |
If ~/.local/bin is not on your PATH, the script prints a reminder. Add it:
export PATH="$HOME/.local/bin:$PATH"
Supported platforms
| Platform | Release asset |
|---|---|
| Linux x86_64 | cdx-x86_64-unknown-linux-musl (static) |
| macOS (Apple silicon) | cdx-aarch64-apple-darwin |
| macOS (Intel) | cdx-x86_64-apple-darwin |
The Linux binary is statically linked against musl, so it runs on both glibc and musl distributions with no system dependencies. USB device sync is Linux-only; the rest of codex works on every supported platform.
Manual download
Grab the asset for your platform from the
releases page, verify it, and
move it onto your PATH:
target="x86_64-unknown-linux-musl" # pick yours
tag="v1.0.0"
base="https://github.com/Danielwsx64/codex/releases/download/$tag"
curl -fsSLO "$base/cdx-$target"
curl -fsSLO "$base/cdx-$target.sha256"
sha256sum -c "cdx-$target.sha256" # macOS: shasum -a 256 -c
chmod +x "cdx-$target"
mv "cdx-$target" ~/.local/bin/cdx
Keeping it up to date
Once installed, codex can update itself — see Updating cdx:
cdx update
Building from source
You need a Rust toolchain (MSRV 1.80):
git clone https://github.com/Danielwsx64/codex
cd codex
cargo build --release
# binary at target/release/cdx
Getting started
1. Create a catalog
A catalog is a directory you choose — it can live anywhere, including inside a
git repo. codex tracks the set of catalogs in a small registry at
$XDG_CONFIG_HOME/cdx/config.toml and remembers which one is current.
cdx catalog init home ~/books
This creates ~/books/catalog.db and ~/books/books/, registers it under the
name home, and makes it the current catalog.
2. Add some books
cdx add ~/Downloads/*.epub ~/Downloads/dune.pdf
codex extracts basic metadata, stores each file under
books/<id>/Author_-_Title.ext, and skips exact duplicates (override with
--force). Supported formats: EPUB, PDF, MOBI, AZW3 (plus TXT/Markdown for the
reader).
3. List and inspect
cdx ls
cdx inspect 1 # by id
cdx inspect "Dune" # or exact title (case-insensitive)
Add --json to any read command for JSONL output:
cdx ls --json | jq -c '{id, title, author}'
4. Open the terminal UI
cdx tui
The TUI mirrors every command. Press ? on any screen for its key bindings,
: for the command palette, and q (or Ctrl+C) to quit.
Global flags
These work on every subcommand:
| Flag | Meaning |
|---|---|
--json | Emit machine-readable JSONL on stdout |
-v / -vv / -vvv | Increase log verbosity (info / debug / trace) |
--catalog <name> | Use a registered catalog other than the current one |
--data-dir <path> | Override the config dir (mostly for tests) |
Next: learn about Catalogs and the Library.
Catalogs
codex is multi-catalog. Each catalog is a directory you pick; the registry at
$XDG_CONFIG_HOME/cdx/config.toml lists them and points at the current one.
On-disk layout of a catalog:
<catalog>/
catalog.db # SQLite metadata
books/<id>/<file> # the stored book files
Commands
| Command | What it does |
|---|---|
cdx catalog init <name> <path> | Create a new catalog at <path> and register it |
cdx catalog add <name> <path> | Register an existing catalog directory |
cdx catalog ls | List registered catalogs (current + (missing)) |
cdx catalog use <name> | Switch the current catalog |
cdx catalog rm <name> | Unregister a catalog (--purge deletes its files) |
init and add accept --description <text> and --no-switch (register
without making it current).
cdx catalog init fiction ~/lib/fiction --description "Novels"
cdx catalog add archive /mnt/nas/ebooks
cdx catalog ls
cdx catalog use archive
A catalog whose directory has disappeared from disk is shown as (missing) in
cdx catalog ls rather than causing an error.
In the TUI
The Catalogs screen lists every catalog (current one marked), lets you
switch with Enter and remove with confirmation. The New catalog wizard
covers both init and add. Reach it from the welcome menu or the :catalogs
command-palette entry.
Library
The library commands operate on books in the current catalog.
Importing
cdx add file1.epub file2.pdf ...
Accepts EPUB, PDF, MOBI, AZW3 (and TXT/Markdown for the reader). codex extracts
metadata, stores each file under a sanitized Author_-_Title.ext, and skips
content-duplicate imports. Use --force to import anyway.
Listing
cdx ls
cdx ls --columns id,title,author,rating
cdx ls --all-columns
cdx ls --json
Available columns: id, title, author, tags, series, rating, publisher, language, published, isbn, format, embed.
Inspecting
cdx inspect <id|title>
Accepts a numeric id or an exact (case-insensitive) title. An ambiguous title returns an error listing the candidate ids.
Editing metadata
cdx edit <id> # opens $EDITOR with the book's metadata as TOML
cdx tag <id> <tag>... # add tags
cdx untag <id> <tag>... # remove tags (--all clears every tag)
cdx rate <id> <0-5> # set rating (0 clears)
cdx series <id> <name> --index 2 # set series + position (--clear removes it)
Any metadata change marks the book embed_status = pending so it can be embedded
back into the file later — see Metadata embedding.
Searching
cdx search dune # substring across title/author/tags
cdx search --author tolkien # field filters
cdx search hobbit --tag fantasy --rating 4..5
Whitespace-separated tokens are AND'd. Filters: --author, --tag (repeatable,
AND), --series, --rating (exact 4 or range 3..5). --json emits JSONL.
Removing
cdx rm <id|title> # removes from catalog and deletes the file
cdx rm <id|title> --keep # moves the file to the cwd instead of deleting
In the TUI
The Library screen lists books with the same columns and actions
(inspect / edit / open / push / delete / column selection / embed). / filters
by text; g switches to group browsing.
Metadata embedding
codex stores metadata in the catalog database. Embedding writes that metadata back into the book file itself, so the values travel with the book when you copy it to a device or another app.
The embed cycle
- Any edit (
cdx edit,cdx tag,cdx rate,cdx series, or the TUI edit modal) sets the book'sembed_statustopending. - Running the sync embeds the metadata into every pending book and marks it
synced(EPUB/PDF) orunsupported(MOBI/AZW3, which are not rewritten).
cdx embed sync
This walks every book with status pending, embeds the metadata, and prints
per-book progress plus a final summary.
Format support
| Format | Embedding |
|---|---|
| EPUB, PDF | Supported — metadata is written into the file |
| MOBI, AZW3 | Not supported — marked unsupported |
The embed column in cdx ls (and the Inspect screen in the TUI) shows the
current status. In the TUI, press w on the Inspect screen to embed a single
book.
Note: for EPUB, codex keeps a stable content fingerprint that ignores the rewritten OPF, so re-embedding does not make a book look like a duplicate.
Devices (Kindle sync)
codex syncs with USB-mounted Kindles on Linux, with support for several devices connected at once. Each device is identified by the stable serial read from the USB descriptor; you can give it an alias and use that day-to-day.
Device sync is Linux-only. On other platforms the commands compile but detect no devices.
Selecting a device
Every device command takes --device <serial|alias>. With exactly one device
connected it is the implicit default; with two or more and no flag you get an
error listing the candidates. The last device you used explicitly becomes the
current device (per catalog), so it stays the default across runs.
Commands
| Command | What it does |
|---|---|
cdx device ls | List detected and known devices (alias, serial, free space, book count) |
cdx device alias <target> <alias> | Set or rename a device alias |
cdx device books [--device <a>] | List books on a device with catalog presence |
cdx device clean [--device <a>] | Remove books from a device (never touches the catalog) |
| `cdx push <id | title> [--device ]` |
cdx pull <path> [--device <a>] | Import a book from the device into the catalog |
cdx sync [--device <a>] | Bidirectional diff + interactive apply |
push and pull open an interactive picker when you omit the target/path.
clean supports --all and --yes; pull supports --force.
How identity works
- Exact: the
device_bookssync-state table records every book codex sent or pulled (book id ↔ device path + SHA-256 + size/mtime). No guessing for these. - By metadata: books that arrived on the device by other means are matched on normalized title + author (casefold, NFKD without diacritics, collapsed punctuation/whitespace) — never the filename, since formats differ between ends. A genuinely ambiguous match becomes a conflict for you to resolve.
Sync
cdx sync # interactive, git-add-p style
cdx sync --dry-run # print the plan only
cdx sync --yes # accept everything (scripts)
cdx sync --verify # full SHA-256 instead of the size+mtime fast path
The plan lists what is missing on each end, modified, missing, and match
conflicts. You confirm item by item (y apply / n skip / a accept the rest /
q abort). Sync never deletes on either end — removal is always manual (use
cdx device clean).
In the TUI
The Devices screen lists devices (r renames, Enter opens the device's
books). Presence indicators (both / local only / device only / modified)
appear in the Library and device views. p in the Library pushes the selected
book to the current device.
Duplicates
cdx dedup finds duplicate books in the current catalog and suggests which copy
to remove. Signals are combined by union — if any method flags a group, it
becomes a candidate.
- Content hash — SHA-256 fingerprints catch byte-identical copies and the same EPUB before/after embedding.
- Normalized title + author — casefold + NFKD, collapsed punctuation/whitespace; catches the same book in different formats or editions where the hash won't match.
For each group codex suggests deleting the "worst" copy: the one with the fewest filled-in metadata fields, breaking ties toward the older / weaker-embed copy. The decision is always yours — codex only suggests.
Usage
cdx dedup # list duplicate groups + the suggested removal
cdx dedup --by hash # restrict the detection signal
cdx dedup --by meta
cdx dedup --by all # default: union of both
cdx dedup --json # JSONL, one object per group
Assisted removal:
cdx dedup --rm # interactive picker (accepts the suggestion by default)
cdx dedup --rm --keep # move removed files to the cwd instead of deleting
cdx dedup --yes # accept every suggestion (scripts)
Removal reuses the cdx rm path and never deletes without confirmation (unless
--yes).
In the TUI
The Duplicates action in the Library section lists the groups, highlights the suggested copy, and removes with confirmation.
Groups
Group browsing lets you navigate the catalog like a folder tree: pick a metadata field as the "grouper" and each distinct value becomes a folder. It is a mode of the Library screen, not a separate section — inside a folder you get the same columns and actions as the normal listing.
A folder's scope is exact equality (the "Jane Austen" folder contains only
author = 'Jane Austen'), unlike search, which is substring. author is a
single column (a book falls in one folder); tags is many-to-many (a book can
appear in several folders).
CLI
cdx groups --by author
cdx groups --by tag
cdx groups --by rating
cdx groups --by publisher
cdx groups --by language
cdx groups --by series
cdx groups --by format
cdx groups --by author --json
Each row is a value plus its book count. In --json (JSONL, one object per
group), the catch-all group — no author / no tags / no rating — has value: null. An empty catalog prints nothing in --json.
In the TUI
Press g in the Library to open the grouper selector (Author / Tags / Rating /
Publisher / Language / Series / Format / Off). The folder level lists value +
count; Enter drills in. A breadcrumb shows the current group. Esc steps back
one layer at a time: clear the filter → back to folders → leave grouping → back
to the welcome screen. / inside a folder filters that folder's books in memory
without widening its exact scope.
TUI reader
codex can read books directly in the terminal. The reader is TUI-only — open a book from the Library (there is no CLI equivalent). Text is reflowed to the viewport and recomputed on resize.
Supported formats
| Format | Notes |
|---|---|
| EPUB | Spine extraction + HTML→text |
| TXT / Markdown | Markdown via CommonMark; TXT read directly |
| MOBI | PalmDOC / uncompressed; HUFF/CDIC is refused with a clear message |
| AZW3 (KF8) | Dual-stream AZW3 reads the legacy stream; KF8-only is refused with a suggestion to convert to EPUB |
| Single-column text; multi-column is best-effort with a warning |
DRM-protected books (Amazon Topaz / KFX / protected AZW) are detected and refused — codex does not remove DRM. Only sideloaded, DRM-free files work.
Navigation
Vim-style cursor and paging:
| Keys | Action |
|---|---|
h j k l | Move the cursor |
w b e 0 $ | Word / line motions |
gg G | Top / bottom |
Space Ctrl+f Ctrl+b | Page down / up |
Ctrl+d Ctrl+u | Half-page down / up |
[ ] | Previous / next chapter |
:N | Jump to absolute page N |
:cN | Jump to chapter N |
? | Contextual help |
Esc | Back to the Library |
The footer shows chapter X/Y · page A/B. For PDF, each page is a chapter, so
:cN jumps to the real document page.
Reading progress
Progress (last chapter, offset, and time) is saved when you change chapters, page, or leave the reader, and restored when you reopen the book.
Performance
Conversion results (PDF/EPUB/MOBI/AZW3) are cached on disk under the XDG cache
dir and invalidated by source mtime + size. Opening runs in a background thread
with an animated loading screen; the TUI stays responsive and Esc cancels.
Terminal UI
cdx tui
The TUI mirrors every CLI subcommand — both surfaces share the same domain
modules, only the rendering differs. The welcome screen is the home and lists the
top-level sections as navigable links (↑/↓ to move, Enter to enter):
- Library — list / view / edit / remove books, groups, duplicates
- Search — the Library's filtered mode
- Catalogs — the catalog registry + new-catalog wizard
- Devices — ereader sync
Command palette
Press : from any screen to open a vim-style input at the bottom. Commands:
:library, :catalogs, :search, :devices, and :quit (:q). Tab
completes the shortest unique prefix (:l, :c, :s, :d, :q). Enter runs
it; Esc cancels.
Reserved keys
These are consistent across the whole TUI:
| Key | Meaning |
|---|---|
q, Ctrl+C | Quit (the only exit keys) |
Esc | Back / cancel within a screen — never quits |
Enter | Confirm / drill in |
? | Contextual help for the active screen |
Per-screen bindings beyond these are documented in each screen's ? overlay.
CLI reference
cdx <command> [args]. Run cdx --help or cdx <command> --help for the
authoritative, always-current text. Global flags (--json, -v/-vv/-vvv,
--catalog <name>, --data-dir <path>) work on every subcommand.
Catalogs
| Command | Description |
|---|---|
cdx catalog init <name> <path> | Create + register a catalog (--description, --no-switch) |
cdx catalog add <name> <path> | Register an existing catalog (--description, --no-switch) |
cdx catalog ls | List catalogs |
cdx catalog use <name> | Switch the current catalog |
cdx catalog rm <name> | Unregister (--purge deletes files) |
Library
| Command | Description |
|---|---|
cdx add <file>... | Import books (--force) |
cdx ls | List books (--columns, --all-columns) |
cdx inspect <id|title> | Show detailed metadata |
cdx search <query> | Search (--author, --tag, --series, --rating) |
cdx edit <id> | Edit metadata in $EDITOR |
cdx tag <id> <tag>... | Add tags |
cdx untag <id> <tag>... | Remove tags (--all) |
cdx rate <id> <0-5> | Set/clear rating |
cdx series <id> <name> | Set series (--index, --clear) |
cdx rm <id|title> | Remove book (--keep) |
cdx groups --by <field> | Group by author/tag/rating/publisher/language/series/format |
cdx dedup | Find duplicates (--by, --rm, --yes, --keep) |
Metadata embedding
| Command | Description |
|---|---|
cdx embed sync | Embed metadata into all pending books |
Devices
| Command | Description |
|---|---|
cdx device ls | List devices |
cdx device alias <target> <alias> | Set/rename alias |
cdx device books | List device books (--device) |
cdx device clean | Remove books from a device (--device, --all, --yes) |
cdx push <id|title> | Copy to device (--device) |
cdx pull <path> | Import from device (--device, --force) |
cdx sync | Bidirectional sync (--device, --dry-run, --yes, --verify) |
Maintenance
| Command | Description |
|---|---|
cdx tui | Open the terminal UI |
cdx update | Self-update (--check, --yes) — see Updating cdx |
cdx completions <shell> | Print a completion script — see Shell completions |
JSON output
Every read command accepts --json and emits JSONL — one JSON object per
line, no surrounding array — so it streams through jq -c, head, and
fzf --preview. A command with zero records prints nothing.
cdx ls --json | jq -c 'select(.rating >= 4) | {title, rating}'
cdx search --author tolkien --json | jq -r '.title'
Updating cdx
codex can update itself in place from the latest GitHub release.
Check for a newer version
cdx update --check
This reports whether a newer release exists and installs nothing. Add --json
for machine-readable output:
cdx update --check --json
# {"current":"1.0.0","latest":"1.1.0","newer_available":true,"html_url":"..."}
Install the update
cdx update
codex downloads the release binary for your platform, verifies its SHA-256
checksum, and atomically replaces the running executable. You are asked to
confirm first; pass --yes to skip the prompt (for scripts):
cdx update --yes
How it works
- The target platform is chosen automatically (Linux x86_64 → the static musl binary; macOS → the matching Apple-silicon or Intel binary).
- The download is checksum-verified before anything is replaced; a mismatch aborts without touching your installed binary.
- The new binary is written next to the current one and swapped in with an atomic
rename, so an interrupted update never leaves you with a half-written
cdx.
If your platform has no prebuilt release, cdx update tells you to build from
source (see Installation). You can also re-run the install
script at any time.
Shell completions
cdx completions <shell> prints a completion script to stdout. Supported shells:
bash, zsh, fish, elvish, powershell.
bash
# system-wide (needs bash-completion installed)
cdx completions bash | sudo tee /etc/bash_completion.d/cdx >/dev/null
# or per-user: source it from ~/.bashrc
cdx completions bash > ~/.local/share/bash-completion/completions/cdx
zsh
# put it on your $fpath, e.g.
cdx completions zsh > ~/.zfunc/_cdx
# then ensure ~/.zfunc is on the fpath before compinit in ~/.zshrc:
# fpath=(~/.zfunc $fpath)
# autoload -U compinit && compinit
fish
cdx completions fish > ~/.config/fish/completions/cdx.fish
Reload your shell (or source the relevant rc file) and completions for cdx
subcommands and flags become available.
Dynamic completion of positional arguments (for example, completing book titles and ids in
cdx inspect <TAB>against the catalog) is planned for a later release; today's completions cover subcommands and flags.