Files
tracer-cloud--opensre/surfaces/cli/commands/doctor.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

229 lines
7.8 KiB
Python

"""Full environment diagnostic command, inspired by ``fly doctor``.
Rendered output (colour roles):
──────────────────────────────────────────── [DIM rule]
OpenSRE Doctor [TEXT bold section header]
──────────────────────────────────────────── [DIM rule]
✓ python Python 3.13.2 [HIGHLIGHT ✓] [SECONDARY check] [TEXT detail]
✓ env_file .env (12 keys)
⚠ integrations no integrations [WARNING ⚠]
✗ llm_provider ANTHROPIC_API_KEY unset [ERROR ✗]
✓ version <version> (up to date)
✓ network github.com reachable
──────────────────────────────────────────── [DIM rule]
1 error · 1 warning — run opensre doctor [ERROR/WARNING counts · SECONDARY hint]
All checks passed. [HIGHLIGHT — when clean]
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
from typing import Any
import click
from rich.console import Console
from rich.rule import Rule
from rich.text import Text
import platform
from config.version import get_opensre_version
from platform.common.exit_codes import ERROR as EXIT_ERROR
from platform.common.exit_codes import SUCCESS
from platform.common.runtime_flags import is_json_output
from platform.terminal.theme import (
DIM,
ERROR,
GLYPH_ERROR,
GLYPH_SUCCESS,
GLYPH_WARNING,
HIGHLIGHT,
SECONDARY,
TEXT,
WARNING,
)
def _check(name: str, fn: Any) -> dict[str, str]:
"""Run a single diagnostic check and return a result dict."""
try:
ok, detail = fn()
return {"check": name, "status": "ok" if ok else "warn", "detail": detail}
except Exception as exc:
return {"check": name, "status": "error", "detail": str(exc)}
def _check_python_version() -> tuple[bool, str]:
version = platform.python_version()
major, minor = sys.version_info[:2]
if (major, minor) < (3, 11):
return False, f"Python {version} — opensre requires >= 3.11"
return True, f"Python {version}"
def _check_env_file() -> tuple[bool, str]:
env_path = os.getenv("OPENSRE_PROJECT_ENV_PATH", ".env")
path = Path(env_path)
if not path.exists():
return False, f"{env_path} not found"
content = path.read_text(encoding="utf-8", errors="replace")
lines = [ln for ln in content.splitlines() if ln.strip() and not ln.strip().startswith("#")]
return True, f"{env_path} ({len(lines)} keys)"
def _check_llm_provider() -> tuple[bool, str]:
from config.config import get_configured_llm_provider
from config.llm_auth.credentials import status as credential_status
provider = get_configured_llm_provider()
auth = credential_status(provider)
if auth.configured and not auth.stale:
return True, f"provider={provider}, auth={auth.source} ({auth.detail})"
if auth.stale:
return False, f"provider={provider}, auth stale ({auth.detail})"
return False, f"provider={provider}, auth missing ({auth.detail})"
def _check_integrations() -> tuple[bool, str]:
from integrations.store import STORE_PATH, list_integrations
path = Path(str(STORE_PATH))
if not path.exists():
return False, f"{STORE_PATH} not found — run 'opensre integrations setup'"
items = list_integrations()
if not items:
return False, "no integrations configured"
names = [i["service"] for i in items]
return True, f"{len(items)} configured: {', '.join(names)}"
def _check_version_freshness() -> tuple[bool, str]:
current = get_opensre_version()
from surfaces.cli.lifecycle.update import (
_fetch_latest_version,
_is_update_available,
development_install_doctor_version_detail,
)
dev_detail = development_install_doctor_version_detail(current)
if dev_detail is not None:
return True, dev_detail
try:
latest = _fetch_latest_version()
if _is_update_available(current, latest):
return False, f"current={current}, latest={latest} — run 'opensre update'"
return True, f"{current} (up to date)"
except Exception as exc:
return True, f"{current} (could not check: {exc})"
_CHECKS = [
("python", _check_python_version),
("env_file", _check_env_file),
("llm_provider", _check_llm_provider),
("integrations", _check_integrations),
("version", _check_version_freshness),
]
def _render_doctor_results(console: Console, results: list[dict[str, str]]) -> None:
"""Print formatted diagnostic results with design-system colour roles.
Section header: TEXT bold + DIM rule (col 45).
✓ checks: HIGHLIGHT glyph, SECONDARY check name, TEXT detail.
⚠ warnings: WARNING glyph, SECONDARY check name, TEXT detail.
✗ errors: ERROR glyph, SECONDARY check name, TEXT detail.
Summary: ERROR count + WARNING count in their respective roles, SECONDARY hint.
"""
console.print()
console.print(Rule(style=DIM))
# Section header — TEXT weight, DIM rule to col 45.
header = Text()
header.append(" OpenSRE Doctor", style=f"bold {TEXT}")
console.print(header)
console.print(Rule(style=DIM))
console.print()
check_col = 18 # fixed column width for check name alignment
for r in results:
status = r["status"]
if status == "ok":
glyph = GLYPH_SUCCESS
glyph_style = f"bold {HIGHLIGHT}"
detail_style = TEXT
elif status == "warn":
glyph = GLYPH_WARNING
glyph_style = f"bold {WARNING}"
detail_style = TEXT
else:
glyph = GLYPH_ERROR
glyph_style = f"bold {ERROR}"
detail_style = TEXT
row = Text()
row.append(f" {glyph} ", style=glyph_style)
row.append(f"{r['check']:<{check_col}}", style=SECONDARY)
row.append(r["detail"], style=detail_style)
console.print(row)
console.print()
console.print(Rule(style=DIM))
error_count = sum(1 for r in results if r["status"] == "error")
warn_count = sum(1 for r in results if r["status"] == "warn")
if error_count == 0 and warn_count == 0:
summary = Text()
summary.append(f" {GLYPH_SUCCESS} ", style=f"bold {HIGHLIGHT}")
summary.append("All checks passed.", style=TEXT)
console.print(summary)
else:
summary = Text()
summary.append(" ")
if error_count:
summary.append(
f"{error_count} error{'s' if error_count > 1 else ''}", style=f"bold {ERROR}"
)
if error_count and warn_count:
summary.append(" · ", style=SECONDARY)
if warn_count:
summary.append(
f"{warn_count} warning{'s' if warn_count > 1 else ''}", style=f"bold {WARNING}"
)
summary.append(" — fix and rerun ", style=SECONDARY)
summary.append("opensre doctor", style=f"bold {TEXT}")
console.print(summary)
console.print()
@click.command(name="doctor")
def doctor_command() -> None:
"""Run a full environment diagnostic to surface setup issues."""
results: list[dict[str, str]] = []
for name, fn in _CHECKS:
results.append(_check(name, fn))
if is_json_output():
click.echo(json.dumps(results, indent=2))
else:
console = Console(
highlight=False,
force_terminal=True,
color_system="truecolor",
legacy_windows=False,
)
_render_doctor_results(console, results)
has_errors = any(r["status"] == "error" for r in results)
raise SystemExit(EXIT_ERROR if has_errors else SUCCESS)