Skip to content

Repository files navigation

Release Docker CI CodeQL Dependabot Python 3.13+ GHCR

Any.down

Backup your Any.do tasks to JSON and Markdown.

🙏 Acknowledgments

Flow inspired by Any.do's own efficient web implementation, ensuring this client remains respectful of Any.do's infrastructure while providing useful backup capabilities that do not currently exist on the official site.

This project is created as a tribute to Any.do's excellent task management service.

🌟 Key Features

  • 🛡️ Server-Friendly: Designed to minimize impact on Any.do's infrastructure with smart change detection and incremental sync
  • 🔐 Secure Authentication: Session persistence with email 2FA support
  • 📊 Multiple Export Formats: JSON and Markdown exports

Quick Start

Prerequisites

  • uv
  • Any.do account

Setup & Run

git clone <repository-url>
cd anydo-api
uv sync
uv run anydown

On first run, the script will prompt you to create a config.json with your Any.do credentials. You'll then receive a 2FA code by email to paste in.

Credentials can also be supplied via environment variables (ANYDO_EMAIL, ANYDO_PASSWORD) or the config file directly.

CLI Options

uv run anydown                  # Smart sync (incremental when possible)
uv run anydown --full-sync      # Force full sync
uv run anydown --quiet          # Reduce output
uv run anydown --debug          # Verbose debug logging

Watch mode

Keep the process running and sync on a recurring schedule:

uv run anydown --watch                              # Sync every 90 ± 10 minutes (default)
uv run anydown --watch --watch-interval 60          # Change base interval to 60 minutes
uv run anydown --watch --watch-interval 60 --watch-jitter 5   # Tighter jitter
uv run anydown --watch --full-sync                  # Force full sync each time

The jitter is re-randomised each sleep, so intervals vary naturally (e.g. 82, 97, 88 min) rather than firing at a fixed cadence. Press Ctrl+C to stop.

Note: The Docker setup uses the same --watch mode by default (see Docker below).

Utility Commands

uv run anydown-debug                # Troubleshoot login issues
uv run anydown-dupes                # Find duplicate tasks (dry run)
uv run anydown-dupes --delete       # Fresh-sync, confirm, then delete via API
uv run anydown-dupes --delete --yes # Skip confirmation prompt
uv run anydown-dupes --keep newest  # Keep newest copy instead of oldest

Docker

Run continuously with jittered watch-mode sync (default: every 90 ± 10 minutes):

docker compose pull
docker compose up -d

This expects:

  • config.json in the repo root (mounted read-only)
  • outputs/ directory will be created for exports

Images are published to ghcr.io/aioue/any.down on each push to main (:latest) and on version tags (:v1.2.3). For local development you can still docker build -t anydown . and set image: anydown in docker-compose.yml.

The container runs anydown --watch as its main process. Adjust scheduling with ANYDOWN_WATCH_INTERVAL and ANYDOWN_WATCH_JITTER in docker-compose.yml. Session state is persisted in a Docker volume. Timezone is autodetected from the host via /etc/localtime.

HTTP API (agent exports)

When ANYDOWN_API_ENABLED=1 (default in Docker), a lightweight JSON API runs on port 8080 alongside watch mode:

Method Path Description
GET /health Liveness check
GET /agent or /api/agent Latest outputs/agent/latest.json payload
GET /agent?live=1 Run sync first, then return agent JSON
POST /sync or /api/sync Trigger sync and return agent JSON

Optional bearer auth: set ANYDOWN_API_TOKEN and send Authorization: Bearer <token>.

curl -s http://localhost:8080/health
curl -s http://localhost:8080/agent | jq '.pending_tasks'
curl -X POST http://localhost:8080/sync

See AGENT_API_HANDOFF.md for homelab deployment details and agent integration notes.

To override the timezone sent to the Any.do API, set ANYDO_TIMEZONE in your environment or docker-compose.yml.

Configuration

config.json (auto-created on first run, gitignored):

{
  "email": "your@email.com",
  "password": "your_password",
  "save_raw_data": true,
  "auto_export": true,
  "text_wrap_width": 80,
  "dedup_keep": "oldest",
  "rotate_client_id": false
}

dedup_keep controls which copy anydown-dupes --delete preserves: "oldest" (default) or "newest". Tasks are only considered duplicates when their title, list, parent task, note, and subtasks all match exactly.

rotate_client_id controls whether a new random device ID is generated on every run. The default (false) reuses the persisted ID so Any.do recognises the same device, which reduces unnecessary 2FA prompts.

Manual Session Setup

If you hit login issues (2FA complications, rate limiting), you can extract a session cookie from your browser:

  1. Open Any.do in your browser, ensure you're logged in
  2. Open DevTools (F12) > Application > Cookies > https://any.do
  3. Copy the SPRING_SECURITY_REMEMBER_ME_COOKIE value
  4. Create session.json (see session.json.example for the template)

Export Output

outputs/
├── agent/             # Compact JSON for agents (~70 KB)
│   └── latest.json    # Stable path — pending tasks with IDs
├── raw-json/          # Complete API responses (~900 KB)
│   └── latest.json
└── markdown/          # Formatted task tables (~45 KB, no IDs)
    └── latest.md

Files are timestamped (YYYY-MM-DD_HHMM-SS_*) and only created when data has actually changed (SHA-256 hash comparison). latest.* files are always refreshed on export.

Development

uv sync                         # Install all deps (including dev)
uv run pytest -v                # Run tests
uv run pytest -v --cov=anydown  # With coverage
uv run ruff check .             # Lint
uv run ruff check --fix .       # Auto-fix lint
uv run ruff format .            # Format

Or via Make:

make test
make lint
make format

Project Structure

anydo-api/
├── src/anydown/
│   ├── __init__.py
│   ├── client.py           # API client library
│   ├── cli.py              # Main CLI entry point
│   ├── debug_login.py      # Login troubleshooting
│   └── find_duplicates.py  # Duplicate finder & remover
├── tests/
├── pyproject.toml          # Config, deps, tool settings
├── uv.lock                 # Locked dependency versions
├── Dockerfile
├── docker-compose.yml
└── entrypoint.sh

How It Works

  • Smart sync: Uses incremental sync to check if anything changed since the last run; only performs a full download when changes are detected
  • Session persistence: Saves auth session to avoid repeated 2FA prompts
  • Change detection: SHA-256 hashing of exported data prevents writing duplicate files
  • Rate limiting: Client-side cooldown prevents full syncs more than once per minute
  • Compression: Requests gzip/br/zstd; decompression handled by the HTTP library
  • Retry logic: Exponential backoff with automatic retries on 429/5xx

Made with ❤️ for the Any.do community

About

A Python client for Any.do markdown export

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages