from __future__ import annotations from .common import ( Any, Check, InstallerError, Path, Runner, Which, _load_json_object, _project_root, asdict, shutil, subprocess, ) from .project import _venv_python_path, inspect_project def doctor_project( project_root: str | Path | None = None, *, runner: Runner = subprocess.run, which: Which = shutil.which, python_version: tuple[int, int, int] | None = None, ) -> dict[str, Any]: root = _project_root(project_root) inspection = inspect_project(root, which=which, python_version=python_version) checks = [Check(**item) for item in inspection["checks"]] config_path = root / "config.json" if config_path.is_file(): try: config = _load_json_object(config_path) env_name = config.get("env") config_ok = isinstance(env_name, str) and bool(env_name.strip()) detail = f"config env is {env_name!r}" if config_ok else "config.json has no non-empty env" except InstallerError as error: config_ok = False detail = str(error) else: config_ok = False detail = "config.json is missing; run prepare" checks.append(Check("configured_env", config_ok, True, detail)) uv_bin = which("uv") if uv_bin: commands = ( [uv_bin, "lock", "--check"], [ str(_venv_python_path(root)), "-c", "import mcp; import nocobase_crm_mcp", ], ) labels = ("lockfile", "python_environment") for label, command in zip(labels, commands, strict=True): try: result = runner(command, cwd=str(root), capture_output=True, text=True, check=False) ok = result.returncode == 0 detail = "passed" if ok else f"failed with exit code {result.returncode}" except OSError as error: ok = False detail = str(error) checks.append(Check(label, ok, True, detail)) else: checks.extend( [ Check("lockfile", False, True, "uv was not found on PATH"), Check("python_environment", False, True, "uv was not found on PATH"), ] ) return { "ok": all(check.ok for check in checks if check.required), "scope": "local-installation", "projectRoot": str(root), "checks": [asdict(check) for check in checks], "remainingVerification": [ "Have the installation Agent verify the saved env through nb CLI's default redacted info output.", "Register the generated stdio MCP configuration in the user's client.", "Call the MCP tool crm_health and require ok=true.", ], }