- Posted on
- • Artificial Intelligence
Artificial Intelligence Calendar Automation
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence Calendar Automation on Linux (with Bash)
If your calendar is a battlefield and your terminal is your home turf, this guide is for you. Imagine typing “Lunch with Sam next Friday at 12:30 for 90 minutes at Café Rio” and having an AI turn that into a real calendar event—synced across devices—right from Bash. That’s AI calendar automation, and you can build it today using open-source tools on Linux.
In this article:
Why AI-driven scheduling is worth your time
What tools you need (and how to install them with apt, dnf, and zypper)
A working Bash script that turns natural language into calendar events
Summarizing your day with AI
Optional sync to CalDAV (Nextcloud, Fastmail, etc.) using CLI tools
A simple way to automate the flow
Why automate calendars with AI?
Natural language to structure: Humans think in “tomorrow afternoon” and “every other Thursday.” AI is good at parsing that and returning structured data.
Fewer context switches: Keep it all in the terminal. No clicking through web calendars or mobile apps for basic entries and summaries.
Private and portable: Use local LLMs (via Ollama) for privacy, or swap in a cloud endpoint when you need higher accuracy.
Glue everything with Bash: Bash + CLI tools like khal/vdirsyncer/calcurse make flexible, scriptable pipelines for real-world workflows.
Prerequisites and installation
We’ll use:
khal (CLI calendar) and vdirsyncer (CalDAV sync)
curl and jq for HTTP and JSON
Optionally calcurse (TUI calendar) if you prefer it, plus import support
A local AI engine via Ollama (or a drop-in OpenAI-compatible endpoint)
Install the basics:
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y khal vdirsyncer jq curl
# optional:
sudo apt install -y calcurse
Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y khal vdirsyncer jq curl
# optional:
sudo dnf install -y calcurse
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y khal vdirsyncer jq curl
# optional:
sudo zypper install -y calcurse
Install an AI engine (Ollama: local LLMs)
Ollama runs local models and exposes a simple HTTP API—perfect for scripts.
Install Ollama:
curl -fsSL https://ollama.com/install.sh | sh
sudo systemctl enable --now ollama
Pull a model (good balance of speed/quality on modern laptops):
ollama pull llama3.1
# or smaller: ollama pull llama3.1:8b
Alternative via containers (if you prefer Podman/Docker):
Debian/Ubuntu:
sudo apt update
sudo apt install -y podman
Fedora/RHEL/CentOS Stream:
sudo dnf install -y podman
openSUSE:
sudo zypper install -y podman
Then:
podman run -d --name ollama -p 11434:11434 -v ollama:/root/.ollama ollama/ollama
podman exec -it ollama ollama pull llama3.1
Notes:
Ollama listens on http://localhost:11434
You can swap in any OpenAI-compatible REST API later by adjusting the curl call in the script.
Step 1: (Optional) Link khal to a CalDAV server with vdirsyncer
If you already use a CalDAV provider (Nextcloud, Fastmail, mailbox.org, etc.), link your local CLI calendar for two-way sync.
Create vdirsyncer config:
mkdir -p ~/.config/vdirsyncer ~/.calendars
cat > ~/.config/vdirsyncer/config <<'EOF'
[general]
status_path = "~/.vdirsyncer/status/"
[pair calendar]
a = "calendar_local"
b = "calendar_remote"
collections = ["from a", "from b"]
conflict_resolution = "b wins"
[storage calendar_local]
type = "filesystem"
path = "~/.calendars/"
fileext = ".ics"
[storage calendar_remote]
type = "caldav"
# Replace with your server’s CalDAV URL and credentials:
url = "https://yourserver.example.com/remote.php/dav/calendars/YOURUSER/"
username = "YOURUSER"
password = "APP_SPECIFIC_PASSWORD"
EOF
vdirsyncer discover
vdirsyncer sync
Create khal config and point it at that vdir:
mkdir -p ~/.config/khal
cat > ~/.config/khal/config <<'EOF'
[calendars]
[[all]]
path = ~/.calendars/
type = discover
[default]
# Replace after running `khal printcalendars` if needed
default_calendar = personal
EOF
khal printcalendars
Tip: Run khal list today to ensure it’s reading your events.
If you don’t want CalDAV sync, you can still use khal standalone and import ICS directly—it will store events locally.
Step 2: Teach your terminal to understand “human scheduling”
We’ll build a Bash script that: 1) Sends your natural-language request to an AI model 2) Receives structured JSON (title, date, start time, duration, etc.) 3) Generates an ICS event from that JSON 4) Imports it into khal (and syncs if you set up vdirsyncer)
Create ai2cal.sh:
#!/usr/bin/env bash
set -euo pipefail
# Configurable defaults
MODEL="${MODEL:-llama3.1}"
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
KHAL_CAL="${KHAL_CAL:-personal}"
usage() {
echo "Usage: $0 \"Natural language event\""
echo "Env: MODEL, OLLAMA_URL, KHAL_CAL"
exit 1
}
[[ $# -ge 1 ]] || usage
INPUT="$*"
# Ask the LLM for structured JSON. We request strict JSON via format:"json".
read -r -d '' PROMPT <<'EOF'
You are a scheduling parser. Convert the user's request into strict minified JSON:
{
"title": "string",
"date": "YYYY-MM-DD",
"start_time": "HH:MM|null",
"end_time": "HH:MM|null",
"duration_minutes": "integer|null",
"location": "string|null",
"notes": "string|null",
"allday": "boolean"
}
Rules:
- Use local (floating) times, not timezones, unless the user specifies a timezone.
- If the user specifies duration but not end_time, set duration_minutes and leave end_time null.
- If the user implies an all-day event (e.g., 'on Friday'), set allday true and times null.
- Return ONLY valid minified JSON. No extra text.
EOF
RAW=$(curl -fsS -X POST "$OLLAMA_URL/api/generate" \
-H 'Content-Type: application/json' \
-d @- <<JSON
{
"model": "$MODEL",
"prompt": "$(printf '%s\n\nInput: %s' "$PROMPT" "$INPUT" | sed 's/"/\\"/g')",
"format": "json",
"stream": false
}
JSON
)
RESP=$(jq -r '.response' <<< "$RAW")
# Extract fields
title=$(jq -r '.title' <<< "$RESP")
date=$(jq -r '.date' <<< "$RESP")
start_time=$(jq -r '.start_time' <<< "$RESP")
end_time=$(jq -r '.end_time' <<< "$RESP")
duration=$(jq -r '.duration_minutes' <<< "$RESP")
location=$(jq -r '.location // empty' <<< "$RESP")
notes=$(jq -r '.notes // empty' <<< "$RESP")
allday=$(jq -r '.allday' <<< "$RESP")
# Basic validation
if [[ -z "$title" || -z "$date" ]]; then
echo "Parser returned incomplete data. Response was:"
echo "$RESP" | jq .
exit 2
fi
# Compute end_time if needed
if [[ "$end_time" == "null" && "$duration" != "null" && "$start_time" != "null" ]]; then
end_time=$(date -d "$date $start_time + ${duration} minutes" +%H:%M)
fi
# ICS helpers
uid="$(date +%s%N)@${HOSTNAME:-localhost}"
dcompact=$(tr -d '-' <<< "$date") # YYYYMMDD
toHHMM() { tr -d ':' <<< "$1"; }
# Build ICS content
{
echo "BEGIN:VCALENDAR"
echo "VERSION:2.0"
echo "PRODID:-//ai2cal//EN"
echo "BEGIN:VEVENT"
echo "UID:$uid"
echo "SUMMARY:${title}"
[[ -n "$location" ]] && echo "LOCATION:${location}"
[[ -n "$notes" ]] && echo "DESCRIPTION:${notes}"
if [[ "$allday" == "true" || "$start_time" == "null" ]]; then
echo "DTSTART;VALUE=DATE:${dcompact}"
# All-day events typically use next-day exclusive DTEND; we’ll omit for simplicity
else
st="$(toHHMM "$start_time")"
echo "DTSTART:${dcompact}T${st}00"
if [[ "$end_time" != "null" && -n "$end_time" ]]; then
et="$(toHHMM "$end_time")"
echo "DTEND:${dcompact}T${et}00"
elif [[ "$duration" != "null" ]]; then
echo "DURATION:PT${duration}M"
fi
fi
echo "END:VEVENT"
echo "END:VCALENDAR"
} | khal import -a "$KHAL_CAL" -
echo "Event created in calendar: $KHAL_CAL"
Make it executable:
chmod +x ai2cal.sh
Try it:
./ai2cal.sh "Lunch with Sam next Friday at 12:30 for 90 minutes at Café Rio. Add note: discuss Q3 targets."
khal list nextweek
If you’re syncing via vdirsyncer:
vdirsyncer sync
Tip: If khal shows multiple calendars, run khal printcalendars and set KHAL_CAL env var, e.g.:
export KHAL_CAL=personal
Step 3: Summarize your day with AI
Turn your raw schedule into a concise brief. This reads your agenda from khal and asks the model for a short summary with priorities.
Create day_brief.sh:
#!/usr/bin/env bash
set -euo pipefail
MODEL="${MODEL:-llama3.1}"
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434}"
AGENDA=$(khal list today || true)
read -r -d '' PROMPT <<'EOF'
You are an executive assistant. Summarize today's agenda into:
- 3-5 bullet highlights with times
- Potential conflicts or tight transitions
- 2-3 focus priorities
Keep it concise.
EOF
curl -fsS -X POST "$OLLAMA_URL/api/generate" \
-H 'Content-Type: application/json' \
-d @- <<JSON | jq -r '.response'
{
"model": "$MODEL",
"prompt": "$(printf '%s\n\nAgenda:\n%s' "$PROMPT" "$AGENDA" | sed 's/"/\\"/g')",
"stream": false
}
JSON
Make it executable and run:
chmod +x day_brief.sh
./day_brief.sh
Step 4: Automate the flow
- Cron: Process a simple inbox file of natural-language entries each hour.
# crontab -e
0 * * * * /home/you/bin/process_inbox.sh >> /home/you/logs/ai2cal.log 2>&1
Example process_inbox.sh:
#!/usr/bin/env bash
set -euo pipefail
INBOX="$HOME/ai-inbox.txt"
[[ -f "$INBOX" ]] || exit 0
tmp=$(mktemp)
mv "$INBOX" "$tmp" || exit 0
while IFS= read -r line; do
[[ -n "$line" ]] || continue
"$HOME/ai2cal.sh" "$line" || echo "Failed: $line" >&2
done < "$tmp"
rm -f "$tmp"
vdirsyncer sync || true
- Systemd timers: Preferable on servers or laptops that sleep/wake frequently.
Real-world examples
- Quick capture:
./ai2cal.sh "Team retro on the first Monday of next month at 10am for 60m, Zoom link in notes."
- All-day travel:
./ai2cal.sh "Travel to Berlin on Sept 20, all day. Note: Flight LH181 at 09:15."
- Scheduling from email text:
sed -n '/Proposed meeting/,/Best regards/p' email.txt | tr '\n' ' ' | xargs -0 ./ai2cal.sh
- Daily brief:
./day_brief.sh
Optional: calcurse import instead of khal
If you prefer calcurse, you can import the same ICS content:
- Install calcurse:
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y calcurse
Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y calcurse
openSUSE (zypper):
sudo zypper install -y calcurse
Replace the final line of ai2cal.sh to save ICS and import:
} > /tmp/ai2cal.ics
calcurse -i /tmp/ai2cal.ics
rm -f /tmp/ai2cal.ics
Using a cloud LLM instead of Ollama (optional)
If you have an OpenAI-compatible endpoint:
export OPENAI_API_KEY="sk-..."
export OPENAI_BASE_URL="https://api.openai.com/v1"
Then replace the curl call in ai2cal.sh with something like:
RAW=$(curl -fsS -X POST "$OPENAI_BASE_URL/chat/completions" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H 'Content-Type: application/json' \
-d @- <<JSON
{
"model": "gpt-4o-mini",
"response_format": { "type": "json_object" },
"messages": [
{"role":"system","content":"$PROMPT"},
{"role":"user","content":"$INPUT"}
]
}
JSON
)
RESP=$(jq -r '.choices[0].message.content' <<< "$RAW")
Keep the rest of the script the same.
Troubleshooting
khal says “No calendar named …”: Run
khal printcalendarsand setexport KHAL_CAL=<name>.vdirsyncer fails auth: Use an app-specific password and verify your CalDAV URL from your provider’s docs; re-run
vdirsyncer discover.AI returns non-JSON: Ensure you’re using
format:"json"with Ollama orresponse_formatwith cloud APIs; consider simplifying the prompt.Timezones: This script uses “floating” local times. If you need strict timezone handling, enrich the JSON schema with timezone and add conversion to UTC (
date -d).
Security and privacy
Local models (Ollama) keep your text on your machine.
If using cloud APIs, treat event text as sensitive and set strong auth and logging policies.
Store secrets (like CalDAV passwords) in protected config files with 600 permissions.
Conclusion and next steps
You’ve just turned Bash into a calendar automation cockpit:
Natural language → Structured JSON → ICS → khal/vdirsyncer
Daily summaries from your terminal
Cron/systemd to automate the pipeline
Next steps:
Extend the parser to handle recurring rules (RRULE) and attendees (mailto:).
Add conflict checks (use
khal listfor the window you’re adding into).Wire this into your email or chat client for one-keystroke capture.
If this saved you clicks, share it with your team. And if you build cool additions (RRULEs, rescheduling bots, meeting notes exporters), open-source them—your future self (and the Linux community) will thank you.