Spaces:
Running
Running
| /** | |
| * 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 = `π― <b>CEO Command Center</b> β 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 | |
| <b>Quick Commands:</b> | |
| /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 | |
| <i>Type /ceo for the full command list.</i>`; | |
| 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; | |