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
229 lines
7.8 KiB
Python
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)
|