claude-browse
Resume software work across Claude Code, CodeX, Gemini, and Copilot from the terminal, then continue it in Claude, CodeX, Gemini, Copilot, or Cursor. Interactive fzf browser with a restart-card preview pane, full-text search across folders and first messages, provider-aware native resume, and target-app browsers that open everything in Claude, CodeX, Gemini, Copilot, or Cursor by default.
[demo GIF goes here — 10–15s: open, filter, preview, resume]
claude-browse
Find thread where...
45m ago team-ops 22msg finalize pre-flight smoke tests ###abc…
3h ago claude-br 7msg roadmap for shipping v1 ###def…
Apr 19 sales 14msg draft proposal for acme co ###ghi…
Apr 17 web 3msg why is signup failing on safari ###jkl…
...
By default: no network, no accounts, no API calls. It reads local session history from
~/.claude/projects/, ~/.codex/, ~/.gemini/tmp/, and
~/.copilot/session-state/, then reconstructs enough task state to help you
keep moving instead of dropping you into a stale transcript. Cursor is
currently a launch target, not a local session source.
Install
With pip (recommended once the package is on PyPI)
pip install claude-browse
From source
git clone https://github.com/fortytwode/claude-browse.git
cd claude-browse
./install.sh
External dependency — fzf
claude-browse, codex-browse, gemini-browse, copilot-browse, and cursor-browse use
fzf for the interactive UI. Install it once
via your system package manager:
brew install fzf # macOS
sudo apt install fzf # Debian / Ubuntu
sudo dnf install fzf # Fedora / RHEL
sudo pacman -S fzf # Arch
sudo apk add fzf # Alpine
Requirements
- Python 3.9+
- fzf (for
claude-browse,codex-browse,gemini-browse,copilot-browse, andcursor-browse) - Claude Code, CodeX, Gemini, GitHub Copilot CLI, and/or Cursor Agent CLI
- Optional experimental providers can be loaded from local Python modules or local provider directories
Usage
Interactive TUI
claude-browse # most recent 100 sessions, opens everything in Claude
codex-browse # most recent 100 sessions, opens everything in CodeX
gemini-browse # most recent 100 sessions, opens everything in Gemini
copilot-browse # most recent 100 sessions, opens everything in Copilot
cursor-browse # most recent 100 sessions, opens everything in Cursor
claude-browse --all # every session you've ever run
codex-browse --here # only sessions started in the current directory
claude-browse --relocate # force-resume the chosen thread in the current dir, even if it's the thread's own folder
claude-browse --no-canonicalize # accepted for compatibility; canonicalization still happens at index time
claude-browse --web # open a local browser tab to read full past transcripts and scan sessions
Web viewer
claude-browse --web
Opens a local-only browser tab (bound to 127.0.0.1, no accounts, no outbound
network calls) alongside the usual fzf picker -- not a replacement for it.
Use it when you actually want to read a past conversation: a sidebar lists
sessions (current folder first, searchable, "this folder only" toggle) and
selecting one renders the full thread -- opened at the latest exchange,
scroll up for history -- with fenced code blocks and an in-thread search
box. This is prose only (user/assistant text) -- tool calls, file edits,
and command output aren't captured. --here scopes the whole server to the
current folder; --all widens the sidebar past the default 100 sessions.
Requests from any Host other than 127.0.0.1/localhost are rejected
(DNS-rebinding protection). Ctrl-C in the terminal shuts the server down.
Scripting the web viewer (JSON API)
The page is backed by three JSON endpoints you can call directly (the server prints its URL to stderr on startup; the port is OS-assigned):
| Endpoint | Returns |
|---|---|
GET /api/meta |
{"here_only_forced": bool} -- whether --here scoping is forced server-side |
GET /api/sessions?q=<query>&here=1 |
{"sessions": [...]} -- session list; q runs the same ranked search as the fzf picker, here=1 scopes to the launch folder. Each session carries session_id, provider, provider_name, folder, cwd, title, first_msg, last_msg, msg_count, timestamp, last_timestamp, and a preformatted when |
GET /api/session/<sid> |
{"meta": {...}, "turns": [{"role", "text"}, ...]} -- the full prose transcript, newlines preserved |
Errors come back as JSON too: 404 (unknown session/route), 403 (foreign
Host header), 500 (unreadable transcript), 503 (search index mid-rebuild
-- retry shortly). For non-HTTP scripting, the same primitives are plain
Python imports: claude_browse.fts.list_recent / sessions_for_cwd /
search_ranked / get_by_sid, and
claude_browse.providers.get_provider(p).transcript_turns(path, sid).
While the TUI is up:
| Key | What it does |
|---|---|
| Type | Write a short sentence about the thread you want, or use exact names / phrases |
| ↑ ↓ | Move between sessions |
| Shift-↑ Shift-↓ | Scroll the preview pane |
| Enter | Resume the selected thread in the app you launched (claude-browse, codex-browse, gemini-browse, copilot-browse, or cursor-browse) in yolo mode. If the thread is from a different folder than where you launched, it relocates automatically — grafting its context into a fresh session in your current directory instead of yanking you back to the origin folder. |
| Ctrl-O | Resume immediately, bypassing the multiline-paste safety guard |
| Ctrl-T | Re-enter the matched topic in a fresh session in that app |
| Ctrl-S | Open in that same app in safe mode |
| Ctrl-Y | Print the suggested next prompt for the selected thread |
| Ctrl-B | Print the restart card for the selected thread |
| Ctrl-H | Print a reusable handoff brief |
| Ctrl-U | Print a concise status update |
| Esc | Quit |
Examples:
-
Sentence-style query examples:
where i was asking about teammate feedbacklast closeout session for clientbrand brief where we questioned the opportunities"runna sca2"when you know the exact phrase already
-
In
claude-browse, a Claude thread resumes natively in Claude and CodeX or Gemini threads start fresh Claude sessions with imported context. -
In
codex-browse, a CodeX thread resumes natively in CodeX and Claude or Gemini threads start fresh CodeX sessions with imported context. -
In
gemini-browse, a Gemini thread resumes natively in Gemini and Claude or CodeX threads start fresh Gemini sessions with imported context. -
In
copilot-browse, a Copilot thread resumes natively in Copilot and Claude, CodeX, or Gemini threads start fresh Copilot sessions with imported context. -
In
cursor-browse, Claude, CodeX, and Gemini threads start fresh Cursor sessions with imported context. -
The UI is meant to encourage sentence-style recall, not one- or two-word pecking. You should feel comfortable typing a short description like
where i was asking about teammate feedback. -
Enteris the normal open key again. There is now a short paste guard: if a pasted long or multiline quote just changed the query, the first Enter arms the selection and the second Enter opens it.Ctrl-Ostill opens immediately. -
The picker now shows an interpreted-query tip row at the top, for example
Looking for: client + closeoutorLooking for threads about: brand. -
If your query is too vague, the picker tells you to add one anchor like a person, client, brand, or folder instead of pretending the search is confident.
-
Result rows now show trust/provenance tags like
primary subject,folder match,title match,opening match,mentioned later,feedback,critique,closeout, ordriftedso you can tell why a hit surfaced before opening preview. -
Descriptive queries are reduced to the most specific anchors under the hood, so
find me the thread where i was asking about teammate feedbackbehaves like a thread-recall query, not a hard AND over filler words. -
Descriptive queries now separate anchor terms from intent words, so
last closeout session for clienttreatsclientas the hard anchor andlast / closeoutas ranking signals instead of weighting every word equally. -
Descriptive queries now use local concept cues for things like closeout, feedback, critique, and human-performance review, so the ranker can still prefer the right exchange when the exact wording differs.
-
Search now prioritizes the most recent relevant mention of your query, not only the thread's latest unrelated activity.
-
The preview pane now starts with a
Why this surfacedblock: match type, match time, match confidence, and best action (EntervsCtrl-T), then shows the last matching exchange and whether the thread later drifted to another topic. -
Ctrl-Tis the honest cross-provider answer to thread drift: it starts a new session anchored on the matched exchange instead of pretending the tool can rewind the original thread in place. -
Ctrl-Ylets you emit the suggested next prompt without launching anything.Ctrl-Bprints the restart card itself for copy/paste or handoff. -
Ctrl-Hprints a fuller handoff brief with restart state, reopen intent, and recent turns.Ctrl-Uprints a shorter status update you can paste into Slack, notes, or a standup. -
Cross-provider open is not a true native resume. It creates a new session seeded from the old thread.
-
Cross-folder open auto-relocates: native
claude --resume <id>only works from a thread's own project folder, so selecting a thread from a different directory now grafts its context into a fresh session in your current directory instead of chdir-ing you back (or failing with "No conversation found" when the origin is gone). Same-folder threads still resume natively. Use--relocateto force this even for a thread's own folder. -
Cursor is currently a target-only built-in provider. It opens everything in Cursor, but this tool does not yet claim to index Cursor-origin CLI sessions.
Optional Dense Embeddings
The default search stack is local only: exact URL/page ID matching, weighted FTS, segment windows, and a local TF-IDF-style semantic window index. If you want paraphrase-level recall, you can opt into dense embeddings while keeping storage and retrieval local:
export CLAUDE_BROWSE_DENSE_EMBEDDINGS=1
export OPENAI_API_KEY=...
Optional knobs:
export CLAUDE_BROWSE_EMBEDDING_MODEL=text-embedding-3-small
export CLAUDE_BROWSE_EMBEDDING_DIMENSIONS=256
export CLAUDE_BROWSE_EMBEDDING_BATCH_SIZE=64
export CLAUDE_BROWSE_DENSE_MIN_SCORE=0.25
When enabled, claude-browse embeds local transcript windows through the
embeddings API and stores the resulting vectors in the same local SQLite
cache. Query embeddings are cached locally too. It does not use a hosted vector
store or File Search. Exact URL/page ID search still runs first.
Privacy/cost boundary: transcript window text and search queries are sent to
the embedding API only when CLAUDE_BROWSE_DENSE_EMBEDDINGS=1 is set. With
the default text-embedding-3-small model, the current local corpus measured
around 10.75M overlapping-window tokens, which is roughly $0.21 to embed at
$0.02 per 1M tokens.
Why
Claude Code already has claude --resume, CodeX has codex resume, Gemini
has gemini --resume, and Copilot has copilot --resume, but all four are
provider-local pickers. claude-browse, codex-browse, gemini-browse,
copilot-browse, and cursor-browse are better at three things:
- Thread recall across all your sessions, not just the last few. Type any thread description, person, folder, client, or phrase and recall the exact old thread you want.
- Recover work state before you resume. The preview pane reconstructs the
current task, topic drift, repo status, last meaningful ask, latest
assistant progress, suggested next prompt, and a provenance block that says
why this result surfaced and whether
EnterorCtrl-Tis the better move. - Re-enter an earlier topic honestly. When topic A is buried inside a thread that later drifted to B/C/D, the browser can start a fresh session anchored on the matched exchange instead of faking a mid-thread native rewind.
- Choose the target app up front. Launch
claude-browseif you want to work in Claude,codex-browseif you want to work in CodeX, orgemini-browseif you want to work in Gemini,copilot-browseif you want to work in Copilot, orcursor-browseif you want to work in Cursor. When the source app differs, the browser writes a compact import brief and starts a fresh session in the target app instead of pretending cross-vendor native resume exists.
If you live in tmux and start a lot of agent sessions across different
projects, this is the tool.
Cross-machine setup (Mac ↔ Linux)
If you sync ~/.claude/projects/ between a Mac and a Linux box (Syncthing,
rclone, etc.), session cwds recorded on one machine won't match the other
(/Users/<name> vs /home/<name>). By default the browsers
canonicalize both to $HOME, so the same project shows up once, not
twice. --no-canonicalize is still accepted for compatibility, but it no
longer changes display behavior because canonicalization now happens at index
time.
For custom path aliases (corporate devcontainers, Windows drives, etc.), set an environment variable:
export CLAUDE_BROWSE_PATH_ALIASES="/workspaces/repo=$HOME/repo"
# multiple pairs separated by :
export CLAUDE_BROWSE_PATH_ALIASES="/Volumes/Work=$HOME/work:/mnt/c/code=$HOME/code"
Short folder names
If your sessions all live under a monorepo, you can set
CLAUDE_BROWSE_FOLDER_PREFIXES to strip common prefixes from the folder
column:
export CLAUDE_BROWSE_FOLDER_PREFIXES="monorepo/apps/:monorepo/lib/"
Experimental external providers
Built-in providers are still the supported path, but the registry can now load additional providers from local Python modules via:
export CLAUDE_BROWSE_PROVIDER_MODULES="my_pkg.my_provider"
or from plugin-style directories of .py files via:
export CLAUDE_BROWSE_PROVIDER_DIRS="$HOME/.config/claude-browse/providers"
Each external provider module or file must expose:
from claude_browse.providers.base import (
PROVIDER_API_VERSION as API_V1,
ProviderSpec,
)
PROVIDER_API_VERSION = API_V1 # optional today, but recommended
PROVIDER = ProviderSpec(...)
The contract is intentionally experimental:
- It may change between releases
- There is no compatibility promise or marketplace yet
- External providers can be source-capable (index local sessions), target-capable (launch target only), or both
- Directory-loaded providers are regular local Python files, not sandboxed plugins
For target-capable external providers, you can use either:
claude-browse --target my-provider
or a thin shim/symlink named my-provider-browse that points at
claude-browse.
To inspect what loaded successfully without opening fzf:
claude-browse --list-providers
That prints built-in vs external providers, source/target capability, binary availability, experimental status, and auth state when a provider reports one.
Agent Board (live session status + notifications)
Turns every Claude Code session into a tracked, auto-named thread with a
live state (working / idle / needs-input / gone / ended), shown in
your statusline, pushed as a native macOS notification (with sound) on
completion or when blocked, and (optionally) mirrored to Firestore + a
private Slack channel so you can see every session across multiple
machines in one place.
Native notification titles are formatted as [folder] Model state (for
example [claude-browse] Codex needs input or [team-operations] Opus done) so the repo and model are visible even when macOS truncates the
message body.
Notification persistence: the banner plays a sound (your System
Settings > Sound > Alert sound), but still auto-dismisses after a few
seconds by default -- that auto-dismiss timing is a per-app Notification
Center setting, not something this code can set programmatically. To make
it stay on screen until you dismiss it: System Settings > Notifications >
find the app that's actually registered as the sender (likely "Script
Editor", or whichever terminal app you run Claude Code in -- Terminal,
iTerm2, etc.; check both if unsure) > set Alert Style to "Alerts" instead
of "Banners". The Slack #agent-status board is the durable fallback if
you miss the banner entirely -- it never auto-dismisses.
Setup (one machine):
./install.sh
This idempotently wires hooks + a statusLine command into
~/.claude/settings.json (backing it up first; safe to re-run), symlinks
agent-board, disables Claude Code's built-in folderless push
notifications so Agent Board is the only local alert source, and reports:
- whether hooks/statusLine were already wired (skips if so)
- the local notification setting (
agentPushNotifEnabled, kept false to avoid duplicate lower-information banners) and how many of your recent sessions already have anai-title-- the namer only calls Haiku for the rest) - live Firestore + Slack connectivity (
agent-board sync check)
Add this to your shell rc (not done automatically) for work <name>
(tmux attach-or-create) and aj (board glance):
source "/path/to/claude-browse/shell/agent-board.zsh"
Cross-laptop sync (optional): requires the board-sync extra and
Firestore/Slack creds. Without it, the local loop (statusline,
notifications, aj) still works fully -- sync just no-ops and logs to
~/.claude/agent-board/sync.log.
python3 -m venv .venv
./.venv/bin/pip install -e ".[board-sync]"
Firestore uses Application Default Credentials (gcloud auth application-default login). Point it at YOUR project via
CLAUDE_BROWSE_BOARD_PROJECT, CLAUDE_BROWSE_BOARD_DATABASE, and
CLAUDE_BROWSE_BOARD_COLLECTION (defaults preserve existing installs). Slack
needs SLACK_BOT_TOKEN -- set in your environment, or in
~/team-operations/.env (auto-detected as a fallback, since hooks run
with a minimal inherited environment that usually won't have it exported).
Rolling out to a second laptop:
git pullthis repo on the second machine.- Run
./install.shthere -- same idempotent wiring, own localstate.db. - Set up
board-sync+ creds the same way if you want that machine's sessions on the shared board too. - Both machines' sessions appear together, grouped by hostname, in the
#agent-statusboard and viaagent-board board(which only reads the local machine'sstate.db-- Slack is the cross-machine view).
Troubleshooting
Search diagnostics are written locally to:
~/.claude/cache/claude-browse-search.log.jsonl
Each line records the query, ranker, elapsed time, result count, and top result
metadata/snippets. This is the closest equivalent to a server log for the local
fzf workflow. Set CLAUDE_BROWSE_LOG=0 to disable it, or
CLAUDE_BROWSE_LOG_PATH=/path/to/log.jsonl to move it. The log rotates at
5 MB by default; override with CLAUDE_BROWSE_LOG_MAX_BYTES.
fzf: command not found
Install fzf via your package manager (see Install section above).
No sessions found
You haven't run claude, codex, gemini, or copilot yet — or your sessions are in a
non-standard location. The browsers read ~/.claude/projects/,
~/.codex/sessions/, ~/.codex/state_5.sqlite, ~/.codex/history.jsonl,
~/.gemini/tmp/, and ~/.copilot/session-state/. If yours live elsewhere,
file an issue.
Original folder no longer exists
The directory you ran that session from has been deleted or moved. You can
still resume with the native command (claude --resume <session-id>,
codex resume <session-id>, gemini --resume <session-id>, or
copilot --resume <session-id>) manually from any cwd.
Resume opens but the session looks empty
The session file may be in a different encoded-directory than Claude Code
expects for the current cwd. See the cross-machine section for context. A
proper fix is on the roadmap as part of the claude-sync companion tool.
How it works
Claude Code writes each session as a JSONL file under
~/.claude/projects/<encoded-cwd>/<uuid>.jsonl. CodeX writes canonical
session JSONL transcripts under ~/.codex/sessions/, with thread metadata
in ~/.codex/state_5.sqlite and user-turn history fallback in
~/.codex/history.jsonl.
Gemini stores project-scoped chat JSON under
~/.gemini/tmp/<project>/chats/session-*.json plus aliases in
~/.gemini/projects.json. Copilot stores each session in
~/.copilot/session-state/<session-id>/ with an events.jsonl transcript and
workspace.yaml metadata. The browsers normalize all four into one local
SQLite index, then hand that to fzf. Search combines exact identifier lookup
for URLs/page IDs, weighted FTS, segment-window matching, a local
TF-IDF-style semantic window index for natural-language recall, and optional
local dense-vector retrieval when explicitly enabled. When you pick a thread,
the tool cds back to the original cwd, rebuilds a restart card from the
local transcript plus current repo state, and then either launches the native
resume command for the target app or creates a Markdown import brief and starts
a fresh cross-provider handoff session.
Without optional dense embeddings, no data leaves your machine. No telemetry. No API calls. The core runtime remains stdlib Python.
See ROADMAP.md for what's planned, what's out of scope, and
the direction for the paired claude-sync and claude-browse-web projects.
Contributing
Small, focused PRs welcome. See CONTRIBUTING.md for how to run tests and what's in/out of scope.
License
MIT — © 2026 Shamanth Rao
Related work and future products
This is the free, local, single-machine tool — including --web, which is a
local-only reading surface for this machine's sessions (it binds to
127.0.0.1, serves your own indexed history, and works offline). The paid
companion products (cross-device sync + hosted mobile/web browsing across
all your machines + AI search across sessions) are a different surface:
they follow your sessions across devices without a terminal on each one.
They're tracked in ROADMAP.md. If you want to know when they
ship, star the repo or open a discussion — a waitlist will go up close to
launch.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_browse-1.3.0.tar.gz.
File metadata
- Download URL: claude_browse-1.3.0.tar.gz
- Upload date:
- Size: 193.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b01aac8cf60dcf6ba9a1ed646b708ed048ce3ca54667615bec51c2f1c5d00a9b
|
|
| MD5 |
1b19792dc04a05daec8add90f514dee8
|
|
| BLAKE2b-256 |
31546cf66a62e02062c0955d3b62f0ed22482400eccffd893c385139a5d63633
|
Provenance
The following attestation bundles were made for claude_browse-1.3.0.tar.gz:
Publisher:
publish.yml on fortytwode/claude-browse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_browse-1.3.0.tar.gz -
Subject digest:
b01aac8cf60dcf6ba9a1ed646b708ed048ce3ca54667615bec51c2f1c5d00a9b - Sigstore transparency entry: 2405946391
- Sigstore integration time:
-
Permalink:
fortytwode/claude-browse@db39442cf0559f12b7ef20854e2da01ed4591912 -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/fortytwode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db39442cf0559f12b7ef20854e2da01ed4591912 -
Trigger Event:
release
-
Statement type:
File details
Details for the file claude_browse-1.3.0-py3-none-any.whl.
File metadata
- Download URL: claude_browse-1.3.0-py3-none-any.whl
- Upload date:
- Size: 131.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d846f2bcc33b09ae19bcfc6662ab9aa046ae819ca95a4d5cb31069ac3ddc6e9
|
|
| MD5 |
6acf3dffff9044504075723fab5f2fde
|
|
| BLAKE2b-256 |
20809dda5607665b28c8e856280237b526dc7557ac583f82ac8882c41b4ee54c
|
Provenance
The following attestation bundles were made for claude_browse-1.3.0-py3-none-any.whl:
Publisher:
publish.yml on fortytwode/claude-browse
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_browse-1.3.0-py3-none-any.whl -
Subject digest:
2d846f2bcc33b09ae19bcfc6662ab9aa046ae819ca95a4d5cb31069ac3ddc6e9 - Sigstore transparency entry: 2405946411
- Sigstore integration time:
-
Permalink:
fortytwode/claude-browse@db39442cf0559f12b7ef20854e2da01ed4591912 -
Branch / Tag:
refs/tags/v1.3.0 - Owner: https://github.com/fortytwode
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@db39442cf0559f12b7ef20854e2da01ed4591912 -
Trigger Event:
release
-
Statement type: