A file search toolkit for humans and AI agents. Really fast.
Typo-resistant path and content search, frequency-ranked file access, a background watcher, and a lightweight in-memory content index. Way faster than CLIs like ripgrep and fzf in any long-running process that searches more than once.
GroepOnline/pi-tools is GroepOnline's file search toolkit. The product we ship is the pi extension @groeponline/pi-tools. The same repository also contains the Neovim plugin, MCP server, Node/Bun SDKs, C library, Python bindings, and Rust crates, so every frontend shares one Rust core.
- Pi agent extension —
@groeponline/pi-tools - Packages — what we publish
- Carried components — MCP server, fff.nvim, Node/Bun SDK, Rust crate, C library, Python bindings
- Performance
- Repository layout
- Contributing · License
A pi extension that adds FFF-powered search tools alongside Pi's built-in find and grep. In the default tools-and-ui mode it also replaces the interactive editor's @-mention autocomplete with frecency-ranked results. The optional override mode additionally replaces Pi's built-in find and grep tool names with FFF implementations.
pi install npm:@groeponline/pi-toolsProject-local install:
pi install -l npm:@groeponline/pi-tools| Built-in tool | pi-tools replacement | Improvement |
|---|---|---|
find (spawns fd) |
fffind (FFF fileSearch) |
Fuzzy matching, frecency ranking, git-aware, pre-indexed |
grep (spawns rg) |
ffgrep (FFF grep) |
SIMD-accelerated, frecency-ordered, mmap-cached, no subprocess |
| (none) | fff-multi-grep (FFF multiGrep, opt-in) |
OR-logic multi-pattern search via Aho-Corasick |
@ file autocomplete (fd-backed) |
@ file autocomplete (FFF-backed, default) |
Fuzzy ranking from the FFF index and frecency |
Three operating modes, switchable at runtime with /fff-mode:
| Mode | What it does |
|---|---|
tools-and-ui (default) |
Adds ffgrep and fffind tools, replaces @-mention autocomplete with FFF. |
tools-only |
Only tool injection. Keeps pi's native editor autocomplete. |
override |
Replaces pi's built-in grep and find with FFF implementations. With PI_FFF_MULTIGREP=1, also registers multi_grep. |
Set PI_FFF_MULTIGREP=1 to opt in to fff-multi-grep (or multi_grep in override mode). Without it, only ffgrep and fffind are registered.
Env vars: PI_FFF_MODE, FFF_FRECENCY_DB, FFF_HISTORY_DB. Flags: --fff-mode, --fff-frecency-db, --fff-history-db. The databases default to your existing fff.nvim ones when present, otherwise ~/.pi/agent/fff/.
ffgrep. Content search. Acceptspath,exclude(comma, space, or array; leading!optional),caseSensitive,context, and cursor pagination. Auto-detects regex, falls back to fuzzy on zero exact matches, rejects.*-style wildcard-only patterns up front.fffind. Path and filename search. Matches the whole repo-relative path, not just the filename. Frecency-aware. The weak-match detector flags scattered fuzzy noise before it floods the agent's context.
/fff-mode [tools-and-ui | tools-only | override]. Show or switch the mode./fff-health. Picker, frecency, and git integration status./fff-rescan. Force a rescan.
Source: packages/pi-tools/. Full documentation: packages/pi-tools/README.md.
- We publish under the
@groeponlinenpm scope:@groeponline/pi-tools,@groeponline/fff-node,@groeponline/fff-bun. Releases are cut fromv*tags; seedocs/RELEASE.md. - CI. Build and publish runs on version tags and manual dispatch. The test matrix is Linux-only on push/PR and expands to the full 3-OS matrix on release tags.
- Native binaries. The Node/Bun SDKs load
@ff-labs/fff-bin-*platform packages from npm. We consume those packages; we do not republish them.
These frontends share the Rust core in this repository. Install the published @groeponline packages where they exist; otherwise build from source here.
A file search MCP server for Claude Code, Codex, OpenCode, Cursor, Cline and any MCP-capable client. Fewer grep roundtrips, less wasted context.
- Frecency memory, warm-up from git touch history.
- Definition-first hinting classified on the Rust side.
- Smart-case with auto-fuzzy fallback:
IsOffTheRecordfinds snake_case variants; zero-match queries retry as fuzzy. - Git-aware annotations for modified, untracked and staged files.
Installers: install-mcp.sh and install-mcp.ps1. Prebuilt binaries: GitHub Releases. Source: crates/fff-mcp/.
curl -fsSL https://raw.githubusercontent.com/GroepOnline/pi-tools/main/install-mcp.sh | bashA Neovim file picker built on the same Rust core: fuzzy + frecency + git-aware ranking, live grep, preview, multi-select and quickfix.
{ 'GroepOnline/pi-tools', build = 'make build' }Config reference: :help fff.nvim. Source: lua/ + crates/fff-nvim/.
TypeScript wrapper over the C library. Build custom agent tools, CLIs or IDE integrations.
npm install @groeponline/fff-node
# or
bun add @groeponline/fff-nodeimport { FileFinder } from "@groeponline/fff-node";
const finder = FileFinder.create({ basePath: process.cwd(), aiMode: true });
if (!finder.ok) throw new Error(finder.error);
await finder.value.waitForScan(10_000);
const files = finder.value.fileSearch("incognito profile", { pageSize: 20 });
const hits = finder.value.grep("GetOffTheRecordProfile", { mode: "plain", smartCase: true });
// 10-100x faster glob matching than Bun's and Node's implementations
const rustFiles = finder.value.glob("**/*.rs", { pageSize: 100 });
finder.value.destroy();Every method returns a Result<T> ({ ok: true, value } | { ok: false, error }). Type reference: packages/fff-node/src/types.ts.
FFF is written in Rust, so this is the lowest-overhead way to use it. Use the workspace crate from this repository:
[dependencies]
fff-search = { git = "https://github.com/GroepOnline/pi-tools" }Source: crates/fff-core/.
Stable C ABI. Bind from C/C++, Zig, Go via cgo, Python via ctypes, or anything with C FFI.
make build-c-lib
# or: cargo build --release -p fff-c --features zlobThe zlob feature (requires the Zig toolchain) switches glob matching and filesystem traversal to zlob's native parallel walker. The output is a cdylib (libfff_c.so / .dylib / fff_c.dll); the header lives at crates/fff-c/include/fff.h. Source: crates/fff-c/.
Build from this repository with uv:
cd packages/fff-python
uv sync --all-extras
uv run maturin develop --releasefrom fff import FileFinder
with FileFinder("/path/to/project", watch=False) as finder:
finder.wait_for_scan_blocking(timeout_ms=5000)
result = finder.search("main")
for item, score in zip(result.items, result.scores):
print(f"{item.relative_path}: {score.total}")
hits = finder.grep("class Profile", mode="plain", before_context=1, after_context=1)ripgrep and fzf are great CLI tools, but every invocation forks a new process, re-reads .gitignore, re-stats directories and rebuilds state before it can answer. FFF keeps the index and file cache resident in one long-lived process and exposes the same Rust core through every layer. On a 500k-file Chromium checkout that is the difference between 3–9 seconds per ripgrep spawn and sub-10 ms per FFF query.
- No process spawn. Every call stays in-process.
- Typo-resistant matching. Smith-Waterman fuzzy scoring on the grep path; SIMD-accelerated fuzzy matching (from the frizbee core) for paths, surviving dropped characters and reorderings.
- Persistent memory. Directory tree, git status, frecency and content index stay warm between searches.
FFF keeps its index in RAM: about 360 bytes per indexed file for the content index (≈36 MB for a 100k-file repo). On a 14k-file repo the resident footprint is ≈26 MB. Binaries, oversized files and non-grep-able files are skipped; the index can be memory-mapped instead of anonymous RAM.
If you run one grep from a shell, rg is still the right tool. If you run dozens inside one process, FFF pays for itself from the second call.
Performance claims are reproducible: run scripts/benchmark-compare.sh (pinned workloads, records tool version + commit + host, writes a JSON artifact) and link that artifact in any PR that changes the numbers below.
- ripgrep — same regex engine, better plain-text matching, resident content index. Wins on repeated-search workloads, loses on "grep once from bash".
- fzf — FFF is fuzzy like fzf, but also frecency-aware, git-aware and more typo-tolerant.
- Telescope / fzf-lua / snacks.picker — FFF ships its own picker on the same core.
- Tantivy / full-text engines — different class: Tantivy persists an inverted index for document scoring at scale; FFF is scoped to one repository and optimised for sub-10 ms response.
crates/fff-core- Rust core: index, watcher, frecency, scoring.crates/fff-grep- SIMD content search.crates/fff-query-parser- Query constraint parsing.crates/fff-c- C FFI library used by every language binding.crates/fff-mcp- MCP server binary.crates/fff-nvim- Lua/mlua bindings for the Neovim plugin.crates/fff-python- Python bindings (maturin).packages/fff-node- Node.js SDK (@groeponline/fff-node).packages/fff-bun- Bun SDK (@groeponline/fff-bun).packages/pi-tools- pi extension (@groeponline/pi-tools).packages/fff-python- Python package sources.packages/fff-bin-*- Platform binary package layouts (@ff-labs/fff-bin-*on npm; consumed, not republished here).lua/- Neovim plugin code.doc/- vimdoc.
Bug reports and pull requests welcome at GroepOnline/pi-tools. Agentic coding tools are welcome, but human review is mandatory. Keep code in line with the rules in AGENTS.md. Release process: docs/RELEASE.md.
MIT.
There is intentionally no single canonical definition. Pick your favourite:
- Fast File Finder
- Fuzzy File Finder
- will search Files For Food
The brand hex is #F87216, not #FFF. Logo variants: orange · dark · light.
