npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frsilva77/findex

v0.3.2

Published

Hybrid (BM25 + semantic) code search over local repos, backed by SQLite (FTS5 + sqlite-vec), exposed to Claude Code via MCP.

Readme

findex

Hybrid (BM25 + semantic) code search over local repos, backed by a local SQLite file (FTS5 + sqlite-vec), exposed to Claude Code as an MCP tool.

No setup, service, or Docker container is needed: everything runs via npx, the index lives at $HOME/.findex/db, and the embedding model is downloaded to $HOME/.findex/models on first use.

Requirements

Node 22 or newer — but you do not have to make it your default.

If you run findex on an older Node, it looks for a Node 22+ you already have installed (fnm, nvm, volta, asdf, n, Homebrew) and re-runs itself on that one, printing which it picked. Nothing to switch, nothing to pass:

$ node --version
v20.20.2
$ npx -y @frsilva77/findex install-skill
findex requires Node 22 or newer — findex is on Node v20.20.2. Re-running it on Node v22.14.0 (found via fnm):
  /Users/you/.local/share/fnm/node-versions/v22.14.0/installation/bin/node

Only when no Node 22+ can be found anywhere does it stop, with the upgrade command for the version manager you actually use.

The MCP server gets the same treatment, pinned once at install time rather than re-resolved on every launch: install-skill registers it against the absolute path of an installed Node 22+ whenever it finds one. That matters because Claude Code spawns the server from its PATH — a GUI app never sources the shell rc file that applies your fnm/nvm shim, so its node can be the old system one even when your terminal shows v22. Use --node <path> to pin a specific binary yourself. With no version-managed install to point at, it falls back to plain npx and the server's own startup check.

See ADR 0016.

How to use

1. Index a repo

npx -y @frsilva77/findex index /absolute/path/to/some-repo

Re-run the same command any time to pick up changes — unchanged files are skipped via content-hash comparison. Repeat for every repo you want Claude to be able to search; they all live in the same shared index.

2. Register the MCP server (and install the skill)

npx -y @frsilva77/findex install-skill

That registers the MCP server in your user scope — so it works in every project — and installs the findex Claude Code skill described in step 3. Re-running it is a no-op. npx -y @frsilva77/findex uninstall-skill reverses both, leaving the index alone.

Useful flags: --dry-run (print what would happen), --no-mcp (skill only), --force (overwrite an edited skill file), --index <path> (also index a repo, repeatable), --server-name <name> (register under a different name), --node <path> (pin the server to a specific Node 22+ binary).

If you'd rather edit your MCP config by hand, this is the equivalent entry. Prefer the pinned form below unless you know the npx this resolves to is on Node 22+ in Claude Code's environment: a hand-written config skips the install-time check, and the server can only re-exec itself onto a newer Node if it finds one installed. Note that findex-mcp is a bin inside the @frsilva77/findex package, not a package of its own, so it needs --package:

{
  "mcpServers": {
    "findex": {
      "command": "npx",
      "args": ["-y", "--package=@frsilva77/findex", "findex-mcp"]
    }
  }
}

Pinned form — an absolute Node 22+ plus npm's own npx-cli.js, so nothing is resolved through PATH. This is what install-skill writes for you:

{
  "mcpServers": {
    "findex": {
      "command": "/Users/you/.local/share/fnm/node-versions/v22.14.0/installation/bin/node",
      "args": [
        "/Users/you/.local/share/fnm/node-versions/v22.14.0/installation/lib/node_modules/npm/bin/npx-cli.js",
        "-y", "--package=@frsilva77/findex", "findex-mcp"
      ]
    }
  }
}

This exposes one tool, search_code, with params:

| param | type | default | |---------|-----------------------------------|----------| | query | string (required) | — | | repo | string (optional) | all repos| | mode | "hybrid" | "semantic" | "bm25" | "hybrid" | | limit | number | 5 | | detail | "compact" | "full" | "compact" |

Results come back as plain text, one block per hit — repo · file:startLine-endLine · symbolName followed by the code. "compact" previews the first 8 lines of each hit and tells Claude where to read the rest; "full" returns the whole matched chunk. Overlapping chunks of the same file are fused into one result, and the response is capped so a single search can't flood the conversation (see ADR 0011).

npm run bench:tokens measures what a response costs, against whatever is already indexed — useful when tuning limit, detail, or the chunk size cap.

3. Ask Claude

Step 2 also installs a Claude Code skill at ~/.claude/skills/findex/. Without it, agents tend to fall back on Grep/Glob loops out of habit; the skill tells Claude to reach for search_code first, to read the exact line ranges it returns instead of whole files, and when grep genuinely is the better tool. It triggers on its own, or you can invoke it explicitly with /findex.

Once one or more repos are indexed, you can ask Claude conceptual questions about the code:

Where do we validate webhook signatures?

Claude calls search_code({ query: "webhook signature validation" }), gets back ranked chunks, and can answer by reading the specific files/line ranges returned instead of grepping the whole codebase.

Where do we retry failed background jobs?

How does my-service calculate rate limits? (scope to one repo: search_code({ query: "rate limit calculation", repo: "my-service" }))

You can also call search_code directly via the MCP tool in Claude Code without phrasing it as a question — pass the query/repo/mode/limit params explicitly if you want a specific search strategy (e.g. mode: "bm25" to force an exact-term lexical match).

Optional: per-repo config

Copy findex.config.example.json to findex.config.json inside the repo you're indexing, to extend/force-include ignore patterns or file extensions, or tune chunk size.

How it works

  • Indexing (CLI, manual, one repo at a time): walks a local folder, respects .gitignore + a default ignore list, chunks TS/JS files by AST symbol (function/class/method) via tree-sitter and everything else by fixed-size sliding window, embeds each chunk locally with @huggingface/transformers, and stores it in a single shared SQLite database (all indexed repos live in one file, distinguished by a repo column). Re-running the index command only re-embeds files whose content hash changed.
  • Search (MCP tool, called by Claude): runs BM25 (SQLite FTS5) and vector similarity (sqlite-vec) search in parallel and fuses the two ranked lists with Reciprocal Rank Fusion. Returns {file, startLine, endLine, snippet, score} per match.

The MCP server never triggers indexing itself — if a repo isn't indexed yet or looks stale, it tells you which CLI command to run.

See docs/adr/ for the reasoning behind every major design decision.

Development

Working on findex itself (not just using it):

npm install
cp .env.example .env
npm run typecheck
npm test

This repo uses npm — there is no yarn lockfile, and engine-strict in .npmrc enforces the Node version in engines.

.env optionally overrides the embedding model name or the SQLite index location — see .env.example. Run against source directly with npm run index index <path> / npm run mcp, without building first.

Releasing

Releases are automatic: every pull request merged into main publishes a new version of @frsilva77/findex to npm, tags it, and creates a GitHub release (.github/workflows/release.yml). There is no manual publish step.

How big the bump is comes from the labels on the pull request:

| Label | 0.2.1 becomes | Use for | | --------------- | --------------- | ------------------------------------------ | | (none) | 0.2.2 | the default — fixes, deps, small changes | | release:patch | 0.2.2 | same as the default, stated explicitly | | release:minor | 0.3.0 | new features, new CLI flags or MCP tools | | release:major | 1.0.0 | breaking changes for existing users | | release:skip | nothing | docs- or CI-only changes worth no release |

The highest label wins if more than one is applied. A commit pushed straight to main belongs to no PR and releases a patch.

The workflow runs npm run typecheck and npm test before publishing, and commits the bumped package.json back to main as chore: release vX.Y.Z [skip ci], so package.json always reflects the latest published version.

Two things still need a human:

  • If src/core/schema.sql changed shape, bump SCHEMA_VERSION in src/core/db.ts in the same PR. It is versioned independently of the package on purpose (see ADR 0013) — most releases should not touch it.
  • Choosing the label. Note that since the MCP config and docs float @latest (no pinned version), a merge is live for every user within minutes.

See ADR 0017 for why labels rather than Conventional Commits.