#!/usr/bin/env python3 """Build reproducible, self-contained Claude skill ZIPs without runtime data or secrets.""" from __future__ import annotations import argparse import hashlib import json from pathlib import Path import zipfile ROOT = Path(__file__).resolve().parents[1] BASE = ROOT / 'packages/claude' ENGINES = ('rag', 'graphrag') PACKAGE_VERSION = '1.6.0' def json_bytes(value): return (json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + '\n').encode() def build_package(engine: str, output: Path) -> dict: if engine not in ENGINES: raise ValueError('Unknown engine') name = engine source = BASE / name files = {'SKILL.md': (source / 'SKILL.md').read_bytes(), 'scripts/query.py': (source / 'scripts/query.py').read_bytes(), 'scripts/client.py': (BASE / 'client.py').read_bytes()} if not files['SKILL.md'].startswith(('---\nname: ' + name + '\n').encode()): raise ValueError('Skill metadata does not match its package') hashes = {path: hashlib.sha256(data).hexdigest() for path, data in sorted(files.items())} files['manifest.json'] = json_bytes({'schema_version': 1, 'name': name, 'version': PACKAGE_VERSION, 'engine': engine, 'entrypoint': 'SKILL.md', 'minimum_python': '3.10', 'backend_required': True, 'default_mode': 'verified_answer', 'files': hashes}) output.mkdir(parents=True, exist_ok=True) destination = output / ('zpwiki-' + name + '.zip') with zipfile.ZipFile(destination, 'w', compression=zipfile.ZIP_DEFLATED) as archive: for path, data in sorted(files.items()): info = zipfile.ZipInfo(name + '/' + path, date_time=(1980, 1, 1, 0, 0, 0)) info.compress_type = zipfile.ZIP_DEFLATED info.create_system = 3 info.external_attr = 0o100644 << 16 archive.writestr(info, data) return {'name': name, 'archive': destination.name, 'sha256': hashlib.sha256(destination.read_bytes()).hexdigest()} def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--output', type=Path, default=BASE / 'dist') args = parser.parse_args() packages = [build_package(engine, args.output) for engine in ENGINES] (args.output / 'packages.json').write_bytes(json_bytes({'schema_version': 1, 'packages': packages})) print(json.dumps(packages, ensure_ascii=False)) if __name__ == '__main__': main()