obsidian-axi

August 11, 2026 ยท View on GitHub

A command line tool that lets coding agents work with your Obsidian notes.

It reads and writes the vault folder directly. No plugin to install, no server to run, no API key, and Obsidian does not even have to be open.

$ npx -y @andershoffmann/obsidian-axi
bin: ~/.local/bin/obsidian-axi
description: Agent ergonomic CLI for Obsidian vaults - read, search, write, and link notes.
vault:
  name: My Vault
  path: ~/Documents/My Vault
  notes: 214
  source: obsidian.json (open)
recent[5]{path,modified,ago}:
  Projects/Roadmap.md,2026-08-10,1d
  Inbox/Idea.md,2026-08-09,2d
  ...
help[3]:
  obsidian-axi search <query>
  obsidian-axi ls --recursive
  obsidian-axi tags list

Built to the AXI standard, which is a set of design rules for CLIs that agents drive.

Why not an MCP server?

An MCP server loads its tool definitions into the model's context on every single turn, and that cost adds up. A CLI does not, and it pipes into grep and head like anything else.

The Obsidian MCP servers also need real setup: a community plugin, a bearer token, and a running Obsidian. The most popular one does not currently install on a fresh machine, and four of its tools no longer work with the current plugin.

The trade is that anything needing a live Obsidian is out of scope here. No Dataview, no Templater, no semantic search, no command palette. In exchange it works everywhere, right away.

Install

Nothing to install. Just run it:

npx -y @andershoffmann/obsidian-axi <command>

If you want the setup hooks and update commands too, install it properly:

npm i -g @andershoffmann/obsidian-axi
obsidian-axi setup hooks

setup hooks wires a SessionStart hook into Claude Code, Codex and OpenCode, so an agent session opens with your vault summary already loaded. Running it twice is safe, and it does not touch your other settings.

The package also ships an agent skill at skills/obsidian-axi/SKILL.md. It is generated from the CLI's own help text, so it cannot drift out of date.

Needs Node 20 or newer.

Getting started

Run it with no arguments to see where you are:

obsidian-axi                            # your vault and its recent notes
obsidian-axi ls -r --sort modified      # every note, newest first
obsidian-axi read Notes/Roadmap.md      # one note, trimmed to 1000 characters
obsidian-axi read a.md b.md --metadata  # just frontmatter and counts
obsidian-axi search cosmosdb --context 2
obsidian-axi recent --days 7            # changed this week
obsidian-axi vault list                 # every vault Obsidian knows about

Every command takes --help.

One rule to remember: flags go after the command.

obsidian-axi vault info --vault "My Vault"     # yes
obsidian-axi --vault "My Vault" vault info     # no

Search takes a query, some filters, or both.

obsidian-axi search TODO --tag project --context 2
obsidian-axi search --tag work --modified-since 7d      # filters only, no query
obsidian-axi search --frontmatter status=active
obsidian-axi search "^## " --regex --path "Notes/**"

--tag project also matches #project/axi. You can repeat --tag and --frontmatter, and everything you pass has to match.

Writing

obsidian-axi write Inbox/idea.md --content "# Idea"
obsidian-axi append Notes/log.md --content "- [ ] ship it"
echo "- piped note" | obsidian-axi append Inbox/log.md
obsidian-axi rm Inbox/draft.md                  # goes to .trash, so you can get it back
obsidian-axi mv Inbox/draft.md Notes/final.md

patch changes one part of a note without rewriting the whole thing. It can target a heading's section, a block reference, or a frontmatter field.

obsidian-axi patch a.md --target-type heading --target Tasks --content "- [ ] new"
obsidian-axi patch a.md --target-type heading --target "Tasks::Today" --op prepend --content "- [ ] first"
obsidian-axi patch a.md --target-type frontmatter --target status=done --op replace
obsidian-axi patch a.md --target-type block --target abc123 --op delete

Two things worth knowing about patch:

  • When you append to a heading, the text lands in that section only. It will not slip into a nested subsection below it. To write inside ### Today, name it: --target "Tasks::Today". Using --op delete on a heading removes the heading and everything under it.
  • Your line endings and trailing newline are left alone, so editing the middle of a note gives you a one line diff and nothing else.

Re-running a write is safe. Appending text a note already ends with, deleting a note that is already gone, and moving a note onto itself all do nothing and exit 0.

obsidian-axi links "Roadmap.md"         # what it links to, and what links back
obsidian-axi links --broken             # links pointing at notes that do not exist
obsidian-axi links --orphans            # notes nothing links to
obsidian-axi tags                       # every tag, with a note count
obsidian-axi tags files project/axi

Links are resolved the way Obsidian resolves them: exact path first, then relative to the note doing the linking, then a matching filename anywhere in the vault, then frontmatter aliases. Attachments count too, so ![[Pasted image.png]] is not flagged as broken.

Tag counts roll up. #project includes notes tagged only #project/axi.

What Obsidian had open

obsidian-axi active                     # the note Obsidian last had focused
obsidian-axi open "Roadmap.md"          # ask Obsidian to jump to a note

active reads .obsidian/workspace.json. Obsidian only writes that file when the layout changes, not on every tab switch, so treat it as the last saved state rather than live. The output tells you how old it is.

Picking a vault

First match wins:

  1. --vault <name|path>
  2. OBSIDIAN_VAULT=<name|path>
  3. a .obsidian/ folder in the current directory or above it
  4. defaultVault in ~/.config/obsidian-axi/config.json
  5. the vault Obsidian has open
  6. the vault opened most recently

vault info tells you which rule applied, so you can check it picked the one you meant.

Output

Output is TOON, a compact format that writes lists as a header plus rows, using roughly 40% fewer tokens than JSON. Lists come with a count: line, long note bodies are trimmed to 1000 characters (use --full for all of it), and an empty result says so in words instead of printing nothing.

Errors go to stdout too, with a code you can match on and a hint for what to try next:

error: Note not found `Notes/Roadmap.md`
code: NOTE_NOT_FOUND
help[2]:
  obsidian-axi ls Notes
  obsidian-axi search Roadmap

Exit codes: 0 worked (including "nothing to do"), 1 something went wrong, 2 you typed it wrong.

If you use Obsidian Sync

Obsidian picks up outside edits and reloads the note. But if you have that note open with unsaved changes, Obsidian may overwrite what this tool just wrote. There is no way to detect that from the filesystem, so save or close the note first if it matters.

Writes go to a temp file and get renamed into place, so a note can never end up half written. And rm moves notes to .trash unless you pass --permanent.

Status

Under active development, but all 16 commands work: the dashboard, ls, read, search, recent, write, append, patch, rm, mv, links, tags, active, open, vault and setup, plus update from the AXI SDK.

It is fast because it does the simple thing. No cache, no index, everything synchronous. On a 103 note vault a full recursive listing takes about 88ms and a full text search about 120ms, both including Node startup.

Does it actually save tokens?

npm run bench measures each command against the shell command an agent would otherwise reach for. On a 103 note vault it returns 18% fewer characters overall, but that average hides most of the story:

taskobsidian-axiplain shell
search for a term57641340643%
read a note's metadata11151222%
read a note651512127%
list every note83696218135%
list tags637502127%

Searching is the real win, since grep returns every matching line and this does not. Reading a typical note actually costs more, because the median note here is shorter than the 1000 character trim limit, so nothing gets trimmed and the metadata is overhead. On a long note the same command lands near 5%. Listing costs more too, because each row carries more than a path.

And when it does cost more, it is often answering a better question. links --broken resolves real wikilinks, which no grep one liner can do.

bench/README.md has the full table, the caveats, and why a fair comparison against the Obsidian MCP servers is not possible right now.

License

MIT