# ClawTex — Governed Agent with Genetic Memory
# Built by DeVere Cooley / AN2B LLC · an2b.com
# BB4C — Breath Before Code

"""
Skill installer — thin wrapper around the clawhub CLI binary.

clawhub is the OpenClaw skill registry client.
Install it with: npm install -g clawhub
"""

from __future__ import annotations

import asyncio
import json
import logging
import shutil
from pathlib import Path

logger = logging.getLogger(__name__)

_CLAWHUB_NOT_FOUND = "Install clawhub: npm install -g clawhub"


def _clawhub_bin() -> str | None:
    return shutil.which("clawhub")


async def _run(args: list[str]) -> tuple[int, str, str]:
    """Run a clawhub command. Returns (returncode, stdout, stderr)."""
    bin_path = _clawhub_bin()
    if bin_path is None:
        return 1, "", _CLAWHUB_NOT_FOUND

    proc = await asyncio.create_subprocess_exec(
        bin_path,
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await proc.communicate()
    return proc.returncode or 0, stdout.decode(), stderr.decode()


async def install(slug: str, skills_dir: str) -> dict:
    """Install a skill from clawhub into skills_dir."""
    code, out, err = await _run(["install", slug, "--dir", skills_dir])
    if code != 0:
        logger.warning("clawhub install failed: %s", err or out)
        return {"success": False, "error": err or out, "slug": slug}
    return {"success": True, "output": out, "slug": slug}


async def search(query: str) -> list:
    """Search clawhub for skills matching query."""
    code, out, err = await _run(["search", query, "--json"])
    if code != 0:
        logger.warning("clawhub search failed: %s", err or out)
        return []
    try:
        return json.loads(out) if out.strip() else []
    except json.JSONDecodeError:
        return [{"raw": out}]


async def list_installed(skills_dir: str) -> list:
    """List installed skills in skills_dir by scanning for SKILL.md files."""
    skills_path = Path(skills_dir)
    if not skills_path.exists():
        return []
    result = []
    for d in sorted(skills_path.iterdir()):
        if d.is_dir() and (d / "SKILL.md").exists():
            result.append({"name": d.name, "path": str(d)})
    return result


async def update(slug: str | None, skills_dir: str) -> dict:
    """Update one skill (by slug) or all installed skills (slug=None)."""
    args = ["update"]
    if slug:
        args.append(slug)
    args += ["--dir", skills_dir]
    code, out, err = await _run(args)
    if code != 0:
        logger.warning("clawhub update failed: %s", err or out)
        return {"success": False, "error": err or out}
    return {"success": True, "output": out}
