feat(cli): enhance version/info/diagnostics with --format and --check
Add structured data builders and Rich renderers for version, info, and diagnostics commands. Support --format rich/plain/json/yaml output parity and --check flag for diagnostics (exits non-zero on errors). Includes Behave scenarios, Robot smoke tests, ASV benchmarks, and updates existing tests to match the new Rich panel output format.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""ASV benchmarks for enhanced CLI core system commands.
|
||||
|
||||
Measures in-process execution time for version, info, and diagnostics
|
||||
commands across all supported output formats (rich, plain, json, yaml).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from cleveragents.cli.commands.system import (
|
||||
build_diagnostics_data,
|
||||
build_info_data,
|
||||
build_version_data,
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
except ModuleNotFoundError:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||||
from cleveragents.cli.commands.system import (
|
||||
build_diagnostics_data,
|
||||
build_info_data,
|
||||
build_version_data,
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
|
||||
class VersionSuite:
|
||||
"""Benchmark version data assembly and formatting."""
|
||||
|
||||
def time_build_version_data(self) -> None:
|
||||
"""Measure build_version_data() latency."""
|
||||
build_version_data()
|
||||
|
||||
def time_version_json(self) -> None:
|
||||
"""Measure version + JSON format."""
|
||||
data = build_version_data()
|
||||
format_output(data, "json")
|
||||
|
||||
def time_version_yaml(self) -> None:
|
||||
"""Measure version + YAML format."""
|
||||
data = build_version_data()
|
||||
format_output(data, "yaml")
|
||||
|
||||
def time_version_plain(self) -> None:
|
||||
"""Measure version + plain format."""
|
||||
data = build_version_data()
|
||||
format_output(data, "plain")
|
||||
|
||||
|
||||
class InfoSuite:
|
||||
"""Benchmark info data assembly and formatting."""
|
||||
|
||||
def time_build_info_data(self) -> None:
|
||||
"""Measure build_info_data() latency."""
|
||||
build_info_data()
|
||||
|
||||
def time_info_json(self) -> None:
|
||||
"""Measure info + JSON format."""
|
||||
data = build_info_data()
|
||||
format_output(data, "json")
|
||||
|
||||
def time_info_yaml(self) -> None:
|
||||
"""Measure info + YAML format."""
|
||||
data = build_info_data()
|
||||
format_output(data, "yaml")
|
||||
|
||||
def time_info_plain(self) -> None:
|
||||
"""Measure info + plain format."""
|
||||
data = build_info_data()
|
||||
format_output(data, "plain")
|
||||
|
||||
|
||||
class DiagnosticsSuite:
|
||||
"""Benchmark diagnostics data assembly and formatting."""
|
||||
|
||||
def time_build_diagnostics_data(self) -> None:
|
||||
"""Measure build_diagnostics_data() latency."""
|
||||
build_diagnostics_data()
|
||||
|
||||
def time_diagnostics_json(self) -> None:
|
||||
"""Measure diagnostics + JSON format."""
|
||||
data = build_diagnostics_data()
|
||||
format_output(data, "json")
|
||||
|
||||
def time_diagnostics_yaml(self) -> None:
|
||||
"""Measure diagnostics + YAML format."""
|
||||
data = build_diagnostics_data()
|
||||
format_output(data, "yaml")
|
||||
|
||||
def time_diagnostics_plain(self) -> None:
|
||||
"""Measure diagnostics + plain format."""
|
||||
data = build_diagnostics_data()
|
||||
format_output(data, "plain")
|
||||
@@ -10,15 +10,13 @@ Feature: CleverAgents CLI metadata
|
||||
|
||||
Scenario: Display diagnostics
|
||||
When I run the CleverAgents CLI with "diagnostics"
|
||||
Then the output should contain "CleverAgents Diagnostics"
|
||||
And the output should contain "Version:"
|
||||
And the output should contain "Python:"
|
||||
And the output should contain "Platform:"
|
||||
Then the output should contain "Checks"
|
||||
And the output should contain "Summary"
|
||||
|
||||
Scenario: Display info
|
||||
When I run the CleverAgents CLI with "info"
|
||||
Then the output should contain "CleverAgents Information"
|
||||
And the output should contain "Version: 1.0.0"
|
||||
Then the output should contain "Environment"
|
||||
And the output should contain "Runtime"
|
||||
|
||||
Scenario Outline: Display help for command groups
|
||||
When I run the CleverAgents CLI with "<command> --help"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
Feature: Core system commands (version, info, diagnostics)
|
||||
As a user of CleverAgents
|
||||
I want version, info, and diagnostics commands
|
||||
So that I can inspect the system state and troubleshoot issues
|
||||
|
||||
# -- version command --
|
||||
|
||||
Scenario: Version command shows version string
|
||||
When I run the system version command
|
||||
Then the system version output should contain "1.0.0"
|
||||
And the system version output should contain "CleverAgents"
|
||||
|
||||
Scenario: Version command with plain format
|
||||
When I run the system version command with format "plain"
|
||||
Then the system version output should contain "version: 1.0.0"
|
||||
|
||||
Scenario: Version command with json format
|
||||
When I run the system version command with format "json"
|
||||
Then the system version json output should have key "version" with value "1.0.0"
|
||||
And the system version json output should have key "schema" with value "v3"
|
||||
And the system version json output should have key "channel" with value "stable"
|
||||
|
||||
Scenario: Version command with yaml format
|
||||
When I run the system version command with format "yaml"
|
||||
Then the system version output should contain "version: 1.0.0"
|
||||
And the system version output should contain "schema: v3"
|
||||
|
||||
Scenario: Version command includes python version
|
||||
When I run the system version command with format "json"
|
||||
Then the system version json output should have key "python"
|
||||
|
||||
Scenario: Version command includes build metadata
|
||||
When I run the system version command with format "json"
|
||||
Then the system version json output should have key "build_date"
|
||||
And the system version json output should have key "commit"
|
||||
And the system version json output should have key "platform"
|
||||
|
||||
Scenario: Version command includes dependencies
|
||||
When I run the system version command with format "json"
|
||||
Then the system version json output should have nested key "dependencies"
|
||||
|
||||
# -- info command --
|
||||
|
||||
Scenario: Info command shows environment details
|
||||
When I run the system info command
|
||||
Then the system info output should contain "Environment"
|
||||
And the system info output should contain "Runtime"
|
||||
|
||||
Scenario: Info command with json format
|
||||
When I run the system info command with format "json"
|
||||
Then the system info json output should have key "version" with value "1.0.0"
|
||||
And the system info json output should have key "data_dir"
|
||||
And the system info json output should have key "database"
|
||||
And the system info json output should have key "server_mode" with value "disabled"
|
||||
|
||||
Scenario: Info command with plain format
|
||||
When I run the system info command with format "plain"
|
||||
Then the system info output should contain "version: 1.0.0"
|
||||
And the system info output should contain "server_mode: disabled"
|
||||
|
||||
Scenario: Info command shows provider count
|
||||
When I run the system info command with format "json"
|
||||
Then the system info json output should have key "providers_configured"
|
||||
|
||||
Scenario: Info command shows automation level
|
||||
When I run the system info command with format "json"
|
||||
Then the system info json output should have key "automation"
|
||||
|
||||
Scenario: Info command shows storage info
|
||||
When I run the system info command with format "json"
|
||||
Then the system info json output should have nested key "storage"
|
||||
|
||||
# -- diagnostics command --
|
||||
|
||||
Scenario: Diagnostics command runs checks
|
||||
When I run the system diagnostics command
|
||||
Then the system diagnostics output should contain "Checks"
|
||||
And the system diagnostics output should contain "Summary"
|
||||
|
||||
Scenario: Diagnostics command with json format
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics json output should have key "checks"
|
||||
And the system diagnostics json output should have key "summary"
|
||||
And the system diagnostics json output should have key "recommendations"
|
||||
|
||||
Scenario: Diagnostics json summary has counts
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics json summary should have key "total"
|
||||
And the system diagnostics json summary should have key "warnings"
|
||||
And the system diagnostics json summary should have key "errors"
|
||||
And the system diagnostics json summary should have key "duration_s"
|
||||
|
||||
Scenario: Diagnostics checks include config file
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics checks should include "Config file"
|
||||
|
||||
Scenario: Diagnostics checks include database
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics checks should include "Database"
|
||||
|
||||
Scenario: Diagnostics checks include git
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics checks should include "Git"
|
||||
|
||||
Scenario: Diagnostics checks include disk space
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics checks should include "Disk space"
|
||||
|
||||
Scenario: Diagnostics checks include file permissions
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics checks should include "File permissions"
|
||||
|
||||
Scenario: Diagnostics --check exits zero when no errors
|
||||
When I run the system diagnostics command with check flag
|
||||
Then the system diagnostics exit code should be 0
|
||||
|
||||
Scenario: Diagnostics with plain format
|
||||
When I run the system diagnostics command with format "plain"
|
||||
Then the system diagnostics output should contain "checks:"
|
||||
And the system diagnostics output should contain "summary:"
|
||||
|
||||
Scenario: Diagnostics recommendations are a list
|
||||
When I run the system diagnostics command with format "json"
|
||||
Then the system diagnostics json recommendations should be a list
|
||||
@@ -0,0 +1,237 @@
|
||||
"""Step definitions for core system commands (version, info, diagnostics).
|
||||
|
||||
Covers features/cli_core.feature — the enhanced CLI0.core commands that
|
||||
support ``--format`` (rich/plain/json/yaml) and ``--check`` for diagnostics.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from behave import then, when
|
||||
from behave.runner import Context
|
||||
|
||||
from cleveragents.cli.commands.system import (
|
||||
build_diagnostics_data,
|
||||
build_info_data,
|
||||
build_version_data,
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _format_data(data: dict[str, Any], fmt: str) -> str:
|
||||
"""Format *data* and return the rendered string."""
|
||||
if fmt == "rich":
|
||||
from io import StringIO
|
||||
from unittest.mock import patch
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
buf = StringIO()
|
||||
console = Console(file=buf, width=200, no_color=True)
|
||||
|
||||
with patch("cleveragents.cli.main.get_console", return_value=console):
|
||||
if data.get("checks") is not None:
|
||||
from cleveragents.cli.commands.system import render_diagnostics_rich
|
||||
|
||||
render_diagnostics_rich(data)
|
||||
elif "data_dir" in data:
|
||||
from cleveragents.cli.commands.system import render_info_rich
|
||||
|
||||
render_info_rich(data)
|
||||
else:
|
||||
from cleveragents.cli.commands.system import render_version_rich
|
||||
|
||||
render_version_rich(data)
|
||||
|
||||
return buf.getvalue()
|
||||
return format_output(data, fmt)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# VERSION — When steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I run the system version command")
|
||||
def step_run_system_version(context: Context) -> None:
|
||||
"""Run version with default (rich) format."""
|
||||
data = build_version_data()
|
||||
context.sys_version_data = data
|
||||
context.sys_version_output = _format_data(data, "rich")
|
||||
|
||||
|
||||
@when('I run the system version command with format "{fmt}"')
|
||||
def step_run_system_version_fmt(context: Context, fmt: str) -> None:
|
||||
"""Run version with a specific output format."""
|
||||
data = build_version_data()
|
||||
context.sys_version_data = data
|
||||
context.sys_version_output = _format_data(data, fmt)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# VERSION — Then steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then('the system version output should contain "{text}"')
|
||||
def step_system_version_output_contains(context: Context, text: str) -> None:
|
||||
output = context.sys_version_output
|
||||
assert text in output, f"Expected '{text}' in version output:\n{output}"
|
||||
|
||||
|
||||
@then('the system version json output should have key "{key}" with value "{value}"')
|
||||
def step_system_version_json_key_value(context: Context, key: str, value: str) -> None:
|
||||
data = context.sys_version_data
|
||||
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
||||
assert str(data[key]) == value, f"Expected {key}={value!r}, got {data[key]!r}"
|
||||
|
||||
|
||||
@then('the system version json output should have key "{key}"')
|
||||
def step_system_version_json_key(context: Context, key: str) -> None:
|
||||
data = context.sys_version_data
|
||||
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
||||
|
||||
|
||||
@then('the system version json output should have nested key "{key}"')
|
||||
def step_system_version_json_nested_key(context: Context, key: str) -> None:
|
||||
data = context.sys_version_data
|
||||
assert key in data, f"Key '{key}' not in version data: {list(data.keys())}"
|
||||
assert isinstance(data[key], dict), (
|
||||
f"Expected '{key}' to be a dict, got {type(data[key])}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# INFO — When steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I run the system info command")
|
||||
def step_run_system_info(context: Context) -> None:
|
||||
"""Run info with default (rich) format."""
|
||||
data = build_info_data()
|
||||
context.sys_info_data = data
|
||||
context.sys_info_output = _format_data(data, "rich")
|
||||
|
||||
|
||||
@when('I run the system info command with format "{fmt}"')
|
||||
def step_run_system_info_fmt(context: Context, fmt: str) -> None:
|
||||
"""Run info with a specific output format."""
|
||||
data = build_info_data()
|
||||
context.sys_info_data = data
|
||||
context.sys_info_output = _format_data(data, fmt)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# INFO — Then steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then('the system info output should contain "{text}"')
|
||||
def step_system_info_output_contains(context: Context, text: str) -> None:
|
||||
output = context.sys_info_output
|
||||
assert text in output, f"Expected '{text}' in info output:\n{output}"
|
||||
|
||||
|
||||
@then('the system info json output should have key "{key}" with value "{value}"')
|
||||
def step_system_info_json_key_value(context: Context, key: str, value: str) -> None:
|
||||
data = context.sys_info_data
|
||||
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
||||
assert str(data[key]) == value, f"Expected {key}={value!r}, got {data[key]!r}"
|
||||
|
||||
|
||||
@then('the system info json output should have key "{key}"')
|
||||
def step_system_info_json_key(context: Context, key: str) -> None:
|
||||
data = context.sys_info_data
|
||||
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
||||
|
||||
|
||||
@then('the system info json output should have nested key "{key}"')
|
||||
def step_system_info_json_nested_key(context: Context, key: str) -> None:
|
||||
data = context.sys_info_data
|
||||
assert key in data, f"Key '{key}' not in info data: {list(data.keys())}"
|
||||
assert isinstance(data[key], dict), (
|
||||
f"Expected '{key}' to be a dict, got {type(data[key])}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DIAGNOSTICS — When steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when("I run the system diagnostics command")
|
||||
def step_run_system_diagnostics(context: Context) -> None:
|
||||
"""Run diagnostics with default (rich) format."""
|
||||
data = build_diagnostics_data()
|
||||
context.sys_diag_data = data
|
||||
context.sys_diag_output = _format_data(data, "rich")
|
||||
|
||||
|
||||
@when('I run the system diagnostics command with format "{fmt}"')
|
||||
def step_run_system_diagnostics_fmt(context: Context, fmt: str) -> None:
|
||||
"""Run diagnostics with a specific output format."""
|
||||
data = build_diagnostics_data()
|
||||
context.sys_diag_data = data
|
||||
context.sys_diag_output = _format_data(data, fmt)
|
||||
|
||||
|
||||
@when("I run the system diagnostics command with check flag")
|
||||
def step_run_system_diagnostics_check(context: Context) -> None:
|
||||
"""Run diagnostics with --check flag."""
|
||||
data = build_diagnostics_data()
|
||||
context.sys_diag_data = data
|
||||
context.sys_diag_output = _format_data(data, "rich")
|
||||
# Compute exit code: 1 if errors, 0 otherwise
|
||||
context.sys_diag_exit_code = 1 if data["has_errors"] else 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DIAGNOSTICS — Then steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then('the system diagnostics output should contain "{text}"')
|
||||
def step_system_diag_output_contains(context: Context, text: str) -> None:
|
||||
output = context.sys_diag_output
|
||||
assert text in output, f"Expected '{text}' in diagnostics output:\n{output}"
|
||||
|
||||
|
||||
@then('the system diagnostics json output should have key "{key}"')
|
||||
def step_system_diag_json_key(context: Context, key: str) -> None:
|
||||
data = context.sys_diag_data
|
||||
assert key in data, f"Key '{key}' not in diagnostics data: {list(data.keys())}"
|
||||
|
||||
|
||||
@then('the system diagnostics json summary should have key "{key}"')
|
||||
def step_system_diag_json_summary_key(context: Context, key: str) -> None:
|
||||
summary = context.sys_diag_data.get("summary", {})
|
||||
assert key in summary, (
|
||||
f"Key '{key}' not in diagnostics summary: {list(summary.keys())}"
|
||||
)
|
||||
|
||||
|
||||
@then('the system diagnostics checks should include "{name}"')
|
||||
def step_system_diag_checks_include(context: Context, name: str) -> None:
|
||||
checks = context.sys_diag_data.get("checks", [])
|
||||
names = [c["name"] for c in checks]
|
||||
assert name in names, f"Check '{name}' not in diagnostics checks: {names}"
|
||||
|
||||
|
||||
@then("the system diagnostics exit code should be {code:d}")
|
||||
def step_system_diag_exit_code(context: Context, code: int) -> None:
|
||||
actual = getattr(context, "sys_diag_exit_code", 0)
|
||||
assert actual == code, f"Expected exit code {code}, got {actual}"
|
||||
|
||||
|
||||
@then("the system diagnostics json recommendations should be a list")
|
||||
def step_system_diag_recommendations_list(context: Context) -> None:
|
||||
recs = context.sys_diag_data.get("recommendations")
|
||||
assert isinstance(recs, list), (
|
||||
f"Expected recommendations to be a list, got {type(recs)}"
|
||||
)
|
||||
@@ -184,15 +184,11 @@ def step_test_cli_commands(context):
|
||||
|
||||
# Test info
|
||||
result = runner.invoke(app, ["info"])
|
||||
context.results.append(
|
||||
(result.exit_code, "CleverAgents Information" in result.stdout)
|
||||
)
|
||||
context.results.append((result.exit_code, "Environment" in result.stdout))
|
||||
|
||||
# Test diagnostics
|
||||
result = runner.invoke(app, ["diagnostics"])
|
||||
context.results.append(
|
||||
(result.exit_code, "CleverAgents Diagnostics" in result.stdout)
|
||||
)
|
||||
context.results.append((result.exit_code, "Checks" in result.stdout))
|
||||
|
||||
# Test init - use temp directory to avoid conflicts
|
||||
import os
|
||||
|
||||
+4
-6
@@ -46,17 +46,15 @@ CLI Diagnostics Command
|
||||
[Documentation] Test diagnostics command functionality
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} CleverAgents Diagnostics
|
||||
Should Contain ${result.stdout} Version:
|
||||
Should Contain ${result.stdout} Python:
|
||||
Should Contain ${result.stdout} Platform:
|
||||
Should Contain ${result.stdout} Checks
|
||||
Should Contain ${result.stdout} Summary
|
||||
|
||||
CLI Info Command
|
||||
[Documentation] Test info command functionality
|
||||
${result}= Run Process ${PYTHON} -m cleveragents info
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} CleverAgents Information
|
||||
Should Contain ${result.stdout} Version:
|
||||
Should Contain ${result.stdout} Environment
|
||||
Should Contain ${result.stdout} Runtime
|
||||
|
||||
Invalid Command Error Handling
|
||||
[Documentation] Verify proper error handling for invalid commands
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
*** Settings ***
|
||||
Documentation Integration tests for enhanced version, info, diagnostics commands
|
||||
Resource ${CURDIR}/common.resource
|
||||
Library Process
|
||||
Library OperatingSystem
|
||||
Library String
|
||||
Library Collections
|
||||
Suite Setup Setup Test Environment
|
||||
Suite Teardown Cleanup Test Environment
|
||||
|
||||
*** Variables ***
|
||||
${PYTHON} python
|
||||
|
||||
*** Test Cases ***
|
||||
Version Command Default Rich Format
|
||||
[Documentation] Version command with default (rich) format shows version string
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} 1.0.0
|
||||
Should Contain ${result.stdout} CleverAgents
|
||||
|
||||
Version Command JSON Format
|
||||
[Documentation] Version command with --format json returns valid JSON
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format json timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "version": "1.0.0"
|
||||
Should Contain ${result.stdout} "schema": "v3"
|
||||
Should Contain ${result.stdout} "channel": "stable"
|
||||
Should Contain ${result.stdout} "python"
|
||||
Should Contain ${result.stdout} "dependencies"
|
||||
Should Contain ${result.stdout} "build_date"
|
||||
Should Contain ${result.stdout} "commit"
|
||||
|
||||
Version Command Plain Format
|
||||
[Documentation] Version command with --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format plain timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} schema: v3
|
||||
|
||||
Version Command YAML Format
|
||||
[Documentation] Version command with --format yaml returns YAML
|
||||
${result}= Run Process ${PYTHON} -m cleveragents version --format yaml timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} schema: v3
|
||||
|
||||
Info Command Default Rich Format
|
||||
[Documentation] Info command with default (rich) format shows environment details
|
||||
${result}= Run Process ${PYTHON} -m cleveragents info timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} Environment
|
||||
Should Contain ${result.stdout} Runtime
|
||||
|
||||
Info Command JSON Format
|
||||
[Documentation] Info command with --format json returns structured data
|
||||
${result}= Run Process ${PYTHON} -m cleveragents info --format json timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "version": "1.0.0"
|
||||
Should Contain ${result.stdout} "data_dir"
|
||||
Should Contain ${result.stdout} "database"
|
||||
Should Contain ${result.stdout} "server_mode": "disabled"
|
||||
|
||||
Info Command Plain Format
|
||||
[Documentation] Info command with --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents info --format plain timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} version: 1.0.0
|
||||
Should Contain ${result.stdout} server_mode: disabled
|
||||
|
||||
Diagnostics Command Default Rich Format
|
||||
[Documentation] Diagnostics command with default (rich) format runs checks
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} Checks
|
||||
Should Contain ${result.stdout} Summary
|
||||
|
||||
Diagnostics Command JSON Format
|
||||
[Documentation] Diagnostics command with --format json returns structured data
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} "checks"
|
||||
Should Contain ${result.stdout} "summary"
|
||||
Should Contain ${result.stdout} "recommendations"
|
||||
Should Contain ${result.stdout} "total"
|
||||
Should Contain ${result.stdout} "warnings"
|
||||
Should Contain ${result.stdout} "errors"
|
||||
|
||||
Diagnostics Command Plain Format
|
||||
[Documentation] Diagnostics command with --format plain returns key-value pairs
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format plain timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
Should Contain ${result.stdout} checks:
|
||||
Should Contain ${result.stdout} summary:
|
||||
|
||||
Diagnostics Command Check Flag No Errors
|
||||
[Documentation] Diagnostics --check exits zero when no errors
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --check timeout=30s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
|
||||
Diagnostics Command Performance
|
||||
[Documentation] Diagnostics command completes within acceptable time
|
||||
${start}= Get Time epoch
|
||||
${result}= Run Process ${PYTHON} -m cleveragents diagnostics --format json timeout=30s
|
||||
${end}= Get Time epoch
|
||||
${duration}= Evaluate ${end} - ${start}
|
||||
Should Be True ${duration} < 30 Diagnostics took too long: ${duration}s
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
@@ -0,0 +1,508 @@
|
||||
"""Core system commands: version, info, diagnostics.
|
||||
|
||||
Implements the CLI0.core group per specification. Each command supports
|
||||
``--format`` for output parity (rich/plain/json/yaml) and ``diagnostics``
|
||||
additionally supports ``--check`` to exit non-zero when any check fails.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from enum import StrEnum
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from cleveragents import __version__
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Diagnostic check status
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class CheckStatus(StrEnum):
|
||||
"""Status of a single diagnostic check."""
|
||||
|
||||
OK = "ok"
|
||||
WARN = "warn"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data builders (format-agnostic)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _git_sha() -> str:
|
||||
"""Return the short git SHA of HEAD, or 'unknown'."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
||||
pass
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _dep_version(package: str) -> str:
|
||||
"""Return the installed version of *package*, or 'not installed'."""
|
||||
try:
|
||||
from importlib.metadata import version as pkg_version
|
||||
|
||||
return pkg_version(package)
|
||||
except Exception:
|
||||
return "not installed"
|
||||
|
||||
|
||||
def build_version_data() -> dict[str, Any]:
|
||||
"""Assemble structured data for the ``version`` command."""
|
||||
return {
|
||||
"version": __version__,
|
||||
"channel": "stable",
|
||||
"python": platform.python_version(),
|
||||
"build_date": datetime.now(tz=UTC).strftime("%Y-%m-%d"),
|
||||
"commit": _git_sha(),
|
||||
"schema": "v3",
|
||||
"platform": f"{sys.platform}-{platform.machine()}",
|
||||
"dependencies": {
|
||||
"langgraph": _dep_version("langgraph"),
|
||||
"langchain-core": _dep_version("langchain-core"),
|
||||
"pydantic": _dep_version("pydantic"),
|
||||
"typer": _dep_version("typer"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def build_info_data() -> dict[str, Any]:
|
||||
"""Assemble structured data for the ``info`` command."""
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
configured_providers = settings.configured_provider_names()
|
||||
data_dir = settings.data_dir
|
||||
config_path = Path(
|
||||
os.environ.get(
|
||||
"CLEVERAGENTS_CONFIG_PATH",
|
||||
str(settings.storage_path / "config.toml"),
|
||||
)
|
||||
)
|
||||
db_url = settings.database_url
|
||||
|
||||
# Storage sizes (best effort)
|
||||
storage: dict[str, str] = {}
|
||||
try:
|
||||
db_path_str = db_url.replace("sqlite:///", "")
|
||||
db_path = Path(db_path_str)
|
||||
if db_path.exists():
|
||||
size_mb = db_path.stat().st_size / (1024 * 1024)
|
||||
storage["db_size"] = f"{size_mb:.1f} MB"
|
||||
else:
|
||||
storage["db_size"] = "0 MB"
|
||||
except Exception:
|
||||
storage["db_size"] = "unknown"
|
||||
|
||||
log_dir = settings.log_dir
|
||||
if log_dir.exists():
|
||||
total = sum(f.stat().st_size for f in log_dir.rglob("*") if f.is_file())
|
||||
storage["logs"] = f"{total / (1024 * 1024):.1f} MB"
|
||||
else:
|
||||
storage["logs"] = "0 MB"
|
||||
|
||||
return {
|
||||
"version": __version__,
|
||||
"data_dir": str(data_dir),
|
||||
"config_path": str(config_path),
|
||||
"database": db_url,
|
||||
"server_mode": "disabled",
|
||||
"platform": f"{platform.system()} {platform.release()} ({platform.machine()})",
|
||||
"automation": settings.default_automation_level,
|
||||
"providers_configured": len(configured_providers),
|
||||
"providers": configured_providers,
|
||||
"debug_mode": settings.debug_enabled,
|
||||
"storage": storage,
|
||||
}
|
||||
|
||||
|
||||
def _check_config_file() -> dict[str, Any]:
|
||||
"""Check that the config path is readable."""
|
||||
config_path = Path(
|
||||
os.environ.get(
|
||||
"CLEVERAGENTS_CONFIG_PATH",
|
||||
"config.toml",
|
||||
)
|
||||
)
|
||||
if config_path.exists():
|
||||
readable = os.access(config_path, os.R_OK)
|
||||
return {
|
||||
"name": "Config file",
|
||||
"status": CheckStatus.OK if readable else CheckStatus.ERROR,
|
||||
"details": "readable" if readable else "not readable",
|
||||
}
|
||||
return {
|
||||
"name": "Config file",
|
||||
"status": CheckStatus.OK,
|
||||
"details": "not present (using defaults)",
|
||||
}
|
||||
|
||||
|
||||
def _check_data_dir() -> dict[str, Any]:
|
||||
"""Check that the data directory exists and is writable."""
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
data_dir = settings.data_dir
|
||||
|
||||
if not data_dir.exists():
|
||||
return {
|
||||
"name": "Data directory",
|
||||
"status": CheckStatus.WARN,
|
||||
"details": f"missing ({data_dir})",
|
||||
"recommendation": f"Create it with: mkdir -p {data_dir}",
|
||||
}
|
||||
|
||||
writable = os.access(data_dir, os.W_OK)
|
||||
return {
|
||||
"name": "Data directory",
|
||||
"status": CheckStatus.OK if writable else CheckStatus.ERROR,
|
||||
"details": "writable" if writable else "not writable",
|
||||
}
|
||||
|
||||
|
||||
def _check_database() -> dict[str, Any]:
|
||||
"""Check database connectivity."""
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
db_url = settings.database_url
|
||||
|
||||
if db_url.startswith("sqlite"):
|
||||
db_path_str = db_url.replace("sqlite:///", "")
|
||||
db_path = Path(db_path_str)
|
||||
if db_path.exists():
|
||||
writable = os.access(db_path, os.W_OK)
|
||||
return {
|
||||
"name": "Database",
|
||||
"status": CheckStatus.OK if writable else CheckStatus.ERROR,
|
||||
"details": "writable" if writable else "locked or not writable",
|
||||
}
|
||||
# DB file doesn't exist yet — that's OK for SQLite (created on first use)
|
||||
parent = db_path.parent
|
||||
if parent.exists() and os.access(parent, os.W_OK):
|
||||
return {
|
||||
"name": "Database",
|
||||
"status": CheckStatus.OK,
|
||||
"details": "will be created on first use",
|
||||
}
|
||||
return {
|
||||
"name": "Database",
|
||||
"status": CheckStatus.ERROR,
|
||||
"details": f"parent dir not writable ({parent})",
|
||||
}
|
||||
|
||||
return {
|
||||
"name": "Database",
|
||||
"status": CheckStatus.OK,
|
||||
"details": "configured",
|
||||
}
|
||||
|
||||
|
||||
def _check_providers() -> list[dict[str, Any]]:
|
||||
"""Check provider API key configuration."""
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
results: list[dict[str, Any]] = []
|
||||
|
||||
provider_checks = [
|
||||
("openai", "OPENAI_API_KEY"),
|
||||
("anthropic", "ANTHROPIC_API_KEY"),
|
||||
("google", "GOOGLE_API_KEY"),
|
||||
("openrouter", "OPENROUTER_API_KEY"),
|
||||
]
|
||||
|
||||
for provider_name, env_var in provider_checks:
|
||||
configured = settings.has_provider_configured(provider_name)
|
||||
results.append(
|
||||
{
|
||||
"name": f"{provider_name.capitalize()} key",
|
||||
"status": CheckStatus.OK if configured else CheckStatus.WARN,
|
||||
"details": "configured" if configured else "missing",
|
||||
"recommendation": (
|
||||
f"Set {env_var} to enable {provider_name.capitalize()} models"
|
||||
if not configured
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def _check_disk_space() -> dict[str, Any]:
|
||||
"""Check available disk space."""
|
||||
try:
|
||||
usage = shutil.disk_usage(Path.cwd())
|
||||
free_gb = usage.free / (1024**3)
|
||||
if free_gb < 0.5:
|
||||
return {
|
||||
"name": "Disk space",
|
||||
"status": CheckStatus.ERROR,
|
||||
"details": f"{free_gb:.1f} GB free (critically low)",
|
||||
}
|
||||
if free_gb < 1.0:
|
||||
return {
|
||||
"name": "Disk space",
|
||||
"status": CheckStatus.WARN,
|
||||
"details": f"{free_gb:.1f} GB free (low)",
|
||||
}
|
||||
return {
|
||||
"name": "Disk space",
|
||||
"status": CheckStatus.OK,
|
||||
"details": f"{free_gb:.1f} GB free",
|
||||
}
|
||||
except OSError:
|
||||
return {
|
||||
"name": "Disk space",
|
||||
"status": CheckStatus.WARN,
|
||||
"details": "unable to check",
|
||||
}
|
||||
|
||||
|
||||
def _check_git() -> dict[str, Any]:
|
||||
"""Check git availability."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "--version"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
version_str = result.stdout.strip().replace("git version ", "")
|
||||
return {
|
||||
"name": "Git",
|
||||
"status": CheckStatus.OK,
|
||||
"details": f"git {version_str}",
|
||||
}
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
||||
pass
|
||||
return {
|
||||
"name": "Git",
|
||||
"status": CheckStatus.ERROR,
|
||||
"details": "not found",
|
||||
"recommendation": "Install git for version control integration",
|
||||
}
|
||||
|
||||
|
||||
def _check_file_permissions() -> dict[str, Any]:
|
||||
"""Check file permissions on the data directory."""
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
data_dir = settings.data_dir
|
||||
|
||||
if not data_dir.exists():
|
||||
return {
|
||||
"name": "File permissions",
|
||||
"status": CheckStatus.WARN,
|
||||
"details": "data dir does not exist",
|
||||
}
|
||||
|
||||
readable = os.access(data_dir, os.R_OK)
|
||||
writable = os.access(data_dir, os.W_OK)
|
||||
if readable and writable:
|
||||
return {
|
||||
"name": "File permissions",
|
||||
"status": CheckStatus.OK,
|
||||
"details": "data dir r/w",
|
||||
}
|
||||
perms = []
|
||||
if readable:
|
||||
perms.append("r")
|
||||
if writable:
|
||||
perms.append("w")
|
||||
return {
|
||||
"name": "File permissions",
|
||||
"status": CheckStatus.ERROR if not writable else CheckStatus.WARN,
|
||||
"details": f"data dir {''.join(perms) or 'no access'}",
|
||||
}
|
||||
|
||||
|
||||
def build_diagnostics_data() -> dict[str, Any]:
|
||||
"""Run all diagnostic checks and return structured results."""
|
||||
start = time.monotonic()
|
||||
|
||||
checks: list[dict[str, Any]] = []
|
||||
checks.append(_check_config_file())
|
||||
checks.append(_check_data_dir())
|
||||
checks.append(_check_database())
|
||||
checks.extend(_check_providers())
|
||||
checks.append(_check_disk_space())
|
||||
checks.append(_check_file_permissions())
|
||||
checks.append(_check_git())
|
||||
|
||||
elapsed = time.monotonic() - start
|
||||
|
||||
total = len(checks)
|
||||
ok_count = sum(1 for c in checks if c["status"] == CheckStatus.OK)
|
||||
warn_count = sum(1 for c in checks if c["status"] == CheckStatus.WARN)
|
||||
error_count = sum(1 for c in checks if c["status"] == CheckStatus.ERROR)
|
||||
|
||||
recommendations: list[str] = []
|
||||
for check in checks:
|
||||
rec = check.get("recommendation")
|
||||
if rec:
|
||||
recommendations.append(rec)
|
||||
|
||||
return {
|
||||
"checks": checks,
|
||||
"summary": {
|
||||
"total": total,
|
||||
"ok": ok_count,
|
||||
"warnings": warn_count,
|
||||
"errors": error_count,
|
||||
"duration_s": round(elapsed, 2),
|
||||
},
|
||||
"recommendations": recommendations,
|
||||
"has_errors": error_count > 0,
|
||||
"has_warnings": warn_count > 0,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Rich rendering helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def render_version_rich(data: dict[str, Any]) -> None:
|
||||
"""Print version information using Rich panels."""
|
||||
from rich.panel import Panel
|
||||
|
||||
from cleveragents.cli.main import get_console
|
||||
|
||||
console = get_console()
|
||||
|
||||
# CLI Version panel
|
||||
version_lines = [
|
||||
"[bold]CleverAgents CLI[/bold]",
|
||||
f"[blue]Version:[/blue] {data['version']}",
|
||||
f"[blue]Channel:[/blue] {data['channel']}",
|
||||
f"[blue]Python:[/blue] {data['python']}",
|
||||
]
|
||||
console.print(Panel("\n".join(version_lines), title="CLI Version", expand=False))
|
||||
|
||||
# Build panel
|
||||
build_lines = [
|
||||
f"[green]Build Date:[/green] {data['build_date']}",
|
||||
f"[magenta]Commit:[/magenta] {data['commit']}",
|
||||
f"[blue]Schema:[/blue] {data['schema']}",
|
||||
f"[blue]Platform:[/blue] {data['platform']}",
|
||||
]
|
||||
console.print(Panel("\n".join(build_lines), title="Build", expand=False))
|
||||
|
||||
# Dependencies panel
|
||||
deps = data.get("dependencies", {})
|
||||
dep_lines = [f"[blue]{k}:[/blue] {v}" for k, v in deps.items()]
|
||||
if dep_lines:
|
||||
console.print(Panel("\n".join(dep_lines), title="Dependencies", expand=False))
|
||||
|
||||
console.print("[green]OK[/green] Version reported")
|
||||
|
||||
|
||||
def render_info_rich(data: dict[str, Any]) -> None:
|
||||
"""Print info using Rich panels."""
|
||||
from rich.panel import Panel
|
||||
|
||||
from cleveragents.cli.main import get_console
|
||||
|
||||
console = get_console()
|
||||
|
||||
env_lines = [
|
||||
f"[blue]Data Dir:[/blue] {data['data_dir']}",
|
||||
f"[blue]Config:[/blue] {data['config_path']}",
|
||||
f"[green]Database:[/green] {data['database']}",
|
||||
f"[yellow]Server Mode:[/yellow] {data['server_mode']}",
|
||||
f"[blue]Platform:[/blue] {data['platform']}",
|
||||
]
|
||||
console.print(Panel("\n".join(env_lines), title="Environment", expand=False))
|
||||
|
||||
runtime_lines = [
|
||||
f"[magenta]Automation:[/magenta] {data['automation']}",
|
||||
f"[blue]Providers:[/blue] {data['providers_configured']} configured",
|
||||
f"[blue]Debug Mode:[/blue] {data['debug_mode']}",
|
||||
]
|
||||
console.print(Panel("\n".join(runtime_lines), title="Runtime", expand=False))
|
||||
|
||||
storage = data.get("storage", {})
|
||||
if storage:
|
||||
storage_lines = [f"[blue]{k}:[/blue] {v}" for k, v in storage.items()]
|
||||
console.print(Panel("\n".join(storage_lines), title="Storage", expand=False))
|
||||
|
||||
console.print("[green]OK[/green] Environment details ready")
|
||||
|
||||
|
||||
def render_diagnostics_rich(data: dict[str, Any]) -> None:
|
||||
"""Print diagnostics using Rich panels and table."""
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from cleveragents.cli.main import get_console
|
||||
|
||||
console = get_console()
|
||||
|
||||
# Checks table
|
||||
table = Table(title="Checks", show_header=True, expand=False)
|
||||
table.add_column("Check", style="cyan")
|
||||
table.add_column("Status")
|
||||
table.add_column("Details")
|
||||
|
||||
for check in data["checks"]:
|
||||
status = check["status"]
|
||||
if status == CheckStatus.OK:
|
||||
status_text = "[green]OK[/green]"
|
||||
elif status == CheckStatus.WARN:
|
||||
status_text = "[yellow]WARN[/yellow]"
|
||||
else:
|
||||
status_text = "[red]ERROR[/red]"
|
||||
table.add_row(check["name"], status_text, check.get("details", ""))
|
||||
|
||||
console.print(table)
|
||||
|
||||
# Summary panel
|
||||
summary = data["summary"]
|
||||
summary_lines = [
|
||||
f"[blue]Checks:[/blue] {summary['total']} total",
|
||||
f"[yellow]Warnings:[/yellow] {summary['warnings']}",
|
||||
f"[red]Errors:[/red] {summary['errors']}",
|
||||
f"[green]Duration:[/green] {summary['duration_s']}s",
|
||||
]
|
||||
console.print(Panel("\n".join(summary_lines), title="Summary", expand=False))
|
||||
|
||||
# Recommendations
|
||||
recs = data.get("recommendations", [])
|
||||
if recs:
|
||||
rec_lines = [f"- {r}" for r in recs]
|
||||
console.print(
|
||||
Panel("\n".join(rec_lines), title="Recommendations", expand=False)
|
||||
)
|
||||
|
||||
# Final status line
|
||||
if data["has_errors"]:
|
||||
console.print(f"[red]ERROR[/red] {summary['errors']} errors must be resolved")
|
||||
elif data["has_warnings"]:
|
||||
console.print(
|
||||
f"[yellow]WARN[/yellow] {summary['warnings']} warnings require attention"
|
||||
)
|
||||
else:
|
||||
console.print("[green]OK[/green] All checks passed")
|
||||
@@ -187,68 +187,77 @@ def main_callback(
|
||||
|
||||
|
||||
@app.command()
|
||||
def version() -> None:
|
||||
def version(
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
) -> None:
|
||||
"""Display version information."""
|
||||
console = get_console()
|
||||
console.print(f"CleverAgents {__version__}")
|
||||
from cleveragents.cli.commands.system import (
|
||||
build_version_data,
|
||||
render_version_rich,
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
data = build_version_data()
|
||||
if fmt.lower() == "rich":
|
||||
render_version_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
|
||||
|
||||
@app.command()
|
||||
def info() -> None:
|
||||
def info(
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
) -> None:
|
||||
"""Display system information and configuration."""
|
||||
from rich.panel import Panel
|
||||
from cleveragents.cli.commands.system import build_info_data, render_info_rich
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
from cleveragents.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
actor_configured = getattr(
|
||||
settings, "has_actor_configured", settings.has_provider_configured
|
||||
)()
|
||||
|
||||
info_text = f"""
|
||||
[bold]CleverAgents Information[/bold]
|
||||
Version: {__version__}
|
||||
Storage: {settings.storage_path}
|
||||
Debug Mode: {settings.debug_enabled}
|
||||
Actor Configured: {actor_configured}
|
||||
|
||||
"""
|
||||
|
||||
console = get_console()
|
||||
console.print(Panel(info_text.strip(), title="System Info", expand=False))
|
||||
data = build_info_data()
|
||||
if fmt.lower() == "rich":
|
||||
render_info_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
|
||||
|
||||
@app.command()
|
||||
def diagnostics() -> None:
|
||||
def diagnostics(
|
||||
check: bool = typer.Option(
|
||||
False,
|
||||
"--check",
|
||||
help="Exit non-zero when any check fails",
|
||||
),
|
||||
fmt: str = typer.Option(
|
||||
"rich",
|
||||
"--format",
|
||||
"-f",
|
||||
help="Output format: rich, plain, json, yaml",
|
||||
),
|
||||
) -> None:
|
||||
"""Run system diagnostics and health checks."""
|
||||
import platform
|
||||
from cleveragents.cli.commands.system import (
|
||||
build_diagnostics_data,
|
||||
render_diagnostics_rich,
|
||||
)
|
||||
from cleveragents.cli.formatting import format_output
|
||||
|
||||
from cleveragents.config.settings import get_settings
|
||||
data = build_diagnostics_data()
|
||||
if fmt.lower() == "rich":
|
||||
render_diagnostics_rich(data)
|
||||
else:
|
||||
typer.echo(format_output(data, fmt))
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
# Check basic configuration
|
||||
checks = {
|
||||
"Configuration Loaded": True,
|
||||
"Storage Directory": settings.storage_path.exists(),
|
||||
"Actor Configured": getattr(
|
||||
settings, "has_actor_configured", settings.has_provider_configured
|
||||
)(),
|
||||
"Debug Mode": settings.debug_enabled,
|
||||
}
|
||||
|
||||
console = get_console()
|
||||
console.print("\n[bold]CleverAgents Diagnostics[/bold]\n")
|
||||
console.print(f"Version: {__version__}")
|
||||
console.print(f"Python: {sys.version.split()[0]}")
|
||||
console.print(f"Platform: {platform.platform()}\n")
|
||||
|
||||
for check, result in checks.items():
|
||||
status = "[green]✓[/green]" if result else "[red]✗[/red]"
|
||||
console.print(f"{status} {check}: {result}")
|
||||
|
||||
console.print()
|
||||
if check and data["has_errors"]:
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
|
||||
@app.command()
|
||||
|
||||
Reference in New Issue
Block a user