59 lines
2.4 KiB
Python
59 lines
2.4 KiB
Python
#!/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.2.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()
|