Local-first outliner. Markdown is the source of truth. Sync that doesn't corrupt your tree when two devices edit offline.
Inspired by Roam Research and Logseq.
Tree CRDT sync (Kleppmann et al. 2022), per-device append-only op log, IDs in a sidecar so the .md you see is the .md you wrote.
- Why outl? → outl.app/docs/why-outl.html
- Sync, done right: → outl.app/docs/sync.html
- CRDT walkthrough: → outl.app/docs/crdt.html
# macOS / Linux via Homebrew (beta channel — every push to main)
brew tap outlmd/outl https://github.com/outlmd/outl
brew trust outlmd/outl # one-time, third-party tap
brew install outl-beta # TUI/CLI/MCP
brew install --cask outl-desktop-beta # GUIiOS beta on TestFlight: join here. Point the TUI at the same iCloud Drive container (<container>/Documents/) and both clients share a workspace.
Android: a signed arm64 APK (outl-android-arm64.apk) rides every release.
Sideload-only for now — not on Play, and each release is signed with a fresh key, so a new build means uninstalling the old one first (#171).
- From source / channels: → getting started, homebrew
outl init ~/notes # scaffold a workspace
outl --workspace ~/notes # opens the TUI on today's journalPress ? for keymap, : for the command palette, Ctrl+P to fuzzy-jump.
You don't need a server. Pair your laptop and your phone and they sync directly, P2P. That's the normal setup.
But P2P converges only when two devices can reach each other, and a laptop shut at 18:00 never overlaps a phone edited at 22:00. If that's you, a third device that never sleeps closes the gap — on hardware you own.
docker compose build
outl peer pair --name laptop # on a device that HAS your notes
docker compose run --rm outl peer pair --ticket <ticket> # the server joins
docker compose up -dNot a server your notes sync through — no account, no upload, nobody else's infrastructure holding your graph. Just one more peer that happens to be awake all the time, holding a full replica of the op log. Turn it off and your other devices carry on talking to each other. Self-hosting guide.
outl import logseq ~/path/to/logseq-graph ~/notes
outl import roam ~/Downloads/backup.json ~/notesThe importers (Roam, Logseq, Obsidian — or auto to detect) resolve ((uid)) block refs and {{embed}}s into real outl block references, keep folded state, and translate each dialect.
Everything touched is counted in the import report — run with --dry-run first to measure fidelity against your backup.
Anything unresolvable stays as ((unresolved:UID)) for manual triage.
- Developer setup
- Contributing guide
- Architecture
- Roadmap — where the project is going
The engineering decisions behind outl on avelino.run:
- File sync isn't trivial — why concurrent file moves are a distributed-systems problem that Dropbox and Google Drive still get wrong.
- From paper to outliner — the gap between "the CRDT converges" and "the app ships": projections, content-addressable reconciliation, surviving iCloud's lazy materialisation.
MIT.
