<div align="right">
**English** · [中文](./README.zh.md)
</div>
<br />
<div align="center">
# roam-to-logseq-db
**Migrate a Roam Research graph into Logseq's DB version — refs, images and journals intact.**
<br/>
An agent Skill (SKILL.md format) with a bundled converter; runs in Claude Code, Codex, and any agent that loads Skills.
<br/>
Block references · daily-note journals · tables · localized images · timestamps · via Logseq's own File→DB importer.
<br/>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-000?style=flat-square" alt="MIT"/></a>
<a href="SKILL.md"><img src="https://img.shields.io/badge/agent-Skill-000?style=flat-square" alt="Agent Skill"/></a>
<img src="https://img.shields.io/badge/converter-Python_stdlib_only-000?style=flat-square" alt="Python stdlib only"/>
<img src="https://img.shields.io/badge/target-Logseq_DB_v2-000?style=flat-square" alt="Logseq DB v2"/>
<img src="https://img.shields.io/badge/version-1.0.0-000?style=flat-square" alt="v1.0.0"/>
</div>
<br/>
## What it does
Logseq split in two: the old **file / Markdown** version, which has a built-in *Import Roam JSON* — and the new **DB / SQLite** version, now the default, which has **no Roam importer at all**. This Skill fills that gap. It converts your Roam JSON export into a Logseq **file graph**, then hands that to Logseq's **own official File→DB importer** — so block references, page links, daily-note journals, tasks, tables, headings and timestamps all survive, and every remote image is downloaded and localized before Roam's servers can drop it.
```text
In your agent's chat, after install:
I exported my Roam graph to ~/Downloads/graph.json — migrate it into Logseq
```
No LLM keys, no third-party services — the converter is deterministic Python; your agent drives the workflow and the QC.
<br/>
## Why this exists
You'd expect a dozen tools for this — Roam users have moved to Logseq for years. The catch: **Logseq handled it, but only in the old version.** The file/Markdown Logseq ships a built-in Roam importer, so no standalone tool was ever needed. The DB version — the one everyone is migrating to for performance — quietly dropped that capability, and nothing in the community replaced it.
| | Roam import | This tool |
|---|:---:|:---:|
| **Old file / Markdown Logseq** | built-in — use it | not needed |
| **New DB Logseq (v2, default)** | none · no community tool | **this is that tool** |
<br/>
## What it preserves
Everything that makes a Roam graph a *graph* — not just the prose.
| Roam construct | In Logseq DB | Note |
|---|---|---|
| Block references `((uid))` | live `[[uuid]]` refs + `id::` | the graph's edges, rebuilt — proven by a zero-dangling scan |
| Page links `[[Title]]` · `#tags` | node references | resolved by title |
| Daily notes | real `#Journal` pages | date-parsed, `journal-day` set |
| Images — Firebase / remote | local `#Asset` nodes | **downloaded and localized** before the links rot |
| Tables `{{[[table]]}}` | GFM tables | diagonal-nested cells flattened losslessly |
| Tasks · headings · highlights · LaTeX | native equivalents | `^^highlight^^` kept as-is |
| Create / edit timestamps | `created-at` / `updated-at` | preserved, not reset to import time |
| `roam/css` · `r/moved` · dead macros | dropped | noise, not content |
<br/>
## Install
Clone into your agent's Skills directory — the path depends on your agent:
```bash
git clone https://github.com/O0000-code/roam-to-logseq-db.git \
~/.claude/skills/roam-to-logseq-db # Claude Code
# ~/.codex/skills/roam-to-logseq-db # Codex
# ./.claude/skills/roam-to-logseq-db # project-local (Cursor, etc.)
```
The converter is **pure Python standard library** — nothing to `pip install`. The one setup cost is the importer that ingests the file graph; [`references/toolchain-setup.md`](references/toolchain-setup.md) covers both routes: **headless** (scripted, needs a Logseq source checkout) and **GUI** (one click in any Logseq desktop, no setup).
<br/>
## How it works
```text
Roam JSON ─▶ roam_to_logseq.py ─▶ Logseq file graph ─▶ File→DB importer ─▶ DB graph
convert + localize .md + assets/ Logseq's own engine
```
The **file graph** is the universal intermediate every Roam→Logseq tool emits; we feed it to Logseq's own importer instead of writing the database directly. That choice is load-bearing: the direct-write path (`import-edn`) is a pure database transaction that never touches the filesystem — so it **cannot copy image files**, and every picture renders broken. Only the File→DB importer physically copies assets.
Two ways to run the final import:
- **Headless** — the nbb script `db_import.cljs` from a Logseq source checkout; fully scriptable (convert → stage → QC → final). One-time toolchain setup.
- **GUI** — produce the file graph, then **Import → File to DB graph** in Logseq desktop. Same engine, one click, no setup.
The full recipe is in [`SKILL.md`](SKILL.md); per-construct rules in [`references/conversion-rules.md`](references/conversion-rules.md); verification queries and failure modes in [`references/qc-and-troubleshooting.md`](references/qc-and-troubleshooting.md).
<br/>
## Why it's more than a script
Converting the text is the easy half. The hard half is everything Logseq DB does differently from what the docs imply — and the Skill encodes each one, with the *why*, so a fresh run never has to rediscover it the hard way:
- Block refs resolve as `[[uuid]]`, **not** Roam's `((uid))` — the source reads the other way; only empirical testing catches it (and it would silently break *every* reference).
- **Quit Logseq** before importing to a graph it holds open, or the concurrent SQLite write corrupts it.
- A stale CLI db-worker serves *old* data and will lie to your QC — kill it between runs.
- A DB graph can't be renamed; blockquotes and inline-opened code fences swallow trailing properties; the import is non-idempotent.
This is the difference between "ran a converter" and "landed a 180k-block graph with zero dangling references and every image showing."
<br/>
## License
MIT — see [`LICENSE`](LICENSE). What it stands on: [`ACKNOWLEDGMENTS.md`](ACKNOWLEDGMENTS.md).