Files
claude-hub/cli/webhook-cli.js
Jonathan Flatt fc567071dd Initial commit
2025-05-20 17:01:59 +00:00

151 lines
4.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* CLI tool to call the GitHub webhook endpoint
* Usage: ./webhook-cli.js --repo owner/repo --command "your command" [options]
*/
const axios = require('axios');
const crypto = require('crypto');
const { Command } = require('commander');
const dotenv = require('dotenv');
// Load environment variables
dotenv.config();
const program = new Command();
program
.name('webhook-cli')
.description('CLI to call the Claude GitHub webhook endpoint')
.version('1.0.0')
.requiredOption('-r, --repo <repo>', 'GitHub repository (format: owner/repo or repo)')
.requiredOption('-c, --command <command>', 'Command to send to Claude')
.option('-i, --issue <number>', 'Issue number', '1')
.option('-p, --pr', 'Treat as pull request instead of issue')
.option('-b, --branch <branch>', 'Branch name for PR (only used with --pr)')
.option('-u, --url <url>', 'API URL', process.env.API_URL || 'http://localhost:3003')
.option('-s, --secret <secret>', 'Webhook secret', process.env.GITHUB_WEBHOOK_SECRET)
.option('-t, --token <token>', 'GitHub token', process.env.GITHUB_TOKEN)
.option('-v, --verbose', 'Verbose output')
.parse(process.argv);
const options = program.opts();
// Handle repo format - if no owner specified, use default from env
let owner, repo;
if (options.repo.includes('/')) {
[owner, repo] = options.repo.split('/');
} else {
owner = process.env.DEFAULT_GITHUB_OWNER || 'default-owner';
repo = options.repo;
}
const fullRepoName = `${owner}/${repo}`;
// Create webhook payload
const payload = {
action: 'created',
repository: {
full_name: fullRepoName,
name: repo,
owner: {
login: owner
}
},
sender: {
login: process.env.DEFAULT_GITHUB_USER || owner
}
};
// Add issue or PR specific payload
if (options.pr) {
payload.pull_request = {
number: parseInt(options.issue),
body: `@${process.env.BOT_USERNAME || 'ClaudeBot'} ${options.command}`,
user: {
login: process.env.DEFAULT_GITHUB_USER || owner
},
head: {
ref: options.branch || process.env.DEFAULT_BRANCH || 'main'
}
};
} else {
payload.issue = {
number: parseInt(options.issue),
title: 'CLI Request',
body: 'Request from CLI'
};
payload.comment = {
id: Date.now(),
body: `@${process.env.BOT_USERNAME || 'ClaudeBot'} ${options.command}`,
user: {
login: process.env.DEFAULT_GITHUB_USER || owner
}
};
}
// Calculate webhook signature if secret is provided
function calculateSignature(payload, secret) {
const body = JSON.stringify(payload);
const hmac = crypto.createHmac('sha256', secret);
return 'sha256=' + hmac.update(body).digest('hex');
}
// Make the request
async function sendWebhook() {
try {
const headers = {
'Content-Type': 'application/json',
'X-GitHub-Event': options.pr ? 'pull_request' : 'issue_comment',
'X-GitHub-Delivery': 'cli-delivery-' + Date.now()
};
// Add signature if secret is provided
if (options.secret) {
headers['X-Hub-Signature-256'] = calculateSignature(payload, options.secret);
}
const url = `${options.url}/api/webhooks/github`;
if (options.verbose) {
console.log('Sending request to:', url);
console.log('Headers:', JSON.stringify(headers, null, 2));
console.log('Payload:', JSON.stringify(payload, null, 2));
}
const response = await axios.post(url, payload, { headers });
console.log('\n✅ Success!');
console.log('Status:', response.status);
if (response.data.claudeResponse) {
console.log('\n📝 Claude Response:');
console.log('-'.repeat(50));
console.log(response.data.claudeResponse);
console.log('-'.repeat(50));
}
if (response.data.context) {
console.log('\n📍 Context:');
console.log(JSON.stringify(response.data.context, null, 2));
}
} catch (error) {
console.error('\n❌ Error:', error.response ? error.response.data : error.message);
if (error.response && options.verbose) {
console.error('Full error response:', error.response.data);
}
process.exit(1);
}
}
// Run the CLI
console.log(`🚀 Sending command to Claude for ${fullRepoName}...`);
console.log(`📋 Command: ${options.command}`);
console.log(`${options.pr ? '🔀 Type: Pull Request' : '📄 Type: Issue'}`);
console.log();
sendWebhook();