{"id":35637026,"url":"https://github.com/bigspawn/anilist-mal-sync","last_synced_at":"2026-04-16T07:05:20.184Z","repository":{"id":257813160,"uuid":"868175324","full_name":"bigspawn/anilist-mal-sync","owner":"bigspawn","description":"Program to synchronize your AniList and MyAnimeList accounts.","archived":false,"fork":false,"pushed_at":"2026-02-01T22:00:10.000Z","size":669,"stargazers_count":12,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-02T05:52:31.659Z","etag":null,"topics":["anilist","cli","myanimelist","sync"],"latest_commit_sha":null,"homepage":"","language":"Go","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/bigspawn.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":"2024-10-05T17:07:33.000Z","updated_at":"2026-02-01T22:00:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"43f276e4-b625-4c78-a099-6ad8a64eac84","html_url":"https://github.com/bigspawn/anilist-mal-sync","commit_stats":null,"previous_names":["bigspawn/anilist-mal-sync"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/bigspawn/anilist-mal-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigspawn%2Fanilist-mal-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigspawn%2Fanilist-mal-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigspawn%2Fanilist-mal-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigspawn%2Fanilist-mal-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bigspawn","download_url":"https://codeload.github.com/bigspawn/anilist-mal-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bigspawn%2Fanilist-mal-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29120484,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T10:47:47.471Z","status":"ssl_error","status_checked_at":"2026-02-05T10:45:08.119Z","response_time":65,"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":["anilist","cli","myanimelist","sync"],"created_at":"2026-01-05T10:13:09.124Z","updated_at":"2026-04-16T07:05:20.172Z","avatar_url":"https://github.com/bigspawn.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# anilist-mal-sync [![Build Status](https://github.com/bigspawn/anilist-mal-sync/workflows/go/badge.svg)](https://github.com/bigspawn/anilist-mal-sync/actions) [![codecov](https://codecov.io/gh/bigspawn/anilist-mal-sync/branch/main/graph/badge.svg)](https://codecov.io/gh/bigspawn/anilist-mal-sync)\n\n\u003e **Note:** This project is under development. Feedback, suggestions, and issues are highly appreciated!\n\nProgram to synchronize your AniList and MyAnimeList accounts.\n\n## Features\n\n- Bidirectional sync between AniList and MyAnimeList (anime and manga)\n- Favorites synchronization (MAL → AniList with add-only, AniList → MAL report-only)\n- OAuth2 authentication\n- CLI interface\n- Manual ID mappings and ignore rules via `mappings.yaml`\n- Duplicate target detection with automatic conflict resolution\n- Unmapped entries tracking with interactive management (`unmapped` command)\n- Offline ID mapping using anime-offline-database (prevents incorrect season matches)\n- Optional ARM API integration for online ID lookups\n\n## What gets synced\n\nFor each entry in your list the following fields are synchronized from source to target:\n\n| Field | Synced |\n|-------|--------|\n| Status (watching / completed / on-hold / dropped / plan to watch) | ✅ |\n| Score (automatically normalized between AniList and MAL score formats) | ✅ |\n| Progress (episodes watched / chapters + volumes read) | ✅ |\n| Start date | ✅ (nil source date never overwrites a set target date) |\n| Finish date | ✅ (only when status is Completed) |\n| Favorites | ✅ optional, via `--favorites` flag |\n\n**Conflict rule:** the source service always wins. In the default direction (AniList → MAL) your AniList data overwrites MAL. Use `--reverse-direction` to flip.\n\nSee [docs/date-sync.md](docs/date-sync.md) for detailed date synchronization rules.\n\n## Prerequisites\n\n| Deployment | Requirements |\n|---|---|\n| **Docker** | [Docker](https://docs.docker.com/get-docker/) + [docker compose](https://docs.docker.com/compose/install/) (v2, built-in) or legacy `docker-compose` (v1) |\n| **Binary (go install)** | [Go 1.25+](https://go.dev/dl/) |\n| **Local build** | [Go 1.25+](https://go.dev/dl/), git |\n\n## Quick Start (Docker)\n\n### Step 1: Create OAuth applications\n\n**AniList:**\n1. Go to [AniList Developer Settings](https://anilist.co/settings/developer)\n2. Create New Client with redirect URL: `http://localhost:18080/callback`\n3. Save Client ID and Client Secret\n\n**MyAnimeList:**\n1. Go to [MAL API Settings](https://myanimelist.net/apiconfig)\n2. Create Application with redirect URL: `http://localhost:18080/callback`\n3. Save Client ID and Client Secret\n\n### Step 2: Configure\n\nDownload and rename the example compose file, then fill in your credentials:\n\n```bash\ncp docker-compose.example.yaml docker-compose.yaml\n```\n\nEdit `docker-compose.yaml` with your credentials:\n\n```yaml\nservices:\n  sync:\n    image: ghcr.io/bigspawn/anilist-mal-sync:latest\n    command: [\"watch\", \"--once\"]\n    ports:\n      - \"18080:18080\"\n    environment:\n      # User/Group ID for volume permissions (run: id -u / id -g)\n      - PUID=1000\n      - PGID=1000\n      # Required: API credentials\n      - ANILIST_CLIENT_ID=your_anilist_client_id\n      - ANILIST_CLIENT_SECRET=your_anilist_secret\n      - ANILIST_USERNAME=your_anilist_username\n      - MAL_CLIENT_ID=your_mal_client_id\n      - MAL_CLIENT_SECRET=your_mal_secret\n      - MAL_USERNAME=your_mal_username\n      # Watch mode interval (min: 1h, max: 168h / 7 days)\n      - WATCH_INTERVAL=12h\n      # Optional: Manual mappings file path\n      # - MAPPINGS_FILE_PATH=/home/appuser/.config/anilist-mal-sync/mappings.yaml\n      # Optional: ID Mapping settings\n      # - OFFLINE_DATABASE_ENABLED=true  # Enable offline DB (default: true)\n      # - HATO_API_ENABLED=true  # Enable Hato API (default: true)\n      # - HATO_API_URL=https://hato.malupdaterosx.moe  # Hato API base URL\n      # - HATO_API_CACHE_DIR=/home/appuser/.config/anilist-mal-sync/hato-cache  # Cache directory\n      # - HATO_API_CACHE_MAX_AGE=720h  # Cache max age (default: 720h / 30 days)\n      # - ARM_API_ENABLED=false  # Enable ARM API (default: false)\n      # - ARM_API_URL=https://arm.haglund.dev  # ARM API base URL\n      # - JIKAN_API_ENABLED=false  # Enable Jikan API for manga ID mapping\n      # - JIKAN_API_CACHE_DIR=/home/appuser/.config/anilist-mal-sync/jikan-cache\n      # - JIKAN_API_CACHE_MAX_AGE=168h\n      # - FAVORITES_SYNC_ENABLED=false  # Enable favorites sync (requires Jikan API)\n    volumes:\n      - tokens:/home/appuser/.config/anilist-mal-sync\n    restart: unless-stopped\n\nvolumes:\n  tokens:\n```\n\n### Step 3: Authenticate\n\n```bash\ndocker-compose run --rm --service-ports sync login\n```\n\nThe tool will print two URLs — one for MyAnimeList and one for AniList. For each:\n1. Copy the URL and open it in your browser\n2. Authorize the application on the website\n3. Your browser will redirect to `http://localhost:18080/callback` — the tool captures this automatically\n4. Repeat for both services (MAL first, then AniList)\n\n\u003e **Note:** The `--service-ports` flag is required here so that the OAuth redirect to port 18080 reaches the container. Make sure port 18080 is free on your host.\n\nTokens are saved into the `tokens` Docker volume and persist across restarts.\n\n### Step 4: Run\n\n#### Step 4.1: Preview changes (dry run)\n\n**Recommended before the first real sync.** On a large list the tool may update hundreds of\nentries at once — dry run lets you see exactly what will change without touching anything.\n\n```bash\ndocker-compose run --rm sync sync --dry-run --all\n```\n\n#### Step 4.2: Start the sync daemon\n\n`docker-compose up -d` starts the container in **watch mode** (`--once` flag causes an\nimmediate first sync, then it repeats every `WATCH_INTERVAL` hours in the background).\n\n```bash\ndocker-compose up -d\n```\n\nDone! The service will sync your lists every 12 hours (or whatever you set in `WATCH_INTERVAL`).\n\nTo check that the service started correctly and view sync output:\n\n```bash\ndocker-compose logs -f sync\n```\n\n\u003e **Note:** `WATCH_INTERVAL` accepts values between `1h` and `168h` (7 days).\n\u003e To run a one-off sync instead of the daemon use:\n\u003e ```bash\n\u003e docker-compose run --rm sync sync\n\u003e ```\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `login` | Authenticate with services |\n| `logout` | Remove authentication tokens |\n| `status` | Check authentication status |\n| `sync` | Synchronize anime/manga lists |\n| `watch` | Run sync on interval |\n| `unmapped` | Show and manage unmapped entries from last sync |\n\n**Global options** (available for all commands):\n| Short | Long | Description |\n|-------|------|-------------|\n| `-c` | `--config` | Path to config file (optional, uses env vars if not specified) |\n\n**Login/Logout options:**\n| Short | Long | Description |\n|-------|------|-------------|\n| `-s` | `--service` | Service: `anilist`, `myanimelist`, `all` (default: `all`) |\n\n**Sync options:**\n| Short | Long | Description |\n|-------|------|-------------|\n| `-f` | `--force` | Force sync all entries |\n| `-d` | `--dry-run` | Dry run without making changes |\n| | `--manga` | Sync manga instead of anime |\n| | `--all` | Sync both anime and manga |\n| | `--verbose` | Enable verbose logging |\n| | `--reverse-direction` | Sync from MyAnimeList to AniList |\n| | `--offline-db` | Enable offline database for anime ID mapping (default: `true`, ignored for `--manga`) |\n| | `--offline-db-force-refresh` | Force re-download offline database |\n| | `--arm-api` | Enable ARM API for anime ID mapping (default: `false`, ignored for `--manga`) |\n| | `--arm-api-url` | ARM API base URL |\n| | `--jikan-api` | Enable Jikan API for manga ID mapping (default: `false`, ignored for anime) |\n| | `--favorites` | Sync favorites between services (requires Jikan API for MAL favorites) |\n\n**Watch options:**\n| Short | Long | Description |\n|-------|------|-------------|\n| `-i` | `--interval` | Sync interval: 1h-168h (overrides config) |\n| | `--once` | Run one sync immediately, then start the interval loop. **Without this flag the first sync is delayed by the full interval.** |\n\nInterval can be set via `--interval` flag or in `config.yaml` under `watch.interval`.\n\n**Unmapped options:**\n| Short | Long | Description |\n|-------|------|-------------|\n| | `--fix` | Interactively fix unmapped entries (ignore or map to MAL ID) |\n| | `--ignore-all` | Add all unmapped entries to ignore list |\n\nFor backward compatibility, running `anilist-mal-sync [options]` without a command will execute sync.\n\n## Configuration\n\n### Config file\n\nFull `config.yaml` example:\n\n```yaml\noauth:\n  port: \"18080\"\n  redirect_uri: \"http://localhost:18080/callback\"\nanilist:\n  client_id: \"your_client_id\"\n  client_secret: \"your_secret\"\n  auth_url: \"https://anilist.co/api/v2/oauth/authorize\"\n  token_url: \"https://anilist.co/api/v2/oauth/token\"\n  username: \"your_username\"\nmyanimelist:\n  client_id: \"your_client_id\"\n  client_secret: \"your_secret\"\n  auth_url: \"https://myanimelist.net/v1/oauth2/authorize\"\n  token_url: \"https://myanimelist.net/v1/oauth2/token\"\n  username: \"your_username\"\ntoken_file_path: \"\"  # Leave empty for default: ~/.config/anilist-mal-sync/token.json\nmappings_file_path: \"\"  # Leave empty for default: ~/.config/anilist-mal-sync/mappings.yaml\nwatch:\n  interval: \"24h\"  # Sync interval for watch mode (1h-168h), can be overridden with --interval flag\nhttp_timeout: \"30s\"  # HTTP client timeout for API requests (default: 30s)\noffline_database:\n  enabled: true\n  cache_dir: \"\"  # Default: ~/.config/anilist-mal-sync/aod-cache\n  auto_update: true\narm_api:\n  enabled: false\n  base_url: \"https://arm.haglund.dev\" # Default: https://arm.haglund.dev\nhato_api:\n  enabled: true  # Enable Hato API for ID mapping (default: true)\n  base_url: \"https://hato.malupdaterosx.moe\"  # Hato API base URL\n  cache_dir: \"\"  # Leave empty for default: ~/.config/anilist-mal-sync/hato-cache\n  cache_max_age: \"720h\"  # Cache max age (default: 720h / 30 days)\njikan_api:\n  enabled: false  # Enable Jikan API for manga ID mapping (default: false)\n  cache_dir: \"\"  # Default: ~/.config/anilist-mal-sync/jikan-cache\n  cache_max_age: \"168h\"  # Cache max age (default: 168h / 7 days)\nfavorites:\n  enabled: false  # Enable favorites synchronization (default: false)\n```\n\n## ID Mapping Strategies\n\nThe tool uses different ID mapping strategies for anime and manga, and the chain differs by direction.\n\n### Forward direction (AniList → MAL, default)\n\n**Anime** (`sync` or `sync --all`):\n1. **Manual Mapping** - User-defined AniList↔MAL mappings from `mappings.yaml`\n2. **Direct ID lookup** - If the entry already exists in your target list\n3. **Offline Database** (optional, enabled by default) - Local database from [anime-offline-database](https://github.com/manami-project/anime-offline-database)\n4. **Hato API** (optional, enabled by default) - Online API for anime/manga ID mapping\n5. **ARM API** (optional, disabled by default) - Online fallback to [arm-server](https://arm.haglund.dev)\n6. **Title matching** - Match by title similarity\n7. **API search** - Search the MAL API\n\n**Manga** (`sync --manga` or `sync --all`):\n1. **Manual Mapping** - User-defined AniList↔MAL mappings from `mappings.yaml`\n2. **Direct ID lookup** - If the entry already exists in your target list\n3. **Hato API** (optional, enabled by default) - Online API for manga ID mapping\n4. **Title matching** - Match by title similarity\n5. **Jikan API** (optional, disabled by default) - Online API for manga ID mapping via [Jikan](https://jikan.moe/) (unofficial MAL API)\n6. **API search** - Search the MAL API\n\n### Reverse direction (MAL → AniList, `--reverse-direction`)\n\n**Anime** (`sync --reverse-direction`):\n1. **Manual Mapping** - User-defined AniList↔MAL mappings from `mappings.yaml`\n2. **Direct ID lookup** - If the entry already exists in your target list\n3. **Offline Database** (optional, enabled by default)\n4. **Hato API** (optional, enabled by default)\n5. **ARM API** (optional, disabled by default)\n6. **Title matching**\n7. **MAL ID lookup** - Find AniList entry by MAL ID directly\n8. **API search** - Search the AniList API\n\n**Manga** (`sync --manga --reverse-direction`):\n1. **Manual Mapping**\n2. **Direct ID lookup**\n3. **Hato API** (optional, enabled by default)\n4. **Title matching**\n5. **Jikan API** (optional, disabled by default)\n6. **MAL ID lookup** - Find AniList entry by MAL ID directly\n7. **API search** - Search the AniList API\n\n**Notes:**\n- The offline database and ARM API are anime-only and automatically disabled when using `--manga` flag (without `--all`) to improve startup performance.\n- Hato API supports both anime and manga and is enabled by default.\n\n### Manual Mappings \u0026 Ignore Rules\n\nYou can define manual ID mappings and ignore rules in a YAML file (`mappings.yaml`):\n\n```yaml\nmanual_mappings:\n  - anilist_id: 12345\n    mal_id: 67890\n    comment: \"Season 2 mapped manually\"\nignore:\n  anilist_ids:\n    - 99999 # Title Name : reason for ignoring\n  titles:\n    - \"Some Title to Ignore\"\n```\n\nDefault location: `~/.config/anilist-mal-sync/mappings.yaml`\n\nYou can also manage ignore rules interactively:\n```bash\n# Show unmapped entries from last sync\nanilist-mal-sync unmapped\n\n# Interactively fix unmapped entries (ignore or map to MAL ID)\nanilist-mal-sync unmapped --fix\n\n# Add all unmapped entries to ignore list\nanilist-mal-sync unmapped --ignore-all\n```\n\n### Environment variables\n\nConfiguration can be provided entirely via environment variables (recommended for Docker):\n\n**Required:**\n- `ANILIST_CLIENT_ID` - AniList Client ID\n- `ANILIST_CLIENT_SECRET` - AniList Client Secret (also accepts `CLIENT_SECRET_ANILIST`)\n- `ANILIST_USERNAME` - AniList username\n- `MAL_CLIENT_ID` - MyAnimeList Client ID\n- `MAL_CLIENT_SECRET` - MyAnimeList Client Secret (also accepts `CLIENT_SECRET_MYANIMELIST`)\n- `MAL_USERNAME` - MyAnimeList username\n\n**Required for `watch` mode:**\n- `WATCH_INTERVAL` - Sync interval (e.g., `12h`, `24h`); range `1h`–`168h`. Without this (or `--interval` flag) the watch command fails.\n\n**Optional:**\n- `HTTP_TIMEOUT` - HTTP client timeout for API requests (default: `30s`, e.g., `10s`, `1m`)\n- `OAUTH_PORT` - OAuth server port (default: `18080`)\n- `OAUTH_REDIRECT_URI` - OAuth redirect URI (default: `http://localhost:18080/callback`)\n- `TOKEN_FILE_PATH` - Token file path (default: `~/.config/anilist-mal-sync/token.json`)\n- `MAPPINGS_FILE_PATH` - Path to manual mappings YAML file (default: `~/.config/anilist-mal-sync/mappings.yaml`)\n- `PUID` / `PGID` - User/Group ID for Docker volume permissions\n- `OFFLINE_DATABASE_ENABLED` - Enable offline database for anime ID mapping (default: `true`, not used for manga-only sync)\n- `OFFLINE_DATABASE_CACHE_DIR` - Cache directory (default: `~/.config/anilist-mal-sync/aod-cache`)\n- `OFFLINE_DATABASE_AUTO_UPDATE` - Auto-update database (default: `true`)\n- `HATO_API_ENABLED` - Enable Hato API for ID mapping (default: `true`, supports both anime and manga)\n- `HATO_API_URL` - Hato API base URL (default: `https://hato.malupdaterosx.moe`)\n- `HATO_API_CACHE_DIR` - Hato API cache directory (default: `~/.config/anilist-mal-sync/hato-cache`)\n- `HATO_API_CACHE_MAX_AGE` - Hato API cache max age (default: `720h` / 30 days)\n- `ARM_API_ENABLED` - Enable ARM API for anime ID mapping (default: `false`, not used for manga-only sync)\n- `ARM_API_URL` - ARM API base URL (default: `https://arm.haglund.dev`)\n- `JIKAN_API_ENABLED` - Enable Jikan API for manga ID mapping (default: `false`, not used for anime sync)\n- `JIKAN_API_CACHE_DIR` - Jikan API cache directory (default: `~/.config/anilist-mal-sync/jikan-cache`)\n- `JIKAN_API_CACHE_MAX_AGE` - Jikan API cache max age (default: `168h` / 7 days)\n- `FAVORITES_SYNC_ENABLED` - Enable favorites synchronization (default: `false`)\n\n## Favorites Synchronization\n\nFavorites sync is an optional feature that synchronizes your favorited anime and manga between AniList and MyAnimeList. It runs as a separate phase after the main status/progress synchronization.\n\n### API Limitations\n\n| Direction | Read | Write | Behavior |\n|-----------|------|-------|----------|\n| MAL → AniList | ✅ via Jikan API | ✅ via ToggleFavourite mutation | Full sync (add missing favorites) |\n| AniList → MAL | ✅ via isFavourite field | ❌ MAL API v2 has no favorites endpoint | Report only |\n\n### Enabling Favorites Sync\n\n**Via CLI flag:**\n```bash\n# Sync with favorites enabled\nanilist-mal-sync sync --favorites\n\n# Reverse sync (MAL → AniList) with favorites\nanilist-mal-sync sync --favorites --reverse-direction\n```\n\n**Via environment variable:**\n```bash\nexport FAVORITES_SYNC_ENABLED=true\nanilist-mal-sync sync\n```\n\n**Via config file:**\n```yaml\nfavorites:\n  enabled: true\n```\n\n**Via Docker:**\n```yaml\nenvironment:\n  - FAVORITES_SYNC_ENABLED=true\n```\n\nNote: The `--favorites` flag automatically enables Jikan API (required for reading MAL favorites).\n\n### Behavior\n\n#### MAL → AniList (with `--reverse-direction`)\n- Reads your MAL favorites via Jikan API (public user profile)\n- Compares with your AniList list entries\n- **Adds** missing favorites on AniList\n- **Does not remove** favorites that exist only on AniList (you may have intentionally favorited different items)\n\nExample output:\n```\n★ [Favorites] Added \"Cowboy Bebop\" to AniList favorites\n★ [Favorites] Added \"Monster\" to AniList favorites\n★ Favorites sync complete: +2 added on AniList (15 skipped)\n```\n\n#### AniList → MAL (default direction)\n- Reads your AniList favorites from list entries (via `isFavourite` field)\n- Reads your MAL favorites via Jikan API\n- Reports differences (cannot write to MAL)\n\nExample output:\n```\n★ [Favorites] anime \"Cowboy Bebop\" is only on AniList\n★ [Favorites] manga \"Berserk\" is only on MAL\n★ Favorites: 2 mismatches (AniList→MAL, report only)\n```\n\nFor detailed documentation, see [docs/favorites-sync.md](docs/favorites-sync.md).\n\n## Advanced\n\n### Install as binary (without Docker)\n\nRequires **Go 1.25+** ([download](https://go.dev/dl/)).\n\n**Option A — install from registry:**\n```bash\ngo install github.com/bigspawn/anilist-mal-sync@latest\n```\n\n**Option B — clone and build locally:**\n```bash\ngit clone https://github.com/bigspawn/anilist-mal-sync.git\ncd anilist-mal-sync\ngo build -o anilist-mal-sync .\n```\n\n**First run:**\n```bash\n# 1. Create config file\ncp config.example.yaml config.yaml\n# Edit config.yaml with your AniList and MAL credentials\n\n# 2. Authenticate (opens OAuth flow on port 18080)\nanilist-mal-sync -c config.yaml login\n\n# 3. Preview changes before syncing\nanilist-mal-sync -c config.yaml sync --dry-run --all\n\n# 4. Run sync\nanilist-mal-sync -c config.yaml sync\n```\n\nTokens are saved to `~/.config/anilist-mal-sync/token.json` by default.\n\n### Docker\n\n\u003e **docker compose vs docker-compose:** Examples use `docker-compose` (CLI v1). If your system has Docker Compose v2 (bundled with modern Docker Desktop / Engine), replace `docker-compose` with `docker compose` (no hyphen).\n\nSee [Quick Start](#quick-start-docker) for the recommended setup.\n\n**Using config file instead of environment variables:**\n\n```bash\ndocker run --rm -p 18080:18080 \\\n  -e PUID=$(id -u) -e PGID=$(id -g) \\\n  -v $(pwd)/config.yaml:/etc/anilist-mal-sync/config.yaml:ro \\\n  -v $(pwd)/tokens:/home/appuser/.config/anilist-mal-sync \\\n  ghcr.io/bigspawn/anilist-mal-sync:latest -c /etc/anilist-mal-sync/config.yaml sync\n```\n\n### Watch mode\n\nEnable continuous sync by setting `WATCH_INTERVAL` environment variable:\n\n```yaml\nenvironment:\n  - WATCH_INTERVAL=12h  # Sync every 12 hours\n```\n\nOr run watch command manually:\n```bash\ndocker-compose run --rm sync watch --interval=12h\n```\n\n**Interval limits:** 1h - 168h (7 days). `WATCH_INTERVAL` (or `--interval`) is **required** for watch mode — without it the command exits with an error.\n\n### Scheduling (non-Docker)\n\nUse your system's scheduler for periodic sync:\n\n```bash\n# Linux/macOS cron (daily at 2 AM)\n0 2 * * * /usr/local/bin/anilist-mal-sync sync\n```\n\n## Troubleshooting\n\n**\"Required environment variables not set\"**\n- Set required env vars: `ANILIST_CLIENT_ID`, `ANILIST_CLIENT_SECRET`, `ANILIST_USERNAME`, `MAL_CLIENT_ID`, `MAL_CLIENT_SECRET`, `MAL_USERNAME`\n- Or use config file with `-c /path/to/config.yaml`\n\n**Authentication fails**\n- Check redirect URL matches exactly: `http://localhost:18080/callback`\n- Verify client ID and secret are correct\n- Ensure port 18080 is not already in use\n\n**Sync appears frozen**\n- Both services have rate limits. Wait a few minutes and try again\n- Use `--verbose` to see progress\n\n**Token expired**\n- Run `anilist-mal-sync status` to check\n- Run `anilist-mal-sync login` to reauthenticate (re-authenticates both services)\n\n## Disclaimer\n\nThis project is not affiliated with AniList or MyAnimeList. Use at your own risk.\n\n## Roadmap\n\n- [ ] Sync rewatching and rereading counts\n\n## Credits\n\n- [anime-offline-database](https://github.com/manami-project/anime-offline-database) for JSON based anime dataset\n- [arm-server](https://github.com/BeeeQueue/arm-server) for API anime dataset\n- [Hato](https://github.com/Atelier-Shiori/Hato) for JSON API anime and manga\n- [Jikan](https://jikan.moe/) for unofficial MyAnimeList API\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigspawn%2Fanilist-mal-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbigspawn%2Fanilist-mal-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbigspawn%2Fanilist-mal-sync/lists"}