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. The distribution is named da-sync; the command it installs is da.

pipx install da-sync                   # isolated venv, recommended
da --version

pip install da-sync works too, but pipx gives the tool its own environment, which is what you want for something that puts a command on your PATH.

Two neighbouring PyPI names are not this project: dacli (an unrelated data-engineering tool) and da-cli (unregistered — PyPI rejects it as too similar to dacli, which is why this ships as da-sync). Since this package installs a da command and handles DeviantArt OAuth tokens, check the name.

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.1.tar.gz (224.9 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.1-py3-none-any.whl (92.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: da_sync-0.1.1.tar.gz
  • Upload date:
  • Size: 224.9 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.1.tar.gz
Algorithm Hash digest
SHA256 c3af31322f3990e4d8af534bb7c45fdd4ee7858d2a6d6ae9c3790a2c51ab7bb0
MD5 427afb58dc0249feae1463dd0615658e
BLAKE2b-256 640ffb14c7347e4f32597128e7f1caffad65cf135e493a21ad96a5a1a4d722df

See more details on using hashes here.

Provenance

The following attestation bundles were made for da_sync-0.1.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: da_sync-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 92.6 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 42022ee44e5823424e506efa09e8cd95a0f2221520d643c709ac82fecca86b61
MD5 1d07585eb4e522eaab3ff6e77aec9cf7
BLAKE2b-256 b9c9c382d3240b34ac04880c87b75e73d2693181361cf95a0fba9c7d88a50be5

See more details on using hashes here.

Provenance

The following attestation bundles were made for da_sync-0.1.1-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