Phase 1 of plugin-structure refactor: - Add hooks/hooks.json for SessionStart automation - Refactor gmail skill: - Extract inline scripts to scripts/check_unread.py, check_urgent.py, search.py - Add references/query-patterns.md for query documentation - Simplify SKILL.md to reference scripts instead of inline code - Add gcal/scripts/agenda.py for direct calendar access - Make all scripts executable This follows the "Skill with Bundled Resources" pattern from developing-claude-code-plugins best practices. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
105 lines
3.3 KiB
Python
Executable File
105 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Get calendar agenda for a time range."""
|
|
import os
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
|
|
# Set credentials path
|
|
os.environ.setdefault('GMAIL_CREDENTIALS_PATH', os.path.expanduser('~/.gmail-mcp/credentials.json'))
|
|
|
|
from gmail_mcp.utils.GCP.gmail_auth import get_calendar_service
|
|
|
|
def format_event(event):
|
|
"""Format a single event for display."""
|
|
start = event['start'].get('dateTime', event['start'].get('date'))
|
|
end = event['end'].get('dateTime', event['end'].get('date'))
|
|
|
|
# Parse datetime
|
|
if 'T' in start:
|
|
start_dt = datetime.fromisoformat(start.replace('Z', '+00:00'))
|
|
end_dt = datetime.fromisoformat(end.replace('Z', '+00:00'))
|
|
time_str = start_dt.strftime('%I:%M %p').lstrip('0')
|
|
duration = end_dt - start_dt
|
|
if duration.seconds >= 3600:
|
|
dur_str = f"{duration.seconds // 3600}h"
|
|
else:
|
|
dur_str = f"{duration.seconds // 60}m"
|
|
else:
|
|
time_str = "All day"
|
|
dur_str = ""
|
|
|
|
summary = event.get('summary', '(No title)')
|
|
location = event.get('location', '')
|
|
|
|
line = f" {time_str:>10} {summary}"
|
|
if dur_str:
|
|
line += f" ({dur_str})"
|
|
if location:
|
|
line += f"\n 📍 {location}"
|
|
|
|
return line
|
|
|
|
def main():
|
|
mode = sys.argv[1] if len(sys.argv) > 1 else 'today'
|
|
|
|
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)
|
|
title = f"Today — {start.strftime('%A, %b %d')}"
|
|
elif mode == 'tomorrow':
|
|
start = (now + timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
end = start + timedelta(days=1)
|
|
title = f"Tomorrow — {start.strftime('%A, %b %d')}"
|
|
elif mode == 'week':
|
|
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
end = start + timedelta(days=7)
|
|
title = f"This Week — {start.strftime('%b %d')}-{end.strftime('%d')}"
|
|
else:
|
|
start = now
|
|
end = now + timedelta(days=7)
|
|
title = "Upcoming"
|
|
|
|
events_result = service.events().list(
|
|
calendarId='primary',
|
|
timeMin=start.isoformat() + 'Z',
|
|
timeMax=end.isoformat() + 'Z',
|
|
singleEvents=True,
|
|
orderBy='startTime',
|
|
maxResults=50
|
|
).execute()
|
|
|
|
events = events_result.get('items', [])
|
|
|
|
print(f"📅 {title}\n")
|
|
|
|
if not events:
|
|
print("No events scheduled.")
|
|
return
|
|
|
|
if mode == 'week':
|
|
# Group by day
|
|
by_day = {}
|
|
for event in events:
|
|
start_str = event['start'].get('dateTime', event['start'].get('date'))
|
|
day = start_str[:10]
|
|
if day not in by_day:
|
|
by_day[day] = []
|
|
by_day[day].append(event)
|
|
|
|
for day, day_events in sorted(by_day.items()):
|
|
day_dt = datetime.fromisoformat(day)
|
|
print(f"━━━ {day_dt.strftime('%A, %b %d')} ━━━")
|
|
for event in day_events:
|
|
print(format_event(event))
|
|
print()
|
|
else:
|
|
for event in events:
|
|
print(format_event(event))
|
|
print("\nNo more events" + (" today." if mode == 'today' else "."))
|
|
|
|
if __name__ == '__main__':
|
|
main()
|