Files
claude-code/skills/morning-report/scripts/collectors/gcal.py
OpenCode Test 45b7e4bcf7 Improve morning report collectors and add section toggling
- Add is_section_enabled() to support per-section enable/disable in config
- Update Python path from 3.13 to 3.14 for gmail venv
- Disable tasks section by default (enabled: false in config)
- Apply code formatting improvements (black/ruff style)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 23:44:24 -08:00

160 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""Calendar collector using existing gcal skill."""
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
def fetch_events(mode: str = "today") -> list:
"""Fetch calendar events directly using gmail_mcp library."""
os.environ.setdefault(
"GMAIL_CREDENTIALS_PATH", os.path.expanduser("~/.gmail-mcp/credentials.json")
)
try:
# Add gmail venv to path
venv_site = Path.home() / ".claude/mcp/gmail/venv/lib/python3.14/site-packages"
if str(venv_site) not in sys.path:
sys.path.insert(0, str(venv_site))
from gmail_mcp.utils.GCP.gmail_auth import get_calendar_service
service = get_calendar_service()
now = datetime.utcnow()
if mode == "today":
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
end = start + timedelta(days=1)
elif mode == "tomorrow":
start = (now + timedelta(days=1)).replace(
hour=0, minute=0, second=0, microsecond=0
)
end = start + timedelta(days=1)
else:
start = now
end = now + timedelta(days=7)
events_result = (
service.events()
.list(
calendarId="primary",
timeMin=start.isoformat() + "Z",
timeMax=end.isoformat() + "Z",
singleEvents=True,
orderBy="startTime",
maxResults=20,
)
.execute()
)
return events_result.get("items", [])
except Exception as e:
return [{"error": str(e)}]
def format_events(today_events: list, tomorrow_events: list = None) -> str:
"""Format calendar events - no LLM needed, structured data."""
lines = []
# Today's events
if today_events and (len(today_events) == 0 or "error" not in today_events[0]):
if not today_events:
lines.append("No events today")
else:
for event in today_events:
start = event.get("start", {})
time_str = ""
if "dateTime" in start:
# Timed event
dt = datetime.fromisoformat(
start["dateTime"].replace("Z", "+00:00")
)
time_str = dt.strftime("%I:%M %p").lstrip("0")
elif "date" in start:
time_str = "All day"
summary = event.get("summary", "(No title)")
duration = ""
# Calculate duration if end time available
end = event.get("end", {})
if "dateTime" in start and "dateTime" in end:
start_dt = datetime.fromisoformat(
start["dateTime"].replace("Z", "+00:00")
)
end_dt = datetime.fromisoformat(
end["dateTime"].replace("Z", "+00:00")
)
mins = int((end_dt - start_dt).total_seconds() / 60)
if mins >= 60:
hours = mins // 60
remaining = mins % 60
duration = (
f" ({hours}h{remaining}m)" if remaining else f" ({hours}h)"
)
else:
duration = f" ({mins}m)"
lines.append(f"{time_str} - {summary}{duration}")
elif today_events and "error" in today_events[0]:
error = today_events[0].get("error", "Unknown")
lines.append(f"⚠️ Could not fetch calendar: {error}")
else:
lines.append("No events today")
# Tomorrow preview
if tomorrow_events is not None:
if tomorrow_events and (
len(tomorrow_events) == 0 or "error" not in tomorrow_events[0]
):
count = len(tomorrow_events)
if count > 0:
first = tomorrow_events[0]
start = first.get("start", {})
if "dateTime" in start:
dt = datetime.fromisoformat(
start["dateTime"].replace("Z", "+00:00")
)
first_time = dt.strftime("%I:%M %p").lstrip("0")
else:
first_time = "All day"
lines.append(
f"Tomorrow: {count} event{'s' if count > 1 else ''}, first at {first_time}"
)
else:
lines.append("Tomorrow: No events")
return "\n".join(lines) if lines else "No calendar data"
def collect(config: dict) -> dict:
"""Main collector entry point."""
cal_config = config.get("calendar", {})
show_tomorrow = cal_config.get("show_tomorrow", True)
today_events = fetch_events("today")
tomorrow_events = fetch_events("tomorrow") if show_tomorrow else None
formatted = format_events(today_events, tomorrow_events)
has_error = today_events and len(today_events) == 1 and "error" in today_events[0]
return {
"section": "Today",
"icon": "📅",
"content": formatted,
"raw": {"today": today_events, "tomorrow": tomorrow_events},
"error": today_events[0].get("error") if has_error else None,
}
if __name__ == "__main__":
config = {"calendar": {"show_tomorrow": True}}
result = collect(config)
print(f"## {result['icon']} {result['section']}")
print(result["content"])