UNPKG

webrtc-mcp-chat

Version:

A remote WebRTC chat server with secure temporary rooms and MCP support for background agents

321 lines (278 loc) 11.8 kB
#!/usr/bin/env node import { Command } from 'commander'; import fetch from 'node-fetch'; const program = new Command(); const CHAT_SERVER_URL = process.env.CHAT_SERVER_URL || process.env.REMOTE_CHAT_URL || 'http://localhost:3000'; // Helper function to create fetch headers with ngrok support function createHeaders(additionalHeaders = {}) { const headers = { 'Content-Type': 'application/json', ...additionalHeaders }; // Add ngrok header if URL contains ngrok if (CHAT_SERVER_URL.includes('ngrok') || CHAT_SERVER_URL.includes('ngrok-free.app')) { headers['ngrok-skip-browser-warning'] = 'true'; } return headers; } // Colors for console output const colors = { green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', reset: '\x1b[0m', bold: '\x1b[1m' }; function colorize(color, text) { return `${colors[color]}${text}${colors.reset}`; } program .name('chat-room') .description('CLI tool for secure temporary chat rooms') .version('2.0.2'); // Create temporary room command program .command('create') .description('Create a secure temporary chat room') .option('-e, --expires <minutes>', 'Room expiration in minutes (default: 60, max: 1440)', '60') .option('-c, --created-by <identifier>', 'Creator identifier', 'cli-agent') .option('-o, --output <format>', 'Output format: json, text, env', 'text') .action(async (options) => { try { const expiresInMinutes = Math.min(parseInt(options.expires), 1440); console.log(colorize('cyan', '🔐 Creating secure temporary chat room...')); const response = await fetch(`${CHAT_SERVER_URL}/api/create-temp-room`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ expiresInMinutes, createdBy: options.createdBy }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to create room'); } if (options.output === 'json') { console.log(JSON.stringify(result, null, 2)); } else if (options.output === 'env') { console.log(`export ROOM_ID="${result.roomId}"`); console.log(`export ROOM_TOKEN="${result.roomToken}"`); console.log(`export ROOM_URL="${result.joinUrl}"`); } else { console.log(colorize('green', '✅ Room created successfully!')); console.log(); console.log(colorize('bold', '🔐 Room Details:')); console.log(` ${colorize('cyan', 'Room ID:')} ${result.roomId}`); console.log(` ${colorize('cyan', 'Token:')} ${result.roomToken}`); console.log(` ${colorize('cyan', 'Expires:')} ${expiresInMinutes} minutes (${new Date(result.expiresAt).toLocaleString()})`); console.log(` ${colorize('cyan', 'Join URL:')} ${result.joinUrl}`); console.log(); console.log(colorize('yellow', '⚠️ Keep the token secure! Anyone with the room ID and token can join.')); console.log(); console.log(colorize('bold', '🚀 Quick commands:')); console.log(` ${colorize('green', 'Join:')} chat-room join ${result.roomId} ${result.roomToken} <username>`); console.log(` ${colorize('green', 'Send:')} chat-room send ${result.roomId} ${result.roomToken} <username> "<message>"`); console.log(` ${colorize('green', 'Info:')} chat-room info ${result.roomId} ${result.roomToken}`); } } catch (error) { console.error(colorize('red', `❌ Error: ${error.message}`)); process.exit(1); } }); // Join room command program .command('join') .description('Join a temporary chat room') .argument('<roomId>', 'Room ID') .argument('<roomToken>', 'Room token') .argument('<username>', 'Your username') .option('-m, --message <text>', 'Optional join message') .action(async (roomId, roomToken, username, options) => { try { console.log(colorize('cyan', `🚀 Joining room ${roomId} as ${username}...`)); const response = await fetch(`${CHAT_SERVER_URL}/mcp/join`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, roomToken, username, message: options.message || `${username} joined the chat` }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to join room'); } console.log(colorize('green', '✅ Successfully joined room!')); console.log(` ${colorize('cyan', 'Message:')} ${result.message}`); console.log(` ${colorize('cyan', 'Active users:')} ${result.activeUsers}`); if (result.expiresAt) { console.log(` ${colorize('yellow', 'Expires:')} ${new Date(result.expiresAt).toLocaleString()}`); } } catch (error) { console.error(colorize('red', `❌ Error: ${error.message}`)); process.exit(1); } }); // Send message command program .command('send') .description('Send a message to a temporary chat room') .argument('<roomId>', 'Room ID') .argument('<roomToken>', 'Room token') .argument('<username>', 'Your username') .argument('<message>', 'Message to send') .action(async (roomId, roomToken, username, message) => { try { const response = await fetch(`${CHAT_SERVER_URL}/mcp/message`, { method: 'POST', headers: createHeaders(), body: JSON.stringify({ roomId, roomToken, username, message }) }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to send message'); } console.log(colorize('green', '💬 Message sent!')); console.log(` ${colorize('cyan', 'Room:')} ${roomId}`); console.log(` ${colorize('cyan', 'Message:')} "${message}"`); console.log(` ${colorize('cyan', 'Active users:')} ${result.activeUsers}`); } catch (error) { console.error(colorize('red', `❌ Error: ${error.message}`)); process.exit(1); } }); // Room info command program .command('info') .description('Get information about a temporary chat room') .argument('<roomId>', 'Room ID') .argument('<roomToken>', 'Room token') .option('-o, --output <format>', 'Output format: json, text', 'text') .action(async (roomId, roomToken, options) => { try { const response = await fetch(`${CHAT_SERVER_URL}/api/temp-room/${roomId}?token=${roomToken}`, { headers: createHeaders() }); const result = await response.json(); if (!response.ok) { throw new Error(result.error || 'Failed to get room info'); } if (options.output === 'json') { console.log(JSON.stringify(result, null, 2)); } else { console.log(colorize('cyan', `🔐 Room ${roomId} Information:`)); console.log(); console.log(` ${colorize('bold', 'Active users:')} ${result.userCount}`); if (result.users && result.users.length > 0) { result.users.forEach(user => { const type = user.isMCP ? colorize('yellow', 'MCP') : colorize('blue', 'Web'); console.log(` • ${user.username} (${type})`); }); } else { console.log(` ${colorize('yellow', '• No users currently active')}`); } console.log(); if (result.expiresAt) { console.log(` ${colorize('cyan', 'Expires:')} ${new Date(result.expiresAt).toLocaleString()}`); } console.log(` ${colorize('cyan', 'Created by:')} ${result.createdBy || 'Unknown'}`); console.log(); console.log(colorize('yellow', '💡 Note: Web users have video/audio, MCP users have text chat')); } } catch (error) { console.error(colorize('red', `❌ Error: ${error.message}`)); process.exit(1); } }); // Health check command program .command('health') .description('Check the chat server status') .action(async () => { try { console.log(colorize('cyan', '🔍 Checking server health...')); const response = await fetch(`${CHAT_SERVER_URL}/health`, { headers: createHeaders() }); const result = await response.json(); console.log(colorize('green', `🟢 Server is ${result.status}`)); console.log(); console.log(` ${colorize('cyan', 'Server URL:')} ${result.serverUrl}`); console.log(` ${colorize('cyan', 'Remote mode:')} ${result.remoteMode}`); console.log(` ${colorize('cyan', 'Active rooms:')} ${result.activeRooms}`); console.log(` ${colorize('cyan', 'Temporary rooms:')} ${result.temporaryRooms}`); console.log(` ${colorize('cyan', 'Connected users:')} ${result.connectedUsers}`); console.log(); console.log(colorize('green', '✅ Server is ready for operations!')); } catch (error) { console.error(colorize('red', '🔴 Server health check failed')); console.error(` ${colorize('cyan', 'Server URL:')} ${CHAT_SERVER_URL}`); console.error(` ${colorize('red', 'Error:')} ${error.message}`); console.log(); console.log(colorize('yellow', '💡 Check:')); console.log(' 1. Server is running and accessible'); console.log(' 2. CHAT_SERVER_URL environment variable is correct'); console.log(' 3. Network connectivity to the server'); process.exit(1); } }); // Interactive mode command program .command('interactive') .alias('i') .description('Start interactive mode for background agents') .action(async () => { console.log(colorize('bold', '🤖 Background Agent Interactive Mode')); console.log(colorize('cyan', '='.repeat(40))); console.log(); try { // Check server health first await fetch(`${CHAT_SERVER_URL}/health`, { headers: createHeaders() }); console.log(colorize('green', '✅ Connected to chat server')); console.log(` ${colorize('cyan', 'Server:')} ${CHAT_SERVER_URL}`); console.log(); console.log(colorize('bold', '🚀 Quick start for agents:')); console.log(); console.log(colorize('yellow', '1. Create a secure room:')); console.log(` ${colorize('green', 'chat-room create --expires 120 --created-by agent-1')}`); console.log(); console.log(colorize('yellow', '2. Share room credentials with other agents')); console.log(); console.log(colorize('yellow', '3. Join and communicate:')); console.log(` ${colorize('green', 'chat-room join <roomId> <token> agent-2')}`); console.log(` ${colorize('green', 'chat-room send <roomId> <token> agent-2 "Hello from agent!"')}`); console.log(); console.log(colorize('yellow', '4. Room automatically expires and cleans up')); console.log(); console.log(colorize('cyan', '💡 Perfect for:')); console.log(' • Inter-agent coordination'); console.log(' • CI/CD notifications'); console.log(' • Temporary service communication'); console.log(' • Secure agent-to-agent messaging'); console.log(); console.log(colorize('bold', 'Environment Setup:')); console.log(` ${colorize('cyan', 'export CHAT_SERVER_URL=')}${CHAT_SERVER_URL}`); } catch (error) { console.error(colorize('red', '❌ Cannot connect to chat server')); console.error(` ${colorize('cyan', 'URL:')} ${CHAT_SERVER_URL}`); console.error(` ${colorize('red', 'Error:')} ${error.message}`); process.exit(1); } }); // Error handling program.configureOutput({ writeErr: (str) => process.stderr.write(colorize('red', str)) }); program.parse();