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,187 @@
#!/usr/bin/env python3
"""Infrastructure collector for K8s and workstation health."""
import subprocess
from pathlib import Path
def check_k8s_health() -> dict:
"""Check Kubernetes cluster health."""
try:
# Quick node check
result = subprocess.run(
["kubectl", "get", "nodes", "-o", "jsonpath={.items[*].status.conditions[-1].type}"],
capture_output=True,
text=True,
timeout=15
)
if result.returncode != 0:
return {"status": "unknown", "error": "kubectl failed"}
# Check if all nodes are Ready
conditions = result.stdout.strip().split()
all_ready = all(c == "Ready" for c in conditions) if conditions else False
# Quick pod check for issues
pod_result = subprocess.run(
["kubectl", "get", "pods", "-A", "--field-selector=status.phase!=Running,status.phase!=Succeeded",
"-o", "jsonpath={.items[*].metadata.name}"],
capture_output=True,
text=True,
timeout=15
)
problem_pods = pod_result.stdout.strip().split() if pod_result.stdout.strip() else []
if all_ready and len(problem_pods) == 0:
return {"status": "green", "message": "All nodes ready, no problem pods"}
elif all_ready:
return {"status": "yellow", "message": f"{len(problem_pods)} pods not running"}
else:
return {"status": "red", "message": "Node(s) not ready"}
except subprocess.TimeoutExpired:
return {"status": "unknown", "error": "timeout"}
except Exception as e:
return {"status": "unknown", "error": str(e)}
def check_workstation_health() -> dict:
"""Check local workstation health."""
try:
issues = []
# Disk usage
result = subprocess.run(
["df", "-h", "/"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
lines = result.stdout.strip().split("\n")
if len(lines) > 1:
parts = lines[1].split()
if len(parts) >= 5:
usage = int(parts[4].rstrip("%"))
if usage > 90:
issues.append(f"disk {usage}%")
elif usage > 80:
issues.append(f"disk {usage}%")
# Memory usage
result = subprocess.run(
["free", "-m"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
lines = result.stdout.strip().split("\n")
if len(lines) > 1:
parts = lines[1].split()
if len(parts) >= 3:
total = int(parts[1])
used = int(parts[2])
pct = (used / total) * 100 if total > 0 else 0
if pct > 90:
issues.append(f"mem {pct:.0f}%")
# Load average
result = subprocess.run(
["cat", "/proc/loadavg"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
load_1m = float(result.stdout.split()[0])
# Get CPU count
cpu_result = subprocess.run(["nproc"], capture_output=True, text=True, timeout=5)
cpus = int(cpu_result.stdout.strip()) if cpu_result.returncode == 0 else 4
if load_1m > cpus * 2:
issues.append(f"load {load_1m:.1f}")
if not issues:
return {"status": "green", "message": "OK"}
elif len(issues) <= 1 and "disk 8" in str(issues):
return {"status": "yellow", "message": ", ".join(issues)}
else:
return {"status": "red" if len(issues) > 1 else "yellow", "message": ", ".join(issues)}
except Exception as e:
return {"status": "unknown", "error": str(e)}
def format_status(k8s: dict, workstation: dict) -> str:
"""Format infrastructure status with traffic lights."""
status_icons = {
"green": "🟢",
"yellow": "🟡",
"red": "🔴",
"unknown": ""
}
k8s_icon = status_icons.get(k8s.get("status", "unknown"), "")
ws_icon = status_icons.get(workstation.get("status", "unknown"), "")
k8s_detail = k8s.get("error", k8s.get("message", ""))
ws_detail = workstation.get("error", workstation.get("message", ""))
# Keep it simple for traffic light mode
parts = [f"K8s: {k8s_icon}", f"Workstation: {ws_icon}"]
# Add details only if not green
details = []
if k8s.get("status") != "green" and k8s_detail:
details.append(f"K8s: {k8s_detail}")
if workstation.get("status") != "green" and ws_detail:
details.append(f"WS: {ws_detail}")
result = " | ".join(parts)
if details:
result += f"\n{'; '.join(details)}"
return result
def collect(config: dict) -> dict:
"""Main collector entry point."""
infra_config = config.get("infra", {})
k8s_result = {"status": "unknown", "message": "disabled"}
ws_result = {"status": "unknown", "message": "disabled"}
if infra_config.get("check_k8s", True):
k8s_result = check_k8s_health()
if infra_config.get("check_workstation", True):
ws_result = check_workstation_health()
formatted = format_status(k8s_result, ws_result)
# Determine overall status
statuses = [k8s_result.get("status"), ws_result.get("status")]
if "red" in statuses:
overall = "red"
elif "yellow" in statuses or "unknown" in statuses:
overall = "yellow"
else:
overall = "green"
return {
"section": "Infrastructure",
"icon": "🖥",
"content": formatted,
"raw": {"k8s": k8s_result, "workstation": ws_result},
"status": overall,
"error": None
}
if __name__ == "__main__":
config = {"infra": {"check_k8s": True, "check_workstation": True}}
result = collect(config)
print(f"## {result['icon']} {result['section']}")
print(result["content"])