/**
* 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;