| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960 |
- """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()
|