vinos-engine / server.js
VinOS Agent
feat(conversation): multi-modal response router β€” text, voice, and image
9a45931
require('dotenv').config();
const zernioPoster = require('./zernio-poster');
const postProxyPoster = require('./postproxy-poster');
const research = require('./research');
const aiContent = require('./ai-content');
const imageGen = require('./image-gen');
const scheduler = require('./scheduler');
const sheets = require('./sheets');
const dns = require('node:dns');
// HF Spaces DNS Bypass (M.O.A.B.)
dns.setDefaultResultOrder('ipv4first');
dns.setServers(['8.8.8.8', '1.1.1.1', '[2001:4860:4860::8888]']);
const originalLookup = dns.lookup;
dns.lookup = function(hostname, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
}
originalLookup(hostname, options, (err, address, family) => {
if (err && (err.code === 'ENOTFOUND' || err.code === 'EAI_AGAIN')) {
dns.resolve4(hostname, (err2, addresses) => {
if (err2 || !addresses || !addresses.length) return callback(err);
callback(null, addresses[0], 4);
});
} else {
callback(err, address, family);
}
});
};
const express = require('express');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const memory = require('./skills/memory');
const apiCaller = require('./skills/api_caller');
const playbookManager = require('./skills/playbook_manager');
const conversationMemory = require('./skills/conversation_memory');
const setTelegramMenu = require('./skills/set_telegram_menu');
const reportGen = require('./skills/report_generator');
const hfStorage = require('./skills/hf_storage');
const trelloManager = require('./skills/trello_manager');
const hfDeployer = require('./skills/hf_deployer');
const skillCreator = require('./skills/skill_creator');
const seoWriter = require('./skills/seo_writer');
const wordpressPublisher = require('./skills/wordpress_publisher');
const googleIndexer = require('./skills/google_indexer');
const reportFlow = require('./skills/report_flow');
const carouselFlow = require('./skills/carousel_flow');
const sentimentAgent = require('./skills/sentiment_agent');
const competitorResearch = require('./competitor-research');
const analyticsEngine = require('./analytics-engine');
const viralFlow = require('./skills/viral_flow');
const salesEngine = require('./skills/sales_engine');
const mayarClient = require('./skills/mayar_client');
const costTracker = require('./skills/cost_tracker');
const scoutAgent = require('./skills/scout_agent');
const videoEngine = require('./skills/video_engine');
const quantaBridge = require('./skills/quanta_bridge');
const ceoAgent = require('./skills/ceo_agent');
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.get('/', (req, res) => {
res.status(200).send('VinOS Hugging Face Cloud Engine is RUNNING natively!');
});
// ============================================
// BULK INDEXING DASHBOARD API
// ============================================
app.get('/indexer', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'indexer.html'));
});
app.get('/viral', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'viral-dashboard.html'));
});
app.get('/revenue', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'revenue-dashboard.html'));
});
app.get('/landing', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'landing.html'));
});
app.get('/api/landing', (req, res) => {
try {
const dash = salesEngine.getDashboardData();
const activeOffers = dash.activeOffers.map(o => ({
title: o.title, description: o.description || '',
priceIDR: o.priceIDR, paymentUrl: o.paymentUrl, pillar: o.pillar || ''
}));
res.json({ success: true, offers: activeOffers });
} catch (e) { res.json({ success: true, offers: [] }); }
});
// ============================================
// QUANTA BRIDGE API (for QUANTA to pull data)
// ============================================
app.get('/api/scout/:keyword', async (req, res) => {
try {
const result = await scoutAgent.runScout(req.params.keyword);
res.json({ success: true, data: result });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// ============================================
// SKILL API β€” Authenticated endpoints for Space-to-Space calls
// Used by: CEO Command Center, future external Spaces
// ============================================
const { authMiddleware } = require('./skills/api_auth');
const clientCrm = require('./skills/client_crm');
const visibilityEngine = require('./skills/visibility_engine');
const researchEngine = require('./skills/research_engine');
const googleCalendar = require('./skills/google_calendar');
// --- CRM Skills ---
app.post('/api/skills/crm/list', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.getAllClients() }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/search', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.searchClients(req.body.query || '') }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/followups', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.getFollowups(req.body.daysOverdue || 0) }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/by-stage', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.getByStage(req.body.stage || 'lead') }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/add', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.addClient(req.body.data || {}) }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/update', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.updateClient(req.body.id, req.body.updates || {}) }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/format-detail', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.formatClientDetail(req.body.id) }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/crm/format-pipeline', authMiddleware, (req, res) => {
try { res.json({ success: true, data: clientCrm.formatPipeline() }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
// --- Visibility Skills ---
app.post('/api/skills/visibility/audit', authMiddleware, async (req, res) => {
try {
const result = await visibilityEngine.runVisibilityAudit(req.body.brand, req.body.options || {});
res.json({ success: true, data: result });
} catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/visibility/weekly', authMiddleware, async (req, res) => {
try {
const result = await visibilityEngine.weeklyBrandScan(req.body.brands || []);
res.json({ success: true, data: result });
} catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
// --- Research Skills ---
app.post('/api/skills/research/light', authMiddleware, async (req, res) => {
try {
const result = await researchEngine.runLightResearch(req.body.topic, req.body.focus);
res.json({ success: true, data: result });
} catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.post('/api/skills/research/visibility', authMiddleware, async (req, res) => {
try {
const result = await researchEngine.runVisibilityResearch(req.body.brand);
res.json({ success: true, data: result });
} catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
// --- Sales & Calendar Skills ---
app.get('/api/skills/sales/dashboard', authMiddleware, (req, res) => {
try { res.json({ success: true, data: salesEngine.getDashboardData() }); }
catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
app.get('/api/skills/calendar/events', authMiddleware, async (req, res) => {
try {
if (!googleCalendar.isConnected()) return res.json({ success: true, data: { connected: false, events: [] } });
const events = await googleCalendar.getUpcomingEvents(null, req.query.days || 7);
res.json({ success: true, data: { connected: true, events } });
} catch (e) { res.status(500).json({ success: false, error: e.message }); }
});
// ============================================
// CEO AGENT API & DASHBOARD (deprecated β€” migrating to AIgoose/ceo-command-center)
// ============================================
app.get('/ceo', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'ceo-dashboard.html'));
});
app.get('/api/ceo/briefing', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.briefing_history?.[0] || null });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/pipeline', (req, res) => {
try {
const crm = require('./skills/client_crm');
res.json({ success: true, data: crm.getAllClients() });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/calendar', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.content_calendar || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/goals', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.goals || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/visibility', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.visibility || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/funnel', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.digitalfinese_funnel || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/pulse', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.heartpulse || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/meetings', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.meetings || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/hosting', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.hosting_domains || {} });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/ceo/products', (req, res) => {
try {
const db = ceoAgent.readCeoDB();
res.json({ success: true, data: db.products || [] });
} catch (e) { res.json({ success: false, error: e.message }); }
});
// ============================================
// VIDEO ENGINE API (proxy to Remotion Studio)
// ============================================
app.get('/videos-dashboard', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'videos-dashboard.html'));
});
app.get('/api/videos/templates', async (req, res) => {
try {
const r = await apiCaller.axiosIPv4.get(`${process.env.REMOTION_URL || 'https://AIgoose-remotion-studio.hf.space'}/api/templates`, { timeout: 10000 });
res.json(r.data);
} catch (e) { res.json({ templates: [] }); }
});
app.post('/api/videos/create', async (req, res) => {
try {
const { topic, template, duration } = req.body;
// Generate script via video engine then submit
const chatId = 'web';
await videoEngine.createVideo(chatId, topic || 'AI Technology', template || 'text-explainer');
res.json({ success: true, message: 'Video queued. Check the job queue.' });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/videos/jobs', async (req, res) => {
try {
const r = await apiCaller.axiosIPv4.get(`${process.env.REMOTION_URL || 'https://AIgoose-remotion-studio.hf.space'}/api/jobs?limit=${req.query.limit || 20}`, { timeout: 10000 });
res.json(r.data);
} catch (e) { res.json({ jobs: [] }); }
});
app.get('/api/videos/download/:id', async (req, res) => {
try {
const remotionUrl = process.env.REMOTION_URL || 'https://AIgoose-remotion-studio.hf.space';
const r = await apiCaller.axiosIPv4.get(`${remotionUrl}/api/download/${req.params.id}`, { responseType: 'stream', timeout: 60000 });
res.set('Content-Type', 'video/mp4');
res.set('Content-Disposition', `attachment; filename="${req.params.id}.mp4"`);
r.data.pipe(res);
} catch (e) { res.status(404).json({ error: 'Video not available' }); }
});
// ============================================
// SALES ENGINE API
// ============================================
app.get('/api/sales/dashboard', (req, res) => {
try { res.json({ success: true, ...salesEngine.getDashboardData() }); }
catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/sales/offers', (req, res) => {
try {
const db = salesEngine.readSalesDB();
res.json({ success: true, offers: db.offers });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/sales/offers/create', async (req, res) => {
try {
const { topic, priceIDR } = req.body;
if (!topic) return res.json({ success: false, error: 'topic required' });
const result = await salesEngine.createOfferFromTopic(topic, priceIDR || 49000);
res.json(result);
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/sales/offers/:id/pause', (req, res) => {
try { res.json(salesEngine.pauseOffer(req.params.id)); }
catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/sales/offers/:id/retire', (req, res) => {
try { res.json(salesEngine.retireOffer(req.params.id)); }
catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/sales/webhook/mayar', async (req, res) => {
try {
const signature = req.headers['x-mayar-signature'] || req.headers['x-callback-token'] || '';
if (!mayarClient.verifyWebhook(req.body, signature)) {
console.log('[Webhook] Invalid Mayar signature');
return res.status(401).json({ error: 'Invalid signature' });
}
const data = req.body.data || req.body;
const webhookData = {
productId: data.productId || data.product_id || '',
amount: data.amount || data.total || 0,
email: data.email || data.customerEmail || data.customer_email || '',
transactionId: data.id || data.transactionId || data.transaction_id || ''
};
const result = await salesEngine.recordSale(webhookData);
res.json(result);
} catch (e) {
console.error('[Webhook] Mayar error:', e.message);
res.status(500).json({ success: false, error: e.message });
}
});
// Click tracking redirect (for A/B variant attribution)
app.get('/api/track', (req, res) => {
const { offer, variant, action } = req.query;
if (!offer) return res.status(400).json({ error: 'Missing offer param' });
const redirectUrl = salesEngine.trackClick(offer, variant || 'v0');
if (redirectUrl) {
const utm = `${redirectUrl.includes('?') ? '&' : '?'}utm_source=track&utm_medium=cta&utm_campaign=${encodeURIComponent(offer)}`;
res.redirect(302, redirectUrl + utm);
} else {
res.status(404).json({ error: 'Offer not found' });
}
});
// ============================================
// VIRAL CONTENT ENGINE API
// ============================================
app.get('/api/viral/brain', (req, res) => {
try { res.json({ success: true, brain: viralFlow.loadBrain() }); }
catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/viral/topics', (req, res) => {
try {
const limit = parseInt(req.query.limit) || 20;
res.json({ success: true, topics: viralFlow.loadJsonl(viralFlow.TOPICS_FILE, limit) });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/viral/angles', (req, res) => {
try {
const limit = parseInt(req.query.limit) || 10;
res.json({ success: true, angles: viralFlow.loadJsonl(viralFlow.ANGLES_FILE, limit) });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/viral/scripts', (req, res) => {
try {
const limit = parseInt(req.query.limit) || 10;
res.json({ success: true, scripts: viralFlow.loadJsonl(viralFlow.SCRIPTS_FILE, limit) });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/viral/discover', async (req, res) => {
try {
const { query } = req.body;
if (!query) return res.json({ success: false, error: 'query required' });
const brain = viralFlow.loadBrain();
if (!brain.icp) return res.json({ success: false, error: 'Brain not configured. Run /viral onboard in Telegram first.' });
const topics = await viralFlow.runDiscover(query, brain);
res.json({ success: true, topics });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/viral/angle', async (req, res) => {
try {
const { topic } = req.body;
if (!topic) return res.json({ success: false, error: 'topic required' });
const brain = viralFlow.loadBrain();
if (!brain.icp) return res.json({ success: false, error: 'Brain not configured. Run /viral onboard first.' });
const angles = await viralFlow.runAngle(topic, brain);
res.json({ success: true, angles });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/viral/script', async (req, res) => {
try {
const { topic, angle, format } = req.body;
if (!topic) return res.json({ success: false, error: 'topic required' });
const brain = viralFlow.loadBrain();
if (!brain.icp) return res.json({ success: false, error: 'Brain not configured. Run /viral onboard first.' });
const script = await viralFlow.runScript(topic, angle || 'Contrast Formula', format || 'shortform', brain);
res.json({ success: true, script });
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.post('/api/viral/publish-to-social', async (req, res) => {
try {
const { scriptId } = req.body;
if (!scriptId) return res.json({ success: false, error: 'scriptId required' });
const scripts = viralFlow.loadJsonl(viralFlow.SCRIPTS_FILE, 200);
const script = scripts.find(s => s.id === scriptId);
if (!script) return res.json({ success: false, error: 'Script not found' });
const draft = await scheduler.runInteractiveCycle(
{ type: 'topic', value: `${script.topic}: ${script.best_hook || ''}`.trim(), tier: 'fast' },
{ funnelStage: 'TOFU', language: 'both', coreMessage: (script.script || '').substring(0, 500) }
);
res.json(draft);
} catch (e) { res.json({ success: false, error: e.message }); }
});
app.get('/api/sites', (req, res) => {
try {
const sites = JSON.parse(process.env.WP_SITES_JSON || '{}');
res.json({ success: true, sites: Object.keys(sites) });
} catch (e) {
res.json({ success: false, error: 'WP_SITES_JSON not configured' });
}
});
app.get('/api/recent-posts', async (req, res) => {
const { siteId } = req.query;
try {
const sites = JSON.parse(process.env.WP_SITES_JSON || '{}');
const site = sites[siteId];
if (!site) return res.json({ success: false, error: 'Unknown siteId' });
const authBuffer = Buffer.from(`${site.user}:${site.pass}`).toString('base64');
const axios = require('axios');
const response = await axios.get(`${site.url}/wp-json/wp/v2/posts?per_page=20&orderby=date&order=desc&_fields=link,title,date,status`, {
headers: { 'Authorization': `Basic ${authBuffer}` }
});
const posts = response.data.map(p => ({ url: p.link, title: p.title.rendered, date: p.date }));
res.json({ success: true, posts });
} catch (e) {
res.json({ success: false, error: e.message });
}
});
app.get('/api/index-status', async (req, res) => {
const { url } = req.query;
try {
const result = await googleIndexer.getStatus(url);
res.json(result);
} catch (e) {
res.json({ success: false, status: 'UNKNOWN', error: e.message });
}
});
const DAILY_QUOTA = 200;
const QUOTA_DELAY_MS = 2000;
let todayQuotaDate = new Date().toDateString();
let todayQuotaUsed = 0;
app.post('/api/bulk-index', async (req, res) => {
const { urls } = req.body;
if (!urls || !Array.isArray(urls)) return res.json({ success: false, error: 'Invalid payload' });
// Reset quota counter on new day
if (new Date().toDateString() !== todayQuotaDate) {
todayQuotaDate = new Date().toDateString();
todayQuotaUsed = 0;
}
const remaining = DAILY_QUOTA - todayQuotaUsed;
const toProcess = urls.slice(0, remaining);
const skipped = urls.length - toProcess.length;
const results = [];
for (const url of toProcess) {
const r = await googleIndexer.submitUrl(url);
todayQuotaUsed++;
results.push({ url, success: r.success, error: r.error || null });
await new Promise(resolve => setTimeout(resolve, QUOTA_DELAY_MS));
}
res.json({ success: true, processed: toProcess.length, skipped, quotaUsed: todayQuotaUsed, results });
});
app.get('/api/quota', (req, res) => {
if (new Date().toDateString() !== todayQuotaDate) { todayQuotaDate = new Date().toDateString(); todayQuotaUsed = 0; }
res.json({ used: todayQuotaUsed, total: DAILY_QUOTA, remaining: DAILY_QUOTA - todayQuotaUsed });
});
// ============================================
// SOCIAL AUTOPILOT API
// ============================================
app.get('/social', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'social-dashboard.html'));
});
app.get('/api/social/status', async (req, res) => {
try {
const result = await zernioPoster.listAccounts();
const db = scheduler.config;
res.json({
success: true,
accounts: result.accounts || [],
autopilot: db.autopilot,
postCount: db.posts.length,
quota: db.monthly_quota
});
} catch (e) {
res.json({ success: false, error: e.message });
}
});
app.get('/api/social/posts', (req, res) => {
res.json(scheduler.config.posts);
});
app.post('/api/social/research', async (req, res) => {
const { url, input, inputType, tier } = req.body;
// Support both legacy { url } and new { input, inputType, tier } formats
if (inputType === 'topic' && input) {
const draft = await scheduler.runContentCycle({ type: 'topic', value: input, tier: tier || 'fast' });
return res.json({ success: !!draft, draft });
}
const targetUrl = url || input;
if (!targetUrl) return res.json({ success: false, error: 'URL or topic required' });
const draft = await scheduler.runContentCycle({ type: 'url', value: targetUrl, tier: tier || 'fast' });
res.json({ success: !!draft, draft });
});
app.post('/api/social/approve', async (req, res) => {
const { postId, variant, scheduleTime, revisedText, platform } = req.body;
if (revisedText) {
const p = scheduler.config.posts.find(x => x.id === postId);
if (p) {
const v = variant || 'v1';
if (platform && p[v]) {
const fieldMap = { instagram: 'ig_id', threads: 'threads_id', twitter: 'x_id', linkedin: 'linkedin_id', facebook: 'fb_id', pinterest: 'pinterest_id' };
const field = fieldMap[platform];
if (field) p[v][field] = revisedText;
} else if (p[v]) {
['ig_id', 'ig_en', 'x_id', 'threads_id', 'fb_id', 'pinterest_id', 'linkedin_id'].forEach(f => { if (p[v]) p[v][f] = revisedText; });
}
scheduler.saveDB();
}
}
const result = await scheduler.approveAndPost(postId, variant || 'v1', scheduleTime);
res.json(result);
});
app.delete('/api/social/delete/:postId', async (req, res) => {
const { postId } = req.params;
scheduler.config.posts = scheduler.config.posts.filter(p => p.id !== postId);
scheduler.saveDB();
res.json({ success: true });
});
app.post('/api/social/brainstorm', async (req, res) => {
const { topic } = req.body;
const result = await scheduler.brainstorm(topic);
res.json(result);
});
// Interactive pipeline: generate with TOFU/MOFU/BOFU + language + core message
app.post('/api/social/generate', async (req, res) => {
const { input, inputType, tier, funnelStage, language, coreMessage, slideCount } = req.body;
if (!input) return res.json({ success: false, error: 'Input required' });
const result = await scheduler.runInteractiveCycle(
{ type: inputType || 'topic', value: input, tier: tier || 'fast' },
{ funnelStage: funnelStage || 'TOFU', language: language || 'both', coreMessage: coreMessage || '', slideCount: slideCount || 7 }
);
res.json(result);
});
app.post('/api/social/autopilot', (req, res) => {
const { enabled } = req.body;
scheduler.config.autopilot = !!enabled;
scheduler.saveDB();
res.json({ success: true, autopilot: !!enabled });
});
app.get('/api/social/connect/:platform', async (req, res) => {
const { platform } = req.params;
const postProxyPlatforms = ['twitter', 'linkedin', 'x'];
// LinkedIn and X are handled by PostProxy (manual connection via their dashboard)
if (postProxyPlatforms.includes(platform.toLowerCase())) {
const dashUrl = process.env.POSTPROXY_DASHBOARD_URL || 'https://app.postproxy.io/accounts';
return res.json({ success: true, authUrl: dashUrl, provider: 'postproxy', platform });
}
// Zernio: IG, Threads, Facebook, Pinterest
try {
const profileRes = await zernioPoster.getProfile();
if (!profileRes.success) return res.json({ success: false, error: 'No Zernio profile found. Ensure ZERNIO_API_KEY is set.' });
const urlRes = await zernioPoster.getConnectUrl(platform, profileRes.profile._id);
res.json(urlRes);
} catch (e) {
res.json({ success: false, error: e.message });
}
});
app.get('/api/social/init-sheets', async (req, res) => {
const r = await sheets.initializeSheet();
res.json(r);
});
app.get('/api/social/postproxy-profiles', async (req, res) => {
const r = await postProxyPoster.getProfiles();
res.json(r);
});
// ============================================
// SOCIAL AUTOPILOT β€” ENHANCED API ENDPOINTS
// ============================================
// Social settings (model, pulse config)
app.get('/api/social/settings', (req, res) => {
const db = memory.readDB();
const currentModel = db.user_profile_snapshot?.social_content_model || apiCaller.SOCIAL_MODELS.FREE;
const tierMap = {
[apiCaller.SOCIAL_MODELS.PREMIUM]: { tier: 'premium', name: 'Claude Haiku 4.5', cost: '$1.00/$5.00' },
[apiCaller.SOCIAL_MODELS.STANDARD]: { tier: 'standard', name: 'Gemini 2.5 Flash Lite', cost: '$0.10/$0.40' },
[apiCaller.SOCIAL_MODELS.FREE]: { tier: 'free', name: 'Llama 3.3 70B', cost: 'FREE' }
};
const info = tierMap[currentModel] || tierMap[apiCaller.SOCIAL_MODELS.FREE];
res.json({ success: true, model: { id: currentModel, ...info }, pulseConfig: db.pulse_config || null });
});
app.post('/api/social/settings', (req, res) => {
const { socialModel, pulseConfig } = req.body;
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
if (socialModel) {
const valid = [apiCaller.SOCIAL_MODELS.PREMIUM, apiCaller.SOCIAL_MODELS.STANDARD, apiCaller.SOCIAL_MODELS.FREE];
if (valid.includes(socialModel)) {
db.user_profile_snapshot.social_content_model = socialModel;
}
}
if (pulseConfig) {
db.pulse_config = { ...(db.pulse_config || {}), ...pulseConfig };
}
memory.writeDB(db);
res.json({ success: true });
});
// Per-channel quota status
app.get('/api/social/quota', (req, res) => {
res.json({ success: true, quota: scheduler.config.monthly_quota });
});
// Account stats (Zernio + PostProxy combined)
app.get('/api/social/account-stats', async (req, res) => {
try {
const [zernioRes, ppRes] = await Promise.allSettled([
zernioPoster.listAccounts(),
postProxyPoster.getProfiles()
]);
const accounts = [];
if (zernioRes.status === 'fulfilled' && zernioRes.value.success) {
for (const a of zernioRes.value.accounts || []) {
accounts.push({ platform: a.platform, username: a.username, provider: 'zernio', id: a._id, ...a });
}
}
if (ppRes.status === 'fulfilled' && ppRes.value.success) {
for (const p of ppRes.value.profiles || []) {
accounts.push({ platform: p.platform, username: p.name || p.id, provider: 'postproxy', id: p.id, ...p });
}
}
res.json({ success: true, accounts });
} catch (e) { res.json({ success: false, error: e.message }); }
});
// Recent posts performance
app.get('/api/social/analytics/recent/:count', (req, res) => {
const count = Math.min(parseInt(req.params.count) || 12, 50);
res.json({ success: true, posts: scheduler.getRecentPerformance(count) });
});
// Manual analytics sync
app.post('/api/social/analytics/sync', async (req, res) => {
try {
await scheduler.syncStatusAndAnalytics();
res.json({ success: true, message: 'Analytics synced' });
} catch (e) { res.json({ success: false, error: e.message }); }
});
// Per-channel posting
app.post('/api/social/pipeline/post/:postId', async (req, res) => {
const { postId } = req.params;
const { channels, variant, scheduleTime } = req.body;
if (!channels || !Array.isArray(channels) || channels.length === 0) {
return res.json({ success: false, error: 'channels[] required' });
}
const result = await scheduler.postToChannel(postId, channels, variant || 'v1', scheduleTime);
res.json(result);
});
// MiroFish sentiment pre-flight for post titles
app.post('/api/social/pipeline/sentiment/:postId', async (req, res) => {
try {
const post = scheduler.config.posts.find(p => p.id === req.params.postId);
if (!post) return res.json({ success: false, error: 'Post not found' });
const { variant } = req.body;
const v = post[variant || 'v1'];
if (!v) return res.json({ success: false, error: 'Variant not found' });
const titles = [v.title, v.ig_en?.substring(0, 125), v.threads_en?.substring(0, 200)].filter(Boolean);
const result = await sentimentAgent.quickPostPreFlight(titles);
// Store sentiment results on post
post.sentiment = result;
scheduler.saveDB();
res.json({ success: true, sentiment: result });
} catch (e) { res.json({ success: false, error: e.message }); }
});
// Competitor research
app.post('/api/social/competitor-research', async (req, res) => {
const { url, topic, count } = req.body;
const result = await competitorResearch.analyzeCompetitors({ url, topic, count: count || 10 });
res.json(result);
});
app.get('/api/social/competitor-insights', (req, res) => {
const insights = competitorResearch.getCachedInsights();
res.json({ success: true, insights });
});
// Analytics engine
app.get('/api/social/analytics/dashboard', (req, res) => {
res.json({ success: true, ...analyticsEngine.getDashboardData() });
});
app.get('/api/social/analytics/insights', (req, res) => {
res.json({ success: true, insights: analyticsEngine.getInsightsForAI() });
});
app.post('/api/social/analytics/pull', async (req, res) => {
const result = await analyticsEngine.pullDailyAnalytics();
res.json(result);
});
// Download post media as zip
app.get('/api/social/download/:postId/zip', async (req, res) => {
try {
const post = scheduler.config.posts.find(p => p.id === req.params.postId);
if (!post) return res.status(404).json({ success: false, error: 'Post not found' });
const archiver = require('archiver');
const archive = archiver('zip', { zlib: { level: 5 } });
res.attachment(`${post.id}_media.zip`);
archive.pipe(res);
// Collect media URLs and add as remote files
const mediaUrls = [];
if (post.type === 'carousel' && post.mediaUrls) {
mediaUrls.push(...post.mediaUrls);
} else if (post.v1?.mediaUrl) {
mediaUrls.push(post.v1.mediaUrl);
}
// Add content text files for each platform
const variants = ['v1', 'v2', 'v3'];
for (const vk of variants) {
const v = post[vk];
if (!v) continue;
let content = `Title: ${v.title || ''}\n\n`;
content += `--- Instagram ---\n${v.ig_id || v.ig_en || ''}\n\n`;
content += `--- Threads ---\n${v.threads_id || v.threads_en || ''}\n\n`;
content += `--- Twitter/X ---\n${v.x_id || v.x_en || ''}\n\n`;
content += `--- LinkedIn ---\n${v.linkedin_id || v.linkedin_en || ''}\n\n`;
content += `--- Facebook ---\n${v.fb_id || v.fb_en || ''}\n\n`;
content += `--- Pinterest ---\n${v.pinterest_id || v.pinterest_en || ''}\n`;
if (v.hashtags) content += `\n--- Hashtags ---\n${v.hashtags.join(' ')}\n`;
archive.append(content, { name: `${vk}_content.txt` });
}
// For media, add URLs as a reference file (can't stream remote URLs in HF easily)
if (mediaUrls.length > 0) {
archive.append(mediaUrls.join('\n'), { name: 'media_urls.txt' });
}
await archive.finalize();
} catch (e) {
if (!res.headersSent) res.status(500).json({ success: false, error: e.message });
}
});
// Download post as branded text PDF
app.get('/api/social/download/:postId/pdf', (req, res) => {
try {
const post = scheduler.config.posts.find(p => p.id === req.params.postId);
if (!post) return res.status(404).json({ success: false, error: 'Post not found' });
const PDFDocument = require('pdfkit');
const doc = new PDFDocument({ size: 'A4', margin: 50 });
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${post.id}_content.pdf"`);
doc.pipe(res);
// Brand header
doc.rect(0, 0, doc.page.width, 80).fill('#071330');
doc.fontSize(24).fillColor('#f7cb2d').text('@deeferdinand', 50, 25);
doc.fontSize(10).fillColor('#ffffff').text(`Social Content Draft β€” ${post.funnelStage || 'TOFU'} | ${new Date(post.ts).toLocaleDateString()}`, 50, 55);
doc.moveDown(3);
const variants = ['v1', 'v2', 'v3'];
const variantLabels = { v1: 'Variant 1: Hook Post', v2: 'Variant 2: Carousel', v3: 'Variant 3: Story/Contrarian' };
for (const vk of variants) {
const v = post[vk];
if (!v) continue;
doc.fillColor('#071330').fontSize(16).text(variantLabels[vk], { underline: true });
doc.moveDown(0.3);
if (v.title) doc.fontSize(12).fillColor('#333').text(`Title: ${v.title}`);
if (v.pillar) doc.fontSize(10).fillColor('#666').text(`Pillar: ${v.pillar}`);
doc.moveDown(0.5);
// Carousel slides
if (v.slides && Array.isArray(v.slides)) {
doc.fontSize(11).fillColor('#071330').text('Carousel Slides:', { underline: true });
for (const slide of v.slides) {
doc.moveDown(0.2);
const label = slide.type ? `Slide ${slide.slide} (${slide.type})` : `Slide ${slide.slide}`;
doc.fontSize(10).fillColor('#333').text(label, { continued: false });
if (slide.text_en) doc.fontSize(9).fillColor('#555').text(`EN: ${slide.text_en}`);
if (slide.text_id) doc.fontSize(9).fillColor('#555').text(`ID: ${slide.text_id}`);
}
doc.moveDown(0.5);
}
// Platform captions
const platforms = [
['Instagram', v.ig_en, v.ig_id],
['Threads', v.threads_en, v.threads_id],
['LinkedIn', v.linkedin_en, v.linkedin_id],
['X/Twitter', v.x_en, v.x_id],
['Facebook', v.fb_en, v.fb_id],
['Pinterest', v.pinterest_en, v.pinterest_id]
];
for (const [name, en, id] of platforms) {
if (en || id) {
doc.fontSize(10).fillColor('#071330').text(`${name}:`);
if (en) doc.fontSize(9).fillColor('#444').text(`EN: ${en.substring(0, 500)}`);
if (id) doc.fontSize(9).fillColor('#444').text(`ID: ${id.substring(0, 500)}`);
doc.moveDown(0.3);
}
}
if (v.hashtags) doc.fontSize(9).fillColor('#666').text(`Hashtags: ${v.hashtags.join(' ')}`);
if (v.best_time_to_post) doc.fontSize(9).fillColor('#666').text(`Best time: ${v.best_time_to_post}`);
doc.moveDown(1);
if (vk !== 'v3') doc.addPage();
}
doc.end();
} catch (e) {
if (!res.headersSent) res.status(500).json({ success: false, error: e.message });
}
});
// Visual PDF β€” renders carousel slides as images then combines into PDF
app.get('/api/social/download/:postId/visual-pdf', async (req, res) => {
try {
const post = (scheduler.config.posts || []).find(p => p.id === req.params.postId);
if (!post) return res.status(404).json({ success: false, error: 'Post not found' });
const v = post.v1;
if (!v) return res.status(400).json({ success: false, error: 'No v1 variant found' });
const carouselGen = require('./carousel-gen');
const PDFDocument = require('pdfkit');
// Build slides from post content
const slides = [];
if (v.slides && Array.isArray(v.slides)) {
v.slides.forEach((s, i) => {
slides.push({
slide: i + 1,
title: s.text_en || s.title || s.hook || '',
subtitle: s.text_id || s.subtitle || '',
type: s.type || (i === 0 ? 'HOOK' : ''),
page: `${i + 1} / ${v.slides.length}`
});
});
} else {
slides.push({
slide: 1,
title: v.title || v.ig_en?.substring(0, 80) || 'Untitled',
subtitle: v.ig_id?.substring(0, 120) || '',
type: 'HOOK',
page: '1 / 1'
});
}
const imagePaths = await carouselGen.generateSlides(slides, post.id, 'dark');
const doc = new PDFDocument({ size: [1080, 1350], margin: 0 });
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${post.id}_visual.pdf"`);
doc.pipe(res);
for (let i = 0; i < imagePaths.length; i++) {
if (i > 0) doc.addPage({ size: [1080, 1350], margin: 0 });
doc.image(imagePaths[i], 0, 0, { width: 1080, height: 1350 });
}
doc.end();
} catch (e) {
if (!res.headersSent) res.status(500).json({ success: false, error: e.message });
}
});
// ============================================
// MIROFISH SENTIMENT DASHBOARD API
// ============================================
app.get('/sentiment', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'sentiment-dashboard.html'));
});
app.get('/api/sentiment/history', (req, res) => {
try {
const history = sentimentAgent.getHistory();
res.json({ success: true, history });
} catch (e) {
res.json({ success: false, error: e.message });
}
});
app.post('/api/sentiment/run', async (req, res) => {
const { topic, country, language, angle } = req.body;
if (!topic) return res.json({ success: false, error: 'Topic is required' });
const c = country || 'Indonesia';
const l = language || 'id';
const a = angle || 'sentiment';
try {
// Step 1: Quick analysis
const result = await sentimentAgent.quickAnalysis({ topic, country: c, language: l, angle: a });
if (!result.success) return res.json(result);
// Step 2: Enhanced analysis + MiroFish data in parallel
const [enhancedResult, miroFishData] = await Promise.allSettled([
sentimentAgent.generateEnhancedAnalysis({ topic, country: c, language: l, angle: a, quickSummary: result.analysis }),
sentimentAgent.fetchMiroFishData(null)
]);
const enhancedText = enhancedResult.status === 'fulfilled' && enhancedResult.value.success
? enhancedResult.value.analysis : null;
const mfData = miroFishData.status === 'fulfilled' ? miroFishData.value : null;
// Step 3: Generate enhanced PDF
const pdfResult = await sentimentAgent.generateEnhancedPDF({
topic, country: c, language: l, angle: a,
quickAnalysis: result.analysis,
enhancedAnalysis: enhancedText,
miroFishData: mfData
});
// Save to history
sentimentAgent.saveToHistory({
topic, country: c, language: l, angle: a,
mode: enhancedText ? 'enhanced' : 'quick',
analysis: enhancedText || result.analysis,
pdfFileName: pdfResult.success ? pdfResult.fileName : null
});
res.json({
success: true,
analysis: enhancedText || result.analysis,
quickSummary: result.analysis,
pdf: pdfResult.success ? { fileName: pdfResult.fileName } : null,
hasMiroFishData: !!mfData
});
} catch (e) {
res.json({ success: false, error: e.message });
}
});
app.get('/api/sentiment/report/:fileName', (req, res) => {
const fileName = req.params.fileName;
if (fileName.includes('..') || fileName.includes('/') || fileName.includes('\\')) {
return res.status(400).json({ error: 'Invalid filename' });
}
const filePath = path.join(__dirname, 'database', 'reports', fileName);
if (!require('fs').existsSync(filePath)) {
return res.status(404).json({ error: 'Report not found' });
}
res.download(filePath);
});
// ============================================
app.use(express.static(path.join(__dirname, 'public')));
// API Routes
app.get('/api/profile', (req, res) => {
const db = memory.readDB();
res.json(db.user_profile_snapshot);
});
app.get('/api/sprints', (req, res) => {
const db = memory.readDB();
res.json(db.active_sprints || []);
});
app.get('/api/status', (req, res) => {
const db = memory.readDB();
const status = {
health: {
openrouter: !!process.env.OPENROUTER_API_KEY,
groq: !!process.env.GROQ_API_KEY,
telegram: !!process.env.TELEGRAM_BOT_TOKEN,
mayar: !!process.env.MAYAR_API_KEY,
whop: !!process.env.WHOP_API_KEY,
apify: !!process.env.APIFY_API_KEY
},
costs: db.costs || { total: 0, by_model: {}, log: [] },
stats: {
experiments: (db.active_sprints || []).length,
offers: (db.playbooks || []).length,
reach: `${(scheduler.config.posts || []).filter(p => p.status === 'posted' || p.channel_posts).length} posts`,
viralTopics: viralFlow.loadJsonl(viralFlow.TOPICS_FILE).length,
viralScripts: viralFlow.loadJsonl(viralFlow.SCRIPTS_FILE).length,
socialDrafts: (scheduler.config.posts || []).length
},
revenue: salesEngine.getDashboardData().revenue
};
res.json(status);
});
const dailyPulse = require('./use_cases/daily_pulse');
const offerArchitect = require('./use_cases/offer_architect');
app.post('/api/generate-image', async (req, res) => {
const { prompt } = req.body;
if (!prompt) return res.status(400).json({ error: "Prompt is required" });
const result = await apiCaller.generateImage(prompt);
if (result.success) {
const base64 = result.buffer.toString('base64');
res.json({ success: true, image_base64: base64, source: result.source });
} else {
res.json(result);
}
});
app.post('/api/usecase/pulse', async (req, res) => {
const result = await dailyPulse();
res.json(result);
});
app.post('/api/usecase/offer', async (req, res) => {
const { topic } = req.body;
if (!topic) return res.status(400).json({ error: "Topic is required" });
const result = await offerArchitect(topic);
res.json(result);
});
// Daily Check-in Mechanism
const DAILY_INTERVAL = 24 * 60 * 60 * 1000;
const chatID = process.env.TELEGRAM_CHAT_ID;
const botToken = process.env.TELEGRAM_BOT_TOKEN;
if (chatID && botToken) {
console.log(`Telegram Bot Active for Chat ID: ${chatID}`);
setInterval(async () => {
console.log("Triggering daily check-in...");
await apiCaller.sendTelegramMessage(chatID, "<b>VinOS Daily Check-in</b>\nHow is the Token Gashapon project coming along? Ready for the next arbitrage move?");
}, DAILY_INTERVAL);
}
// Webhook ingestion
app.post('/api/webhook', (req, res) => {
const data = req.body;
console.log("General Webhook received:", data);
res.status(200).send({ status: 'success' });
});
// SSE clients list for real-time push
const sseClients = [];
app.get('/api/telegram-stream', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
sseClients.push(res);
req.on('close', () => sseClients.splice(sseClients.indexOf(res), 1));
});
const broadcastSSE = (data) => sseClients.forEach(c => c.write(`data: ${JSON.stringify(data)}\n\n`));
app.get('/api/telegram-messages', (req, res) => {
const db = memory.readDB();
res.json(db.telegram_log || []);
});
app.post('/api/send-telegram', async (req, res) => {
const { message } = req.body;
if (!message) return res.status(400).json({ error: 'Message required' });
const chatId = process.env.TELEGRAM_CHAT_ID;
const result = await apiCaller.sendTelegramMessage(chatId, `<b>[VinOS Dashboard]</b> ${message}`);
broadcastSSE({ direction: 'OUT', chatId, text: `[VinOS Dashboard]: ${message}`, ts: new Date().toISOString() });
res.json(result);
});
const voiceTranscriber = require('./skills/voice_transcriber');
const intentRouter = require('./skills/intent_router');
const autoCron = require('./skills/infinite_money_cron');
const responseRouter = require('./skills/response_router');
// Helper to handle resolved intents
async function handleVinIntent(chatId, from, userText, confirmed = false) {
const db = memory.readDB();
const autoMode = db.user_profile_snapshot?.automatic_mode || false;
// 1. Resolve intent
const { intent, params } = await intentRouter.resolveIntent(userText);
if (!confirmed && !autoMode && (intent === 'gash' || intent === 'pulse' || intent === 'offer')) {
if (!db.pending_commands) db.pending_commands = {};
db.pending_commands[chatId] = { intent, params, userText, ts: Date.now() };
memory.writeDB(db);
const confirmationMsg = `πŸ€– <b>Intent:</b> <i>${intent}</i>\n<b>Params:</b> <i>${params || 'none'}</i>\n\nShould I execute? (Reply: <b>Confirm</b> / <b>Cancel</b>)\n<i>Tip: Use /auto on for speed mode.</i>`;
return await apiCaller.sendTelegramMessage(chatId, confirmationMsg);
}
// 2. Route to appropriate skill
switch (intent) {
case 'remember':
await apiCaller.sendTelegramMessage(chatId, "🧠 <i>Designing playbook for:</i> " + params);
const playbookPrompt = `User wants me to remember this instructions or insight: "${params}"\nCreate a structured playbook including: 1. Trigger, 2. Solution, 3. Tags. Format as JSON. Respond ONLY with JSON.`;
const pbGen = await apiCaller.callOpenRouter([{ role: "user", content: playbookPrompt }]);
if (pbGen.success) {
try {
const pbData = JSON.parse(pbGen.data.replace(/```json|```/g, ''));
playbookManager.savePlaybook(pbData.trigger, pbData.solution, pbData.tags);
const pbId = `pb_${Date.now()}`;
await hfStorage.saveRecord('playbooks', pbId, {
title: `Playbook: ${pbData.trigger}`,
timestamp: new Date().toISOString(),
niche: pbData.tags?.[0] || "General"
}, `### TRIGGER: ${pbData.trigger}\n\n### SOLUTION:\n${pbData.solution}\n\n### TAGS:\n${(pbData.tags || []).join(', ')}`);
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Playbook Saved to HF Knowledge Bank.</b>`);
} catch (e) {
await apiCaller.sendTelegramMessage(chatId, "❌ Failed to parse JSON. Saving raw instead.");
playbookManager.savePlaybook(params, "Manual note", ["raw"]);
}
}
break;
case 'recall':
const playbooks = playbookManager.searchPlaybooks(params);
if (playbooks.length > 0) {
const results = playbooks.slice(0, 3).map(pb => `πŸ“Œ <b>${pb.trigger}</b>\n${pb.solution}`).join('\n\n');
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Found:</b>\n\n${results}`);
} else {
await apiCaller.sendTelegramMessage(chatId, `🀷 No playbooks found for "${params}".`);
}
break;
case 'gash':
const gashPrompt = params || userText;
await apiCaller.sendTelegramMessage(chatId, "🎨 <i>Creating your image...</i>");
const gResult = await apiCaller.generateImage(gashPrompt);
if (gResult.success && gResult.buffer) {
// Send as photo via Telegram
const FormData = require('form-data');
const gashForm = new FormData();
gashForm.append('chat_id', chatId);
gashForm.append('photo', gResult.buffer, { filename: 'gash.jpg', contentType: 'image/jpeg' });
// Save image to HF + get link
let hfLinkIntent = '';
try {
const hfRes = await hfStorage.saveFile(`images/gash_${Date.now()}.jpg`, gResult.buffer, 'VinOS: Image gen');
if (hfRes.success) hfLinkIntent = `\nπŸ”— <a href="${hfRes.url}">View on HF</a>`;
} catch (e) { console.error('[HF Auto] Image save failed:', e.message); }
gashForm.append('caption', `✨ <b>Image Created!</b>\n<i>${gashPrompt.substring(0, 100)}</i>\nπŸ”§ Source: ${gResult.source}${hfLinkIntent}`);
gashForm.append('parse_mode', 'HTML');
try {
await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendPhoto`, gashForm, { headers: gashForm.getHeaders() });
} catch (photoErr) {
console.error("[Gash] Photo upload error:", photoErr.response?.data || photoErr.message);
await apiCaller.sendTelegramMessage(chatId, "✨ Image generated but couldn't send as photo. Source: " + gResult.source);
}
// Log to Trello
try {
await trelloManager.logImageGen(gashPrompt, gResult.source, hfLinkIntent ? hfLinkIntent.match(/href="([^"]+)"/)?.[1] : '');
} catch (e) { console.error('[Trello] Image log failed:', e.message); }
} else {
await apiCaller.sendTelegramMessage(chatId, "❌ Image generation failed: " + (gResult.error || 'Unknown error'));
}
break;
case 'pulse':
await apiCaller.sendTelegramMessage(chatId, "πŸ“Š <i>Scanning market...</i>");
await dailyPulse();
break;
case 'offer':
const topic = params || userText;
await apiCaller.sendTelegramMessage(chatId, `πŸ’‘ <i>Architecting offer for:</i> ${topic}`);
await offerArchitect(topic);
break;
default:
await processOrchestratorIntent(chatId, intent, userText, params);
break;
}
}
// Orchestrator handler
async function processOrchestratorIntent(chatId, intent, userText, params, inputModality = 'text') {
const history = conversationMemory.getHistory(chatId);
const persona = fs.readFileSync(path.join(__dirname, 'prompts/vin_personality.md'), 'utf8');
const relatedPlaybooks = playbookManager.searchPlaybooks(userText).slice(0, 1);
let playbookContext = "";
if (relatedPlaybooks.length > 0) {
playbookContext = `\n\n# Relevant Playbook Found:\nTrigger: ${relatedPlaybooks[0].trigger}\nSolution: ${relatedPlaybooks[0].solution}`;
}
const messages = [
{ role: "system", content: persona + playbookContext + `\n\n[System Note: Intent: ${intent}]` },
...history,
{ role: "user", content: userText }
];
conversationMemory.addMessage(chatId, "user", userText);
const chatResult = await apiCaller.callOpenRouter(messages);
if (chatResult.success) {
conversationMemory.addMessage(chatId, "assistant", chatResult.data);
await responseRouter.route({ chatId, userText, inputModality, intent, responseText: chatResult.data });
// Auto-push significant conversations to HF
const history = conversationMemory.getHistory(chatId) || [];
if (userText.length > 200 || history.length % 10 === 0) {
try {
await hfStorage.saveRecord('conversations', `convo_${chatId}_${Date.now()}`, {
title: `Chat: ${userText.substring(0, 50)}`,
timestamp: new Date().toISOString(),
niche: 'Conversation'
}, `**User:** ${userText}\n\n**Vin:** ${chatResult.data}`);
} catch (e) {
console.error('[HF Auto] Conversation push failed:', e.message);
}
}
} else {
await apiCaller.sendTelegramMessage(chatId, "I'm having trouble thinking clearly. Try again.");
}
}
// --- Helper: handle /write context reply ---
async function _handleWrite(chatId, pending, userContext) {
const { targetSiteId, topic, size, pov, intent, tone } = pending;
const initRes = await apiCaller.sendTelegramMessage(chatId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β–‘β–‘β–‘β–‘β–‘] 16% AI Architecting: "${topic}"...`);
const msgId = initRes.messageId;
try {
const articleRes = await seoWriter.generateArticle(size, pov, intent, tone, topic, userContext);
if (!articleRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ <b>Pipeline Failed</b>\n❌ Writer Error: ${articleRes.error}`);
return;
}
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β–‘β–‘β–‘β–‘] 33% QC: Scoring EEAT Quality...`);
const scoreRes = await seoWriter.scoreEEAT(articleRes.html);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] 50% Image Engine: Generating ${articleRes.imagePlaceholders?.length || 0} AI Images...`);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β–‘β–‘] 66% Publishing: Uploading to [${targetSiteId}]...`);
const pubRes = await wordpressPublisher.publishPost(articleRes, targetSiteId);
if (!pubRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ <b>Pipeline Failed</b>\n❌ WordPress Error: ${pubRes.error}`);
return;
}
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β– β–‘] 83% SEO Indexing: Google Search Console...`);
const idxRes = await googleIndexer.submitUrl(pubRes.url);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β– β– ] 100% CRM Sync: Trello Workflow...`);
const trelloRes = await trelloManager.logSeoPost(topic, targetSiteId || 'Default', pubRes.url, scoreRes.score || 'N/A');
let finalMsg = `🌟 <b>Pipeline Complete!</b>\n\n`;
finalMsg += `<b>πŸ”— Article:</b> ${pubRes.url}\n`;
if (scoreRes.success) finalMsg += `<b>🧠 EEAT Score:</b> ${scoreRes.score}/100\n`;
if (idxRes.success) finalMsg += `<b>πŸ” Google:</b> Indexed 🟒\n`;
else finalMsg += `<b>πŸ” Google:</b> Pending 🟑 (${idxRes.error})\n`;
if (trelloRes.success) finalMsg += `<b>πŸ“Š Trello:</b> <a href="${trelloRes.url}">View Card</a> πŸ“‹\n`;
else finalMsg += `<b>πŸ“Š Trello:</b> Failed πŸ”΄\n`;
// Check for matching sales offer β†’ suggest creating one if none
try {
const offerLink = salesEngine.injectOfferLink(topic);
if (offerLink) {
finalMsg += `\nπŸ’° <b>Offer CTA injected:</b> ${offerLink}\n`;
} else {
finalMsg += `\nπŸ’‘ No matching offer for "${topic}". Create one? <code>/offer ${topic}</code>\n`;
}
} catch (e) { /* non-critical */ }
await apiCaller.sendTelegramMessage(chatId, finalMsg);
// Auto-push SEO publish metadata to HF
try {
await hfStorage.saveRecord('seo', `seo_${Date.now()}`, {
title: `SEO: ${topic}`,
timestamp: new Date().toISOString(),
source_url: pubRes.url,
niche: 'SEO'
}, `# Published: ${topic}\n\n- URL: ${pubRes.url}\n- Site: ${targetSiteId || 'Default'}\n- EEAT Score: ${scoreRes.score || 'N/A'}\n- Google Indexed: ${idxRes.success}\n- Trello: ${trelloRes.success ? trelloRes.url : 'N/A'}`);
} catch (e) {
console.error('[HF Auto] SEO log failed:', e.message);
}
} catch (err) {
console.error('[_handleWrite] Error:', err.message);
await apiCaller.sendTelegramMessage(chatId, `⚠️ <b>Write Pipeline Error:</b> ${err.message}`);
}
}
// --- Helper: handle /research context reply ---
async function _handleResearch(chatId, url, angle) {
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Research Auto-Pilot</b>\nAnalyzing URL with focus: <i>${angle || 'General'}</i>...`);
const researchModule = require('./research');
const aiContentMod = require('./ai-content');
const imageGenMod = require('./image-gen');
const schedulerMod = require('./scheduler');
try {
const scrapeRes = await researchModule.scrapePost(url, angle);
if (!scrapeRes.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ Scrape failed: ${scrapeRes.error}`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `🧠 Scrape successful! Remixing content...`);
const remixRes = await aiContentMod.remix(scrapeRes.data);
if (!remixRes.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ AI Remix failed: ${remixRes.error}`);
return;
}
const v1 = remixRes.data.variant_1;
await apiCaller.sendTelegramMessage(chatId, `🎨 Generating high-conversion visual...`);
const imgRes = await imageGenMod.generateAndUpload(v1.visual_prompt, 'res_v1');
if (imgRes.success) v1.mediaUrl = imgRes.publicUrl;
const draftId = `res_${Date.now().toString().slice(-6)}`;
const draftPost = {
id: draftId, status: 'draft', created: new Date().toISOString(),
input: url, platform: scrapeRes.data.platform || 'web',
sourceData: scrapeRes.data, type: 'standard',
v1, v2: remixRes.data.variant_2, v3: remixRes.data.variant_3
};
schedulerMod.config.posts.push(draftPost);
schedulerMod.saveDB();
if (imgRes.success && v1.mediaUrl) {
const FormData = require('form-data');
const fForm = new FormData();
fForm.append('chat_id', chatId);
fForm.append('photo', v1.mediaUrl.startsWith('/') ? fs.createReadStream(path.join(__dirname, 'public', v1.mediaUrl)) : v1.mediaUrl);
fForm.append('caption', `βœ… <b>Research Draft Ready!</b>\n\n${v1.ig_en.substring(0, 800)}...\n\nReview in Dashboard.`);
fForm.append('parse_mode', 'HTML');
await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendPhoto`, fForm, { headers: fForm.getHeaders() });
} else {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Research Draft Ready!</b>\n\n${v1.ig_en.substring(0, 800)}...\n\nReview in Dashboard.`);
}
} catch (err) {
console.error('[_handleResearch] Error:', err.message);
await apiCaller.sendTelegramMessage(chatId, `⚠️ <b>Research Error:</b> ${err.message}`);
}
}
// Telegram Webhook
app.post('/api/telegram-webhook', async (req, res) => {
res.status(200).send({ status: 'received' });
(async () => {
const update = req.body;
const message = update.message;
if (!message) return;
const chatId = message.chat.id;
const from = message.from?.first_name || 'User';
// Bug #1 fix: guard against undefined for non-text messages (stickers, photos w/o caption, etc.)
let userText = message.text || '';
// Bug #6 fix: transcribe voice BEFORE flow intercepts so voice replies work inside multi-step flows
if (message.voice && !userText) {
await apiCaller.sendTelegramMessage(chatId, '🎀 <i>Transcribing...</i>');
const transcription = await voiceTranscriber.transcribeVoice(message.voice.file_id);
if (transcription) {
userText = transcription;
apiCaller.logTelegramMessage('IN', chatId, `[Voice]: ${userText}`);
// Voice-to-voice conversation: natural speech (not a /command) β†’ Apex replies with voice
if (!userText.startsWith('/')) {
const vibeVoice = require('./skills/vibe_voice');
// Echo transcription so user sees what was heard
await apiCaller.sendTelegramMessage(chatId, `🎀 <i>"${userText}"</i>`);
if (vibeVoice.isEnabled()) {
const voiceResp = await ceoAgent.voiceConversation(chatId, userText);
if (voiceResp.success && voiceResp.text) {
await responseRouter.route({ chatId, userText, inputModality: 'voice', intent: 'chat', responseText: voiceResp.text });
} else {
// Fallback: text response if voice synthesis fails
await apiCaller.sendTelegramMessage(chatId, `🎯 ${voiceResp.text || 'Apex is thinking... try again.'}`);
}
} else {
// VibeVoice not configured β€” fall through to normal text flow
// userText is set, normal command router handles it below
}
return;
}
} else {
await apiCaller.sendTelegramMessage(chatId, '❌ Transcription failed.');
return;
}
}
// --- File Handling (Photos & Documents) ---
if (message.photo || message.document) {
const caption = message.caption || '';
const isProposalFile = caption.toLowerCase().startsWith('/proposal');
if (isProposalFile) {
const initRes = await apiCaller.sendTelegramMessage(chatId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β–‘β–‘β–‘β–‘β–‘] 15% Analyzing file for Proposal...');
const msgId = initRes.messageId;
try {
let rfqText = '';
if (message.photo) {
const fileId = message.photo[message.photo.length - 1].file_id;
const fileRes = await apiCaller.downloadTelegramFile(fileId);
if (fileRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β–‘β–‘β–‘β–‘] 30% OCR: Reading text from image via Vision...');
const ocrRes = await apiCaller.analyzeImage(fileRes.buffer, "Extract the full project requirements or RFQ details from this image. DO NOT summarize, just transcribe the core needs.");
if (ocrRes.success) rfqText = ocrRes.data;
else throw new Error("Vision analysis failed: " + ocrRes.error);
} else throw new Error("Download failed: " + fileRes.error);
} else if (message.document && message.document.mime_type === 'application/pdf') {
const fileId = message.document.file_id;
const fileRes = await apiCaller.downloadTelegramFile(fileId);
if (fileRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] 45% Quanta is parsing your PDF document...');
const data = await quantaBridge.submitPDF(fileRes.buffer, message.document.file_name, chatId, from);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β– β– β–‘] 85% Generating final summary...');
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProposalSummary(data));
return;
} else throw new Error("PDF download failed: " + fileRes.error);
} else {
await apiCaller.sendTelegramMessage(chatId, "⚠️ Only Photos or PDF documents are supported for /proposal analysis currently.");
return;
}
// For images (where we extracted text locally), forward text to Quanta
if (rfqText) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] 45% Quanta is architecting your proposal...');
const data = await quantaBridge.submitRFQ(rfqText, chatId, from);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β– β– β–‘] 85% Generating final summary...');
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProposalSummary(data));
}
} catch (e) {
console.error('[File-Proposal] Error:', e.message);
await apiCaller.sendTelegramMessage(chatId, `⚠️ <b>File Analysis Error:</b> ${e.message}`);
}
return;
}
}
// --- Clear Flow State on New Command ---
const isNewCommand = userText.startsWith('/') && !['/cancel', '/confirm', '/yes', '/no'].includes(userText.toLowerCase());
const _db = memory.readDB();
if (isNewCommand && _db.pending_commands && _db.pending_commands[chatId]) {
delete _db.pending_commands[chatId];
memory.writeDB(_db);
await apiCaller.sendTelegramMessage(chatId, `πŸ”„ <i>Previous workflow cancelled. Starting new command.</i>`);
}
// --- Flow Intercepts ---
if (_db.pending_commands?.[chatId]?.type === 'report_flow' && !isNewCommand) {
await reportFlow.handle(chatId, userText);
return;
}
if (_db.pending_commands?.[chatId]?.type === 'carousel_flow' && !isNewCommand) {
await carouselFlow.handle(chatId, userText);
return;
}
// Multi-step research flow: funnel β†’ language β†’ core message β†’ generate
if (_db.pending_commands?.[chatId]?.type === 'research_flow' && !isNewCommand) {
const pending = _db.pending_commands[chatId];
const text = userText.trim();
if (pending.step === 'awaiting_funnel') {
const funnelMap = { '1': 'TOFU', '2': 'MOFU', '3': 'BOFU', 'tofu': 'TOFU', 'mofu': 'MOFU', 'bofu': 'BOFU' };
const funnel = funnelMap[text.toLowerCase()] || 'TOFU';
pending.funnelStage = funnel;
pending.step = 'awaiting_language';
memory.writeDB(_db);
await apiCaller.sendTelegramMessage(chatId,
`βœ… Stage: <b>${funnel}</b>\n\nNow, <b>language</b>?\n\n` +
`1️⃣ <b>Both</b> (EN + ID)\n2️⃣ <b>EN</b> only\n3️⃣ <b>ID</b> only`
);
return;
}
if (pending.step === 'awaiting_language') {
const langMap = { '1': 'both', '2': 'en', '3': 'id', 'both': 'both', 'en': 'en', 'id': 'id', 'english': 'en', 'indonesian': 'id', 'indo': 'id' };
const lang = langMap[text.toLowerCase()] || 'both';
pending.language = lang;
pending.step = 'awaiting_core_message';
memory.writeDB(_db);
await apiCaller.sendTelegramMessage(chatId,
`βœ… Language: <b>${lang}</b>\n\nWhat's the <b>core message</b> you want to convey?\n\n` +
`(e.g. "AI can save 10 hours/week" or type <b>skip</b> to auto-detect)`
);
return;
}
if (pending.step === 'awaiting_core_message') {
const coreMessage = ['skip', 'go', 'auto'].includes(text.toLowerCase()) ? '' : text;
delete _db.pending_commands[chatId];
memory.writeDB(_db);
await apiCaller.sendTelegramMessage(chatId,
`πŸš€ <b>Generating content...</b>\n\n` +
`Stage: ${pending.funnelStage} | Lang: ${pending.language} | Tier: ${pending.tier}\n` +
`${coreMessage ? `Message: "${coreMessage.substring(0, 60)}"` : 'Auto-detecting best angle'}\n\n` +
`This may take 30-60 seconds...`
);
const result = await scheduler.runInteractiveCycle(
{ type: pending.inputType, value: pending.input, tier: pending.tier },
{ funnelStage: pending.funnelStage, language: pending.language, coreMessage }
);
if (result.success) {
const v1 = result.draft.v1;
await apiCaller.sendTelegramMessage(chatId,
`βœ… <b>Draft Created!</b>\n\n` +
`<b>Title:</b> ${v1.title}\n` +
`<b>Pillar:</b> ${v1.pillar}\n` +
`<b>Stage:</b> ${pending.funnelStage}\n\n` +
`Review and approve at: ${process.env.PUBLIC_URL || 'http://localhost:3000'}/social`
);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Generation failed: ${result.error}`);
}
return;
}
// Legacy fallback (old research_flow without step field)
delete _db.pending_commands[chatId];
memory.writeDB(_db);
const angle = ['skip','go'].includes(text.toLowerCase()) ? '' : text;
await _handleResearch(chatId, pending.url || pending.input, angle);
return;
}
if (_db.pending_commands?.[chatId]?.type === 'write_flow' && !isNewCommand) {
const pending = _db.pending_commands[chatId];
// Expire after 30 minutes (Bug #7 fix)
if (Date.now() - pending.ts > 30 * 60 * 1000) {
delete _db.pending_commands[chatId];
memory.writeDB(_db);
await apiCaller.sendTelegramMessage(chatId, '⏱ <i>Write session expired. Please send /write again.</i>');
return;
}
delete _db.pending_commands[chatId];
memory.writeDB(_db);
const ctx = userText.toLowerCase() === 'go' ? '' : userText;
await _handleWrite(chatId, pending, ctx);
return;
}
// CEO agent multi-step flow intercept
if (_db.pending_commands?.[chatId]?.type === 'ceo_flow' && !isNewCommand) {
await ceoAgent.handleFlowStep(chatId, userText, _db.pending_commands[chatId]);
return;
}
// Viral onboard multi-step flow intercept
if (_db.pending_commands?.[chatId]?.type === 'viral_onboard' && !isNewCommand) {
const handled = await viralFlow.handleFlowStep(chatId, userText, _db.pending_commands[chatId]);
if (handled) return;
}
// --- Commands ---
// --- CEO Agent: /ceo command ---
if (userText && userText.toLowerCase().startsWith('/ceo')) {
await ceoAgent.start(chatId, userText);
return;
}
// --- QUANTA: /proposal command ---
if (userText && userText.toLowerCase().startsWith('/proposal')) {
const args = userText.substring(9).trim();
if (!args || args === 'help') {
await apiCaller.sendTelegramMessage(chatId, `πŸ“‹ <b>QUANTA Proposal Generator</b>\n\n<code>/proposal [RFQ text]</code> β€” Generate bilingual proposal\n<code>/proposal status [id]</code> β€” Check project status\n<code>/proposal list</code> β€” Recent projects\n\nPaste a client RFQ, TOR, or WhatsApp message after /proposal to generate a full bilingual proposal with LQS scoring, pricing, and a client portal link.`);
return;
}
if (args.toLowerCase().startsWith('status ')) {
const projectId = args.substring(7).trim();
try {
await apiCaller.sendTelegramMessage(chatId, 'πŸ“‹ Fetching project status...');
const data = await quantaBridge.getProjectStatus(projectId);
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProjectStatus(data));
} catch (e) {
await apiCaller.sendTelegramMessage(chatId, `⚠️ Error: ${e.message}`);
}
return;
}
if (args.toLowerCase() === 'list') {
try {
const data = await quantaBridge.listProjects();
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProjectsList(data));
} catch (e) {
await apiCaller.sendTelegramMessage(chatId, `⚠️ Error: ${e.message}`);
}
return;
}
// Forward RFQ text to QUANTA
try {
const initRes = await apiCaller.sendTelegramMessage(chatId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β–‘β–‘β–‘β–‘β–‘] 15% Receiving RFQ details...');
const msgId = initRes.messageId;
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] 45% Quanta is architecting your proposal...');
const data = await quantaBridge.submitRFQ(args, chatId, from);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, 'πŸ“‹ <b>QUANTA Pipeline</b>\n[β– β– β– β– β– β–‘] 85% Generating final summary...');
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProposalSummary(data));
} catch (e) {
console.error(`[QUANTA] Error for ${chatId}:`, e.message);
let errorMsg = `⚠️ <b>QUANTA Error</b>\n\n`;
if (e.message.includes('timeout')) {
errorMsg += `The system is taking longer than usual. The proposal may still be generating in the background. Check <code>/proposal list</code> in a minute.`;
} else if (e.message.includes('401')) {
errorMsg += `Authentication failure. Quanta agent name or API keys might be misconfigured.`;
} else {
errorMsg += `Failed to process RFQ: ${e.message}`;
}
await apiCaller.sendTelegramMessage(chatId, errorMsg);
}
return;
}
// --- QUANTA: /scrape command ---
if (userText && userText.toLowerCase().startsWith('/scrape')) {
const args = userText.substring(7).trim();
if (!args || args === 'help') {
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Web Scraper</b>\n\n<code>/scrape [url]</code> β€” Scrape website\n<code>/scrape yt [video-url]</code> β€” YouTube transcript\n<code>/scrape ig [username]</code> β€” Instagram profile\n<code>/scrape tt [username]</code> β€” TikTok profile\n<code>/scrape audit [domain]</code> β€” Full competitor audit`);
return;
}
try {
await apiCaller.sendTelegramMessage(chatId, 'πŸ” Scraping...');
if (args.toLowerCase().startsWith('yt ')) {
const data = await quantaBridge.scrapeYouTube(args.substring(3).trim());
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatScrapeResult(data, 'youtube'));
} else if (args.toLowerCase().startsWith('ig ')) {
const data = await quantaBridge.scrapeSocial('instagram', args.substring(3).trim());
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatScrapeResult(data, 'social'));
} else if (args.toLowerCase().startsWith('tt ')) {
const data = await quantaBridge.scrapeSocial('tiktok', args.substring(3).trim());
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatScrapeResult(data, 'social'));
} else if (args.toLowerCase().startsWith('audit ')) {
const data = await quantaBridge.scrapeAudit(args.substring(6).trim());
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatScrapeResult(data, 'audit'));
} else {
// Default: scrape URL
const data = await quantaBridge.scrapeUrl(args);
await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatScrapeResult(data, 'url'));
}
} catch (e) {
await apiCaller.sendTelegramMessage(chatId, `⚠️ Scrape error: ${e.message}`);
}
return;
}
if (userText && userText.toLowerCase().startsWith('/videos')) {
await videoEngine.start(chatId, userText);
return;
}
if (userText && userText.toLowerCase().startsWith('/speak')) {
const speakText = userText.replace(/^\/speak\s*/i, '').trim();
if (!speakText) {
await apiCaller.sendTelegramMessage(chatId, 'πŸŽ™ Usage: /speak [text]\n\nExample: /speak Hello, this is Apex speaking.');
return;
}
const vibeVoice = require('./skills/vibe_voice');
if (!vibeVoice.isEnabled()) {
await apiCaller.sendTelegramMessage(chatId, '❌ VibeVoice not configured. Set VIBEVOICE_URL env var.');
return;
}
const loadingMsg = await apiCaller.sendTelegramMessage(chatId, 'πŸŽ™ Generating voice...');
const result = await vibeVoice.speak(chatId, speakText);
if (!result.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ Voice error: ${result.error}`);
}
return;
}
if (userText && userText.toLowerCase().startsWith('/viral')) {
await viralFlow.start(chatId, userText);
return;
}
if (userText === '/sync') {
await apiCaller.sendTelegramMessage(chatId, "πŸš€ <b>Infrastructure Sync Started...</b>");
const res = await hfDeployer.syncCode();
await apiCaller.sendTelegramMessage(chatId, res.success ? "βœ… <b>Deployment Successful.</b>" : `❌ <b>Sync Failed:</b> ${res.error}`);
return;
}
if (userText === '/social' || userText === '/post') {
const socialGuide = `πŸ“± <b>Social Media Instructions (v1.0)</b>
To get the highest engagement from the AI Remixer, feed it raw text or ideas using this framework:
<b>1. The Hook (First 1-3 lines)</b>
Use a pattern interrupt, a bold claim, or an open loop.
<i>Example: "Stop posting daily. It's killing your reach. Do this instead:"</i>
<b>2. The Value (The Meat)</b>
Provide 3-5 specific, actionable points or a storytelling arc (Situation β†’ Challenge β†’ Insight).
<b>3. The Format Request (Optional)</b>
Tell the AI what emotion or 'edge' you want.
<i>Example: "Remix this into a contrarian take on AI productivity."</i>
<b>4. Image Prompting</b>
β€’ Text/Face logic is automatic! If your prompt includes words like "text", "quote", "face", or "person", it routes to the premium Nano Banana model.
β€’ Standard visuals use Hive AI (Flux Schnell).
<i>Try sending me a URL or text paragraph right now to brainstorm!</i>`;
await apiCaller.sendTelegramMessage(chatId, socialGuide);
return;
}
if (userText && userText.toLowerCase().startsWith('/carousel')) {
await carouselFlow.start(chatId, userText);
return;
}
if (userText && userText.toLowerCase().startsWith('/research')) {
const rawInput = userText.replace(/^\/research\s*/i, '').trim();
if (!rawInput) {
const researchGuide = `πŸ” <b>Research Auto-Pilot β€” v2.0</b>
This engine researches any URL or topic and generates content with your options.
<b>Usage:</b>
<code>/research [URL or topic]</code>
<code>/research [URL or topic] smart</code> β€” use Sonar Pro (deeper)
<b>Supported:</b> URLs (X, IG, Threads, blogs) OR text topics
<b>Flow:</b>
1. Research β†’ 2. TOFU/MOFU/BOFU β†’ 3. Language β†’ 4. Core message β†’ 5. Generate
<i>Try: /research AI productivity tips</i>`;
await apiCaller.sendTelegramMessage(chatId, researchGuide);
return;
}
// Detect tier from last word
const parts = rawInput.split(' ');
let tier = 'fast';
if (parts[parts.length - 1]?.toLowerCase() === 'smart') { tier = 'smart'; parts.pop(); }
else if (parts[parts.length - 1]?.toLowerCase() === 'fast') { tier = 'fast'; parts.pop(); }
const input = parts.join(' ');
// Detect if URL or topic
const isUrl = input.startsWith('http') || input.includes('.com') || input.includes('.net');
const rDb = memory.readDB();
if (!rDb.pending_commands) rDb.pending_commands = {};
rDb.pending_commands[chatId] = {
type: 'research_flow',
step: 'awaiting_funnel',
input, inputType: isUrl ? 'url' : 'topic', tier,
ts: Date.now()
};
memory.writeDB(rDb);
await apiCaller.sendTelegramMessage(chatId,
`πŸ” <b>Research:</b> ${input.substring(0, 50)}${input.length > 50 ? '...' : ''}\n` +
`<b>Tier:</b> ${tier === 'smart' ? 'Smart (Sonar Pro)' : 'Fast (Sonar)'}\n\n` +
`What's the <b>funnel stage</b>?\n\n` +
`1️⃣ <b>TOFU</b> β€” Awareness (grow audience)\n` +
`2️⃣ <b>MOFU</b> β€” Consideration (educate, build trust)\n` +
`3️⃣ <b>BOFU</b> β€” Conversion (drive action)\n\n` +
`Reply: <b>1</b>, <b>2</b>, <b>3</b>, or <b>TOFU/MOFU/BOFU</b>`
);
return;
}
// /socialmodel [premium|standard|free] β€” switch social content AI model
if (userText && userText.toLowerCase().startsWith('/socialmodel')) {
const arg = userText.split(' ')[1]?.toLowerCase();
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
const tierMap = {
premium: { id: apiCaller.SOCIAL_MODELS.PREMIUM, name: 'Claude Haiku 4.5', cost: '$1.00/$5.00 per 1M tokens' },
standard: { id: apiCaller.SOCIAL_MODELS.STANDARD, name: 'Gemini 2.5 Flash Lite', cost: '$0.10/$0.40 per 1M tokens' },
free: { id: apiCaller.SOCIAL_MODELS.FREE, name: 'Llama 3.3 70B', cost: 'FREE' }
};
if (!arg) {
const current = db.user_profile_snapshot.social_content_model || apiCaller.SOCIAL_MODELS.FREE;
const currentTier = Object.entries(tierMap).find(([, v]) => v.id === current);
const tierName = currentTier ? currentTier[0].toUpperCase() : 'FREE';
const tierInfo = currentTier ? currentTier[1] : tierMap.free;
await apiCaller.sendTelegramMessage(chatId, `πŸ€– <b>Social Content Model</b>\n\nCurrent: <b>${tierInfo.name}</b> (${tierName})\nCost: ${tierInfo.cost}\n\nSwitch with:\n<code>/socialmodel premium</code> β€” Claude Haiku 4.5 (SOTA human tone)\n<code>/socialmodel standard</code> β€” Gemini Flash Lite (good daily driver)\n<code>/socialmodel free</code> β€” Llama 3.3 70B (default, zero cost)`);
return;
}
const tier = tierMap[arg];
if (!tier) {
await apiCaller.sendTelegramMessage(chatId, '❌ Invalid tier. Use: <code>/socialmodel premium|standard|free</code>');
return;
}
db.user_profile_snapshot.social_content_model = tier.id;
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Social Model β†’ ${tier.name}</b>\nTier: ${arg.toUpperCase()}\nCost: ${tier.cost}\n\nAll new content generation will use this model.`);
return;
}
// /compete [url or topic] β€” competitor research
if (userText && userText.toLowerCase().startsWith('/compete')) {
const input = userText.replace(/^\/compete\s*/i, '').trim();
if (!input) {
await apiCaller.sendTelegramMessage(chatId, 'πŸ” <b>Competitor Research</b>\n\nUsage: <code>/compete [URL or topic]</code>\n\nAnalyzes top creators and extracts winning patterns.\n\nExamples:\n<code>/compete https://instagram.com/creator</code>\n<code>/compete AI productivity tips</code>');
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸ” Analyzing competitors for: "${input.substring(0, 50)}"...\nThis may take 30-60 seconds.`);
const isUrl = input.startsWith('http') || input.includes('.com') || input.includes('.net');
const result = await competitorResearch.analyzeCompetitors(isUrl ? { url: input } : { topic: input });
if (result.success) {
const p = result.data.patterns || {};
let msg = `πŸ“Š <b>Competitor Insights</b>${result.cached ? ' (cached)' : ''}\n\n`;
if (p.keyInsight) msg += `πŸ’‘ <b>Key Insight:</b> ${p.keyInsight}\n\n`;
if (p.topPostTypes) msg += `<b>Top Post Types:</b> ${p.topPostTypes.map(t => `${t.type} (${t.pct}%)`).join(', ')}\n`;
if (p.bestHookStyles) msg += `<b>Best Hooks:</b> ${p.bestHookStyles.map(h => `${h.style} (${h.pct}%)`).join(', ')}\n`;
if (p.bestTimes) msg += `<b>Best Times:</b> ${p.bestTimes.join(', ')}\n`;
if (p.engagementDrivers) msg += `\n<b>Engagement Drivers:</b>\n${p.engagementDrivers.map(d => `β€’ ${d}`).join('\n')}\n`;
if (p.recommendation) msg += `\n<b>Recommendation:</b> ${p.recommendation}`;
await apiCaller.sendTelegramMessage(chatId, msg);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Research failed: ${result.error}`);
}
return;
}
// /pulseconfig β€” configure daily pulse
if (userText && userText.toLowerCase().startsWith('/pulseconfig')) {
const args = userText.replace(/^\/pulseconfig\s*/i, '').trim().toLowerCase();
const db = memory.readDB();
if (!db.pulse_config) db.pulse_config = { enabled: true, time: '07:00', timezone: 'Asia/Jakarta', include: ['social_analytics', 'market_trends', 'crm_updates'] };
if (!args) {
const c = db.pulse_config;
await apiCaller.sendTelegramMessage(chatId,
`βš™οΈ <b>Pulse Configuration</b>\n\n` +
`<b>Enabled:</b> ${c.enabled ? 'Yes' : 'No'}\n` +
`<b>Time:</b> ${c.time} WIB\n` +
`<b>Sections:</b> ${(c.include || []).join(', ')}\n\n` +
`Commands:\n` +
`<code>/pulseconfig on</code> or <code>off</code>\n` +
`<code>/pulseconfig time 08:30</code>\n` +
`<code>/pulseconfig include social_analytics,market_trends</code>`
);
return;
}
if (args === 'on' || args === 'off') {
db.pulse_config.enabled = args === 'on';
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, `βœ… Pulse ${args === 'on' ? 'enabled' : 'disabled'}.`);
return;
}
if (args.startsWith('time ')) {
const time = args.replace('time ', '').trim();
if (/^\d{1,2}:\d{2}$/.test(time)) {
db.pulse_config.time = time;
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, `βœ… Pulse time set to <b>${time} WIB</b>.`);
} else {
await apiCaller.sendTelegramMessage(chatId, '❌ Invalid time format. Use HH:MM (e.g. 08:30).');
}
return;
}
if (args.startsWith('include ')) {
const sections = args.replace('include ', '').split(',').map(s => s.trim());
db.pulse_config.include = sections;
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, `βœ… Pulse sections: <b>${sections.join(', ')}</b>`);
return;
}
await apiCaller.sendTelegramMessage(chatId, '❌ Unknown option. Use: on|off|time HH:MM|include section1,section2');
return;
}
// /analytics β€” get analytics summary
if (userText && userText.toLowerCase() === '/analytics') {
const result = await analyticsEngine.generateDailySummary();
if (!result.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ Analytics error: ${result.error || 'Unknown error'}`);
}
// generateDailySummary already sends via Telegram if TELEGRAM_CHAT_ID is set
// If chatId differs, send directly
if (chatId !== process.env.TELEGRAM_CHAT_ID) {
await apiCaller.sendTelegramMessage(chatId, result.message || 'No analytics data yet.');
}
return;
}
if (userText === '/turbo') {
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
db.user_profile_snapshot.active_chat_model = 'google/gemini-2.0-flash-lite-001';
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, "⚑ <b>Turbo Mode Activated!</b>\nNow using <i>Gemini 2.0 Flash Lite</i> for lightning-fast responses.");
return;
}
if (userText === '/reason') {
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
db.user_profile_snapshot.active_chat_model = 'nvidia/nemotron-nano-9b-v2:free';
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, "🧠 <b>Reasoning Mode Activated!</b>\nNow using <i>NVIDIA Nemotron Nano</i> for deep-think brainstorming.");
return;
}
// Bug #2 fix: /auto on|off toggles speed mode (was advertised in /start but never implemented)
if (userText && userText.toLowerCase().startsWith('/auto')) {
const arg = userText.split(' ')[1]?.toLowerCase();
const db = memory.readDB();
if (!db.user_profile_snapshot) db.user_profile_snapshot = {};
const isOn = arg === 'on' || (!arg && !db.user_profile_snapshot.automatic_mode);
db.user_profile_snapshot.automatic_mode = isOn;
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, isOn
? '⚑ <b>Auto Mode ON</b> β€” Intent actions run instantly without confirmation.'
: 'πŸ›‘ <b>Auto Mode OFF</b> β€” Actions will ask for confirmation before executing.'
);
return;
}
if (userText && userText.toLowerCase().startsWith('/sentiment')) {
const args = userText.replace(/^\/sentiment\s*/i, '').trim();
if (!args) {
await apiCaller.sendTelegramMessage(chatId,
`🐟 <b>MiroFish Sentiment Engine</b>\n\n` +
`<b>Usage:</b>\n` +
`<code>/sentiment [topic]</code>\n` +
`<code>/sentiment [topic] | [country] | [id/en]</code>\n\n` +
`<b>Examples:</b>\n` +
`<code>/sentiment TikTok Shop ban</code>\n` +
`<code>/sentiment Shopee ads | Indonesia | id</code>\n` +
`<code>/sentiment crypto regulation | USA | en</code>\n\n` +
`<b>Angles:</b> ads, sentiment, market, policy\n` +
`<code>/sentiment EV subsidies | Indonesia | id | policy</code>\n\n` +
`<i>Default: Indonesia, Bahasa Indonesia, sentiment angle</i>`
);
return;
}
// Parse: topic | country | language | angle
const parts = args.split('|').map(s => s.trim());
const topic = parts[0];
const country = parts[1] || 'Indonesia';
const language = (parts[2] || 'id').toLowerCase().startsWith('en') ? 'en' : 'id';
const angle = parts[3] || 'sentiment';
const langLabel = language === 'id' ? 'Bahasa Indonesia' : 'English';
await apiCaller.sendTelegramMessage(chatId,
`🐟 <b>MiroFish Sentiment Engine</b>\n\n` +
`πŸ“Œ <b>Topic:</b> ${topic}\n` +
`🌍 <b>Market:</b> ${country}\n` +
`πŸ—£οΈ <b>Language:</b> ${langLabel}\n` +
`🎯 <b>Angle:</b> ${angle}\n\n` +
`<i>Running quick sentiment analysis...</i>`
);
// Step 1: Quick LLM analysis β†’ send to Telegram immediately
const result = await sentimentAgent.quickAnalysis({ topic, country, language, angle });
if (!result.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ Analysis failed: ${result.error}`);
return;
}
// Send quick summary to Telegram immediately
const summary = result.analysis.length > 3500
? result.analysis.substring(0, 3500) + '\n\n<i>... (detailed PDF generating...)</i>'
: result.analysis;
await apiCaller.sendTelegramMessage(chatId, summary);
await apiCaller.sendTelegramMessage(chatId, `<i>πŸ“„ Generating enhanced PDF with deep analysis...</i>`);
// Step 2: Enhanced analysis + MiroFish data in parallel
const [enhancedResult, miroFishData] = await Promise.allSettled([
sentimentAgent.generateEnhancedAnalysis({ topic, country, language, angle, quickSummary: result.analysis }),
sentimentAgent.fetchMiroFishData(null)
]);
const enhancedText = enhancedResult.status === 'fulfilled' && enhancedResult.value.success
? enhancedResult.value.analysis : null;
const mfData = miroFishData.status === 'fulfilled' ? miroFishData.value : null;
// Step 3: Generate enhanced PDF (last β€” includes all data)
const pdfResult = await sentimentAgent.generateEnhancedPDF({
topic, country, language, angle,
quickAnalysis: result.analysis,
enhancedAnalysis: enhancedText,
miroFishData: mfData
});
if (pdfResult.success) {
try {
await apiCaller.sendTelegramDocument(chatId, pdfResult.path, `πŸ“Š Sentiment: ${topic}`);
} catch (e) {
console.error('[Sentiment] PDF send failed:', e.message);
}
}
// Save to dashboard history
sentimentAgent.saveToHistory({
topic, country, language, angle,
mode: enhancedText ? 'enhanced' : 'quick',
analysis: enhancedText || result.analysis,
pdfFileName: pdfResult.success ? pdfResult.fileName : null
});
// Create Trello card
const trelloResult = await sentimentAgent.createTrelloCard(
topic, country, language, enhancedText || result.analysis, pdfResult.success ? pdfResult.path : null
);
// Status footer
const statusParts = [];
if (pdfResult.success) statusParts.push('πŸ“„ Enhanced PDF');
if (enhancedText) statusParts.push('πŸ“Š Deep analysis');
if (mfData) statusParts.push('🐟 MiroFish data');
if (trelloResult.success) statusParts.push('πŸ“‹ Trello card');
if (statusParts.length) {
await apiCaller.sendTelegramMessage(chatId,
`βœ… <b>Sentiment analysis complete!</b>\n${statusParts.join(' β€’ ')}`
);
}
// Background: Try full MiroFish simulation (fire-and-forget)
sentimentAgent.runSimulation({ topic, country, language, rounds: 10, angle })
.then(simResult => {
if (simResult.success) {
apiCaller.sendTelegramMessage(chatId,
`🐟 <b>MiroFish Deep Simulation Complete!</b>\n\n` +
`Project: <code>${simResult.project_id}</code>\n` +
`Simulation: <code>${simResult.simulation_id}</code>\n\n` +
`<i>Full swarm intelligence report is ready in MiroFish dashboard.</i>`
);
}
})
.catch(() => {}); // Silent fail β€” analysis already delivered
return;
}
if (userText && userText.toLowerCase().startsWith('/report')) {
await reportFlow.start(chatId, userText);
return;
}
if (userText && userText.toLowerCase().startsWith('/push')) {
const pushArg = userText.split(' ')[1]?.toLowerCase() || '';
if (!pushArg || !['db', 'reports', 'all'].includes(pushArg)) {
await apiCaller.sendTelegramMessage(chatId,
`πŸ“€ <b>/push β€” Manual HF Upload</b>\n\n` +
`/push db β€” Snapshot local_db.json\n` +
`/push reports β€” All PDFs in database/reports/\n` +
`/push all β€” Everything above`
);
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸ“€ <i>Pushing to HF dataset...</i>`);
const results = [];
if (pushArg === 'db' || pushArg === 'all') {
try {
const dbPath = path.join(__dirname, 'database/local_db.json');
const dbContent = fs.readFileSync(dbPath);
const res = await hfStorage.saveFile(`snapshots/local_db_${Date.now()}.json`, dbContent, 'VinOS: DB snapshot');
results.push(`DB snapshot: ${res.success ? 'βœ…' : '❌ ' + res.error}`);
} catch (e) {
results.push(`DB snapshot: ❌ ${e.message}`);
}
}
if (pushArg === 'reports' || pushArg === 'all') {
try {
const reportsDir = path.join(__dirname, 'database/reports');
if (fs.existsSync(reportsDir)) {
const pdfs = fs.readdirSync(reportsDir).filter(f => f.endsWith('.pdf'));
if (pdfs.length > 0) {
const files = pdfs.map(f => ({
path: `reports_pdf/${f}`,
content: fs.readFileSync(path.join(reportsDir, f))
}));
const res = await hfStorage.saveBatch(files, `VinOS: Push ${pdfs.length} PDFs`);
results.push(`PDFs (${pdfs.length}): ${res.success ? 'βœ…' : '❌ ' + res.error}`);
} else {
results.push('PDFs: No files found');
}
} else {
results.push('PDFs: reports/ folder not found');
}
} catch (e) {
results.push(`PDFs: ❌ ${e.message}`);
}
}
await apiCaller.sendTelegramMessage(chatId, `πŸ“€ <b>Push Complete</b>\n\n${results.join('\n')}`);
return;
}
if (userText && (userText.toLowerCase() === 'confirm' || userText.toLowerCase() === 'cancel')) {
const db = memory.readDB();
const pending = db.pending_commands?.[chatId];
if (userText.toLowerCase() === 'confirm' && pending) {
delete db.pending_commands[chatId];
memory.writeDB(db);
await apiCaller.sendTelegramMessage(chatId, "βœ… <b>Confirmed.</b> Running now...");
return await handleVinIntent(chatId, from, pending.userText, true);
} else if (userText.toLowerCase() === 'cancel' && pending) {
delete db.pending_commands[chatId];
memory.writeDB(db);
return await apiCaller.sendTelegramMessage(chatId, "🀝 <b>Cancelled.</b>");
}
}
if (message.voice) {
await apiCaller.sendTelegramMessage(chatId, "🎀 <i>Transcribing...</i>");
const transcription = await voiceTranscriber.transcribeVoice(message.voice.file_id);
if (transcription) {
userText = transcription;
apiCaller.logTelegramMessage('IN', chatId, `[Voice]: ${userText}`);
} else return await apiCaller.sendTelegramMessage(chatId, "❌ Transcription failed.");
}
// Handle photo + /gash caption (Face Mode β€” style transfer with face preservation)
if (message.photo && message.caption && message.caption.toLowerCase().startsWith('/gash')) {
const caption = message.caption;
const gashFaceArgs = caption.split(' ').slice(1).join(' ') || 'recreate this person in a professional setting';
const photoObj = message.photo[message.photo.length - 1];
await apiCaller.sendTelegramMessage(chatId, `πŸ“Έ <b>Face Mode Activated!</b>\n<i>Reference photo received. Generating style transfer with face preservation...</i>\n<i>${gashFaceArgs.substring(0, 80)}</i>`);
try {
const axios = require('axios');
const fileRes = await axios.get(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/getFile?file_id=${photoObj.file_id}`);
const filePath = fileRes.data.result.file_path;
const photoUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${filePath}`;
// Download the reference photo as buffer
const photoResponse = await axios.get(photoUrl, { responseType: 'arraybuffer' });
const photoBuffer = Buffer.from(photoResponse.data);
// Generate with face-preserving multimodal (Gemini sees the photo)
const facePrompt = `${gashFaceArgs}, professional photography, cinematic lighting, high detail`;
const faceResult = await apiCaller.generateImageFromReference(facePrompt, photoBuffer);
if (faceResult.success && faceResult.buffer) {
// Save to HF + get link
let hfLink = '';
try {
const hfRes = await hfStorage.saveFile(`images/gash_face_${Date.now()}.jpg`, faceResult.buffer, 'VinOS: Face mode image');
if (hfRes.success) hfLink = `\nπŸ”— <a href="${hfRes.url}">View on HF</a>`;
} catch (e) { console.error('[HF Auto] Face image save failed:', e.message); }
const FormData = require('form-data');
const fForm = new FormData();
fForm.append('chat_id', chatId);
fForm.append('photo', faceResult.buffer, { filename: 'gash_face.jpg', contentType: 'image/jpeg' });
fForm.append('caption', `✨ <b>Face Mode Result!</b>\nπŸ”§ ${faceResult.source}\n<i>${gashFaceArgs.substring(0, 100)}</i>${hfLink}`);
fForm.append('parse_mode', 'HTML');
try {
await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendPhoto`, fForm, { headers: fForm.getHeaders() });
} catch (fErr) {
console.error("[Face] Photo upload error:", fErr.response?.data || fErr.message);
}
// Log to Trello
try {
await trelloManager.logImageGen(gashFaceArgs, faceResult.source, hfLink ? hfLink.match(/href="([^"]+)"/)?.[1] : '');
} catch (e) { console.error('[Trello] Image log failed:', e.message); }
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Image generation failed: ${faceResult.error || 'All providers exhausted'}`);
}
} catch (photoErr) {
console.error('[Gash Face] Error:', photoErr.message);
await apiCaller.sendTelegramMessage(chatId, `❌ Face mode error: ${photoErr.message}`);
}
return;
}
if (userText) {
if (userText.startsWith('/start')) {
const db = memory.readDB();
const currentModel = db.user_profile_snapshot?.active_chat_model || 'nvidia/nemotron-nano-9b-v2:free';
const isTurbo = currentModel.includes('google');
const modeEmoji = isTurbo ? "⚑" : "🧠";
const modeName = isTurbo ? "Turbo Mode" : "Reasoning Mode";
const startMsg = `🦍 <b>VinOS Command Center β€” v4.1.0</b>\n` +
`Current Mode: ${modeEmoji} <b>${modeName}</b>\n\n` +
`πŸ“Š <b>STRATEGY</b>\n` +
`/report β€” Interactive Research β†’ PDF Report\n` +
`<i>Vin asks: topic β†’ depth β†’ focus β†’ confirm</i>\n` +
`β€’ 1️⃣ Quick Brief (2-3 pages, ~30s)\n` +
`β€’ 2️⃣ Deep Analysis (5-8 pages, ~2min)\n` +
`β€’ 3️⃣ Brand Visibility Audit (presence + score)\n` +
`β€’ 4️⃣ From Your Briefing (paste text β†’ structured PDF)\n` +
`<i>Output: McKinsey-style PDF sent to this chat</i>\n\n` +
`🎨 <b>CREATE</b>\n` +
`/gash [prompt] β€” AI Image Creator\n\n` +
`πŸ§ͺ <b>ENGINES</b>\n` +
`/turbo β€” Switch to Fast Mode\n` +
`/reason β€” Switch to Think Mode\n\n` +
`πŸš€ <b>DEPLOY</b>\n` +
`/sync β€” Push code to HF Space\n` +
`/push [db|reports|all] β€” Push data to HF Dataset\n\n` +
`🧠 <b>KNOWLEDGE</b>\n` +
`<i>"remember [x]"</i> β€” Save a playbook\n\n` +
`πŸ› οΈ <b>SYSTEM</b>\n` +
`/newskill [name] [desc] β€” Auto-code an agent\n` +
`/agents β€” List active agent skills\n\n` +
`πŸ“ <b>SEO ENGINE</b>\n` +
`/seo [keyword] β€” Architect SEO Pillar Strategy\n` +
`/write [siteId] [size] [topic] β€” Publish SEO Article\n` +
`/seo2offer [keyword] β€” Full pipeline: scout β†’ offer β†’ article β†’ publish β†’ index\n` +
`/index [url] β€” Submit to Google API\n\n` +
`πŸ’° <b>SALES ENGINE</b>\n` +
`/offer β€” List active offers with payment links\n` +
`/offer [topic] β€” Create offer (AI copy β†’ Mayar link)\n` +
`/offer pause [id] β€” Pause underperforming offer\n` +
`/offer ab [id] β€” Create A/B test variant\n` +
`/offer variants [id] β€” View variant stats\n` +
`/offer board β€” Pipeline stage counts\n` +
`/offer eval β€” Run offer evaluation\n` +
`/revenue β€” Revenue summary\n` +
`/revenue today β€” Today's sales only\n\n` +
`🎬 <b>VIDEO ENGINE</b>\n` +
`/videos create [topic] β€” AI-generate + render video\n` +
`/videos templates β€” List video templates\n` +
`/videos status β€” Render queue status\n\n` +
`πŸ” <b>RESEARCH</b>\n` +
`/scout [keyword] β€” Market viability scout\n` +
`/costs β€” API spend breakdown\n` +
`/landing β€” Your link-in-bio page\n\n` +
`🎯 <b>CEO AGENT</b>\n` +
`/ceo β€” Dashboard summary\n` +
`/ceo briefing β€” Morning briefing\n` +
`/ceo client add/list β€” Client pipeline\n` +
`/ceo plan β€” Weekly content plan\n` +
`/ceo visibility [brand] β€” AI visibility audit\n` +
`/ceo check [brand] β€” Background check\n` +
`/ceo pulse β€” System health\n` +
`/ceo hosting list β€” Hosting & domains\n` +
`/ceo meeting list β€” Meetings\n` +
`/ceo funnel β€” DigitalFinese stats\n\n` +
`πŸ“‹ <b>CRM MANAGER</b>\n` +
`/updates – View Trello project statuses\n` +
`/move [4-char-ID] [new list] – Update card status`;
await apiCaller.sendTelegramMessage(chatId, startMsg);
} else if (userText.toLowerCase().startsWith('/newskill')) {
const parts = userText.split(' ');
const skillName = parts[1];
const skillDesc = parts.slice(2).join(' ');
if (!skillName || !skillDesc) {
await apiCaller.sendTelegramMessage(chatId, "❌ Usage: /newskill [name] [description]");
} else {
await apiCaller.sendTelegramMessage(chatId, `πŸ› οΈ <b>Architecting Skill:</b> ${skillName}...\nWriting code via LLM...`);
const genRes = await skillCreator.generateSkillCode(skillName, skillDesc);
if (genRes.success) {
const saveRes = await skillCreator.saveSkill(skillName, genRes.code);
if (saveRes.success) {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Skill '${saveRes.name}' Created!</b>\nCode saved to <code>skills/${saveRes.name}.js</code>\n\nRun <b>/sync</b> to push to cloud, then register it in <code>server.js</code>.`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Failed to save skill: ${saveRes.error}`);
}
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Code generation failed: ${genRes.error}`);
}
}
} else if (userText.toLowerCase().startsWith('/agents')) {
const files = fs.readdirSync(path.join(__dirname, 'skills')).filter(f => f.endsWith('.js'));
const list = files.map(f => `β€’ <code>${f}</code>`).join('\n');
await apiCaller.sendTelegramMessage(chatId, `πŸ€– <b>Active Skills (${files.length}):</b>\n\n${list}`);
} else if (userText.toLowerCase().startsWith('/gash')) {
const gashArgs = userText.split(' ').slice(1).join(' ');
if (!gashArgs) {
await apiCaller.sendTelegramMessage(chatId,
`🎨 <b>/gash β€” AI Image Creator</b>\n\n` +
`<b>Usage:</b>\n` +
`/gash [prompt] [ratio]\n\n` +
`<b>Ratios:</b> 9:16, 16:9, 4:5, 4:3, 1:1 (default)\n\n` +
`<b>Examples:</b>\n` +
`/gash A futuristic city at sunset 16:9\n` +
`/gash Professional headshot minimalistic 4:5\n\n` +
`<b>πŸ“Έ Face Mode:</b> Send a photo with caption <code>/gash [prompt]</code> to use the face`
);
} else {
// Parse ratio from the end of prompt
const ratioMatch = gashArgs.match(/\s+(9:16|16:9|4:5|4:3|1:1)\s*$/);
const ratio = ratioMatch ? ratioMatch[1] : '1:1';
const gashPrompt = ratioMatch ? gashArgs.replace(ratioMatch[0], '').trim() : gashArgs;
await apiCaller.sendTelegramMessage(chatId, `🎨 <i>Creating image (${ratio})...</i>\n<i>${gashPrompt.substring(0, 80)}</i>`);
// Parse ratio to width/height for image gen
const ratioMap = { '9:16': {w:768,h:1344}, '16:9': {w:1344,h:768}, '4:5': {w:896,h:1120}, '4:3': {w:1152,h:896}, '1:1': {w:1024,h:1024} };
const dims = ratioMap[ratio] || ratioMap['1:1'];
const gashResult = await apiCaller.generateImage(gashPrompt);
if (gashResult.success && gashResult.buffer) {
// Save image to HF + get link
let hfLinkCmd = '';
try {
const hfRes = await hfStorage.saveFile(`images/gash_${Date.now()}.jpg`, gashResult.buffer, 'VinOS: Image gen');
if (hfRes.success) hfLinkCmd = `\nπŸ”— <a href="${hfRes.url}">View on HF</a>`;
} catch (e) { console.error('[HF Auto] Image save failed:', e.message); }
const FormData = require('form-data');
const gForm = new FormData();
gForm.append('chat_id', chatId);
gForm.append('photo', gashResult.buffer, { filename: 'gash.jpg', contentType: 'image/jpeg' });
gForm.append('caption', `✨ <b>Image Created!</b>\nπŸ“ ${ratio}\nπŸ”§ ${gashResult.source}\n<i>${gashPrompt.substring(0, 100)}</i>${hfLinkCmd}`);
gForm.append('parse_mode', 'HTML');
try {
await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendPhoto`, gForm, { headers: gForm.getHeaders() });
} catch (photoErr) {
console.error("[Gash] Ratio upload error:", photoErr.response?.data || photoErr.message);
await apiCaller.sendTelegramMessage(chatId, `✨ Image generated (${gashResult.source}) but photo upload failed.`);
}
// Log to Trello
try {
await trelloManager.logImageGen(gashPrompt, gashResult.source, hfLinkCmd ? hfLinkCmd.match(/href="([^"]+)"/)?.[1] : '');
} catch (e) { console.error('[Trello] Image log failed:', e.message); }
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Image generation failed: ${gashResult.error || 'All providers exhausted'}`);
}
}
} else if (userText.toLowerCase().startsWith('/seo2offer')) {
const keyword = userText.split(' ').slice(1).join(' ').trim();
if (!keyword) {
await apiCaller.sendTelegramMessage(chatId, `❌ Usage: <code>/seo2offer [keyword]</code>\nFull pipeline: scout β†’ offer β†’ article β†’ publish β†’ index`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>SEO-to-Revenue Pipeline</b>\n[β– β–‘β–‘β–‘β–‘β–‘] Scouting: "${keyword}"...`);
// 1. Scout market viability
let scoutResult;
try { scoutResult = await scoutAgent.runScout(keyword); } catch (e) { scoutResult = 'Scout unavailable'; }
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>SEO-to-Revenue Pipeline</b>\n[β– β– β–‘β–‘β–‘β–‘] Creating offer + Mayar link...`);
// 2. Create offer
const offerResult = await salesEngine.createOfferFromTopic(keyword, 49000);
let offerMsg = '';
if (offerResult.success) {
offerMsg = `βœ… Offer: ${offerResult.offer.title}\nπŸ’³ ${offerResult.offer.paymentUrl || 'Pending'}`;
} else {
offerMsg = `⚠️ Offer: ${offerResult.verdict || 'skipped'} (${offerResult.reason || offerResult.error || ''})`;
}
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>SEO-to-Revenue Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] Writing SEO article...`);
// 3. Write article
let sites = {};
try { sites = JSON.parse(process.env.WP_SITES_JSON || "{}"); } catch(e){}
const siteKeys = Object.keys(sites);
const targetSite = siteKeys.length > 0 ? siteKeys[0] : null;
const articleRes = await seoWriter.generateArticle('medium', '3rd person', 'Educational', 'Authoritative', keyword, '');
if (!articleRes.success) {
await apiCaller.sendTelegramMessage(chatId, `⚠️ Article failed: ${articleRes.error}\n\n${offerMsg}\n\nπŸ•΅οΈ <b>Scout:</b>\n${scoutResult}`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>SEO-to-Revenue Pipeline</b>\n[β– β– β– β– β–‘β–‘] Publishing to WordPress (CTA auto-injected)...`);
// 4. Publish (CTA injection happens inside publisher automatically)
const pubRes = await wordpressPublisher.publishPost(articleRes, targetSite);
if (!pubRes.success) {
await apiCaller.sendTelegramMessage(chatId, `⚠️ Publish failed: ${pubRes.error}\n\n${offerMsg}\n\nπŸ•΅οΈ <b>Scout:</b>\n${scoutResult}`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>SEO-to-Revenue Pipeline</b>\n[β– β– β– β– β– β–‘] Indexing on Google...`);
// 5. Index
const idxRes = await googleIndexer.submitUrl(pubRes.url);
// 6. Final report
let finalMsg = `🎯 <b>SEO-to-Revenue Complete!</b>\n\n`;
finalMsg += `πŸ”— <b>Article:</b> ${pubRes.url}\n`;
finalMsg += `πŸ” <b>Google:</b> ${idxRes.success ? 'Indexed 🟒' : 'Pending 🟑'}\n`;
finalMsg += `${offerMsg}\n\n`;
finalMsg += `πŸ•΅οΈ <b>Scout Report:</b>\n${typeof scoutResult === 'string' ? scoutResult.substring(0, 500) : 'N/A'}`;
await apiCaller.sendTelegramMessage(chatId, finalMsg);
} else if (userText.toLowerCase().startsWith('/seo')) {
const keyword = userText.split(' ').slice(1).join(' ');
if (!keyword) {
await apiCaller.sendTelegramMessage(chatId, "❌ Usage: /seo [main keyword]");
} else {
await apiCaller.sendTelegramMessage(chatId, `πŸ—ΊοΈ <b>SEO Engine:</b> Mapping Pillar Strategy for "${keyword}"...`);
const strategyRes = await seoWriter.architectSeoStrategy(keyword);
if (strategyRes.success) {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>SEO Blueprint Generated:</b>\n\n${strategyRes.data}\n\n<i>To write an article for any of these clusters, reply with <code>/write [Choose a Blog Title]</code></i>`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ Strategy mapping failed: ${strategyRes.error}`);
}
}
} else if (userText.toLowerCase().startsWith('/write')) {
// Parse arguments smartly handling quotes: /write site "Topic" size "POV" "Intent" "Tone"
const args = userText.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
// Remove quotes from matched strings
const cleanArgs = args.map(arg => arg.replace(/^["'](.*)["']$/, '$1'));
let sites = {};
try { sites = JSON.parse(process.env.WP_SITES_JSON || "{}"); } catch(e){}
const siteKeys = Object.keys(sites);
const defaultSite = siteKeys.length > 0 ? siteKeys[0] : 'None';
if (cleanArgs.length < 7) {
const helpMsg = `⚠️ <b>Missing Parameters for /write</b>\n\n` +
`To architect an elite SEO article, I need instructions. Please use quotes for multi-word parameters.\n\n` +
`<b>Structure:</b>\n` +
`/write [SiteID] "[Topic]" [Size] "[POV]" "[Intent]" "[Tone]"\n\n` +
`<b>Available Site IDs:</b> ${siteKeys.join(', ') || 'Configure WP_SITES_JSON'}\n` +
`<b>Sizes:</b> short, medium, dense\n` +
`<b>POV:</b> "1st person singular", "1st person plural", "2nd person", "3rd person"\n\n` +
`<b>Example:</b>\n` +
`/write ${defaultSite} "Agentic Workflow" medium "1st person plural" "Educational" "Authoritative"`;
await apiCaller.sendTelegramMessage(chatId, helpMsg);
} else {
const targetSiteId = cleanArgs[1];
const topic = cleanArgs[2];
const size = cleanArgs[3].toLowerCase();
const pov = cleanArgs[4];
const intent = cleanArgs[5];
const tone = cleanArgs[6];
if (!siteKeys.includes(targetSiteId)) {
await apiCaller.sendTelegramMessage(chatId, `❌ Unknown Site ID: ${targetSiteId}\nAvailable: ${siteKeys.join(', ')}`);
return;
}
// Bug #4 fix: use DB-backed pending_commands instead of volatile global
const wDb = memory.readDB();
if (!wDb.pending_commands) wDb.pending_commands = {};
wDb.pending_commands[chatId] = { type: 'write_flow', targetSiteId, topic, size, pov, intent, tone, ts: Date.now() };
memory.writeDB(wDb);
await apiCaller.sendTelegramMessage(chatId,
`πŸ€” <b>Context Check for "${topic}"</b>\n\n` +
`Before I write, I want to make sure I get this right.\n\n` +
`β€’ What is "${topic}" about in your context?\n` +
`β€’ Any specific angle or key points?\n` +
`β€’ Target audience?\n\n` +
`Reply with details, or send <b>"go"</b> to let me decide.`
);
}
} else if (global.pendingResearch && global.pendingResearch[chatId]) {
const pending = global.pendingResearch[chatId];
delete global.pendingResearch[chatId];
const angle = (userText.toLowerCase() === 'skip' || userText.toLowerCase() === 'go') ? '' : userText;
const url = pending.url;
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Research Auto-Pilot</b>\nAnalyzing URL with focus: <i>${angle || 'General'}</i>...`);
const researchModule = require('./research');
const aiContent = require('./ai-content');
const imageGen = require('./image-gen');
const scheduler = require('./scheduler');
const scrapeRes = await researchModule.scrapePost(url, angle);
if (!scrapeRes.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ Scrape failed: ${scrapeRes.error}`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `🧠 Scrape successful! Remixing content...`);
const remixRes = await aiContent.remix(scrapeRes.data);
if (!remixRes.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ AI Remix failed: ${remixRes.error}`);
return;
}
const v1 = remixRes.data.variant_1;
await apiCaller.sendTelegramMessage(chatId, `🎨 Generating high-conversion visual...`);
const imgRes = await imageGen.generateAndUpload(v1.visual_prompt, 'res_v1');
if (imgRes.success) v1.mediaUrl = imgRes.publicUrl;
// Save draft
const draftId = `res_${Date.now().toString().slice(-6)}`;
const draftPost = {
id: draftId,
status: 'draft',
created: new Date().toISOString(),
input: url,
platform: scrapeRes.data.platform || 'web',
sourceData: scrapeRes.data,
type: 'standard',
v1,
v2: remixRes.data.variant_2,
v3: remixRes.data.variant_3
};
scheduler.config.posts.push(draftPost);
scheduler.saveDB();
if (imgRes.success && v1.mediaUrl) {
const FormData = require('form-data');
const fForm = new FormData();
fForm.append('chat_id', chatId);
fForm.append('photo', v1.mediaUrl.startsWith('/') ? fs.createReadStream(path.join(__dirname, 'public', v1.mediaUrl)) : v1.mediaUrl);
fForm.append('caption', `βœ… <b>Research Draft Ready!</b>\n\n${v1.ig_en.substring(0, 800)}...\n\nReview in Dashboard.`);
fForm.append('parse_mode', 'HTML');
await apiCaller.axiosIPv4.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendPhoto`, fForm, { headers: fForm.getHeaders() });
} else {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Research Draft Ready!</b>\n\n${v1.ig_en.substring(0, 800)}...\n\nReview in Dashboard.`);
}
} else if (global.pendingWrites && global.pendingWrites[chatId]) {
// --- HANDLE CONTEXT REPLY FOR PENDING /write ---
const pending = global.pendingWrites[chatId];
delete global.pendingWrites[chatId];
const userContext = userText.toLowerCase() === 'go' ? '' : userText;
const { targetSiteId, topic, size, pov, intent, tone } = pending;
const initRes = await apiCaller.sendTelegramMessage(chatId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β–‘β–‘β–‘β–‘β–‘] 16% AI Architecting: "${topic}"...`);
const msgId = initRes.messageId;
const articleRes = await seoWriter.generateArticle(size, pov, intent, tone, topic, userContext);
if (articleRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β–‘β–‘β–‘β–‘] 33% QC: Scoring EEAT Quality...`);
const scoreRes = await seoWriter.scoreEEAT(articleRes.html);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β–‘β–‘β–‘] 50% Image Engine: Generating ${articleRes.imagePlaceholders?.length || 0} AI Images...`);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β–‘β–‘] 66% Publishing: Uploading to [${targetSiteId}]...`);
const pubRes = await wordpressPublisher.publishPost(articleRes, targetSiteId);
if (pubRes.success) {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β– β–‘] 83% SEO Indexing: Google Search Console...`);
const idxRes = await googleIndexer.submitUrl(pubRes.url);
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `πŸ“ <b>SEO Engine Pipeline</b>\n[β– β– β– β– β– β– ] 100% CRM Sync: Trello Workflow...`);
const trelloRes = await trelloManager.logSeoPost(topic, targetSiteId || 'Default', pubRes.url, scoreRes.score || 'N/A');
let finalMsg = `🌟 <b>Pipeline Complete!</b>\n\n`;
finalMsg += `<b>πŸ”— Article:</b> ${pubRes.url}\n`;
if (scoreRes.success) finalMsg += `<b>🧠 EEAT Score:</b> ${scoreRes.score}/100\n`;
if (idxRes.success) finalMsg += `<b>πŸ” Google:</b> Indexed 🟒\n`;
else finalMsg += `<b>πŸ” Google:</b> Pending 🟑 (${idxRes.error})\n`;
if (trelloRes.success) {
finalMsg += `<b>πŸ“Š Trello:</b> <a href="${trelloRes.url}">View Card</a> πŸ“‹\n`;
} else {
finalMsg += `<b>πŸ“Š Trello:</b> Failed πŸ”΄\n`;
}
await apiCaller.sendTelegramMessage(chatId, finalMsg);
// Auto-push SEO publish metadata to HF
try {
await hfStorage.saveRecord('seo', `seo_${Date.now()}`, {
title: `SEO: ${topic}`,
timestamp: new Date().toISOString(),
source_url: pubRes.url,
niche: 'SEO'
}, `# Published: ${topic}\n\n- URL: ${pubRes.url}\n- Site: ${targetSiteId || 'Default'}\n- EEAT Score: ${scoreRes.score || 'N/A'}\n- Google Indexed: ${idxRes.success}\n- Trello: ${trelloRes.success ? trelloRes.url : 'N/A'}`);
} catch (e) {
console.error('[HF Auto] SEO log failed:', e.message);
}
} else {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ <b>Pipeline Failed</b>\n❌ WordPress Error: ${pubRes.error}`);
}
} else {
if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ <b>Pipeline Failed</b>\n❌ Writer Error: ${articleRes.error}`);
}
} else if (userText.toLowerCase().startsWith('/updates')) {
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Checking Trello CRM...</b>`);
const updateRes = await trelloManager.getProjectUpdates();
if (updateRes.success) {
await apiCaller.sendTelegramMessage(chatId, updateRes.report);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ **Failed to fetch updates:** ${updateRes.error}`);
}
} else if (userText.toLowerCase().startsWith('/move')) {
const parts = userText.split(' ');
if (parts.length < 3) {
await apiCaller.sendTelegramMessage(chatId, `❌ Usage: /move [4-char-ID] [New List Name]`);
} else {
const shortId = parts[1];
const newListName = parts.slice(2).join(' ');
await apiCaller.sendTelegramMessage(chatId, `πŸš€ <b>Attempting to move card...</b>`);
const moveRes = await trelloManager.moveCard(shortId, newListName);
if (moveRes.success) {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Success!</b>\nMoved card "${moveRes.cardName}" to πŸ“ <b>${moveRes.listName}</b>.`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ **Move Failed:** ${moveRes.error}`);
}
}
} else if (userText.toLowerCase().startsWith('/index')) {
const url = userText.split(' ')[1];
if (!url) {
await apiCaller.sendTelegramMessage(chatId, "❌ Usage: /index [url]");
} else {
await apiCaller.sendTelegramMessage(chatId, `πŸ” Submitting to Google Indexing...`);
const idxRes = await googleIndexer.submitUrl(url);
if (idxRes.success) {
await apiCaller.sendTelegramMessage(chatId, `βœ… <b>Successfully submitted to Google.</b>`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ <b>Indexing Failed:</b> ${idxRes.error}`);
}
}
// ========== SALES ENGINE COMMANDS ==========
} else if (userText.toLowerCase().startsWith('/offer')) {
const args = userText.split(' ').slice(1).join(' ').trim();
if (!args) {
// /offer β€” list active offers
const dash = salesEngine.getDashboardData();
if (dash.activeOffers.length === 0) {
await apiCaller.sendTelegramMessage(chatId, 'πŸ“¦ No active offers.\n\nCreate one: <code>/offer [topic]</code>');
} else {
const lines = dash.activeOffers.map(o =>
`β€’ <b>${o.title}</b>\n πŸ’΅ Rp ${(o.priceIDR || 0).toLocaleString()} | πŸ“Š ${o.totalSales} sales | πŸ’° Rp ${(o.totalRevenue || 0).toLocaleString()}\n ${o.paymentUrl ? `πŸ”— ${o.paymentUrl}` : '⏳ Link pending'}\n <code>${o.id}</code>`
).join('\n\n');
await apiCaller.sendTelegramMessage(chatId, `πŸ“¦ <b>Active Offers (${dash.activeCount})</b>\n\n${lines}`);
}
} else if (args.toLowerCase().startsWith('pause ')) {
// /offer pause [id]
const offerId = args.split(' ')[1];
const result = salesEngine.pauseOffer(offerId);
if (result.success) {
await apiCaller.sendTelegramMessage(chatId, `⏸️ Offer paused: <b>${result.offer.title}</b>`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ ${result.error}`);
}
} else if (args.toLowerCase().startsWith('retire ')) {
// /offer retire [id]
const offerId = args.split(' ')[1];
const result = salesEngine.retireOffer(offerId);
if (result.success) {
await apiCaller.sendTelegramMessage(chatId, `πŸ—‘οΈ Offer retired: <b>${result.offer.title}</b>`);
} else {
await apiCaller.sendTelegramMessage(chatId, `❌ ${result.error}`);
}
} else if (args.toLowerCase() === 'board') {
// /offer board β€” pipeline stage counts
const db = salesEngine.readSalesDB();
const counts = {};
for (const o of db.offers) { counts[o.status] = (counts[o.status] || 0) + 1; }
const stages = ['active', 'pending_link', 'pending_validation', 'paused', 'retired'];
const emoji = { active: '🟒', pending_link: '🟑', pending_validation: 'πŸ”΅', paused: '⏸️', retired: 'πŸ”΄' };
const lines = stages.filter(s => counts[s]).map(s => `${emoji[s] || 'β€’'} ${s}: ${counts[s]}`).join('\n');
await apiCaller.sendTelegramMessage(chatId, `πŸ“‹ <b>Offer Pipeline</b>\n\n${lines || 'No offers yet.'}\n\nTotal: ${db.offers.length}`);
} else if (args.toLowerCase().startsWith('ab ')) {
// /offer ab [id] β€” create A/B variant
const offerId = args.split(' ')[1];
await apiCaller.sendTelegramMessage(chatId, `πŸ§ͺ <i>Creating variant for ${offerId}...</i>`);
const result = await salesEngine.createVariant(offerId);
if (!result.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ ${result.error}`);
}
} else if (args.toLowerCase().startsWith('variants ')) {
// /offer variants [id] β€” show all variants
const offerId = args.split(' ')[1];
const result = salesEngine.getVariants(offerId);
if (!result.success) {
await apiCaller.sendTelegramMessage(chatId, `❌ ${result.error}`);
} else {
const lines = result.variants.map(v => {
const convRate = v.clicks > 0 ? ((v.sales / v.clicks) * 100).toFixed(1) : '0.0';
return `πŸ”€ <b>${v.id}:</b> ${v.title}\n πŸ“Š ${v.sales} sales | ${v.clicks} clicks | ${convRate}% conv | ${v.isActive ? '🟒 Active' : 'πŸ”΄ Paused'}`;
}).join('\n\n');
await apiCaller.sendTelegramMessage(chatId, `πŸ§ͺ <b>Variants: ${result.offerTitle || offerId}</b>\n\n${lines}`);
}
} else if (args.toLowerCase().startsWith('eval')) {
// /offer eval β€” run evaluation
await apiCaller.sendTelegramMessage(chatId, 'πŸ“Š <i>Evaluating offers...</i>');
const result = await salesEngine.evaluateOffers();
await apiCaller.sendTelegramMessage(chatId, `βœ… Evaluation done. ${result.actions.length} actions taken.`);
} else {
// /offer [topic] β€” create new offer
await apiCaller.sendTelegramMessage(chatId, `πŸ’° <i>Creating offer for: ${args}...</i>\n<i>AI copy β†’ Trend check β†’ Mayar link</i>`);
const result = await offerArchitect(args);
if (result.success) {
// Notification already sent by salesEngine
} else {
const msg = result.verdict === 'wait'
? `⏳ <b>Topic queued for re-check</b>\nScore: ${result.score}/100\n${result.reason}`
: result.verdict === 'skip'
? `⏭️ <b>Topic skipped</b>\nScore: ${result.score}/100\n${result.reason}`
: `❌ Offer creation failed: ${result.reason}`;
await apiCaller.sendTelegramMessage(chatId, msg);
}
}
} else if (userText.toLowerCase().startsWith('/revenue')) {
const args = userText.split(' ').slice(1).join(' ').trim().toLowerCase();
const dash = salesEngine.getDashboardData();
const rev = dash.revenue;
if (args === 'today') {
const today = new Date().toISOString().split('T')[0];
const todaySales = rev.dailyLog.filter(d => d.date === today);
const todayTotal = todaySales.reduce((s, d) => s + d.amount, 0);
await apiCaller.sendTelegramMessage(chatId,
`πŸ“Š <b>Today's Revenue</b>\n\n` +
`πŸ’΅ Rp ${todayTotal.toLocaleString()}\n` +
`πŸ›’ ${todaySales.length} transactions\n\n` +
(todaySales.length > 0 ? todaySales.map(d => `β€’ Rp ${d.amount.toLocaleString()} (${d.offerId})`).join('\n') : '<i>No sales today yet.</i>')
);
} else {
// Top offer by revenue
const topOffer = dash.activeOffers.sort((a, b) => (b.totalRevenue || 0) - (a.totalRevenue || 0))[0];
// Pillar breakdown
const pillarLines = Object.entries(rev.byPillar || {}).map(([p, amt]) => `β€’ ${p}: Rp ${amt.toLocaleString()}`).join('\n');
await apiCaller.sendTelegramMessage(chatId,
`πŸ’° <b>Revenue Dashboard</b>\n\n` +
`πŸ“ˆ Total: <b>Rp ${(rev.total || 0).toLocaleString()}</b>\n` +
`πŸ“… This Month: <b>Rp ${(rev.thisMonth || 0).toLocaleString()}</b>\n` +
`πŸ“¦ Active Offers: ${dash.activeCount}\n` +
`πŸ›’ Total Transactions: ${dash.recentTransactions.length}\n\n` +
(topOffer ? `πŸ† <b>Top Offer:</b> ${topOffer.title} (${topOffer.totalSales} sales, Rp ${(topOffer.totalRevenue || 0).toLocaleString()})\n\n` : '') +
(pillarLines ? `πŸ“Š <b>By Pillar:</b>\n${pillarLines}` : '')
);
}
} else if (userText.toLowerCase().startsWith('/costs')) {
const costs = costTracker.getCosts();
let msg = `πŸ’Έ <b>API Cost Tracker</b>\n\n`;
msg += `πŸ“Š <b>Total Spend:</b> $${(costs.total || 0).toFixed(4)}\n\n`;
const models = Object.entries(costs.by_model || {}).sort((a, b) => b[1] - a[1]);
if (models.length > 0) {
msg += `<b>By Model:</b>\n`;
for (const [model, cost] of models) {
msg += `β€’ ${model}: $${cost.toFixed(4)}\n`;
}
} else {
msg += `<i>No costs tracked yet.</i>`;
}
await apiCaller.sendTelegramMessage(chatId, msg);
} else if (userText.toLowerCase().startsWith('/scout')) {
const keyword = userText.split(' ').slice(1).join(' ').trim();
if (!keyword) {
await apiCaller.sendTelegramMessage(chatId, `❌ Usage: <code>/scout [keyword]</code>\nExample: /scout AI productivity tools`);
return;
}
await apiCaller.sendTelegramMessage(chatId, `πŸ” <b>Scouting:</b> "${keyword}"...\nAnalyzing competition, volume, and angles...`);
const result = await scoutAgent.runScout(keyword);
await apiCaller.sendTelegramMessage(chatId, `πŸ•΅οΈ <b>Scout Report: ${keyword}</b>\n\n${result}`);
} else if (userText.toLowerCase().startsWith('/landing')) {
const publicUrl = process.env.PUBLIC_URL || `https://${process.env.SPACE_ID || 'aigoose-vinos-engine'}.hf.space`;
await apiCaller.sendTelegramMessage(chatId, `πŸ”— <b>Your Landing Page:</b>\n${publicUrl}/landing\n\nShare this link as your link-in-bio!`);
} else {
await handleVinIntent(chatId, from, userText);
}
}
})().catch(err => console.error("Webhook error:", err));
});
// Resilient Startup
async function initializeVinOS() {
console.log("πŸš€ Initializing VinOS (Cloud Mode)...");
const PORT = process.env.PORT || 7860;
app.listen(PORT, '0.0.0.0', () => {
console.log(`βœ… VinOS Online: http://0.0.0.0:${PORT}`);
autoCron.startJobs();
});
try {
await setTelegramMenu();
} catch (e) {
console.error("⚠️ Startup components delayed:", e.message);
}
}
initializeVinOS();