An open-source, command-line toolset for managing, reading, and annotating a personal digital library using the Dewey Decimal Classification (DDC) system.
Now featuring an AI Reading Assistant that answers questions about the page you are reading in real-time.
- System-Wide CLI: Type
dlmfrom anywhere to fuzzy-search your library. - AI Reading Assistant: Ask "What does this mean?" about your current page (powered by Google Gemini via OAuth or API key).
- Smart Annotation Sync: Auto-extracts highlights from Skim (PDF) and Apple Books (EPUB) to Joplin.
- Auto-Sorting: Automatically organizes
_Inboxfiles into DDC subject folders. - Code/Data Separation: Your library lives wherever you want (local, OneDrive, Dropbox); the code lives here.
🔄 Upgrading to XDG Compliance\n\nDLM now follows XDG Base Directory spec:\n\n| Type | Old Path | New Path |\n|------|----------|----------|\n| Config | ~/.dlm/config.json | ~/.config/dlm/config.py |\n| Data | ~/.dlm/data/ | ~/.local/share/dlm/ |\n\nAutomatic migration happens on first run.\nRun ./migrate.py for manual/standalone migration (safe, idempotent).\n\nEnv vars: DLM_DATA_DIR, XDG_CONFIG_HOME, XDG_DATA_HOME supported.\n\n---\n\n## 🛠️ Installation & Setup\n\n### 1. Prerequisites
Required:
- macOS (Skim/AppleScript features are macOS-only)
- Python 3.9+
- pipx (
brew install pipx) — for installing DLM globally - fzf (
brew install fzf) — interactive fuzzy search - poppler (
brew install poppler) — providespdfinfofor extracting PDF title/author metadata. Without it,dlm-catalogfalls back to parsing filenames, which often produces bad titles.
Recommended:
- Skim (
brew install --cask skim) — the best PDF reader for macOS. Supports AppleScript page tracking, sidecar annotation files, andskimnotesCLI for extracting highlights. DLM falls back to the system default PDF viewer if Skim is not installed. - exiftool (
brew install exiftool) — required bydlm-metafixto write metadata (title, author) into PDF files. - Joplin (with Web Clipper enabled) — for syncing highlights and annotations to searchable notes.
git clone https://github.com/utrumsit/dlm.git
cd dlm
pipx install -e .This installs the following commands system-wide:
| Command | Description |
|---|---|
dlm |
Fuzzy-search your library and open books |
dlm-catalog |
Rescan all folders and rebuild catalog.json. Run after adding files. |
dlm-sort |
Scan _Inbox/, look up ISBNs/titles, and move files to correct DDC folders. |
dlm-init |
Scaffold the DDC directory structure (000_Computer_Science/, 100_Philosophy/, etc.) and a starter config.py in your library root. |
dlm-toc |
Generate a TOC.md markdown file listing your entire collection. |
dlm-auth |
Authenticate with Google via OAuth for the AI Reading Assistant. |
dlm-metafix |
Scan library for missing/bad metadata, look up via ISBN/OpenLibrary/Google Books, and write results into files. |
Tell DLM where your library lives. Add this to ~/.zshrc (or ~/.bash_profile):
# Local folder (recommended — fast, no cloud timeouts):
export DLM_LIBRARY_ROOT="/path/to/your/DigitalLibrary"
# Or a cloud-synced folder (works but may be slow):
# export DLM_LIBRARY_ROOT="$HOME/Library/CloudStorage/OneDrive-Personal/Documents/DigitalLibrary"Then reload: source ~/.zshrc
-
Scaffold the folders (first time only):
dlm-init
-
Create your local config:
mkdir -p ~/.config/dlm cp config.py.example ~/.config/dlm/config.py
Then edit
~/.config/dlm/config.py:- Joplin: Add your Web Clipper token (Settings → Web Clipper → Advanced Options).
- Gemini AI (choose one):
- OAuth (recommended): Add
GOOGLE_CLIENT_IDandGOOGLE_CLIENT_SECRET(create at GCP Console — enable "Generative Language API", create Desktop OAuth client), then rundlm-auth. - API key: Set
GOOGLE_API_KEY(get one at Google AI Studio).
- OAuth (recommended): Add
- Skim: Set
SKIM_APP_PATHif Skim is not at/Applications/Skim.app(e.g. Homebrew cask on an external drive).
Config lookup order:
~/.config/dlm/config.py→$DLM_LIBRARY_ROOT/config.py(legacy fallback).The local path is recommended: API keys and tokens stay on the machine, no secrets in the cloud, no timeouts from OneDrive/iCloud "Files On-Demand."
-
Build the catalog:
dlm-catalog
This scans your library, extracts PDF/EPUB metadata (title, author), and writes
catalog.json. Re-run whenever you add or rename files.
dlm- Type to fuzzy-search by title, author, subject, or filename.
- Press Enter to open in Skim (PDF) or Apple Books (EPUB).
- Use
--ddc 780to filter by DDC category,--recentfor recently opened,--type pdffor file type.
Once a book is open, dlm enters Reading Mode:
--- Reading Session: [Book Title] ---
(dlm) > ask Explain this theorem like I'm 5
It scrapes text from the active Skim page, sends it to Gemini, and prints the answer.
In the reading prompt, sync highlights to Joplin:
(dlm) > notes
Pulls highlights from Skim (via skimnotes) or Apple Books and appends them to a Joplin notebook.
Scan and fix metadata for existing books:
# Dry run (safe, just shows what it would find)
dlm-metafix --dry-run
# Run on a specific subfolder
dlm-metafix --folder 700_Arts/741.5_Cartoons_and_Comics
# Apply all found metadata without prompting
dlm-metafix --yesUses a cascade lookup: ISBN from PDF text → OpenLibrary → Google Books. Results are written directly into the file's metadata (exiftool for PDF, OPF patch for EPUB) and the catalog.json is updated.
- Sync Data, Not Code: Keep your library folder in OneDrive/Dropbox (or sync with rclone). Keep this code repo separate.
- Local Config: Use
~/.config/dlm/config.pyon each machine with that machine's Joplin token and API credentials. No hostname dict needed — each machine has its own file. - rclone (recommended over OneDrive app): OneDrive's macOS filesystem driver often shows files as "local" but can't actually read them (Files On-Demand). Use rclone to sync to a real local folder:
Set
brew install rclone rclone config # one-time — authorize with Microsoft rclone sync onedrive:Documents/DigitalLibrary /path/to/local/DigitalLibraryDLM_LIBRARY_ROOTto the local path. Use a launchd plist or cron job for periodic sync. - Skim Sidecars: In Skim Preferences, enable "Automatically save notes sidecar file" so annotation
.skimfiles are created alongside PDFs. These sync across machines and DLM can extract notes from them.
dlm/
├── src/dlm/
│ ├── auth.py # OAuth2 flow for Google Gemini
│ ├── catalog.py # Library scanner, PDF/EPUB metadata extraction
│ ├── cli.py # Main CLI entry point and reading mode
│ ├── context.py # Skim AppleScript page text extraction
│ ├── data.py # Catalog and progress file I/O
│ ├── extractor.py # Skim/Apple Books highlight extraction
│ ├── fzf.py # fzf interactive search interface
│ ├── init.py # Library scaffolding (dlm-init)
│ ├── joplin.py # Joplin Web Clipper integration
│ ├── llm.py # Gemini AI reading assistant
│ ├── lookup.py # Shared metadata lookup (ISBN, OpenLibrary, Google Books)
│ ├── metafix.py # Retroactive metadata fixer CLI
│ ├── opener.py # File opening (Skim, Books, system default)
│ ├── settings.py # Config loading (~/.config/dlm/ → library root)
│ ├── sort.py # Inbox auto-sorting by ISBN/title lookup
│ └── toc.py # Table of contents generator
├── config.py.example # Template config file
├── pyproject.toml # Dependencies and entry points
└── README.md
License: MIT
