{"id":35888258,"url":"https://github.com/liketheduck/supernote-apple-reminders-sync","last_synced_at":"2026-01-15T09:00:43.997Z","repository":{"id":330760083,"uuid":"1123769782","full_name":"liketheduck/supernote-apple-reminders-sync","owner":"liketheduck","description":"Bidirectional sync between Supernote (MariaDB/Docker) and Apple Reminders on macOS","archived":false,"fork":false,"pushed_at":"2025-12-27T20:22:19.000Z","size":66,"stargazers_count":9,"open_issues_count":0,"forks_count":2,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-29T19:18:27.387Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liketheduck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-27T15:28:37.000Z","updated_at":"2025-12-28T21:00:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/liketheduck/supernote-apple-reminders-sync","commit_stats":null,"previous_names":["liketheduck/supernote-apple-reminders-sync"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/liketheduck/supernote-apple-reminders-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liketheduck%2Fsupernote-apple-reminders-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liketheduck%2Fsupernote-apple-reminders-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liketheduck%2Fsupernote-apple-reminders-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liketheduck%2Fsupernote-apple-reminders-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liketheduck","download_url":"https://codeload.github.com/liketheduck/supernote-apple-reminders-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liketheduck%2Fsupernote-apple-reminders-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28447922,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-15T05:05:00.929Z","status":"ssl_error","status_checked_at":"2026-01-15T05:04:58.515Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-09T01:00:43.771Z","updated_at":"2026-01-15T09:00:43.990Z","avatar_url":"https://github.com/liketheduck.png","language":"Python","funding_links":[],"categories":["Resources"],"sub_categories":["Tools"],"readme":"# Supernote ↔ Apple Reminders Sync\n\nA bidirectional sync tool that synchronizes tasks between a Supernote device's to-do database (running in Docker/MariaDB) and Apple Reminders on macOS.\n\n## Features\n\n- **Bidirectional sync**: Changes in either system propagate to the other\n- **Category rename tracking**: Renaming a category/list on either side automatically renames it on the other\n- **Document link preservation**: Supernote note links are preserved during sync\n- **Anti-loop architecture**: Content hashing prevents infinite sync loops\n- **Conflict resolution**: Uses modification timestamps to resolve conflicts\n- **Dry-run mode**: Preview changes before applying them\n- **Backup/restore**: Snapshot Apple Reminders for safe recovery\n\n## Prerequisites\n\n- macOS (tested on Sonoma)\n- Python 3.11+\n- Docker (with Supernote MariaDB container running) OR remote MariaDB server\n- Swift compiler (included with Xcode Command Line Tools)\n\n## Installation\n\n1. Clone this repository\n2. Install Python dependencies:\n   ```bash\n   pip install -r requirements.txt\n   ```\n3. Install reminders-cli:\n   ```bash\n   cd /tmp\n   git clone --depth 1 https://github.com/keith/reminders-cli.git\n   cd reminders-cli \u0026\u0026 swift build -c release\n   mkdir -p ~/.local/bin\n   cp .build/release/reminders ~/.local/bin/\n   ```\n4. Compile the Swift helper:\n   ```bash\n   cd swift\n   swiftc -O -o reminder-helper reminder-helper.swift\n   ```\n5. Grant Reminders access when prompted\n\n## Quick Start\n\n```bash\n# Set database password (required)\nexport SUPERNOTE_DB_PASSWORD=\"your-password-here\"\n\n# Initialize the sync system (creates backup + tests connections)\npython -m src.main init\n\n# Preview what sync would do (ALWAYS do this first!)\npython -m src.main sync --dry-run\n\n# Run the actual sync\npython -m src.main sync\n\n# Check status\npython -m src.main status\n```\n\n## Commands\n\n### Sync Operations\n\n```bash\n# Preview changes without making them\npython -m src.main sync --dry-run\n\n# Execute the sync\npython -m src.main sync\n\n# Show sync status\npython -m src.main status\n\n# Show category mappings\npython -m src.main categories\n```\n\n### Backup/Restore\n\n```bash\n# Create a snapshot of Apple Reminders\npython -m src.main snapshot create\n\n# List all snapshots\npython -m src.main snapshot list\n\n# Show snapshot details\npython -m src.main snapshot info snapshots/apple_reminders_YYYYMMDD_HHMMSS.json\n\n# Restore from snapshot (preview)\npython -m src.main restore snapshots/apple_reminders_YYYYMMDD_HHMMSS.json\n\n# Restore from snapshot (execute)\npython -m src.main restore --execute snapshots/apple_reminders_YYYYMMDD_HHMMSS.json\n```\n\n### Diagnostics\n\n```bash\n# Test connections to both systems\npython -m src.main test\n\n# Show current configuration\npython -m src.main config\n\n# Clear sync state (debug only)\npython -m src.main clear-state --yes\n```\n\n## How It Works\n\n### Sync Algorithm\n\n1. **Load tasks** from both Supernote (MariaDB) and Apple Reminders\n2. **Match tasks** using the sync state database (falls back to title matching)\n3. **Detect changes** using content hashing (title + notes + status + priority + category)\n4. **Resolve conflicts** using modification timestamps (most recent wins)\n5. **Apply changes** to the appropriate system\n\n### Sync ID Storage\n\nThe sync tool maintains task relationships in a local SQLite database (`sync_state.db`):\n- Maps Apple Reminder IDs (`apple_id`) to Supernote task IDs (`supernote_id`)\n- Tracks content hashes for change detection\n- Falls back to title-based matching for initial sync of existing tasks\n- No metadata is added to your Apple Reminders notes\n\n### Document Links\n\nSupernote tasks can link to specific pages in notes. These are stored as Base64-encoded JSON in the `links` field and are preserved during sync. In Apple Reminders, they appear as:\n\n```\n📎 My Note.note (page 3)\n```\n\n## Project Structure\n\n```\nsupernote-reminders-sync/\n├── src/\n│   ├── main.py              # CLI interface\n│   ├── sync_engine.py       # Core sync logic\n│   ├── supernote_db.py      # Supernote database interface\n│   ├── apple_reminders.py   # Apple Reminders interface\n│   ├── sync_state.py        # Sync state management\n│   ├── models.py            # Data models\n│   ├── snapshot.py          # Backup/restore\n│   └── config.py            # Configuration management\n├── swift/\n│   └── reminder-helper.swift  # Swift helper for due date, priority, move\n├── config/\n│   ├── category_map.json    # Category mappings\n│   └── settings.json        # Settings\n├── docs/\n│   ├── supernote_schema.md  # Database schema documentation\n│   └── research_notes.md    # Research findings\n├── snapshots/               # Apple Reminders backups\n├── logs/                    # Sync logs\n└── sync_state.db           # Sync tracking database\n```\n\n## Configuration\n\n### Environment Variables\n\nCopy the example files and configure your settings:\n\n```bash\n# Environment variables\ncp .env.example .env\n\n# Config files\ncp config/settings.example.json config/settings.json\ncp config/category_map.example.json config/category_map.json\n\n# Edit with your values\n```\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `SUPERNOTE_DB_PASSWORD` | **(required)** | Password for the Supernote MariaDB database |\n| `SUPERNOTE_DB_MODE` | `docker` | Connection mode: `docker` (local container) or `tcp` (remote server) |\n| `SUPERNOTE_DB_HOST` | `localhost` | MariaDB host (only used if mode=tcp) |\n| `SUPERNOTE_DB_PORT` | `3306` | MariaDB port (only used if mode=tcp) |\n| `SUPERNOTE_DOCKER_CONTAINER` | `supernote-mariadb` | Docker container name (only used if mode=docker) |\n| `SUPERNOTE_DB_NAME` | `supernotedb` | Database name |\n| `SUPERNOTE_DB_USER` | `supernote` | Database user |\n| `REMINDERS_CLI_PATH` | `~/.local/bin/reminders` | Path to reminders-cli binary |\n| `SYNC_STATE_DB` | `./sync_state.db` | Path to sync state database |\n| `SNAPSHOTS_DIR` | `./snapshots` | Directory for Apple Reminders backups |\n| `LOGS_DIR` | `./logs` | Directory for log files |\n| `SYNC_CONFLICT_RESOLUTION` | `prefer_recent` | Conflict strategy: `prefer_recent`, `prefer_apple`, `prefer_supernote` |\n| `SYNC_CONFLICT_WINDOW` | `60` | Seconds to consider changes as simultaneous |\n| `SYNC_COMPLETED_TASKS` | `true` | Whether to sync completed tasks |\n\n### config/settings.json\n\n```json\n{\n  \"supernote\": {\n    \"docker_container\": \"supernote-mariadb\",\n    \"database\": \"supernotedb\",\n    \"user\": \"supernote\"\n  },\n  \"sync\": {\n    \"conflict_resolution\": \"prefer_recent\",\n    \"conflict_window_seconds\": 60,\n    \"preserve_document_links\": true,\n    \"sync_completed_tasks\": true\n  }\n}\n```\n\n### config/category_map.json\n\n```json\n{\n  \"mappings\": [\n    {\"apple\": \"Inbox\", \"supernote\": \"Inbox\"},\n    {\"apple\": \"Work\", \"supernote\": \"Work\"}\n  ],\n  \"defaults\": {\n    \"apple\": \"Inbox\",\n    \"supernote\": \"Inbox\"\n  },\n  \"auto_create_missing\": true\n}\n```\n\n## Emergency Recovery\n\nIf something goes wrong, restore from a snapshot:\n\n```bash\n# List available snapshots\npython -m src.main snapshot list\n\n# Preview restore\npython -m src.main restore snapshots/apple_reminders_YYYYMMDD_HHMMSS.json\n\n# Execute restore (requires typing \"RESTORE\" to confirm)\npython -m src.main restore --execute snapshots/apple_reminders_YYYYMMDD_HHMMSS.json\n```\n\n## Automated Sync with launchd\n\nTo run sync automatically every 15 minutes:\n\n1. Copy and configure the plist template:\n   ```bash\n   cp com.supernote.reminders-sync.example.plist com.supernote.reminders-sync.plist\n   # Edit the plist with your actual paths and password\n   ```\n\n2. Install and load the launch agent:\n   ```bash\n   cp com.supernote.reminders-sync.plist ~/Library/LaunchAgents/\n   launchctl load ~/Library/LaunchAgents/com.supernote.reminders-sync.plist\n   ```\n\n3. Check status and logs:\n   ```bash\n   launchctl list | grep supernote\n   tail -f logs/sync.log\n   ```\n\nTo stop: `launchctl unload ~/Library/LaunchAgents/com.supernote.reminders-sync.plist`\n\n## Permissions\n\n### Reminders Access\n\nOn first run, macOS will prompt for Reminders access. You must grant access to:\n- **Terminal** (if running manually)\n- **Python** (the python3 binary)\n\nIf you accidentally deny access, re-enable it in:\n**System Settings \u003e Privacy \u0026 Security \u003e Reminders**\n\n### Full Disk Access for launchd\n\nWhen running via launchd, the sync may fail silently if Python doesn't have Full Disk Access. To fix:\n\n1. Go to **System Settings \u003e Privacy \u0026 Security \u003e Full Disk Access**\n2. Click **+** and add `/opt/homebrew/bin/python3` (or your Python path)\n3. Reload the launch agent\n\n### Docker Access\n\nThe sync connects to MariaDB via `docker exec`. Ensure Docker Desktop is running and the Supernote container is started.\n\n## Limitations\n\n- **Supernote database access**: Requires the Supernote Cloud self-hosted MariaDB container\n- **macOS only**: Uses macOS-specific tools (reminders-cli, Swift EventKit)\n- **No recurrence sync**: Recurring tasks are not yet supported\n- **No location sync**: Location-based reminders are not synced\n\n## Technical Details\n\n### Supernote Database\n\nThe Supernote to-do database is MariaDB. Supports two connection modes:\n- **Docker mode** (default): Connects via `docker exec` to a local MariaDB container\n- **TCP mode**: Connects directly via TCP to a remote MariaDB server (e.g., NAS via Tailscale)\n\nKey tables:\n- `t_schedule_task`: Main tasks table\n- `t_schedule_task_group`: Categories/lists\n\nSee `docs/supernote_schema.md` for full schema documentation.\n\n### Apple Reminders\n\nUses two Swift-based tools for fast native EventKit access:\n- **reminders-cli**: For reading reminders (JSON output) and basic write operations (add, complete, uncomplete, delete, edit)\n- **reminder-helper**: Custom Swift helper for operations reminders-cli doesn't support (set-due-date, set-priority, move, rename-list, delete-list)\n\n### Category Sync\n\nCategories are tracked by their internal IDs (not names) to detect renames:\n- Supernote uses `task_list_id` (UUID)\n- Apple uses `calendarIdentifier` (UUID)\n\nWhen you rename a category on one system, the sync detects that the ID still exists but the name changed, and propagates the rename to the other system. This prevents orphaned tasks when categories are renamed.\n\n## License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliketheduck%2Fsupernote-apple-reminders-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliketheduck%2Fsupernote-apple-reminders-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliketheduck%2Fsupernote-apple-reminders-sync/lists"}