LLMSync
Self-hostable LLM conversation sync service with end-to-end encryption
LLMSync synchronizes LLM conversation history across devices using email-based identity and zero-knowledge end-to-end encryption. Built for developers using multiple AI coding tools (Claude Code, Cursor, Codex, etc.).
Features
- 🔒 End-to-End Encryption: Zero-knowledge architecture with client-side encryption (AES-256-GCM)
- 🔄 Real-Time Sync: WebSocket support with polling fallback
- 📧 Magic Link Auth: Email-based authentication (OAuth support included)
- 📦 File Attachments: Encrypted file storage up to 100MB per file
- 🐳 Easy Deployment: Docker Compose for one-command setup
- 🔌 REST API: Full-featured API for building integrations
Quick Start
Prerequisites
- Docker & Docker Compose
- Git
1. Clone & Setup
git clone <repository-url> llmsync
cd llmsync
cp .env.example .env
2. Start Services
docker-compose up -d
This starts:
- API (FastAPI): http://localhost:8000
- PostgreSQL: localhost:5432
- Redis: localhost:6379
- MinIO: http://localhost:9000 (console: http://localhost:9001)
3. Verify Installation
curl http://localhost:8000/api/health
Expected response:
{
"status": "healthy",
"database": "connected"
}
API Overview
Authentication
Magic Link Login:
curl -X POST http://localhost:8000/api/auth/login/magic-link \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com"}'
Check console logs for the magic link, then verify:
curl "http://localhost:8000/api/auth/verify?token=<TOKEN>"
Threads & Messages
Create Thread:
curl -X POST http://localhost:8000/api/threads \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{"title":"My Conversation"}'
Create Message:
curl -X POST http://localhost:8000/api/threads/<THREAD_ID>/messages \
-H "Authorization: Bearer <TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"id":"<UUID>",
"thread_seq":1,
"encrypted_content":"<base64>",
"encrypted_key":"<base64>",
"nonce":"<base64>",
"role":"user",
"timestamp":"2026-09-30T12:00:00Z"
}'
List Messages:
curl "http://localhost:8000/api/threads/<THREAD_ID>/messages?limit=100" \
-H "Authorization: Bearer <TOKEN>"
File Uploads
curl -X POST http://localhost:8000/api/files \
-H "Authorization: Bearer <TOKEN>" \
-F "message_id=<MESSAGE_ID>" \
-F "file=@screenshot.png"
WebSocket Sync
Connect to WebSocket for real-time updates:
const ws = new WebSocket('ws://localhost:8000/api/sync/ws?token=<TOKEN>');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Sync event:', data);
};
// Send ping
ws.send(JSON.stringify({ type: 'ping' }));
Polling Fallback
curl "http://localhost:8000/api/sync/events?cursor=0&limit=100" \
-H "Authorization: Bearer <TOKEN>"
Architecture
┌─────────────────────────────────────┐
│ Client Devices (E2EE) │
│ Claude Code, Cursor, Gemini CLI │
└─────────────┬───────────────────────┘
│ HTTPS/WSS
┌─────────────▼───────────────────────┐
│ FastAPI Backend (Stateless) │
│ - Auth API (Magic Links) │
│ - Sync API (REST + WebSocket) │
│ - File API (Encrypted uploads) │
└──┬────────────────┬─────────────────┘
│ │
┌──▼──────┐ ┌──────▼────┐ ┌─────────┐
│Postgres │ │ Redis │ │ MinIO │
│(Metadata) │ (Sessions)│ │ (Files) │
└─────────┘ └───────────┘ └─────────┘
Project Structure
llmsync/
├── backend/
│ ├── llmsync/
│ │ ├── main.py # FastAPI app
│ │ ├── config.py # Settings
│ │ ├── models.py # SQLAlchemy models
│ │ ├── database.py # DB connection
│ │ ├── auth.py # JWT utilities
│ │ ├── crypto.py # E2EE utilities
│ │ ├── dependencies.py # Auth dependencies
│ │ ├── schemas.py # Pydantic schemas
│ │ ├── routers/ # API routers
│ │ │ ├── auth.py
│ │ │ ├── threads.py
│ │ │ ├── messages.py
│ │ │ ├── files.py
│ │ │ └── sync.py
│ │ └── services/ # External services
│ │ ├── redis_client.py
│ │ ├── minio_client.py
│ │ └── email.py
│ ├── alembic/ # Database migrations
│ ├── tests/ # Test suite
│ ├── pyproject.toml # Dependencies
│ └── Dockerfile
├── docker-compose.yml # Service orchestration
├── .env.example # Environment template
└── README.md
Security
Encryption Model
- Master Key: 256-bit key generated client-side
- Recovery Phrase: 12-word BIP39 mnemonic for key recovery
- Message Encryption: AES-256-GCM via ephemeral keys
- File Encryption: Separate ephemeral keys per file
- Server Storage: Only encrypted ciphertext, never plaintext
Zero-Knowledge Architecture
The server:
- ✅ Stores encrypted content only
- ✅ Cannot read messages or files
- ✅ Cannot recover lost recovery phrases
- ❌ Never sees plaintext data
- ❌ Cannot decrypt without client keys
Development
Running Tests
cd backend
poetry install
poetry run pytest
Database Migrations
Create migration:
cd backend
alembic revision --autogenerate -m "description"
Apply migrations:
alembic upgrade head
Logs
View API logs:
docker-compose logs -f api
Configuration
Edit .env for custom configuration:
- Ports: Change
API_PORT,POSTGRES_PORT, etc. - Security: Set strong
SECRET_KEYin production - OAuth: Add Google/GitHub client credentials
- SMTP: Configure for production email delivery
Production Deployment
- Use HTTPS: Add nginx or Traefik for TLS
- Strong Secrets: Generate random
SECRET_KEYand database passwords - Backups: Backup PostgreSQL and MinIO volumes regularly
- Monitoring: Add Prometheus/Grafana for observability
- Rate Limiting: Configure nginx rate limits
Roadmap
- Web UI for browsing conversations
- Python SDK
- TypeScript SDK
- Claude Code plugin
- Cursor extension
- Mobile apps (iOS/Android)
- Team/organization support
- Search & tagging
License
MIT License - see LICENSE file
Contributing
Contributions welcome! Please open an issue first to discuss changes.
Support
- Issues: GitHub Issues
- Docs: See
/docsdirectory - Community: Discord (coming soon)
Built with ❤️ for the AI coding community
Metadata
Release files for llmsync 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llmsync-0.1.0.tar.gz | 28.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llmsync-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.9 kB
Release files / llmsync-0.1.0.tar.gz
| Download URL | llmsync-0.1.0.tar.gz |
|---|---|
| Size | 28.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
18074b9c3658a5797bc9cd85f6e0f4092af2caf9a444cf19d50142a5171bc0fe
|
|
BLAKE2b-256 checksum How to use checksums |
4a864fbec1cb94a6e9537966b73adbb75cbc3ace90de1fe3b536344b5cb55798
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / llmsync-0.1.0-py3-none-any.whl
| Download URL | llmsync-0.1.0-py3-none-any.whl |
|---|---|
| Size | 32.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bc55909e2e24fac7b00d674d59f6f5d055b51441d4e7a8a6ae0c3c932c178ebc
|
|
BLAKE2b-256 checksum How to use checksums |
eeb42851eb7f6485866cbbe219ad2bc60b2396ede9f5f6f1835c892893826b53
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|