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

1"""Skill scanner integration for OpenClawenv.""" 

2 

3from __future__ import annotations 

4 

5import shutil 

6import subprocess 

7import uuid 

8from pathlib import Path 

9 

10from openenv.core.errors import CommandError 

11from openenv.core.models import Manifest 

12from openenv.core.utils import rewrite_openclaw_home_paths 

13 

14 

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 

41 

42 

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:] 

56 

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 

74 

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