Latin nidus, a nest
Point it at your files. Ask it anything.
nidus is a search engine you run as a single file. Give it documents, notes, a support archive or a codebase, and it finds the passages that answer a question, by meaning or by exact words. It is the retrieval half of a RAG app, the memory behind an agent, and a plain search box, all out of one folder.
curl -fsSL https://nidus.duckedup.org/install.sh | sh No Rust toolchain needed. It also works as a Rust library, if you would rather keep the index inside your own program.
# read a folder. no key, no model, no network call.
$ nidus ingest ./docs --collection docs --dir ./store \
--strategy markdown --fts-only nidus.text
# ask it something the way you would ask a colleague.
$ nidus text-search --dir ./store nidus.text -k 3 \
"how do we avoid losing data when the process crashes" [
{ "id": "guides/storage.md#3",
"score": 11.747257,
"attrs": { "nidus.source_path":
{ "Str": "guides/storage.md" } } },
{ "id": "guides/storage.md#0",
"score": 11.720629, "attrs": { … } },
{ "id": "guides/cli-and-server.md#19",
"score": 9.665971, "attrs": { … } }
] 27 markdown files became 758 searchable passages in 0.4 seconds, and the question landed on the durability guide.
Stop your model guessing. Hand it your data.
A model knows what it was trained on and whatever you paste in. Ask it about your product, your runbooks, or last quarter's tickets and it will invent something that reads perfectly and is wrong.
The fix is to look the answer up first and hand the model what you found. That lookup is the hard half, and it is the half nidus is: index your content once, ask in plain English, get back the few passages that actually answer it, each with the file it came from.
It works with any model and any framework, because what comes back is JSON from a command or an HTTP call. It also remembers: save something worth keeping, find it again weeks later by meaning rather than by filename.
$ nidus ingest ./handbook --collection docs --dir ./store \
--embed-provider voyage Markdown, PDFs exported to text, notes, transcripts, a support archive. Run it again and it skips every file you have not touched.
$ nidus recall docs "what is our refund window" \
--dir ./store --rollup 1 --neighbours 1 --rollup keeps the best passage per document and stitches its neighbours back on, so the model gets something readable instead of three overlapping fragments.
$ nidus serve --dir ./store --embed-provider voyage
$ curl localhost:8080/collections/docs/recall \
-d '{"query": "what is our refund window"}' These three need an embedding provider (Voyage, OpenAI, Ollama and others). The run at the top of this page needed none.
And it reads code as code, not as text.
Point it at a repository and it splits by function, struct and trait rather than by character count. Every result is a whole symbol with the lines it lives on, so you land on the thing instead of somewhere near it.
That makes it a good lookup table for a coding assistant, which has a hard limit on how much it can hold and blows past it on any real codebase. It asks in plain English, gets back a file and a line range, then opens that file for the real text. It can never quote a stale copy, because it was never handed a copy.
One line connects it to Claude Code, Cursor, or anything else that speaks MCP (the standard those tools use for plugging in extra abilities).
$ nidus code ingest . --dir ./store
$ nidus code search "where do we fsync the write ahead log" \
--dir ./store [
{ "path": "store/write.rs", "language": "rust",
"symbols": [
{ "symbol": "commit", "kind": "function",
"start_line": 136, "end_line": 148, "score": 10.09 },
{ "symbol": "maybe_sync", "kind": "function",
"start_line": 109, "end_line": 119, "score": 7.75 }
] }
] 138 files became 3,403 symbols in 1.7 seconds, with no API key, and it found the two functions that do the work.
$ claude mcp add nidus -- nidus mcp --dir ~/.nidus \
--embed-provider voyage --embed-model voyage-4 That is the entire integration. No SDK to install, no glue code to write.
Sometimes you know the exact words.
An error code. A ticket number. A function name someone said in standup. Searching by meaning is bad at those, because there is no meaning to match, only a string.
So nidus does ordinary keyword search too, ranked the way a good search box ranks things (BM25, the same scoring Elasticsearch uses). Same data, same command, no model and no API key anywhere near it. Ask it which words matched and it hands back the fragment with them marked.
$ nidus set-fts-schema --dir ./store docs --field body --field title
$ nidus text-search --dir ./store body "crash fsync" \
-k 3 --include-attr title [
{
"id": "wal-commit",
"score": 1.876369,
"attrs": { "title": { "Str": "Write-ahead log" } }
}
] $ nidus text-search --dir ./store body "crash" \
--highlight --fragment-chars 60 "fragments": [
{ "text": "before commit returns, so a crash loses at most the in",
"spans": [[28, 33]] }
] Character positions, so you can wrap the match in a <mark> without guessing.
And sometimes you only know what you meant.
Ask "how do we keep data safe if the process dies" and the right file comes back, even though it uses none of those words. That is the other half of nidus: it compares meaning rather than spelling (this is the vector search part, if that is the phrase you came here for).
It checks everything by default, which is quick at the size of a project on your laptop and always exactly right. When you outgrow that, one flag switches to a faster index that gives up a sliver of accuracy for a lot of speed, and another scans a compressed copy first and then double-checks the winners at full precision.
Filters run before anything is ranked, so narrowing a search makes it cheaper rather than more expensive. And one command runs both halves at once and merges the two rankings into a single list.
$ echo '[0.9,0.1,0.0]' | nidus search --dir ./store docs \
-k 3 --include-attr title [
{ "id": "wal-commit", "score": 1.0,
"attrs": { "title": { "Str": "Write-ahead log" } } },
{ "id": "compaction", "score": 0.32151973,
"attrs": { "title": { "Str": "Compaction" } } },
{ "id": "hnsw", "score": 0.13098952,
"attrs": { "title": { "Str": "HNSW index" } } }
] # trade a little accuracy for a lot of speed
$ echo '[0.9,0.1,0.0]' | nidus search --dir ./store docs --ann hnsw -k 5
# scan a compressed copy, then check the winners exactly
$ echo '[0.9,0.1,0.0]' | nidus search --dir ./store docs \
--quantization int8
# narrow it down before anything is ranked
$ echo '[0.9,0.1,0.0]' | nidus search --dir ./store docs \
--where '[{"Eq":["lang",{"Str":"rust"}]}]' $ echo '[0.9,0.1,0.0]' | nidus hybrid-search --dir ./store \
body "write ahead log" --text-weight 3 -k 5 Try it before you install it.
The terminal beside this is nidus itself, compiled to WebAssembly and
running in this tab. Type search and a question, or
help to see what else it takes.
One honest caveat: no browser ships a language model, so in here the words are turned into numbers by a crude stand-in rather than a real one. The index, the ranking and the scores are the actual thing.
search how do I keep data safe if the process dies
A real nidus, compiled to wasm and running in this tab. No browser ships an embedding model, so text becomes a vector by character trigrams plus a small concept map; the store, the ranking and the scores are the real thing. Data persists in OPFS, private to this origin.
Where it fits, and where it doesn’t.
Good fit
- A repo, a docs folder, or a pile of notes on your own machine.
- Giving a coding assistant a way to look through your code.
- A side project or an internal tool that needs real search.
- One service that owns its own index and does not want a second thing to operate.
Look elsewhere
- Billions of records spread across a cluster.
- A managed service with somebody else carrying the pager.
- Anything that should be your primary database. This sits beside one.
It is still one binary.
Everything on this page is in the same download. There are no editions, no
paid tier and no build flags to get right: the install script, the prebuilt
binaries and cargo install nidus all hand you the same file.
It is written in Rust with no C libraries underneath, which is why
installing it is a download rather than an afternoon.
Start it on a port and talk to it with fetch. Everything the library does has a URL, and there are JavaScript, Go and Python clients that wrap them.
nidus mcp Plug it into your editorOne line registers it with Claude Code, Cursor, or anything else that speaks MCP. Several editors can share one running copy.
--persistence s3://… Start local, move it laterThe same index works from a folder, from S3, or from Google Cloud Storage. Changing where it lives means changing one URL, not running a migration.
nidus backup Hard to breakKill it mid-write and it comes back clean: it loses at most the write that was in flight, and repairs itself when you reopen it. Backing up is copying a folder.
--rerank Squeeze the top of the listCast a wider net, then have a reranker read the query and each candidate together and reorder them. Better answers without scanning more.
wasm32 Runs in a browser tabThe whole thing compiles to WebAssembly and saves to browser storage. That is what the demo further up this page is running.
One command, and you’re searching.
No account, nothing to sign up for, nothing left running afterwards. Install it, point it at a folder, ask it something.
curl -fsSL https://nidus.duckedup.org/install.sh | sh