npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geml/logseq-sync

v2.3.0

Published

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

Readme

Sync Vault with GEML

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.

How it works

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.

The toolbar reports the last sync The plugin's settings

Two directions, and only one of them is a round trip

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.

What you get

  • 📦 A plain-text copy that stays yours — every page a readable file in a folder you chose
  • 🔁 Continuous, not one-shot — edit in Logseq, seconds later the file has caught up; with --two-way, the reverse too
  • ↩️ A way back — logseq-sync restore imports the vault into a graph, merging by block uuid
  • 🌿 Git if you want it — point the vault at a repository and every sync is a clean commit; point it at a plain folder and nothing git-shaped appears

Pages 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.

Setup

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 vault

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

Editing the vault from outside

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.

Going back: logseq-sync restore

logseq-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.

Honesty corner

  • Restore merges, it does not replace. An import lands by uuid over whatever the graph currently holds; it will not remove pages the vault no longer has.
  • Files the sync did not write are never touched — not deleted, not overwritten. A manifest records what it wrote; anything else belongs to whoever put it there, so it is held and named. --overwrite-unmanaged is how you say you meant it.
  • A running Logseq holds db.sqlite exclusively, so continuous sync goes through the desktop app's own CLI, which asks the running app instead of opening the file.
  • A conflict is held, not resolved. With --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.
  • The in-app half is verified against the 2.0.1 runtime, the watcher half end-to-end in CI with no Logseq installed. If something misbehaves in your setup, an issue with your Logseq version is gold.

Versioning: MAJOR tracks the Logseq major this speaks to — 2.x means Logseq 2.x DB graphs, and nothing older.

MIT © GEML contributors