Files
Zakaria a46764fb1b
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
first-commit
2026-05-04 14:58:14 -04:00

109 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Package a validated atlas as a local Codex pet."""
from __future__ import annotations
import argparse
import json
import os
import re
import shutil
from pathlib import Path
from PIL import Image
ATLAS_SIZE = (1536, 1872)
def default_codex_home() -> Path:
return Path(os.environ.get("CODEX_HOME") or "~/.codex").expanduser().resolve()
def slugify(value: str) -> str:
value = value.strip().lower()
value = re.sub(r"[^a-z0-9]+", "-", value)
value = re.sub(r"-{2,}", "-", value)
return value.strip("-")
def validate_spritesheet(path: Path) -> str:
with Image.open(path) as image:
if image.size != ATLAS_SIZE:
raise SystemExit(
f"expected {ATLAS_SIZE[0]}x{ATLAS_SIZE[1]}, got {image.width}x{image.height}"
)
if image.format not in {"PNG", "WEBP"}:
raise SystemExit(f"expected PNG or WebP, got {image.format}")
return str(image.format)
def write_webp_spritesheet(source: Path, target: Path, source_format: str) -> None:
if source_format == "WEBP":
shutil.copy2(source, target)
return
with Image.open(source) as image:
target.parent.mkdir(parents=True, exist_ok=True)
image.convert("RGBA").save(
target,
format="WEBP",
lossless=True,
quality=100,
method=6,
)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--pet-name", default="")
parser.add_argument("--display-name", default="")
parser.add_argument("--description", required=True)
parser.add_argument("--spritesheet", required=True)
parser.add_argument("--codex-home", default=str(default_codex_home()))
parser.add_argument(
"--output-dir",
help="Exact pet package directory. Defaults to ${CODEX_HOME:-$HOME/.codex}/pets/<pet-name>.",
)
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
raw_pet_name = (args.pet_name or args.display_name).strip()
if not raw_pet_name:
raise SystemExit("pet name is required")
pet_id = slugify(raw_pet_name)
if not pet_id:
raise SystemExit("pet name must contain at least one letter or digit")
display_name = (args.display_name or raw_pet_name).strip()
source = Path(args.spritesheet).expanduser().resolve()
source_format = validate_spritesheet(source)
target_dir = (
Path(args.output_dir).expanduser().resolve()
if args.output_dir
else Path(args.codex_home).expanduser().resolve() / "pets" / pet_id
)
target_dir.mkdir(parents=True, exist_ok=True)
target_sheet = target_dir / "spritesheet.webp"
manifest_path = target_dir / "pet.json"
if not args.force and (target_sheet.exists() or manifest_path.exists()):
raise SystemExit(f"{target_dir} already contains pet files; pass --force to overwrite")
write_webp_spritesheet(source, target_sheet, source_format)
manifest = {
"id": pet_id,
"displayName": display_name,
"description": args.description,
"spritesheetPath": target_sheet.name,
}
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
print(
json.dumps(
{"ok": True, "pet_dir": str(target_dir), "manifest": str(manifest_path)}, indent=2
)
)
if __name__ == "__main__":
main()