overmind-mcp
Version:
Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour
182 lines (154 loc) • 7.58 kB
JavaScript
/**
* ═══════════════════════════════════════════════════════════════════════════════
* OVERMIND — Ngrok Webhook Tunnel
* ═══════════════════════════════════════════════════════════════════════════════
*
* Démarre un tunnel ngrok vers le bridge Overmind pour recevoir les webhooks
* entrants (Telegram, VoIP.ms, Twilio, Discord, generic).
*
* Usage:
* node scripts/ngrok-webhook.mjs # default: bridge :3100
* node scripts/ngrok-webhook.mjs --port 3149 # custom port (bt-sms)
* node scripts/ngrok-webhook.mjs --port 3001 --region eu # sniperbot
* node scripts/ngrok-webhook.mjs --provider telegram # affiche l'URL Telegram
*
* Prérequis:
* npm install -g ngrok (ou snap install ngrok)
* ngrok authtoken <TOKEN> (une seule fois)
*
* Le script:
* 1. Démarre ngrok sur le port spécifié
* 2. Récupère l'URL publique
* 3. Affiche les URLs de webhook pour chaque provider
* 4. Sur Ctrl+C: ferme le tunnel proprement
* ═══════════════════════════════════════════════════════════════════════════════
*/
import { spawn } from 'child_process';
import { createInterface } from 'readline';
// ─── Parse args ─────────────────────────────────────────────────────────────
const args = process.argv.slice(2);
function getFlag(name, defaultValue) {
const idx = args.indexOf(`--${name}`);
if (idx !== -1 && args[idx + 1]) return args[idx + 1];
return defaultValue;
}
const PORT = parseInt(getFlag('port', '3100'), 10);
const REGION = getFlag('region', '');
const PROVIDER = getFlag('provider', '');
const SUBDOMAIN = getFlag('subdomain', '');
// ─── Provider webhook paths ──────────────────────────────────────────────────
const PROVIDER_PATHS = {
telegram: '/webhook/telegram',
voipms: '/webhook/voipms',
twilio: '/webhook/twilio',
discord: '/webhook/discord',
generic: '/webhook/generic',
};
// ─── Colors ──────────────────────────────────────────────────────────────────
const C = {
cyan: '\x1b[36m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
dim: '\x1b[2m',
bold: '\x1b[1m',
reset: '\x1b[0m',
};
function banner(text) { console.log(`${C.bold}${C.cyan}══════════════════════════════════════════════════════${C.reset}`); console.log(`${C.bold}${C.cyan} ${text}${C.reset}`); console.log(`${C.bold}${C.cyan}══════════════════════════════════════════════════════${C.reset}\n`); }
// ─── Main ────────────────────────────────────────────────────────────────────
banner('🚀 Overmind Ngrok Webhook Tunnel');
// Build ngrok args
const ngrokArgs = ['http', PORT.toString()];
if (REGION) ngrokArgs.push('--region', REGION);
if (SUBDOMAIN) ngrokArgs.push('--domain', SUBDOMAIN);
console.log(`${C.dim}Starting ngrok:${C.reset} ngrok ${ngrokArgs.join(' ')}\n`);
const ngrok = spawn('ngrok', ngrokArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
let publicUrl = '';
let gotUrl = false;
const stdout = createInterface({ input: ngrok.stdout });
const stderr = createInterface({ input: ngrok.stderr });
stdout.on('line', (line) => {
// ngrok prints forwarding info on stderr in newer versions, but check both
const urlMatch = line.match(/url=(https:\/\/[^\s]+)/i) || line.match(/(https:\/\/[a-z0-9-]+\.ngrok[a-z.-]*)/i);
if (urlMatch && !gotUrl) {
publicUrl = urlMatch[1];
gotUrl = true;
printUrls();
}
if (line.trim()) console.log(`${C.dim}[ngrok] ${line}${C.reset}`);
});
stderr.on('line', (line) => {
const urlMatch = line.match(/url=(https:\/\/[^\s]+)/i) || line.match(/(https:\/\/[a-z0-9-]+\.ngrok[a-z.-]*)/i);
if (urlMatch && !gotUrl) {
publicUrl = urlMatch[1];
gotUrl = true;
printUrls();
}
if (line.trim()) console.error(`${C.red}[ngrok] ${line}${C.reset}`);
});
function printUrls() {
console.log(`\n${C.green}${C.bold}✅ Tunnel actif!${C.reset}\n`);
console.log(`${C.bold}Public URL:${C.reset} ${C.yellow}${publicUrl}${C.reset}\n`);
if (PROVIDER) {
// Single provider mode
const path = PROVIDER_PATHS[PROVIDER] || `/webhook/${PROVIDER}`;
console.log(`${C.bold}Webhook URL (${PROVIDER}):${C.reset}`);
console.log(` ${C.green}${publicUrl}${path}${C.reset}\n`);
printProviderConfig(PROVIDER, `${publicUrl}${path}`);
} else {
// All providers
console.log(`${C.bold}Webhook URLs:${C.reset}\n`);
for (const [name, path] of Object.entries(PROVIDER_PATHS)) {
console.log(` ${name.padEnd(12)} ${C.green}${publicUrl}${path}${C.reset}`);
}
console.log('');
for (const name of Object.keys(PROVIDER_PATHS)) {
printProviderConfig(name, `${publicUrl}${PROVIDER_PATHS[name]}`);
}
}
console.log(`${C.dim}Ctrl+C pour fermer le tunnel.${C.reset}\n`);
}
function printProviderConfig(provider, url) {
switch (provider) {
case 'telegram':
console.log(`${C.dim}Telegram: curl -s "https://api.telegram.org/bot<TOKEN>/setWebhook?url=${url}"${C.reset}`);
break;
case 'voipms':
console.log(`${C.dim}VoIP.ms: Configure SMS callback URL → ${url}${C.reset}`);
break;
case 'twilio':
console.log(`${C.dim}Twilio: Phone Numbers → Webhook → ${url}${C.reset}`);
break;
case 'discord':
console.log(`${C.dim}Discord: Application → Interactions Endpoint → ${url}${C.reset}`);
break;
case 'generic':
console.log(`${C.dim}Generic: POST any JSON to ${url}${C.reset}`);
break;
}
}
// ─── Cleanup ─────────────────────────────────────────────────────────────────
process.on('SIGINT', () => {
console.log(`\n${C.yellow}Closing tunnel...${C.reset}`);
ngrok.kill('SIGTERM');
process.exit(0);
});
process.on('SIGTERM', () => {
ngrok.kill('SIGTERM');
process.exit(0);
});
ngrok.on('exit', (code) => {
console.log(`${C.dim}ngrok exited (code ${code})${C.reset}`);
process.exit(code ?? 0);
});
// Timeout si ngrok ne démarre pas
setTimeout(() => {
if (!gotUrl) {
console.error(`\n${C.red}❌ ngrok n'a pas répondu après 15s.${C.reset}`);
console.error(`${C.dim}Vérifiez: ngrok authtoken <TOKEN>${C.reset}`);
console.error(`${C.dim}Ou installez: npm install -g ngrok${C.reset}`);
ngrok.kill('SIGTERM');
process.exit(1);
}
}, 15000);