
Gh Work Report
- 48 installs
- 70 repo stars
- Updated July 26, 2026
- rysweet/amplihack
Helps with ai & agent building tasks.
About
gh-work-report is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- gh-work-report
- AI & Agent Building
- AI-coding skill
Gh Work Report by the numbers
- 48 all-time installs (skills.sh)
- +1 installs in the week ending Jul 26, 2026 (Skillselion tracking)
- Ranked #7,374 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rysweet/amplihack --skill gh-work-reportAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 48 |
|---|---|
| repo stars | ★ 70 |
| Last updated | July 26, 2026 |
| Repository | rysweet/amplihack ↗ |
What it does
Helps with ai & agent building tasks.
Files
GitHub Work Report Skill
Generates rich GitHub activity reports across all authenticated gh accounts.
Invocation
The user triggers this skill with phrases like:
/gh-work-report(default: last 7 days)/gh-work-report show me the last 30 daysgenerate a github work report for the last 90 dayswhat did I work on this week
Parse the time period
Extract the number of days from the user's message. Valid values: 1, 5, 7, 30, 90. Default to 7 if not specified or if an invalid value is given.
Workflow
Follow these steps in order. Check off each step as you complete it.
Step 1: Detect accounts
gh auth status 2>&1Parse the output to identify all authenticated accounts (e.g., rysweet on github.com, rysweet_microsoft on github.com). Store the list of account values and note which is currently active.
Step 2: Gather data per account
For each account, switch to it and collect data:
gh auth switch --user <ACCOUNT>Then gather:
2a. Repositories with recent activity
# Get repos the user pushed to in the time window
gh api graphql --paginate -f query='
query($cursor: String) {
viewer {
repositories(first: 100, after: $cursor, orderBy: {field: PUSHED_AT, direction: DESC}) {
pageInfo { hasNextPage endCursor }
nodes {
nameWithOwner
url
description
pushedAt
homepageUrl
isPrivate
}
}
}
}'Filter to repos with pushedAt within the time window.
2b. Pull requests
gh search prs --author=@me --created=">YYYY-MM-DD" --limit 200 --json number,title,repository,state,createdAt,url,mergedAtAlso gather PRs merged (not just created) in the window:
gh search prs --author=@me --merged=">YYYY-MM-DD" --limit 200 --json number,title,repository,state,createdAt,url,mergedAtDeduplicate by URL.
2c. Issues
gh search issues --author=@me --created=">YYYY-MM-DD" --limit 100 --json number,title,repository,state,createdAt,url2d. Releases
# For each active repo, check for releases
gh api repos/{owner}/{repo}/releases --jq '.[].tag_name' | head -5Step 3: Combine and deduplicate
Merge data from all accounts. Deduplicate repos by nameWithOwner and PRs by URL. Tag each item with the account that produced it.
Step 4: Analyze and synthesize
This is where you add value beyond raw data:
1. Identify themes: Group repos/PRs by topic (e.g., "infrastructure", "security", "new features"). Use repo descriptions, PR titles, and any patterns you observe. 2. Highlight big wins: PRs with significant impact — large features merged, important bug fixes, new repos created. 3. Extract usage examples: For notable features, write a short "here's how to use this" snippet based on PR titles, descriptions, and repo READMEs. 4. Spot new work: Repos with first-ever commits in the time window.
Step 5: Generate the report
Use the template structure from reference.md. The report must include:
- Executive summary (3-5 sentences)
- Activity overview with mermaid charts
- Per-project sections with PR tables
- Themes and big wins
- Usage examples for notable features
- Appendix with raw data links
Save the report as a markdown file named gh-work-report-YYYY-MM-DD-to-YYYY-MM-DD.md.
Step 6: Restore original account
Switch back to the account that was active before the report started:
gh auth switch --user <ORIGINAL_ACCOUNT>Step 7: Offer automation infrastructure
After generating the report, ask the user:
Would you like me to create a private GitHub repo with automated weekly/monthly reports and a GitHub Pages site to browse them?
If yes, follow the infrastructure setup in reference.md § Infrastructure Setup.
Key Rules
- Never hardcode usernames — always detect from
gh auth status - Never use fallbacks — no silent defaults, no
2>/dev/null, no hardcoded values. Errors must fail loud with descriptive messages. - All charts must be code-generated — every number in every chart and table must come from actual API data. Never fabricate or estimate chart data.
- 4 query filters for complete PR coverage:
created:>DATE(new),is:open(all WIP),merged:>DATE(merged during window),closed:>DATE(closed during window). Deduplicate by URL. - Clamp chart timelines — Gantt and timeline charts must be scoped to the report window. Clamp start dates to
max(pr_date, window_start). - Handle private repos gracefully — note them but don't expose sensitive details unless the report itself is private
- Date math: Use
date -d "$DAYS days ago" +%Y-%m-%d(Linux) for the start date - Rate limiting: If
gh apireturns 403, wait and retry. Use--paginatefor large result sets. - Empty results are fine — if an account has no activity, say so briefly and move on
Authentication & Automation
- Local (multi-account): Use
./run.sh [days]which leveragesgh auth switchacross all locally authenticated accounts. This is the recommended approach for users with multiple accounts (e.g., public + EMU). - GitHub Actions (single-account): Use the workflow templates with a PAT secret. A PAT is scoped to one identity and cannot cross accounts.
- Two-PAT approach: For Actions across two accounts, use separate PAT secrets (
ACCOUNT1_PAT,ACCOUNT2_PAT) with explicit--header "authorization: token $PAT"per API call. - GitHub Pages is static only — report generation must happen elsewhere (local script, Actions, gh-aw). Pages just serves the output.
Reference Files
reference.md— Report template, mermaid chart patterns, infrastructure setup guidetemplates/weekly-report.yml— GitHub Actions workflow for automated reportstemplates/pages-index.html— GitHub Pages aggregation site template
gh-work-report Reference
Report Template
Use this structure for every report. Adapt section depth based on the volume of activity.
# GitHub Activity Report: {START_DATE} → {END_DATE}
> **Generated**: {GENERATION_DATE}
> **Period**: {DAYS} days
> **Accounts**: {ACCOUNT_LIST}
## Executive Summary
{3-5 sentences highlighting the most important work, themes, and wins across all projects.}
## Activity Overview
### Contribution Summary
| Metric | Count |
|--------|-------|
| Projects active | {N} |
| PRs created | {N} |
| PRs merged | {N} |
| Issues opened | {N} |
| Releases published | {N} |
### Activity Timeline
gantt title Project Activity dateFormat YYYY-MM-DD section {Project1} PR: {title} :done, {start}, {end} section {Project2} PR: {title} :done, {start}, {end}
### PR Distribution by Project
pie title PRs by Project "{Project1}" : {count} "{Project2}" : {count} "{Project3}" : {count}
### Weekly Commit Activity
xychart-beta title "Commits per Day" x-axis ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] y-axis "Commits" bar [{n}, {n}, {n}, {n}, {n}, {n}, {n}]
## 🏆 Big Wins
{For each significant accomplishment:}
### {Win Title}
**Project**: [{repo}]({url})
**Impact**: {Brief description of why this matters}
{Optional: link to PR, release, or demo}
## 📋 Projects
{For each project with activity:}
### [{owner/repo}]({url})
{Short description from repo metadata. If a GitHub Pages site exists, link it.}
{If homepage URL exists:}
🌐 **Site**: [{homepageUrl}]({homepageUrl})
#### Pull Requests
| # | Title | Status | Created | Merged |
|---|-------|--------|---------|--------|
| [#{n}]({pr_url}) | {title} | {state} | {date} | {date or —} |
#### Notable Features & Updates
{For important PRs/features, write a short description with usage example:}
**{Feature Name}** — {What it does}
// Quick example showing how to use this feature {code snippet based on PR description and repo context}
{If docs exist:}
📖 **Docs**: [{link text}]({docs_url})
## 🔍 Themes
{Identify 3-5 themes across all projects. For each:}
### {Theme Name}
{2-3 sentences about this theme — what work fell into this category, why it matters, what direction it's heading.}
**Related PRs**: {list of PR links}
## 🆕 New Work
{Projects or repos that had their first activity in this period:}
- [{repo}]({url}) — {description}
## Appendix
### All Repositories ({N} total)
| Repository | Description | Last Push | Private |
|-----------|-------------|-----------|---------|
| [{repo}]({url}) | {desc} | {date} | {yes/no} |
### Account Summary
| Account | Host | PRs | Repos |
|---------|------|-----|-------|
| {user} | github.com | {n} | {n} |Mermaid Chart Guidelines
- Gantt charts: Use for showing PR timelines across projects. Limit to top 15 PRs to keep readable.
- Pie charts: Use for PR/commit distribution across projects. Collapse projects with < 3% into "Other".
- XY charts: Use for daily/weekly commit activity. Aggregate by day of week for short periods, by week for 30+ day periods.
- Flowcharts: Use sparingly — only to illustrate architecture changes when a project had significant structural work.
Keep chart data realistic — pull actual counts from the gathered data. Never fabricate chart values. Every number must trace back to real API data. If you don't have data for a chart, omit the chart entirely rather than estimating.
gh CLI Command Patterns
Date calculation
# Linux
START_DATE=$(date -d "$DAYS days ago" +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
# macOS fallback
START_DATE=$(date -v-${DAYS}d +%Y-%m-%d)Multi-account iteration
# Parse accounts from gh auth status
ACCOUNTS=$(gh auth status 2>&1 | grep -oP '(?<=account )\S+' || \
gh auth status 2>&1 | grep -oP '✓ Logged in to .+ account \K\S+')
# Save current account
ORIGINAL=$(gh auth status 2>&1 | grep '✓ Logged in' | head -1 | grep -oP 'account \K\S+')
# Iterate
for ACCT in $ACCOUNTS; do
gh auth switch --user "$ACCT"
# ... gather data ...
done
# Restore
gh auth switch --user "$ORIGINAL"Handling rate limits
If a gh api call returns HTTP 403 or 429, the response includes Retry-After or X-RateLimit-Reset headers. Wait the indicated time before retrying. For gh search commands, GitHub's search API has a 30 requests/minute limit — add brief sleeps between calls if processing many repos.
Pagination
Always use --paginate with gh api graphql for large result sets. For REST endpoints, use --paginate with gh api.
For gh search prs, the --limit flag controls result count (max 1000).
Infrastructure Setup
When the user accepts the automation offer, create the following:
1. Create the private repo
# Determine the user's public GitHub account
PUBLIC_ACCOUNT=$(gh auth status 2>&1 | grep 'github.com' | grep -v 'ghe' | head -1 | grep -oP 'account \K\S+')
gh auth switch --user "$PUBLIC_ACCOUNT"
REPO_NAME="gh-work-reports"
gh repo create "$REPO_NAME" --private --description "Automated GitHub activity reports" --clone
cd "$REPO_NAME"2. Set up directory structure
gh-work-reports/
├── .github/
│ └── workflows/
│ ├── weekly-report.yml
│ └── monthly-report.yml
├── docs/
│ ├── index.html # Pages aggregation site
│ ├── style.css
│ └── reports/ # Individual reports go here
│ └── .gitkeep
├── scripts/
│ └── generate-report.sh # Report generation script
└── README.md3. GitHub Actions workflows
Copy the workflow templates from templates/weekly-report.yml. The workflows need:
GH_TOKENsecret withreposcope for both accounts (if applicable)- Scheduled triggers (cron) and manual dispatch
- The report generation script produces the markdown and commits it to
docs/reports/
4. GitHub Pages
Enable Pages from the repo settings pointing to docs/ on the main branch. The index.html template auto-discovers reports by listing markdown files in docs/reports/.
5. Script: generate-report.sh
The generation script should:
#!/usr/bin/env bash
set -euo pipefail
DAYS="${1:-7}"
START_DATE=$(date -d "$DAYS days ago" +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
REPORT_FILE="docs/reports/report-${START_DATE}-to-${END_DATE}.md"
# Use gh CLI to gather data (same commands as the skill workflow)
# Write the markdown report to $REPORT_FILE
# Rebuild the index page
# Commit and pushFor the full GitHub Actions workflow, Claude should read and adapt templates/weekly-report.yml.
Report File Naming
Use gh-work-report-{START_DATE}-to-{END_DATE}.md for standalone reports and report-{START_DATE}-to-{END_DATE}.md inside the automation repo's docs/reports/ directory.
Privacy Considerations
- Default to including private repo names but NOT their descriptions or PR details
- If the user says the report is for sharing publicly, strip private repo details
- Never include tokens, secrets, or authentication details in reports
name: Monthly GitHub Work Report
on:
schedule:
# First day of each month at 08:00 UTC
- cron: '0 8 1 * *'
workflow_dispatch:
inputs:
days:
description: 'Number of days to cover'
required: false
default: '30'
type: choice
options:
- '30'
- '90'
permissions:
contents: write
pages: write
id-token: write
concurrency:
group: "report-generation"
cancel-in-progress: false
jobs:
generate-report:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up date range
id: dates
run: |
set -euo pipefail
DAYS="${{ github.event.inputs.days || '30' }}"
START_DATE=$(date -d "$DAYS days ago" +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
echo "days=$DAYS" >> "$GITHUB_OUTPUT"
echo "start=$START_DATE" >> "$GITHUB_OUTPUT"
echo "end=$END_DATE" >> "$GITHUB_OUTPUT"
echo "filename=report-${START_DATE}-to-${END_DATE}.md" >> "$GITHUB_OUTPUT"
- name: Gather repository data
env:
GH_TOKEN: ${{ secrets.REPORT_TOKEN }}
run: |
set -euo pipefail
START="${{ steps.dates.outputs.start }}"
mkdir -p /tmp/report-data
gh api graphql --paginate -f query='
query($cursor: String) {
viewer {
repositories(first: 100, after: $cursor, orderBy: {field: PUSHED_AT, direction: DESC}) {
pageInfo { hasNextPage endCursor }
nodes {
nameWithOwner url description pushedAt homepageUrl isPrivate
}
}
}
}' --jq '.data.viewer.repositories.nodes[] | select(.pushedAt >= "'"$START"'")' > /tmp/report-data/repos.json
gh search prs --author=@me --created=">$START" --limit 500 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-created.json
gh search prs --author=@me --state=open --limit 500 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-open.json
gh search prs --author=@me --merged=">$START" --limit 500 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-merged.json
gh search prs --author=@me --closed=">$START" --limit 500 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-closed.json
gh search issues --author=@me --created=">$START" --limit 200 \
--json number,title,repository,state,createdAt,url \
> /tmp/report-data/issues.json
- name: Generate report
run: |
set -euo pipefail
START="${{ steps.dates.outputs.start }}"
END="${{ steps.dates.outputs.end }}"
DAYS="${{ steps.dates.outputs.days }}"
FILENAME="${{ steps.dates.outputs.filename }}"
REPORT="docs/reports/$FILENAME"
mkdir -p docs/reports
REPO_COUNT=$(cat /tmp/report-data/repos.json | wc -l)
PR_CREATED=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/prs-created.json'))))")
PR_MERGED=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/prs-merged.json'))))")
ISSUES=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/issues.json'))))")
cat > "$REPORT" << EOF
# GitHub Activity Report: ${START} → ${END}
> **Generated**: $(date +%Y-%m-%d)
> **Period**: ${DAYS} days (Monthly)
## Activity Summary
| Metric | Count |
|--------|-------|
| Projects active | ${REPO_COUNT} |
| PRs created | ${PR_CREATED} |
| PRs merged | ${PR_MERGED} |
| Issues opened | ${ISSUES} |
## Pull Requests
EOF
echo "| # | Title | Repository | Status | Created |" >> "$REPORT"
echo "|---|-------|-----------|--------|---------|" >> "$REPORT"
python3 -c "
import json
prs = json.load(open('/tmp/report-data/prs-created.json'))
for pr in prs[:100]:
repo = pr.get('repository', {})
repo_name = repo.get('nameWithOwner', 'unknown') if isinstance(repo, dict) else str(repo)
print(f'| [#{pr[\"number\"]}]({pr[\"url\"]}) | {pr[\"title\"]} | {repo_name} | {pr[\"state\"]} | {pr[\"createdAt\"][:10]} |')
" >> "$REPORT"
echo "" >> "$REPORT"
echo "## Active Repositories" >> "$REPORT"
echo "" >> "$REPORT"
echo "| Repository | Description | Last Push |" >> "$REPORT"
echo "|-----------|-------------|-----------|" >> "$REPORT"
cat /tmp/report-data/repos.json | python3 -c "
import json, sys
for line in sys.stdin:
r = json.loads(line)
desc = (r.get('description') or '—')[:60]
print(f'| [{r[\"nameWithOwner\"]}]({r[\"url\"]}) | {desc} | {r[\"pushedAt\"][:10]} |')
" >> "$REPORT"
sed -i 's/^ //' "$REPORT"
- name: Update index page
run: |
python3 << 'PYEOF'
import os, re
reports_dir = "docs/reports"
reports = sorted(
[f for f in os.listdir(reports_dir) if f.endswith(".md") and f != ".gitkeep"],
reverse=True
)
index_items = []
for r in reports:
match = re.search(r"report-(\d{4}-\d{2}-\d{2})-to-(\d{4}-\d{2}-\d{2})", r)
if match:
start, end = match.groups()
index_items.append(f'<li><a href="reports/{r}">{start} → {end}</a></li>')
with open("docs/index.html", "r") as f:
html = f.read()
list_html = "\n".join(index_items) if index_items else "<li>No reports yet</li>"
html = re.sub(
r"(<!-- REPORT_LIST_START -->).*?(<!-- REPORT_LIST_END -->)",
rf"\1\n{list_html}\n\2",
html,
flags=re.DOTALL
)
with open("docs/index.html", "w") as f:
f.write(html)
PYEOF
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/
git diff --staged --quiet || (git commit -m "📊 Monthly Report: ${{ steps.dates.outputs.start }} → ${{ steps.dates.outputs.end }}" && git push)
deploy-pages:
needs: generate-report
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitHub Work Reports</title>
<style>
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--text: #e6edf3;
--text-muted: #8b949e;
--accent: #58a6ff;
--accent-hover: #79c0ff;
--green: #3fb950;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 2rem 1rem;
}
header {
text-align: center;
padding: 3rem 0 2rem;
border-bottom: 1px solid var(--border);
margin-bottom: 2rem;
}
header h1 {
font-size: 2rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
header p {
color: var(--text-muted);
font-size: 1rem;
}
.report-list {
list-style: none;
}
.report-list li {
border: 1px solid var(--border);
border-radius: 6px;
margin-bottom: 0.5rem;
transition: border-color 0.2s;
}
.report-list li:hover {
border-color: var(--accent);
}
.report-list a {
display: flex;
align-items: center;
padding: 1rem 1.25rem;
color: var(--accent);
text-decoration: none;
font-size: 1rem;
}
.report-list a:hover {
color: var(--accent-hover);
}
.report-list a::before {
content: '📊';
margin-right: 0.75rem;
font-size: 1.25rem;
}
.badge {
display: inline-block;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 2em;
padding: 0.2em 0.75em;
font-size: 0.75rem;
color: var(--text-muted);
margin-left: auto;
}
.empty-state {
text-align: center;
padding: 3rem;
color: var(--text-muted);
}
footer {
text-align: center;
padding: 2rem 0;
margin-top: 2rem;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.85rem;
}
footer a { color: var(--accent); text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>📊 GitHub Work Reports</h1>
<p>Automated activity reports across all GitHub accounts</p>
</header>
<main>
<ul class="report-list">
<!-- REPORT_LIST_START -->
<li class="empty-state">No reports yet — reports will appear here after the first workflow run.</li>
<!-- REPORT_LIST_END -->
</ul>
</main>
<footer>
Generated by <a href="https://github.com">gh-work-report</a> skill
</footer>
</div>
</body>
</html>
name: Weekly GitHub Work Report
on:
schedule:
# Every Monday at 08:00 UTC
- cron: '0 8 * * 1'
workflow_dispatch:
inputs:
days:
description: 'Number of days to cover (1, 5, 7, 30, 90)'
required: false
default: '7'
type: choice
options:
- '1'
- '5'
- '7'
- '30'
- '90'
permissions:
contents: write
pages: write
id-token: write
concurrency:
group: "report-generation"
cancel-in-progress: false
jobs:
generate-report:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up date range
id: dates
run: |
set -euo pipefail
DAYS="${{ github.event.inputs.days || '7' }}"
START_DATE=$(date -d "$DAYS days ago" +%Y-%m-%d)
END_DATE=$(date +%Y-%m-%d)
echo "days=$DAYS" >> "$GITHUB_OUTPUT"
echo "start=$START_DATE" >> "$GITHUB_OUTPUT"
echo "end=$END_DATE" >> "$GITHUB_OUTPUT"
echo "filename=report-${START_DATE}-to-${END_DATE}.md" >> "$GITHUB_OUTPUT"
- name: Gather repository data
env:
GH_TOKEN: ${{ secrets.REPORT_TOKEN }}
run: |
set -euo pipefail
START="${{ steps.dates.outputs.start }}"
END="${{ steps.dates.outputs.end }}"
mkdir -p /tmp/report-data
# Get repos with recent pushes
gh api graphql --paginate -f query='
query($cursor: String) {
viewer {
repositories(first: 100, after: $cursor, orderBy: {field: PUSHED_AT, direction: DESC}) {
pageInfo { hasNextPage endCursor }
nodes {
nameWithOwner url description pushedAt homepageUrl isPrivate
}
}
}
}' --jq '.data.viewer.repositories.nodes[] | select(.pushedAt >= "'"$START"'")' > /tmp/report-data/repos.json
# Get PRs created in window
gh search prs --author=@me --created=">$START" --limit 200 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-created.json
# Get all open/WIP PRs (may predate window)
gh search prs --author=@me --state=open --limit 200 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-open.json
# Get PRs merged in window
gh search prs --author=@me --merged=">$START" --limit 200 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-merged.json
# Get PRs closed in window
gh search prs --author=@me --closed=">$START" --limit 200 \
--json number,title,repository,state,createdAt,url,mergedAt \
> /tmp/report-data/prs-closed.json
# Get issues
gh search issues --author=@me --created=">$START" --limit 100 \
--json number,title,repository,state,createdAt,url \
> /tmp/report-data/issues.json
- name: Generate report
run: |
set -euo pipefail
START="${{ steps.dates.outputs.start }}"
END="${{ steps.dates.outputs.end }}"
DAYS="${{ steps.dates.outputs.days }}"
FILENAME="${{ steps.dates.outputs.filename }}"
REPORT="docs/reports/$FILENAME"
mkdir -p docs/reports
# Count metrics
REPO_COUNT=$(cat /tmp/report-data/repos.json | wc -l)
PR_CREATED=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/prs-created.json'))))")
PR_MERGED=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/prs-merged.json'))))")
ISSUES=$(python3 -c "import json; print(len(json.load(open('/tmp/report-data/issues.json'))))")
cat > "$REPORT" << EOF
# GitHub Activity Report: ${START} → ${END}
> **Generated**: $(date +%Y-%m-%d)
> **Period**: ${DAYS} days
## Activity Summary
| Metric | Count |
|--------|-------|
| Projects active | ${REPO_COUNT} |
| PRs created | ${PR_CREATED} |
| PRs merged | ${PR_MERGED} |
| Issues opened | ${ISSUES} |
## Pull Requests
EOF
# Add PR table
echo "| # | Title | Repository | Status | Created |" >> "$REPORT"
echo "|---|-------|-----------|--------|---------|" >> "$REPORT"
python3 -c "
import json
prs = json.load(open('/tmp/report-data/prs-created.json'))
for pr in prs[:50]:
repo = pr.get('repository', {})
repo_name = repo.get('nameWithOwner', 'unknown') if isinstance(repo, dict) else str(repo)
print(f'| [#{pr[\"number\"]}]({pr[\"url\"]}) | {pr[\"title\"]} | {repo_name} | {pr[\"state\"]} | {pr[\"createdAt\"][:10]} |')
" >> "$REPORT"
echo "" >> "$REPORT"
echo "## Active Repositories" >> "$REPORT"
echo "" >> "$REPORT"
echo "| Repository | Description | Last Push |" >> "$REPORT"
echo "|-----------|-------------|-----------|" >> "$REPORT"
cat /tmp/report-data/repos.json | python3 -c "
import json, sys
for line in sys.stdin:
r = json.loads(line)
desc = (r.get('description') or '—')[:60]
print(f'| [{r[\"nameWithOwner\"]}]({r[\"url\"]}) | {desc} | {r[\"pushedAt\"][:10]} |')
" >> "$REPORT"
# Strip leading whitespace from heredoc
sed -i 's/^ //' "$REPORT"
- name: Update index page
run: |
python3 << 'PYEOF'
import os, re
reports_dir = "docs/reports"
reports = sorted(
[f for f in os.listdir(reports_dir) if f.endswith(".md") and f != ".gitkeep"],
reverse=True
)
index_items = []
for r in reports:
match = re.search(r"report-(\d{4}-\d{2}-\d{2})-to-(\d{4}-\d{2}-\d{2})", r)
if match:
start, end = match.groups()
index_items.append(f'<li><a href="reports/{r}">{start} → {end}</a></li>')
with open("docs/index.html", "r") as f:
html = f.read()
# Replace report list placeholder
list_html = "\n".join(index_items) if index_items else "<li>No reports yet</li>"
html = re.sub(
r"(<!-- REPORT_LIST_START -->).*?(<!-- REPORT_LIST_END -->)",
rf"\1\n{list_html}\n\2",
html,
flags=re.DOTALL
)
with open("docs/index.html", "w") as f:
f.write(html)
PYEOF
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/
git diff --staged --quiet || (git commit -m "📊 Report: ${{ steps.dates.outputs.start }} → ${{ steps.dates.outputs.end }}" && git push)
deploy-pages:
needs: generate-report
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: main
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4