Add morning-report and stock-lookup skills

Add comprehensive morning report skill with collectors for calendar, email, tasks,
infrastructure status, news, stocks, and weather. Add stock lookup skill for quote queries.
This commit is contained in:
OpenCode Test
2026-01-03 10:54:54 -08:00
parent ae958528a6
commit daa4de8832
13 changed files with 1590 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
#!/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.13/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"])