Packaged for Linux (RPM, DEB, tarball), Windows (zip), and macOS (dmg). Interfaces are stable; breaking changes are avoided where possible.
DocuBrowse turns a messy pile of documents into something you can actually search. Point it at your files β PDFs, ebooks, Word docs, notes, whatever β and it builds a smart index that understands not just keywords, but meaning. Ask for "that contract about the lease renewal" and find it even if those exact words never appear. Click any result for an instant AI summary before you even open the file. PII aware and works with multiple document directories.
DocuBrowse runs entirely on your own machine using local AI models β no internet connection required, no accounts, no API keys, and no per-query costs eating into a token budget. Your data. Your AI.
Under the hood: SQLite FTS5 keyword search plus AI-powered semantic similarity and synopsis generation (Ollama + nomic-embed-text + dolphin3). Supports multiple document and source code types.
Keyword Searchβ fast full-text search via SQLite FTS5 (title, author, subject, tags, snippet)** Semantic Search**β AI-powered similarity via Ollama embeddings (nomic-embed-text:latest)** Hybrid Mode**(default) β 70% semantic + 30% keyword, merged and re-ranked
- Click any document title for a Kindle-style book-jacket synopsis, generated on demand
via Ollama (
dolphin3:latest
) and cached in the database after first generation. Semantic search embeddings are produced by a second local model (nomic-embed-text:latest
)
Formats: PDF, DOCX, PPTX, XLSX, EPUB, MOBI, AZW3, AZW, HTML, TXT, Markdown** PDF intelligence**: pdfplumber (preferred) with pypdf fallback for bloated-object files;layout=False
retry for complex layouts; scanned (image-only) PDFs detected and routed toocr_list_pdfs.txt
Word documents: python-docx extracts paragraphs, tables, and core properties (title, author, subject)** Presentations**: python-pptx extracts slide text, notes, and core properties** Spreadsheets**: openpyxl extracts cell values and sheet names** E-books**: ebooklib for EPUB; mobi package + Calibre fallback for MOBI/AZW3; DRM-encrypted AZW files indexed with metadata only (title/author visible, body not searchable)Metadata: title, author, subject extracted from document metadata fields; auto-generated tags from directory structure and content keywordsPII protection: post-ingest scanner detects SSN, credit card, bank routing/account number, DOB, MRN, driver license, passport patterns; removes matching documents and permanently blacklists them
-
Dark/Light theme toggle
-
Paginated results (50 docs/page) with Back/Next controls
-
Alphabetic index bar (AβZ, 0β9) for quick navigation, with state preserved across page loads; Home button to reset
-
Tag cloud for filtering by topic
-
Relevance score badges (0β100%) on every result Open button on each result card β launches the file in your default app via
xdg-open -
Click document title for an AI synopsis; π copies path to clipboard; π deletes file from disk and index (with confirmation)
-
Moved/deleted documents: clicking a doc whose file no longer exists shows a dismissable modal (and removes it from the index on dismiss) if its filesystem is mounted, or a toast (no index change) if the filesystem can't be verified (e.g. unmounted drive)
-
General panel: document directory (with live directory browser), any number of additional scan directories (added/removed under the same panel β automatically included in
scan
/rescan
, no extra command needed), working directory, and port - Ignored Directories panel: browse to add a directory to
ignore_dirs.txt
, with a confirmation prompt before purging already-indexed documents under it, and a confirmation before removing an entry
- Search latency: <150ms typical
- Parallel PDF extraction with
ProcessPoolExecutor
(physical-core-aware worker count) - Memory-safe: kernel-enforced RLIMIT_AS (6 GB/worker) + /resume on free-RAM threshold
Click any thumbnail to view full size.
| Dark Mode | Light Mode |
|---|
| Settings | AI Synopsis |
|---|
Settings is a standalone page at
/settings
(opened in a new tab via the gear icon). The General panel covers the document directory (with live directory browser, plus any number of additional scan directories), working directory, and port; the Ignored Directories panel manages scan exclusions, each with a directory browser, add/clear controls, and confirmation before removal.
- Python 3.9+
pdfplumber
,pypdf
β PDF extractionpython-docx
β Word documentspython-pptx
β PowerPoint presentationsopenpyxl
β Excel spreadsheetsebooklib
,beautifulsoup4
,mobi
β E-bookspsutil
β cross-platform process and hardware detectionCalibreβ E-book metadata and conversion (required for MOBI/AZW3/AZW indexing):sudo dnf install calibre
orsudo apt install calibre
- Ollama β installed automatically by
docubrowser start
if missing - Modern browser (Chrome, Firefox, Safari, Edge)
See INSTALL.md for a full step-by-step guide.
DocuBrowse ships as RPM, DEB, tarball, Windows zip, and macOS dmg packages. Download the appropriate package from the Releases page.
sudo dnf install ./docubrowser-foss-0.9.0-7.noarch.rpm
sudo apt install ./docubrowser-foss_0.9.0-7_all.deb
tar xzf docubrowser-foss-0.9.0-7.tar.gz
cd docubrowser-foss-0.9.0-7
sudo ./install.sh
Windows: Extract the zip, then double-click Install.bat
. Requires
Python 3.9+ and Ollama to be pre-installed. Installs to
%USERPROFILE%\DocuBrowse
with a Start Menu shortcut β no admin required. You may need to log out and back in for the shortcut to appear.
macOS: Open the dmg, then double-click Install.command
(right-click β
Open the first time β the scripts are unsigned). Requires Python 3.9+.
Installs to ~/Applications/DocuBrowse/
with a Python virtualenv, CLI
wrappers at /usr/local/bin/docubrowser
and /usr/local/bin/docuback
(sudo prompted; falls back to ~/bin/
if declined), and a DocuBrowse.app
launcher that starts the server and opens the web UI in Terminal.
All Linux methods install to /opt/docubrowser/
with a Python virtualenv,
CLI wrappers at /usr/bin/docubrowser
and /usr/bin/docuback
, a desktop
menu entry under Office, and all Python dependencies from requirements.txt
.
Once installed, the CLI is the docubrowser
command β drop the ./
and .py
from every example below. For example, docubrowser start
and
docubrowser rescan
. The ./docubrowser.py <cmd>
form shown throughout the rest of this README is the dev / cloned-repo path (running directly out of a checkout).
To uninstall: sudo dnf remove docubrowser-foss
(RPM),
sudo apt remove docubrowser-foss
(DEB), sudo ./uninstall.sh
(tarball),
double-click Uninstall.bat
(Windows), or double-click Uninstall.command
(macOS β on the dmg or in ~/Applications/DocuBrowse/
).
cd /path/to/DocuBrowse
./docubrowser.py rescan
./docubrowser.py start
./docubrowser.py open
On an installed system, use the
docubrowser
command instead, e.g.docubrowser rescan
/docubrowser start
/docubrowser open
.
docubrowser.py start
automatically verifies Ollama is installed, running, and has
both required models β nomic-embed-text:latest
(embeddings) and
dolphin3:latest
(synopsis generation) β installing/starting/pulling as needed.
Usage: docubrowser.py <command> [options]
| Command | Description |
|---|---|
start |
|
| Start the search server (runs Ollama check first) | |
stop |
|
| Stop the server | |
restart |
|
| Stop then start | |
status |
|
| Show server status, document count, embedding count, tag count | |
scan [TYPE ...] |
|
| Scan and index documents (no embedding) | |
rescan [TYPE ...] |
|
| Scan + generate embeddings | |
scan-file --file PATH |
|
| Extract and index a single file, then embed it | |
embed |
|
| Generate/refresh embeddings for un-embedded documents | |
open |
|
| Open the DocuBrowse UI in your default browser | |
purge |
|
| Scan index for PII and remove matching documents | |
| `ignore add | remove |
| Manage directories excluded from scanning (auto-purges on add) | |
report |
|
| Walk doc directory and show file-type breakdown (no DB changes) | |
scan-missing [--db PATH] [--dry-run] |
|
Opt-in cleanup: classify every indexed path as present/missing/unmounted, delete missing rows (cascades), leave unmounted rows alone |
|
stopall |
|
| Stop all running scans, embeds, and the server | |
duplist |
|
| List duplicate documents (exact SHA256 + optional near-duplicate) | |
dupclean |
|
| Interactive TUI to review and remove duplicate documents |
--db PATH SQLite database path (overrides config)
--port PORT Server port (overrides config)
--config FILE Config file path
./docubrowser.py start
./docubrowser.py start --port 9000
./docubrowser.py status
./docubrowser.py stop
./docubrowser.py stopall
./docubrowser.py scan # scan all supported types
./docubrowser.py scan pdf # PDFs only
./docubrowser.py scan pdf txt # PDFs and plain text
./docubrowser.py scan --limit 100 # first 100 unindexed files only
./docubrowser.py rescan # scan + embed all types
./docubrowser.py rescan pdf --workers 4 # PDFs only, 4 workers
./docubrowser.py rescan --no-embed # scan without embedding step
./docubrowser.py rescan --doc-dir /data/docs
./docubrowser.py scan-file --file /path/to/document.pdf
./docubrowser.py scan-file --file /path/with spaces/doc.pdf # no quoting needed
./docubrowser.py scan-file --file /path/to/doc.pdf --no-embed
./docubrowser.py report # file-type breakdown, no DB changes
./docubrowser.py embed # embed any un-embedded docs
./docubrowser.py purge --dry-run # preview PII matches (safe)
./docubrowser.py purge # remove PII documents (prompts)
./docubrowser.py ignore add /mnt/data/Documents/myWorkDocs # exclude + purge indexed docs under it
./docubrowser.py ignore list # show ignored directories
./docubrowser.py ignore remove /mnt/data/Documents/myWorkDocs # re-allow (rescan to re-index)
./docubrowser.py duplist # find exact SHA256 duplicates
./docubrowser.py duplist --near-dups # also find near-duplicates (cosine β₯97%)
./docubrowser.py duplist --near-dups --threshold 0.95
./docubrowser.py dupclean # interactive Keep A/Keep B/Keep Both TUI
./docubrowser.py dupclean --near-dups # include near-duplicates in cleanup
./docubrowser.py scan-missing --dry-run # report counts only, no DB changes
./docubrowser.py scan-missing # delete rows for genuinely-missing files
Types: pdf txt md html (default: all four)
Examples:
rescan pdf PDFs only
rescan pdf txt PDFs and plain text
rescan all supported types (prompts if unfiltered)
scan-file
is designed for retrying individual problem files:
- Removes the file from
scan_blacklist.txt
if listed (explicit retry) - Refuses files in
pii_blacklist.txt
(permanent PII block) - Detects scanned (image-only) PDFs β adds to
ocr_list_pdfs.txt
- Paths with spaces work without quoting:
--file
accepts multiple tokens and rejoins them
DocuBrowse reads the first config file it finds:
/etc/docubrowse.config
(system-wide)./docubrowse.config
(next todocubrowser.py
)
If neither exists, built-in defaults apply β except doc_dir
, which has no
default. Until a document directory is configured (via the Settings gear icon
in the web UI, or by setting doc_dir
in docubrowse.config
), the web UI
shows a banner prompting you to configure one, and CLI commands that need a
document directory (rescan
, report
, scan
) exit with an error explaining how to set it.
doc_dir = /mnt/data/Documents
db_path = /home/user/DocuBrowse/du-docs.db
port = 8643
work_dir = /home/user/DocuBrowse
| Key | Default |
|---|---|
doc_dir |
|
| (none β must be configured via Settings or docubrowse.config) | |
db_path |
|
<script dir>/du-docs.db |
|
port |
|
8643 |
|
work_dir |
|
<script dir> |
βββββββββββββββββββββββββββββββββββββββ
β docubrowser.py (CLI entry point) β
β ensure_ollama.py (prereq check) β
ββββββββββββ¬βββββββββββββββββββββββββββ
β subprocess / direct call
β
ββββββββββββββββββββββββ βββββββββββββββββββββββββββββββββββ
β scan_docs.py β β doc_search.py (HTTP :8643) β
β ProcessPoolExecutor β β GET / /api/search β
β pdf_extractor.py β β GET /api/stats /api/tags β
β embed_docs.py β β GET /api/open /api/config β
β β GET /api/delete /api/synopsis β
ββββββββββββ¬ββββββββββββ ββββββββββββββββ¬βββββββββββββββββββ
β β
ββββββββββββ¬ββββββββββββββββββββ
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β du-docs.db (SQLite FTS5) β
β Ollama (nomic-embed-text + dolphin3) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Script | Role |
|---|---|
docubrowser.py |
|
| CLI launcher β all commands | |
ensure_ollama.py |
|
| Checks/installs Ollama binary, service, and required models | |
doc_search.py |
|
| HTTP server; search API and UI | |
docubrowse_db.py |
|
| SQLite schema and migrations | |
platform_paths.py |
|
| Cross-platform path resolution and process management | |
scan_docs.py |
|
| Document discovery, extraction, and DB writes | |
pdf_extractor.py |
|
| PDF-specific extraction with pdfplumber/pypdf | |
docx_extractor.py |
|
| Word document extraction (python-docx) | |
ebook_extractor.py |
|
| EPUB/MOBI/AZW3/AZW extraction (ebooklib + Calibre) | |
hardware_utils.py |
|
| CPU/GPU/RAM detection, worker count formula | |
embed_docs.py |
|
| Sends text to Ollama; stores 768-dim vectors | |
purge_pii.py |
|
| Scans index for PII; removes and blacklists matches | |
dup_detect.py |
|
| Exact (SHA256) and near-duplicate (cosine similarity) detection |
| File | Purpose | Permanent? |
|---|---|---|
scan_blacklist.txt |
||
| Files that failed extraction | No β remove line to retry | |
pii_blacklist.txt |
||
| Files removed for containing PII | Yes β never re-ingest | |
ocr_list_pdfs.txt |
||
| Image-only PDFs needing OCR | N/A β informational | |
ignore_dirs.txt |
||
Directories excluded from scanning (managed via ignore command) |
||
No β ignore remove + rescan |
Base URL: http://localhost:8643
| Method | Path | Description |
|---|---|---|
GET |
||
/ |
||
Serve index.html (injects a per-process CSRF token) |
||
GET |
||
/settings |
||
Serve settings.html |
||
GET |
||
/api/stats |
||
| Total docs, embedded count, unique tag count | ||
GET |
||
/api/tags |
||
| Tag list with counts (β₯3 occurrences) | ||
GET |
||
/api/search |
||
| Search with pagination | ||
GET |
||
/api/letters |
||
| First-letter index for the alphabetic bar | ||
GET |
||
/api/synopsis |
||
| Generate/return an AI synopsis for a document | ||
GET |
||
/api/config |
||
| Current server configuration | ||
GET |
||
/api/ignore-dirs |
||
| List excluded directories | ||
GET |
||
/api/scan-dirs |
||
| List additional scan directories | ||
π GET |
||
/api/browse |
||
| Directory browser for Settings (token-gated) | ||
π POST |
||
/api/open |
||
| Open a file with xdg-open/gio (validates against DB) | ||
π POST |
||
/api/delete |
||
| Delete a file from disk and remove from index (path must be indexed) | ||
π POST |
||
/api/config |
||
| Save server configuration | ||
π POST |
||
/api/ignore-dirs |
||
| Add/remove an excluded directory | ||
π POST |
||
/api/scan-dirs |
||
| Add/remove an additional scan directory |
π = state-changing or filesystem-exposing; requires the per-process
X-CSRF-Token
header and a loopback Origin
/Referer
. The token is injected
into the served HTML, so only the first-party UI can call these. See
Security. All requests are also rejected unless the Host
header
is localhost
/127.0.0.1
/[::1]
(DNS-rebinding protection).
If the indexed path no longer exists on disk, /api/open
returns one of:
{"ok": false, "error": "missing", "message": "..."}
{"ok": false, "error": "unmounted", "message": "..."}
missing
means the file's filesystem is mounted and the file is genuinely gone β the UI
shows a dismissable modal and deletes the document from the index (and disk-adjacent DB
rows) when dismissed. unmounted
means the path's filesystem can't currently be verified
(likely an unmounted drive) β the UI shows a toast and makes no DB changes. See
scan-missing
for the equivalent batch cleanup.
GET /api/search?q=QUERY&offset=0&mode=both
| Param | Values | Default |
|---|---|---|
q |
||
| search string | "" (returns all docs) |
|
mode |
||
both |
keyword |
semantic |
both |
||
offset |
||
| integer | 0 |
{
"documents": [
{
"id": 1,
"name": "doc.pdf",
"title": "Document Title",
"author": "Jane Smith",
"subject": "Cloud Security",
"description": "First 500 chars of content...",
"path": "/mnt/data/Documents/doc.pdf",
"tags": ["pdf", "security", "cloud"],
"modified_at": "2026-06-07T14:30:00",
"score": 0.95,
"fts_score": 0.8,
"sem_score": 0.98
}
],
"query": "cloud security",
"count": 50,
"total": 312,
"offset": 0,
"has_more": true,
"mode": "both"
}
curl "http://localhost:8643/api/stats"
curl "http://localhost:8643/api/search?q=kubernetes&mode=both"
curl "http://localhost:8643/api/search?q=&offset=50"
Mutating endpoints (/api/delete
, /api/open
, the POST
config/dir routes)
and /api/browse
require the per-process CSRF token and a loopback origin, so
they aren't easily exercised with a bare curl
β drive them from the UI, or
pass -X POST -H "X-CSRF-Token: <token>" -H "Origin: http://localhost:8643"
where <token>
is read from the <meta name="csrf-token">
tag in /
.
A non-empty query is scored two ways and merged; metadata is then fetched only for the requested page (the whole corpus is no longer loaded per request).
final_score = 0.3 Γ keyword_score + 0.7 Γ semantic_score (mode=both)
- Backed by the SQLite
FTS5 index via
MATCH
+bm25()
β not a Python substring scan. - Query tokens are quoted and prefix-matched (
"tok"*
), OR-combined, so arbitrary input (operators, quotes,C++
,&
) can't break the query. - Per-column BM25 weights echo the old field priorities (name 6, title 8, author 7, subject 5, description 3, content_snippet 3, tags 4); the result is normalized to 0β1.
- Orphan
doc_fts
rowids (contentless FTS has no FK cascade) are pruned against the live document set so they can't inflate totals.
- Cosine similarity between the query embedding and each document embedding.
- Computed against an in-process, L2-normalized embedding matrix(one vectorized NumPy matrix-vector product), cached and invalidated when the embeddings table changes β instead of re every BLOB per request. - Range 0.0β1.0; minimum threshold (semantic-only mode): 0.30. - Embeddings: 768-dimensional float32 vectors (nomic-embed-text:latest).
DocuBrowse binds to localhost and is intended for single-user local use, but it is hardened so a malicious web page you happen to visit can't reach it:
Host-header allow-listβ every request is rejected unlessHost
islocalhost
/127.0.0.1
/[::1]
(optionally with the serving port). Defeats DNS-rebinding against the localhost-bound server.CSRF tokens on mutationsβ/api/delete
and/api/open
are POST-only; they plus the POST config/dir routes and the filesystem-exposing/api/browse
require a per-processX-CSRF-Token
(injected into the served HTML, so only the first-party UI has it) and a loopbackOrigin
/Referer
.No stored-data injectionβ document fields are escaped for HTML and card actions usedata-*
attributes + delegated listeners (no inlineonclick
built from document data), closing a stored-XSS vector.PII purge validates structurally before deleting: SSNs against SSA allocation rules, credit cards by length + issuer prefix + Luhn, bank routing numbers by ABA checksum + Federal Reserve prefix β so it both catches more real PII and avoids deleting docs over incidental number groups.
The server is localhost-only β it binds the loopback subnet and rejects all non-loopback connections at the socket level. No authentication is needed because only the local user can reach the server.
DocuBrowse/
βββ docubrowser.py # CLI entry point (all commands)
βββ ensure_ollama.py # Ollama prerequisite checker/installer
βββ doc_search.py # HTTP search server (port 8643)
βββ docubrowse_db.py # SQLite schema and migrations
βββ scan_docs.py # Scanner: discovery, extraction, DB writes
βββ pdf_extractor.py # PDF extraction (pdfplumber + pypdf fallback)
βββ docx_extractor.py # Word document extraction (python-docx)
βββ ebook_extractor.py # EPUB/MOBI/AZW3/AZW extraction (ebooklib + Calibre)
βββ hardware_utils.py # CPU/GPU/RAM detection, worker formula
βββ embed_docs.py # Embedding generation pipeline
βββ purge_pii.py # PII scanner and purge tool
βββ dup_detect.py # Exact (SHA256) and near-duplicate detection
βββ platform_paths.py # Cross-platform paths and process management
βββ index.html # Frontend UI (single-file, dark/light theme)
βββ du-docs.db # SQLite database (gitignored)
βββ du-docs.db.example # Empty schema for new installs
βββ scan_blacklist.txt # Failed-extraction skiplist (gitignored)
βββ pii_blacklist.txt # PII-removed files β permanent (gitignored)
βββ ocr_list_pdfs.txt # Image-only PDFs needing OCR (gitignored)
βββ ignore_dirs.txt # Directories excluded from scanning (gitignored)
βββ scan_dirs.txt # Additional scan directories (gitignored)
βββ docubrowse.config # Local config (optional, gitignored)
βββ INSTALL.md # Step-by-step install guide
βββ README.md # This file
βββ LICENSE # GPL-3.0
βββ packaging/ # RPM spec, DEB control, build scripts, installers
β βββ build_packages.sh # Builds RPM, DEB, and tarball (Linux)
β βββ build_windows_zip.sh # Builds Windows zip
β βββ docubrowser-foss.spec # RPM spec
β βββ docubrowser.desktop # Desktop menu entry (Linux)
β βββ install.sh # Tarball installer (Linux)
β βββ uninstall.sh # Tarball uninstaller (Linux)
β βββ windows/ # Windows installer/uninstaller scripts
β β βββ Install.bat # Double-click to install
β β βββ Uninstall.bat # Double-click to uninstall
β β βββ install.ps1 # PowerShell installer
β β βββ uninstall.ps1 # PowerShell uninstaller
β βββ macos/ # macOS installer/uninstaller scripts
β βββ build_macos_dmg.sh # Builds the macOS dmg
β βββ Install.command # Double-click to install
β βββ Uninstall.command # Double-click to uninstall
βββ systemd/
β βββ docubrowser.service # systemd unit file
βββ status_docs/ # Project planning and decision logs
β βββ project_status.md # Current version, session history
β βββ DECISIONS.md # Deferred decisions and known issues
βββ test_pdfs_live/ # 100 sample PDFs for testing
Large document collections may trigger:
OSError: [Errno 28] inotify watch limit reached
This is a Linux kernel limit, not a disk space issue. It's safe to ignore these warnings β but if you'd rather not see them, raise the limit for the duration of the scan:
- Edit
/etc/sysctl.conf
and find the line:
fs.inotify.max_user_instances=128
- Raise it to
256
or512
:
fs.inotify.max_user_instances=256
- Apply the change without rebooting:
sudo sysctl -p
Once ingestion is finished, you can set the value back to 128
(edit the
file again and re-run sudo sysctl -p
) β or just leave it raised.
Some PDFs cause pdfminer to hang. These are auto-detected and blacklisted. If a specific file is causing problems, check:
pdfinfo /path/to/file.pdf | grep -i objects
./docubrowser.py scan-file --file /path/to/file.pdf
Files with >8,000 PDF objects (usually caused by repeated ExifTool metadata updates) are automatically routed through pypdf instead of pdfminer.
PDFs with no extractable text are detected and added to ocr_list_pdfs.txt
. They are
indexed with a placeholder ([scanned PDF β OCR required]
) so they appear in browse but won't match keyword or semantic searches until OCR is run.
Check the log:
tail -f /var/log/docubrowser.log
tail -f ~/.local/share/docubrowser/docubrowser.log
ollama serve # start manually
ollama list # verify both models are present
ollama pull nomic-embed-text:latest # embeddings, if missing
ollama pull dolphin3:latest # synopsis generation, if missing
| Limitation | Status |
|---|---|
| DRM-encrypted AZW not fully searchable | Metadata indexed; DeDRM_tools required for body text |
| Scanned PDFs not searchable | Listed in ocr_list_pdfs.txt; OCR deferred |
| Multiple top-level doc directories | Fully supported β configure any number of additional scan directories in the General panel (scan_dirs.txt ); scan /rescan automatically scan all of them into the single shared database |
| Moved/renamed files | Not detected as moves β old path is removed (interactively or via scan-missing ), new path is picked up on next rescan as a fresh entry; true duplicates are caught by duplist /dupclean |
| No authentication | Local use only; hardened against cross-origin/CSRF/DNS-rebinding (see |
status_docs/DECISIONS.md
)DocuBrowse is now packaged for Linux and Windows with native installers.
RPM, DEB, tarball, and Windows zip packagesβbuild_packages.sh
produces Linux packages;build_windows_zip.sh
produces the Windows zip. Linux installs to/opt/docubrowser/
with a Python virtualenv, CLI wrappers at/usr/bin/docubrowser
and/usr/bin/docuback
, and a desktop menu entry under Office. Windows installs to%USERPROFILE%\DocuBrowse
with a Start Menu shortcut (no admin required).Windows installerβInstall.bat
/install.ps1
detects Python, creates a virtualenv, installs dependencies, and creates a Start Menu shortcut.Uninstall.bat
reverses everything.Cross-platform path abstractionβ newplatform_paths.py
centralizes all runtime path selection (PID files, log files, backup directory) and process management (kill, find-by-script, kill-port). Linux paths are unchanged; Windows paths use%USERPROFILE%\DocuBrowse\
.Windows compatibilityβ guarded all Unix-only constructs (resource
,SIGALRM
,os.killpg
,/proc
access) behind platform checks. Process management usespsutil
with/proc
fallback on Linux.Backup/restoreβbackup_restore.py
supports Windows privilege checks (IsUserAnAdmin
) and handles missingpwd
module gracefully.Desktop menu entryβ.desktop
file usesxdg-terminal-exec
for reliable terminal launching across all desktop environments; categorized under Office.Systemd service fileβ included for system-level deployments on Linux.** dist/ pruning**β build script keeps only the latest 2 releases per format.** macOS dmg installer**βpackaging/macos/build_macos_dmg.sh
produces a dmg with double-clickableInstall.command
/Uninstall.command
scripts. Installs to~/Applications/DocuBrowse/
(no sudo for the app itself) with a Python virtualenv, CLI wrappers, and aDocuBrowse.app
launcher whose icon is generated fromicons/icon-512.png
via sips/iconutil.
Removes unused code paths and experimental features that were never part of the FOSS release, leaving a cleaner and more focused codebase.
Leaner serverβdoc_search.py
reduced by ~300 lines; removed unused network configuration, protocol negotiation, and handler code that had accumulated during development.Leaner CLIβdocubrowser.py
reduced by ~240 lines; removed thesetup-tls
command and related helpers that are not applicable to a localhost application.Stale files removedβ deletedbranding.json.example
and other development-only files that were never used in production.Documentation updatedβ README, INSTALL, and architecture notes cleaned up to accurately reflect the current feature set.
Hide documents from viewβ each card now has a π Hide icon that tags the document as "hidden" and fades it out of the listing. Hidden documents remain in the database and can be restored at any time."Show π" toggle buttonβ added next to the page count in all views (All Documents, letter filter, search results). Clicking it reveals all hidden cards alongside normal ones; the button label switches to "Hide π" to toggle them back off.Unhide (π) iconβ when hidden cards are visible, they display a π icon instead of π. Clicking it removes the "hidden" tag server-side, swaps the icon back to π, and removes the "hidden" tag chip from the card.New API endpoint:β removes a single tag from a document. Parameters:POST /api/remove-tag
path
(URL-encoded file path),tag
(tag name). Returns the updated tag list. CSRF-protected.Card action icons restyledβ all icons (π π π β) now use solid, colorful emoji at full opacity. No more faded/dim icons in dark mode.β Top
Dark & light mode palettes redesignedβ new CSS variable theming withdata-theme
attribute switching. Dark mode uses deep navy/purple tones with cyan, orange, and violet accents. Light mode uses clean whites with darkened accent variants for readability. Tag colors cycle through five distinct hues vianth-child
selectors. Score badges, mode buttons, and action buttons all use the new palette.Trash icon now opens a 4-option modal instead of immediately deleting: (1) Remove from index only (file stays on disk, re-scanned next run), (2) Remove & blacklist (file stays, future scans skip it), (3) Remove & delete file from disk (with double-confirmation), (4) Cancel. Server API updated:POST /api/delete?path=...&mode=db_only|blacklist|delete_file
(defaults todb_only
for backward compatibility).Search scoring fixed in "both" modeβ keyword matches were previously buried under thousands of low-similarity semantic results. Now applies a semantic floor (SEM_FLOOR=0.30
) and usesmax(fts, sem)
scoring instead of a weighted average that capped keyword-only hits at 0.3.(same asscan
command now embeds by defaultrescan
) β new installations get working semantic search out of the box. Added--no-embed
and--embed-workers
flags for opt-out.Security hardeningβ CSRF protection added to/api/synopsis
; exception leak to client suppressed.
Open action button replaces the old clickable file path link on each result card β launches the file in your default app viaxdg-open
.Button styling updated β buttons use accent-colored border and text with a filled hover state, replacing the previous dim/greyed appearance.
with current schema β fresh installs no longer hit an HTTP 500 ("no such column: d.subject") on first page load. The old example was built against an older schema (pre-author/subject/synopsis columns and pre-full-FTS5 index), causing the lazy migration to race against the first search request. New example has the correct schema from the start.du-docs.db.example
regenerated
Settings moved to a standalone page(/settings
, opened in a new tab via the gear icon) β replaces the old modal. Full-width layout, a header "Done" button that saves config and returns to the search tab, and a redesigned Ignored Directories panel (description text, "Add a directory to exclude" row, "Currently excluded directories" list with inline β remove buttons, confirm-before-purge on add and rescan-reminder on remove).Alpha index bar (0-9, A-Z) is now a true global filterβ clicking a letter queries/api/search?letter=X
forallmatching documents (not just the loaded page), paginated by the existing page-size preference; Next/Back and page-size changes work while filtered, and clicking the active letter again returns to All Documents. A "Home" button (left of "0-9") returns to All Documents from anywhere, and the index bar now persists across every view (All Documents, letter-filtered, search, paginated).Multiple document directories confirmed fully automatic:resolve_doc_dirs()
unifies the configured docPath withscan_dirs.txt
into one ordered list;scan
/rescan
loop over every directory into the single shared database and run embedding once at the end β no manual per-directory rescan needed.- Removed the "N embedded" count from the header stats bar (now "N docs Β· N tags").
friendlyError()
helper inindex.html
gives a clear "Cannot reach the DocuBrowse service" message (instead of a generic network error) if the server is down while a page is loaded β applied to search, filter, pagination, synopsis, open, and delete.- Synopsis modal's "Generating synopsis..." message now updates at 6s/25s with reassuring text so a slow cold-start Ollama request (up to ~90s) doesn't look hung.
doc_dir
/docPath
no longer default to/mnt/data/Documents
β an unconfigured document directory is now a valid state across the CLI (docubrowser.py
), API (doc_search.py
/api/config
), andinstall.sh
's generated config.- CLI commands that need a document directory (
rescan
,report
,scan
) now exit with a clear error pointing to the Settings gear,docubrowse.config
, or--doc-dir
if none is configured. index.html
shows a banner β "No document directory configured yet. Click the Settings (gear) icon..." β whenever/api/config
reports an emptydocPath
.- Added
uninstall.sh
, mirroringinstall.sh
's user/system mode detection: stops/disables/removes the systemd unit, removes the CLI wrapper and install directory, cleans up pid/log files, and (system mode, separate confirmation) can remove the dedicateddocubrowse
user/group.
Installer: rewritteninstall.sh
/uninstall.sh
with a clean user vs. system split β user mode installs to~/.docubrowse
(own venv, wrapper at~/.local/bin/docubrowser
, no root, no systemd); system mode installs to/opt/docubrowse
as a dedicateddocubrowse
user with a (not auto-enabled)docubrowser.service
systemd unit and/usr/local/bin/docubrowser
wrapper.Pre-flight checks: the installer validates all prerequisites up front (python3 β₯ 3.9 + venv/ensurepip, rsync, curl, tar, calibre, ollama, plus getent/useradd/groupadd/systemctl in system mode) and reports everything missing at once before making any changes.CLI: the launcher is now installed as thedocubrowser
command (no.py
).requirements.txt added and installed viapip install -r requirements.txt
β now includes previously-missing deps (numpy, python-pptx, openpyxl) alongside pdfplumber, pypdf, python-docx, ebooklib, beautifulsoup4, mobi.Fresh installs start empty:du-docs.db.example
now ships empty, so new installs begin with no documents indexed.Multiple document directories: Settings now shows a single "Document directories" list (the old separate docPath + "additional directories" panels are merged).rescan
/scan
indexevery listed directory; an explicit--doc-dir
still targets just one.doc_dir
is now optional.
Remediation of a full code-quality + security audit (details in
status_docs/DECISIONS.md
). Highlights:
Security: Host-header allow-list (anti DNS-rebinding);/api/delete
and/api/open
moved to POST and, with the POST config/dir routes and/api/browse
, gated by a per-process CSRF token + loopback origin; stored-XSS vector closed (data-attribute + delegated listeners); PII purge now validates with SSA rules + Luhn/IIN.Search: keyword path now uses the FTS5bm25()
index and semantic scoring uses a cached NumPy embedding matrix instead of the whole corpus per request (keyword ~4ms, both ~55ms); server-side semantic search fixed (was silently returning nothing).Reliability:INSERT β¦ ON CONFLICT
upsert (re-indexing no longer wipes tags/embeddings/synopsis); worker-death "suspect isolation" blacklists only the real offender; schema init runs once per process; scan/embed commit on a ~2s time budget so the server isn't blocked;dupclean
no longer corrupts the disk/DB on its main path; precise/proc
-based worker termination; single shared document-delete helper; assorted medium/low fixes.UI: pagination Back/Next correct at all page sizes; a newer search now supersedes an in-flight page load (no stale results).
/api/open
now returns{"ok": false, "error": "missing"|"unmounted", "message": ...}
for files that no longer exist, instead of a generic error.- The UI shows a dismissable modal for
missing
files (and removes them from the index on dismiss), or a toast forunmounted
files (filesystem can't be verified, no index change). - New opt-in
scan-missing [--dry-run]
CLI command batch-cleansmissing
rows across the whole index without touchingunmounted
rows.
- Fixed "Open file" (
/api/open
) silently doing nothing β the server's environment was missingDBUS_SESSION_BUS_ADDRESS
/DISPLAY
/XAUTHORITY
/XDG_RUNTIME_DIR
, soxdg-open
exited successfully without launching the default app.handle_open
now reconstructs the desktop session environment and prefersgio open
for reliable launches.
-
β DOCX extractor (python-docx)
-
β EPUB/MOBI/AZW3/AZW extraction (ebooklib + Calibre)
-
No-extension file classification (magic bytes)
-
Scale to 10K+ documents
-
β
duplist
/dupclean
β exact + near-duplicate detection and interactive cleanup - β Config read/write via Settings UI (port, docPath, workDir)
-
Sliding window ETA for progress bar
-
File-type filter in search UI
-
Config persistence
-
Advanced filtering (date range, type, author)
-
Result export (CSV/JSON)
-
OCR integration for scanned PDFs
-
API key authentication
-
Document similarity clustering
-
Docker deployment
DocuBrowse is developed with Claude as an active coding partner. To resume a session with full context, load these files at the start:
| File | Contents |
|---|---|
.claude/CLAUDE.md |
|
| Project rules, key files, hard-won lessons | |
status_docs/project_status.md |
|
| Version, session history, what's in progress | |
status_docs/DECISIONS.md |
|
| Deferred decisions, known issues, rationale |
cat .claude/CLAUDE.md status_docs/project_status.md status_docs/DECISIONS.md
GNU General Public License v3.0 or later (GPL-3.0-or-later).
Copyright (C) 2026 James Sparenberg
See LICENSE or https://www.gnu.org/licenses/gpl-3.0.html.
DocuBrowse v0.9.0 β Fast, local, AI-powered document search.