Semantic code search that runs entirely on your Mac. Point it at one repo or many, ask in plain English ("where do we validate JWTs?"), and get back the exact code, with file paths, line numbers, and symbol names.
No cloud. No code ever leaves your laptop. The chunking, embeddings, vector store, and search all run locally; the only "server" is a tiny embedding daemon that manages itself.
codeseekr index ~/work/api
codeseekr search "where do we validate JWTs?"- 🔒 Local-first. Everything runs on-device via Apple's MLX on Apple Silicon. No network at query time.
- 🧠 Understands meaning. It finds the right code even when your words don't match the source, by combining semantic (vector) and keyword (BM25) search.
- 📦 Zero setup. No
npm install, no build, no native compiling. Install and go. - 🤖 Built for agents. It ships as a Claude Code plugin and speaks JSON for scripts.
- ⚡ Fast. A warm model daemon keeps searches sub-second.
You need an Apple Silicon Mac, Node.js 24, and Python 3. That's it.
/plugin marketplace add bcilabs/codeseekr # one time
/plugin install codeseekr@mach-marketplace
Then just ask the agent to find something. It indexes the project on first use and searches it for you.
git clone git@github.com:bcilabs/codeseekr.git
alias codeseekr='node "$PWD/codeseekr/bin/codeseekr.js"'
codeseekr setup # one-time: provision the model
codeseekr index ~/work/api # index a repo (incremental on re-run)
codeseekr search "retry logic" -k 5 --repo apiRe-running index is always safe; only changed files are re-embedded.
1. Your code gets split into pieces. Codeseekr reads each file and cuts it along real code boundaries (a function, a class, a method) using tree-sitter. Each piece is called a chunk. This keeps related lines together instead of cutting through the middle of a function.
2. Each chunk gets turned into a list of numbers. A small AI model reads a chunk and outputs a vector: a long list of numbers that captures what the code means. Two chunks that do similar things end up with similar numbers, even if they don't share any words. This is what lets you search by meaning.
The model maps each input to a fixed-length vector, and it learns to place related things along consistent directions in that space. The classic illustration uses words: the step from King to Queen points the same way as Man to Woman, and every country sits a parallel hop from its capital. Codeseekr applies the exact same idea to code, so a function that retries failed requests lands near one that "backs off and tries again," even with no shared words.
3. Those vectors get placed in a "vector database." Think of every chunk as a dot in space, positioned by its numbers. Chunks that mean similar things land near each other, forming clusters. When you ask a question, your question becomes a dot too, and the search finds the nearest dots. The picture below shows what that space looks like once thousands of chunks are placed in it, similar things naturally group together:
4. At the same time, a keyword index is built. Vectors are great at meaning but blurry on exact spellings, so during indexing Codeseekr also feeds every chunk into a BM25 keyword index (an FTS5 full-text index over the chunk's code and its symbol name). BM25 is the classic ranking used by search engines: it splits text into tokens, then scores a chunk for a query by how often the query's terms appear in it (term frequency), damped so that rare, distinctive terms like RETRY_MAX_ATTEMPTS count for more than common ones, and normalized so long chunks don't win just by being big. This index is built once at index time and updated incrementally as files change, right alongside the vectors, so both ways of searching are ready before you ever type a query.
5. Your question is searched two ways at once. Codeseekr runs your query through both kinds of search and merges the results:
| Dense (vector) search | Sparse (BM25 keyword) search | |
|---|---|---|
| What it matches | Meaning and intent | Exact words and symbols |
| Good for questions like | "where do we validate JWTs?" — finds verifyToken() even though "JWT" never appears |
"find RETRY_MAX_ATTEMPTS" — finds the exact constant by name |
| Strength | Catches the right code when your words don't match the source | Nails exact identifiers, error strings, rare names |
| Weakness | Can miss an exact literal you typed verbatim | Misses anything phrased differently from the code |
6. The two result lists are fused into one. A step called RRF fusion (Reciprocal Rank Fusion) takes both ranked lists and combines them, so a chunk that scored well on either search rises to the top. That's what hybrid means: you get the meaning-matching of dense search and the exact-match precision of sparse search in a single ranked, de-duplicated result list. A question like "how is rate limiting configured?" benefits from both, dense search finds the throttling logic by concept, while sparse search locks onto the literal rateLimit config key.
The full story lives in the knowledge base: architecture, configuration, the CLI reference, the agent contract, and contributor docs.
- Getting Started: requirements and your first search
- Architecture Overview: the pipeline end to end
- CLI Reference: every command and flag
- Configuration: config files and environment variables
- Scripting and Agents:
--jsonoutput anddoctorexit codes - Claude Code Integration: how the plugin, skill, and hook fit together
- Building from Source: for contributors


