Coverage for src / openenv / integrations / scanner.py: 96.30%
44 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 13:36 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-25 13:36 +0000
1"""Skill scanner integration for OpenClawenv."""
3from __future__ import annotations
5import shutil
6import subprocess
7import uuid
8from pathlib import Path
10from openenv.core.errors import CommandError
11from openenv.core.models import Manifest
12from openenv.core.utils import rewrite_openclaw_home_paths
15def materialize_skills(manifest: Manifest, target_dir: str | Path) -> Path:
16 """Write inline skills to a directory tree consumable by skill-scanner."""
17 skills_root = Path(target_dir)
18 skills_root.mkdir(parents=True, exist_ok=True)
19 for skill in manifest.skills:
20 skill_dir = skills_root / skill.name
21 skill_dir.mkdir(parents=True, exist_ok=True)
22 (skill_dir / "SKILL.md").write_text(
23 skill.rendered_content(
24 state_dir=manifest.openclaw.state_dir,
25 workspace=manifest.openclaw.workspace,
26 ),
27 encoding="utf-8",
28 )
29 for relative_path, content in sorted(skill.assets.items()):
30 asset_path = skill_dir / relative_path
31 asset_path.parent.mkdir(parents=True, exist_ok=True)
32 asset_path.write_text(
33 rewrite_openclaw_home_paths(
34 content,
35 state_dir=manifest.openclaw.state_dir,
36 workspace=manifest.openclaw.workspace,
37 ),
38 encoding="utf-8",
39 )
40 return skills_root
43def run_skill_scanner(
44 manifest_path: str | Path,
45 manifest: Manifest,
46 *,
47 scanner_bin: str = "skill-scanner",
48 scanner_args: list[str] | None = None,
49 keep_artifacts: bool = False,
50) -> Path | None:
51 """Materialize skills and invoke the external skill-scanner CLI."""
52 manifest_root = Path(manifest_path).resolve().parent
53 extra_args = list(scanner_args or [])
54 if extra_args and extra_args[0] == "--":
55 extra_args = extra_args[1:]
57 scan_root = manifest_root / f"openclawenv-scan-tmp-{uuid.uuid4().hex}"
58 scan_root.mkdir(parents=True, exist_ok=False)
59 try:
60 skills_root = materialize_skills(manifest, scan_root / "skills")
61 command = [scanner_bin, "scan-all", str(skills_root), "--recursive", *extra_args]
62 try:
63 subprocess.run(command, check=True, cwd=manifest_root)
64 except OSError as exc:
65 raise CommandError(
66 "skill-scanner is not available on PATH. "
67 "Install it with `pip install .[scan]` or provide --scanner-bin."
68 ) from exc
69 except subprocess.CalledProcessError as exc:
70 raise CommandError(
71 f"skill-scanner failed with exit code {exc.returncode}.",
72 exit_code=exc.returncode,
73 ) from exc
75 if keep_artifacts:
76 destination = manifest_root / ".openclawenv-scan"
77 if destination.exists(): 77 ↛ 78line 77 didn't jump to line 78 because the condition on line 77 was never true
78 shutil.rmtree(destination, ignore_errors=True)
79 shutil.copytree(scan_root, destination)
80 return destination
81 finally:
82 shutil.rmtree(scan_root, ignore_errors=True)
83 return None