#!/usr/bin/env node import { createHmac } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; const cwd = process.cwd(); function parseArgs(argv) { const out = {}; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (!token.startsWith('--')) continue; const key = token.slice(2); const next = argv[i + 1]; if (!next || next.startsWith('--')) { out[key] = 'true'; continue; } out[key] = next; i += 1; } return out; } function firstString(...values) { for (const value of values) { if (typeof value === 'string' && value.trim()) return value.trim(); } return null; } function readEnv(name) { if (!name) return null; const value = process.env[name]; return typeof value === 'string' && value.trim() ? value.trim() : null; } function normalizeRoute(entry, defaultWebhookPath) { return { agentSlug: firstString(entry.agentSlug, entry.preferredAgentSlug, entry.appSlug), appSlug: firstString(entry.appSlug), botName: firstString(entry.botName), appId: Number(firstString(String(entry.appId || '').trim(), readEnv(entry.appIdEnv))), webhookPath: firstString(entry.routePath, entry.webhookPath, defaultWebhookPath || '/github/webhook'), webhookSecret: firstString(entry.webhookSecret, readEnv(entry.webhookSecretEnv)), }; } async function main() { const args = parseArgs(process.argv.slice(2)); const configPath = path.resolve(cwd, args.config || process.env.SIN_GITHUB_APP_ROUTING_PATH || './config/github-app-routing.local.json'); const baseUrl = firstString(args['base-url'], process.env.SIN_GITHUB_ISSUES_PUBLIC_BASE_URL, 'http://127.0.0.1:46031'); const agent = firstString(args.agent, args.slug, args['app-slug']); if (!agent) throw new Error('missing_required_arg: --agent '); const raw = await readFile(configPath, 'utf8'); const config = JSON.parse(raw); const defaultWebhookPath = firstString(config.defaultWebhookPath, '/github/webhook'); const apps = Array.isArray(config.apps) ? config.apps.map((entry) => normalizeRoute(entry, defaultWebhookPath)) : []; const target = apps.find((entry) => entry.agentSlug === agent || entry.appSlug === agent); if (!target) throw new Error(`unknown_agent_route:${agent}`); if (!target.webhookSecret) throw new Error(`missing_webhook_secret:${agent}`); if (!Number.isFinite(target.appId)) throw new Error(`missing_app_id:${agent}`); const payload = JSON.stringify({ action: args.action || 'opened', installation: { id: Number(args['installation-id'] || 987654321), app_id: target.appId }, repository: { full_name: args.repo || 'OpenSIN-AI/OpenSIN-backend' }, }); const signature256 = `sha256=${createHmac('sha256', target.webhookSecret).update(payload).digest('hex')}`; const response = await fetch(`${baseUrl.replace(/\/+$/, '')}${target.webhookPath}`, { method: 'POST', headers: { 'content-type': 'application/json', 'x-hub-signature-256': signature256, 'x-github-event': args.event || 'pull_request', 'x-github-delivery': args.delivery || `smoke-${target.appSlug || target.agentSlug}`, }, body: payload, }); const text = await response.text(); console.log(JSON.stringify({ ok: response.ok, baseUrl, routePath: target.webhookPath, agentSlug: target.agentSlug, appSlug: target.appSlug, appId: target.appId, status: response.status, response: text ? JSON.parse(text) : null, }, null, 2)); if (!response.ok) process.exitCode = 1; } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });