The mpd for your CalDAV calendars.
A lightweight background daemon that syncs CalDAV calendars (Nextcloud, Baïkal, iCloud, Fastmail, Radicale, etc.), keeps a local SQLite cache, sends native desktop notifications for upcoming events, and exposes a dead-simple CLI + optional TUI.
No GUI. No Electron. No web UI. No AI. Just a daemon and its clients.
Security note: Passwords can be stored in the OS keychain by setting
CALD_PASSWORDenv var instead of writing it in the config file. See Configuration for details.
- 🔄 Background sync — Periodic CalDAV sync every 5 min (configurable)
- 📴 Offline-first — Local SQLite cache, works without network
- 🔔 Desktop notifications — Native alerts N minutes before events
- 🖥️ CLI —
cald today,cald week,cald add "Dentist 3pm",cald search foo - 📺 TUI — Interactive agenda viewer (
cald tui) - 📊 Bar module —
cald nextfor polybar/waybar integration - 🔒 Privacy-first — Data stays on your machine
- ⚡ Fast — <50 MB RAM, starts in <100 ms
- 🌐 Multi-provider — Works with Nextcloud, iCloud, Fastmail, Baïkal, Radicale, and more
- 🦀 Rust — Static binary, cross-platform, no runtime deps
# Install (from source)
cargo install --path .
# Or clone and build
git clone https://github.com/cald/cald.git
cd cald && cargo build --release
# Setup config
cald setup
# Edit ~/.config/cald/config.toml with your CalDAV credentials
$EDITOR ~/.config/cald/config.toml
# First sync
cald sync
# Check your calendar
cald today
cald week
# Start the daemon (sync + notifications)
cald daemoncald today Show today's events
cald week Show this week's events
cald next Show next event (for bar modules)
cald add "Dentist 3pm" Add a new event
cald add "Conf" -A -s "2024-06-15" All-day event
cald search "meeting" Search events
cald status Show sync status and cache info
cald sync One-time sync with server
cald daemon Start background daemon
cald tui Interactive TUI agenda
cald setup Create config file
# Quick add (defaults to next hour)
cald add "Team standup"
# With specific time
cald add "Dentist" -s "3pm"
cald add "Lunch" -s "12:30" -e "1:30pm"
# Tomorrow
cald add "Sprint review" -s "tomorrow 10am"
# All-day event
cald add "Conference" -A -s "2024-06-15"
# With location
cald add "Meeting" -s "2pm" -l "Room 405"| Input | Meaning |
|---|---|
3pm |
3:00 PM today |
9:30am |
9:30 AM today |
15:00 |
3:00 PM today (24h) |
tomorrow |
Tomorrow 9:00 AM |
tomorrow 2pm |
Tomorrow 2:00 PM |
2024-06-15 |
June 15 at 9:00 AM |
2024-06-15 3pm |
June 15 at 3:00 PM |
now |
Right now |
Config file: ~/.config/cald/config.toml
# CalDAV server URL
server_url = "https://cloud.example.com/remote.php/dav"
# Credentials
username = "you@example.com"
password = "" # Or use CALD_PASSWORD env var (takes precedence)
# Sync interval in seconds (default: 300 = 5 min)
sync_interval = 300
# Notify N minutes before events (default: 10)
notify_minutes = 10
# Enable desktop notifications (default: true)
notifications = true
# Calendar filter (empty = all calendars)
calendars = []
# Accept self-signed/invalid TLS certificates (for self-hosted servers)
# Default: false
allow_invalid_certs = false| Variable | Description |
|---|---|
CALD_PASSWORD |
Override config password (takes precedence over config file). Useful for keyring integration or CI/CD. |
RUST_LOG |
Set log level (e.g., cald=info, cald=debug) |
| Provider | server_url |
|---|---|
| Nextcloud | https://cloud.example.com/remote.php/dav |
| iCloud | https://caldav.icloud.com |
| Fastmail | https://caldav.fastmail.com/dav |
| Baïkal | https://baikal.example.com/dav.php |
| Radicale | https://radicale.example.com/ |
Add to your config.ini:
[module/cald]
type = custom/script
exec = cald next
interval = 60
click-left = cald tuiAdd to your config:
"custom/cald": {
"exec": "cald next",
"interval": 60,
"on-click": "cald tui"
}[cald]
command=cald next
interval=60# Copy the service file
cp contrib/cald.service ~/.config/systemd/user/
# Edit the path if needed
sed -i "s|%h/.cargo/bin/cald|$(which cald)|" ~/.config/systemd/user/cald.service
# Enable and start
systemctl --user daemon-reload
systemctl --user enable --now cald# Copy the plist
cp contrib/com.cald.daemon.plist ~/Library/LaunchAgents/
# Load
launchctl load ~/Library/LaunchAgents/com.cald.daemon.plistcald daemon (background)
├── Sync loop (every 5 min)
│ ├── PROPFIND → discover calendars
│ ├── REPORT → fetch events
│ └── PUT → push local changes
├── Notification loop (every 1 min)
│ └── Check upcoming events → desktop notify
└── Graceful shutdown (Ctrl+C / SIGTERM)
cald CLI (foreground)
├── today/week/next → read SQLite cache
├── add → write SQLite + push to server
├── search → query SQLite
├── sync → one-time full sync
└── tui → ratatui interactive viewer
| File | Path |
|---|---|
| Config | ~/.config/cald/config.toml |
| Cache | ~/.local/share/cald/cache.db |
# Requirements: Rust 1.70+
git clone https://github.com/cald/cald.git
cd cald
cargo build --release
# Binary at ./target/release/cald
# Optional: install to ~/.cargo/bin
cargo install --path .| Metric | CalD | Thunderbird | Nextcloud Web |
|---|---|---|---|
| RAM usage | ~15 MB | ~400 MB | ~200 MB (tab) |
| Startup | <100 ms | ~5 s | ~3 s (load) |
| Binary size | ~8 MB | ~250 MB | N/A |
| Offline | ✅ | ❌ |
- ✅ Nextcloud (tested)
- ✅ iCloud (tested)
- ✅ Fastmail
- ✅ Baïkal
- ✅ Radicale
- ✅ DAViCal
- ✅ Synology Calendar
- ✅ Any RFC 4791 compliant CalDAV server
MIT
CalD — mpd, but your calendar never goes offline.