A Markdown vault, addressed by block
SkillDocs & knowledgeWork 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.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
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:
- An address survives edits. A line number stops being true the moment
anything above it changes.
#entitiesdoes not. - A write needs nothing read first. No whole page pulled into context to
build an
old_stringthat has to match exactly. - 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```dataviewblocks 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:
- Never
setthe 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. - Never write to an
@hashaddress.@…is a content hash and changes the moment the content does. Writes address#idonly. --bodyon a prose block appends instead of replacing. Match the flag to the kindgeml listreports:--bodyfor a heading, no--bodyfor prose. The wrong way round is refused on a heading and silent on prose.- Never
Writea page that already exists. Useset/add.Writeis for a page you are creating. - A repeated heading's address is a position. The second
## Addedis#added-1— the anchor GitHub gives it — and inserting another## Addedabove renumbers it. Take such an address from a freshgeml listright before the write. - Links resolve inside the resolution root, and no further.
[[Note]],[[Note#Heading]], aliases, embeds and#^blockresolve 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--rootnames 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
Related picks
Skill · larksuite
The pick for Markdownmarkdown-formatter
Skill · nvidia
The pick for Markdownhandoff
Skill · mattpocock
More in Docs & knowledgecanvas-design
Skill · anthropics
More in Docs & knowledgedoc-coauthoring
Skill · anthropics
More in Docs & knowledgepopups
Skill · coreyhaines31
More in Docs & knowledge