StoryVault is a private, local-first archive search app for imported story collections.
The app keeps the source archive on disk, extracts readable text into a local SQLite database, builds a full-text search index, and gives you a quiet reader/search interface for exploring the collection.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
npm install
npm run dev:apiIn a second terminal:
npm run dev:webOpen http://127.0.0.1:5173.
For a single-server run after building the frontend:
npm run build
storyvault serveOpen http://127.0.0.1:8765.
StoryVault is private by default. To let other people on the same trusted Wi-Fi or LAN view it, build the frontend and run the single-server app on all network interfaces:
npm run build
npm run serve:lanFind this Mac's local network IP:
ipconfig getifaddr en0Other devices on the same network can then open:
http://YOUR_LOCAL_IP:8765
For example, if the IP is 192.168.86.107, use:
http://192.168.86.107:8765
Keep this mode limited to networks and people you trust. StoryVault does not have user accounts or a password gate yet, so anyone who can reach the LAN URL can browse the imported archive through the app.
For development with hot reloading over the LAN, run these in two terminals:
npm run dev:api
npm run dev:web:lanThen open http://YOUR_LOCAL_IP:5173 from another device.
Your Mac also has a Bonjour/mDNS name. You can see it with:
scutil --get LocalHostNameIf that prints MacBook-Air-Miguel, other devices may be able to open:
http://MacBook-Air-Miguel.local:8765
To make the app reachable as storyvault.local, change the Mac's local hostname:
sudo scutil --set LocalHostName storyvaultThen open:
http://storyvault.local:8765
The bare name http://storyvault:8765 is less reliable because it needs local DNS or per-device hosts-file setup. For that, reserve this Mac's IP address in your router and add a DNS/hosts entry:
192.168.86.107 storyvault
The default archive path is:
/Users/miguel/Projects/story-search/download/www.asstr-mirror.org/files
storyvault scan --limit 1000
storyvault scan --reset
storyvault search "slow atmospheric science fiction"
storyvault embeddings status
storyvault embeddings index --limit 5000
storyvault embeddings index --all --provider fastembed
storyvault serve
pytest
npm run buildThe web UI has two search modes:
Fastuses document-level SQLite FTS plus local semantic reranking.Deepruns as a background search job. It combines local embedding matches, passage-level FTS matches, and document-level fallbacks, then returns ranked matches with evidence snippets, confidence, and a short reason.
New imports write passage chunks into the local SQLite index. If you already have an older import without chunks, Deep Search still works by slicing top candidate stories into passages on demand.
StoryVault can build a private local embedding index for story passages. The app stores vectors in SQLite and uses them in Deep Search as a hybrid signal alongside FTS.
The default install includes a lightweight hash embedder so the feature works without extra packages. For better semantic matching, install the optional local model dependency:
python -m pip install -e ".[embeddings]"Then build embeddings from the web UI's Semantic index controls or from the CLI:
storyvault embeddings status
storyvault embeddings index --limit 5000
storyvault embeddings index --all --provider fastembedfastembed downloads its model files locally the first time it runs. Set these environment variables to control the provider/model:
STORYVAULT_EMBEDDINGS_PROVIDER=fastembed
STORYVAULT_EMBEDDING_MODEL=BAAI/bge-small-en-v1.5StoryVault stores local app data in ~/Library/Application Support/StoryVault/storyvault.sqlite3 by default.
Avoid keeping the SQLite database, .venv, or node_modules in an iCloud/Dropbox "online only" folder. If macOS or a cloud backup provider offloads those files, SQLite can fail at startup with sqlite3.OperationalError: disk I/O error.
Set these environment variables to override paths:
STORYVAULT_DATA_DIR=/path/to/data-folder
STORYVAULT_DB=/path/to/storyvault.sqlite3
STORYVAULT_ARCHIVE=/path/to/archiveIf an older project-local .storyvault/storyvault.sqlite3 was offloaded, either mark the project folder as available offline in Finder/Dropbox or let StoryVault rebuild the index in the default app-data folder by running a fresh import from the UI.
- Import
.txt,.text,.html,.htm, and likely text files with no extension. - Preserve original file paths and source metadata.
- Extract clean text for reading.
- Parse common ASSTR-style headers such as title, author, summary, keywords, rating, and date.
- Build an SQLite FTS5 index for local candidate retrieval.
- Build optional local passage embeddings and use them for hybrid Deep Search.
- Support conversational search phrasing, local semantic concept expansion/reranking, author filters, negative terms, and length hints.
- Provide a local web UI for importing, searching, filtering, and reading.
- Capture internal archive links so index pages and chapter/story pages can navigate to linked imported documents.
Future versions can add local embedding models, Tauri packaging, richer dedupe review, manual tagging, and saved reading state.