/** * CEO Command Center — server.js * * Standalone HuggingFace Space for Apex (CEO Agent). * Handles: Telegram webhook, CEO commands, cron jobs, dashboard. * * Architecture: * - Telegram webhook → ceo_agent.start() / handleFlowStep() * - Crons: morning briefing (07:00 WIB), content reminder (20:00 WIB), * hosting check (09:00 WIB daily), heartpulse (every 6h) * - Dashboard: GET / → public/index.html * - Health: GET /health → 200 OK */ const express = require('express'); const path = require('path'); const cron = require('node-cron'); const ceoAgent = require('./skills/ceo_agent'); const app = express(); const PORT = process.env.PORT || 7860; const ADMIN_CHAT_ID = process.env.ADMIN_CHAT_ID || process.env.TELEGRAM_CHAT_ID; const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || ''; // ─── Middleware ────────────────────────────────────────────────────────────── app.use(express.json({ limit: '10mb' })); app.use(express.static(path.join(__dirname, 'public'))); // ─── Health Check ──────────────────────────────────────────────────────────── app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'CEO Command Center', agent: 'Apex', uptime: Math.floor(process.uptime()), timestamp: new Date().toISOString() }); }); // ─── Telegram Webhook ──────────────────────────────────────────────────────── app.post('/webhook', async (req, res) => { // Validate secret token if configured if (WEBHOOK_SECRET) { const providedSecret = req.headers['x-telegram-bot-api-secret-token']; if (providedSecret !== WEBHOOK_SECRET) { console.warn('[webhook] Rejected: invalid secret token'); return res.sendStatus(403); } } res.sendStatus(200); // Always ack immediately const update = req.body; try { // Handle callback queries (inline buttons) if (update.callback_query) { const cq = update.callback_query; const chatId = cq.message.chat.id; const data = cq.data; console.log(`[webhook] Callback query from ${chatId}: ${data}`); await ceoAgent.start(chatId, data); return; } // Handle text messages const msg = update.message || update.edited_message; if (!msg || !msg.text) return; const chatId = msg.chat.id; const text = msg.text.trim(); console.log(`[webhook] Message from ${chatId}: ${text.substring(0, 80)}`); // Security: only accept from admin chat if (ADMIN_CHAT_ID && String(chatId) !== String(ADMIN_CHAT_ID)) { console.warn(`[webhook] Rejected message from unknown chat: ${chatId}`); return; } // Check for pending multi-step flow const db = ceoAgent.readCeoDB(); const pending = db.pending_commands?.[chatId]; if (pending && !text.startsWith('/')) { await ceoAgent.handleFlowStep(chatId, text, pending); return; } // Route commands if (text.startsWith('/ceo') || text.startsWith('/apex')) { await ceoAgent.start(chatId, text.replace(/^\/apex/i, '/ceo')); return; } if (text === '/start') { const welcomeMsg = `🎯 CEO Command Center — Powered by Apex I'm your personal AI CEO advisor. I help you: 📋 Manage clients & pipeline 📅 Plan & execute content 📊 Track revenue goals 👁 Monitor AI visibility 💼 Run morning briefings 🔔 Watch hosting renewals Quick Commands: /ceo — Dashboard /ceo briefing — Morning briefing /ceo client list — View pipeline /ceo plan — Content plan /ceo goals — Revenue tracker /ceo visibility [brand] — AI audit /ceo pulse — System heartbeat Type /ceo for the full command list.`; const apiCaller = require('./skills/api_caller'); await apiCaller.sendTelegramMessage(chatId, welcomeMsg); return; } // Fallback: pass anything else to CEO agent as freeform if (!text.startsWith('/')) { await ceoAgent.start(chatId, '/ceo ' + text); } } catch (err) { console.error('[webhook] Unhandled error:', err.message); } }); // ─── Dashboard API ─────────────────────────────────────────────────────────── app.get('/api/dashboard', async (req, res) => { try { const db = ceoAgent.readCeoDB(); const vinosClient = require('./skills/vinos_client'); const vinosOnline = await vinosClient.isVinosOnline(); res.json({ success: true, db, vinosOnline, uptime: Math.floor(process.uptime()), timestamp: new Date().toISOString() }); } catch (e) { res.status(500).json({ success: false, error: e.message }); } }); // ─── Webhook Registration ──────────────────────────────────────────────────── async function registerWebhook() { if (!TELEGRAM_BOT_TOKEN) { console.log('[webhook] No TELEGRAM_BOT_TOKEN — skipping webhook registration'); return; } const SPACE_URL = process.env.SPACE_URL || process.env.CEO_SPACE_URL; if (!SPACE_URL) { console.log('[webhook] No SPACE_URL env var — skipping webhook registration'); return; } const webhookUrl = `${SPACE_URL}/webhook`; const https = require('https'); const dns = require('dns'); dns.setDefaultResultOrder('ipv4first'); const axios = require('axios'); const ipv4Agent = new https.Agent({ family: 4 }); const axiosIPv4 = axios.create({ httpsAgent: ipv4Agent, timeout: 15000 }); try { const body = { url: webhookUrl, allowed_updates: ['message', 'callback_query', 'edited_message'] }; if (WEBHOOK_SECRET) body.secret_token = WEBHOOK_SECRET; const res = await axiosIPv4.post( `https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook`, body ); if (res.data.ok) { console.log(`[webhook] Registered: ${webhookUrl}`); } else { console.error('[webhook] Registration failed:', res.data.description); } } catch (e) { console.error('[webhook] Registration error:', e.message); } } // ─── Cron Jobs ──────────────────────────────────────────────────────────────── // All times in UTC. WIB = UTC+7. function startCrons() { if (!ADMIN_CHAT_ID) { console.log('[cron] No ADMIN_CHAT_ID — crons will not send messages'); return; } // Morning briefing — 07:00 WIB = 00:00 UTC cron.schedule('0 0 * * *', async () => { console.log('[cron] Morning briefing...'); try { await ceoAgent.sendBriefing(ADMIN_CHAT_ID); } catch (e) { console.error('[cron] Briefing error:', e.message); } }, { timezone: 'UTC' }); // Content reminder — 20:00 WIB = 13:00 UTC cron.schedule('0 13 * * *', async () => { console.log('[cron] Content reminder...'); try { await ceoAgent.contentReminder(ADMIN_CHAT_ID); } catch (e) { console.error('[cron] Content reminder error:', e.message); } }, { timezone: 'UTC' }); // Hosting due date check — 09:00 WIB = 02:00 UTC cron.schedule('0 2 * * *', async () => { console.log('[cron] Hosting due dates check...'); try { await ceoAgent.checkHostingDueDates(ADMIN_CHAT_ID); } catch (e) { console.error('[cron] Hosting check error:', e.message); } }, { timezone: 'UTC' }); // Heartpulse — every 6 hours cron.schedule('0 */6 * * *', async () => { console.log('[cron] Heartpulse check...'); try { await ceoAgent.systemHeartpulse(ADMIN_CHAT_ID); } catch (e) { console.error('[cron] Heartpulse error:', e.message); } }, { timezone: 'UTC' }); console.log('[cron] All jobs scheduled'); } // ─── Start ──────────────────────────────────────────────────────────────────── app.listen(PORT, '0.0.0.0', async () => { console.log(`[CEO Command Center] Running on port ${PORT}`); console.log(`[CEO Command Center] Agent: Apex`); console.log(`[CEO Command Center] Admin chat: ${ADMIN_CHAT_ID || 'NOT SET'}`); await registerWebhook(); startCrons(); }); module.exports = app;