1"""Environment inspection -> evidence/environment.json (no secret values are printed, only presence)."""2from __future__ import annotations3import json, os, platform, subprocess, sys, shutil, re4from pathlib import Path5from .util import utc_now, ROOT, write_json67def _sysctl(k):8 try:9 return subprocess.check_output(["sysctl", "-n", k], text=True).strip()10 except Exception:11 return None1213def _env_key_present(name: str) -> bool:14 if os.environ.get(name):15 return True16 for p in [ROOT / ".env", Path.home() / "Desktop/Recherche-UQO/immobilier/agentic-valuation-quebec/.env"]:17 if p.exists() and re.search(rf"^{name}=\S+", p.read_text(), re.M):18 return True19 return False2021def doctor() -> dict:22 import mlx.core as mx, mlx_lm, numpy, psutil23 info = {24 "timestamp_utc": utc_now(), "hostname": platform.node(), "os": platform.platform(), "macos_version": platform.mac_ver()[0],25 "cpu": _sysctl("machdep.cpu.brand_string"), "cores": os.cpu_count(), "memory_bytes": int(_sysctl("hw.memsize") or 0),26 "disk_free_bytes": shutil.disk_usage(str(ROOT)).free, "python": sys.version, "executable": sys.executable,27 "mlx": mx.__version__, "mlx_lm": mlx_lm.__version__, "numpy": numpy.__version__,28 "metal_available": bool(mx.metal.is_available()) if hasattr(mx, "metal") else None,29 "device_info": {k: (v if isinstance(v, (int, str)) else str(v)) for k, v in (mx.device_info().items() if hasattr(mx, "device_info") else [])},30 "credentials_present": {k: _env_key_present(k) for k in ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "HF_TOKEN"]},31 "hf_cache_models": sorted(p.name for p in (Path.home() / ".cache/huggingface/hub").glob("models--*")),32 "latex": {t: shutil.which(t) for t in ["latexmk", "pdflatex", "lualatex", "bibtex"]},33 "git_commit": None,34 }35 try:36 import torch; info["torch"] = torch.__version__; info["torch_mps"] = bool(torch.backends.mps.is_available())37 except Exception as e:38 info["torch"] = f"unavailable: {type(e).__name__}"39 try:40 import llmlingua; info["llmlingua"] = getattr(llmlingua, "__version__", "installed")41 except Exception:42 info["llmlingua"] = None43 try:44 info["git_commit"] = subprocess.check_output(["git", "-C", str(ROOT), "rev-parse", "HEAD"], text=True, stderr=subprocess.DEVNULL).strip()45 except Exception:46 pass47 return info4849def main(output: str):50 info = doctor(); write_json(output, info)51 print(json.dumps({k: info[k] for k in ["hostname", "cpu", "memory_bytes", "mlx", "mlx_lm", "python", "credentials_present"]}, indent=1))52 return 053