diff --git a/mcp-server/src/legal_mcp/court_fetch_service/server.py b/mcp-server/src/legal_mcp/court_fetch_service/server.py index c1c6c40..96fb429 100644 --- a/mcp-server/src/legal_mcp/court_fetch_service/server.py +++ b/mcp-server/src/legal_mcp/court_fetch_service/server.py @@ -34,6 +34,7 @@ import logging import os import sys +import aiohttp from aiohttp import web _pkg_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) @@ -93,6 +94,41 @@ async def _pm2_run(*args: str, timeout: float = 10) -> tuple[int, bytes, bytes]: return proc.returncode or 0, out, err +# claude.ai subscription usage — the 5-hour / weekly utilization % the Claude +# Code status bar shows, from the (undocumented) OAuth usage endpoint. Host-only: +# the OAuth token lives in the CLI credentials file on the host, never in the +# container. Read-only (no auth), like /pm2. The claude-code User-Agent is +# REQUIRED — without it the request lands in an aggressively rate-limited bucket. +_CLAUDE_CRED_PATH = "/home/chaim/.claude/.credentials.json" +_OAUTH_USAGE_URL = "https://api.anthropic.com/api/oauth/usage" +_USAGE_UA = "claude-code/2.1.177" + + +async def usage_status(request: web.Request) -> web.Response: + """Proxy the claude.ai subscription usage % (host-only — needs the local + OAuth token). Returns the endpoint's JSON, or a 502 with an error string.""" + try: + with open(_CLAUDE_CRED_PATH) as f: + token = json.load(f)["claudeAiOauth"]["accessToken"] + except Exception as e: + return web.json_response({"error": f"no claude credentials: {e}"}, status=502) + headers = { + "Authorization": f"Bearer {token}", + "User-Agent": _USAGE_UA, + "anthropic-beta": "oauth-2025-04-20", + } + try: + timeout = aiohttp.ClientTimeout(total=15) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(_OAUTH_USAGE_URL, headers=headers) as r: + if r.status != 200: + return web.json_response( + {"error": f"usage endpoint {r.status}"}, status=502) + return web.json_response(await r.json()) + except Exception as e: # never throw + return web.json_response({"error": f"usage fetch failed: {e}"}, status=502) + + async def pm2_status(request: web.Request) -> web.Response: """Return a trimmed ``pm2 jlist`` for the legal-ai background services.""" try: @@ -235,6 +271,7 @@ def build_app() -> web.Application: app = web.Application(client_max_size=64 * 1024 * 1024) app.router.add_get("/health", health) app.router.add_get("/pm2", pm2_status) + app.router.add_get("/usage", usage_status) app.router.add_post("/pm2/control", pm2_control) app.router.add_post("/fetch", fetch) return app diff --git a/web-ui/src/app/operations/page.tsx b/web-ui/src/app/operations/page.tsx index 2486a27..daa0c22 100644 --- a/web-ui/src/app/operations/page.tsx +++ b/web-ui/src/app/operations/page.tsx @@ -33,12 +33,68 @@ import { type OperationsSnapshot, type PipelineStats, type AgentRun, + type SubscriptionUsage, + type UsageWindow, } from "@/lib/api/operations"; function mb(bytes: number): string { return `${Math.round((bytes || 0) / 1024 / 1024)}MB`; } +// claude.ai subscription usage — the fuel gauge for every agent/drain. +// Bar turns amber > 75% and red > 90%; the halacha drain auto-pauses at 100% +// and resumes on its own when a window resets. +function usageResetLabel(iso: string | null): string { + if (!iso) return "—"; + const d = new Date(iso); + return `איפוס ${d.toLocaleTimeString("he-IL", { hour: "2-digit", minute: "2-digit" })}`; +} + +function UsageMeter({ label, w }: { label: string; w?: UsageWindow | null }) { + const util = w?.utilization; + const active = typeof util === "number"; + const pct = active ? Math.min(100, Math.max(0, util as number)) : 0; + const fill = pct >= 90 ? "bg-danger" : pct >= 75 ? "bg-warn" : "bg-gold"; + return ( +
- הצעת הדיסטילציה (סגנון/שיטה בלבד — INV-LRN5). בחר/י מה לאמץ; ייכתב לערוצים שהכותב צורך (שער-יו"ר). + הצעת הדיסטילציה (סגנון/שיטה בלבד — INV-LRN5). בחר/י מה לאמץ; ייכתב לערוצים שהכותב צורך (שער-יו"ר).
{data.overall_assessment && ({data.overall_assessment}
diff --git a/web-ui/src/lib/api/operations.ts b/web-ui/src/lib/api/operations.ts index 83baf6f..26c0a39 100644 --- a/web-ui/src/lib/api/operations.ts +++ b/web-ui/src/lib/api/operations.ts @@ -42,9 +42,26 @@ export type PipelineStats = { by_status: Record