Skip to main content

Fuzzy-find and resume any AI coding session across multiple AI assistants

Project description

fzf-ai

Fuzzy-find and resume any AI coding session across claude-code, codex, opencode, droid (factory), and pi from a single fzf picker.

┌──── AI coding sessions ──────────────────────────────────────────────────┐
│  claude    2026-04-17 03:16     6  ~/Work/fzfai        refactor preview  │
│▶ codex     2026-04-16 12:01    53  ~/Work/kimi-ai/...  ai-missions impl  │
│  pi        2026-04-16 11:16    30  ~/Work/kimi-ai/...  hi                │
│  droid     2026-04-04 17:47    20  ~/Work/droid-ai/... tui todo app      │
│  opencode  2025-08-11 20:03     2  ~/                   Greeting message │
└──────────────────────────────────────────────────────────────────────────┘

What it does

  • Indexes every session on disk:
    • claude ─ ~/.claude/projects/<proj>/<uuid>.jsonl
    • codex ─ ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl
    • opencode ─ the global sqlite db at ~/.local/share/opencode/opencode.db (tables session / message / part). Legacy per-project <repo>/.opencode/opencode.db files are deliberately skipped — those sessions are not resumable by current opencode and just created ghost rows.
    • droid ─ ~/.factory/sessions/<proj>/<uuid>.jsonl
    • pi ─ ~/.pi/agent/sessions/<proj>/<iso>_<uuid>.jsonl
  • Sorts newest-first and shows a rich ANSI preview of the real conversation (first real prompt, last user prompt, recent replies, tool calls, reasoning, model, cwd). Boilerplate environment / policy blocks are hidden from the preview when possible.
  • Smart-case exact matching by default across agent / cwd / title / hidden session content snippets. --exact keeps matches high-signal because the hidden content blob is a 4000-char concatenation of prompts where fuzzy matching would otherwise flood the picker with incidental letter-order hits. Recency breaks ties via --scheme=history + --tiebreak=index. Prefix a word with ' to fuzzy-match that term, !word to exclude, ^word / word$ to anchor, or a | b for OR.
  • Uses newer fzf features to make the picker behave like a small TUI:
    • reload-sync for clean initial load and reindex without flicker
    • change-nth + FZF_NTH to switch search scopes on the fly
    • click-header so the scope tags in the header are clickable
    • bg-transform-* + {*f} to render a live footer summary of matches
    • zero event to show a clear "no matches" hint with a recovery tip
    • --history for persistent query history between runs
    • --tmux so it opens in a popup automatically when run inside tmux
    • --id-nth for cross-reload tracking by session identity
    • --cycle for wrap-around list navigation
    • --keep-right so the title field stays visible on long rows
    • --exact + --scheme=history + --tiebreak=index so matches are literal (precise) and recency wins ties
    • --smart-case so queries only become case-sensitive when you type an uppercase letter
    • --accept-nth for clean output parsing without visible fields leaking
  • On enter, fzf exits cleanly, then the launcher execs the correct CLI from the session's original cwd. Using exec-after-exit (rather than fzf's become action) avoids a terminal-state race that caused codex to hang and opencode's input to freeze on hand-off.
    • claude --resume <id>
    • codex resume -C <cwd> <id> (explicit -C suppresses codex's interactive "change directory?" prompt)
    • opencode <cwd> --session <id> (passes the project dir as positional so opencode opens the correct db and finds the session)
    • droid --resume <id>
    • pi --session <path> (preferred) or pi --resume <id>

Install

Option 1: Using pip (recommended)

# Install from the source distribution
pip install fzf_ai-1.0.0.tar.gz
# Or install from PyPI when published
pip install fzf-ai

The pip installation will automatically add the scripts to your PATH if you have the Python bin directory in your PATH (typically ~/.local/bin or ~/Library/Python/3.x/bin).

Option 2: Using Make (traditional method)

# Clone or download the repository
cd fzf-ai
make install-symlink
# Make sure ~/.local/bin is in your PATH
export PATH="$HOME/.local/bin:$PATH"

Option 3: Manual installation

# Add the bin directory to your PATH
export PATH="$PWD/bin:$PATH"
# Or create symlinks to a directory in your PATH
ln -s "$PWD/bin/fzf-ai"          ~/.local/bin/fzf-ai
ln -s "$PWD/bin/fzf-ai-index"    ~/.local/bin/fzf-ai-index
ln -s "$PWD/bin/fzf-ai-preview"  ~/.local/bin/fzf-ai-preview
ln -s "$PWD/bin/fzf-ai-resume"   ~/.local/bin/fzf-ai-resume

Option 3: Manual installation

# put the scripts on your PATH
ln -s "$PWD/bin/fzf-ai"          ~/.local/bin/fzf-ai
ln -s "$PWD/bin/fzf-ai-index"    ~/.local/bin/fzf-ai-index
ln -s "$PWD/bin/fzf-ai-preview"  ~/.local/bin/fzf-ai-preview
ln -s "$PWD/bin/fzf-ai-resume"   ~/.local/bin/fzf-ai-resume

Requires: fzf ≥ 0.63, python3 (stdlib only), each AI CLI you want to resume on your $PATH.

Performance tuning

The indexer reads session stores in parallel across agents and within each agent. Set FZFAI_INDEX_JOBS=<n> to override the per-agent worker count. The default is min(32, cpu_count + 4).

PyPI Publishing

GitHub Actions trusted publishing is configured in .github/workflows/pypi-publish.yml.

Regular validation for pushes and pull requests runs in .github/workflows/ci.yml.

To enable it on PyPI, add a Trusted Publisher for project fzf-ai with:

  • owner: tuxcanfly
  • repository: fzf-ai
  • workflow: .github/workflows/pypi-publish.yml
  • environment: pypi

If fzf-ai does not exist on PyPI yet, create a pending publisher under your PyPI account with the same values.

Publishing flow:

git tag v1.0.0
git push origin v1.0.0

The workflow will build the sdist/wheel, run twine check, and publish to PyPI using GitHub OIDC instead of a long-lived API token.

Usage

fzf-ai                    # browse everything
fzf-ai claude codex       # only these agents

Search syntax (exact smart-case by default)

query meaning
noita contains noita
noita webgpu contains noita AND contains webgpu
'word fuzzy-match for this term (unquote)
^use starts with use
.md$ ends with .md
!draft excludes items containing draft
`a b

Queries are case-insensitive until you type an uppercase letter (--smart-case). Sessions are pre-sorted by last-modified time and --tiebreak=index preserves that order when match scores are equal. The default is --exact because the hidden content blob is long enough that fuzzy matching tends to return unrelated sessions whose letters happen to appear in the right order.

Keys

key action
enter resume session in its native CLI (cd to its cwd)
ctrl-o open a shell in the session's working directory
ctrl-e open the raw .jsonl / sqlite source in $EDITOR
ctrl-y copy session id to clipboard
ctrl-k copy the exact resume command to clipboard
ctrl-p move up
ctrl-n move down
ctrl-r rebuild the index in place
ctrl-s cycle search scope: all → cwd → title → content → agent
? toggle preview pane
alt-p cycle preview position / hide preview
alt-w toggle preview wrap
shift-up/down scroll preview by half a page
ctrl-shift-up/down scroll preview by a full page
pgup/pgdn page through the session list
alt-< / alt-> jump to first / last session
alt-1..5 filter to claude / codex / opencode / droid / pi
alt-0 clear the temporary agent filter

Scope Switching

The header is clickable. Click [all], [cwd], [title], [content], or [agent] to change what fzf searches against. The current scope is reflected in the prompt and in the dynamic list label.

Query History

Search history is persisted at:

${XDG_STATE_HOME:-~/.local/state}/fzf-ai/query-history

fzf-ai rebinds ctrl-p / ctrl-n back to list navigation, so query history remains available through fzf's alternate history actions only if you bind them yourself.

Footer Summary

The footer is computed asynchronously from the current match set using fzf's {*f} placeholder. It shows:

  • matched session count
  • distinct project count
  • summed message count
  • per-agent counts for the current match set

How it fits together

bin/fzf-ai            ─ bash launcher wiring advanced fzf bindings
bin/fzf-ai-index      ─ python: walks every session store, emits 9-col TSV
bin/fzf-ai-preview    ─ python: renders a conversation preview for fzf
bin/fzf-ai-resume     ─ bash:   cd into cwd and exec the right AI CLI
bin/fzf-ai-ui         ─ bash:   dynamic labels / scope switching / footer stats

The index format (TAB-separated):

1 agent(raw)   2 session_id   3 source      ← hidden, machine only
4 agent(ui)    5 updated      6 msgs        ← visible, padded + ANSI
7 cwd          8 title                       ← visible
9 search blob (hidden session content)      ← pushed past the right edge,
                                               used for content search only

fzf runs with:

--with-nth=4,5,6,7,8,9

That keeps columns 1-3 hidden from the list while still exposing:

  • transformed field 1 = agent
  • transformed field 4 = cwd
  • transformed field 5 = title
  • transformed field 6 = hidden content blob

So change-nth can dynamically retarget the search scope without rebuilding the list.

Notes

  • The indexer reads JSONL session stores in parallel across agents and within each agent. Set FZFAI_INDEX_JOBS=<n> to override the per-agent worker count.
  • The launcher now requires fzf >= 0.63.0 because it relies on reload-sync, footer sections, async transforms, and {*f}.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fzf_ai-1.2.0.tar.gz (32.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fzf_ai-1.2.0-py3-none-any.whl (29.1 kB view details)

Uploaded Python 3

File details

Details for the file fzf_ai-1.2.0.tar.gz.

File metadata

  • Download URL: fzf_ai-1.2.0.tar.gz
  • Upload date:
  • Size: 32.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fzf_ai-1.2.0.tar.gz
Algorithm Hash digest
SHA256 079e9bcc641083f8c6a2f972bf40201f1a074ecd873d5bf2fc0f098bd97693b2
MD5 ca96fd8177d89d125c4718a6164059e1
BLAKE2b-256 09f985ce9db369399e110d5a18be532b3b45d2197b3f3c1e138c944d1227cc14

See more details on using hashes here.

Provenance

The following attestation bundles were made for fzf_ai-1.2.0.tar.gz:

Publisher: pypi-publish.yml on tuxcanfly/fzf-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fzf_ai-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: fzf_ai-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 29.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fzf_ai-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 edf9e703a701a6515d84160bdaf1743422920aaedc60d3381c882f92b560d281
MD5 b7ff665288bbcd94461331a59684d7aa
BLAKE2b-256 6b5e5219dcde1cbe63685c9d8b1e61fdd4ee371d8cfae9d9b022aa1294e4ef81

See more details on using hashes here.

Provenance

The following attestation bundles were made for fzf_ai-1.2.0-py3-none-any.whl:

Publisher: pypi-publish.yml on tuxcanfly/fzf-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page