Skip to content

Weekly OpenCHAMI Digest → Website PR #10

Weekly OpenCHAMI Digest → Website PR

Weekly OpenCHAMI Digest → Website PR #10

name: Weekly OpenCHAMI Digest → Website PR
on:
schedule:
# Mondays 09:00 America/Denver (16:00 UTC most of the year)
- cron: "0 16 * * 1"
workflow_dispatch:
permissions:
contents: read
pull-requests: write
models: read
env:
ORG: OpenCHAMI
WEBSITE_REPO: OpenCHAMI/openchami.org
POST_DIR: content/posts/weekly
LOCAL_TZ: America/Denver
POST_TIME: "09:00:00" # local publish time in the Hugo front matter
MODEL_ID: openai/gpt-4.1-mini
jobs:
weekly_digest:
runs-on: ubuntu-latest
steps:
- name: Checkout (community repo context)
uses: actions/checkout@v4
- name: Install GitHub CLI + models extension
run: |
sudo apt-get update && sudo apt-get install -y jq
gh extension install github/gh-models
env:
GH_TOKEN: ${{ github.token }}
- name: Verify org-level token is available
env:
GH_TOKEN: ${{ secrets.ORG_READ_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${GH_TOKEN:-}" ]]; then
echo "ORG_READ_TOKEN is not available to this workflow."
exit 1
fi
# Lightweight call that should succeed with a fine-grained PAT that can read public repos
gh api "/rate_limit" >/dev/null
echo "ORG_READ_TOKEN present and usable."
- name: Debug token + org visibility
env:
GH_TOKEN: ${{ secrets.ORG_READ_TOKEN }}
ORG: ${{ env.ORG }}
run: |
set -Eeuo pipefail
echo "==> GH auth check"; gh api /rate_limit >/dev/null && echo "rate_limit OK"
echo "==> Who am I?"
gh api /user | jq '{login, type}'
echo "==> Can I view the org?"
gh api "/orgs/${ORG}" | jq '{login, visibility, members_url}' || { echo "Org read failed"; exit 1; }
echo "==> List first 5 repos visible to this token"
gh api "/orgs/${ORG}/repos?per_page=5&type=public" | jq '.[].full_name'
# One issue result via REST Search API
gh api -H "Accept: application/vnd.github+json" \
"/search/issues?q=org:${ORG}+is:issue+is:public&per_page=1" \
| jq '{count:.total_count, sample: (.items[0] | {title, html_url, repository_url})}'
- name: Compute dates (Denver-local & UTC)
id: dates
run: |
# Past 7 days window (UTC for queries)
SINCE_UTC=$(date -u -d "7 days ago" +"%Y-%m-%d")
NOW_UTC=$(date -u +"%Y-%m-%d")
# Publication date/time in local tz for Hugo front matter & filenames
export TZ="${LOCAL_TZ}"
PUB_DATE=$(date +"%Y-%m-%d")
PUB_YEAR=$(date +"%Y")
PUB_MONTH=$(date +"%m")
PUB_ISO_LOCAL="${PUB_DATE}T${POST_TIME}"
# Pretty range for title
RANGE_START_PRETTY=$(date -d "7 days ago" +"%b %d")
RANGE_END_PRETTY=$(date +"%b %d, %Y")
echo "since_utc=$SINCE_UTC" >> $GITHUB_OUTPUT
echo "now_utc=$NOW_UTC" >> $GITHUB_OUTPUT
echo "pub_date=$PUB_DATE" >> $GITHUB_OUTPUT
echo "pub_year=$PUB_YEAR" >> $GITHUB_OUTPUT
echo "pub_month=$PUB_MONTH" >> $GITHUB_OUTPUT
echo "pub_iso_local=$PUB_ISO_LOCAL" >> $GITHUB_OUTPUT
echo "range_start=$RANGE_START_PRETTY" >> $GITHUB_OUTPUT
echo "range_end=$RANGE_END_PRETTY" >> $GITHUB_OUTPUT
- name: Collect org activity → activity.json (REST + pagination)
env:
GH_TOKEN: ${{ secrets.ORG_READ_TOKEN }}
ORG: ${{ env.ORG }}
run: |
set -Eeuo pipefail
SINCE="${{ steps.dates.outputs.since_utc }}"
# ---- Search Issues (REST with pagination)
: > issues.jsonl
for PAGE in $(seq 1 10); do
RES=$(gh api -H "Accept: application/vnd.github+json" \
"/search/issues?q=org:${ORG}+updated:>${SINCE}+is:issue+is:public&per_page=100&page=${PAGE}")
COUNT=$(echo "$RES" | jq '.items | length')
echo "$RES" | jq -c '.items[]' >> issues.jsonl
[[ "$COUNT" -lt 100 ]] && break
done
jq -s '[.[] | {title,number,repository: .repository_url | capture("repos/(?<full>.+)$").full, author:.user.login, createdAt:.created_at, updatedAt:.updated_at, state:.state, labels: [.labels[].name], url:.html_url}]' issues.jsonl > issues.json
# ---- Search PRs (REST with pagination)
: > prs.jsonl
for PAGE in $(seq 1 10); do
RES=$(gh api -H "Accept: application/vnd.github+json" \
"/search/issues?q=org:${ORG}+updated:>${SINCE}+is:pr+is:public&per_page=100&page=${PAGE}")
COUNT=$(echo "$RES" | jq '.items | length')
echo "$RES" | jq -c '.items[]' >> prs.jsonl
[[ "$COUNT" -lt 100 ]] && break
done
jq -s '[.[] | {title,number,repository: .repository_url | capture("repos/(?<full>.+)$").full, author:.user.login, createdAt:.created_at, updatedAt:.updated_at, state:(.state + (if .pull_request.merged_at then "-merged" else "" end)), mergedAt:(.pull_request.merged_at // null), labels: [.labels[].name], url:.html_url}]' prs.jsonl > prs.json
# ---- Releases (enumerate repos; more reliable than is:release)
gh api "/orgs/${ORG}/repos?per_page=100&type=public" > repos.json
: > releases.jsonl
jq -r '.[].name' repos.json | while read -r REPO; do
gh api -H "Accept: application/vnd.github+json" "/repos/${ORG}/${REPO}/releases?per_page=100" \
| jq -c --arg SINCE "${SINCE}" '.[] | select(.created_at >= ($SINCE+"T00:00:00Z")) | {name, tag_name, created_at, html_url, repository:"'${ORG}'/'${REPO}'"}' \
>> releases.jsonl || true
done
jq -s '.' releases.jsonl > releases.json || echo '[]' > releases.json
# ---- Public org events (best effort)
gh api "/orgs/${ORG}/events" > events.json || echo '[]' > events.json
# ---- Merge all payloads
jq -n \
--slurpfile issues issues.json \
--slurpfile prs prs.json \
--slurpfile releases releases.json \
--slurpfile events events.json \
'{issues:$issues[0], prs:$prs[0], releases:$releases[0], events:$events[0]}' \
> activity.json
echo "==> Counts:"
echo " issues: $(jq 'length' issues.json)"
echo " prs: $(jq 'length' prs.json)"
echo " releases: $(jq 'length' releases.json)"
echo " events: $(jq 'length' events.json)"
- name: Write prompt file
run: |
mkdir -p prompts
cat > prompts/digest.prompt.yml <<'YML'
name: Weekly Org Digest
description: Summarize OpenCHAMI org activity for new users and contributors.
messages:
- role: system
content: >
You are a technical writer + developer marketer.
Create a concise, skimmable weekly digest in Markdown.
Sections: Highlights, New & Notable PRs, Issues to Watch, Releases, Contributor Thanks.
Link to items. Prefer user-facing language. Avoid internal jargon.
End with "What’s next" suggestions and 3–5 proposed blog titles.
- role: user
content: >
Here is JSON of activity across the org for the last 7 days.
Extract themes (user value), group by repo when helpful,
and surface at most 10 bullets in Highlights.
Data:\n{{input}}
YML
- name: Generate digest (Markdown) with GitHub Models → DIGEST.md
run: |
set -Eeuo pipefail
gh models run --model "$MODEL_ID" --file prompts/digest.prompt.yml < activity.json > DIGEST.md
env:
GH_TOKEN: ${{ github.token }}
MODEL_ID: ${{ env.MODEL_ID }}
- name: Build Hugo post content → POST.md
id: post
run: |
set -e
TITLE="OpenCHAMI Weekly Digest: ${{ steps.dates.outputs.range_start }}–${{ steps.dates.outputs.range_end }}"
SLUG="weekly-digest-${{ steps.dates.outputs.pub_date }}"
FRONTMATTER=$(cat <<EOF
---
title: "${TITLE}"
date: "${{ steps.dates.outputs.pub_iso_local }}"
slug: "${SLUG}"
tags: ["weekly", "updates", "openchami"]
draft: false
---
EOF
)
echo "${FRONTMATTER}" > POST.md
echo "" >> POST.md
cat DIGEST.md >> POST.md
# Output for later steps
echo "title=${TITLE}" >> $GITHUB_OUTPUT
echo "slug=${SLUG}" >> $GITHUB_OUTPUT
- name: Clone website repo (openchami.org)
run: |
git clone "https://x-access-token:${WEBSITE_REPO_TOKEN}@github.com/${WEBSITE_REPO}.git" website
env:
WEBSITE_REPO_TOKEN: ${{ secrets.WEBSITE_REPO_TOKEN }}
- name: Create post file, branch, commit, and push
id: prprep
run: |
set -e
BRANCH="weekly-digest/${{ steps.dates.outputs.pub_date }}"
POST_PATH="website/${POST_DIR}/${{ steps.dates.outputs.pub_date }}.md"
mkdir -p "website/${POST_DIR}"
cp POST.md "${POST_PATH}"
cd website
git config user.name "openchami-bot"
git config user.email "bot@openchami.org"
git checkout -b "$BRANCH"
git add "${POST_PATH}"
git commit -m "feat(blog): add ${{ steps.post.outputs.title }}"
git push --set-upstream origin "$BRANCH"
echo "branch=$BRANCH" >> $GITHUB_OUTPUT
echo "post_path=$POST_PATH" >> $GITHUB_OUTPUT
- name: Open PR to website repo
run: |
gh pr create \
--repo "${WEBSITE_REPO}" \
--base main \
--head "${{ steps.prprep.outputs.branch }}" \
--title "${{ steps.post.outputs.title }}" \
--body "This PR adds the weekly digest post generated on ${{ steps.dates.outputs.pub_date }}.
- Post path: \`${{ steps.prprep.outputs.post_path }}\`
- Slug: \`${{ steps.post.outputs.slug }}\`
Please review copy and publish.
"
env:
GH_TOKEN: ${{ secrets.WEBSITE_REPO_TOKEN }}