
Research
/Security News
PolinRider Spreads Through Compromised GitHub Accounts and Packagist
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.
@geml/logseq-sync
Advanced tools
Continuously sync a Logseq DB graph to a Git-friendly folder of readable plain-text GEML files — the watcher half of the Sync Vault with GEML plugin. Built on the official @logseq/cli export; writes only files that changed, commits scoped strictly to the
Your Logseq DB graph as continuously synced plain-text files — pages and journals back in readable files and folders, the way OG vaults felt, kept in step with the database. And, when you want it, back again.
Edit a block; seconds later the file on disk has caught up, and the toolbar says so. Two settings, and only the first usually needs touching.

Logseq 2.x DB graph ⇄ .logseq-sync-vault-with-geml/*.geml two-way, LOSSLESS
→ pages/*.md journals/*.md one-way, LOSSY
The GEML tree is the source of truth: restore and --two-way read only it,
and npm test proves EDN → GEML → EDN is a structural identity. The Markdown
tree is a copy for reading — Logseq's own file-version dialect, so the
folder opens in the file version of the app, but typed properties, tags, tables
and data blocks have no OG shape and do not survive it. An edit to a .md file
never reaches the graph, and the sync refuses to overwrite one rather than
pretending otherwise.
That gap is the whole reason this exists. Logseq ships both ends of it —
logseq export gives Markdown (readable, lossy), logseq export-edn gives EDN
(lossless, not something a person edits). GEML is the point between: as
readable as the one, as lossless as the other, and addressable, so a tool
can edit one block instead of round-tripping the graph.
--two-way, the reverse toologseq-sync restore imports the vault into a graph, merging by block uuidPages you delete in Logseq are kept on disk and reported — a plain folder
has no history to recover them from. --mirror asks for an exact copy instead.
1. Install the plugin — from the marketplace, or the zip from the latest release (it carries the built plugin; nothing to compile).
2. Set the vault folder — Settings → Plugins → Sync Vault with GEML →
Vault folder. Any folder: ~/logseq-vault, a directory in a repository you
already keep, one your backup tool watches. Created if missing. There is
deliberately no default.
3. Run the watcher:
npx @geml/logseq-sync
That is the setup. With no arguments it works out the rest — the CLI inside the
Logseq app, the graph the app has open, the plugin's signal file, the vault path
you just set — makes the vault a git repository if it is not one, syncs, and
keeps watching. logseq-sync --help lists the flags; the ones you are likely to
want are --two-way, --once, --mirror, --no-markdown and --graph.
Not sure it is wired up? npx @geml/logseq-sync doctor prints what it found
and what is missing, and exits non-zero when the setup cannot sync.
The GEML tree is laid out the way an OG vault is; Logseq's file-graph indexer walks past the dot directory, so the Markdown at the top is what it sees.
<vault>/
pages/<name>.md journals/2025_02_20.md OG Markdown. Opens in Logseq
(file version). Lossy, one-way.
.logseq-sync-vault-with-geml/ The source of truth.
ontology.geml :properties/:classes as `data {format=edn}`
graph.geml page ORDER, an addressable data block
journals/2025_02_20.geml journal pages, OG date names
pages/<name>.geml one per page:
block title → `=== text` body
block uuid → `{#uuid}` ← geml get/set address
outline tree → flat blocks with `.level-N`
everything else → `data {format=edn}`
.geml-manifest.json what this tool last wrote, so a stranger's edit
is distinguishable from its own echo
The vault is ordinary text, and that is the point: agents, scripts and plain
sed all work on it, and none of them needs to know Logseq exists. With
--two-way running, an edit imports on the next cycle; without it, run
logseq-sync restore when you are ready.
An agent gets addressed, validated block edits from the
geml MCP server — geml mcp --root <your-vault-dir> --no-history. The flag matters: git is this vault's history,
and without it every write also saves a .gemlhistory sidecar.
One block, by its address — every block carries its uuid:
geml find "that phrase" …/.logseq-sync-vault-with-geml # → pages/foo.geml #<uuid>
printf 'new text' | geml set …/pages/foo.geml '#<uuid>' --in - -o <same-file>
One property is an address too. They ride in a data block named after the
block they belong to, so a coordinate reaches one value and changes only it:
=== text {#1111… .level-1}
ship the sync plugin
===
=== data {#meta-1111… .block-meta format=edn}
{:build/keep-uuid? true
:build/properties {:user.property/status "doing"
:user.property/tags #{"infra" "logseq"}}}
===
printf 'done' | geml set …/pages/foo.geml \
'#meta-<uuid>[":build/properties"][":user.property/status"]' --in - -o <same-file>
A page's own properties are #page-meta, the graph's definitions #properties
in ontology.geml. Needs @geml/geml 1.10.2 or newer.
In bulk it is whatever your shell already does — the result is re-imported
by uuid, so identity survives the edit. geml check <file> --root <vault> after
one is cheap insurance: it names a mangled block, and a reference that now goes
nowhere, before the import carries either into the graph.
logseq-sync restorelogseq-sync restore # rehearse: says what it would import, writes nothing
logseq-sync restore --yes # take a Logseq backup, then import the vault
The vault imports by block uuid, so an edit lands in place rather than
duplicating. This is the one direction that writes into your notes, so it
rehearses unless you pass --yes, and --yes takes the app's own graph backup
first.
--overwrite-unmanaged is how
you say you meant it.db.sqlite exclusively, so continuous sync goes
through the desktop app's own CLI, which asks the running app instead of
opening the file.--two-way, a file changed on both
sides since the last sync is left exactly as you wrote it and named in the
status. Deletions are never imported.Versioning: MAJOR tracks the Logseq major this speaks to — 2.x means Logseq 2.x DB graphs, and nothing older.
MIT © GEML contributors
FAQs
Continuously sync a Logseq DB graph to a Git-friendly folder of readable plain-text GEML files — the watcher half of the Sync Vault with GEML plugin. Built on the official @logseq/cli export; writes only files that changed, commits scoped strictly to the
The npm package @geml/logseq-sync receives a total of 49 weekly downloads. As such, @geml/logseq-sync popularity was classified as not popular.
We found that @geml/logseq-sync demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.