Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions packaging/openai/build_skills_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Build the skills-only package the OpenAI plugin portal uploads.

The portal's Skills step takes a ZIP whose root holds a portable Agent Plugins
manifest and a skills/ directory. Everything inside comes from the canonical
source, so this package can never describe workflows we do not ship. The build
is byte-reproducible: entries are sorted and timestamps fixed, which lets
scripts/validate.py fail when the committed ZIP drifts from the skills.

Usage: python3 packaging/openai/build_skills_package.py [--check]
"""
import io, json, sys, zipfile
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
CANONICAL = ROOT / 'plugins/dreamlayer/skills'
PACKAGE = Path(__file__).resolve().parent / 'dreamlayer-skills-1.0.0.zip'
# Fixed so two builds of the same skills produce the same bytes.
TIMESTAMP = (2026, 1, 1, 0, 0, 0)
IGNORED = {'.DS_Store', '__pycache__'}
MANIFEST = {
'$schema': 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json',
'name': 'dreamlayer',
'version': '1.0.0',
'description': (
'Generate and edit images, remove backgrounds, upscale, and build sprite-sheet '
'animations, with the credit cost quoted before each job.'
),
'author': {'name': 'Mackenzie Derival', 'email': 'Mackenzie@dreamlayer.io', 'url': 'https://dreamlayer.io'},
'homepage': 'https://dreamlayer.io/agent',
'repository': 'https://github.com/TheDesignFounder/dreamlayer-agent-plugin',
'license': 'MIT',
'keywords': ['image-generation', 'image-editing', 'sprite-sheets', 'logo-concepts', 'product-images'],
}


def contents():
"""The exact file map the package carries, sorted for reproducibility."""
files = {'plugin.json': (json.dumps(MANIFEST, indent=2) + '\n').encode()}
for path in sorted(CANONICAL.rglob('*')):
relative = path.relative_to(CANONICAL)
if path.is_file() and not IGNORED & set(relative.parts):
files[f'skills/{relative.as_posix()}'] = path.read_bytes()
return files


def build():
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as archive:
for name, data in sorted(contents().items()):
info = zipfile.ZipInfo(name, date_time=TIMESTAMP)
info.compress_type = zipfile.ZIP_DEFLATED
info.external_attr = 0o644 << 16
archive.writestr(info, data)
return buffer.getvalue()


def main(argv):
built = build()
if '--check' in argv:
if not PACKAGE.exists():
print(f'MISSING: {PACKAGE.relative_to(ROOT)}; run python3 packaging/openai/build_skills_package.py')
return 1
if PACKAGE.read_bytes() != built:
print(f'DRIFT: {PACKAGE.relative_to(ROOT)} does not match the canonical skills; rebuild it')
return 1
print(f'{PACKAGE.name} matches {len(contents()) - 1} canonical files')
return 0
PACKAGE.write_bytes(built)
print(f'wrote {PACKAGE.relative_to(ROOT)} ({len(built)} bytes, {len(contents())} entries)')
return 0


if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))
Binary file added packaging/openai/dreamlayer-skills-1.0.0.zip
Binary file not shown.
4 changes: 4 additions & 0 deletions scripts/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
assert 'dreamlayer_cancel' not in text
for folder in skills:
assert folder.name in text, f'ClawHub entry point does not route to {folder.name}'
spec=importlib.util.spec_from_file_location('skills_package',ROOT/'packaging/openai/build_skills_package.py')
skills_package=importlib.util.module_from_spec(spec);spec.loader.exec_module(skills_package)
assert skills_package.PACKAGE.exists(),'the OpenAI skills package is missing; run packaging/openai/build_skills_package.py'
assert skills_package.PACKAGE.read_bytes()==skills_package.build(),'the OpenAI skills package has drifted from the canonical skills'
for doc in ('docs/copilot-setup.md','docs/cline-setup.md'):
body=(ROOT/doc).read_text()
for folder in skills:
Expand Down
Loading