Your browser history is a personal library built up over years of reading — documentation, articles, discussions, research, notes. memoir turns it into a searchable, queryable knowledge base that lives entirely on your machine.
No extension. No cloud. No subscription. Just your history, indexed and searchable.
You've read the answer to this problem before. You just can't find it.
It's in a tab you closed six months ago, or a Hacker News thread from last year, or documentation you bookmarked and forgot. Your browser's built-in history search only matches URLs and titles — not the actual content of pages you visited.
memoir fetches and indexes the full text of the pages in your history. Then you can search them like you'd search a codebase: by what the pages said, not just what they were called.
It also embeds everything with a local ML model so you can search by meaning ("how does Rust handle async cancellation") instead of keywords. And if you have a local LLM running, you can ask questions directly and get answers grounded in pages you've actually read.
Everything runs on your computer. memoir never sends your history, your queries, or your pages anywhere.
Search
Ask
Chat
/chatQuick Palette
Manage
Clusters
Starred Pages
Orion Reading List
MCP Server
search, ask, and starred tools to any MCP-compatible clientActivity Log
/log — filterable by category (Sync, Search, Ask, Errors)Desktop App (macOS)
Privacy
/ask feature| Browser | kind value | Notes |
|---|---|---|
| Orion | orion | Default. Reading List is also indexed |
| Chrome | chrome | |
| Brave | brave | |
| Arc | arc | |
| Edge | edge | |
| Chromium | chromium | Any Chromium-based build |
memoir uses the OpenAI-compatible /v1/chat/completions endpoint by default, and the Anthropic Messages API when provider = "anthropic". The LLM is optional — full-text search works without it.
Set provider = "none" to disable the LLM and semantic search entirely. This also prevents the embedding model from being downloaded.
| Server | provider value | Notes |
|---|---|---|
| None | none | Disables Ask and semantic search. No embedding model is downloaded |
| LM Studio | lm_studio | Default. memoir auto-loads the model at startup via the LM Studio REST API |
| Ollama | lm_studio | Point base_url at http://localhost:11434 |
| Any OpenAI-compatible server | openai | Set base_url and model in config |
| OpenAI | openai | Set api_key, base_url = "https://api.openai.com", and model |
| Anthropic API | anthropic | Set api_key, base_url = "https://api.anthropic.com", and model |
.dmg from the Releases pageGatekeeper note: memoir is not signed with an Apple Developer certificate. macOS will show a warning the first time. Right-click → Open bypasses this. Alternatively:
xattr -dr com.apple.quarantine /Applications/Memoir.app
Once installed, Memoir checks for updates automatically at startup and downloads them in the background. When a download is ready, the menu bar icon menu shows Restart to Apply Update — click it to install and relaunch. You can also trigger a manual check with Check for Updates… in the same menu.
# Install Rust (if you don't have it)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install Tauri CLI
cargo install tauri-cli --version "^2"
git clone https://github.com/your-username/memoir
cd memoir
cargo tauri build
The built .app bundle appears in src-tauri/target/release/bundle/macos/.
cargo install --path .
Or build manually:
cargo build --release --bin memoir
cp target/release/memoir /usr/local/bin/memoir
The first run downloads the embedding model (~130 MB from HuggingFace) and caches it in the data directory.
On first launch, the setup wizard opens automatically and walks through:
After setup, memoir syncs in the background every 60 minutes (configurable). You can trigger a sync at any time from the tray menu.
The Tauri app starts automatically and keeps running in the menu bar after the window is closed.
Tray menu:
| Item | Action |
|---|---|
| Open Memoir | Show the main window |
| Sync Now | Run a sync immediately |
| Pause Sync / Resume Sync | Toggle the background sync loop |
| Quit | Exit the app |
Keyboard shortcut: Press ⌘⇧Space anywhere to open the search palette. Results appear live as you type. The shortcut is configurable via application.hotkey in config.toml.
Serve — start the web interface and sync loop:
memoir
Then open http://localhost:8734.
Sync — fetch and index pages from your recent history (one-shot, no server):
memoir sync
Pick — interactively fuzzy-search your history index from the terminal (macOS and Linux only):
memoir pick # browse all indexed pages
memoir pick rust # pre-filter by full-text search, then fuzzy-pick
Selecting a result opens it in your browser and copies the URL to the clipboard. The selected URL is also printed to stdout, so you can pipe it:
memoir pick | xargs open # redundant but works
memoir pick rust > url.txt
The search uses the same FTS5 index as the web UI — it matches against page titles, body text, and URLs.
The CLI reads the same config and index as the desktop app (~/.memoir/), so no separate setup is needed if you already have the Tauri app running.
Pass --no-sync to skip the background sync loop (useful if you only want the UI or are running sync separately):
memoir --no-sync
Pass --config-dir <path> to use a config directory other than the default:
memoir --config-dir /path/to/config
In Orion: Settings → New Tab → Custom URL → http://localhost:3000
memoir implements a Model Context Protocol server. Two transports are supported — use whichever fits your client.
The MCP endpoint is built into the web server at POST /mcp. No separate process needed — if memoir is running, MCP is running.
{
"mcpServers": {
"memoir": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
memoir also speaks MCP over stdin/stdout. The client spawns memoir as a subprocess and pipes messages to it. The full web server and sync loop start alongside the MCP handler.
{
"mcpServers": {
"memoir": {
"command": "/usr/local/bin/memoir",
"args": ["--no-sync"]
}
}
}
--no-syncprevents a second sync process when memoir is already running as the desktop app. Omit it if this is your only memoir instance.
Config file location for Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
| Tool | Description |
|---|---|
search | Full-text + semantic search over your indexed history |
ask | Ask a question; returns an LLM answer grounded in your history |
get_page | Retrieve the full stored content of a page by URL |
get_recent | List recently visited pages, newest first |
get_starred | Retrieve your starred/bookmarked pages |
The web interface is backed by a local Axum server. You can call it directly from other tools.
| Method | Path | Description |
|---|---|---|
GET | / | Start page (recent history + starred) |
GET | /manage | Manage page (browse, star, delete, ban) |
GET | /settings | Settings page |
GET | /setup | Setup wizard |
GET | /palette | Quick search palette |
GET | /health | Health check |
POST | /mcp | MCP JSON-RPC endpoint (HTTP transport) |
GET | /api/recent?limit=20 | Recently visited pages |
GET | /api/top-sites?limit=20 | Most visited pages |
GET | /api/search?q=…&limit=20 | Full-text + semantic search |
GET | /api/ask?q=…&k=5 | Ask a question (requires LLM + embedder) |
GET | /api/stats | Index counts |
GET | /api/pages?limit=50&offset=0&q=… | Browse all indexed pages |
GET | /api/starred?limit=20 | Starred pages |
POST | /api/star?url=…&starred=true | Star or unstar a page |
DELETE | /api/page?url=… | Delete a single page |
DELETE | /api/host?host=… | Delete all pages for a host |
POST | /api/ban | Ban a host (body: {"host":"example.com"}) |
POST | /api/bookmark | Bookmark a URL (body: {"url":"…","title":"…"}) |
GET | /api/favicon?host=… | Serve cached favicon |
GET | /api/clusters?days=14 | Browsing session clusters |
POST | /api/clusters/ignore | Ignore a domain in cluster view |
DELETE | /api/clusters/ignore | Unignore a domain |
GET | /api/export/starred | Download starred pages as JSON |
POST | /api/import/starred | Import starred pages from JSON |
POST | /api/sync | Trigger a sync |
GET | /api/sync/status | Sync status and interval |
POST | /api/sync/pause?paused=true | Pause or resume sync |
GET | /api/settings | Get current settings |
POST | /api/settings | Save settings |
GET | /api/open-url?url=… | Open a URL in the default browser |
GET | /chat | Chat page (multi-turn conversational interface) |
GET | /log | Activity log page |
POST | /api/chat | Multi-turn chat with history-grounded answers |
GET | /api/log?kind=… | Session log entries (all, or filtered by sync/search/llm/error) |
Search response:
[
{
"url": "https://doc.rust-lang.org/book/",
"title": "The Rust Programming Language",
"snippet": "…ownership and <b>borrowing</b> rules…",
"rank": -1.234,
"first_visit_at": "2024-11-01T09:00:00Z",
"last_visit_at": "2025-03-15T14:22:00Z",
"starred": false
}
]
Ask response:
{
"answer": "Ownership in Rust means each value has a single owner…",
"sources": ["https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html"]
}
/api/ask returns 503 if the embedding model is unavailable, and {"answer": "No relevant pages found.", "sources": []} if no indexed pages match.
Chat request (POST /api/chat):
{
"messages": [
{ "role": "user", "content": "What was I reading about Rust last week?" },
{ "role": "assistant", "content": "You read several articles about…" },
{ "role": "user", "content": "Which one covered async cancellation?" }
],
"k": 5
}
Chat response:
{
"answer": "<p>The article on async cancellation was…</p>",
"answer_md": "The article on async cancellation was…",
"sources": ["https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html"]
}
answer is HTML for rendering; answer_md is the raw markdown to append to messages for the next turn. The backend searches your index on every turn — no separate retrieval call needed.
Star the current page you are on and start its import from the click of a button in a browser.
window.fetch('http://127.0.0.1:3000/api/bookmark?url=' + encodeURIComponent(location.href) + '&title=' + encodeURIComponent(document.title), { method: 'POST' })
memoir stores everything in ~/.memoir/:
| File | Contents |
|---|---|
index.db | SQLite database — page text, FTS5 index, embeddings, starred flags, ban list, favicons |
config.toml | Configuration (created by setup wizard or edited manually — see CONFIG.md) |
The browser history database is never modified. memoir copies it to a temp file before reading.
See CONFIG.md for the full reference. Defaults are compiled in. Create ~/.memoir/config.toml to override:
[application]
host = "127.0.0.1"
port = 3000
[data]
dir = "~/.memoir"
[browser]
history_db_path = "~/Library/Application Support/Orion/Defaults/history"
kind = "orion" # orion | chrome | brave | arc | edge | chromium
[fetch]
delay_ms = 200
timeout_secs = 15
ban = ["web.archive.org", "mail.google.com"]
# firecrawl_api_key = "fc-..." # enables Firecrawl as auth-wall fallback
# firecrawl_base_url = "http://localhost:3002" # self-hosted crw or Firecrawl instance
[llm]
provider = "lm_studio" # none | lm_studio | openai | anthropic
base_url = "http://localhost:1234" # LM Studio default
model = "local-model" # must match the model key in LM Studio
# api_key = "sk-..." # required for OpenAI / Anthropic
[sync]
interval_mins = 60
The sync interval is re-read each cycle, so changes take effect without restarting the app.
For the full option reference see CONFIG.md. For CSS class names available for custom_css see HTML.md.
Sync reads the 1,000 most recent URLs from your browser's SQLite history, registers any new ones, then fetches and extracts text from each page — respecting a configurable crawl delay, skipping auth walls and non-HTML content. URLs that fail 3 times are marked and no longer retried. When a page returns an auth wall, memoir tries Firecrawl (if an API key is configured) and then the Wayback Machine as fallbacks before giving up.
Fetched pages are stored in index.db and inserted into an FTS5 virtual table for BM25-ranked full-text search.
After fetching, the embedding model (BAAI/bge-small-en-v1.5 via ONNX Runtime, ~130 MB, downloaded from HuggingFace on first run) encodes each page's title + body into a 384-dimensional vector stored as a BLOB. The model runs entirely locally. Set provider = "none" in [llm] to skip the download entirely — full-text search still works, but semantic search and Ask are disabled.
Search queries the FTS5 index and returns ranked results with highlighted snippets. Semantic search re-ranks results using cosine similarity (minimum score: 0.3).
Ask embeds the query, retrieves the top-k pages by cosine similarity and BM25, builds a context prompt from the page bodies, and calls the configured LLM.
Clusters groups your visit history into sessions by time proximity, letting you see what you were researching on any given day.
Desktop app: the Axum server runs inside the Tauri process. The WebView points at http://127.0.0.1:<port>. A background task runs the sync loop, re-reading config each cycle.
MIT
34 commits
Rust
62.4%
HTML
37.6%
Your browser history is a personal library built up over years of reading — documentation, articles, discussions, research, notes. memoir turns it into a searchable, queryable knowledge base that lives entirely on your machine.
No extension. No cloud. No subscription. Just your history, indexed and searchable.
You've read the answer to this problem before. You just can't find it.
It's in a tab you closed six months ago, or a Hacker News thread from last year, or documentation you bookmarked and forgot. Your browser's built-in history search only matches URLs and titles — not the actual content of pages you visited.
memoir fetches and indexes the full text of the pages in your history. Then you can search them like you'd search a codebase: by what the pages said, not just what they were called.
It also embeds everything with a local ML model so you can search by meaning ("how does Rust handle async cancellation") instead of keywords. And if you have a local LLM running, you can ask questions directly and get answers grounded in pages you've actually read.
Everything runs on your computer. memoir never sends your history, your queries, or your pages anywhere.
Search
Ask
Chat
/chatQuick Palette
Manage
Clusters
Starred Pages
Orion Reading List
MCP Server
search, ask, and starred tools to any MCP-compatible clientActivity Log
/log — filterable by category (Sync, Search, Ask, Errors)Desktop App (macOS)
Privacy
/ask feature| Browser | kind value | Notes |
|---|---|---|
| Orion | orion | Default. Reading List is also indexed |
| Chrome | chrome | |
| Brave | brave | |
| Arc | arc | |
| Edge | edge | |
| Chromium | chromium | Any Chromium-based build |
memoir uses the OpenAI-compatible /v1/chat/completions endpoint by default, and the Anthropic Messages API when provider = "anthropic". The LLM is optional — full-text search works without it.
Set provider = "none" to disable the LLM and semantic search entirely. This also prevents the embedding model from being downloaded.
| Server | provider value | Notes |
|---|---|---|
| None | none | Disables Ask and semantic search. No embedding model is downloaded |
| LM Studio | lm_studio | Default. memoir auto-loads the model at startup via the LM Studio REST API |
| Ollama | lm_studio | Point base_url at http://localhost:11434 |
| Any OpenAI-compatible server | openai | Set base_url and model in config |
| OpenAI | openai | Set api_key, base_url = "https://api.openai.com", and model |
| Anthropic API | anthropic | Set api_key, base_url = "https://api.anthropic.com", and model |
.dmg from the Releases pageGatekeeper note: memoir is not signed with an Apple Developer certificate. macOS will show a warning the first time. Right-click → Open bypasses this. Alternatively:
xattr -dr com.apple.quarantine /Applications/Memoir.app
Once installed, Memoir checks for updates automatically at startup and downloads them in the background. When a download is ready, the menu bar icon menu shows Restart to Apply Update — click it to install and relaunch. You can also trigger a manual check with Check for Updates… in the same menu.
# Install Rust (if you don't have it)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install Tauri CLI
cargo install tauri-cli --version "^2"
git clone https://github.com/your-username/memoir
cd memoir
cargo tauri build
The built .app bundle appears in src-tauri/target/release/bundle/macos/.
cargo install --path .
Or build manually:
cargo build --release --bin memoir
cp target/release/memoir /usr/local/bin/memoir
The first run downloads the embedding model (~130 MB from HuggingFace) and caches it in the data directory.
On first launch, the setup wizard opens automatically and walks through:
After setup, memoir syncs in the background every 60 minutes (configurable). You can trigger a sync at any time from the tray menu.
The Tauri app starts automatically and keeps running in the menu bar after the window is closed.
Tray menu:
| Item | Action |
|---|---|
| Open Memoir | Show the main window |
| Sync Now | Run a sync immediately |
| Pause Sync / Resume Sync | Toggle the background sync loop |
| Quit | Exit the app |
Keyboard shortcut: Press ⌘⇧Space anywhere to open the search palette. Results appear live as you type. The shortcut is configurable via application.hotkey in config.toml.
Serve — start the web interface and sync loop:
memoir
Then open http://localhost:8734.
Sync — fetch and index pages from your recent history (one-shot, no server):
memoir sync
Pick — interactively fuzzy-search your history index from the terminal (macOS and Linux only):
memoir pick # browse all indexed pages
memoir pick rust # pre-filter by full-text search, then fuzzy-pick
Selecting a result opens it in your browser and copies the URL to the clipboard. The selected URL is also printed to stdout, so you can pipe it:
memoir pick | xargs open # redundant but works
memoir pick rust > url.txt
The search uses the same FTS5 index as the web UI — it matches against page titles, body text, and URLs.
The CLI reads the same config and index as the desktop app (~/.memoir/), so no separate setup is needed if you already have the Tauri app running.
Pass --no-sync to skip the background sync loop (useful if you only want the UI or are running sync separately):
memoir --no-sync
Pass --config-dir <path> to use a config directory other than the default:
memoir --config-dir /path/to/config
In Orion: Settings → New Tab → Custom URL → http://localhost:3000
memoir implements a Model Context Protocol server. Two transports are supported — use whichever fits your client.
The MCP endpoint is built into the web server at POST /mcp. No separate process needed — if memoir is running, MCP is running.
{
"mcpServers": {
"memoir": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
memoir also speaks MCP over stdin/stdout. The client spawns memoir as a subprocess and pipes messages to it. The full web server and sync loop start alongside the MCP handler.
{
"mcpServers": {
"memoir": {
"command": "/usr/local/bin/memoir",
"args": ["--no-sync"]
}
}
}
--no-syncprevents a second sync process when memoir is already running as the desktop app. Omit it if this is your only memoir instance.
Config file location for Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
| Tool | Description |
|---|---|
search | Full-text + semantic search over your indexed history |
ask | Ask a question; returns an LLM answer grounded in your history |
get_page | Retrieve the full stored content of a page by URL |
get_recent | List recently visited pages, newest first |
get_starred | Retrieve your starred/bookmarked pages |
The web interface is backed by a local Axum server. You can call it directly from other tools.
| Method | Path | Description |
|---|---|---|
GET | / | Start page (recent history + starred) |
GET | /manage | Manage page (browse, star, delete, ban) |
GET | /settings | Settings page |
GET | /setup | Setup wizard |
GET | /palette | Quick search palette |
GET | /health | Health check |
POST | /mcp | MCP JSON-RPC endpoint (HTTP transport) |
GET | /api/recent?limit=20 | Recently visited pages |
GET | /api/top-sites?limit=20 | Most visited pages |
GET | /api/search?q=…&limit=20 | Full-text + semantic search |
GET | /api/ask?q=…&k=5 | Ask a question (requires LLM + embedder) |
GET | /api/stats | Index counts |
GET | /api/pages?limit=50&offset=0&q=… | Browse all indexed pages |
GET | /api/starred?limit=20 | Starred pages |
POST | /api/star?url=…&starred=true | Star or unstar a page |
DELETE | /api/page?url=… | Delete a single page |
DELETE | /api/host?host=… | Delete all pages for a host |
POST | /api/ban | Ban a host (body: {"host":"example.com"}) |
POST | /api/bookmark | Bookmark a URL (body: {"url":"…","title":"…"}) |
GET | /api/favicon?host=… | Serve cached favicon |
GET | /api/clusters?days=14 | Browsing session clusters |
POST | /api/clusters/ignore | Ignore a domain in cluster view |
DELETE | /api/clusters/ignore | Unignore a domain |
GET | /api/export/starred | Download starred pages as JSON |
POST | /api/import/starred | Import starred pages from JSON |
POST | /api/sync | Trigger a sync |
GET | /api/sync/status | Sync status and interval |
POST | /api/sync/pause?paused=true | Pause or resume sync |
GET | /api/settings | Get current settings |
POST | /api/settings | Save settings |
GET | /api/open-url?url=… | Open a URL in the default browser |
GET | /chat | Chat page (multi-turn conversational interface) |
GET | /log | Activity log page |
POST | /api/chat | Multi-turn chat with history-grounded answers |
GET | /api/log?kind=… | Session log entries (all, or filtered by sync/search/llm/error) |
Search response:
[
{
"url": "https://doc.rust-lang.org/book/",
"title": "The Rust Programming Language",
"snippet": "…ownership and <b>borrowing</b> rules…",
"rank": -1.234,
"first_visit_at": "2024-11-01T09:00:00Z",
"last_visit_at": "2025-03-15T14:22:00Z",
"starred": false
}
]
Ask response:
{
"answer": "Ownership in Rust means each value has a single owner…",
"sources": ["https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html"]
}
/api/ask returns 503 if the embedding model is unavailable, and {"answer": "No relevant pages found.", "sources": []} if no indexed pages match.
Chat request (POST /api/chat):
{
"messages": [
{ "role": "user", "content": "What was I reading about Rust last week?" },
{ "role": "assistant", "content": "You read several articles about…" },
{ "role": "user", "content": "Which one covered async cancellation?" }
],
"k": 5
}
Chat response:
{
"answer": "<p>The article on async cancellation was…</p>",
"answer_md": "The article on async cancellation was…",
"sources": ["https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html"]
}
answer is HTML for rendering; answer_md is the raw markdown to append to messages for the next turn. The backend searches your index on every turn — no separate retrieval call needed.
Star the current page you are on and start its import from the click of a button in a browser.
window.fetch('http://127.0.0.1:3000/api/bookmark?url=' + encodeURIComponent(location.href) + '&title=' + encodeURIComponent(document.title), { method: 'POST' })
memoir stores everything in ~/.memoir/:
| File | Contents |
|---|---|
index.db | SQLite database — page text, FTS5 index, embeddings, starred flags, ban list, favicons |
config.toml | Configuration (created by setup wizard or edited manually — see CONFIG.md) |
The browser history database is never modified. memoir copies it to a temp file before reading.
See CONFIG.md for the full reference. Defaults are compiled in. Create ~/.memoir/config.toml to override:
[application]
host = "127.0.0.1"
port = 3000
[data]
dir = "~/.memoir"
[browser]
history_db_path = "~/Library/Application Support/Orion/Defaults/history"
kind = "orion" # orion | chrome | brave | arc | edge | chromium
[fetch]
delay_ms = 200
timeout_secs = 15
ban = ["web.archive.org", "mail.google.com"]
# firecrawl_api_key = "fc-..." # enables Firecrawl as auth-wall fallback
# firecrawl_base_url = "http://localhost:3002" # self-hosted crw or Firecrawl instance
[llm]
provider = "lm_studio" # none | lm_studio | openai | anthropic
base_url = "http://localhost:1234" # LM Studio default
model = "local-model" # must match the model key in LM Studio
# api_key = "sk-..." # required for OpenAI / Anthropic
[sync]
interval_mins = 60
The sync interval is re-read each cycle, so changes take effect without restarting the app.
For the full option reference see CONFIG.md. For CSS class names available for custom_css see HTML.md.
Sync reads the 1,000 most recent URLs from your browser's SQLite history, registers any new ones, then fetches and extracts text from each page — respecting a configurable crawl delay, skipping auth walls and non-HTML content. URLs that fail 3 times are marked and no longer retried. When a page returns an auth wall, memoir tries Firecrawl (if an API key is configured) and then the Wayback Machine as fallbacks before giving up.
Fetched pages are stored in index.db and inserted into an FTS5 virtual table for BM25-ranked full-text search.
After fetching, the embedding model (BAAI/bge-small-en-v1.5 via ONNX Runtime, ~130 MB, downloaded from HuggingFace on first run) encodes each page's title + body into a 384-dimensional vector stored as a BLOB. The model runs entirely locally. Set provider = "none" in [llm] to skip the download entirely — full-text search still works, but semantic search and Ask are disabled.
Search queries the FTS5 index and returns ranked results with highlighted snippets. Semantic search re-ranks results using cosine similarity (minimum score: 0.3).
Ask embeds the query, retrieves the top-k pages by cosine similarity and BM25, builds a context prompt from the page bodies, and calls the configured LLM.
Clusters groups your visit history into sessions by time proximity, letting you see what you were researching on any given day.
Desktop app: the Axum server runs inside the Tauri process. The WebView points at http://127.0.0.1:<port>. A background task runs the sync loop, re-reading config each cycle.
MIT
34 commits
Rust
62.4%
HTML
37.6%