from __future__ import annotations import subprocess # nosec B404 from typing import Optional import typer from rich.table import Table from decnet.env import DECNET_INGEST_LOG_FILE from . import utils as _utils from .gating import _agent_mode_active, _require_master_mode from .utils import console, log def register(app: typer.Typer) -> None: @app.command() def redeploy( log_file: str = typer.Option(DECNET_INGEST_LOG_FILE, "--log-file", "-f", help="Path to the DECNET log file"), ) -> None: """Check running DECNET services and relaunch any that are down.""" log.info("redeploy: checking services") registry = _utils._service_registry(str(log_file)) table = Table(title="DECNET Services", show_lines=True) table.add_column("Service", style="bold cyan") table.add_column("Status") table.add_column("PID", style="dim") table.add_column("Action") relaunched = 0 for name, match_fn, launch_args in registry: pid = _utils._is_running(match_fn) if pid is not None: table.add_row(name, "[green]UP[/]", str(pid), "—") else: try: subprocess.Popen( # nosec B603 launch_args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, start_new_session=True, ) table.add_row(name, "[red]DOWN[/]", "—", "[green]relaunched[/]") relaunched += 1 except (FileNotFoundError, subprocess.SubprocessError) as exc: table.add_row(name, "[red]DOWN[/]", "—", f"[red]failed: {exc}[/]") console.print(table) if relaunched: console.print(f"[green]{relaunched} service(s) relaunched.[/]") else: console.print("[green]All services running.[/]") @app.command() def status() -> None: """Show running deckies and their status.""" log.info("status command invoked") from decnet.engine import status as _status _status() registry = _utils._service_registry(str(DECNET_INGEST_LOG_FILE)) if _agent_mode_active(): registry = [r for r in registry if r[0] not in {"Mutator", "Profiler", "API"}] svc_table = Table(title="DECNET Services", show_lines=True) svc_table.add_column("Service", style="bold cyan") svc_table.add_column("Status") svc_table.add_column("PID", style="dim") for name, match_fn, _launch_args in registry: pid = _utils._is_running(match_fn) if pid is not None: svc_table.add_row(name, "[green]UP[/]", str(pid)) else: svc_table.add_row(name, "[red]DOWN[/]", "—") console.print(svc_table) @app.command() def teardown( all_: bool = typer.Option(False, "--all", help="Tear down all deckies and remove network"), id_: Optional[str] = typer.Option(None, "--id", help="Tear down a specific decky by name"), ) -> None: """Stop and remove deckies.""" _require_master_mode("teardown") if not all_ and not id_: console.print("[red]Specify --all or --id .[/]") raise typer.Exit(1) log.info("teardown command invoked all=%s id=%s", all_, id_) from decnet.engine import teardown as _teardown _teardown(decky_id=id_) log.info("teardown complete all=%s id=%s", all_, id_) if all_: _utils._kill_all_services()