jeffersongoncalves / obsidian-notes-cli
CLI that writes structured Markdown notes generated by Claude Code into an Obsidian vault.
Package info
github.com/jeffersongoncalves/obsidian-notes-cli
Type:project
pkg:composer/jeffersongoncalves/obsidian-notes-cli
Fund package maintenance!
Requires
- php: ^8.3
Requires (Dev)
- guzzlehttp/guzzle: ^7.15
- jeffersongoncalves/laravel-zero-self-update: ^1.0
- laravel-zero/framework: ^12.0
- laravel/pint: ^1.25
- mockery/mockery: ^1.6
- pestphp/pest: ^3.8|^4.1
- phpstan/phpstan: ^2.1
README
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. |
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 # 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.
