diff --git a/.claude-plugin/icon.png b/.claude-plugin/icon.png new file mode 100644 index 0000000..fb9c35c Binary files /dev/null and b/.claude-plugin/icon.png differ diff --git a/scripts/check_package.py b/scripts/check_package.py index 988ebb4..f2a668b 100644 --- a/scripts/check_package.py +++ b/scripts/check_package.py @@ -31,6 +31,7 @@ } PACKAGE_FILES = { ".claude-plugin/plugin.json", + ".claude-plugin/icon.png", ".github/workflows/validate.yml", ".mcp.json", "LICENSE", @@ -69,6 +70,10 @@ def check_tree() -> None: if relative not in PACKAGE_FILES: raise ValueError(f"unexpected package file: {relative}") found_files.add(relative) + if relative == ".claude-plugin/icon.png": + if not path.read_bytes().startswith(b"\x89PNG\r\n\x1a\n"): + raise ValueError("listing icon must be a PNG") + continue if CREDENTIAL_ASSIGNMENT.search(path.read_text(encoding="utf-8")): raise ValueError(f"possible credential assignment in package file: {relative}") else: @@ -104,8 +109,8 @@ def check_package() -> None: manifest_dir = ROOT / ".claude-plugin" if manifest_dir.is_symlink() or not manifest_dir.is_dir(): raise ValueError(".claude-plugin must be a directory") - if {path.name for path in manifest_dir.iterdir()} != {"plugin.json"}: - raise ValueError(".claude-plugin must contain only plugin.json") + if {path.name for path in manifest_dir.iterdir()} != {"plugin.json", "icon.png"}: + raise ValueError(".claude-plugin must contain only plugin.json and icon.png") skills_dir = ROOT / "skills" if skills_dir.is_symlink() or not skills_dir.is_dir(): diff --git a/scripts/test_check_package.py b/scripts/test_check_package.py index ceee56a..d992338 100644 --- a/scripts/test_check_package.py +++ b/scripts/test_check_package.py @@ -31,6 +31,14 @@ def check_staged_package(self) -> None: with patch.object(check_package, "ROOT", self.root): check_package.check_package() + def test_binary_listing_icon_is_accepted(self) -> None: + self.check_staged_package() + + def test_non_png_listing_icon_is_rejected(self) -> None: + (self.root / ".claude-plugin/icon.png").write_text("not a PNG") + with self.assertRaisesRegex(ValueError, "listing icon must be a PNG"): + self.check_staged_package() + def set_version(self, version: str) -> None: manifest_path = self.root / ".claude-plugin/plugin.json" manifest = json.loads(manifest_path.read_text(encoding="utf-8"))