feat(operations): show real claude.ai subscription usage % on /operations
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Surfaces the 5-hour / weekly / weekly-Opus utilization the Claude Code status bar shows — the authoritative number, not a token estimate. Design approved via the Claude Design gate (card 02c-operations-usage.html). Three layers: - court-fetch-service (host bridge): new GET /usage reads the OAuth token from ~/.claude/.credentials.json and proxies /api/oauth/usage with the required claude-code User-Agent. Read-only, no auth (like /pm2). Host-only — the token never enters the container. - web/app.py: _ops_subscription_usage() proxies the bridge /usage; the /api/operations snapshot gains a `subscription_usage` field (null when the undocumented endpoint is unreachable). - web-ui: SubscriptionUsagePanel renders three meters (label · % · bar · reset) at the top of /operations; bar turns amber >75% / red >90%; hidden when usage is null. Types added to operations.ts (hand-maintained snapshot type). Also fixes a pre-existing react/no-unescaped-entities lint error in learning-panel.tsx (escaped a `"` in Hebrew text — renders identically). tsc --noEmit passes; lint error count 0. (Full next build is blocked only by the manual-worktree node_modules symlink — the Docker build has real node_modules.) Invariants: G2 (usage surfaced through the existing host bridge + /api/operations snapshot — no parallel control path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
web/app.py
17
web/app.py
@@ -6525,6 +6525,21 @@ async def _ops_pm2_services() -> dict:
|
||||
return {"services": [], "error": f"לא ניתן להגיע לשירות-המארח: {e}"}
|
||||
|
||||
|
||||
async def _ops_subscription_usage() -> dict | None:
|
||||
"""Proxy the host court-fetch-service /usage — the claude.ai subscription
|
||||
utilization % (5-hour / weekly). Host-only (the OAuth token lives on the
|
||||
host). Returns the endpoint JSON, or None if unavailable (undocumented
|
||||
endpoint — the dashboard shows nothing when this is None)."""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=18.0) as client:
|
||||
r = await client.get(f"{_COURT_FETCH_SERVICE_URL}/usage")
|
||||
if r.status_code == 200:
|
||||
return r.json()
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
async def _ops_pm2_control(name: str, action: str) -> dict:
|
||||
"""Proxy a mutating pm2 action to the host bridge (Bearer-authenticated)."""
|
||||
secret = os.environ.get("COURT_FETCH_SHARED_SECRET", "").strip()
|
||||
@@ -6636,6 +6651,7 @@ async def operations_snapshot():
|
||||
]
|
||||
|
||||
pm2 = await _ops_pm2_services()
|
||||
subscription_usage = await _ops_subscription_usage()
|
||||
controls = await db.get_drain_controls()
|
||||
bursts = await db.get_drain_bursts()
|
||||
for svc in pm2["services"]:
|
||||
@@ -6652,6 +6668,7 @@ async def operations_snapshot():
|
||||
return {
|
||||
"services": pm2["services"],
|
||||
"services_error": pm2["error"],
|
||||
"subscription_usage": subscription_usage,
|
||||
"pipelines": {
|
||||
"court_fetch": {
|
||||
**_norm_pipeline(
|
||||
|
||||
Reference in New Issue
Block a user