Point B2 at a folder of Markdown and it becomes a second brain that reads everything, lets you search it, and surfaces the notes most similar in meaning for you to connect.
This guide takes you from an empty checkout to a working vault you can search and
grow — about ten minutes, most of it a one-time model download. Your .md files stay
plain and yours the whole way; B2 only ever adds a hidden index folder and (with your say-so) a
typed link in a note's frontmatter.
You'll need a Rust toolchain (rustup.rs) to build the
b2 binary — B2 ships as source today, as one static binary. macOS and Linux are the
tested platforms.
One thing is optional, and it's local — B2 makes no network calls and needs no account or API key at any point:
b2 init) of a local
model that powers semantic search and connection discovery. Skip it and run in an offline
keyword-only mode instead (shown below). Everything runs entirely on your machine.b2 CLIClone the repo and build the release binary, then alias it so b2 is on your path
for this shell:
$ git clone https://github.com/AlteredCraft/B2 && cd B2 $ cargo build --release -p b2-cli $ alias b2=./target/release/b2 $ b2 --help B2 — explore a Markdown vault's typed graph and search from the terminal
-C <path> (or --vault) to point at your vault — or set
B2_VAULT_PATH once so every command finds it without the flag — and
--json to emit machine-readable output for agents and scripts. The examples below
run from inside the vault folder, so read-only commands default to .; commands
that write (reindex, add, mv, link) need an
explicit vault (a path, -C, or B2_VAULT_PATH), so they can't silently touch the
wrong directory.Semantic search and connection discovery run on a local embedding model — no account, no
API key, no network at query time. b2 init downloads and verifies the model into a
shared cache once — it is not re-downloaded per vault, and there is never a surprise
download mid-command later.
$ b2 init Installed 'bge-base-en-v1.5' (768 dims). Run `b2 reindex` to embed your vault.
B2_EMBEDDER=fake. Search still works on
keywords (BM25), but the semantic half is switched off — the CLI tells you so, and never pretends
a result is semantic when it isn't.A vault is nothing more than a folder of Markdown files. Point B2 at notes you already have, or start fresh — there's no import step and no proprietary format to convert into.
Just write Markdown files. Here are two — a concept and a note that connects to it. The frontmatter is minimal; the body is plain prose:
# ~/vault/concepts/memory.md
---
type: concept
title: "Human memory"
created: 2026-07-03
---
The brain encodes, stores, and retrieves information.
# ~/vault/notes/spaced-repetition.md
---
type: note
title: "Spaced repetition"
created: 2026-07-03
---
Spaced repetition exploits the retrieval curve.
## Relations
- elaborates [[concepts/memory|Human memory]] — applies the forgetting curve
That last line is a typed connection: the verb elaborates, a
wikilink [[concepts/memory|Human memory]] to the target, and a "why". A bare
[[path|title]] anywhere in prose is an untyped references link. The verbs
come from a small closed vocabulary — references, relates,
elaborates, supports, refutes, contradicts,
example-of, part-of, supersedes,
derived-from — the same palette you'll pick from with b2 link.
b2 add notes/spaced-repetition --title "Spaced repetition" --content "…" writes a
valid note and indexes it in one step (so you can skip the reindex in step 5 for
notes you create this way). The .md extension is optional; parent folders are created
for you.Nothing is hidden — the files stay ordinary Markdown that work in Obsidian, or anything else, with no B2 running. After indexing (next step), that first note gains exactly one line B2 manages:
--- b2id: 01KWMCW242E0WSKVYDYNQBT7SN # set by B2 on first index — a stable id, never changes type: note title: "Spaced repetition" created: 2026-07-03 --- Spaced repetition exploits the retrieval curve. ## Relations - elaborates [[concepts/memory|Human memory]] — applies the forgetting curve
b2id the first time it
indexes a note, and — only when you run b2 link (step 8) — it appends a typed link
to a frontmatter relations: block. Connections you write yourself live in the body and
are labelled inline; ones you commit with b2 link live in frontmatter and
are labelled frontmatter. That's the whole of B2's write surface.b2 reindex reads every note, stamps any missing b2id, and builds the
searchable index. It's how B2 catches up to files you've created or edited by hand:
$ cd ~/vault $ b2 reindex . Indexing /Users/you/vault embedding 2/2 · notes/spaced-repetition.md (2 chunks) Indexed 2 notes (2 embedded, 2 stamped)
Run it again after editing notes — it's incremental: unchanged notes keep their vectors, so only what actually changed is re-embedded. Two flags help:
b2 reindex --dry-run — preview what would happen (writes nothing, not
even a b2id).b2 reindex --force — re-embed everything from scratch..b2/ folder inside your vault. It's disposable — delete it and the next
reindex rebuilds it identical from your Markdown. Nothing durable lives outside your
notes. Add .b2/ to your .gitignore if the vault is a git repo.One command, hybrid ranking — it fuses keyword (BM25) and semantic (vector) matches, so you find notes by wording and by meaning:
$ b2 search "how does forgetting work" 0.0328 Spaced repetition (notes/spaced-repetition.md) Spaced repetition exploits the retrieval curve. 0.0161 Human memory (concepts/memory.md) The brain encodes, stores, and retrieves information.
Use --limit N to widen or narrow the result set.
Follow the connections around any note. neighbors is a quick list of what a note
links to and from; explain adds each connection's why and its provenance:
$ b2 neighbors notes/spaced-repetition → elaborates Human memory (concepts/memory.md) — applies the forgetting curve $ b2 explain concepts/memory Human memory (concepts/memory.md) [b2id 01KWMCW242E0WSKVYDYNQBT7SN] Connections: ← elaborated-by Spaced repetition (notes/spaced-repetition.md) [inline] why: applies the forgetting curve
You can pass a note as either its vault-relative path or its b2id. When nothing
points at a note, explain flags it as an orphan — surfaced for you to notice,
never auto-changed.
This is the point of B2. b2 similar <note> surfaces the notes most
semantically similar to a given one that you haven't linked yet — a pure, instant read over
the vectors reindex already stored. No model call, no network, no cost: the
ranking and the "∖ already-connected" exclusion happen locally over your whole vault.
$ b2 similar notes/spaced-repetition 0.71 Cramming works (notes/cramming.md) Massed practice yields only short-term recall. 0.63 The forgetting curve (concepts/forgetting-curve.md) Retention decays exponentially without review.
Each row is a similarity score, the note's title and path, and the passage that made it similar.
The note itself and anything already linked to it never appear. It lists about ten by default;
--limit N widens or narrows that.
Pick the connections worth keeping and commit them. Either write a [[link]] in your
note's body yourself, or run b2 link:
$ b2 link notes/spaced-repetition notes/cramming --type contradicts --explanation "massed vs. spaced practice" Linked spaced-repetition → cramming (contradicts). Wrote the typed relation into the source note's frontmatter.
b2 link <src> <dst> appends the typed relation to the source
note's frontmatter relations: — Markdown-first, never the body. --type
picks the verb from the closed vocabulary (defaults to references);
--explanation records the "why". Re-run b2 similar afterward and a note you
just linked drops off the list — it's connected now, so the exclusion filters it out.
Edit notes in whatever you like — B2 is a layer over your files, not a place you have to live in. When you rename or reorganize, let B2 keep the graph intact:
$ b2 mv concepts/memory concepts/human-memory Moved concepts/memory.md → concepts/human-memory.md Rewrote 1 inbound link(s) across 1 file(s).
Because connections key on the immutable b2id, a move never breaks a backlink —
B2 also repairs the human-readable link text in every note that pointed at it. Refactor fearlessly:
move, split, merge, rename. After a batch of hand-edits, a quick b2 reindex catches
everything up.
The mental model in one line: your Markdown is the only source of truth. The index is a disposable cache you can drop and rebuild identically from your notes — nothing durable exists that your Markdown can't reconstruct. Nothing locks you in.
| Command | What it does | Model? |
|---|---|---|
b2 init | Download + verify the embedding model (one-time, per machine). | downloads it |
b2 add <path> | Create a note (--title, --content) and index it. | embeds |
b2 reindex | Re-project the vault; incremental. --dry-run, --force. | embeds |
b2 search <query> | Hybrid keyword + semantic search. --limit N. | embeds query |
b2 neighbors <note> | List a note's typed links, in and out. | no |
b2 explain <note> | Every connection with its "why" + provenance; flags orphans. | no |
b2 mv <from> <to> | Move/rename a note and repair every inbound link. | re-embeds touched |
b2 similar <note> | Surface the semantically nearest notes you haven't linked yet. --limit N. | reads vectors |
b2 link <src> <dst> | Commit a typed relation into the source note's frontmatter. --type, --explanation. | re-embeds note |
Zero-config is the happy path — everything above works with no config file. When you want to tune,
a single optional TOML at ~/.config/b2/config.toml configures the embedder:
# ~/.config/b2/config.toml (all optional) [embedder] model = "bge-base-en-v1.5"
| Environment variable | Effect |
|---|---|
B2_VAULT_PATH | Vault root, so commands find it without -C. An explicit -C/--vault overrides it. Read-only commands fall back to the current dir; commands that write (reindex/add/mv/link) require it explicitly. |
B2_EMBEDDER=fake | Offline mode: deterministic non-semantic embedder. Search runs keyword-only. |
B2_DEBUG | Print internal error detail after the generic user-facing message. |
reindex first. similar reads the vectors a prior
reindex stored, so index before you discover. And under B2_EMBEDDER=fake
the semantic half of search and similarity is off — great for offline exploring, not for real recall.