"""Anthropic (Claude Code) provider. Wraps the daemon's existing OAuth + rate-limit poll + local-usage logic behind the Provider interface. Behaviour is identical to pre-v3 Clawdmeter — this only moves the Claude-specific work behind a seam so OpenAI/z.ai can slot in beside it. The heavy lifting (compute_today_usage, poll_api, token refresh) still lives in claude_usage_daemon_windows.py; this just orchestrates it into a ProviderStatus. """ from __future__ import annotations from .base import Provider, ProviderStatus class AnthropicProvider(Provider): id = "anthropic" label = "Claude Code" accent = "d97757" # brand terra-cotta async def poll(self) -> ProviderStatus: # Lazy import: the daemon module imports this package, so importing it # back at module load would be circular. By poll() time it's resolved. from daemon.claude_usage_daemon_windows import ( compute_today_usage, refresh_token_if_needed, read_token, poll_api, AuthError, log, ) fresh = compute_today_usage() # local token/cost — no network, always available rl = None auth_problem = False # Proactively refresh the OAuth token; a DNS/network blip here must not # crash the loop (SC#5) — it just means we poll with the current token. try: await refresh_token_if_needed() except Exception as e: # belt-and-braces log(f"Token refresh skipped: {e!r}") token = read_token() if not token: auth_problem = True log("No token; sending local usage only") else: try: rl = await poll_api(token) except AuthError: # Rejected despite the proactive refresh — force one refresh and # retry the poll once before flagging the token as bad. try: forced = await refresh_token_if_needed(force=True) except Exception as e: log(f"Forced token refresh failed: {e!r}") forced = False if forced and (token := read_token()): try: rl = await poll_api(token) except AuthError: auth_problem = True else: auth_problem = True status = ProviderStatus( tokens_today=int(fresh.get("tk", 0) or 0), output_today=int(fresh.get("to", 0) or 0), cost_cents_today=int(fresh.get("tc", 0) or 0), messages_today=int(fresh.get("tn", 0) or 0), auth_problem=auth_problem, ) if rl is not None: status.s = rl.get("s", 0.0) status.sr = rl.get("sr", -1) status.w = rl.get("w", 0.0) status.wr = rl.get("wr", -1) status.st = rl.get("st", "unknown") status.ok = True # rate-limit data is fresh else: status.ok = False # unknown → the watch usage view goes idle, not 0% return status