LeafLink
A CLI for syncing local folders with Overleaf and cn.overleaf projects
Browser login, clone, pull, push, PDF download, and pseudo real-time sync in one tool.
Overview
LeafLink is an open source command-line tool for synchronizing a local directory with an Overleaf project without relying on Overleaf's paid Git bridge.
It is inspired by the product shape of overleaf-sync, but it is not a fork. LeafLink is a clean-room rewrite built for:
- clearer architecture
- maintainable Python packaging
- browser-based authentication
- reliable local/remote state tracking
- easier testing and long-term extension
Why Rewrite Instead of Fork
- To decouple authentication, client, sync engine, and CLI from the start
- To support both
overleaf.comandcn.overleaf.comas first-class targets - To add a
synccommand for local watch + remote polling collaboration - To make the project easier to test, release, and maintain as a real PyPI package
Highlights
- Browser login with persisted session cookies
- Cookie import (interactive prompt,
--cookie, or JSON file) for servers without a browser - List all accessible projects
- Clone by project URL, project ID, or exact project name
- Pull remote changes into a local working tree
- Push local changes to the remote project
- Download the latest compiled PDF
- Inspect local vs remote status without applying changes
synccommand for pseudo real-time collaboration- Three-way text merge powered by
merge3 - Binary-safe syncing for images, PDFs, archives, BibTeX files, style files, and more
Supported URLs
https://www.overleaf.com/project/...https://cn.overleaf.com/project/...
Self-hosted Overleaf (0.3.0)
Use the origin of your instance, including its port when needed:
leaflink login --base-url https://overleaf.lan.example.com
leaflink list --base-url https://overleaf.lan.example.com
leaflink clone https://overleaf.lan.example.com/project/PROJECT_ID ./paper
Browser login or leaflink auth import --base-url ... (see
Import cookies) can supply a session for that instance. Keep cookie files and auth.json out of
Git and workflow logs. Login sessions are scoped to their instance. Self-hosted
HTTP(S) origins are supported; reverse-proxy subpath deployments are not.
TLS certificate verification remains enabled. Editor API compatibility still
depends on the deployed Overleaf version; validate uploads on a test project first.
Ignore behavior
LeafLink creates .leafignore at the project root on clone, and on the first pull, push,
status or sync in projects cloned by older versions (not with --dry-run). It skips this when the
project already has an ignore file or one was synced before and then deleted. The generated file
ignores itself (/.leafignore), so it stays on your machine; delete that line to share the rules
with collaborators through Overleaf. --ignore-file selects a different file name.
Rules filter both local and remote changes: clone/pull, push, and deletions.
Adding a rule leaves existing local and remote files untouched; it does not delete
them. The long-running sync loop reloads rules at the next synchronization check.
A first clone uses the remote ignore file if no local one exists; a pre-existing
local ignore file takes precedence. During pull, incoming rule changes are applied
before other files. If both sides edited the rule file, synchronization stops for
manual resolution instead of guessing which policy to use. Ignoring files does not avoid downloading the
project ZIP; filtering happens before files are applied locally.
Supported patterns: *, ?, character classes, ** for whole directory segments,
/ for a root-anchored pattern, trailing / for directories, and ! for re-inclusion.
Later matching rules win. For example:
build/
*.pdf
!figures/**
Unlike Git, LeafLink allows an explicit later rule to re-include a file beneath an
ignored directory. It reads one root ignore file; nested .gitignore files and Git's
backslash-escape syntax are not supported. .git and .leaflink metadata are always
excluded and cannot be re-included. .leafignore itself is synced unless excluded (the generated one excludes itself).
Default LaTeX artifact patterns still apply; ordinary rules may override them.
When running in disposable CI jobs, persist the project's .leaflink baseline
separately from authentication secrets. This release does not install a Gitea
workflow or automatically turn a new checkout into an initialized sync project.
Installation
Install from PyPI
pip install leaflink
playwright install chromium
leaflink doctor
pip install leaflink installs everything LeafLink needs, including Playwright and watchdog.
Playwright downloads its browser separately, so run playwright install chromium once.
push, sync and browser login drive this Chromium to reach Overleaf's editor.
Run it again after upgrading Playwright, since each Playwright release pins its own browser build.
If Chromium is missing, LeafLink stops before changing anything remotely and offers to download it.
leaflink doctor checks the whole setup (see leaflink doctor).
Commands from older versions such as pip install "leaflink[browser,watch]" still work.
Install from source
git clone https://github.com/xiongqi123123/LeafLink.git
cd LeafLink
pip install .
playwright install chromium
Authentication
LeafLink stores reusable session data, not plaintext usernames or passwords.
Default config locations:
~/.config/leaflink/auth.json~/.config/leaflink/config.toml
Browser login
leaflink login
leaflink login --base-url https://cn.overleaf.com
Example output:
[auth] Complete login in the browser window. leaflink will detect the session automatically and close the browser.
[ok] Saved session for https://cn.overleaf.com
Import cookies (servers without a browser)
leaflink login needs a desktop browser. On an SSH server or container, log in with a
browser on any other machine, copy the session cookie, and import it on the server.
-
Log in to Overleaf, then open DevTools → Application (Firefox: Storage) → Cookies → your Overleaf site.
-
Copy the value of the session cookie:
Site Cookie name www.overleaf.com/cn.overleaf.comoverleaf_session2Self-hosted Overleaf (CE / Server Pro) sharelatex.sid -
Import it in one of three ways.
Interactive prompt (recommended) — values are hidden and stay out of shell history:
leaflink auth import --base-url https://www.overleaf.com
[auth] Enter cookies for https://www.overleaf.com from a logged-in browser (DevTools -> Application -> Cookies).
[auth] Session cookie: overleaf_session2 on overleaf.com, sharelatex.sid on self-hosted Overleaf.
[auth] Leave the name empty to finish.
Cookie name: overleaf_session2
Value of overleaf_session2 (hidden):
Cookie name:
[ok] Imported cookies for https://www.overleaf.com
At the name prompt you can also paste a whole Cookie: request header (a=1; b=2) copied from the Network tab.
Command line — handy for scripts, but the value ends up in shell history:
leaflink auth import --base-url https://overleaf.example.com --cookie 'sharelatex.sid=s%3A...'
JSON file:
leaflink auth import --base-url https://www.overleaf.com --cookie-file cookies.json
[
{"name": "overleaf_session2", "value": "s%3A...", "domain": ".overleaf.com"}
]
nameandvalueare required.domainis optional and defaults to the--base-urlhost (.overleaf.comfor the official sites).path,secureandhttpOnlyare optional.{"cookies": [...]}exports from browser cookie extensions and a plain{"overleaf_session2": "s%3A..."}object are also accepted.
leaflink login accepts the same --cookie-file / --cookie options. Run leaflink list afterwards to
check the session works; leaflink auth import --help shows this reference offline.
Logout
leaflink logout
Example output:
[ok] Removed saved auth session.
Quick Start
leaflink login --base-url https://cn.overleaf.com
leaflink list
leaflink clone "Sample Paper Draft"
cd sample-paper-draft
leaflink status
leaflink pull
leaflink push
leaflink download --output build/output.pdf
leaflink sync
Commands and Sample Output
All project names, IDs, paths, and file contents below are fictional examples.
leaflink list
Lists all Overleaf projects accessible to the current account. It is useful before cloning so you can confirm project names, IDs, and recent update times.
leaflink list
Name Project ID Updated
--------------------------- ------------------------ ------------------------
Sample Paper Draft a1b2c3d4e5f60718293a4b5c 2026-03-27T09:16:42.180Z
Research Notes b2c3d4e5f60718293a4b5c6d 2026-03-24T12:08:09.011Z
Resume Template CN c3d4e5f60718293a4b5c6d7e 2026-03-21T07:45:33.501Z
leaflink clone
Clones a remote project into a local directory and initializes the .leaflink/ metadata used to track the mapping between the local workspace and the remote project.
leaflink clone a1b2c3d4e5f60718293a4b5c
leaflink clone "Sample Paper Draft"
leaflink clone https://www.overleaf.com/project/a1b2c3d4e5f60718293a4b5c ./paper-demo
[ok] Cloned Sample Paper Draft into sample-paper-draft
leaflink status
Analyzes local and remote changes without applying anything. Use it to preview added, modified, deleted, and conflicting files before a pull or push.
leaflink status
[local] added: figures/overview.pdf
[local] modified: main.tex
[local] deleted: -
[remote] added: appendix.tex
[remote] modified: refs.bib
[remote] deleted: drafts/old-outline.tex
[conflicts] -
leaflink pull
Pulls the latest remote changes into the local project directory. It handles additions, updates, deletions, and enters conflict resolution when overlapping text edits are detected.
leaflink pull
leaflink pull --conflict-strategy keep-remote
[2026-03-27 14:06:11] [remote] changed: refs.bib (saved 2026-03-27 14:05:57, by collaborator)
[2026-03-27 14:06:11] [pull] updated: refs.bib (saved 2026-03-27 14:05:57, by collaborator)
[2026-03-27 14:06:11] [pull] added: appendix.tex
Conflict example:
[2026-03-27 14:06:11] [conflict] main.tex
Local and remote edits overlap in the same text region.
...
<<<<<<< local/main.tex
\title{Sample Paper Draft v2}
||||||| base/main.tex
\title{Sample Paper Draft}
=======
\title{Sample Paper Draft Revised}
>>>>>>> remote/main.tex
Choose a resolution:
1. keep remote
2. keep local
3. duplicate both
leaflink push
Pushes local changes to the remote project. It tries to upload only changed files and works for common binary assets as well as text sources.
leaflink push
leaflink push --dry-run
[2026-03-27 14:07:42] [push] uploaded: main.tex (saved 2026-03-27 14:07:38)
[2026-03-27 14:07:42] [push] uploaded: figures/overview.pdf (saved 2026-03-27 14:07:11)
leaflink download
Downloads the most recent compiled PDF from the remote project. This is useful for local archiving, submission workflows, or build pipelines.
leaflink download
leaflink download --output build/paper.pdf
[ok] Downloaded PDF to /path/to/project/build/paper.pdf
leaflink sync
Starts the pseudo real-time sync loop. It watches local file changes and pushes them automatically, while polling the remote project on an interval and pulling remote changes when safe.
leaflink sync
leaflink sync --interval 15 --debounce 2.0
leaflink sync --once
[2026-03-27 13:21:23] [sync] Preparing sync service for Sample Paper Draft.
[2026-03-27 13:21:23] [sync] Project lock acquired for Sample Paper Draft.
[2026-03-27 13:21:23] [sync] Starting local watcher.
[2026-03-27 13:21:23] [sync] Sync service started. Watching /path/to/project (remote poll: 10.0s, debounce: 1.5s).
[2026-03-27 13:21:31] [local] modified: main.tex (saved 2026-03-27 13:21:30)
[2026-03-27 13:21:33] [push] uploaded: main.tex (saved 2026-03-27 13:21:30)
[2026-03-27 13:21:43] [sync] Checking remote changes.
[2026-03-27 13:21:44] [remote] changed: refs.bib (saved 2026-03-27 13:21:40, by collaborator)
[2026-03-27 13:21:44] [pull] updated: refs.bib (saved 2026-03-27 13:21:40, by collaborator)
leaflink doctor
Checks the installed packages, whether Chromium starts, and whether the saved session works.
Exits with status 1 if something needs fixing. If Chromium is missing it offers to download it;
--install-browser downloads it without asking.
leaflink doctor
leaflink doctor --base-url https://overleaf.example.com
[ok] leaflink 0.3.6 on Python 3.11.9 (linux)
[ok] playwright 1.63.0
[ok] watchdog 6.0.0
[ok] Chromium starts (needed by push, sync and browser login)
[warn] No display detected: `leaflink login` cannot open a browser here. Use `leaflink auth import` instead.
[ok] Session for https://www.overleaf.com works (12 projects)
.leafignore Example
This is the file LeafLink generates. Add your own rules below it; edits apply on the next command.
# LeafLink ignore rules: matching files are never pulled or pushed.
# Syntax is like .gitignore: *.ext, dir/, /rooted/path, **, and !pattern to re-include.
# Edits take effect on the next leaflink command (or the next `sync` poll).
# Adding a rule never deletes files already on Overleaf; LeafLink just stops syncing them.
#
# Ignored by default, even without this file (use !pattern to re-include):
# .leaflink/ .git/ .DS_Store *.aux *.log *.synctex.gz *.synctex(busy) *.fdb_latexmk *.fls *.out
# Keep this file on this machine only. Delete this line to share these rules
# with collaborators through the Overleaf project.
/.leafignore
# More LaTeX build files
*.toc
*.lof
*.lot
*.blg
*.bcf
*.run.xml
*.xdv
*.dvi
*.nav
*.snm
*.vrb
# Examples:
# build/
# figures/generated/
# main.pdf
See examples/leafignore.example for a complete example.
Conflict Handling
LeafLink uses three-way merge with:
- a shared base snapshot
- the current local file
- the current remote file
Behavior:
- non-overlapping text edits are merged when possible
- overlapping text edits enter conflict resolution
- binary files are not auto-merged
Available strategies:
keep remotekeep localduplicate both
How sync Works and Its Limits
leaflink sync is pseudo real-time sync, not true live collaborative editing.
It works by:
- watching the local directory with
watchdog - debouncing bursts of local writes
- polling the remote project at intervals
- applying automatic push / pull when safe
- entering conflict resolution when overlapping edits are detected
Limitations:
- remote changes are discovered by polling, not server push
- text files are better candidates for auto-merge than binary files
- if Overleaf private web APIs change, some write paths may need updates
Local Metadata
Each cloned project stores internal metadata in:
.leaflink/
project.json
state.json
lock
cache/
logs/
Security Notes
- LeafLink does not store plaintext passwords
- only required session cookies and metadata are persisted
- delete cookie files after importing them (or use the interactive prompt, which leaves nothing on disk)
- logs avoid printing sensitive auth values by default
- storing sessions is best done on trusted personal devices only
Star History
Development and Release
- install dev dependencies:
pip install -e ".[dev]"thenplaywright install chromium - run tests:
python -m unittest discover -s tests -v - build locally:
python scripts/build_dist.py - GitHub Actions:
- CI:
.github/workflows/ci.yml - Publish:
.github/workflows/publish.yml
- CI:
License
This project is licensed under the Apache License 2.0.
Metadata
Release files for leaflink 0.3.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| leaflink-0.3.7.tar.gz | 1.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| leaflink-0.3.7-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.5 MB
Release files / leaflink-0.3.7.tar.gz
| Download URL | leaflink-0.3.7.tar.gz |
|---|---|
| Size | 1.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
456b70f695e24e4f54fbed0d4253a59575fb502d2dcfbfa1b9e66421e06dd8ad
|
|
BLAKE2b-256 checksum How to use checksums |
c1d98975f3c428aae573bda45f46d5767f92ebb01f6609828170cb02d2c86a99
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency logRelease files / leaflink-0.3.7-py3-none-any.whl
| Download URL | leaflink-0.3.7-py3-none-any.whl |
|---|---|
| Size | 58.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
50a71f11c4b01eeb1379dba9b5207a915852a6c57f4b044cb48835cc4e30bae1
|
|
BLAKE2b-256 checksum How to use checksums |
5d43b5a74dafff484a66d156d851bf2bb0bf39645aa8eed7afbc5bb41b7f0f55
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.
Transparency log