dav-manager is a single-binary Go CLI for cleaning, normalizing, auditing, and reclaiming a Radicale-backed CardDAV address book without turning the job into a GUI slog.
It is built for the real maintenance work:
- normalize names, phones, and emails
- move low-value or stressful contacts into structured local buckets with VCF backups
- reconcile a curated markdown contact table back into CardDAV
- force stubborn mobile clients to notice contact changes
- keep photos, UIDs, and refresh behavior under operator control
Fastest local path:
git clone https://github.com/avikalpa/dav-manager.git
cd dav-manager
go build -o bin/dav ./...Release path:
- push a semver tag (
vX.Y.Z) - GitHub Actions publishes Linux, macOS, and Windows binaries on the Releases page
Normal contact apps are decent at casual editing and bad at serious cleanup.
The hard jobs are usually:
- a few thousand contacts accumulated across years
- inconsistent names and structured
Nfields - mixed phone formats and duplicate entries
- contacts you do not want to delete forever, but do not want in the live book
- DAVx5, Android, or WhatsApp refusing to notice edits cleanly
dav-manager treats the address book like real operator data:
- fetch it
- inspect it
- normalize it
- move unwanted cards into explicit buckets
- back everything up in VCF form
- force downstream clients to refresh when needed
- Copy
.env.exampleto.envand fill:RADICALE_BASE_URLRADICALE_COLLECTIONRADICALE_USERRADICALE_PASSUN_CONTACTSPHOTO_MAPENABLE_GRAVATAR
- Build:
go build -o bin/dav ./...
- Inspect the live address book:
bin/dav contacts fetch
- Inspect the bucketed contacts:
bin/dav contacts fetch --un-contacts
bin/dav contacts fetch
bin/dav contacts fetch --touch-allUse --touch-all when you want a broad client refresh after cleanup work.
bin/dav contacts add \
--name "Jane Doe" \
--emails [email protected] \
--phones "+1 4803957551" \
--note "Friend"bin/dav contacts update \
--name "Jane Doe" \
--new-name "Jane D." \
--phones "+1 4803957551,+91 9876543210"For numbers you resolve from a lookup (CallerIDLookup: police, call-centres, delivery, spam) that must NOT enter the phone-synced address book, write an enriched card straight into a bucket:
bin/dav contacts add --bucket calleridlookup \
--name "Bangalore City Police" \
--phones "+91 80 22942222" \
--categories "calleridlookup,police" \
--org "Karnataka Police" \
--photo "https://example/dp.jpg" \
--note "source: calleridlookup 2026-07-24"The card lands only in $UN_CONTACTS/calleridlookup/…vcf. This path makes no server call and needs no
RADICALE_* credentials — it is a pure local write. Promote to the live book later with restore
if the contact turns out to be real. The enrichment flags (--categories, --org, --url,
--photo) work on the Radicale add path too.
Delete with an explicit VCF backup:
bin/dav contacts delete \
--name "Noise Lead" \
--vcf "$UN_CONTACTS/psychology/noise-lead.vcf"Or move directly into a bucket:
bin/dav contacts move \
--name "Vendor X" \
--bucket corporate \
--new-name "Vendor X (2019)"Restore later:
bin/dav contacts restore \
--name "Vendor X (2019)" \
--bucket corporateKeep a table like example-table.md, then apply it:
bin/dav contacts sync \
--source docs/examples/example-table.md \
--apply \
--touchThis is the strongest workflow when you want to review a contact pass in one human-readable document before writing it back.
bin/dav contacts fix-names --apply
bin/dav contacts refresh-uids --apply
bin/dav contacts fetch --touch-allUse this sequence when Android, DAVx5, or WhatsApp are lagging behind renamed or recreated cards.
bin/dav contacts photos --apply --map photo-map.json --gravatarThe photo map is a simple JSON object:
{
"Jane Doe": "/abs/path/jane.jpg"
}UN_CONTACTS is the safety valve for the address book. It keeps pruned contacts in explicit folders rather than deleting them into oblivion.
Typical buckets:
psychologycorporatelost-in-timeneutralemail_onlyarchivecalleridlookup— machine-discovered identities (police / call-centre / delivery / spam), captured byadd --bucket calleridlookupand kept out of the phone-synced live book on purpose
This gives you a practical reclaim loop:
- fetch the live book
- move stale or draining contacts into buckets
- normalize what remains
- sync from markdown for higher-trust curated passes
- force client refresh
- periodically clean bucket hygiene
- Naming, phone, email, and metadata rules live in STANDARDS.md
- Operational examples live in docs/WORKFLOWS.md
- Markdown-table import shape lives in docs/examples/example-table.md
- GitHub Actions builds release binaries for Linux, macOS, and Windows on semver tags
- Release pages should use curated changelog entries, not autogenerated notes
CHANGELOG.mdis the second product surface after this README and should explain what got safer, faster, or easier for a serious operator
Requirements:
- Go 1.21+
- no Python dependency
Useful commands:
go build -o bin/dav ./...
gofmt -w .- code: Apache-2.0, see LICENSE
- Markdown documentation: CC BY-SA 4.0, see LICENSE-CC-BY-SA-4.0