Consolidate the repository into clearer apps, tools, and layered docs areas so contributors can navigate and maintain it more reliably. Align validation, metadata sync, and CI around the same canonical workflow to reduce drift across local checks and GitHub Actions.
262 lines
8.6 KiB
Python
262 lines
8.6 KiB
Python
#!/usr/bin/env python3
|
|
import argparse
|
|
import io
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
from datetime import datetime, timezone
|
|
|
|
GITHUB_REPO = "sickn33/antigravity-awesome-skills"
|
|
SYNC_COMMENT_RE = re.compile(r"<!-- registry-sync: .*? -->")
|
|
|
|
|
|
def configure_utf8_output() -> None:
|
|
"""Best-effort UTF-8 stdout/stderr on Windows without dropping diagnostics."""
|
|
if sys.platform != "win32":
|
|
return
|
|
|
|
for stream_name in ("stdout", "stderr"):
|
|
stream = getattr(sys, stream_name)
|
|
try:
|
|
stream.reconfigure(encoding="utf-8", errors="backslashreplace")
|
|
continue
|
|
except Exception:
|
|
pass
|
|
|
|
buffer = getattr(stream, "buffer", None)
|
|
if buffer is not None:
|
|
setattr(
|
|
sys,
|
|
stream_name,
|
|
io.TextIOWrapper(buffer, encoding="utf-8", errors="backslashreplace"),
|
|
)
|
|
|
|
|
|
def find_repo_root(start_path: str) -> str:
|
|
current = os.path.abspath(start_path)
|
|
while True:
|
|
if os.path.isfile(os.path.join(current, "package.json")) and os.path.isfile(
|
|
os.path.join(current, "README.md")
|
|
):
|
|
return current
|
|
parent = os.path.dirname(current)
|
|
if parent == current:
|
|
raise FileNotFoundError("Could not locate repository root from script path.")
|
|
current = parent
|
|
|
|
|
|
def format_skill_count(total_skills: int) -> str:
|
|
return f"{total_skills:,}+"
|
|
|
|
|
|
def format_star_badge_count(stars: int) -> str:
|
|
if stars >= 1000:
|
|
rounded = int(round(stars / 1000.0))
|
|
return f"{rounded}%2C000%2B"
|
|
return f"{stars}%2B"
|
|
|
|
|
|
def format_star_milestone(stars: int) -> str:
|
|
if stars >= 1000:
|
|
rounded = int(round(stars / 1000.0))
|
|
return f"{rounded},000+"
|
|
return f"{stars}+"
|
|
|
|
|
|
def format_star_celebration(stars: int) -> str:
|
|
if stars >= 1000:
|
|
rounded = int(round(stars / 1000.0))
|
|
return f"{rounded}k"
|
|
return str(stars)
|
|
|
|
|
|
def fetch_star_count(repo: str) -> int | None:
|
|
url = f"https://api.github.com/repos/{repo}"
|
|
request = urllib.request.Request(
|
|
url,
|
|
headers={
|
|
"Accept": "application/vnd.github+json",
|
|
"User-Agent": "antigravity-awesome-skills-readme-sync",
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=10) as response:
|
|
payload = json.load(response)
|
|
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
|
|
return None
|
|
stars = payload.get("stargazers_count")
|
|
return int(stars) if isinstance(stars, int) else None
|
|
|
|
|
|
def load_metadata(base_dir: str, repo: str = GITHUB_REPO) -> dict:
|
|
readme_path = os.path.join(base_dir, "README.md")
|
|
package_path = os.path.join(base_dir, "package.json")
|
|
index_path = os.path.join(base_dir, "skills_index.json")
|
|
|
|
with open(index_path, "r", encoding="utf-8") as file:
|
|
skills = json.load(file)
|
|
|
|
with open(package_path, "r", encoding="utf-8") as file:
|
|
package = json.load(file)
|
|
|
|
with open(readme_path, "r", encoding="utf-8") as file:
|
|
current_readme = file.read()
|
|
|
|
current_star_match = re.search(r"⭐%20([\d%2C\+]+)%20Stars", current_readme)
|
|
current_stars = None
|
|
if current_star_match:
|
|
compact = current_star_match.group(1).replace("%2C", "").replace("%2B", "")
|
|
compact = compact.rstrip("+")
|
|
if compact.isdigit():
|
|
current_stars = int(compact)
|
|
|
|
live_stars = fetch_star_count(repo)
|
|
total_stars = live_stars if live_stars is not None else current_stars or 0
|
|
|
|
return {
|
|
"repo": repo,
|
|
"version": str(package.get("version", "0.0.0")),
|
|
"total_skills": len(skills),
|
|
"total_skills_label": format_skill_count(len(skills)),
|
|
"stars": total_stars,
|
|
"star_badge_count": format_star_badge_count(total_stars),
|
|
"star_milestone": format_star_milestone(total_stars),
|
|
"star_celebration": format_star_celebration(total_stars),
|
|
"updated_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat(),
|
|
"used_live_star_count": live_stars is not None,
|
|
}
|
|
|
|
|
|
def apply_metadata(content: str, metadata: dict) -> str:
|
|
total_skills = metadata["total_skills"]
|
|
total_skills_label = metadata["total_skills_label"]
|
|
version = metadata["version"]
|
|
star_badge_count = metadata["star_badge_count"]
|
|
star_milestone = metadata["star_milestone"]
|
|
star_celebration = metadata["star_celebration"]
|
|
sync_comment = (
|
|
f"<!-- registry-sync: version={version}; skills={total_skills}; "
|
|
f"stars={metadata['stars']}; updated_at={metadata['updated_at']} -->"
|
|
)
|
|
|
|
content = re.sub(
|
|
r"^# 🌌 Antigravity Awesome Skills: .*?$",
|
|
(
|
|
f"# 🌌 Antigravity Awesome Skills: {total_skills_label} "
|
|
"Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More"
|
|
),
|
|
content,
|
|
count=1,
|
|
flags=re.MULTILINE,
|
|
)
|
|
content = re.sub(
|
|
r"^> \*\*The Ultimate Collection of .*?\*\*$",
|
|
(
|
|
f"> **The Ultimate Collection of {total_skills_label} Universal Agentic "
|
|
"Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, "
|
|
"Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**"
|
|
),
|
|
content,
|
|
count=1,
|
|
flags=re.MULTILINE,
|
|
)
|
|
content = re.sub(
|
|
r"https://img\.shields\.io/badge/⭐%20[\d%2C\+]+%20Stars-gold\?style=for-the-badge",
|
|
f"https://img.shields.io/badge/⭐%20{star_badge_count}%20Stars-gold?style=for-the-badge",
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"^\*\*Antigravity Awesome Skills\*\* is a curated, battle-tested library of \*\*.*?\*\* designed",
|
|
(
|
|
f"**Antigravity Awesome Skills** is a curated, battle-tested library of "
|
|
f"**{total_skills_label} high-performance agentic skills** designed"
|
|
),
|
|
content,
|
|
count=1,
|
|
flags=re.MULTILINE,
|
|
)
|
|
content = re.sub(
|
|
r"\[📚 Browse \d[\d,]*\+ Skills\]\(#browse-[^)]+\)",
|
|
f"[📚 Browse {total_skills_label} Skills](#browse-{total_skills}-skills)",
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"\*\*Welcome to the V[\d.]+ .*? Stars Celebration Release!\*\*",
|
|
f"**Welcome to the V{version} {star_celebration} Stars Celebration Release!**",
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"> \*\*🌟 .*? GitHub Stars Milestone!\*\*",
|
|
f"> **🌟 {star_milestone} GitHub Stars Milestone!**",
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"\*\*Antigravity Awesome Skills\*\* \(Release [\d.]+\) is a massive upgrade to your AI's capabilities, now featuring \*\*.*?\*\* skills",
|
|
(
|
|
f"**Antigravity Awesome Skills** (Release {version}) is a massive upgrade "
|
|
f"to your AI's capabilities, now featuring **{total_skills_label} skills**"
|
|
),
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"## Browse \d[\d,]*\+ Skills",
|
|
f"## Browse {total_skills_label} Skills",
|
|
content,
|
|
count=1,
|
|
)
|
|
content = re.sub(
|
|
r"<!-- registry-sync: .*? -->\n?",
|
|
"",
|
|
content,
|
|
count=1,
|
|
)
|
|
return f"{sync_comment}\n{content.lstrip()}"
|
|
|
|
|
|
def update_readme(dry_run: bool = False) -> dict:
|
|
base_dir = find_repo_root(os.path.dirname(__file__))
|
|
readme_path = os.path.join(base_dir, "README.md")
|
|
metadata = load_metadata(base_dir)
|
|
|
|
print(f"📖 Reading README from: {readme_path}")
|
|
print(f"🔢 Total skills found: {metadata['total_skills']}")
|
|
print(f"🏷️ Version found: {metadata['version']}")
|
|
if metadata["used_live_star_count"]:
|
|
print(f"⭐ Live GitHub stars found: {metadata['stars']}")
|
|
else:
|
|
print(f"⭐ Using existing README star count: {metadata['stars']}")
|
|
|
|
with open(readme_path, "r", encoding="utf-8") as file:
|
|
content = file.read()
|
|
|
|
updated_content = apply_metadata(content, metadata)
|
|
if dry_run:
|
|
print("🧪 Dry run enabled; README.md not written.")
|
|
return metadata
|
|
|
|
with open(readme_path, "w", encoding="utf-8", newline="\n") as file:
|
|
file.write(updated_content)
|
|
|
|
print("✅ README.md updated successfully.")
|
|
return metadata
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="Sync generated metadata into README.md.")
|
|
parser.add_argument("--dry-run", action="store_true", help="Compute metadata without writing files.")
|
|
return parser.parse_args()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
configure_utf8_output()
|
|
args = parse_args()
|
|
update_readme(dry_run=args.dry_run)
|