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.

the whole thing, start to finish
# 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"
output
[
  { "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.

nothing running in the background no database to set up no API key to start one folder to back up
nidus recall

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.

index anything you have
$ 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.

ask, and get passages back
$ 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.

or let it be your app's search endpoint
$ 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.

nidus code ingest

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).

read the repo, then ask it something
$ nidus code ingest . --dir ./store

$ nidus code search "where do we fsync the write ahead log" \
    --dir ./store
output
[
  { "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.

hand it to your assistant
$ 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.

nidus text-search

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.

say which fields are searchable, then search them
$ nidus set-fts-schema --dir ./store docs --field body --field title

$ nidus text-search --dir ./store body "crash fsync" \
    -k 3 --include-attr title
output
[
  {
    "id": "wal-commit",
    "score": 1.876369,
    "attrs": { "title": { "Str": "Write-ahead log" } }
  }
]
show me what matched
$ nidus text-search --dir ./store body "crash" \
    --highlight --fragment-chars 60
output
"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.

nidus search

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.

closest matches first
$ echo '[0.9,0.1,0.0]' | nidus search --dir ./store docs \
    -k 3 --include-attr title
output
[
  { "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" } } }
]
faster, smaller, or narrower: one flag each
# 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"}]}]'
or run both halves and merge the results
$ echo '[0.9,0.1,0.0]' | nidus hybrid-search --dir ./store \
    body "write ahead log" --text-weight 3 -k 5
no install required

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.

nidus, in your browser

search how do I keep data safe if the process dies

durability 0.848
recovery 0.651
storage 0.284

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.

before you commit to it

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.
nidus --help

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.

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