da-cli
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 popularandda search newestwere retired (DA dropped the underlying endpoints). Useda search topic <name>,da search tag <tag>, orda dailyinstead — both subcommands now exit 2 with an actionable hint.
Read more, by area:
- Authentication —
da auth,auth logout,auth status,whoami,refresh. The PKCE flow, scopes, and the 90-day refresh-token ceiling. - Syncing art —
sync feed,sync artist,sync watched. What each mode walks, the checkpoint, resuming an interrupted backfill, pacing, and time budgets. - Searching and browsing —
search tag,search topic,daily. Read-only; downloads nothing. - Inspecting users and deviations —
user profile,deviation show,watch list. How to find the id or username a sync command needs. - Configuration commands —
config show,set,get,unset. Where each setting lives and which source wins. - Index, health and benchmarking —
index 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:
-
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. -
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 tops. - 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_challengepair for everyda authrun. 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 macOSsecuritykeychain helper). .gitignoreexcludesconfig.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.
Metadata
Release files for da-sync 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| da_sync-0.1.1.tar.gz | 224.9 kB | With provenance | Details for da_sync-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| da_sync-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for da_sync-0.1.1-py3-none-any.whl |
Total release size: 317.4 kB
Release files / da_sync-0.1.1.tar.gz
| Download URL | da_sync-0.1.1.tar.gz |
|---|---|
| Size | 224.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c3af31322f3990e4d8af534bb7c45fdd4ee7858d2a6d6ae9c3790a2c51ab7bb0
|
|
BLAKE2b-256 checksum How to use checksums |
640ffb14c7347e4f32597128e7f1caffad65cf135e493a21ad96a5a1a4d722df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.
Transparency logRelease files / da_sync-0.1.1-py3-none-any.whl
| Download URL | da_sync-0.1.1-py3-none-any.whl |
|---|---|
| Size | 92.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
42022ee44e5823424e506efa09e8cd95a0f2221520d643c709ac82fecca86b61
|
|
BLAKE2b-256 checksum How to use checksums |
b9c9c382d3240b34ac04880c87b75e73d2693181361cf95a0fba9c7d88a50be5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.
Transparency logSecurity for da-sync 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for da_sync-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.