#!/usr/bin/env python3
"""Run SQLsaber verification commands in an isolated PTY."""

from __future__ import annotations

import argparse
import datetime as dt
import fcntl
import json
import math
import os
import pty
import re
import select
import shutil
import signal
import sqlite3
import struct
import subprocess
import sys
import termios
import time
import tomllib
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator

SKILL_DIR = Path(__file__).resolve().parents[1]
RUN_ID_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,79}\Z")
ANSI_RE = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\)|[@-_])")
PTY_ROWS = 40
PTY_COLS = 120


def checkout_root(skill_dir: Path) -> Path:
    for candidate in (skill_dir, *skill_dir.parents):
        if (candidate / "pyproject.toml").is_file():
            return candidate
    raise SystemExit(f"could not find pyproject.toml above {skill_dir}")


ROOT = checkout_root(SKILL_DIR)
BASE = SKILL_DIR / "artifacts"
STATE_BASE = BASE / ".state"


def validate_run_id(value: str) -> str:
    if not RUN_ID_RE.fullmatch(value):
        raise SystemExit(
            "RUN_ID must start with an alphanumeric character and contain only "
            "letters, numbers, dots, underscores, or hyphens"
        )
    return value


def state_dir(run_id: str) -> Path:
    return STATE_BASE / validate_run_id(run_id)


def evidence_dir(run_id: str) -> Path:
    return BASE / validate_run_id(run_id)


def metadata_path(run_id: str) -> Path:
    return state_dir(run_id) / "run.json"


def load_metadata(run_id: str) -> dict[str, object]:
    path = metadata_path(run_id)
    if not path.exists():
        raise SystemExit(f"Run {run_id!r} is not launched. Run launch first.")
    return json.loads(path.read_text(encoding="utf-8"))


def project_version() -> str:
    with (ROOT / "pyproject.toml").open("rb") as file:
        return str(tomllib.load(file)["project"]["version"])


def git_revision() -> str:
    result = subprocess.run(
        ["git", "rev-parse", "HEAD"],
        cwd=ROOT,
        check=True,
        capture_output=True,
        text=True,
    )
    return result.stdout.strip()


def run_env(run_id: str) -> dict[str, str]:
    state = state_dir(run_id)
    home = state / "home"
    env = {
        name: value
        for name, value in os.environ.items()
        if not name.startswith("SQLSABER_")
    }
    env.update(
        {
            "HOME": str(home),
            "XDG_CONFIG_HOME": str(home / "config"),
            "XDG_DATA_HOME": str(home / "data"),
            "XDG_CACHE_HOME": str(home / "cache"),
            "XDG_STATE_HOME": str(home / "state"),
            "SQLSABER_LOG_FILE": str(state / "sqlsaber.log"),
            "SQLSABER_SKIP_VERSION_CHECK": "1",
            "SQLSABER_VERIFY_RUN_ID": run_id,
            "PYTHON_KEYRING_BACKEND": "keyring.backends.null.Keyring",
            "NO_COLOR": "1",
            "TERM": "xterm-256color",
            "UV_NO_PROGRESS": "1",
        }
    )
    env.pop("FORCE_COLOR", None)
    return env


def command_output(command: list[str], run_id: str) -> str:
    result = subprocess.run(
        command,
        cwd=ROOT,
        env=run_env(run_id),
        check=True,
        capture_output=True,
        text=True,
    )
    return result.stdout.strip()


def seed_fixture(path: Path) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with sqlite3.connect(path) as db:
        db.executescript(
            """
            CREATE TABLE departments (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL UNIQUE
            );
            CREATE TABLE employees (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                department_id INTEGER NOT NULL REFERENCES departments(id),
                active INTEGER NOT NULL
            );
            CREATE TABLE orders (
                id INTEGER PRIMARY KEY,
                employee_id INTEGER NOT NULL REFERENCES employees(id),
                amount_cents INTEGER NOT NULL,
                status TEXT NOT NULL
            );
            INSERT INTO departments VALUES (1, 'Engineering'), (2, 'Finance');
            INSERT INTO employees VALUES
                (1, 'Ada', 1, 1),
                (2, 'Grace', 1, 1),
                (3, 'Linus', 2, 0);
            INSERT INTO orders VALUES
                (1, 1, 12500, 'paid'),
                (2, 2, 7500, 'paid'),
                (3, 3, 3000, 'pending');
            """
        )


def launch(run_id: str) -> None:
    run_id = validate_run_id(run_id)
    state = state_dir(run_id)
    evidence = evidence_dir(run_id)
    if state.exists() or evidence.exists():
        raise SystemExit(
            f"Run {run_id!r} already exists. Use a new RUN_ID so proof cannot mix."
        )

    try:
        state.mkdir(parents=True)
        evidence.mkdir(parents=True)
        for name in ("config", "data", "cache", "state"):
            (state / "home" / name).mkdir(parents=True)

        fixture = state / "fixtures" / "verification.db"
        seed_fixture(fixture)
        subprocess.run(["uv", "sync", "--locked"], cwd=ROOT, check=True)
        expected = project_version()
        actual = command_output(["uv", "run", "saber", "--version"], run_id)
        if actual != expected:
            raise RuntimeError(
                f"CLI version {actual!r} does not match pyproject version {expected!r}"
            )
        metadata = {
            "run_id": run_id,
            "repo": str(ROOT),
            "revision": git_revision(),
            "version": expected,
            "fixture": str(fixture),
            "state": str(state),
            "evidence": str(evidence),
            "launched_at": dt.datetime.now(dt.UTC).isoformat(),
        }
        metadata_path(run_id).write_text(
            json.dumps(metadata, indent=2) + "\n", encoding="utf-8"
        )
        launch_record = (
            f"RUN_ID={run_id}\n"
            f"version={expected}\n"
            f"revision={metadata['revision']}\n"
            f"fixture={fixture}\n"
            f"state={state}\n"
            f"evidence={evidence}\n"
        )
        (evidence / "launch.txt").write_text(launch_record, encoding="utf-8")
        print(f"READY SQLsaber {expected}")
        print(f"fixture: {fixture}")
        print(f"evidence: {evidence}")
    except BaseException:
        shutil.rmtree(state, ignore_errors=True)
        shutil.rmtree(evidence, ignore_errors=True)
        raise


def doctor(run_id: str) -> None:
    metadata = load_metadata(run_id)
    state = state_dir(run_id).resolve()
    expected = project_version()
    actual = command_output(["uv", "run", "saber", "--version"], run_id)
    current_revision = git_revision()

    errors: list[str] = []
    if actual != expected or metadata.get("version") != expected:
        errors.append(
            f"version mismatch: CLI={actual}, pyproject={expected}, "
            f"launch={metadata.get('version')}"
        )
    if metadata.get("revision") != current_revision:
        errors.append("checkout revision changed after launch")

    fixture = Path(str(metadata["fixture"]))
    try:
        with sqlite3.connect(f"file:{fixture}?mode=ro", uri=True) as db:
            employee_count = int(
                db.execute("SELECT COUNT(*) FROM employees").fetchone()[0]
            )
    except (OSError, sqlite3.Error) as exc:
        errors.append(f"fixture is unreadable: {exc}")
        employee_count = -1
    if employee_count != 3:
        errors.append(f"fixture employee count is {employee_count}, expected 3")

    path_script = (
        "import json, platformdirs; "
        "print(json.dumps({'config': platformdirs.user_config_dir('sqlsaber', 'sqlsaber'), "
        "'data': platformdirs.user_data_dir('sqlsaber'), "
        "'log': platformdirs.user_log_dir('sqlsaber', 'sqlsaber')}))"
    )
    paths = json.loads(
        command_output(["uv", "run", "python", "-c", path_script], run_id)
    )
    for kind in ("config", "data", "log"):
        path = Path(paths[kind]).resolve()
        if not path.is_relative_to(state):
            errors.append(f"{kind} path escapes run state: {path}")

    active_path = state / "active.json"
    with run_lock(state):
        if active_lock_path(active_path).exists():
            try:
                active = json.loads(active_path.read_text(encoding="utf-8"))
            except FileNotFoundError:
                release_active(active_path)
            else:
                pid = int(active["pid"])
                try:
                    os.kill(pid, 0)
                except ProcessLookupError:
                    release_active(active_path)
                else:
                    errors.append(f"run is already being driven by PID {pid}")

    credential_names = [
        name
        for name in (
            "ANTHROPIC_API_KEY",
            "OPENAI_API_KEY",
            "GOOGLE_API_KEY",
            "GROQ_API_KEY",
            "MISTRAL_API_KEY",
            "COHERE_API_KEY",
            "HUGGINGFACE_API_KEY",
            "XAI_API_KEY",
        )
        if os.getenv(name)
    ]
    lines = [
        f"run: {run_id}",
        f"version: {actual}",
        f"revision: {current_revision}",
        f"fixture employees: {employee_count}",
        f"config root: {paths['config']}",
        f"data root: {paths['data']}",
        f"log root: {paths['log']}",
        "server: not applicable; each SQLsaber command is a short-lived process",
        "keyring backend: keyring.backends.null.Keyring",
        "model credentials present: "
        + (", ".join(credential_names) if credential_names else "none"),
    ]
    if errors:
        lines.extend(f"ERROR: {error}" for error in errors)
        status = "NOT READY"
    else:
        status = "HEALTHY"
    report = status + "\n" + "\n".join(lines) + "\n"
    print(report, end="")
    (evidence_dir(run_id) / "doctor.txt").write_text(report, encoding="utf-8")
    if errors:
        raise SystemExit(1)


def clean_terminal(text: str) -> str:
    text = ANSI_RE.sub("", text)
    text = text.replace("\r\n", "\n").replace("\r", "\n")
    return text


def terminate_process_group(pid: int) -> None:
    try:
        os.killpg(pid, signal.SIGTERM)
    except ProcessLookupError:
        return
    deadline = time.monotonic() + 3
    while time.monotonic() < deadline:
        try:
            waited, _ = os.waitpid(pid, os.WNOHANG)
        except ChildProcessError:
            return
        if waited == pid:
            return
        time.sleep(0.05)
    try:
        os.killpg(pid, signal.SIGKILL)
    except ProcessLookupError:
        pass


def command_text(command: list[str]) -> str:
    return " ".join(subprocess.list2cmdline([part]) for part in command)


@contextmanager
def run_lock(state: Path) -> Iterator[None]:
    lock_path = state / "run.lock"
    with lock_path.open("a", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        try:
            yield
        finally:
            fcntl.flock(lock.fileno(), fcntl.LOCK_UN)


def atomic_write(path: Path, text: str) -> None:
    temporary = path.with_name(f".{path.name}.{os.getpid()}.tmp")
    try:
        with temporary.open("x", encoding="utf-8") as file:
            file.write(text)
        os.replace(temporary, path)
    finally:
        temporary.unlink(missing_ok=True)


def write_new(path: Path, text: str) -> None:
    try:
        with path.open("x", encoding="utf-8") as file:
            file.write(text)
    except FileExistsError:
        raise SystemExit(
            f"Evidence path already exists: {path}. Use a new evidence name."
        ) from None


def active_lock_path(active_path: Path) -> Path:
    return active_path.with_name("active.lock")


def reserve_active(active_path: Path, command: list[str]) -> None:
    lock_path = active_lock_path(active_path)
    try:
        fd = os.open(lock_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
    except FileExistsError:
        raise SystemExit(
            "This RUN_ID already has an active command. Run doctor or cleanup."
        ) from None
    os.close(fd)
    try:
        write_active(active_path, os.getpid(), command)
    except BaseException:
        lock_path.unlink(missing_ok=True)
        raise


def write_active(active_path: Path, pid: int, command: list[str]) -> None:
    payload = {"pid": pid, "command": command}
    atomic_write(active_path, json.dumps(payload) + "\n")


def release_active(active_path: Path) -> None:
    active_path.unlink(missing_ok=True)
    active_lock_path(active_path).unlink(missing_ok=True)


def positive_float(value: str) -> float:
    parsed = float(value)
    if not math.isfinite(parsed) or parsed <= 0:
        raise argparse.ArgumentTypeError("must be a finite number greater than zero")
    return parsed


def nonnegative_float(value: str) -> float:
    parsed = float(value)
    if not math.isfinite(parsed) or parsed < 0:
        raise argparse.ArgumentTypeError("must be a finite non-negative number")
    return parsed


def drive(
    run_id: str,
    evidence_name: str,
    command: list[str],
    timeout: float,
    input_events: list[tuple[float, str]],
) -> None:
    load_metadata(run_id)
    if command and command[0] == "--":
        command = command[1:]
    if not command:
        raise SystemExit("drive requires a command after --")

    relative = Path(evidence_name)
    if relative.is_absolute() or ".." in relative.parts:
        raise SystemExit("--evidence must be a relative path without '..'")
    output_path = evidence_dir(run_id) / relative
    output_path.parent.mkdir(parents=True, exist_ok=True)
    if output_path.exists():
        raise SystemExit(
            f"Evidence path already exists: {output_path}. Use a new evidence name."
        )

    state = state_dir(run_id)
    active_path = state / "active.json"
    with run_lock(state):
        reserve_active(active_path, command)
        try:
            pid, master_fd = pty.fork()
        except BaseException:
            release_active(active_path)
            raise
        if pid == 0:
            try:
                os.chdir(ROOT)
                os.execvpe(command[0], command, run_env(run_id))
            except BaseException as exc:
                os.write(
                    sys.stderr.fileno(), f"failed to start command: {exc}\n".encode()
                )
                os._exit(127)

        try:
            winsize = struct.pack("HHHH", PTY_ROWS, PTY_COLS, 0, 0)
            fcntl.ioctl(master_fd, termios.TIOCSWINSZ, winsize)
            write_active(active_path, pid, command)
        except BaseException:
            terminate_process_group(pid)
            release_active(active_path)
            os.close(master_fd)
            raise

    started = dt.datetime.now(dt.UTC).isoformat()
    chunks: list[bytes] = []
    started_at = time.monotonic()
    deadline = started_at + timeout
    pending_events = list(input_events)
    status: int | None = None
    timed_out = False
    interrupted: BaseException | None = None
    try:
        while True:
            now = time.monotonic()
            while pending_events and now - started_at >= pending_events[0][0]:
                _, text = pending_events.pop(0)
                os.write(master_fd, text.encode())
            if now >= deadline:
                timed_out = True
                terminate_process_group(pid)
                break

            readable, _, _ = select.select([master_fd], [], [], 0.1)
            if readable:
                try:
                    data = os.read(master_fd, 65536)
                except OSError:
                    data = b""
                if data:
                    chunks.append(data)
                    os.write(sys.stdout.fileno(), data)

            waited, candidate_status = os.waitpid(pid, os.WNOHANG)
            if waited == pid:
                status = candidate_status
                while True:
                    readable, _, _ = select.select([master_fd], [], [], 0)
                    if not readable:
                        break
                    try:
                        data = os.read(master_fd, 65536)
                    except OSError:
                        break
                    if not data:
                        break
                    chunks.append(data)
                    os.write(sys.stdout.fileno(), data)
                break
    except BaseException as exc:
        interrupted = exc
        terminate_process_group(pid)
    finally:
        release_active(active_path)
        os.close(master_fd)

    if timed_out:
        exit_code = 124
    elif interrupted is not None:
        exit_code = 130 if isinstance(interrupted, KeyboardInterrupt) else 1
    else:
        assert status is not None
        exit_code = os.waitstatus_to_exitcode(status)
    body = b"".join(chunks).decode("utf-8", errors="replace")
    transcript = (
        f"run_id: {run_id}\n"
        f"started_at: {started}\n"
        f"command: {command_text(command)}\n"
        "--- terminal output ---\n"
        f"{clean_terminal(body)}"
        "--- result ---\n"
        f"exit_code: {exit_code}\n"
        f"timed_out: {str(timed_out).lower()}\n"
        f"interrupted: {str(interrupted is not None).lower()}\n"
    )
    write_new(output_path, transcript)
    if interrupted is not None:
        raise interrupted
    print(f"\nproof: {output_path}")
    if timed_out:
        raise TimeoutError(f"command exceeded {timeout:g} seconds")
    if exit_code != 0:
        raise SystemExit(exit_code)


def stop_popen(process: subprocess.Popen[bytes]) -> tuple[bytes, bytes]:
    try:
        os.killpg(process.pid, signal.SIGTERM)
    except ProcessLookupError:
        pass
    try:
        return process.communicate(timeout=3)
    except subprocess.TimeoutExpired:
        try:
            os.killpg(process.pid, signal.SIGKILL)
        except ProcessLookupError:
            pass
        return process.communicate()


def with_trailing_newline(text: str) -> str:
    return text if not text or text.endswith("\n") else text + "\n"


def run_non_tty(
    run_id: str,
    evidence_name: str,
    command: list[str],
    timeout: float,
) -> None:
    load_metadata(run_id)
    if command and command[0] == "--":
        command = command[1:]
    if not command:
        raise SystemExit("run requires a command after --")

    relative = Path(evidence_name)
    if relative.is_absolute() or ".." in relative.parts:
        raise SystemExit("--evidence must be a relative path without '..'")
    output_path = evidence_dir(run_id) / relative
    output_path.parent.mkdir(parents=True, exist_ok=True)
    if output_path.exists():
        raise SystemExit(
            f"Evidence path already exists: {output_path}. Use a new evidence name."
        )

    state = state_dir(run_id)
    active_path = state / "active.json"
    with run_lock(state):
        reserve_active(active_path, command)
        try:
            process = subprocess.Popen(
                command,
                cwd=ROOT,
                env=run_env(run_id),
                stdin=subprocess.DEVNULL,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                start_new_session=True,
            )
            write_active(active_path, process.pid, command)
        except BaseException:
            release_active(active_path)
            raise
    started = dt.datetime.now(dt.UTC).isoformat()
    timed_out = False
    interrupted: BaseException | None = None
    try:
        stdout, stderr = process.communicate(timeout=timeout)
    except subprocess.TimeoutExpired:
        timed_out = True
        stdout, stderr = stop_popen(process)
    except BaseException as exc:
        interrupted = exc
        stdout, stderr = stop_popen(process)
    finally:
        release_active(active_path)

    stdout_text = stdout.decode("utf-8", errors="replace")
    stderr_text = stderr.decode("utf-8", errors="replace")
    transcript = (
        f"run_id: {run_id}\n"
        f"started_at: {started}\n"
        f"command: {command_text(command)}\n"
        "--- stdout ---\n"
        f"{with_trailing_newline(stdout_text)}"
        "--- stderr ---\n"
        f"{with_trailing_newline(stderr_text)}"
        "--- result ---\n"
        f"exit_code: {process.returncode}\n"
        f"timed_out: {str(timed_out).lower()}\n"
        f"interrupted: {str(interrupted is not None).lower()}\n"
    )
    write_new(output_path, transcript)
    if stdout:
        os.write(sys.stdout.fileno(), stdout)
    if stderr:
        os.write(sys.stderr.fileno(), stderr)
    print(f"\nproof: {output_path}")
    if timed_out:
        raise TimeoutError(f"command exceeded {timeout:g} seconds")
    if interrupted is not None:
        raise interrupted
    if process.returncode != 0:
        raise SystemExit(process.returncode)


def parse_input_events(args: argparse.Namespace) -> list[tuple[float, str]]:
    if args.input is not None and args.input_sequence is not None:
        raise SystemExit("use only one of --input or --input-sequence")
    if args.input_sequence is None:
        return [] if args.input is None else [(args.input_delay, args.input)]
    try:
        raw_events = json.loads(args.input_sequence)
        events = [(float(delay), str(text)) for delay, text in raw_events]
    except (TypeError, ValueError, json.JSONDecodeError) as exc:
        raise SystemExit(
            "--input-sequence must be JSON pairs such as "
            '\'[[2, "/clear\\r"], [4, "/exit\\r"]]\''
        ) from exc
    if any(not math.isfinite(delay) or delay < 0 for delay, _ in events):
        raise SystemExit("--input-sequence delays must be finite and non-negative")
    return sorted(events, key=lambda event: event[0])


def path_value(run_id: str, name: str) -> None:
    metadata = load_metadata(run_id)
    values = {
        "state": state_dir(run_id),
        "evidence": evidence_dir(run_id),
        "fixture": Path(str(metadata["fixture"])),
        "database-config": state_dir(run_id)
        / "home"
        / "config"
        / "sqlsaber"
        / "database_config.json",
        "auth-config": state_dir(run_id)
        / "home"
        / "config"
        / "sqlsaber"
        / "auth_config.json",
        "model-config": state_dir(run_id)
        / "home"
        / "config"
        / "sqlsaber"
        / "model_config.json",
        "theme-config": state_dir(run_id)
        / "home"
        / "config"
        / "sqlsaber"
        / "theme.json",
        "knowledge-db": state_dir(run_id)
        / "home"
        / "data"
        / "sqlsaber"
        / "knowledge.db",
        "threads-db": state_dir(run_id) / "home" / "config" / "sqlsaber" / "threads.db",
        "log": state_dir(run_id) / "sqlsaber.log",
    }
    print(values[name])


def cleanup(run_id: str) -> None:
    validate_run_id(run_id)
    state = state_dir(run_id)
    evidence = evidence_dir(run_id)
    if not state.exists() and not evidence.exists():
        raise SystemExit(f"Run {run_id!r} does not exist; refusing cleanup.")

    if state.exists():
        with run_lock(state):
            active_path = state / "active.json"
            if active_lock_path(active_path).exists():
                try:
                    active = json.loads(active_path.read_text(encoding="utf-8"))
                except FileNotFoundError:
                    release_active(active_path)
                else:
                    pid = int(active["pid"])
                    try:
                        process = subprocess.run(
                            ["ps", "eww", "-p", str(pid), "-o", "command="],
                            check=False,
                            capture_output=True,
                            text=True,
                        ).stdout
                        os.kill(pid, 0)
                    except ProcessLookupError:
                        release_active(active_path)
                    else:
                        marker = f"SQLSABER_VERIFY_RUN_ID={run_id}"
                        if marker not in process:
                            raise SystemExit(
                                f"Refusing to kill PID {pid}: it does not carry this run marker"
                            )
                        os.killpg(pid, signal.SIGTERM)
                        time.sleep(0.5)
                        try:
                            os.killpg(pid, signal.SIGKILL)
                        except ProcessLookupError:
                            pass
                        release_active(active_path)
            shutil.rmtree(state, ignore_errors=True)

    evidence.mkdir(parents=True, exist_ok=True)
    record = (
        f"cleaned_at={dt.datetime.now(dt.UTC).isoformat()}\n"
        f"removed_state={state}\n"
        f"preserved_evidence={evidence}\n"
    )
    (evidence / "cleanup.txt").write_text(record, encoding="utf-8")
    print(f"CLEAN state removed: {state}")
    print(f"PRESERVED evidence: {evidence}")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description=__doc__)
    subparsers = parser.add_subparsers(dest="action", required=True)

    for action in ("launch", "doctor", "cleanup"):
        child = subparsers.add_parser(action)
        child.add_argument("run_id")

    path_parser = subparsers.add_parser("path")
    path_parser.add_argument("run_id")
    path_parser.add_argument(
        "name",
        choices=(
            "state",
            "evidence",
            "fixture",
            "database-config",
            "auth-config",
            "model-config",
            "theme-config",
            "knowledge-db",
            "threads-db",
            "log",
        ),
    )

    drive_parser = subparsers.add_parser("drive")
    drive_parser.add_argument("run_id")
    drive_parser.add_argument("--evidence", required=True)
    drive_parser.add_argument("--timeout", type=positive_float, default=60)
    drive_parser.add_argument("--input")
    drive_parser.add_argument("--input-delay", type=nonnegative_float, default=1.5)
    drive_parser.add_argument("--input-sequence")
    drive_parser.add_argument("command", nargs="+")

    run_parser = subparsers.add_parser("run")
    run_parser.add_argument("run_id")
    run_parser.add_argument("--evidence", required=True)
    run_parser.add_argument("--timeout", type=positive_float, default=60)
    run_parser.add_argument("command", nargs="+")
    return parser


def main() -> None:
    args = build_parser().parse_args()
    if args.action == "launch":
        launch(args.run_id)
    elif args.action == "doctor":
        doctor(args.run_id)
    elif args.action == "drive":
        drive(
            args.run_id,
            args.evidence,
            args.command,
            args.timeout,
            parse_input_events(args),
        )
    elif args.action == "run":
        run_non_tty(args.run_id, args.evidence, args.command, args.timeout)
    elif args.action == "path":
        path_value(args.run_id, args.name)
    elif args.action == "cleanup":
        cleanup(args.run_id)


if __name__ == "__main__":
    main()
