A small, file-based CLI for editing TriliumNext notes via the ETAPI. Built for the fetch-edit-push loop that LLM-driven workflows (Claude Code, Cursor, Aider, plain shell scripts) live on, with drift detection and pre-overwrite snapshots so you don't lose notes to races or content-type quirks.
One script, stdlib only, no dependencies.
Install isolated via pipx or uv tool. Both give you an upgradable, versioned install and put trilium-note on $PATH.
# pipx (latest main)
pipx install git+https://github.com/io7/trilium-cli.git
# pipx (pinned to a tag)
pipx install git+https://github.com/io7/trilium-cli.git@v0.1.0
# uv tool
uv tool install git+https://github.com/io7/trilium-cli.gitUpgrade:
pipx upgrade trilium-cli
# or
uv tool upgrade trilium-cliUninstall:
pipx uninstall trilium-cli
# or
uv tool uninstall trilium-cliRequires Python 3.10+.
Both env vars are required:
export TRILIUM_API_URL=http://your-trilium-host:8080
export TRILIUM_API_TOKEN=<paste from Trilium → Options → ETAPI>Optional:
export TRILIUM_TMPDIR=/tmp/trilium # defaulttrilium-note search <query> [--limit N] # print "noteId<TAB>title" per line
trilium-note fetch <noteId> [--file PATH] # download to $TRILIUM_TMPDIR/<noteId>.html
trilium-note push <noteId> [--file PATH] [--force] # drift-checked push
trilium-note info <noteId> # title, type, dates, parents
trilium-note fetch J6JsaMQUq1bB
# …edit /tmp/trilium/J6JsaMQUq1bB.html in your editor / with Claude / sed / whatever…
trilium-note push J6JsaMQUq1bBfetch writes a <file>.meta.json sidecar capturing the server's blobId and utcDateModified at fetch time. push uses it for drift detection; delete it if you want a blind push, or use --force.
These are all lessons learned from corrupting a note once:
-
ETAPI PUT content-type is cursed.
PUT /notes/<id>/contentwithContent-Type: text/htmlreturnsHTTP 500 "Cannot set null content"and silently clobbers the note to the literal string[object Object]before the error is returned.trilium-notealways sendstext/plain— the body is still raw HTML, the server stores whatever bytes you send regardless of declared type. -
Drift detection on push. If the note was modified on the server between your fetch and your push — via the Trilium UI, another tool, another session — push refuses with exit code 2 and tells you to re-fetch. Use
--forceif you know what you're doing. -
Pre-overwrite snapshots. Every successful push first GETs the server's current content and saves it as
<file>.bak.<epoch>.htmlnext to the local file. There is always a restore point. No manual backups needed. -
Bad-content guard. Push refuses to upload an empty file or a file whose only content is
[object Object],null, orundefined. That's almost certainly a corrupted local buffer, not a real edit.
0— success1— configuration error (missing env var), missing file, bad content, HTTP error2— drift detected on push (use--forceto override)
MIT. See LICENSE.