"""Host Claude auth helpers. Reads the host's Claude Code credentials from ~/.claude/.credentials.json and returns only the access token needed by egress. Does not expose refresh tokens or raw auth payloads. """ from __future__ import annotations import json import os from datetime import datetime, timezone from pathlib import Path from ...log import die def claude_auth_path(host_env: dict[str, str] | None = None) -> Path: env = os.environ if host_env is None else host_env home = env.get("HOME") if home: return Path(home) / ".claude" / ".credentials.json" return Path.home() / ".claude" / ".credentials.json" def claude_host_access_token( host_env: dict[str, str] | None = None, *, now: datetime | None = None, ) -> str: path = claude_auth_path(host_env) if not path.is_file(): die( f"claude host credentials: auth file missing at {path}. " "Run `claude login` on the host or disable " "agent_provider.forward_host_credentials." ) try: raw = json.loads(path.read_text()) except (OSError, json.JSONDecodeError) as e: die(f"claude host credentials: could not read valid JSON at {path}: {e}") if not isinstance(raw, dict): die(f"claude host credentials: {path} must contain a JSON object") oauth = raw.get("claudeAiOauth") if not isinstance(oauth, dict): die( f"claude host credentials: {path} is missing claudeAiOauth. " "Run `claude login` on the host or disable " "agent_provider.forward_host_credentials." ) access_token = oauth.get("accessToken") if not isinstance(access_token, str) or not access_token: die( f"claude host credentials: {path} claudeAiOauth.accessToken is missing " "or empty. Run `claude login` on the host and restart the bottle." ) # expiresAt is stored in milliseconds expires_at = oauth.get("expiresAt") if isinstance(expires_at, (int, float)): check_now = now or datetime.now(timezone.utc) exp_dt = datetime.fromtimestamp(float(expires_at) / 1000.0, timezone.utc) if exp_dt <= check_now: die( "claude host credentials: host Claude access token is expired. " "Run `claude login` on the host and restart the bottle." ) return access_token __all__ = [ "claude_auth_path", "claude_host_access_token", ]