github-monitor
Watch your GitHub repos across four views - a combined urgency monitor (stale PRs, new issues, releases), a new-issue triage queue, a release upgrade digest, or your own opened-PR tracker.
git clone --depth 1 https://github.com/aeonfun/aeon /tmp/github-monitor && cp -r /tmp/github-monitor/skills/github-monitor ~/.claude/skills/github-monitorSKILL.md
> **${var}** — View selector + optional scope.
> - **empty** → combined **monitor** over every repo in `memory/watched-repos.md`.
> - **`owner/repo`** (a bare repo, no view keyword) → combined **monitor** scoped to that one repo.
> - **`issues [scope]`** → new-issue triage queue. `scope` accepts `owner/repo`, `org:foo`, `user:bar`, or a bare login; empty = all repos owned by the authenticated user.
> - **`releases [repo,repo,…]`** → release upgrade-triage digest. Comma-separated repo list; empty = the built-in watch list.
> - **`prs`** → status tracker for PRs this aeon instance opened across external repos.
> - **`add-repo:<owner/repo>`** → append `owner/repo` to `memory/watched-repos.md`, confirm, and end (the shape the Telegram force-reply sends — see the config-capture note in Shared setup). No view runs.
This skill is four focused views of the same GitHub surface. The combined monitor is the default; `issues`, `releases`, and `prs` each drill into one dimension with the sibling view's own filtering, ranking, and output format. Only the `monitor` and `issues` views take a repo scope; `releases` takes a repo list; `prs` takes no scope (it reads its config from `aeon.yml`/env).
---
## Shared setup (every view)
1. Read `memory/MEMORY.md` for high-level context.
2. Read the last 2 days of `memory/logs/` — used for dedup in the `monitor`, `issues`, and `releases` views.
3. Parse `${var}` into a `VIEW` and a `SCOPE`:
```bash
RAW="$(printf '%s' "${var}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
# Config capture (Telegram force-reply): var="add-repo:<owner/repo>" appends to the watchlist,
# confirms, and ends — it is NOT a view, so it must be intercepted before the VIEW parse below.
case "$RAW" in
add-repo:*)
CAND="$(printf '%s' "${RAW#add-repo:}" \
| sed -e 's#^https\?://github.com/##' -e 's/^@//' -e 's/\.git$//' \
-e 's/^[[:space:]]*//' -e 's/[[:space:]].*$//')"
if ! printf '%s' "$CAND" | grep -qE '^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$'; then
./notify "Couldn't read \"$CAND\" as a repo. Reply with owner/repo (e.g. acme/api)."
# log: - view: add-repo (var="${var}") → BAD_VALUE
exit 0
fi
mkdir -p memory; touch memory/watched-repos.md
if grep -qiE "^[[:space:]]*-[[:space:]]*${CAND}[[:space:]]*$" memory/watched-repos.md; then
./notify "Already watching $CAND."
else
printf -- '- %s\n' "$CAND" >> memory/watched-repos.md
./notify "Now watching $CAND — it'll show up in the next GitHub Monitor run."
fi
# log under ### github-monitor: - view: add-repo (var="${var}") → $CAND
exit 0 ;;
esac
if [ -z "$RAW" ]; then
VIEW=monitor; SCOPE=""
else
VIEW_TOKEN="$(printf '%s' "$RAW" | awk '{print tolower($1)}')"
SCOPE="$(printf '%s' "$RAW" | sed -E 's/^[^[:space:]]+[[:space:]]*//')" # everything after the first word
case "$VIEW_TOKEN" in
issues|releases|prs) VIEW="$VIEW_TOKEN" ;;
*) VIEW=monitor; SCOPE="$RAW" ;; # bare owner/repo scopes the combined monitor
esac
fi
```
4. Dispatch to the matching view section below. Run exactly one view per invocation.
**Selector examples:** `""` → monitor/all · `anza-xyz/agave` → monitor/one-repo · `issues` → issues/all · `issues org:anthropics` → issues/org · `releases` → releases/watch-list · `releases anthropics/claude-code,openai/openai-python` → releases/custom · `prs` → PR tracker.
**Logging convention (all views):** every view appends to `memory/logs/${today}.md` under the single heading `### github-monitor`, and its **first bullet is a discriminator** naming the view that ran: `- view: <monitor|issues|releases|prs> (var="${var}")`. Keep the view-specific bullets exactly as described in each section — the identifiers/URLs they write are what the next run dedups against.
---
## View: monitor (default — empty var, or a bare `owner/repo` scope)
Tiered urgency scan of PRs, new issues, and new releases across watched repos, with concrete next actions.
### Config
Read repos from `memory/watched-repos.md`. If the file is missing or empty, offer to add the first repo via a Telegram force-reply, then log `GITHUB_MONITOR_EMPTY_CONFIG` (under `### github-monitor`) and end. Send the offer **only** if no `add-repo` prompt was already offered in the last 2 days of `memory/logs/` (dedup so an unconfigured fork isn't nagged every run):
```bash
./notify "No repos on the watchlist yet. Which repo should I watch? Reply with owner/repo." \
--force-reply --placeholder "owner/repo" \
--context "github-monitor::add-repo"
```
The reply routes back as `var=add-repo:<owner/repo>`, handled by the config-capture branch in Shared setup. Record `FORCE_REPLY_OFFERED: add-repo` in the log when you send it.
```markdown
# memory/watched-repos.md
- owner/repo
- another-owner/another-repo
```
If `SCOPE` is set (a bare `owner/repo`), monitor **only** that repo. Otherwise monitor every entry in `watched-repos.md`.
### 1. Collect
For each repo, run these three `gh` calls. Capture the JSON; do not trust any shell expansion of untrusted fields.
**Open PRs** (full shape — the extra fields power the tier classifier):
```bash
gh pr list -R $repo --state open --limit 30 \
--json number,title,url,updatedAt,isDraft,reviewDecision,reviewRequests,statusCheckRollup,labels,author
```
**Issues opened in the last 24h**:
```bash
gh issue list -R $repo --state open --limit 20 \
--json number,title,url,createdAt,labels,author
```
Filter client-side to items where `createdAt` is within the last 24h.
**Releases published in the last 24h** (skip drafts and prereleases):
```bash
gh release list -R $repo --limit 5 --exclude-drafts --exclude-pre-releases \
--json tagName,publishedAt,name,url
```
Filter client-side to items where `publishedAt` is within the last 24h.
If any single `gh` call fails (network, auth, 404), record it as `gh_error(<code>)` for that repo and keep going — one repo's failure must not abort the whole run.
### 2. Classify into tiers
WSet up and run an Aeon agent instance — get started from scratch, pick which skills to turn on or install more from packs, reschedule or change what runs, edit what an existing skill does, fix a skill that isn't firing, set the STRATEGY.md north star and soul/ voice, turn a coding-agent chat into a scheduled Aeon skill, and mine past coding-agent conversations for recurring work worth automating as a skill. Use when the user mentions Aeon, aeon.yml, an Aeon skill / instance / routine / pack, asks to schedule, enable, edit, or debug an agent that runs on a cron, or asks what of their repeated/manual work Aeon could take over.
Mention/keyword sweep on social platforms for [REPLACE: KEYWORDS] — trends, sentiment, top posts
5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates
Static config-correctness linter for this instance - catches the silent-failure class (unquoted schedules, duplicate keys, unconfigured skills, mode typos, broken requires/MCP refs) that no run-based health skill can see. Notifies only on problems.
Pull framework updates from the upstream Aeon repo into this instance - 3-way merges canon's new commits into a PR, never clobbering operator config.
Write a publication-ready article in one of three angles - a trending long-form piece, a watched-repo thesis, or a project-through-a-lens essay. Optional Replicate hero image with --visual.
Automatically merge open PRs that have passing CI, no blocking reviews, and no conflicts
Two-mode aeon.yml workflow builder - analyze inspects URLs and emits a tiered, signal-verified skill-enablement plan plus an aeon.yml diff; enable flips slugs to enabled:true and opens a PR.