agentsclimarketplace

Github repo stats

Skill cxcscmu/SkillLearnBench/skills/b4-skill-creator-claude-sonnet-4-6/github-repo-analytics/github-repo-stats

How to gather GitHub repository statistics (PRs, issues, contributors) using the GitHub REST API via curl and Python. Use this skill whenever the user asks for a community pulse, activity summary, monthly report, or repository metrics for any GitHub repo — even if they don't explicitly mention the API.From its SKILL.md

Install
npx -y skills add cxcscmu/SkillLearnBench --skill github-repo-stats

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

4.0 KB, 992 tokens by cl100k_base, as published. Nobody here has run it

GitHub Repository Stats via REST API

Overview

Use the GitHub REST API (no SDK needed) with curl + python3 to pull PR and issue data for a given repo and date range. The unauthenticated rate limit is 60 requests/hour; set GITHUB_TOKEN in the environment for 5000/hour.

Base URL pattern

https://api.github.com/repos/{owner}/{repo}/pulls     # PRs
https://api.github.com/repos/{owner}/{repo}/issues    # Issues (includes PRs!)
https://api.github.com/search/issues                  # Search endpoint

Issues endpoint returns both issues AND pull requests. Filter with "pull_request" in item to separate them.

Authenticated header (use when token available)

AUTH_HEADER="-H \"Authorization: token $GITHUB_TOKEN\""

Pagination pattern

GitHub paginates at 100 items max per page. Always loop until an empty page:

import requests, time

def fetch_all(url, params, token=None):
    headers = {"Authorization": f"token {token}"} if token else {}
    headers["Accept"] = "application/vnd.github+json"
    results = []
    page = 1
    while True:
        params["page"] = page
        params["per_page"] = 100
        r = requests.get(url, headers=headers, params=params)
        if r.status_code == 403:
            time.sleep(60)   # rate-limited, back off
            continue
        data = r.json()
        if not data:
            break
        results.extend(data)
        if len(data) < 100:
            break
        page += 1
    return results

Date filtering

GitHub REST API supports since parameter (ISO 8601) for issues/PRs but NOT until. Filter the until boundary in Python after fetching:

from datetime import datetime, timezone

def parse_dt(s):
    return datetime.fromisoformat(s.replace("Z", "+00:00"))

start = datetime(2024, 12, 1, tzinfo=timezone.utc)
end   = datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc)

created_in_range = [i for i in items
                    if start <= parse_dt(i["created_at"]) <= end]

Key fields

FieldDescription
numberPR/issue number
state"open" or "closed"
created_atISO 8601 creation timestamp
closed_atISO 8601 close timestamp (null if open)
merged_atISO 8601 merge timestamp (PRs only, null if unmerged)
pull_request.merged_atIn issue-endpoint results; same as above
user.loginAuthor login
labelsList of {"name": "..."} objects

Detecting merged PRs

Via /pulls endpoint, merged_at is present and non-null. Via /issues endpoint, check item.get("pull_request", {}).get("merged_at").

Computing average time-to-merge

from datetime import datetime, timezone

def days_between(a, b):
    da = datetime.fromisoformat(a.replace("Z", "+00:00"))
    db = datetime.fromisoformat(b.replace("Z", "+00:00"))
    return (db - da).total_seconds() / 86400

merged = [p for p in prs if p.get("merged_at")]
avg = sum(days_between(p["created_at"], p["merged_at"]) for p in merged) / len(merged)
avg_rounded = round(avg, 1)

Finding top contributor

from collections import Counter
logins = [p["user"]["login"] for p in prs]
top = Counter(logins).most_common(1)[0][0]

Output: write report.json

import json, pathlib
report = {
    "pr": {
        "total": total_prs,
        "merged": merged_count,
        "closed": closed_count,
        "avg_merge_days": avg_merge_days,
        "top_contributor": top_contributor,
    },
    "issue": {
        "total": total_issues,
        "bug": bug_count,
        "resolved_bugs": resolved_bugs,
    }
}
pathlib.Path("/app/report.json").write_text(json.dumps(report, indent=2))

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.