dp-zp-agent/scripts/build_claude_packages.py
2026-11-02 18:16:33 +01:00

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.0.1'
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 = 'zpwiki-' + 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 / (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()