Initial commit: custom OpenClaw skills from docker-test

- workspace: capmetro-monitor, github-notifications, model-selector
- workspace-security: vt-monitor, monitor-unauthorized
- workspace-home: cron-manager, monitor-unauthorized
- extensions: vt-sentinel (VT-Sentinel plugin)

Includes sync.sh for pull/push, README, AGENTS.md, .gitignore.
This commit is contained in:
2026-02-16 15:32:44 +00:00
commit 3b7d6bb67c
37 changed files with 3358 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
---
name: github-notifications
description: Check GitHub notifications for PR activity and major releases. Filters for PRs where user is mentioned/author, and major releases (v*.0.0) plus all Mirrowel/LLM-API-Key-Proxy dev builds. Tracks state to avoid duplicate alerts. Use for periodic GitHub notification checking via cron jobs or manual checks.
---
# GitHub Notifications Checker
Efficiently check GitHub notifications with smart filtering and state tracking.
## What It Does
1. **Fetches notifications** via GitHub CLI (`gh api`)
2. **Filters intelligently:**
- PRs where you're mentioned, author, or review requested
- Major releases (v*.0.0 format)
- ALL releases from `Mirrowel/LLM-API-Key-Proxy` (including dev builds)
- Excludes: rc/pre/beta/alpha/nightly releases
3. **Tracks state** to avoid duplicate notifications
4. **Returns JSON** for easy parsing
## Usage
### Basic Check
```bash
bash skills/github-notifications/scripts/check.sh
```
**Output when nothing new:**
```json
{"hasNew":false}
```
**Output with new activity:**
```json
{
"hasNew": true,
"newPRs": [
{
"repo": "openclaw/openclaw",
"title": "feat: Add cron silent mode",
"url": "https://api.github.com/repos/openclaw/openclaw/pulls/1234",
"updated": "2026-02-03T14:30:00Z",
"reason": "mention",
"id": "openclaw/openclaw#feat: Add cron silent mode"
}
],
"newReleases": [
{
"repo": "some/repo",
"title": "v2.0.0",
"updated": "2026-02-03T12:00:00Z",
"id": "some/repo@v2.0.0"
}
]
}
```
### Environment Variables
- `STATE_FILE` - Path to state tracking file (default: `memory/github-check-state.json`)
- `WORKSPACE` - Workspace directory (default: `/home/node/.openclaw/workspace`)
### State Tracking
State is stored in `memory/github-check-state.json`:
```json
{
"lastCheck": "2026-02-03T14:00:00Z",
"seenPRs": ["repo#PR Title", ...],
"seenReleases": ["repo@v1.0.0", ...]
}
```
## Integration with Cron
This skill is designed to work with OpenClaw cron jobs. The script handles all filtering and state management, only calling the LLM when there's actual content to summarize.
**Recommended cron setup:**
1. Script runs periodically (every 4 hours)
2. If `hasNew: false`, script exits silently - no LLM call, no message
3. If `hasNew: true`, cron job can format the summary and deliver it
This approach:
- ✅ Saves tokens (no LLM call when nothing new)
- ✅ Handles errors gracefully (GitHub API failures logged)
- ✅ Avoids duplicate notifications (state tracking)
- ✅ Faster execution (no LLM parsing)
## Error Handling
If GitHub API fails, returns:
```json
{
"error": "GitHub API failed",
"details": "..."
}
```
Check for `.error` field in output to detect failures.
## Auto-Dismiss Low-Value Notifications
```bash
# Dry run (see what would be dismissed)
DRY_RUN=true bash skills/github-notifications/scripts/auto-dismiss.sh
# Actually dismiss
bash skills/github-notifications/scripts/auto-dismiss.sh
```
**Auto-dismisses:**
- Title matches: nightly, preview, checkpoint, pre-release, canary, alpha, beta, snapshot
- Releases with empty release notes
**Output:**
```json
{"dismissed":3,"checked":12}
```
## Requirements
- `gh` CLI authenticated
- `jq` for JSON parsing
- GitHub token with `notifications` scope

View File

@@ -0,0 +1,74 @@
#!/bin/bash
# Auto-dismiss GitHub notifications matching certain patterns
# - Nightlies, previews, checkpoints, rc, etc (by title pattern)
# - Releases with no release notes
# Exempts specified repos from auto-dismiss
#
# Dismiss = PATCH (read) + DELETE thread + DELETE subscription
set -e
DRY_RUN="${DRY_RUN:-false}"
# Repos exempt from auto-dismiss (always show these)
EXEMPT_REPOS="Mirrowel/LLM-API-Key-Proxy|b3nw/LLM-API-Key-Proxy|pedramamini/Maestro"
# Patterns to auto-dismiss (case-insensitive)
DISMISS_PATTERNS="nightly|preview|checkpoint|pre-release|canary|alpha|beta|snapshot|-rc\.|rc[0-9]"
# Get all unread notifications
NOTIFICATIONS=$(gh api /notifications 2>/dev/null || echo "[]")
if [ "$NOTIFICATIONS" = "[]" ] || [ -z "$NOTIFICATIONS" ]; then
echo '{"dismissed":0,"checked":0}'
exit 0
fi
DISMISSED=0
TOTAL=$(echo "$NOTIFICATIONS" | jq 'length')
echo "$NOTIFICATIONS" | jq -c '.[]' | while read -r notif; do
ID=$(echo "$notif" | jq -r '.id')
TITLE=$(echo "$notif" | jq -r '.subject.title')
TYPE=$(echo "$notif" | jq -r '.subject.type')
URL=$(echo "$notif" | jq -r '.subject.url')
REPO=$(echo "$notif" | jq -r '.repository.full_name')
# Skip exempt repos
if echo "$REPO" | grep -qiE "$EXEMPT_REPOS"; then
continue
fi
SHOULD_DISMISS=false
REASON=""
# Check title patterns
if echo "$TITLE" | grep -qiE "$DISMISS_PATTERNS"; then
SHOULD_DISMISS=true
REASON="title_pattern"
fi
# Check releases with no notes
if [ "$TYPE" = "Release" ] && [ "$SHOULD_DISMISS" = "false" ]; then
RELEASE_BODY=$(gh api "$URL" --jq '.body // ""' 2>/dev/null || echo "")
if [ -z "$RELEASE_BODY" ] || [ "$RELEASE_BODY" = "null" ]; then
SHOULD_DISMISS=true
REASON="empty_release_notes"
fi
fi
if [ "$SHOULD_DISMISS" = "true" ]; then
if [ "$DRY_RUN" = "true" ]; then
echo "Would dismiss: [$REPO] $TITLE ($REASON)" >&2
else
# Full dismiss: mark read + delete thread + delete subscription
gh api -X PATCH "/notifications/threads/$ID" 2>/dev/null || true
gh api -X DELETE "/notifications/threads/$ID" 2>/dev/null || true
gh api -X DELETE "/notifications/threads/$ID/subscription" 2>/dev/null || true
echo "Dismissed: [$REPO] $TITLE ($REASON)" >&2
fi
DISMISSED=$((DISMISSED + 1))
fi
done
echo "{\"dismissed\":$DISMISSED,\"checked\":$TOTAL}"

View File

@@ -0,0 +1,104 @@
#!/bin/bash
# GitHub Notifications Checker
# Filters PRs and releases, tracks state, returns JSON summary
set -e
STATE_FILE="${STATE_FILE:-memory/github-check-state.json}"
WORKSPACE="${WORKSPACE:-/home/node/.openclaw/workspace}"
cd "$WORKSPACE"
# Initialize state file if missing
if [ ! -f "$STATE_FILE" ]; then
mkdir -p "$(dirname "$STATE_FILE")"
echo '{"lastCheck":"1970-01-01T00:00:00Z","seenPRs":[],"seenReleases":[]}' > "$STATE_FILE"
fi
# Load last check time
LAST_CHECK=$(jq -r '.lastCheck' "$STATE_FILE")
NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Fetch notifications (PRs only)
PR_DATA=$(gh api 'notifications?all=true&per_page=100' 2>&1)
if [ $? -ne 0 ]; then
echo '{"error":"GitHub API failed","details":"'"${PR_DATA//\"/\\\"}"'"}' | jq .
exit 1
fi
# Filter PRs where user is mentioned or author
FILTERED_PRS=$(echo "$PR_DATA" | jq -r '[
.[] |
select(.subject.type == "PullRequest") |
select(.reason == "mention" or .reason == "author" or .reason == "review_requested") |
{
repo: .repository.full_name,
title: .subject.title,
url: .subject.url,
updated: .updated_at,
reason: .reason,
id: (.repository.full_name + "#" + .subject.title)
}
]')
# Filter releases
RELEASE_DATA=$(echo "$PR_DATA" | jq -r '[
.[] |
select(.subject.type == "Release") |
{
repo: .repository.full_name,
title: .subject.title,
updated: .updated_at,
id: (.repository.full_name + "@" + .subject.title)
}
]')
# Filter major releases (v*.0.0) + ALL Mirrowel/LLM-API-Key-Proxy releases
FILTERED_RELEASES=$(echo "$RELEASE_DATA" | jq -r '[
.[] |
select(
(.repo == "Mirrowel/LLM-API-Key-Proxy") or
(.title | test("^v[0-9]+\\.0\\.0"))
) |
select(.title | test("(rc|pre|beta|alpha|nightly)") | not)
]')
# Load seen items
SEEN_PRS=$(jq -r '.seenPRs // []' "$STATE_FILE")
SEEN_RELEASES=$(jq -r '.seenReleases // []' "$STATE_FILE")
# Find new items
NEW_PRS=$(echo "$FILTERED_PRS" | jq --argjson seen "$SEEN_PRS" '[
.[] | select(.id as $id | $seen | index($id) | not)
]')
NEW_RELEASES=$(echo "$FILTERED_RELEASES" | jq --argjson seen "$SEEN_RELEASES" '[
.[] | select(.id as $id | $seen | index($id) | not)
]')
# Count new items
NEW_PR_COUNT=$(echo "$NEW_PRS" | jq 'length')
NEW_RELEASE_COUNT=$(echo "$NEW_RELEASES" | jq 'length')
# Update state
ALL_PR_IDS=$(echo "$FILTERED_PRS" | jq -r '[.[].id]')
ALL_RELEASE_IDS=$(echo "$FILTERED_RELEASES" | jq -r '[.[].id]')
jq -n \
--arg now "$NOW" \
--argjson prIds "$ALL_PR_IDS" \
--argjson relIds "$ALL_RELEASE_IDS" \
'{lastCheck:$now, seenPRs:$prIds, seenReleases:$relIds}' \
> "$STATE_FILE"
# Output result
if [ "$NEW_PR_COUNT" -eq 0 ] && [ "$NEW_RELEASE_COUNT" -eq 0 ]; then
echo '{"hasNew":false}'
exit 0
fi
# Return new items
jq -n \
--argjson prs "$NEW_PRS" \
--argjson releases "$NEW_RELEASES" \
'{hasNew:true, newPRs:$prs, newReleases:$releases}'

View File

@@ -0,0 +1,55 @@
#!/bin/bash
# Cron wrapper for GitHub notifications
# 1. Auto-dismisses low-value notifications (nightlies, previews, empty releases)
# 2. Checks remaining notifications and formats for human consumption
set -e
WORKSPACE="${WORKSPACE:-/home/node/.openclaw/workspace}"
cd "$WORKSPACE"
# First: auto-dismiss low-value notifications
bash skills/github-notifications/scripts/auto-dismiss.sh >/dev/null 2>&1 || true
# Then: run the checker
RESULT=$(bash skills/github-notifications/scripts/check.sh)
# Check for errors
if echo "$RESULT" | jq -e '.error' > /dev/null 2>&1; then
ERROR_MSG=$(echo "$RESULT" | jq -r '.error')
ERROR_DETAILS=$(echo "$RESULT" | jq -r '.details')
echo "❌ **GitHub Check Failed**"
echo ""
echo "Error: $ERROR_MSG"
echo "\`\`\`"
echo "$ERROR_DETAILS" | head -20
echo "\`\`\`"
exit 0
fi
# Check if there's new activity
HAS_NEW=$(echo "$RESULT" | jq -r '.hasNew')
if [ "$HAS_NEW" != "true" ]; then
# Nothing new - stay completely silent (no output = no message)
exit 0
fi
# Format and output the summary
echo "🔔 **GitHub Activity Update**"
echo ""
# Process PRs
PR_COUNT=$(echo "$RESULT" | jq '.newPRs | length')
if [ "$PR_COUNT" -gt 0 ]; then
echo "**Pull Requests ($PR_COUNT new):**"
echo "$RESULT" | jq -r '.newPRs[] | "- **\(.repo)** #\(.title)\n Updated: \(.updated) | Reason: \(.reason)"'
echo ""
fi
# Process Releases
RELEASE_COUNT=$(echo "$RESULT" | jq '.newReleases | length')
if [ "$RELEASE_COUNT" -gt 0 ]; then
echo "**Releases ($RELEASE_COUNT new):**"
echo "$RESULT" | jq -r '.newReleases[] | "- **\(.repo)** `\(.title)`\n Released: \(.updated)"'
fi