Add comprehensive morning report skill with collectors for calendar, email, tasks, infrastructure status, news, stocks, and weather. Add stock lookup skill for quote queries.
109 lines
3.3 KiB
Python
Executable File
109 lines
3.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Stocks collector using stock-lookup skill."""
|
|
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def get_quotes(symbols: list) -> list:
|
|
"""Fetch quotes using stock-lookup skill."""
|
|
script = Path.home() / ".claude/skills/stock-lookup/scripts/quote.py"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, str(script), "--json"] + symbols,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30
|
|
)
|
|
if result.returncode == 0:
|
|
return json.loads(result.stdout)
|
|
else:
|
|
return [{"symbol": s, "error": "fetch failed"} for s in symbols]
|
|
except Exception as e:
|
|
return [{"symbol": s, "error": str(e)} for s in symbols]
|
|
|
|
|
|
def format_stocks_with_haiku(quotes: list) -> str:
|
|
"""Use Haiku to format stock data nicely."""
|
|
# Build context
|
|
lines = []
|
|
for q in quotes:
|
|
if "error" in q:
|
|
lines.append(f"{q.get('symbol', '?')}: error - {q['error']}")
|
|
else:
|
|
price = q.get("price", 0)
|
|
prev = q.get("previous_close", price)
|
|
if prev and prev > 0:
|
|
change = ((price - prev) / prev) * 100
|
|
direction = "+" if change >= 0 else ""
|
|
lines.append(f"{q['symbol']}: ${price:.2f} ({direction}{change:.1f}%)")
|
|
else:
|
|
lines.append(f"{q['symbol']}: ${price:.2f}")
|
|
|
|
stock_data = "\n".join(lines)
|
|
|
|
prompt = f"""Format these stock quotes into a compact single line for a morning dashboard.
|
|
Use arrow indicators (▲▼) for direction. Keep it concise.
|
|
|
|
{stock_data}
|
|
|
|
Output ONLY the formatted stock line, nothing else."""
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
["claude", "--print", "--model", "haiku", "-p", prompt],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=30
|
|
)
|
|
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
return result.stdout.strip()
|
|
except Exception:
|
|
pass
|
|
|
|
# Fallback to basic format
|
|
parts = []
|
|
for q in quotes:
|
|
if "error" in q:
|
|
parts.append(f"{q.get('symbol', '?')} ⚠️")
|
|
else:
|
|
price = q.get("price", 0)
|
|
prev = q.get("previous_close", price)
|
|
if prev and prev > 0:
|
|
change = ((price - prev) / prev) * 100
|
|
arrow = "▲" if change >= 0 else "▼"
|
|
parts.append(f"{q['symbol']} ${price:.2f} {'+' if change >= 0 else ''}{change:.1f}% {arrow}")
|
|
else:
|
|
parts.append(f"{q['symbol']} ${price:.2f}")
|
|
|
|
return " ".join(parts)
|
|
|
|
|
|
def collect(config: dict) -> dict:
|
|
"""Main collector entry point."""
|
|
watchlist = config.get("stocks", {}).get("watchlist", ["NVDA", "AAPL", "MSFT"])
|
|
|
|
quotes = get_quotes(watchlist)
|
|
formatted = format_stocks_with_haiku(quotes)
|
|
|
|
errors = [q.get("error") for q in quotes if "error" in q]
|
|
|
|
return {
|
|
"section": "Stocks",
|
|
"icon": "📈",
|
|
"content": formatted,
|
|
"raw": quotes,
|
|
"error": errors[0] if errors else None
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
config = {"stocks": {"watchlist": ["CRWV", "NVDA", "MSFT"]}}
|
|
result = collect(config)
|
|
print(f"## {result['icon']} {result['section']}")
|
|
print(result["content"])
|