Files
clawsec/utils/package_skill.py
T
davida-ps 4542b7b96b Enhance/skill release (#8)
* Refactor skill packaging and checksum generation process

- Removed .skill package creation from the skill-release workflow and scripts, focusing on checksum generation only.
- Updated README and SKILL.md files to reflect new installation methods using clawhub.
- Simplified the skill checksums generator script to only generate checksums without packaging.
- Adjusted installation instructions across various skills to promote clawhub for easier installation.
- Enhanced error handling and verification steps in the installation scripts for individual files.

* Add ext-docs to .gitignore to exclude documentation files from version control
2026-02-08 19:18:21 +02:00

150 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""
Skill Checksums Generator - Generates checksums.json for a skill
Usage:
python utils/package_skill.py <path/to/skill-folder> [output-directory]
Example:
python utils/package_skill.py skills/prompt-agent
python utils/package_skill.py skills/prompt-agent ./dist
"""
import hashlib
import json
import sys
from datetime import datetime, timezone
from pathlib import Path
from validate_skill import validate_skill
def calculate_sha256(file_path: Path) -> str:
"""Calculate SHA256 hash of a file."""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def package_skill(skill_path: str, output_dir: str = None) -> tuple[Path | None, Path | None]:
"""
Generate checksums for a skill folder.
Args:
skill_path: Path to the skill folder
output_dir: Optional output directory (defaults to current directory)
Returns:
Tuple of (None, checksums_file_path) or (None, None) on error
"""
skill_path = Path(skill_path).resolve()
# Validate skill first
print("Validating skill...")
valid, message = validate_skill(skill_path)
if not valid:
print(f"[ERROR] Validation failed:\n{message}")
print(" Please fix validation errors before packaging.")
return None, None
print(f"[OK] {message}\n")
# Load skill.json
skill_json_path = skill_path / "skill.json"
with open(skill_json_path) as f:
skill_data = json.load(f)
skill_name = skill_data["name"]
version = skill_data["version"]
# Determine output location
if output_dir:
output_path = Path(output_dir).resolve()
output_path.mkdir(parents=True, exist_ok=True)
else:
output_path = Path.cwd()
checksums_filename = output_path / "checksums.json"
# Collect files from SBOM
files_to_checksum = []
sbom_files = skill_data.get("sbom", {}).get("files", [])
for file_entry in sbom_files:
file_rel_path = file_entry["path"]
full_path = skill_path / file_rel_path
if full_path.exists():
files_to_checksum.append((file_rel_path, full_path))
# Always include skill.json
files_to_checksum.append(("skill.json", skill_json_path))
# Include README.md if it exists
readme_path = skill_path / "README.md"
if readme_path.exists():
files_to_checksum.append(("README.md", readme_path))
# Generate checksums
print("Generating checksums...")
checksums_data = {
"skill": skill_name,
"version": version,
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"repository": "prompt-security/ClawSec",
"tag": f"{skill_name}-v{version}",
"files": {},
}
for rel_path, full_path in files_to_checksum:
filename = Path(rel_path).name
sha256 = calculate_sha256(full_path)
size = full_path.stat().st_size
checksums_data["files"][filename] = {
"sha256": sha256,
"size": size,
"path": rel_path,
"url": f"https://clawsec.prompt.security/releases/download/{skill_name}-v{version}/{filename}",
}
print(f" {filename}: {sha256[:16]}...")
# Write checksums.json
with open(checksums_filename, "w") as f:
json.dump(checksums_data, f, indent=2)
print(f"\n[OK] Checksums written to: {checksums_filename}")
return None, checksums_filename
def main():
if len(sys.argv) < 2:
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" python utils/package_skill.py skills/prompt-agent")
print(" python utils/package_skill.py skills/prompt-agent ./dist")
sys.exit(1)
skill_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
print(f"Generating checksums for: {skill_path}")
if output_dir:
print(f" Output directory: {output_dir}")
print()
_, checksums_file = package_skill(skill_path, output_dir)
if checksums_file:
print("\n" + "=" * 50)
print("Checksums generation complete!")
print(f" Checksums: {checksums_file}")
print("=" * 50)
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()