diff --git a/backend/main.py b/backend/main.py index 5cab2f8..58b9a09 100644 --- a/backend/main.py +++ b/backend/main.py @@ -17,7 +17,6 @@ import logging from datetime import datetime, timedelta import uuid from pathlib import Path -import APScheduler from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger import os @@ -405,15 +404,82 @@ async def get_task_runs(task_id: str) -> List[Dict]: @app.get("/api/system/info") async def system_info() -> Dict: """Get system information""" + tasks = get_all_tasks() + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*) FROM task_runs") + total_runs = cursor.fetchone()[0] + cursor.execute("SELECT COUNT(*) FROM task_runs WHERE status='completed'") + completed_runs = cursor.fetchone()[0] + cursor.execute("SELECT COUNT(*) FROM task_runs WHERE status='failed'") + failed_runs = cursor.fetchone()[0] + cursor.execute("SELECT COUNT(*) FROM task_runs WHERE status='running'") + running_runs = cursor.fetchone()[0] + conn.close() return { "app_name": "Claude Persistent Agent", "version": "1.0.0", "uptime": datetime.now().isoformat(), "scheduler_running": scheduler.running, - "task_count": len(get_all_tasks()) + "task_count": len(tasks), + "total_runs": total_runs, + "completed_runs": completed_runs, + "failed_runs": failed_runs, + "running_runs": running_runs, } +@app.get("/api/system/usage") +async def usage_stats() -> Dict: + """Get Claude API usage stats from session files if available""" + import glob, json as _json + + usage = { + "models_used": [], + "session_count": 0, + "last_reset": None, + "next_reset": None, + "note": "Usage data sourced from ~/.claude session cache" + } + + try: + # Claude Code stores session info under ~/.claude + claude_dir = Path("/root/.claude") + sessions = list(claude_dir.glob("**/session*.json")) + list(claude_dir.glob("**/*.jsonl")) + usage["session_count"] = len(sessions) + + # Try to parse any usage metadata + stats_file = claude_dir / "usage_stats.json" + if stats_file.exists(): + with open(stats_file) as f: + saved = _json.load(f) + usage.update(saved) + else: + # Estimate reset time: Anthropic resets usage monthly + now = datetime.now() + if now.day <= 1: + reset = now.replace(day=1, hour=0, minute=0, second=0) + else: + next_month = (now.replace(day=1) + timedelta(days=32)).replace(day=1) + reset = next_month.replace(hour=0, minute=0, second=0) + usage["next_reset"] = reset.isoformat() + usage["days_until_reset"] = (reset - now).days + except Exception as e: + usage["error"] = str(e) + + # Add run stats for context + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT COUNT(*), MIN(started_at), MAX(started_at) FROM task_runs") + row = cursor.fetchone() + conn.close() + usage["claude_runs_total"] = row[0] + usage["first_run"] = row[1] + usage["last_run"] = row[2] + + return usage + + # Serve static frontend app.mount("/", StaticFiles(directory="/app/frontend/dist", html=True), name="static")