Add comprehensive morning report skill with collectors for calendar, email, tasks, infrastructure status, news, stocks, and weather. Add stock lookup skill for quote queries.
126 lines
3.9 KiB
Python
Executable File
126 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Weather collector using wttr.in."""
|
|
|
|
import json
|
|
import subprocess
|
|
import urllib.request
|
|
from pathlib import Path
|
|
|
|
|
|
def fetch_weather(location: str) -> dict:
|
|
"""Fetch weather data from wttr.in."""
|
|
# Use wttr.in JSON format
|
|
url = f"https://wttr.in/{location}?format=j1"
|
|
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
|
|
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
return json.load(resp)
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
def format_weather_basic(data: dict, location: str) -> str:
|
|
"""Format weather data without LLM - basic fallback."""
|
|
if "error" in data:
|
|
return f"Weather unavailable: {data['error']}"
|
|
|
|
try:
|
|
current = data["current_condition"][0]
|
|
today = data["weather"][0]
|
|
|
|
temp_f = current.get("temp_F", "?")
|
|
desc = current.get("weatherDesc", [{}])[0].get("value", "Unknown")
|
|
high = today.get("maxtempF", "?")
|
|
low = today.get("mintempF", "?")
|
|
|
|
return f"{location}: {temp_f}°F, {desc} | High {high}° Low {low}°"
|
|
|
|
except Exception as e:
|
|
return f"Weather parse error: {e}"
|
|
|
|
|
|
def format_weather_with_haiku(data: dict, location: str) -> str:
|
|
"""Use Haiku to format weather data nicely."""
|
|
if "error" in data:
|
|
return f"Weather unavailable: {data['error']}"
|
|
|
|
try:
|
|
current = data["current_condition"][0]
|
|
today = data["weather"][0]
|
|
|
|
# Extract key data
|
|
temp_f = current.get("temp_F", "?")
|
|
feels_like = current.get("FeelsLikeF", temp_f)
|
|
desc = current.get("weatherDesc", [{}])[0].get("value", "Unknown")
|
|
humidity = current.get("humidity", "?")
|
|
high = today.get("maxtempF", "?")
|
|
low = today.get("mintempF", "?")
|
|
|
|
# Check for precipitation
|
|
hourly = today.get("hourly", [])
|
|
rain_hours = [h for h in hourly if int(h.get("chanceofrain", 0)) > 50]
|
|
|
|
# Build context for Haiku
|
|
weather_context = f"""Current: {temp_f}°F (feels like {feels_like}°F), {desc}
|
|
High: {high}°F, Low: {low}°F
|
|
Humidity: {humidity}%
|
|
Rain chance >50%: {len(rain_hours)} hours today"""
|
|
|
|
# Check if claude is available
|
|
claude_path = Path.home() / ".local/bin/claude"
|
|
if not claude_path.exists():
|
|
claude_path = "claude" # Try PATH
|
|
|
|
prompt = f"""Format this weather data for {location} into a single concise line for a morning report.
|
|
Add a brief hint if relevant (e.g., "bring umbrella", "nice day for a walk").
|
|
Keep it under 80 characters if possible.
|
|
|
|
{weather_context}
|
|
|
|
Output ONLY the formatted weather line, nothing else."""
|
|
|
|
result = subprocess.run(
|
|
[str(claude_path), "--print", "--model", "haiku", "-p", prompt],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30
|
|
)
|
|
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
return result.stdout.strip()
|
|
else:
|
|
# Fallback to basic format
|
|
return format_weather_basic(data, location)
|
|
|
|
except Exception as e:
|
|
# Fallback to basic format
|
|
return format_weather_basic(data, location)
|
|
|
|
|
|
def collect(config: dict) -> dict:
|
|
"""Main collector entry point."""
|
|
location = config.get("weather", {}).get("location", "Seattle,WA,USA")
|
|
city_name = location.split(",")[0]
|
|
|
|
data = fetch_weather(location)
|
|
|
|
# Try Haiku formatting, fall back to basic
|
|
formatted = format_weather_with_haiku(data, city_name)
|
|
|
|
return {
|
|
"section": "Weather",
|
|
"icon": "🌤",
|
|
"content": formatted,
|
|
"raw": data if "error" not in data else None,
|
|
"error": data.get("error")
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Test
|
|
config = {"weather": {"location": "Seattle,WA,USA"}}
|
|
result = collect(config)
|
|
print(f"## {result['icon']} {result['section']}")
|
|
print(result["content"])
|