#!/usr/bin/env python3 """Google Tasks collector.""" import json import os import sys from datetime import datetime from pathlib import Path # Add gmail venv to path for Google API libraries 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)) # Google Tasks API try: from google.oauth2.credentials import Credentials from google_auth_oauthlib.flow import InstalledAppFlow from google.auth.transport.requests import Request from googleapiclient.discovery import build GOOGLE_API_AVAILABLE = True except ImportError: GOOGLE_API_AVAILABLE = False SCOPES = ["https://www.googleapis.com/auth/tasks.readonly"] TOKEN_PATH = Path.home() / ".gmail-mcp/tasks_token.json" CREDS_PATH = Path.home() / ".gmail-mcp/credentials.json" def get_credentials(): """Get or refresh Google credentials for Tasks API.""" creds = None if TOKEN_PATH.exists(): creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES) if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: if not CREDS_PATH.exists(): return None flow = InstalledAppFlow.from_client_secrets_file(str(CREDS_PATH), SCOPES) creds = flow.run_local_server(port=0) TOKEN_PATH.write_text(creds.to_json()) return creds def fetch_tasks(max_results: int = 10) -> list: """Fetch tasks from Google Tasks API.""" if not GOOGLE_API_AVAILABLE: return [{"error": "Google API libraries not installed"}] try: creds = get_credentials() if not creds: return [ { "error": "Tasks API not authenticated - run: ~/.claude/mcp/gmail/venv/bin/python ~/.claude/skills/morning-report/scripts/collectors/gtasks.py --auth" } ] service = build("tasks", "v1", credentials=creds) # Get default task list tasklists = service.tasklists().list(maxResults=1).execute() if not tasklists.get("items"): return [] tasklist_id = tasklists["items"][0]["id"] # Get tasks results = ( service.tasks() .list( tasklist=tasklist_id, maxResults=max_results, showCompleted=False, showHidden=False, ) .execute() ) tasks = results.get("items", []) return tasks except Exception as e: return [{"error": str(e)}] def format_tasks(tasks: list, max_display: int = 5) -> str: """Format tasks - no LLM needed, structured data.""" if not tasks: return "No pending tasks" if len(tasks) == 1 and "error" in tasks[0]: return f"⚠️ Could not fetch tasks: {tasks[0]['error']}" lines = [] # Count and header total = len(tasks) due_today = 0 today_str = datetime.now().strftime("%Y-%m-%d") for task in tasks: due = task.get("due", "") if due and due.startswith(today_str): due_today += 1 header = f"{total} pending" if due_today > 0: header += f", {due_today} due today" lines.append(header) # List tasks for task in tasks[:max_display]: title = task.get("title", "(No title)") due = task.get("due", "") due_str = "" if due: try: due_date = datetime.fromisoformat(due.replace("Z", "+00:00")) if due_date.date() == datetime.now().date(): due_str = " (due today)" elif due_date.date() < datetime.now().date(): due_str = " (overdue!)" else: due_str = f" (due {due_date.strftime('%b %d')})" except ValueError: pass lines.append(f" • {title}{due_str}") if total > max_display: lines.append(f" ... and {total - max_display} more") return "\n".join(lines) def collect(config: dict) -> dict: """Main collector entry point.""" tasks_config = config.get("tasks", {}) max_display = tasks_config.get("max_display", 5) tasks = fetch_tasks(max_display + 5) formatted = format_tasks(tasks, max_display) has_error = tasks and len(tasks) == 1 and "error" in tasks[0] return { "section": "Tasks", "icon": "✅", "content": formatted, "raw": tasks if not has_error else None, "count": len(tasks) if not has_error else 0, "error": tasks[0].get("error") if has_error else None, } if __name__ == "__main__": import sys if "--auth" in sys.argv: print("Starting Tasks API authentication...") creds = get_credentials() if creds: print(f"✅ Authentication successful! Token saved to {TOKEN_PATH}") else: print("❌ Authentication failed") sys.exit(0) config = {"tasks": {"max_display": 5}} result = collect(config) print(f"## {result['icon']} {result['section']}") print(result["content"])