"""Package the confirmed Skill from its explicit public file manifest.""" from pathlib import Path, PurePosixPath import hashlib import json import zipfile ROOT = Path(__file__).resolve().parents[1] SKILL = ROOT / 'skills/ksf-workshop-runtime' OUT = ROOT / 'docs/workshop-final-files-20261111' ARCHIVE = OUT / 'final-康师傅工作坊.skill' sha = lambda value: hashlib.sha256(value).hexdigest() def main(): entry = (SKILL / 'SKILL.md').read_bytes() if entry != (SKILL / 'final-SKILL.md').read_bytes(): raise ValueError('Run node scripts/build-workshop-skill.mjs before packaging') manifest = (SKILL / 'FILES.sha256').read_bytes() files = {} for line in manifest.decode('utf8').splitlines(): digest, name = line.split(' ', 1) part = PurePosixPath(name) if part.is_absolute() or '..' in part.parts or '\\' in name or ':' in name or name in files: raise ValueError('Invalid manifest path') if any(term in name.lower() for term in ('credential-binding', 'credential-envelope', 'private.pem', 'channel.json', '.env')): raise ValueError('Private file is not a public Skill resource') data = (SKILL / name).read_bytes() if sha(data) != digest: raise ValueError('Manifest mismatch: ' + name) files[name] = data files['FILES.sha256'] = manifest OUT.mkdir(parents=True, exist_ok=True) temporary = ARCHIVE.with_suffix('.skill.tmp') with zipfile.ZipFile(temporary, 'w', zipfile.ZIP_DEFLATED) as archive: for name, data in sorted(files.items()): info = zipfile.ZipInfo('ksf-workshop-runtime/' + name, (2026, 11, 11, 0, 0, 0)) info.external_attr = 0o100600 << 16 archive.writestr(info, data, compress_type=zipfile.ZIP_DEFLATED) with zipfile.ZipFile(temporary) as archive: if archive.testzip() is not None or len(set(archive.namelist())) != len(files): raise ValueError('Archive verification failed') for name, data in files.items(): if archive.read('ksf-workshop-runtime/' + name) != data: raise ValueError('Archive content mismatch: ' + name) temporary.replace(ARCHIVE) receipt = { 'ok': True, 'file': ARCHIVE.name, 'bytes': ARCHIVE.stat().st_size, 'sha256': sha(ARCHIVE.read_bytes()), 'publicFilesVerified': len(files), 'entrySha256': sha(entry), 'canonicalEntry': 'skills/ksf-workshop-runtime/final-SKILL.md', 'archiveEntry': 'ksf-workshop-runtime/SKILL.md', 'format': 'zip-compatible .skill', 'credentialsIncluded': False, 'deployed': False, 'installed': False, 'externalRuntimeDependenciesBundled': False, } (OUT / 'skill-package-receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf8') print(json.dumps(receipt, ensure_ascii=False)) if __name__ == '__main__': main()