Skip to content

Instantly share code, notes, and snippets.

@rianvdm
Created January 6, 2026 19:04
Show Gist options
  • Select an option

  • Save rianvdm/0e48196e2ad40447907c24a30ef251ef to your computer and use it in GitHub Desktop.

Select an option

Save rianvdm/0e48196e2ad40447907c24a30ef251ef to your computer and use it in GitHub Desktop.
Calendar Python Script
#!/usr/bin/env python3
"""
Calendar briefing generator.
Usage:
python scripts/calendar_briefing.py # Tomorrow
python scripts/calendar_briefing.py --today # Today
python scripts/calendar_briefing.py --days 3 # Next 3 days
python scripts/calendar_briefing.py --week # Next 7 days
python scripts/calendar_briefing.py --save # Save to work/cloudflare/briefings/
"""
import argparse
import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()
from google.oauth2.credentials import Credentials
from googleapiclient.discovery import build
import anthropic
def get_my_response_status(event: dict) -> str:
"""Get the current user's response status for this event."""
attendees = event.get("attendees", [])
for attendee in attendees:
if attendee.get("self", False):
return attendee.get("responseStatus", "")
return ""
def is_declined(event: dict) -> bool:
"""Check if the current user has declined this event."""
return get_my_response_status(event) == "declined"
def get_calendar_events(days_ahead: int = 1, num_days: int = 1) -> list[dict]:
"""Fetch calendar events from Google Calendar."""
creds = Credentials(
token=None,
refresh_token=os.environ["GOOGLE_REFRESH_TOKEN"],
client_id=os.environ["GOOGLE_CLIENT_ID"],
client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
token_uri="https://oauth2.googleapis.com/token",
)
service = build("calendar", "v3", credentials=creds)
# Use local time for date calculations, not UTC
now = datetime.now().astimezone()
start = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=days_ahead)
end = start + timedelta(days=num_days)
events = (
service.events()
.list(
calendarId="primary",
timeMin=start.isoformat(),
timeMax=end.isoformat(),
singleEvents=True,
orderBy="startTime",
)
.execute()
)
# Filter out declined events
all_events = events.get("items", [])
return [e for e in all_events if not is_declined(e)]
def format_event(event: dict) -> str:
"""Format a single event for the prompt."""
start = event["start"].get("dateTime", event["start"].get("date"))
summary = event.get("summary", "(No title)")
# Parse time for readability, include date for multi-day briefings
if "T" in start:
dt = datetime.fromisoformat(start.replace("Z", "+00:00"))
time_str = dt.strftime("%A %b %d, %H:%M") # e.g., "Friday Jan 02, 09:00"
else:
dt = datetime.fromisoformat(start)
time_str = dt.strftime("%A %b %d") + " (All day)"
# Include attendees if present
attendees = event.get("attendees", [])
if attendees:
names = [a.get("displayName", a.get("email", "")) for a in attendees[:5]]
attendee_str = f" (with {', '.join(names)})"
if len(attendees) > 5:
attendee_str = f" (with {', '.join(names)} +{len(attendees) - 5} others)"
else:
attendee_str = ""
# Flag tentative meetings
response_status = get_my_response_status(event)
status_str = " [TENTATIVE]" if response_status == "tentative" else ""
return f"- {time_str}: {summary}{attendee_str}{status_str}"
def generate_briefing(events: list[dict], period: str) -> str:
"""Generate briefing using Claude."""
client = anthropic.Anthropic()
# Load personal context
script_dir = Path(__file__).parent.parent
context_dir = script_dir / "context"
about_me = ""
if (context_dir / "about-me.md").exists():
about_me = (context_dir / "about-me.md").read_text()
personalization = ""
if (context_dir / "personalization-instructions.md").exists():
personalization = (context_dir / "personalization-instructions.md").read_text()
if not events:
events_text = "No meetings scheduled."
else:
events_text = "\n".join(format_event(e) for e in events)
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system=f"""You are my calendar briefing assistant.
{personalization}
Context about me:
{about_me}
Your job:
1. Summarize what's on my calendar
2. Flag meetings that could be async (status updates, FYIs)
3. Note any prep I should do
4. Warn if the day looks overloaded
5. Suggest what to delegate or decline if needed
Keep it short. No fluff. Use bullet points.""",
messages=[
{
"role": "user",
"content": f"Here's my calendar for {period}:\n\n{events_text}\n\nBrief me.",
}
],
)
return response.content[0].text
def main():
parser = argparse.ArgumentParser(description="Generate calendar briefing")
parser.add_argument("--today", action="store_true", help="Brief on today")
parser.add_argument("--days", type=int, default=1, help="Number of days to look ahead")
parser.add_argument("--week", action="store_true", help="Brief on next 7 days")
parser.add_argument("--save", action="store_true", help="Save to work/cloudflare/briefings/")
args = parser.parse_args()
# Determine time range
if args.today:
days_ahead = 0
num_days = 1
period = "today"
elif args.week:
days_ahead = 0
num_days = 7
period = "this week"
else:
days_ahead = args.days
num_days = 1
period = "tomorrow" if days_ahead == 1 else f"in {days_ahead} days"
events = get_calendar_events(days_ahead, num_days)
briefing = generate_briefing(events, period)
if args.save:
script_dir = Path(__file__).parent.parent
now = datetime.now()
# Structure: briefings/YYYY/MM/YYYY-MM-DD.md
briefings_dir = script_dir / "work" / "cloudflare" / "briefings" / now.strftime("%Y") / now.strftime("%m")
briefings_dir.mkdir(parents=True, exist_ok=True)
date_str = now.strftime("%Y-%m-%d")
output_file = briefings_dir / f"{date_str}.md"
with open(output_file, "w") as f:
f.write(f"# Briefing for {period}\n\n")
f.write(f"Generated: {now.strftime('%Y-%m-%d %H:%M')}\n\n")
f.write(briefing)
print(f"Saved to {output_file}")
else:
print(briefing)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment