diff --git a/app/main.py b/app/main.py index 28ffb84..0ab5904 100644 --- a/app/main.py +++ b/app/main.py @@ -46,6 +46,7 @@ logger = logging.getLogger("jon-snow") AGENT_OS_DIR = Path(os.getenv("AGENT_OS_DIR", "/opt/agent-os")) SITES_DIR = Path(os.getenv("SITES_DIR", "/opt/sites")) JON_PUBLIC_URL = os.getenv("JON_PUBLIC_URL", "https://jon.nxm.co.za") +ZAR_RATE = float(os.getenv("ZAR_RATE", "18.50")) app = FastAPI(title="Jon Snow", version="0.3.0") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) @@ -98,6 +99,174 @@ class QueueActionRequest(BaseModel): # --- Output helpers --- +def _render_jon_page() -> None: + """Write /opt/sites/jon-snow/index.html with live cost and recent tasks.""" + summary = get_token_summary() + total_usd = summary["all_time"]["cost_usd"] + today_usd = summary["today"]["cost_usd"] + total_zar = total_usd * ZAR_RATE + today_zar = today_usd * ZAR_RATE + + runs_file = AGENT_OS_DIR / "logs" / "jon-snow" / "runs.jsonl" + runs = [] + if runs_file.exists(): + for line in runs_file.read_text().splitlines(): + try: + runs.append(json.loads(line)) + except Exception: + pass + + # Last 3 task entries, newest first + task_runs = [r for r in reversed(runs) if r.get("result", "").startswith("[task]")][:3] + + CLAUDE_INTENTS = {"task", "planning"} + + task_rows = "" + for r in task_runs: + ts = r.get("timestamp", "")[:19].replace("T", " ") + result = r.get("result", "") + # "[task] Task created: TITLE → PROJECT" + body = result.removeprefix("[task] Task created: ").removeprefix("[task] ") + if " → " in body: + title, project = body.split(" → ", 1) + else: + title, project = body, "—" + badge_cls = "badge-claude" if r.get("result", "").startswith("[task]") else "badge-python" + task_rows += ( + f"{ts}{title[:55]}" + f"{project[:30]}" + f'Claude\n' + ) + + if not task_rows: + task_rows = 'No tasks logged yet' + + intent_rows = "" + for intent, v in summary["by_intent"].items(): + zar = v["cost_usd"] * ZAR_RATE + label = "Claude" if intent in CLAUDE_INTENTS or "extract" in intent else "Python" + badge = "badge-claude" if label == "Claude" else "badge-python" + intent_rows += ( + f"{intent}" + f'{label}' + f"{v['calls']}" + f"{v['in']:,} / {v['out']:,}" + f"R{zar:.4f}\n" + ) + if not intent_rows: + intent_rows = 'No API calls yet' + + updated = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") + + html = f""" + + + + + Jon Snow — NxM Agents + + + + + + +
+

Jon Snow

+

Orchestrator · Chief of Staff · port 8900

+ +
+
+
All-time API cost
+
R{total_zar:.2f}
+
${total_usd:.6f} USD · {summary['all_time']['calls']} LLM calls
+
+
+
Today
+
R{today_zar:.2f}
+
${today_usd:.6f} USD · {summary['today']['calls']} calls today
+
+
+
Tokens in / out
+
{summary['all_time']['tokens_in']:,}
+
{summary['all_time']['tokens_out']:,} output · claude-sonnet-4-6
+
+
+
Rate
+
R{ZAR_RATE:.2f}
+
per USD · update ZAR_RATE env var
+
+
+ +

Last 3 Tasks

+
+ + + {task_rows} +
Time (UTC)TaskProjectExtraction
+
+ +

API Usage by Intent

+
+ + + {intent_rows} +
IntentPathCallsTokens in / outCost (ZAR)
+
+ +

Updated {updated}

+
+ +""" + + out_dir = SITES_DIR / "jon-snow" + out_dir.mkdir(parents=True, exist_ok=True) + try: + (out_dir / "index.html").write_text(html) + except Exception as e: + logger.warning(f"page render failed: {e}") + + def _write_status(intent: str, summary: str, status: str = "success") -> None: log_dir = AGENT_OS_DIR / "logs" / "jon-snow" log_dir.mkdir(parents=True, exist_ok=True) @@ -123,6 +292,7 @@ def _write_status(intent: str, summary: str, status: str = "success") -> None: (out_dir / "last-output.md").write_text( f"# Jon Snow — Last Response\n\n**{payload['timestamp']}**\n\nIntent: `{intent}`\n\n{summary[:500]}\n" ) + _render_jon_page() # --- SSE streaming helpers --- @@ -529,7 +699,7 @@ async def chat_completions(req: ChatRequest): f"**{issue['title']}** \n" f"Project: *{issue['project']}* | #{issue['sequence_id']}" ) - summary = f"Task created: {issue['title']}" + summary = f"Task created: {issue['title']} → {issue['project']}" except Exception as e: response_text = f"Couldn't log to Plane ({e}). Task noted locally: {user_message[:100]}" summary = f"Plane error: {e}"