A Markdown vault, addressed by block

SkillDocs & knowledge

Work a Markdown knowledge base, an Obsidian vault, a wiki, a docs tree, by BLOCK ADDRESS instead of by whole page. Use when the job is to find which page and which section says something, read that one section, add a line to an index or a log, or rewrite one section of a note: `geml find` answers with `file#address`, `geml get` hands back that block alone, and `geml set` / `geml add` change it in place while every other block, and the YAML frontmatter, stay byte-for-byte identical. Wikilinks, callouts and embeds are written through verbatim, so the file stays the Markdown Obsidian renders. No Obsidian process, no REST API, no plugin required. Triggers on: wiki, vault, knowledge base, second brain, ingest a source, update the index, append to the log, find which note says, edit one section of a note.

Use A Markdown vault, addressed by block in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add A Markdown vault, addressed by block and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the A Markdown vault, addressed by block skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

A Markdown vault, addressed by blockStart free

What this skill tells your AI

The instructions your AI receives, as published by geml-spec/geml in integrations/obsidian/skills/geml-vault/SKILL.md and read by Ahel’s review.

The pages stay Markdown. Nothing is converted. What changes is how you reach into them: an address (#entities) instead of a line number, and a write that touches one block instead of rewriting a file.

This buys three things grep + Read + Edit cannot give you:

  1. An address survives edits. A line number stops being true the moment anything above it changes. #entities does not.
  2. A write needs nothing read first. No whole page pulled into context to build an old_string that has to match exactly.
  3. It works offline. No Obsidian running, no Local REST API, no API key.

Read: locate, then take one block

geml find '<literal text>' <dir> --head   # → file ⇥ #address ⇥ the matching line
geml get  <file> '#address' --body        # → that block, nothing else
geml list <file>                          # → every address in one page

find walks a directory for *.geml and *.md. It skips hidden directories — a vault that hides sources in .raw/ must name that directory explicitly.

Read a whole page only when list shows it is short enough that taking one block saves nothing. Most notes are; index, log and cache pages are not, and those are where this skill earns its place.

Write: change one block

geml list <file>                          # ALWAYS first — addresses AND the kind column
geml set  <file> '#heading' --body --in - # a HEADING: --body replaces the section body
geml set  <file> '#prose-addr' --in -     # a PROSE block: NO --body, or it appends
geml add  <file> --after '#id' --in -     # insert a new block after one
geml add  <file> --append   --in -        # append to the end (log pages)

--body and the block's kind must agree. On a heading it means "the section below the heading line"; a prose block has no body of its own, so --body writes after the prose instead of over it — silently, exit 0. geml list prints the kind in its second column, which is why it comes first.

A write is re-parsed before it lands: a change that would break the document is refused, and nothing is written.

What you get back, verified by test/vault.test.mjs:

  • frontmatter and every block you did not address are byte-for-byte unchanged;
  • > [!tip] callouts, [[wikilinks]], ![[embeds]] and ```dataview blocks are written verbatim, unescaped — the page still renders in Obsidian.

The rules that keep this safe

Read references/invariants.md before the first write in a session. The six that bite hardest:

  1. Never set the frontmatter block. Its closing --- lives inside the block body; replacing the body deletes it and the page loses every property. Frontmatter is read-only through this skill.
  2. Never write to an @hash address. @… is a content hash and changes the moment the content does. Writes address #id only.
  3. --body on a prose block appends instead of replacing. Match the flag to the kind geml list reports: --body for a heading, no --body for prose. The wrong way round is refused on a heading and silent on prose.
  4. Never Write a page that already exists. Use set / add. Write is for a page you are creating.
  5. A repeated heading's address is a position. The second ## Added is #added-1 — the anchor GitHub gives it — and inserting another ## Added above renumbers it. Take such an address from a fresh geml list right before the write.
  6. Links resolve inside the resolution root, and no further. [[Note]], [[Note#Heading]], aliases, embeds and #^block resolve by note name in any folder under it; a note not yet written is a warning, not a refusal. The root is the page's own folder unless --root names the vault — from a subfolder, pass it, or every link into a sibling folder warns.

Rules 1, 2 and 3 are silent, which is why they have to be rules rather than error messages you would see. Rules 5 and 6 refuse nothing; they are about reading an address, and a warning, correctly.

Using this with an existing vault convention

references/claude-obsidian.md maps this onto the claude-obsidian plugin's vault: its directory layout, the stable addresses in its index.md, which of its skills' steps to replace, and the one file that must never be edited with a tool.

Any other vault works the same way; the reference is an example, not a requirement.

Block history, when you want it

Off unless asked for. geml history save <page> snapshots a page and geml revert <page> '#id' rolls back one block. The cost is one <page>.md.gemlhistory sidecar per edited page, inside a directory that Obsidian syncs and git tracks.

If the vault is already in git — most are, and claude-obsidian commits it on a hook — this is a second, partial history of the same thing. Leave it off.

The link graph

scripts/vault-graph.mjs reports orphans and dead wikilinks, each dead link carrying the block address that holds it — so the fix is a geml set on that address, not a hunt through the page.

node integrations/obsidian/scripts/vault-graph.mjs <vault-dir> [more-dirs…]

Signals

GitHub stars
28
Forks
7
Last commit
Oct 2026
Advanced
Item type
skill
Key
geml-vault
Source
github.com/geml-spec/geml