Created
January 6, 2026 19:04
-
-
Save rianvdm/0e48196e2ad40447907c24a30ef251ef to your computer and use it in GitHub Desktop.
Calendar Python Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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