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
223 lines
7.5 KiB
Python
223 lines
7.5 KiB
Python
"""Single-command CLI entrypoints that do not need their own groups."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
import time
|
|
|
|
import click
|
|
|
|
import platform
|
|
from config.version import get_opensre_version
|
|
from platform.analytics.cli import (
|
|
capture_update_completed,
|
|
capture_update_failed,
|
|
capture_update_started,
|
|
track_investigation,
|
|
)
|
|
from platform.analytics.source import EntrypointSource, TriggerMode
|
|
from platform.common.exit_codes import ERROR, SUCCESS
|
|
from platform.common.runtime_flags import is_json_output, is_yes
|
|
from surfaces.cli.constants import ALERT_TEMPLATE_CHOICES
|
|
|
|
|
|
@click.command(name="uninstall")
|
|
@click.option("--yes", "-y", "local_yes", is_flag=True, help="Skip the confirmation prompt.")
|
|
def uninstall_command(local_yes: bool) -> None:
|
|
"""Remove opensre and all local data from this machine."""
|
|
from surfaces.cli.lifecycle.uninstall import run_uninstall
|
|
|
|
raise SystemExit(run_uninstall(yes=local_yes or is_yes()))
|
|
|
|
|
|
@click.command(name="update")
|
|
@click.option(
|
|
"--check",
|
|
"check_only",
|
|
is_flag=True,
|
|
help="Report whether an update is available without installing.",
|
|
)
|
|
@click.option("--yes", "-y", "local_yes", is_flag=True, help="Skip the confirmation prompt.")
|
|
def update_command(check_only: bool, local_yes: bool) -> None:
|
|
"""Check for a newer main build and update if one is available."""
|
|
from surfaces.cli.lifecycle.update import run_update
|
|
|
|
capture_update_started(check_only=check_only)
|
|
try:
|
|
exit_code = run_update(check_only=check_only, yes=local_yes or is_yes())
|
|
except Exception as exc:
|
|
capture_update_failed(check_only=check_only, reason=type(exc).__name__)
|
|
raise
|
|
|
|
capture_update_completed(
|
|
check_only=check_only,
|
|
updated=exit_code == 0 and not check_only,
|
|
)
|
|
raise SystemExit(exit_code)
|
|
|
|
|
|
@click.command(name="version")
|
|
def version_command() -> None:
|
|
"""Print detailed version, Python and OS info."""
|
|
if is_json_output():
|
|
click.echo(
|
|
json.dumps(
|
|
{
|
|
"opensre": get_opensre_version(),
|
|
"python": platform.python_version(),
|
|
"os": platform.system().lower(),
|
|
"arch": platform.machine(),
|
|
}
|
|
)
|
|
)
|
|
return
|
|
click.echo(f"opensre {get_opensre_version()}")
|
|
click.echo(f"Python {platform.python_version()}")
|
|
click.echo(f"OS {platform.system().lower()} ({platform.machine()})")
|
|
|
|
|
|
@click.command(name="health")
|
|
@click.option("--watch", is_flag=True, help="Continuously refresh the health report.")
|
|
@click.option(
|
|
"--rate", default=5, show_default=True, help="Refresh interval in seconds (with --watch)."
|
|
)
|
|
def health_command(watch: bool, rate: int) -> None:
|
|
"""Show a quick health summary of the local agent setup."""
|
|
from config.config import get_environment
|
|
from integrations.store import STORE_PATH
|
|
from integrations.verify import verify_integrations
|
|
from surfaces.interactive_shell.ui.health import render_health_json, render_health_report
|
|
|
|
def _run_once() -> int:
|
|
results = verify_integrations()
|
|
environment = get_environment().value
|
|
|
|
if is_json_output():
|
|
render_health_json(
|
|
environment=environment,
|
|
integration_store_path=STORE_PATH,
|
|
results=results,
|
|
)
|
|
else:
|
|
from rich.console import Console
|
|
|
|
render_health_report(
|
|
console=Console(highlight=False),
|
|
environment=environment,
|
|
integration_store_path=STORE_PATH,
|
|
results=results,
|
|
)
|
|
|
|
if any(result.get("status") == "failed" for result in results):
|
|
return ERROR
|
|
return SUCCESS
|
|
|
|
if not watch:
|
|
raise SystemExit(_run_once())
|
|
|
|
try:
|
|
while True:
|
|
click.clear()
|
|
_run_once()
|
|
time.sleep(rate)
|
|
except KeyboardInterrupt:
|
|
raise SystemExit(SUCCESS) from None
|
|
|
|
|
|
@click.command(name="investigate")
|
|
@click.argument(
|
|
"alert_file",
|
|
required=False,
|
|
type=click.Path(),
|
|
)
|
|
@click.option(
|
|
"--input",
|
|
"-i",
|
|
"input_path",
|
|
default=None,
|
|
type=click.Path(),
|
|
help="Path to an alert file (.json, .md, .txt, ...). Use '-' to read from stdin.",
|
|
)
|
|
@click.option("--input-json", default=None, help="Inline alert JSON string.")
|
|
@click.option("--interactive", is_flag=True, help="Paste an alert JSON payload into the terminal.")
|
|
@click.option(
|
|
"--print-template",
|
|
type=click.Choice(ALERT_TEMPLATE_CHOICES),
|
|
default=None,
|
|
help="Print a starter alert JSON template and exit.",
|
|
)
|
|
@click.option(
|
|
"--output", "-o", default=None, type=click.Path(), help="Output JSON file (default: stdout)."
|
|
)
|
|
@click.option(
|
|
"--evaluate",
|
|
is_flag=True,
|
|
help="After final diagnosis, LLM-judge vs scoring_points rubric (rubric stripped from agent alert).",
|
|
)
|
|
def investigate_command(
|
|
alert_file: str | None,
|
|
input_path: str | None,
|
|
input_json: str | None,
|
|
interactive: bool,
|
|
print_template: str | None,
|
|
output: str | None,
|
|
evaluate: bool,
|
|
) -> None:
|
|
"""Run an RCA investigation against an alert payload."""
|
|
# Treat a bare positional path the same as ``-i <path>``. Lets users type
|
|
# ``opensre investigate alert.json`` instead of the more verbose
|
|
# ``opensre investigate -i alert.json``. If both are given, the explicit
|
|
# flag wins to keep the behaviour predictable.
|
|
if alert_file and not input_path:
|
|
input_path = alert_file
|
|
|
|
from surfaces.cli import write_json
|
|
from surfaces.cli.investigation import run_investigation_cli, run_investigation_cli_streaming
|
|
from surfaces.cli.investigation.payload import load_payload
|
|
from tools.investigation.alert_templates import build_alert_template
|
|
|
|
try:
|
|
if print_template:
|
|
write_json(build_alert_template(print_template), output)
|
|
raise SystemExit(SUCCESS)
|
|
|
|
payload = load_payload(
|
|
input_path=input_path,
|
|
input_json=input_json,
|
|
interactive=interactive,
|
|
)
|
|
trigger_mode = (
|
|
TriggerMode.PASTE
|
|
if interactive
|
|
else (TriggerMode.INLINE_JSON if input_json is not None else TriggerMode.FILE)
|
|
)
|
|
with track_investigation(
|
|
entrypoint=EntrypointSource.CLI_COMMAND,
|
|
trigger_mode=trigger_mode,
|
|
input_path=input_path,
|
|
input_json=input_json,
|
|
interactive=interactive,
|
|
evaluate_requested=evaluate,
|
|
) as tracker:
|
|
# Only stream the live UI when the user is interactively watching stdout
|
|
# and hasn't asked for machine-readable JSON. Otherwise the spinner and
|
|
# ANSI control codes corrupt the JSON payload that consumers expect on
|
|
# stdout (pipes, redirection, --json, CI logs).
|
|
# --evaluate forces the non-streaming path because the streaming runner
|
|
# does not yet wire opensre_evaluate scoring through the renderer.
|
|
stream_to_stdout = (
|
|
sys.stdout.isatty() and not is_json_output() and output is None and not evaluate
|
|
)
|
|
if stream_to_stdout:
|
|
run_investigation_cli_streaming(raw_alert=payload, tracker=tracker)
|
|
else:
|
|
result = run_investigation_cli(raw_alert=payload, opensre_evaluate=evaluate)
|
|
write_json(result, output)
|
|
except SystemExit:
|
|
raise
|
|
except KeyboardInterrupt:
|
|
raise SystemExit(SUCCESS) from None
|
|
|
|
raise SystemExit(SUCCESS)
|