### Install and Run Daemon - Bash
Source: https://context7.com/hinha/agent-pr/llms.txt
Provides instructions for setting up and running the PR monitor daemon. This includes installing Node.js dependencies, configuring environment variables, starting the daemon in the background using `nohup`, and managing its process ID for graceful stopping.
```bash
# Install dependencies
npm install
# Configure environment
cp .env.example .env
nano .env # Fill in required values
# Start daemon with nohup for background execution
nohup node index.js > logs/daemon.out 2>&1 < /dev/null &
echo $! > daemon.pid
# Stop the daemon
kill $(cat daemon.pid)
```
--------------------------------
### Environment Configuration Example
Source: https://context7.com/hinha/agent-pr/llms.txt
An example of the .env file used to configure the MCP-based PR Monitor Daemon. It includes settings for Telegram integration, MCP and OpenClaw agent paths, GitHub repository details, and operational parameters.
```javascript
// .env configuration example
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_from_botfather
TELEGRAM_CHAT_ID=0000000001
MCP_CONFIG_PATH=/home/ubuntu/.mcporter/mcporter.json
OPENCLAW_AGENT_SUMMARY=pr-summary-agent
OPENCLAW_AGENT_REVIEW=pr-review-agent
OPENCLAW_REVIEW_MODEL=openai/claude-sonnet-4.6
MCP_SERVER_NAME=github-work
GITHUB_OWNER=your-org
GITHUB_REPO=your-repo
CHECK_INTERVAL_MINUTES=30
SKIP_CACHE_DURATION_HOURS=3
LOG_LEVEL=info
```
--------------------------------
### Manage Processed PR State - JavaScript
Source: https://context7.com/hinha/agent-pr/llms.txt
Manages the state of processed Pull Requests (PRs) to prevent duplicate notifications. It provides methods to check if a PR has been processed, get its notification count, increment the count, and mark a PR as fully processed, persisting this state.
```javascript
// src/services/prStateManager.js
// Check if PR has been fully processed
async isProcessed(prId) {
return this.processedPRs.has(prId.toString());
}
// Get current notification count for a PR
getNotificationCount(prId) {
return this.notificationCount.get(prId.toString()) || 0;
}
// Increment notification counter (max 3 notifications per PR)
async incrementNotificationCount(prId) {
const prIdStr = prId.toString();
const current = this.getNotificationCount(prId);
const newCount = current + 1;
this.notificationCount.set(prIdStr, newCount);
await this.saveNotificationCounts();
return newCount;
}
// Mark PR as fully processed
async markProcessed(prId) {
const prIdStr = prId.toString();
if (!this.processedPRs.has(prIdStr)) {
this.processedPRs.add(prIdStr);
await this.saveProcessedPRs();
logger.info(`Marked PR #${prId} as fully processed`);
}
}
```
--------------------------------
### Manage Telegram Notifications and Interactive Actions
Source: https://context7.com/hinha/agent-pr/llms.txt
Handles Telegram bot initialization, sends formatted PR notifications with AI-generated summaries, and processes interactive inline button callbacks to perform GitHub PR operations like approval, rejection, or closure.
```javascript
constructor() {
this.bot = new TelegramBot(config.telegram.botToken, { polling: true });
this.chatId = config.telegram.chatId;
this.threadId = config.telegram.threadId;
this.setupButtonHandlers();
}
async sendPRNotification(pr, summary) {
return this.retryOperation(async () => {
const message = `π NEW PR DETECTED\nPR #${pr.number}: ${pr.title}\nπ€ Author: ${pr.author}\nπ URL: ${pr.url}\n\n---\nAI PR Summary\nπ Purpose: ${summary.purpose}\nπ·οΈ Type: ${summary.type}\nβ οΈ Risk Level: ${summary.riskLevel}\nπ― Impact Area: ${summary.impactArea}\nπ Diff Size: ${summary.diffSize}\nπ¨ Suspicious Patterns: ${summary.suspiciousPatterns?.length ? summary.suspiciousPatterns.join(', ') : 'None detected'}\nβ
Recommended Review: ${summary.recommendedReview}`;
const inlineKeyboard = {
inline_keyboard: [
[{ text: 'π Review Now', callback_data: `review_now:${pr.id}` }, { text: 'π Visit PR', callback_data: `visit:${pr.id}` }],
[{ text: 'β
Approve', callback_data: `approve:${pr.id}` }, { text: 'β Reject', callback_data: `reject:${pr.id}` }],
[{ text: 'π Close PR', callback_data: `close:${pr.id}` }, { text: 'βΈοΈ Skip (3h)', callback_data: `skip:${pr.id}` }]
]
};
await this.bot.sendMessage(this.chatId, message, {
reply_markup: inlineKeyboard,
disable_web_page_preview: true,
message_thread_id: this.threadId,
parse_mode: 'HTML'
});
}, config.retries.telegramRetries, 3000, config.retries.backoffFactor);
}
this.bot.on('callback_query', async (query) => {
const [action, prId] = query.data.split(':');
if (action === 'approve') {
await mcpService.callMCP('create_pull_request_review', {
owner: config.github.owner,
repo: config.github.repo,
pull_number: pr.number,
event: 'APPROVE',
body: 'Approved via OpenClaw PR Monitor'
});
await prStateManager.markProcessed(pr.id);
} else if (action === 'reject') {
await mcpService.callMCP('create_pull_request_review', {
owner: config.github.owner,
repo: config.github.repo,
pull_number: pr.number,
event: 'REQUEST_CHANGES',
body: 'Changes requested via OpenClaw PR Monitor.'
});
} else if (action === 'close') {
await mcpService.callMCP('update_pull_request', {
owner: config.github.owner,
repo: config.github.repo,
pull_number: pr.number,
state: 'closed'
});
} else if (action === 'skip') {
await skipManager.addSkip(parseInt(prId));
}
});
```
--------------------------------
### Invoke AI PR Analysis with OpenClawAgentService
Source: https://context7.com/hinha/agent-pr/llms.txt
Provides methods to generate structured PR summaries and perform deep code reviews by invoking OpenClaw CLI agents. These methods utilize retry logic to ensure robustness against transient network or execution failures.
```javascript
async generateSummary(pr, prDetails) {
return this.retryOperation(async () => {
const payload = JSON.stringify({ pr, prDetails });
const command = `openclaw agent invoke ${config.openclaw.summaryAgent} --message '${payload.replace(/'/g, "\\'")}' --timeout 90`;
const { stdout, stderr } = await execPromise(command);
return JSON.parse(stdout);
}, config.retries.agentRetries, 5000, config.retries.backoffFactor);
}
async runFullReview(pr) {
return this.retryOperation(async () => {
const fullReviewPrompt = `Review pull request #${pr.number} in repository.\n INSTRUCTIONS:\n 1. Compare code between source branch (${pr.headBranch}) and target branch (${pr.baseBranch})\n 2. Create HIGH LEVEL SUMMARY: main purpose, scale of changes, affected areas\n 3. Provide REVIEW COMMENTS: potential issues, best practices, positive points\n 4. ONLY GENERATE REVIEW COMMENTS - do not auto-approve\n\n PR Details:\n - Author: ${pr.author}\n - URL: ${pr.url}\n - Source branch: ${pr.headBranch}\n - Target branch: ${pr.baseBranch}`;
const command = `openclaw run --model github-copilot/claude-sonnet-4.6 --message '${fullReviewPrompt.replace(/'/g, "\\'")}' --timeout 120`;
const { stdout } = await execPromise(command);
return JSON.parse(stdout);
}, config.retries.agentRetries, 10000, config.retries.backoffFactor);
}
```
--------------------------------
### Run PR Check Cycle - JavaScript
Source: https://context7.com/hinha/agent-pr/llms.txt
Orchestrates the main PR monitoring cycle by fetching open PRs, filtering them based on processing status, age, and skip rules, and initiating background processing for eligible PRs. It relies on external services like mcpGithubService, prStateManager, and skipManager.
```javascript
// src/services/schedulerDaemon.js
// Main PR check cycle
async runPRCheckCycle() {
const openPRs = await mcpGithubService.getOpenPRs();
logger.info(`Found ${openPRs.length} total open PRs in repository`);
for (const pr of openPRs) {
// Skip if already processing, processed, or skipped
if (this.activeProcesses.has(pr.id.toString()) ||
await prStateManager.isProcessed(pr.id) ||
skipManager.isSkipped(pr.id)) {
continue;
}
// Skip PRs older than configured max age
const prAge = Date.now() - pr.createdAt.getTime();
if (prAge > config.scheduler.maxAgeMs) {
await prStateManager.markProcessed(pr.id);
continue;
}
// Start processing PR in background
this.processSinglePR(pr);
}
}
// Start the scheduler daemon
start() {
logger.info('β
MCP-based PR monitor daemon started');
this.runPRCheckCycle();
this.checkInterval = setInterval(() => this.runPRCheckCycle(), config.scheduler.checkIntervalMs);
}
// Gracefully stop the daemon
stop() {
if (this.checkInterval) clearInterval(this.checkInterval);
logger.info('Scheduler daemon stopped');
}
```
--------------------------------
### Configuration Object Structure
Source: https://context7.com/hinha/agent-pr/llms.txt
The JavaScript configuration object for the MCP-based PR Monitor Daemon, derived from environment variables. It structures settings for MCP, OpenClaw, GitHub, Telegram, scheduler intervals, and retry logic.
```javascript
const config = {
mcp: {
configPath: process.env.MCP_CONFIG_PATH,
serverName: process.env.MCP_SERVER_NAME || 'github-work',
baseCommand: 'mcporter'
},
openclaw: {
summaryAgent: process.env.OPENCLAW_AGENT_SUMMARY,
reviewAgent: process.env.OPENCLAW_AGENT_REVIEW,
reviewModel: process.env.OPENCLAW_REVIEW_MODEL || 'github-copilot/claude-sonnet-4.6'
},
github: {
owner: process.env.GITHUB_OWNER || 'git_owner',
repo: process.env.GITHUB_REPO || 'git_repo'
},
telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: parseInt(process.env.TELEGRAM_CHAT_ID),
threadId: parseInt(process.env.TELEGRAM_THREAD_ID)
},
scheduler: {
checkIntervalMs: 7 * 60 * 1000,
skipDurationMs: (parseInt(process.env.SKIP_CACHE_DURATION_HOURS) || 3) * 60 * 60 * 1000,
maxAgeMs: 24 * 60 * 60 * 1000
},
retries: {
mcpRetries: 3,
telegramRetries: 3,
agentRetries: 2,
backoffFactor: 2
}
};
```
--------------------------------
### MCPGitHubService - GitHub Interaction via MCP
Source: https://context7.com/hinha/agent-pr/llms.txt
The MCPGitHubService class in Node.js handles all GitHub API interactions using the `mcporter` CLI, avoiding direct REST API calls. It includes methods for executing MCP commands with retries, fetching open pull requests, and retrieving PR details.
```javascript
// src/services/mcpGithubService.js
// Execute MCP tool call with exponential backoff retries
async callMCP(method, args = {}) {
return this.retryOperation(async () => {
const argStrings = Object.entries(args)
.map(([key, value]) => {
if (typeof value === 'object') return `${key}:${JSON.stringify(value)}`;
return `${key}=${JSON.stringify(value)}`;
})
.join(' ');
const fullCommand = `mcporter call github-work.${method} ${argStrings} --output json`;
const { stdout, stderr } = await execPromise(fullCommand);
return JSON.parse(stdout);
}, config.retries.mcpRetries, 2000, config.retries.backoffFactor);
}
// Fetch all open pull requests
async getOpenPRs() {
const rawPRs = await this.callMCP('list_pull_requests', {
owner: this.owner,
repo: this.repo,
state: 'open',
per_page: 100,
page: 1
});
return rawPRs.map(pr => ({
id: pr.id,
number: pr.number,
title: pr.title,
url: pr.html_url,
author: pr.user?.login || 'unknown',
createdAt: new Date(pr.created_at),
description: pr.body || 'No description provided',
baseBranch: pr.base?.ref,
headBranch: pr.head?.ref
}));
}
// Get PR changed files and diff metadata
async getPRDetails(prNumber) {
const files = await this.callMCP('get_pull_request_files', {
owner: this.owner,
repo: this.repo,
pull_number: prNumber
});
return {
filesChanged: files.length,
files: files.map(f => ({
filename: f.filename,
additions: f.additions,
deletions: f.deletions,
changes: f.changes,
status: f.status
})),
totalChanges: files.reduce((sum, f) => sum + f.changes, 0)
};
}
```
--------------------------------
### Manage Skipped PRs - JavaScript
Source: https://context7.com/hinha/agent-pr/llms.txt
Handles temporary suppression of notifications for specific PRs. It allows adding a PR to a skip cache with an expiry duration and checking if a PR is currently skipped, including automatic cleanup of expired entries. This functionality is crucial for preventing alert fatigue.
```javascript
// src/services/skipManager.js
// Add a PR to skip cache for configured duration (default 3 hours)
async addSkip(prId) {
const expiry = new Date(Date.now() + config.scheduler.skipDurationMs);
const prIdStr = prId.toString();
this.skipCache.set(prIdStr, expiry.toISOString());
await this.saveSkipCache();
logger.info(`Added PR #${prId} to skip cache until ${expiry.toISOString()}`);
}
// Check if PR is currently skipped, auto-cleanup expired entries
isSkipped(prId) {
const prIdStr = prId.toString();
if (!this.skipCache.has(prIdStr)) return false;
const expiry = new Date(this.skipCache.get(prIdStr));
if (new Date() > expiry) {
this.skipCache.delete(prIdStr);
this.saveSkipCache();
return false;
}
return true;
}
```
=== COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.