Skip to main content

Sync DeviantArt galleries to local folders — zero-dependency Python CLI with OAuth 2.1 PKCE, a SQLite index, and scheduled macOS syncs.

Project description

da-cli

CI Python 3.10+ License: MIT Code style: ruff Type checked: mypy Coverage: 92%+ Status: Beta Dependencies: 0 runtime

Sync your DeviantArt gallery to a local folder from the command line — a backup of the art you watch, kept current. Zero runtime dependencies: the whole tool is the Python 3.10+ standard library. A local SQLite index means a re-run costs one API call when nothing new was posted, and launchd (macOS) or a systemd timer (Linux) keeps it running unattended. Plus search and browse helpers.

New to da-cli? Follow the Setup Guide — it walks you through everything from install to first sync in about 10 minutes, with screenshots. Status: Beta — the sync and search flows are stable and covered by 868 tests. macOS Keychain integration is production-ready; Linux Secret Service support is planned. See CHANGELOG.md.

Documentation

I want to… Page
install it and download my first art Getting started
understand a command properly Command guides
look up a command or a flag Command reference
fix an error I just got Troubleshooting
sync automatically every day Scheduling
change a setting Configuration
script it or monitor it Scripting
know what it does with my OAuth secret Security model
contribute CONTRIBUTING · ARCHITECTURE

Full index: docs/

Quick tour

Each group below has a page documenting every flag, what it writes and how it behaves on a second run — linked after the block.

da --version                            # print version
da auth                                 # one-time browser-based login
da auth logout                          # forget local tokens (DA-side authorisation persists; revoke at deviantart.com)
da whoami                               # confirm token + identity
da refresh                              # force-refresh the access token

da sync feed                            # incremental: pull new content from your watch feed
da sync feed --no-mature                # ...excluding mature content (included by default)
da sync artist <username>               # walk one artist's full gallery
da sync watched                         # walk every watched user's gallery
da sync watched --via-feed              # discover artists via watch feed (works with `browse` scope alone)
da sync feed --jitter 0.4               # randomise sleeps by ±40% — avoids burst patterns

da search tag nature                    # browse by tag
da search topic digitalart              # browse a curated DA topic
da search topics                        # list all valid topics
da search user deviantart               # resolve a username
da daily 2026-01-15                     # daily-deviation picks for a date

da user profile <username>              # who is this person
da deviation show <deviationid>         # full metadata for one deviation
da deviation morelikethis <id>          # related deviations via DA's recommender
da watch list                           # show who you watch (needs `user` scope)

da config show                          # inspect config + resolved paths
da config path                          # just the file locations
da config set <key> <value>             # store; secrets go to Keychain on macOS
da config get <key> [--unmask]          # read back (secrets masked unless --unmask)
da config unset <key>                   # remove

da index show                           # synced-index stats (rows, top artists)
da index rebuild                        # walk dest, re-import (idempotent)
da diagnose                             # end-to-end health check
da bench                                # synthetic sync benchmark (no network)

da search popular and da search newest were retired (DA dropped the underlying endpoints). Use da search topic <name>, da search tag <tag>, or da daily instead — both subcommands now exit 2 with an actionable hint.

Read more, by area:

  • Authenticationda auth, auth logout, auth status, whoami, refresh. The PKCE flow, scopes, and the 90-day refresh-token ceiling.
  • Syncing artsync feed, sync artist, sync watched. What each mode walks, the checkpoint, resuming an interrupted backfill, pacing, and time budgets.
  • Searching and browsingsearch tag, search topic, daily. Read-only; downloads nothing.
  • Inspecting users and deviationsuser profile, deviation show, watch list. How to find the id or username a sync command needs.
  • Configuration commandsconfig show, set, get, unset. Where each setting lives and which source wins.
  • Index, health and benchmarkingindex show, index rebuild, diagnose, bench. How to tell whether a scheduled run is quietly failing.

Install

da-cli is a stdlib-only Python package with no runtime dependencies. Two install paths:

Option A — git clone + shim (zero global state, recommended for developers):

git clone https://github.com/FZ2000/da-cli.git ~/Documents/da-cli
cd ~/Documents/da-cli
./install.sh                           # copies da + the dacli package to ~/.local/share/da-cli/ and symlinks ~/.local/bin/da → that copy

Option B — pip install. Not published yet. Use Option A for now.

When it is published the distribution will be named da-sync, not da-cli:

pipx install da-sync                   # the command is still `da`

Two names on PyPI are close to this project and are not it: dacli (an unrelated data-engineering tool) and da-cli (unregistered). Since this package installs a da command onto your PATH and handles DeviantArt OAuth tokens, check the name before installing anything.

Python 3.10+ required (uses argparse.BooleanOptionalAction and X | None syntax). No third-party runtime dependencies.

First-time setup

New? Follow the Setup Guide instead — it has screenshots and walks through every step from zero.

Quick version for experienced users:

  1. Create a Confidential OAuth app at https://www.deviantart.com/developers/ → Register Your Application. Set the Redirect URI Whitelist to https://localhost:8765/ (exactly — trailing slash matters). Manage your apps at https://www.deviantart.com/studio/apps.

  2. Configure da-cli:

    da config set client_id 12345
    da config set client_secret <YOUR_SECRET>
    da config set destination ~/Pictures/DA
    da auth
    da whoami
    

The CLI auto-refreshes the access token before each call; you only repeat da auth if you revoke the app or the refresh token expires (every 90 days — da diagnose warns 14 days before).

Scheduled sync

./install_schedule.sh              # macOS: daily at 03:00 via launchd

On macOS this installs a launchd user agent; the script writes the plist and loads it. On Linux, use a systemd user timer or cron. Both are covered, with the Full Disk Access step macOS needs and the enable-linger step systemd needs, in docs/guides/scheduling.md.

Troubleshooting

da diagnose checks configuration, credentials, the destination folder, the index, and the scheduled job, and tells you what is wrong.

For specific errors — a redirect-URI mismatch, an expired refresh token, a scheduled run that silently does nothing — see docs/guides/troubleshooting.md.

Security model

This is documented separately in docs/explanation/security.md; to report a vulnerability see SECURITY.md. Short version:

  • Secrets (client_secret) live in macOS Keychain (or, on other platforms, in a 0600-permissioned config file). Never in this repo, never in git history, never in command-line arguments visible to ps.
  • Tokens (access + refresh) live in ~/.local/state/da-cli/state.json (0600). Refreshing happens automatically before every API call.
  • Network: all DA traffic is HTTPS. Image downloads from wixmp CDN are also HTTPS.
  • PKCE is mandatory: the CLI generates a fresh PKCE code_verifier/code_challenge pair for every da auth run. The verifier never leaves your machine; only the SHA-256 hash is sent in the auth URL.
  • No telemetry, no third-party code at runtime. Stdlib only — urllib, argparse, json, hashlib, subprocess (for the macOS security keychain helper).
  • .gitignore excludes config.json, state.json, sync.log, *.log, and *.tmp — secrets-bearing files cannot be committed by accident.

Examples

The examples/ directory has runnable recipes: a post-sync webhook, a cron health check, a CSV export, and a topic browser that lists what a curated DA topic holds. Each is a short shell or Python script you can copy and edit.

Contributing

Style: stdlib only at runtime. If a feature would need a third-party library, open an issue first. See CONTRIBUTING.md for the lint/type/test bar (every change must pass ruff check, ruff format --check, mypy dacli, and pytest with ≥92 % coverage). See ARCHITECTURE.md for a map of the package.

The Makefile wraps the four-command pipeline:

make dev-setup    # one-time venv + dev-deps install
make check        # ruff + ruff format --check + mypy + pytest (what CI runs)

Pre-commit hooks mirror CI locally — install once with pip install pre-commit && pre-commit install, then every git commit runs ruff / mypy / markdownlint / gitleaks. Config in .pre-commit-config.yaml.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

da_sync-0.1.0.tar.gz (224.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

da_sync-0.1.0-py3-none-any.whl (92.5 kB view details)

Uploaded Python 3

File details

Details for the file da_sync-0.1.0.tar.gz.

File metadata

  • Download URL: da_sync-0.1.0.tar.gz
  • Upload date:
  • Size: 224.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for da_sync-0.1.0.tar.gz
Algorithm Hash digest
SHA256 21ed37465900ad32d382a2448d5ef11eac2b8266ffebc5ee167db179652f4f18
MD5 b8c2ec9ca28c1fa165d49382444a706a
BLAKE2b-256 8c469f0ffd1310c7efba3ce3a2510dac182ae53ca624d13a5533e58c1b211a36

See more details on using hashes here.

Provenance

The following attestation bundles were made for da_sync-0.1.0.tar.gz:

Publisher: release.yml on FZ2000/da-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file da_sync-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: da_sync-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 92.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for da_sync-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 74a359616948ee5c6bbd21de48f31df975124acf2384a2f9317b6158674711b2
MD5 169686fc5f6be2b3755caf2ae8c51c49
BLAKE2b-256 52e53c883c53811131abd516d8d68c70e27b9c9f217d1be68408350d7737d92a

See more details on using hashes here.

Provenance

The following attestation bundles were made for da_sync-0.1.0-py3-none-any.whl:

Publisher: release.yml on FZ2000/da-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page