Metadata-Version: 2.5
Name: claude-explorer
Version: 1.1.3
Summary: Local archive + web viewer + MCP server for Claude Desktop and Claude Code conversations. Browse, search, export, and let Claude query its own history.
Project-URL: Homepage, https://github.com/rpeck/claude-explorer
Project-URL: Documentation, https://github.com/rpeck/claude-explorer#readme
Project-URL: Source, https://github.com/rpeck/claude-explorer
Project-URL: Issues, https://github.com/rpeck/claude-explorer/issues
Project-URL: Changelog, https://github.com/rpeck/claude-explorer/releases
Author: Raymond Peck
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: anthropic,archive,claude,claude-code,llm,mcp,viewer
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: click>=8.1
Requires-Dist: curl-cffi>=0.7
Requires-Dist: fastapi>=0.115
Requires-Dist: fastmcp>=3.0
Requires-Dist: httpx>=0.27
Requires-Dist: mitmproxy>=10.0
Requires-Dist: orjson>=3.10
Requires-Dist: platformdirs>=4.0
Requires-Dist: playwright>=1.40
Requires-Dist: portalocker>=2.8
Requires-Dist: pydantic>=2.0
Requires-Dist: pystray>=0.19.5; sys_platform == 'win32'
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: watchdog>=4.0
Requires-Dist: weasyprint>=68.1
Provides-Extra: dev
Requires-Dist: pypdf>=4.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30; extra == 'dev'
Requires-Dist: pytest-xdist>=3.5; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# Claude Explorer

A local tool to browse, full-text search, and export your entire Claude conversation history — Claude Desktop, Claude Code, and Claude Cowork in one unified, searchable place — and to query it programmatically from Claude itself via a built-in MCP server. It also rescues conversations from accounts whose login email you've lost access to.

> **Disclaimer**: This is an independent, community-built project. It is not affiliated with, endorsed by, sponsored by, or supported by Anthropic, PBC. "Claude" and "Claude Code" are trademarks of Anthropic, PBC. This project consumes Anthropic's products as a user would — via the same APIs and on-disk file formats the official clients use — but nothing here represents an Anthropic-sanctioned interface, and the formats this project depends on may change without notice.

## Read the article series

The Medium series *Unlocking Your Claude History* explains why this project exists and how to use it. Parts 2 and 3 come in more than one version, for different readers.

- **Part 1:** [What This Thing Is and Why You'd Want It](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-1-f19000c05655). The problem, and what the project does about it.
- **Part 2, the web app:**
  - [Quickstart: Claude Explorer in Five Minutes](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-2-quickstart-claude-explorer-in-five-minutes-41526bcce2a9).
  - [User's Guide](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-2-using-the-claude-explorer-web-app-user-guide-109191dc24d4). How to use the web app.
  - [User's Guide with Technical Deep Dive](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-2-using-the-claude-explorer-web-app-user-guide-with-technical-815ad3af1486). The same tour, plus how it works.
- **Part 3, Claude queries your history (the MCP server):**
  - [Quickstart: Query Your Claude History in Five Minutes](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-3-quickstart-query-your-claude-history-in-five-minutes-b12094c9c690).
  - [User's Guide: Let Claude Analyze Your Claude Conversations](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-3-let-claude-analyze-your-claude-conversations-a-users-guide-0797fed94c34).
  - [Deep Dive: Claude Querying Its Own History](https://medium.com/@raymondpeck/unlocking-your-claude-history-part-3-claude-querying-its-own-history-the-mcp-server-b3291ad76e13). How the MCP server works.

## Features

- **Browse conversations** from all three sources in one unified list: Claude Desktop (fetched), Claude Code, and Claude Cowork (both read live from local disk)
- **Full-text search** across all messages with instant results — multi-word queries AND tokens (all words must appear in the same message, any order); wrap in `"double quotes"` to require an exact phrase. Both the title-search and full-text-search honor the sidebar's active scope (source dropdown, workspace, and any active filter), so what you can't see in the sidebar can't appear in search results either.
- **Export** to Markdown or PDF
- **Dark mode** with automatic system preference detection
- **Keyboard navigation** with Emacs and Vim modes
- **Message tree visualization** for branched conversations
- **Bookmarks** (per-message): hover any message and click the star to save it; the **Bookmarks** tab in the right pane lists every saved message, with editable notes and Markdown export
- **Command palette** (Cmd+K) for quick navigation
- **Claude Code integration** with project grouping and subagent display
- **MCP server** exposing your saved sessions to Claude Desktop and Claude Code

---

## Install

Pick the row that matches what you want to do.

| You want to | Go to |
|---|---|
| Browse, search, and export your history in a web app. **Most people start here.** | [Quick Start](#quick-start) |
| Ask Claude about your history, from Claude Code or Claude Desktop | [Quick Start](#quick-start), then [Use it from Claude](#use-it-from-claude) |
| Change the code, or run from a git checkout | [From source](#from-source-for-contributors) |

The app runs on macOS, Linux, and Windows (Intel, AMD, and ARM). It keeps everything on your computer.

## Quick Start

Find the block for your computer below. Run its steps in order, one command at a time. After step 1, close the terminal, open a new one, and come back to this page for step 2.

Step 3 downloads a browser, so it takes the longest.

**Before you start:**

- **macOS and Linux:** use the Terminal app.
- **Windows:** use PowerShell. Do not use Command Prompt (`cmd.exe`).

### macOS

```bash
# 1. Install uv, the tool that installs and updates Claude Explorer:
curl -LsSf https://astral.sh/uv/install.sh | sh
#    Then close this Terminal window and open a new one.

# 2. Install Claude Explorer:
uv tool install claude-explorer

# 3. Install the browser that Claude Explorer uses to log you in to Claude:
uvx --from claude-explorer playwright install chromium

# 4. Install the image-cache watcher (one time; it then starts by itself):
claude-explorer install-watcher

# 5. Add the Claude Explorer app to the Applications folder in your home folder:
claude-explorer install-app

# 6. Start Claude Explorer: open it from Launchpad or Spotlight.
#    It starts the server and opens your browser. Quit it to stop the server.
#    Or, in a terminal, run: claude-explorer serve
```

### Linux

These commands suit Ubuntu and Debian. On other distributions, see the note after the block.

If step 1 reports `curl: command not found`, run `sudo apt-get install curl` first. Some Ubuntu and Debian installs do not include curl.

```bash
# 1. Install uv, the tool that installs and updates Claude Explorer:
curl -LsSf https://astral.sh/uv/install.sh | sh
#    Then close this terminal and open a new one.

# 2. Install Claude Explorer:
uv tool install claude-explorer

# 3. Install the browser that Claude Explorer uses to log you in to Claude.
#    --with-deps also installs the browser's system libraries, so this step
#    asks for your password:
uvx --from claude-explorer playwright install --with-deps chromium

# 4. Install the image-cache watcher (one time; it then starts by itself):
claude-explorer install-watcher
#    Keep the watcher running when you log out:
sudo loginctl enable-linger "$USER"

# 5. Add Claude Explorer to your app menu:
claude-explorer install-app

# 6. Start Claude Explorer: open it from your app menu.
#    It starts the server and opens your browser. To stop the server,
#    right-click it in the menu or dock, and choose "Stop Claude Explorer".
#    Or, in a terminal, run: claude-explorer serve
```

`--with-deps` supports only distributions that use `apt`. On Fedora, Arch, and other distributions, remove `--with-deps` from step 3. Then install Chromium's libraries with your package manager.

### Windows

The commands depend on your processor type. To find it, run this in PowerShell:

```powershell
$env:PROCESSOR_ARCHITECTURE
```

- `AMD64` means an Intel or AMD processor. Use [Windows on Intel or AMD](#windows-on-intel-or-amd). Most Windows PCs are this type.
- `ARM64` means an ARM processor. Use [Windows on ARM](#windows-on-arm). Copilot+ PCs, Surface Pro X, and Snapdragon X laptops are this type.

Windows can show a SmartScreen or Defender prompt the first time it runs a newly downloaded installer. That prompt is normal for this install.

#### Windows on Intel or AMD

```powershell
# 1. Install uv, the tool that installs and updates Claude Explorer:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
#    Then close this PowerShell window and open a new one.

# 2. Install Claude Explorer:
uv tool install claude-explorer

# 3. Install the browser that Claude Explorer uses to log you in to Claude:
uvx --from claude-explorer playwright install chromium

# 4. Install the image-cache watcher (one time; it then starts at each logon):
claude-explorer install-watcher

# 5. Add Claude Explorer to the Start menu:
claude-explorer install-app

# 6. Start Claude Explorer: open it from the Start menu.
#    It starts the server and opens your browser. Its icon appears in the
#    notification area. To stop the server, right-click that icon, and
#    choose Quit. Or, in PowerShell, run: claude-explorer serve
```

#### Windows on ARM

Several libraries that Claude Explorer uses publish no Windows ARM builds. So on ARM, Claude Explorer runs on an x86_64 (Intel-type) Python. Windows translates it automatically, at close to native speed. Microsoft recommends the same approach for Python on Windows on ARM.

**Keep the `--python` option exactly as shown.** Without it in step 2, uv picks an ARM Python, and the install fails.

```powershell
# 1. Install uv, the tool that installs and updates Claude Explorer:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
#    Then close this PowerShell window and open a new one.

# 2. Install Claude Explorer on an x86_64 Python (uv downloads it for you):
uv tool install claude-explorer --python cpython-3.13-windows-x86_64-none

# 3. Install the browser that Claude Explorer uses to log you in to Claude:
uvx --from claude-explorer --python cpython-3.13-windows-x86_64-none playwright install chromium

# 4. Install the image-cache watcher (one time; it then starts at each logon):
claude-explorer install-watcher

# 5. Add Claude Explorer to the Start menu:
claude-explorer install-app

# 6. Start Claude Explorer: open it from the Start menu.
#    It starts the server and opens your browser. Its icon appears in the
#    notification area. To stop the server, right-click that icon, and
#    choose Quit. Or, in PowerShell, run: claude-explorer serve
```

On Windows ARM, the proxy method of credential capture is not available. The default browser login works. See [Method B](#method-b-proxy-interception---proxy).

### After you install

1. Open `http://localhost:8765` in your browser. If you started Claude Explorer from its menu entry or app, it opens this page for you. Your Claude Code and Claude Cowork sessions show immediately.
2. Click **Refresh** in the sidebar to download your Claude Desktop conversations. The first time, a browser window opens. Log in to Claude in that window. The app then saves your login and downloads your conversations.

**Why the watcher matters.** Claude Code deletes old screenshots and pasted images from your disk. The watcher copies each image as soon as it appears. It runs in the background under your operating system's service manager (launchd, systemd, or Task Scheduler). Without it, images are protected only while `claude-explorer serve` runs.

**If a command says "command not found" or "not recognized":**

- After step 1, open a new terminal window. The uv installer updates your settings, but only new windows see the change.
- After step 2, run `uv tool update-shell`. Then open a new terminal window.

### PDF export (optional)

Markdown export works with no extra setup. PDF export needs graphics libraries that Claude Explorer cannot install for you. Skip this section if you need only Markdown.

- **macOS:** install [Homebrew](https://brew.sh) if you do not have it. Then run:

  ```bash
  brew install pango cairo libffi
  ```

- **Ubuntu and Debian:**

  ```bash
  sudo apt-get install libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
  ```

  On other distributions, install the `pango` package.

- **Windows:** PDF export needs the GTK3 runtime libraries. Follow the [WeasyPrint Windows instructions](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html). Or install [MSYS2](https://www.msys2.org), and run `pacman -S mingw-w64-x86_64-pango` in its shell.

  WeasyPrint looks for these libraries in `C:\msys64\mingw64\bin` and `C:\Program Files\GTK3-Runtime Win64\bin`. If you installed them in a different folder, set the user environment variable `WEASYPRINT_DLL_DIRECTORIES` to that folder.

  The standalone WeasyPrint download does not enable PDF export here. It is a separate program, and this app uses WeasyPrint as a library.

After you install the libraries, stop the app and start it again.

### Use it from Claude

Claude Explorer includes an MCP server, so Claude can search and read your history for you. For example, ask *"find the session where I debugged the weasyprint install"*.

- **Claude Code:** run this one command. It works in every project.

  ```bash
  claude mcp add --scope user claude-sessions -- claude-explorer mcp
  ```

- **Claude Desktop:** see [Claude Desktop setup](#claude-desktop-setup).

### Everyday commands

| To | Run |
|---|---|
| Start the app | Open **Claude Explorer** from Launchpad, the Start menu, or your app menu. Or run `claude-explorer serve`, then open `http://localhost:8765` |
| Stop the app | macOS: quit **Claude Explorer**. Windows: right-click its notification-area icon, and choose **Quit**. Linux: right-click it in the menu, and choose **Stop Claude Explorer**. In a terminal: press **Ctrl+C** where `serve` runs |
| Check the watcher | See [Verify it's running](#image-cache-watcher--technical-details) |
| Upgrade | The four steps below |
| Uninstall | The steps below |

**If another program uses port 8765:**

Claude Explorer uses port 8765. If a different program already uses that port, Claude Explorer does not start. It tells you why, and it gives you the command that fixes it, with a free port:

- **The app or menu entry** shows a message with numbered steps. On macOS, click **Copy Command**. On Windows, the command is already on the clipboard.
- **`serve` in a terminal** prints the commands for the terminal and for the app.

The fix, for example with port 8766:

1. Install the launcher again with that port: `claude-explorer install-app --port 8766`
2. Open **Claude Explorer** as usual. It opens `http://localhost:8766`.

In a terminal, run `claude-explorer serve --port 8766`, then open `http://localhost:8766`.

`install-app` remembers the port. When you run it again without `--port`, for example after an upgrade, it uses the same port. Choose the port one time, and keep it: the web page keeps some settings per address, so a new port starts with default settings.

**To upgrade:**

First, find out how you installed Claude Explorer. Run `claude-explorer --version`.

- If it prints a version number, you have a lasting install. Follow [Upgrade a lasting install](#upgrade-a-lasting-install).
- If it reports "command not found" or "not recognized", you installed with `uvx`, as the README for versions before 1.1.0 said. Follow [Move from uvx to a lasting install](#move-from-uvx-to-a-lasting-install).

#### Upgrade a lasting install

1. Stop the app.
2. Stop the watcher: `claude-explorer install-watcher --uninstall`
3. Upgrade: `uv tool upgrade claude-explorer`
4. Start the watcher again: `claude-explorer install-watcher`
5. Build the launcher again, so it gets the new version's fixes: `claude-explorer install-app`. It keeps the port that you chose before.
6. Check the version: `claude-explorer --version`

Stop the watcher first. Windows cannot replace files that a running program uses. On every platform, the watcher then restarts on the new version.

On Windows on ARM, `uv tool upgrade` keeps the x86_64 Python from your install. You do not need the `--python` option again.

If the **Refresh** login later reports a missing browser, run step 3 of your install block again.

#### Move from uvx to a lasting install

`uvx` runs Claude Explorer from a temporary copy in uv's cache. uv can delete that copy at any time. If you installed the watcher with `uvx`, the watcher uses that copy, and it can stop without any warning. Move to a lasting install one time:

1. Stop the app.
2. Remove the old watcher: `uvx claude-explorer install-watcher --uninstall`
3. Do steps 2 to 5 of the install block for your platform in the [Quick Start](#quick-start). They install the tool, the browser, the watcher, and the launcher.
4. Check the version: `claude-explorer --version`

Your downloaded conversations stay where they are. You do not need to download them again.

If you set up the MCP server in Claude Code with `uvx`, that setup still works. To make it use the same version as the app, run these two commands:

```bash
claude mcp remove claude-sessions
claude mcp add --scope user claude-sessions -- claude-explorer mcp
```

For Claude Desktop, see [Claude Desktop setup](#claude-desktop-setup).

**To uninstall:**

1. Remove the watcher: `claude-explorer install-watcher --uninstall`
2. Remove the launcher from the Dock, Start menu, or app menu: `claude-explorer install-app --uninstall`
3. Remove the app: `uv tool uninstall claude-explorer`

Your downloaded conversations stay in the `.claude-explorer` folder in your home folder.

> **Warning:** Delete that folder only if you want to lose your downloaded conversations. Claude Explorer may hold conversations that no longer exist anywhere else, for example from an account that you can no longer log in to.

If you'd rather hack on the project than install it, see [From source (for contributors)](#from-source-for-contributors).

## Install in Claude Desktop (one-click MCP)

If you only want to query your archive from Claude (no web UI), grab the
`.mcpb` bundle from the latest [GitHub Release](https://github.com/rpeck/claude-explorer/releases/latest)
and drag it into Claude Desktop → **Settings** → **Extensions**. Five
tools — `list_sessions`, `list_projects`, `get_session_outline`,
`get_messages`, `export_session` — light up inside Claude Desktop
without you ever touching `claude_desktop_config.json`. The extension
does not configure Claude Code. For Claude Code, run the one command in
[Use it from Claude](#use-it-from-claude).

Two non-obvious things to know up front:

- **The extension is read-only.** It only reads what's already in
  `~/.claude-explorer/conversations/`. It never writes, deletes, or
  modifies your archive.
- **The extension does not download your Claude Desktop conversations.**
  Install the app with the [Quick Start](#quick-start), start it, and
  click **Refresh** once. The extension then reads what the app
  downloaded. Click **Refresh** again whenever you want newer
  conversations.

First launch is a little slow (Claude Desktop's [UV runtime](https://github.com/anthropics/dxt)
resolves and installs the bundle's Python deps on first run; ~10–30 s
depending on your network). Subsequent launches are instant.

## Performance

Claude Explorer keeps interaction latency under the human-perception threshold across the corpus sizes most users actually have (hundreds to low thousands of conversations, single-digit GB on disk). Numbers below come from a ~1,000-conversation / ~2.5 GB corpus measured with [`hyperfine`](https://github.com/sharkdp/hyperfine) on macOS / M3 Pro / local SSD. The "Before" column is the pre-optimization baseline shipped in early V1 betas; "After" is what V1 ships.

| Metric | Before | After | Improvement |
|---|---|---|---|
| Sidebar list (`/api/conversations`), warm cache | 4,518 ms | **72 ms** | ~63× |
| Sidebar list, cold SQLite cache, warm filesystem | 11,168 ms | **134 ms** | ~83× |
| Sidebar list, first install, cold everything | ~6,000 ms | **135 ms** | ~44× |
| Search query, narrow term (`q=foobar`) | ≈1,400 ms (linear) | **≈317 ms** (FTS5) | ~4.4× |
| Search query, broad term (`q=python`, ~770 KB results) | ≈1,400 ms (linear) | **≈750 ms** (FTS5) | ~1.9× |
| Search query cold (first call after restart) | ≈20,850 ms | **≈780 ms** | ~27× |
| Conversation detail, 288 MB CC JSONL (warm) | 1,474 ms | **≈230 ms** | ~6.4× |
| Markdown export of same conversation (warm) | 1,460 ms | **≈230 ms** | ~6.4× |
| Search-ready time after server restart | ~15 s | **<1 s** | ~15× |
| Startup time-to-image-protection | ~15 s | **~1 s** | ~15× |
| In-flight search freshness (CC session updated while running) | up to 600 s | **~2–3 s** | ~200× |
| Sidebar wire payload | 650,640 B | **459,555 B** | −29% |
| Sidebar DOM rows rendered (334-conv corpus) | 334 | **13** | −96% |

What's behind those numbers:

- **A persistent SQLite metadata cache** at `~/.claude-explorer/search-index.sqlite` (`conversation_summaries` table) means the sidebar payload comes from cached rows, not from re-parsing every JSONL on every request.
- **A drift-first FTS5 build** at startup queries the `indexed_files` table for current mtimes before loading any conversation content; only files that actually changed get re-indexed. Search becomes queryable in under a second after restart instead of ~15 s.
- **FTS5 `snippet()` for the search-result fragments** replaces the post-MATCH corpus walk that had to read every matched conversation from disk. The schema carries a two-column projection (`body` for the full text, `body_text` for the text-only view) so the **Tools** toggle behaves the same on the fast path as on the linear-scan path: column-scoped MATCH excludes tool-only hits at MATCH time, not after BM25 ranks. `snippet()` runs entirely in SQLite, and the cold-search path drops from ~16 s to under 1 s on a 1,000-conversation corpus.
- **A truncation envelope on every search response** discloses the FTS5 row cap. `/api/search` returns `{results, total_messages_matched, returned_messages, truncated}` instead of a bare list; the total comes from a sub-10 ms `COUNT(*)` under the same WHERE clauses as the snippet query. The SearchPanel renders a small footer ("Showing first 1,000 of 12,400 message matches. Refine your query to see the rest.") when the cap clipped the response. The MCP `list_sessions` tool uses a higher cap (5,000) so LLM and script consumers can reason about broader queries.
- **An LRU FileCache + cache-routed conversation-detail lookups** mean opening a 288 MB Claude Code session is a dict lookup, not a JSONL re-parse. Markdown / PDF / JSON exports get the same payoff for free.
- **The CC image-warm walk piggybacks on the FTS5 build** instead of running as a separate startup pass: every JSONL the FTS5 build reads also warms its image markers via `cache_all_markers`. Image protection now becomes available within ~1 s of restart instead of ~15 s, and a duplicate disk walk goes away entirely.
- **Event-driven `watchdog` observers** on `~/.claude/image-cache/` and `~/.claude/projects/` catch new images and CC session edits within sub-second latency; a debounced drift pass keeps the search index fresh while the explorer is running.
- **`ORJSONResponse`** plus a skinny `ConversationListItem` Pydantic model (a strict subset of `ConversationSummary`) cuts the wire payload ~29% without breaking MCP, the detail page, or the server-side `?search=` filter.
- **Frontend virtualization** (`@tanstack/react-virtual`) on the flat sidebar list mounts ~13 rows instead of all ~1,000, taking the linear-in-N React reconciliation cost with it.
- **A linear-scan fallback** kept in the codebase covers every "fast path unavailable" case (FTS5 missing on a stock Linux Python, index still warming on first install, summary cache disabled). Search and sidebar never go "down" — slow-but-correct beats fast-but-broken.

### Running the benchmarks yourself

The repo ships a `make bench` target that drives the canonical suite (sidebar list, search warm and cold, conversation-detail at small / medium / large size percentiles auto-picked from the live corpus, Markdown export) against a running backend on `:8765`.

```bash
# 1. Start the backend in one terminal:
DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run uvicorn backend.main:app --port 8765
# Wait for "search index build complete" on stdout (warm restart: <1 s;
# first run after a schema bump can take ~25 s on a large corpus).

# 2. In another terminal, from the repo root:
make bench           # full suite, ~30–60 s, human-readable table
make bench-quick     # same suite with fewer runs per measurement
make bench-json      # structured JSON, suitable for paste into a PR body
make cold-search-instructions  # prints the steps for cold-restart measurement

# Override the target host (defaults to localhost:8765):
make bench BASE=http://localhost:8766
```

`make bench` is **not** a CI gate. It's a developer-loop convenience for "did I regress anything" diffs and PR-body numbers. The harness auto-picks fixture UUIDs from your live corpus and prints them so reruns are reproducible. Full usage notes live in [`benchmarks/README.md`](./benchmarks/README.md).

Two focused scripts also live in `benchmarks/`: `bench_perf.py` covers two endpoints with custom stats, and `bench_search_paths.py` compares the FTS5 and linear-scan search paths in-process so you can confirm both produce the same `(conv_uuid, message_uuid)` set on the same corpus. For ad-hoc one-off measurements, [`hyperfine`](https://github.com/sharkdp/hyperfine) is still the right tool (`brew install hyperfine` on macOS, `apt install hyperfine` on Debian / Ubuntu, `cargo install hyperfine` anywhere with a Rust toolchain).

For the full narrative of how these pieces fit together and what was tried-but-rejected along the way, see ["Building Claude Explorer: Part 2 — The Web App"](./articles/part_2_web_app.md) "Performance (FTS5 index)" section.

---

## Background

### Where Claude Desktop Stores Your Data

Claude Desktop is an Electron app that wraps `claude.ai`. Your conversation history is **stored server-side only** — there is no meaningful local copy. If you go looking on your Mac, here's what you'll find (and won't find):

| Path | What's There |
|------|-------------|
| `~/Library/Application Support/Claude/IndexedDB/https_claude.ai_0.indexeddb.leveldb` | **Chat drafts only** — unsent text in the input box. No conversation history. |
| `~/Library/Caches/com.anthropic.claudefordesktop/Cache.db` | **App update URLs only** — not conversation data. |

The official export path (Settings → Privacy → Export Data) sends a download link to your account email. If you've lost access to that email address, you're stuck.

### The Workaround

This tool offers two ways to capture your session credentials:

**Method A: Browser Login (Default)**
1. Open a Playwright-controlled browser to claude.ai
2. Let you log in normally (SSO, email, etc.)
3. Extract the **session cookie** (`sessionKey`) after authentication
4. Use that session cookie to **bulk-fetch all conversations** directly from the claude.ai API

**Method B: Proxy Interception (--proxy)**

If you've lost access to the login (e.g., work SSO disabled) but Claude Desktop is still authenticated:

1. Run **mitmproxy** as a local proxy on port 8080
2. Launch Claude Desktop through the proxy with `--proxy-server=127.0.0.1:8080 --ignore-certificate-errors`
3. Watch the intercepted traffic to capture the **session cookie** (`sessionKey`)
4. Use that session cookie to **bulk-fetch all conversations** directly from the claude.ai API

Claude Desktop does **not** do SSL certificate pinning, which makes the proxy method possible.

---

## The claude.ai API (Unofficial, Observed)

All endpoints authenticate via a `Cookie` header:

```
Cookie: sessionKey=sk-ant-sid01-...
```

### Endpoints

```
GET /api/organizations/{org_id}/chat_conversations
    ?limit=30&starred=false&offset=0
    → Paginated list of conversation summaries

GET /api/organizations/{org_id}/chat_conversations/count_all
    → { "count": N }

GET /api/organizations/{org_id}/chat_conversations/{uuid}
    ?tree=True&rendering_mode=messages&render_all_tools=true&consistency=strong
    → Full conversation with all messages
```

### Conversation Structure

```json
{
  "uuid": "...",
  "name": "Conversation title",
  "model": "claude-sonnet-4-6",
  "created_at": "2026-02-25T19:14:43Z",
  "updated_at": "2026-02-25T20:30:51Z",
  "is_starred": false,
  "is_temporary": false,
  "current_leaf_message_uuid": "...",
  "chat_messages": [...]
}
```

### Message Structure

```json
{
  "uuid": "...",
  "sender": "human" | "assistant",
  "text": "message text",
  "content": [...],
  "index": 0,
  "created_at": "2026-02-25T19:14:43Z",
  "truncated": false,
  "parent_message_uuid": "...",
  "attachments": [],
  "files": []
}
```

### Message Trees and Branches

Messages form a **tree**, not a flat list. Each message has a `parent_message_uuid` linking it to its parent. When you edit a message and regenerate a response, Claude creates a new branch — so a conversation can have multiple alternate paths.

`current_leaf_message_uuid` on the conversation object points to the tip of the currently active branch. To reconstruct the displayed conversation, walk backward from the leaf following `parent_message_uuid` links until you reach the root, then reverse the list.

### Content Blocks

The `content` array contains typed blocks:

| Type | Description |
|------|-------------|
| `text` | Regular message text |
| `tool_use` | A tool call (name + input dict) |
| `tool_result` | The result of a tool call |
| `image` | An embedded image |

---

## From source (for contributors)

*If you want to hack on the project, run from a git checkout, or you're a contributor: install from source instead.*

### Project Structure

```
claude-explorer/
├── README.md
├── AGENTS.md                 # Development guide (for people and coding agents)
├── TESTING.md                # How to run, write, and trust the tests
├── UX.md                     # UI flows and rules
├── pyproject.toml            # Python dependencies + CLI entry point
├── PLANS/                    # Design notes and implementation plans
├── cli/
│   ├── main.py               # CLI entry point (claude-explorer command)
│   └── watcher.py            # install-watcher: launchd / systemd / Task Scheduler
├── fetcher/
│   ├── playwright_capture.py # Browser-based credential capture (default)
│   ├── mitmproxy_addon.py    # Proxy-based credential capture (--proxy)
│   └── bulk_fetch.py         # Downloads all conversations to local JSON
├── backend/
│   ├── main.py               # FastAPI app
│   ├── models.py             # Pydantic models
│   ├── store.py              # Reads and indexes JSON files from disk
│   ├── search.py             # Search dispatcher (FTS5 index, linear fallback)
│   ├── search_index.py       # SQLite FTS5 search index
│   ├── cc_watcher.py         # Image-cache and search-index watcher
│   ├── export.py             # Markdown + PDF export
│   └── routers/              # API endpoints
├── mcp_server/
│   └── server.py             # stdio MCP server (5 tools)
├── frontend/
│   └── src/                  # React 19 + TypeScript + Tailwind + shadcn/ui
├── scripts/
│   ├── build-mcpb.py                     # Builds the Claude Desktop extension
│   ├── check-cleanup-period.py           # Inspect/fix Claude Code's cleanupPeriodDays
│   └── macos-restore-claude-projects.py  # Recover deleted projects from Time Machine
└── utils/
    └── restore-deleted-sessions-and-images.sh  # Recover both sessions AND image-cache PNGs
```

### Prerequisites

You need three tools:

- **uv:** install it with step 1 of the [Quick Start](#quick-start) block for your platform.
- **git:** to clone the repository.
- **Node.js 20.19 or later:** to build the web UI. Without the build, `serve` runs the API only and shows no UI.

Then run these commands. They work the same in bash, zsh, and PowerShell.

```bash
# 1. Clone the repository:
git clone https://github.com/rpeck/claude-explorer
cd claude-explorer

# 2. Install the Python dependencies, including the test tools:
uv sync --extra dev

# 3. Install the browser for credential capture:
uv run playwright install chromium

# 4. Build the web UI:
cd frontend
npm install
npm run build
cd ..
```

Platform differences:

- **Linux:** in step 3, run `uv run playwright install --with-deps chromium`. It installs the browser's system libraries, so it asks for your password.
- **Windows on ARM:** in step 2, run `uv sync --extra dev --python cpython-3.13-windows-x86_64-none`. The [Windows on ARM](#windows-on-arm) section explains why.
- **PDF export:** install the libraries in [PDF export (optional)](#pdf-export-optional).

### Step 1: Capture Your Session Cookie

There are two methods to capture credentials. Choose the one that fits your situation.

> The commands below use the `uv run` prefix, which works inside a source checkout.
> If you installed from PyPI, drop that prefix and run `claude-explorer ...` directly.

#### Method A: Browser Login (Default)

The simplest approach — opens a browser window where you log into Claude normally:

```bash
uv run claude-explorer capture
```

This opens Chromium, navigates to claude.ai, and waits for you to log in. Once authenticated, credentials are automatically extracted and saved to `~/.claude-explorer/credentials.json`.

**Options:**
- `--timeout N` — Max seconds to wait for login (default: 300)

**Auditing the trust path.** Credential capture is implemented in `fetcher/playwright_capture.py` (function `capture_credentials`). It does exactly two things: read the `sessionKey` cookie out of the Playwright browser context after you log in, and write that cookie plus the org id to `~/.claude-explorer/credentials.json`. The capture step itself has no network egress beyond the browser you're already using to log into Claude.

#### Method B: Proxy Interception (--proxy)

Use this method when you **can't log into the web UI** but Claude Desktop is still authenticated. This was a lifesaver for recovering conversations from a work account after losing access to the SSO login.

```bash
# Terminal 1 — start the proxy (requires ANSI terminal)
uv run claude-explorer capture --proxy

# Terminal 2 — launch Claude Desktop through the proxy
open -a "Claude" --args --proxy-server="127.0.0.1:8080" --ignore-certificate-errors
```

Click around in Claude Desktop for a few seconds. The addon will print:

```
✅ Credentials captured! You can now quit mitmproxy (q) and close Claude Desktop.
```

**Options:**
- `--port N` — Proxy port (default: 8080)

**Platform-specific launch commands.**

macOS:
```bash
open -a "Claude" --args --proxy-server="127.0.0.1:8080" --ignore-certificate-errors
```

Linux:
```bash
claude --proxy-server="127.0.0.1:8080" --ignore-certificate-errors
```

Windows (PowerShell, Intel and AMD only):
```powershell
# 1. Trust the mitmproxy CA. Do this BEFORE you launch Claude Desktop.
#    `--ignore-certificate-errors` covers only Chromium's own requests. It does
#    not cover the Electron main process, so the capture sees nothing without
#    this step. The certificate file appears after the proxy has run once.
Import-Certificate -FilePath "$env:USERPROFILE\.mitmproxy\mitmproxy-ca-cert.cer" -CertStoreLocation "Cert:\CurrentUser\Root"

# 2. Confirm your own install path (launch Claude Desktop first):
Get-Process Claude | Select-Object Path

# 3. Launch through the proxy. The typical installer path is shown here;
#    use the path from step 2 if yours differs.
& "$env:LOCALAPPDATA\AnthropicClaude\Claude.exe" --proxy-server="127.0.0.1:8080" --ignore-certificate-errors
```

**Windows caveats.**

- **Windows ARM: installed, but not yet verified.** Claude Explorer runs on an x86_64 Python there, so mitmproxy installs. Nobody has yet confirmed proxy capture end to end on Windows ARM. If it fails, use Method A.
- **A Microsoft Store install may ignore these flags.** That build lives under `C:\Program Files\WindowsApps\` and runs sandboxed. If no credentials appear after a minute of clicking around in Claude Desktop, use Method A.
- **Use PowerShell, not `cmd.exe`.** `cmd.exe` does not understand `&` or `$env:`.

**Note:** mitmproxy needs a real terminal: Terminal.app or iTerm2 on macOS, Windows Terminal or PowerShell on Windows, any TTY on Linux. It does not work in non-TTY environments.

---

Both methods save credentials to `~/.claude-explorer/credentials.json`.

### Step 2: Download Your Conversations

```bash
uv run claude-explorer fetch
```

This downloads all your conversations as JSON files to `~/.claude-explorer/conversations/`. A rate-limited 0.3s delay between requests keeps things polite. Incremental mode (default) skips conversations you've already downloaded. Attachment bytes (images, PDFs, canvas transcripts) land alongside the JSON in a sibling `~/.claude-explorer/files/` directory keyed by conversation and file UUID.

Options:
- `--full-refresh` — Re-download all conversations
- `--limit N` — Download only N conversations
- `--verbose` — Show detailed progress

### Step 3: Browse and Export

```bash
uv run claude-explorer serve
```

Opens the web app at `http://localhost:8765`.

#### One-button Refresh (Build-9)

Once the web app is running, the **Refresh** button in the sidebar footer owns the entire pipeline. A single click:

1. Tries an incremental fetch with whatever credentials are on disk.
2. If credentials are missing, or the fetch fails with `401`/`403`/`cf-mitigated`, the backend automatically launches the same Playwright login flow as `claude-explorer capture` — a Chromium window opens, you log in, and the new `sessionKey` is written to `~/.claude-explorer/credentials.json` (`0o600`, atomically).
3. The fetch then continues automatically. Existing conversations are preserved (incremental — never `--full-refresh` automatically).

The toast walks you through each phase:

> "Opening browser to log in to Claude…" → "Waiting for you to log in (Ns elapsed)…" → "Credentials captured. Fetching…" → "Fetched +N new conversations."

If the capture window is closed or login times out (5 min default), the toast becomes sticky with a **Retry** action. You never have to drop to the CLI to re-capture.

Manual override: the **Details** modal still exposes "Full Refresh" and "Fetch New" buttons that hit the original `/fetch/start` endpoint without auto-capture.

#### Bookmarks (per-message)

Hover over any message in the conversation pane and click the star icon to bookmark it. The right pane carries a **Bookmarks** tab next to **Search**; the tab lists every saved message grouped by conversation, with a snippet, an optional inline-editable note, and the timestamp. Click any entry to jump straight to that message. A top-of-panel **Export to Markdown** button writes the whole bookmark set to a single `bookmarks-YYYY-MM-DD.md` file.

Bookmarks differ from sidebar stars: stars save a whole conversation, bookmarks save a specific message inside one. Argless command markers (`/exit`, `/clear`) intentionally do not get a bookmark affordance, since "save a meaningful message" is the underlying model.

The back end persists bookmarks atomically to `~/.claude-explorer/bookmarks.json` via the `/bookmarks` REST endpoint (`GET` / `POST` / `PATCH` / `DELETE`); set `CLAUDE_EXPLORER_BOOKMARKS_FILE` to override the location for tests.

For development with hot-reload:
```bash
# Terminal 1 — backend
DYLD_LIBRARY_PATH=/opt/homebrew/lib uv run uvicorn backend.main:app --reload

# Terminal 2 — frontend
cd frontend && npm run dev
```
Then open `http://localhost:5173`.

---

## MCP Server

The project ships with a built-in **Model Context Protocol** server that lets Claude Desktop or Claude Code query your saved conversations directly. Once configured, you can ask Claude things like *"find the session where I debugged the weasyprint install"* or *"export the ZFS conversation I had last week as markdown"* and Claude will use the tools below to answer.

### Tools exposed

| Tool | Purpose |
|------|---------|
| `list_sessions` | Full-text search / list conversation sessions, optionally filtered by source or project |
| `list_projects` | List distinct projects with session counts |
| `get_session_outline` | Lightweight per-message summaries (cached in SQLite) for a specific session |
| `get_messages` | Full message content for specific positions or UUIDs, with optional tool calls/results |
| `export_session` | Markdown export of a full or partial session |

The server runs over **stdio** (no network port) and reads the same on-disk corpus the web UI does: fetched Claude Desktop JSON under `~/.claude-explorer/conversations/`, plus live Claude Code and Claude Cowork sessions.

### Prerequisites

1. Install Claude Explorer with the [Quick Start](#quick-start).
2. Start the app, and click **Refresh** once to download your Claude Desktop conversations. Claude Code and Cowork sessions need no download.

The MCP command is `claude-explorer mcp`. To test it by itself, run:

```bash
claude-explorer mcp
# It prints nothing and waits for MCP messages on stdin. Press Ctrl+C to exit.
```

From a git checkout, run `uv run --directory /path/to/claude-explorer claude-explorer mcp` instead.

### Claude Code setup (all platforms)

Run this one command. It writes the config for you:

```bash
claude mcp add --scope user claude-sessions -- claude-explorer mcp
```

`--scope user` makes the server available in every project (written to `~/.claude.json`). Swap it for `--scope project` to scope it to the current repo instead (written to a `.mcp.json` at the repo root). A bare `claude mcp add` defaults to `local` scope (private to the current project, also stored in `~/.claude.json`). Verify with:

```bash
claude mcp list
```

To edit the config by hand instead, the `mcpServers` block is identical at both scopes; only the file differs — `~/.claude.json` for user scope, `.mcp.json` at the repo root for project scope. (MCP servers do **not** live under `.claude/`, which holds other settings like permissions.)

```json
{
  "mcpServers": {
    "claude-sessions": {
      "type": "stdio",
      "command": "claude-explorer",
      "args": ["mcp"]
    }
  }
}
```

> **Note:** Contributors running from a checkout can instead use `"command": "uv"` with `"args": ["run", "--directory", "/path/to/claude-explorer", "claude-explorer", "mcp"]`.

### Claude Desktop setup

The easiest option is the [one-click extension](#install-in-claude-desktop-one-click-mcp). To configure the server by hand instead, follow these steps.

**1. Find the full path of the command.** Claude Desktop does not read your terminal's `PATH`, so give it the full path.

- macOS and Linux: run `which claude-explorer`. The usual result is `/Users/YOU/.local/bin/claude-explorer` on macOS, or `/home/YOU/.local/bin/claude-explorer` on Linux.
- Windows (PowerShell): run `(Get-Command claude-explorer).Source`. The usual result is `C:\Users\YOU\.local\bin\claude-explorer.exe`.

**2. Open the config file.** In Claude Desktop, open **Settings → Developer → Edit Config**. Or open the file directly:

- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` (typically `C:\Users\YOU\AppData\Roaming\Claude\claude_desktop_config.json`)
- **Linux:** `~/.config/Claude/claude_desktop_config.json`

**3. Add this block.** Put the path from step 1 in `command`:

```json
{
  "mcpServers": {
    "claude-sessions": {
      "command": "/Users/YOU/.local/bin/claude-explorer",
      "args": ["mcp"]
    }
  }
}
```

On Windows, double each backslash in the path, for example `"C:\\Users\\YOU\\.local\\bin\\claude-explorer.exe"`. JSON requires that.

**4. Quit Claude Desktop fully, and start it again.** It reads the config only when it starts.

### Verifying it works

After restarting your client, ask it to search your history, e.g.:

> *"Use the claude-sessions MCP server to list my 5 most recent sessions."*

In Claude Code you can also run `/mcp` to see the server status and the list of tools it registered.

### Troubleshooting

- **"command not found: claude-explorer"** — the MCP client does not see your terminal's `PATH`. Put the full path from `which claude-explorer` (or `(Get-Command claude-explorer).Source` on Windows) in `command`.
- **"Session not found" / empty results** — open the web app and click **Refresh**. The MCP server reads Claude Desktop conversations from `~/.claude-explorer/conversations/`.
- **Need to use a non-default data dir** — set `CLAUDE_EXPLORER_DATA_DIR` via an `env` block in the MCP config:
  ```json
  "env": { "CLAUDE_EXPLORER_DATA_DIR": "/path/to/conversations" }
  ```
- **Stale session outlines after a branch switch** — outlines are cached in `~/.claude-explorer/cache.db`. Delete that file to force a full rebuild.

---

## Image-Cache Watcher — Technical Details

Step 4 of the Quick Start runs `claude-explorer install-watcher`. This section explains what that actually does, where to look when it breaks, and how to tune it.

**Architecture: event-driven primary + backstop poll.** The watcher subscribes to OS-native filesystem events (FSEvents on macOS, inotify on Linux, ReadDirectoryChangesW on Windows, all via the [`watchdog`](https://github.com/gorakhargosh/watchdog) library) and copies new files within sub-second latency at near-zero idle CPU. A periodic backstop poll (default 600s = 10 min) re-runs the full directory walk to catch the rare event the OS dropped or coalesced. On a sandboxed Python or an unsupported filesystem (NFS, etc.) `watchdog` falls back to its `PollingObserver` automatically — strictly worse latency, same correctness — and the watcher logs which backend got selected so misconfigurations are diagnosable.

```bash
# Verify it's running (per platform):
launchctl list | grep claude-explorer                                  # macOS
systemctl --user status claude-explorer-cc-watcher.service             # Linux
schtasks /Query /TN ClaudeExplorerCCWatcher                            # Windows

# Tune the backstop poll interval (default 600s = 10min; events handle
# the latency-critical work, so smaller values do not improve normal
# capture latency):
claude-explorer install-watcher --interval 60

# Uninstall:
claude-explorer install-watcher --uninstall
```

**Where the unit lives + how it's supervised:**

| Platform | Mechanism                        | Path                                                                   |
|----------|----------------------------------|------------------------------------------------------------------------|
| macOS    | launchd user agent               | `~/Library/LaunchAgents/com.claude-explorer.cc-watcher.plist`           |
| Linux    | systemd **user** unit             | `~/.config/systemd/user/claude-explorer-cc-watcher.service`             |
| Windows  | Task Scheduler task (logon trigger)| `ClaudeExplorerCCWatcher` (launcher at `%USERPROFILE%\.claude-explorer\cc-watcher.py`) |

All three run the same Python entry point (`backend.cc_watcher.run_watcher`), which combines the `watchdog` Observer with the periodic backstop poll. Only the supervisor differs. macOS uses `KeepAlive`, Linux uses `Restart=always`, Windows uses an on-logon trigger — each restarts on crash.

**Where logs go:**

| Platform | Logs                                                                                |
|----------|-------------------------------------------------------------------------------------|
| macOS    | `~/Library/Logs/claude-explorer-cc-watcher.{out,err}`                                |
| Linux    | `journalctl --user -u claude-explorer-cc-watcher.service -f`                         |
| Windows  | None: the task runs `pythonw.exe`, which has no console. To see output, run `schtasks /Query /TN ClaudeExplorerCCWatcher /V /FO LIST` and copy the **Task To Run** line. Run that line in PowerShell with `python.exe` in place of `pythonw.exe`, after a `&` (for example `& "C:\...\python.exe" "C:\...\cc-watcher.py"`). |

**Where mirrored images live:** `~/.claude-explorer/cc-images/<sess>/<sess>--<N>.<sha8>.<ext>` — content-addressed, append-only. Safe even if Claude Code rotates the original, even if you reinstall, even if the conversation JSONL itself is deleted later by `cleanupPeriodDays`.

**Linux note — surviving logout:** systemd user units stop when you log out by default. To keep the watcher running across logout (or on a headless box), run once: `sudo loginctl enable-linger $USER`.

---

## Companion Utilities (macOS)

The `scripts/` directory ships two standalone utilities that address a Claude Code gotcha you may have hit while using this tool: **Claude Code silently auto-deletes** session files in `~/.claude/projects/` older than `cleanupPeriodDays` (default: 30). When a project subdirectory becomes empty, it is removed entirely.

### `scripts/check-cleanup-period.py` — inspect/fix the auto-cleanup

Reports the current `cleanupPeriodDays` value in `~/.claude/settings.json` and (with `--set N`) atomically updates it while preserving every other key.

```bash
# Report only
python3 scripts/check-cleanup-period.py

# Effectively disable auto-cleanup (~100 years)
python3 scripts/check-cleanup-period.py --set 36500
```

Refuses to set `0` (which silently disables conversation persistence — Claude Code [issue #23710](https://github.com/anthropics/claude-code/issues/23710)). Warns if you set anything shorter than 30 or 365 days.

### `scripts/macos-restore-claude-projects.py` — recover deleted projects from Time Machine

Walks every Time Machine snapshot under `/Volumes/.timemachine/<UUID>/`, collects the union of every `~/.claude/projects/<name>/` ever seen, diffs against the live directory, and copies the **newest backup** of each missing dir to `~/.claude/projects-recovered/`. Never overwrites anything.

```bash
# Preview what's recoverable across ALL backups
sudo python3 scripts/macos-restore-claude-projects.py --dry-run

# Recover all missing projects (default)
sudo python3 scripts/macos-restore-claude-projects.py

# Limit how far back to scan
sudo python3 scripts/macos-restore-claude-projects.py --since 2026-01-01
sudo python3 scripts/macos-restore-claude-projects.py --days 60

# Recover AND auto-move into ~/.claude/projects/ (skips any that already exist)
sudo python3 scripts/macos-restore-claude-projects.py --apply
```

**Requires:** Terminal must have **Full Disk Access** (System Settings → Privacy & Security → Full Disk Access → add Terminal). Without FDA, macOS returns "Operation not permitted" when reading TM snapshot directories. The script detects this and tells you what to do.

After recovery, **set a high `cleanupPeriodDays`** with the checker above so this doesn't happen again.

### `utils/restore-deleted-sessions-and-images.sh` — recover sessions AND image-cache PNGs

A bash superset of the Python script above that also restores files under `~/.claude/image-cache/<session-uuid>/<N>.png` (Claude Code rotates these off disk on its own schedule, separately from the `cleanupPeriodDays` setting). Walks Time Machine snapshots **newest-first**, restores anything missing from `~/.claude/projects/`, `~/.claude/image-cache/`, and `~/Library/Application Support/Claude/local-agent-mode-sessions/` (so Cowork's `audit.jsonl` trees come back too), **never overwrites** files that still exist, and supports `--dry-run` so you can review the plan before anything moves.

**Step 1 — grant Full Disk Access** (one-time):

System Settings → Privacy & Security → Full Disk Access → add (and enable) whichever terminal you'll run the script from (Terminal.app, iTerm, Ghostty, your IDE's terminal, etc.). **Quit and re-launch the terminal** so it picks up the new permission. Without FDA, even `tmutil` can't read the snapshot tree.

**Step 2 — find the canonical Time Machine path.** The user-friendly mount (e.g. `/Volumes/M3 Max Backups 5T`) is **not** what the script wants — modern macOS keeps snapshots under `/Volumes/.timemachine/<volume-uuid>/<machine-uuid>/`. Discover yours with:

```bash
tmutil destinationinfo   # shows mount points + UUIDs
tmutil latestbackup      # prints the full path to the newest snapshot
```

`tmutil latestbackup` prints something like:

```
/Volumes/.timemachine/52A1580A-…-6E0B20631733/A1B2C3D4-…/2026-05-15-153000.backup/Macintosh HD - Data
```

Pass the **parent of the dated directory** (the `<machine-uuid>` level) as `--tm-disk`.

**Step 3 — dry-run, then apply:**

```bash
# Preview what would be restored (no writes):
./utils/restore-deleted-sessions-and-images.sh \
    --tm-disk /Volumes/.timemachine/52A1580A-…/A1B2C3D4-… \
    --dry-run

# If the plan looks right, drop --dry-run to apply:
./utils/restore-deleted-sessions-and-images.sh \
    --tm-disk /Volumes/.timemachine/52A1580A-…/A1B2C3D4-…
```

**Safety contract:**
- **No-overwrite, three layers deep**: precheck `[ -e dest ]`, then `cp -n`, then a postcheck. Any file that still exists on the live filesystem is left alone.
- **Latest-copy-wins**: snapshots are walked newest-first; the first snapshot that contains a missing file is the one we restore from.
- **Idempotent**: a second `apply` run after the first is a no-op (everything's already there → all skipped).
- **Per-file errors are tallied, not fatal**: one unreadable file in one snapshot doesn't abort the run.

---

## Known Limitations

- **Session key expiry:** `sessionKey` eventually expires. Click **Refresh** in the sidebar to re-capture it — the UI launches the same Playwright login flow automatically. The CLI `claude-explorer capture` is the equivalent fallback.
- **Cloudflare cookie:** The `cf_clearance` cookie may be required on some networks. The fetcher attempts to capture it alongside `sessionKey`.
- **Truncated messages:** The API returns `truncated: true` for very long messages. A per-message full-content endpoint has not yet been identified.
- **Import:** There is no known write API for creating conversations. Migration to a new account is not currently possible programmatically.
- **Proxy capture commands:** The proxy-method launch command varies per OS. See [Method B](#method-b-proxy-interception---proxy) above for the macOS, Windows, and Linux equivalents.

---

## Tech Stack

| Layer | Technology |
|-------|-----------|
| Credential Capture | Playwright (browser login), mitmproxy (proxy interception) |
| Fetcher | httpx, curl_cffi |
| Backend | FastAPI, uvicorn, uv, weasyprint |
| Frontend | React 19, TypeScript, Vite, Tailwind CSS v4, shadcn/ui, TanStack Query |
| Search | SQLite FTS5 (primary), orjson + FileCache + ThreadPoolExecutor linear-scan fallback |
| MCP server | FastMCP (5 tools: list_sessions, list_projects, get_session_outline, get_messages, export_session) |
| Export | Markdown (built-in), PDF (weasyprint) |
| Packaging | hatchling (PyPI wheel includes pre-built React bundle) |

---

## Security

For vulnerability reporting and the running log of supply-chain audits against this project's dependency tree (most recently a check against the 2026-05-11 Mini Shai-Hulud worm that hit the `@tanstack/*` ecosystem), see [SECURITY.md](./SECURITY.md).

---

## Testing

All testing guidance lives in [TESTING.md](./TESTING.md): how to run the suites and trust the result, how to write tests, what CI proves on each platform, and how to verify macOS, Linux, and Windows by hand before a release. CI runs the Windows install path on both x86_64 and ARM64 on every push to the verification branch.

## Contributing

PRs welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for prerequisites, dev workflow, code style, and the CLA process. Coding agents (Claude Code, Codex, Cursor, and others) should read [AGENTS.md](./AGENTS.md) first.

## License

This project is licensed under the [Apache License 2.0](./LICENSE). Contributors agree to the [Contributor License Agreement](./CLA.md) (via [CLA Assistant](https://cla-assistant.io)) on their first pull request; the signature applies to all subsequent contributions.

See the disclaimer at the top of this README regarding Anthropic trademarks and the unaffiliated nature of this project.
