|
| 1 | +# .github/workflows/connector-slack.yml |
| 2 | +# Slack connector — posts to Slack on all important git events |
| 3 | +# Set org secret: SLACK_WEBHOOK_URL (Incoming Webhook URL) |
| 4 | + |
| 5 | +name: "Connector: Slack" |
| 6 | + |
| 7 | +on: |
| 8 | + push: |
| 9 | + branches: [main] |
| 10 | + pull_request: |
| 11 | + types: [opened, closed, merged] |
| 12 | + issues: |
| 13 | + types: [opened, closed] |
| 14 | + release: |
| 15 | + types: [published] |
| 16 | + workflow_run: |
| 17 | + workflows: ["Agent: Org Health Monitor", "Agent: Repo Improver"] |
| 18 | + types: [completed] |
| 19 | + schedule: |
| 20 | + - cron: '0 14 * * 1-5' # Weekdays 2pm UTC — daily digest |
| 21 | + |
| 22 | +permissions: |
| 23 | + contents: read |
| 24 | + issues: read |
| 25 | + pull-requests: read |
| 26 | + |
| 27 | +jobs: |
| 28 | + notify: |
| 29 | + name: "Slack Notify" |
| 30 | + runs-on: ubuntu-latest |
| 31 | + if: vars.SLACK_ENABLED == 'true' || secrets.SLACK_WEBHOOK_URL != '' |
| 32 | + |
| 33 | + steps: |
| 34 | + - name: Build and send Slack message |
| 35 | + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 |
| 36 | + env: |
| 37 | + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} |
| 38 | + with: |
| 39 | + script: | |
| 40 | + const webhook = process.env.SLACK_WEBHOOK; |
| 41 | + if (!webhook) { console.log('No SLACK_WEBHOOK_URL set — skipping'); return; } |
| 42 | +
|
| 43 | + const event = context.eventName; |
| 44 | + const repo = `${context.repo.owner}/${context.repo.repo}`; |
| 45 | + const repoUrl = `https://github.com/${repo}`; |
| 46 | + let blocks = []; |
| 47 | +
|
| 48 | + if (event === 'push') { |
| 49 | + const commits = context.payload.commits || []; |
| 50 | + const branch = context.ref.replace('refs/heads/', ''); |
| 51 | + blocks = [{ |
| 52 | + type: 'section', |
| 53 | + text: { type: 'mrkdwn', text: `*Push to ${repo}* (${branch})\n${commits.map(c => `• <${c.url}|${c.message.split('\n')[0].substring(0, 80)}>`).join('\n')}` } |
| 54 | + }]; |
| 55 | + } |
| 56 | +
|
| 57 | + else if (event === 'pull_request') { |
| 58 | + const pr = context.payload.pull_request; |
| 59 | + const action = context.payload.action; |
| 60 | + const emoji = action === 'opened' ? '🔀' : action === 'closed' && pr.merged ? '✅' : '❌'; |
| 61 | + blocks = [{ |
| 62 | + type: 'section', |
| 63 | + text: { type: 'mrkdwn', text: `${emoji} *PR ${action}:* <${pr.html_url}|${pr.title}>\n${repo} • ${pr.additions}+ ${pr.deletions}- • ${pr.changed_files} files` } |
| 64 | + }]; |
| 65 | + } |
| 66 | +
|
| 67 | + else if (event === 'issues') { |
| 68 | + const issue = context.payload.issue; |
| 69 | + const emoji = context.payload.action === 'opened' ? '🆕' : '✅'; |
| 70 | + blocks = [{ |
| 71 | + type: 'section', |
| 72 | + text: { type: 'mrkdwn', text: `${emoji} *Issue ${context.payload.action}:* <${issue.html_url}|${issue.title}>\n${repo} • Labels: ${issue.labels.map(l => l.name).join(', ') || 'none'}` } |
| 73 | + }]; |
| 74 | + } |
| 75 | +
|
| 76 | + else if (event === 'release') { |
| 77 | + const release = context.payload.release; |
| 78 | + blocks = [{ |
| 79 | + type: 'section', |
| 80 | + text: { type: 'mrkdwn', text: `🚀 *Release published:* <${release.html_url}|${release.tag_name}>\n${repo} • ${release.name || release.tag_name}` } |
| 81 | + }]; |
| 82 | + } |
| 83 | +
|
| 84 | + else if (event === 'workflow_run') { |
| 85 | + const run = context.payload.workflow_run; |
| 86 | + const emoji = run.conclusion === 'success' ? '✅' : '❌'; |
| 87 | + blocks = [{ |
| 88 | + type: 'section', |
| 89 | + text: { type: 'mrkdwn', text: `${emoji} *Agent completed:* ${run.name}\n${repo} • ${run.conclusion} • <${run.html_url}|View run>` } |
| 90 | + }]; |
| 91 | + } |
| 92 | +
|
| 93 | + else if (event === 'schedule') { |
| 94 | + // Daily digest |
| 95 | + const { data: repos } = await github.rest.repos.listForOrg({ |
| 96 | + org: 'BlackRoad-OS-Inc', per_page: 5, sort: 'pushed', direction: 'desc' |
| 97 | + }); |
| 98 | + const { data: issues } = await github.rest.issues.listForRepo({ |
| 99 | + owner: context.repo.owner, repo: context.repo.repo, state: 'open', per_page: 5 |
| 100 | + }); |
| 101 | +
|
| 102 | + blocks = [ |
| 103 | + { type: 'header', text: { type: 'plain_text', text: '📊 BlackRoad Daily Digest' } }, |
| 104 | + { type: 'section', text: { type: 'mrkdwn', text: |
| 105 | + `*Recently active repos:*\n${repos.map(r => `• <${r.html_url}|${r.name}> — ${r.description?.substring(0, 60) || 'no description'}`).join('\n')}` |
| 106 | + }}, |
| 107 | + { type: 'section', text: { type: 'mrkdwn', text: |
| 108 | + `*Open issues (${issues.length}):*\n${issues.map(i => `• <${i.html_url}|${i.title}>`).join('\n') || 'None'}` |
| 109 | + }} |
| 110 | + ]; |
| 111 | + } |
| 112 | +
|
| 113 | + if (blocks.length === 0) return; |
| 114 | +
|
| 115 | + // Add footer |
| 116 | + blocks.push({ |
| 117 | + type: 'context', |
| 118 | + elements: [{ type: 'mrkdwn', text: `BlackRoad OS • <${repoUrl}|${repo}> • ${new Date().toISOString().split('T')[0]}` }] |
| 119 | + }); |
| 120 | +
|
| 121 | + const resp = await fetch(webhook, { |
| 122 | + method: 'POST', |
| 123 | + headers: { 'Content-Type': 'application/json' }, |
| 124 | + body: JSON.stringify({ blocks }) |
| 125 | + }); |
| 126 | + console.log(`Slack: ${resp.status}`); |
0 commit comments