#!/usr/bin/env python3 from __future__ import annotations import argparse import json import re import shutil from pathlib import Path from typing import Any from _project_paths import find_repo_root from plugin_compatibility import build_report as build_plugin_compatibility_report from plugin_compatibility import compatibility_by_skill_id, sync_plugin_compatibility from update_readme import configure_utf8_output, load_metadata SAFE_SKILL_ID_RE = re.compile( r"^(?!.*(?:^|/)\.{1,2}(?:/|$))[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*$" ) REPO_URL = "https://github.com/sickn33/antigravity-awesome-skills" AUTHOR = { "name": "sickn33 and contributors", "url": REPO_URL, } ROOT_CLAUDE_PLUGIN_NAME = "antigravity-awesome-skills" ROOT_CODEX_PLUGIN_NAME = "antigravity-awesome-skills" ROOT_CLAUDE_PLUGIN_DIRNAME = "antigravity-awesome-skills-claude" EDITORIAL_BUNDLES_PATH = Path("data") / "editorial-bundles.json" EDITORIAL_TEMPLATE_PATH = Path("tools") / "templates" / "editorial-bundles.md.tmpl" CLAUDE_MARKETPLACE_PATH = Path(".claude-plugin") / "marketplace.json" CLAUDE_PLUGIN_PATH = Path(".claude-plugin") / "plugin.json" CODEX_MARKETPLACE_PATH = Path(".agents") / "plugins" / "marketplace.json" CODEX_ROOT_PLUGIN_PATH = Path("plugins") / ROOT_CODEX_PLUGIN_NAME / ".codex-plugin" / "plugin.json" CLAUDE_ROOT_PLUGIN_PATH = Path("plugins") / ROOT_CLAUDE_PLUGIN_DIRNAME / ".claude-plugin" / "plugin.json" ACRONYM_TOKENS = { "ab": "A/B", "adb": "ADB", "adr": "ADR", "ads": "ADS", "ai": "AI", "api": "API", "apis": "APIs", "app": "App", "apps": "Apps", "aso": "ASO", "aws": "AWS", "bat": "BAT", "ci": "CI", "cli": "CLI", "cms": "CMS", "crm": "CRM", "cro": "CRO", "css": "CSS", "csv": "CSV", "dag": "DAG", "dbt": "dbt", "ddd": "DDD", "devops": "DevOps", "docx": "DOCX", "dx": "DX", "e2e": "E2E", "expo": "Expo", "fastapi": "FastAPI", "github": "GitHub", "gitlab": "GitLab", "grafana": "Grafana", "html": "HTML", "ios": "iOS", "jwt": "JWT", "k8s": "K8s", "kpi": "KPI", "langfuse": "Langfuse", "langgraph": "LangGraph", "linux": "Linux", "llm": "LLM", "llms": "LLMs", "mcp": "MCP", "nextjs": "Next.js", "nodejs": "Node.js", "oauth2": "OAuth2", "odoo": "Odoo", "openai": "OpenAI", "owasp": "OWASP", "pdf": "PDF", "php": "PHP", "postgres": "Postgres", "pr": "PR", "prd": "PRD", "pwa": "PWA", "python": "Python", "rag": "RAG", "rails": "Rails", "react": "React", "rest": "REST", "rpc": "RPC", "saas": "SaaS", "seo": "SEO", "shopify": "Shopify", "slack": "Slack", "slo": "SLO", "sre": "SRE", "sql": "SQL", "sso": "SSO", "stripe": "Stripe", "svg": "SVG", "swiftui": "SwiftUI", "tailwind": "Tailwind", "tdd": "TDD", "ts": "TS", "tsx": "TSX", "ui": "UI", "ux": "UX", "uv": "uv", "webgl": "WebGL", "xcode": "Xcode", "xml": "XML", "yaml": "YAML", "zod": "Zod", } def _read_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8")) def _write_json(path: Path, payload: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") def _write_text(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8", newline="\n") def _clean_group_label(group: str) -> str: return re.sub(r"^[^A-Za-z0-9]+", "", group).strip() def _bundle_plugin_name(bundle_id: str) -> str: return f"antigravity-bundle-{bundle_id}" def _humanize_skill_label(skill_id: str) -> str: tokens = re.split(r"[-_]+", skill_id.split("/")[-1]) words = [ACRONYM_TOKENS.get(token.lower(), token.capitalize()) for token in tokens if token] return " ".join(words) def _bundle_codex_long_description(bundle: dict[str, Any]) -> str: audience = bundle.get("audience") or bundle["description"] highlights = [ _humanize_skill_label(skill["id"]) for skill in bundle["skills"][:2] if skill.get("id") ] remaining = len(bundle["skills"]) - len(highlights) if not highlights: return f'{audience} Includes {len(bundle["skills"])} curated skills from Antigravity Awesome Skills.' if remaining > 0: return f"{audience} Covers {', '.join(highlights)}, and {remaining} more skills." if len(highlights) == 1: return f"{audience} Covers {highlights[0]}." return f"{audience} Covers {' and '.join(highlights)}." def _format_count_label(count: int) -> str: return f"{count:,}" def _validate_bundle_skill_id(skill_id: str) -> None: if not SAFE_SKILL_ID_RE.fullmatch(skill_id): raise ValueError(f"Invalid skill id in editorial bundles manifest: {skill_id!r}") def _validate_editorial_bundles(root: Path, payload: dict[str, Any]) -> list[dict[str, Any]]: bundles = payload.get("bundles") if not isinstance(bundles, list) or not bundles: raise ValueError("data/editorial-bundles.json must contain a non-empty 'bundles' array.") seen_bundle_ids: set[str] = set() seen_bundle_names: set[str] = set() skills_root = root / "skills" for bundle in bundles: if not isinstance(bundle, dict): raise ValueError("Each editorial bundle must be an object.") bundle_id = str(bundle.get("id", "")).strip() bundle_name = str(bundle.get("name", "")).strip() if not bundle_id or not bundle_name: raise ValueError("Each editorial bundle requires non-empty 'id' and 'name'.") if bundle_id in seen_bundle_ids: raise ValueError(f"Duplicate editorial bundle id: {bundle_id}") if bundle_name in seen_bundle_names: raise ValueError(f"Duplicate editorial bundle name: {bundle_name}") seen_bundle_ids.add(bundle_id) seen_bundle_names.add(bundle_name) plugin_name = _bundle_plugin_name(bundle_id) if len(plugin_name) > 64: raise ValueError(f"Bundle plugin name exceeds 64 characters: {plugin_name}") for key in ("group", "emoji", "tagline", "audience", "description"): if not str(bundle.get(key, "")).strip(): raise ValueError(f"Editorial bundle '{bundle_id}' is missing required field '{key}'.") skills = bundle.get("skills") if not isinstance(skills, list) or not skills: raise ValueError(f"Editorial bundle '{bundle_id}' must include a non-empty 'skills' array.") seen_skill_ids: set[str] = set() for skill in skills: if not isinstance(skill, dict): raise ValueError(f"Editorial bundle '{bundle_id}' contains a non-object skill entry.") skill_id = str(skill.get("id", "")).strip() summary = str(skill.get("summary", "")).strip() _validate_bundle_skill_id(skill_id) if skill_id in seen_skill_ids: raise ValueError(f"Editorial bundle '{bundle_id}' contains duplicate skill '{skill_id}'.") if not summary: raise ValueError(f"Editorial bundle '{bundle_id}' skill '{skill_id}' is missing summary.") skill_path = (skills_root / skill_id).resolve(strict=False) if not skill_path.exists(): raise ValueError(f"Editorial bundle '{bundle_id}' references missing skill '{skill_id}'.") seen_skill_ids.add(skill_id) return bundles def _bundle_target_status(bundle: dict[str, Any], compatibility: dict[str, dict[str, Any]]) -> dict[str, Any]: bundle_skills = [compatibility[skill["id"]] for skill in bundle["skills"] if skill["id"] in compatibility] return { "codex": bool(bundle_skills) and all(skill["targets"]["codex"] == "supported" for skill in bundle_skills), "claude": bool(bundle_skills) and all(skill["targets"]["claude"] == "supported" for skill in bundle_skills), "manual_setup": any(skill["setup"]["type"] == "manual" for skill in bundle_skills), } def _render_bundle_plugin_status(bundle_status: dict[str, Any]) -> str: codex_status = "Codex plugin-safe" if bundle_status["codex"] else "Codex pending hardening" claude_status = "Claude plugin-safe" if bundle_status["claude"] else "Claude pending hardening" parts = [codex_status, claude_status] if bundle_status["manual_setup"]: parts.append("Requires manual setup") return " · ".join(parts) def _render_bundle_sections( bundles: list[dict[str, Any]], compatibility: dict[str, dict[str, Any]], ) -> str: lines: list[str] = [] current_group: str | None = None for bundle in bundles: group = bundle["group"] if group != current_group: if lines: lines.extend(["", "---", ""]) lines.append(f"## {group}") lines.append("") current_group = group bundle_status = _bundle_target_status(bundle, compatibility) lines.append(f'### {bundle["emoji"]} {bundle["tagline"]}') lines.append("") lines.append(f'_{bundle["audience"]}_') lines.append("") lines.append(f'**Plugin status:** {_render_bundle_plugin_status(bundle_status)}') lines.append("") for skill in bundle["skills"]: skill_status = compatibility.get(skill["id"], {}) plugin_info = skill_status.get("setup", {}) if isinstance(skill_status, dict) else {} suffix = " _(manual setup)_" if plugin_info.get("type") == "manual" else "" lines.append( f'- [`{skill["id"]}`](../../skills/{skill["id"]}/): {skill["summary"]}{suffix}' ) lines.append("") return "\n".join(lines).strip() + "\n" def render_bundles_doc( root: Path, metadata: dict[str, Any], bundles: list[dict[str, Any]], compatibility: dict[str, dict[str, Any]], ) -> str: template = (root / EDITORIAL_TEMPLATE_PATH).read_text(encoding="utf-8") return ( template.replace("{{bundle_sections}}", _render_bundle_sections(bundles, compatibility).rstrip()) .replace("{{total_skills_label}}", metadata["total_skills_label"]) .replace("{{bundle_count}}", str(len(bundles))) ) def _copy_file_contents(src: Path, dest: Path, allowed_root: Path) -> None: resolved_src = src.resolve(strict=True) resolved_src.relative_to(allowed_root.resolve()) if resolved_src.is_dir(): dest.mkdir(parents=True, exist_ok=True) for child in resolved_src.iterdir(): _copy_file_contents(child, dest / child.name, allowed_root) return dest.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(resolved_src, dest) def _copy_skill_directory(root: Path, skill_id: str, destination_root: Path) -> None: skills_root = root / "skills" source = (skills_root / skill_id).resolve(strict=True) source.relative_to(skills_root.resolve()) if not source.is_dir(): raise ValueError(f"Editorial bundle skill '{skill_id}' is not a directory.") skill_dest = destination_root / skill_id if skill_dest.exists(): shutil.rmtree(skill_dest) for child in source.iterdir(): _copy_file_contents(child, skill_dest / child.name, skills_root) if not (skill_dest / "SKILL.md").is_file(): raise ValueError(f"Copied bundle skill '{skill_id}' is missing SKILL.md in {skill_dest}") def _root_claude_plugin_manifest(metadata: dict[str, Any], supported_skill_count: int) -> dict[str, Any]: supported_label = _format_count_label(supported_skill_count) return { "name": ROOT_CLAUDE_PLUGIN_NAME, "version": metadata["version"], "description": ( f"Plugin-safe Claude Code distribution of Antigravity Awesome Skills with " f"{supported_label} supported skills." ), "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "claude-code", "skills", "agentic-skills", "plugin-safe", "productivity", ], } def _root_codex_plugin_manifest(metadata: dict[str, Any], supported_skill_count: int) -> dict[str, Any]: supported_label = _format_count_label(supported_skill_count) return { "name": ROOT_CODEX_PLUGIN_NAME, "version": metadata["version"], "description": "Plugin-safe Codex plugin for the Antigravity Awesome Skills library.", "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "codex", "skills", "agentic-skills", "developer-tools", "plugin-safe", ], "skills": "./skills/", "interface": { "displayName": "Antigravity Awesome Skills", "shortDescription": ( f"{supported_label} plugin-safe skills for coding, security, product, and ops workflows." ), "longDescription": ( "Install a plugin-safe Codex distribution of Antigravity Awesome Skills. " "Skills that still need hardening or target-specific setup remain available in the repo " "but are excluded from this plugin." ), "developerName": AUTHOR["name"], "category": "Productivity", "capabilities": ["Interactive", "Write"], "websiteURL": REPO_URL, "defaultPrompt": [ "Use @brainstorming to plan a new feature.", "Use @test-driven-development to fix a bug safely.", "Use @lint-and-validate to verify this branch.", ], "brandColor": "#111827", }, } def _bundle_claude_plugin_manifest(metadata: dict[str, Any], bundle: dict[str, Any]) -> dict[str, Any]: return { "name": _bundle_plugin_name(bundle["id"]), "version": metadata["version"], "description": ( f'Editorial "{bundle["name"]}" bundle for Claude Code from Antigravity Awesome Skills.' ), "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "claude-code", "skills", "bundle", bundle["id"], "antigravity-awesome-skills", ], } def _bundle_codex_plugin_manifest(metadata: dict[str, Any], bundle: dict[str, Any]) -> dict[str, Any]: category = _clean_group_label(bundle["group"]) plugin_name = _bundle_plugin_name(bundle["id"]) skill_count = len(bundle["skills"]) return { "name": plugin_name, "version": metadata["version"], "description": ( f'Install the "{bundle["name"]}" editorial skill bundle from Antigravity Awesome Skills.' ), "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "codex", "skills", "bundle", bundle["id"], "productivity", ], "skills": "./skills/", "interface": { "displayName": bundle["name"], "shortDescription": f"{category} · {skill_count} curated skills", "longDescription": _bundle_codex_long_description(bundle), "developerName": AUTHOR["name"], "category": category, "capabilities": ["Interactive", "Write"], "websiteURL": REPO_URL, "brandColor": "#111827", }, } def _bundle_claude_marketplace_entry(metadata: dict[str, Any], bundle: dict[str, Any]) -> dict[str, Any]: plugin_name = _bundle_plugin_name(bundle["id"]) return { "name": plugin_name, "version": metadata["version"], "description": ( f'Install the "{bundle["name"]}" editorial skill bundle for Claude Code.' ), "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "claude-code", "skills", "bundle", bundle["id"], "marketplace", ], "source": f"./plugins/{plugin_name}", } def _render_claude_marketplace( metadata: dict[str, Any], bundles: list[dict[str, Any]], bundle_support: dict[str, dict[str, Any]], ) -> dict[str, Any]: plugins = [ { "name": ROOT_CLAUDE_PLUGIN_NAME, "version": metadata["version"], "description": ( "Expose the plugin-safe Claude Code subset of Antigravity Awesome Skills " "through a single marketplace entry." ), "author": AUTHOR, "homepage": REPO_URL, "repository": REPO_URL, "license": "MIT", "keywords": [ "claude-code", "skills", "agentic-skills", "plugin", "marketplace", ], "source": f"./plugins/{ROOT_CLAUDE_PLUGIN_DIRNAME}", } ] plugins.extend( _bundle_claude_marketplace_entry(metadata, bundle) for bundle in bundles if bundle_support[bundle["id"]]["claude"] ) return { "name": ROOT_CLAUDE_PLUGIN_NAME, "owner": AUTHOR, "metadata": { "description": ( "Claude Code marketplace entries for the plugin-safe Antigravity Awesome Skills " "library and its compatible editorial bundles." ), "version": metadata["version"], }, "plugins": plugins, } def _render_codex_marketplace( bundles: list[dict[str, Any]], bundle_support: dict[str, dict[str, Any]], ) -> dict[str, Any]: plugins: list[dict[str, Any]] = [ { "name": ROOT_CODEX_PLUGIN_NAME, "source": { "source": "local", "path": f"./plugins/{ROOT_CODEX_PLUGIN_NAME}", }, "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL", }, "category": "Productivity", } ] for bundle in bundles: if not bundle_support[bundle["id"]]["codex"]: continue plugins.append( { "name": _bundle_plugin_name(bundle["id"]), "source": { "source": "local", "path": f'./plugins/{_bundle_plugin_name(bundle["id"])}', }, "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL", }, "category": _clean_group_label(bundle["group"]), } ) return { "name": ROOT_CODEX_PLUGIN_NAME, "interface": { "displayName": "Antigravity Awesome Skills", }, "plugins": plugins, } def _materialize_plugin_skills(root: Path, destination_root: Path, skill_ids: list[str]) -> None: if destination_root.is_symlink() or destination_root.is_file(): destination_root.unlink() elif destination_root.exists(): shutil.rmtree(destination_root) destination_root.mkdir(parents=True, exist_ok=True) for skill_id in skill_ids: _copy_skill_directory(root, skill_id, destination_root) def _supported_skill_ids( compatibility: dict[str, dict[str, Any]], target: str, ) -> list[str]: return sorted( skill_id for skill_id, skill in compatibility.items() if skill["targets"][target] == "supported" ) def _sync_root_plugins( root: Path, metadata: dict[str, Any], compatibility: dict[str, dict[str, Any]], ) -> None: codex_skill_ids = _supported_skill_ids(compatibility, "codex") claude_skill_ids = _supported_skill_ids(compatibility, "claude") codex_root = root / "plugins" / ROOT_CODEX_PLUGIN_NAME claude_root = root / "plugins" / ROOT_CLAUDE_PLUGIN_DIRNAME _materialize_plugin_skills(root, codex_root / "skills", codex_skill_ids) _materialize_plugin_skills(root, claude_root / "skills", claude_skill_ids) _write_json( codex_root / ".codex-plugin" / "plugin.json", _root_codex_plugin_manifest(metadata, len(codex_skill_ids)), ) claude_manifest = _root_claude_plugin_manifest(metadata, len(claude_skill_ids)) _write_json( claude_root / ".claude-plugin" / "plugin.json", claude_manifest, ) _write_json(root / CLAUDE_PLUGIN_PATH, claude_manifest) def _sync_bundle_plugin_directory( root: Path, metadata: dict[str, Any], bundle: dict[str, Any], support: dict[str, Any], ) -> None: if not support["codex"] and not support["claude"]: return plugin_name = _bundle_plugin_name(bundle["id"]) plugin_root = root / "plugins" / plugin_name if plugin_root.exists(): shutil.rmtree(plugin_root) bundle_skills_root = plugin_root / "skills" bundle_skills_root.mkdir(parents=True, exist_ok=True) for skill in bundle["skills"]: _copy_skill_directory(root, skill["id"], bundle_skills_root) if support["claude"]: _write_json( plugin_root / ".claude-plugin" / "plugin.json", _bundle_claude_plugin_manifest(metadata, bundle), ) if support["codex"]: _write_json( plugin_root / ".codex-plugin" / "plugin.json", _bundle_codex_plugin_manifest(metadata, bundle), ) def sync_editorial_bundle_plugins( root: Path, metadata: dict[str, Any], bundles: list[dict[str, Any]], bundle_support: dict[str, dict[str, Any]], ) -> None: plugins_root = root / "plugins" for candidate in plugins_root.glob("antigravity-bundle-*"): if candidate.is_dir(): shutil.rmtree(candidate) for bundle in bundles: _sync_bundle_plugin_directory(root, metadata, bundle, bundle_support[bundle["id"]]) def load_editorial_bundles(root: Path) -> list[dict[str, Any]]: root = Path(root) payload = _read_json(root / EDITORIAL_BUNDLES_PATH) return _validate_editorial_bundles(root, payload) def sync_editorial_bundles(root: Path) -> None: metadata = load_metadata(str(root)) compatibility_report = sync_plugin_compatibility(root) compatibility = compatibility_by_skill_id(compatibility_report) bundles = load_editorial_bundles(root) bundle_support = { bundle["id"]: _bundle_target_status(bundle, compatibility) for bundle in bundles } _write_text( root / "docs" / "users" / "bundles.md", render_bundles_doc(root, metadata, bundles, compatibility), ) _sync_root_plugins(root, metadata, compatibility) _write_json( root / CLAUDE_MARKETPLACE_PATH, _render_claude_marketplace(metadata, bundles, bundle_support), ) _write_json( root / CODEX_MARKETPLACE_PATH, _render_codex_marketplace(bundles, bundle_support), ) _write_json( root / CODEX_ROOT_PLUGIN_PATH, _root_codex_plugin_manifest( metadata, len(_supported_skill_ids(compatibility, "codex")), ), ) sync_editorial_bundle_plugins(root, metadata, bundles, bundle_support) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Sync editorial bundle docs and plugin marketplaces.") parser.add_argument( "--check", action="store_true", help="Validate the editorial bundles manifest and exit without writing files.", ) return parser.parse_args() def main() -> int: args = parse_args() root = find_repo_root(__file__) if args.check: metadata = load_metadata(str(root)) compatibility_report = build_plugin_compatibility_report(root / "skills") compatibility = compatibility_by_skill_id(compatibility_report) bundles = load_editorial_bundles(root) expected_doc = render_bundles_doc(root, metadata, bundles, compatibility) current_doc = (root / "docs" / "users" / "bundles.md").read_text(encoding="utf-8") if current_doc != expected_doc: raise SystemExit("docs/users/bundles.md is out of sync with data/editorial-bundles.json") print("✅ Editorial bundles manifest and generated doc are in sync.") return 0 sync_editorial_bundles(root) print("✅ Editorial bundles synced.") return 0 if __name__ == "__main__": configure_utf8_output() raise SystemExit(main())