Obsidian Notes Cli
CLI that writes structured Markdown notes generated by Claude Code into an Obsidian vault.
#Obsidian Notes CLI
CLI that writes structured Markdown notes into an Obsidian vault — built so Claude Code (or any script) can persist documentation/decisions as notes you can browse and search natively in Obsidian. No custom viewer, no lock-in: it's just Markdown files with frontmatter, in a folder Obsidian already understands.
Part of a 3-repo integration:
obsidian-notes-cli(this repo, PHP / Laravel Zero) — writes the notes. Works standalone — the other two repos are optional conveniences on top.obsidian-claude-notes— Obsidian plugin: sidebar of Claude Code notes, command to trigger the CLI from inside Obsidian, live refresh.claude-code-obsidian-notes— Claude Code plugin (skill +/obsidian-notecommand) that shells out to this CLI.
#Requirements
- PHP >= 8.3 (only needed for the Composer install path — the
.pharis self-contained)
#Install
#Composer (recommended)
composer global require jeffersongoncalves/obsidian-notes-cli
#PHAR
Download obsidian-notes.phar from the latest release, then:
chmod +x obsidian-notes.phar
mv obsidian-notes.phar /usr/local/bin/obsidian-notes
Every release after the first is built and attached by CI (.github/workflows/release.yml) — the binary that ships is exactly what git log for that tag shows, nothing built out-of-band.
#Commands
| Command | What it does |
|---|---|
vault:init <path> |
Scaffolds .claude-notes.json and a Claude Notes/ folder in a vault, and saves it as the default vault. Safe to re-run — leaves an existing config file untouched. Pass --no-default to skip saving the default. |
vault:config [path] |
Saves a default vault path (~/.obsidian-notes-cli/config.json) so --vault / OBSIDIAN_VAULT can be skipped. No path shows the current default. |
note:create |
Writes a note. Body comes from stdin. |
note:list |
Lists notes with source: claude-code frontmatter in a vault, optionally filtered by --project. |
note:read <path> |
Prints a note's content with the YAML frontmatter block stripped — just the body. |
self-update |
Replaces the running binary with the latest release's .phar (PHAR installs only — no-op for Composer installs, which update via composer global update). |
#note:create options
| Option | Required | Notes |
|---|---|---|
--project= |
yes | Groups the note in the folder structure and in the Obsidian plugin's sidebar. |
--title= |
yes | Used to build the filename slug and stored in frontmatter. |
--tags= |
no | Repeatable: --tags=architecture --tags=backend. |
--vault= |
no* | Vault path. *Falls back to OBSIDIAN_VAULT, then the vault:config default — one of the three must resolve. |
#Usage
# One-time: scaffold .claude-notes.json + base folder in a vault obsidian-notes vault:init /path/to/vault # Write a note (body piped via stdin) echo "We chose SQLite over Postgres for the CLI cache." \ | obsidian-notes note:create --project=my-app --title="Decision: SQLite" --tags=architecture --vault=/path/to/vault # List notes generated by Claude Code obsidian-notes note:list --vault=/path/to/vault obsidian-notes note:list --project=my-app --vault=/path/to/vault # Read a note back as plain content (no frontmatter) — e.g. to feed another tool obsidian-notes note:read /path/to/vault/Claude\ Notes/my-app/2026-09-10-decision-sqlite.md # Update the CLI itself (PHAR installs) obsidian-notes self-update
Set OBSIDIAN_VAULT to skip passing --vault every time:
export OBSIDIAN_VAULT=/path/to/vault
Or persist it once with vault:config (handy so Claude Code/scripts never need to know the path):
obsidian-notes vault:config /path/to/vault # save obsidian-notes vault:config # show current default
Resolution order: --vault > OBSIDIAN_VAULT > vault:config default.
#The .claude-notes.json contract
A vault's .claude-notes.json (created by vault:init, or hand-written) is the single source of truth shared with the Obsidian plugin — both read the same file, nothing is duplicated:
{ "folderPattern": "Claude Notes/{project}/{date}-{slug}.md", "frontmatterDefaults": { "source": "claude-code", "tags": [] }, "bridgePort": 27124 }
folderPattern— where notes land. Placeholders:{project},{date}(YYYY-MM-DD),{slug}(from the title).frontmatterDefaults— merged into every note's frontmatter before the CLI's own fields (project,title,tags,created,updated) are added.createdis preserved across re-writes of the same path;updatedalways refreshes.bridgePort— where the CLI tries to reach the Obsidian plugin's optional local bridge (see below).
If no .claude-notes.json exists, the CLI falls back to config/notes.php's defaults — same values as above.
#The bridge
After writing a file, the CLI makes a best-effort POST http://127.0.0.1:<bridgePort>/notes to the Obsidian plugin, so its sidebar refreshes instantly. If the bridge is off (it's opt-in on the plugin side) or Obsidian isn't running, the call is silently ignored — the file is already on disk, and Obsidian picks up external changes natively the next time it gains focus. The CLI never depends on the plugin being installed.
#Development
composer install composer test # Pest + Pint composer phpstan composer build # produces builds/obsidian-notes locally (needs phar.readonly=Off)
#Windows note
Local PHAR builds (composer build / php obsidian-notes app:build) fail on a stock Herd/Windows PHP install because phar.readonly defaults to On. CI builds on Linux with phar.readonly=Off set explicitly, so releases are unaffected — this only blocks building a .phar on your own Windows machine.
#License
MIT.