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, "VinOS Daily Check-in\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, `[VinOS Dashboard] ${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 = `🤖 Intent: ${intent}\nParams: ${params || 'none'}\n\nShould I execute? (Reply: Confirm / Cancel)\nTip: Use /auto on for speed mode.`; return await apiCaller.sendTelegramMessage(chatId, confirmationMsg); } // 2. Route to appropriate skill switch (intent) { case 'remember': await apiCaller.sendTelegramMessage(chatId, "🧠 Designing playbook for: " + 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, `✅ Playbook Saved to HF Knowledge Bank.`); } 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 => `📌 ${pb.trigger}\n${pb.solution}`).join('\n\n'); await apiCaller.sendTelegramMessage(chatId, `🔍 Found:\n\n${results}`); } else { await apiCaller.sendTelegramMessage(chatId, `🤷 No playbooks found for "${params}".`); } break; case 'gash': const gashPrompt = params || userText; await apiCaller.sendTelegramMessage(chatId, "🎨 Creating your image..."); 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🔗 View on HF`; } catch (e) { console.error('[HF Auto] Image save failed:', e.message); } gashForm.append('caption', `✨ Image Created!\n${gashPrompt.substring(0, 100)}\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, "📊 Scanning market..."); await dailyPulse(); break; case 'offer': const topic = params || userText; await apiCaller.sendTelegramMessage(chatId, `💡 Architecting offer for: ${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, `📝 SEO Engine Pipeline\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, `⚠️ Pipeline Failed\n❌ Writer Error: ${articleRes.error}`); return; } if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■□□□□] 33% QC: Scoring EEAT Quality...`); const scoreRes = await seoWriter.scoreEEAT(articleRes.html); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■□□□] 50% Image Engine: Generating ${articleRes.imagePlaceholders?.length || 0} AI Images...`); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■□□] 66% Publishing: Uploading to [${targetSiteId}]...`); const pubRes = await wordpressPublisher.publishPost(articleRes, targetSiteId); if (!pubRes.success) { if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ Pipeline Failed\n❌ WordPress Error: ${pubRes.error}`); return; } if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■■□] 83% SEO Indexing: Google Search Console...`); const idxRes = await googleIndexer.submitUrl(pubRes.url); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■■■] 100% CRM Sync: Trello Workflow...`); const trelloRes = await trelloManager.logSeoPost(topic, targetSiteId || 'Default', pubRes.url, scoreRes.score || 'N/A'); let finalMsg = `🌟 Pipeline Complete!\n\n`; finalMsg += `🔗 Article: ${pubRes.url}\n`; if (scoreRes.success) finalMsg += `🧠 EEAT Score: ${scoreRes.score}/100\n`; if (idxRes.success) finalMsg += `🔍 Google: Indexed 🟢\n`; else finalMsg += `🔍 Google: Pending 🟡 (${idxRes.error})\n`; if (trelloRes.success) finalMsg += `📊 Trello: View Card 📋\n`; else finalMsg += `📊 Trello: Failed 🔴\n`; // Check for matching sales offer → suggest creating one if none try { const offerLink = salesEngine.injectOfferLink(topic); if (offerLink) { finalMsg += `\n💰 Offer CTA injected: ${offerLink}\n`; } else { finalMsg += `\n💡 No matching offer for "${topic}". Create one? /offer ${topic}\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, `⚠️ Write Pipeline Error: ${err.message}`); } } // --- Helper: handle /research context reply --- async function _handleResearch(chatId, url, angle) { await apiCaller.sendTelegramMessage(chatId, `🔍 Research Auto-Pilot\nAnalyzing URL with focus: ${angle || 'General'}...`); 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', `✅ Research Draft Ready!\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, `✅ Research Draft Ready!\n\n${v1.ig_en.substring(0, 800)}...\n\nReview in Dashboard.`); } } catch (err) { console.error('[_handleResearch] Error:', err.message); await apiCaller.sendTelegramMessage(chatId, `⚠️ Research Error: ${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, '🎤 Transcribing...'); 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, `🎤 "${userText}"`); 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, '📋 QUANTA Pipeline\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, '📋 QUANTA Pipeline\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, '📋 QUANTA Pipeline\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, '📋 QUANTA Pipeline\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, '📋 QUANTA Pipeline\n[■■■□□□] 45% Quanta is architecting your proposal...'); const data = await quantaBridge.submitRFQ(rfqText, chatId, from); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, '📋 QUANTA Pipeline\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, `⚠️ File Analysis Error: ${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, `🔄 Previous workflow cancelled. Starting new command.`); } // --- 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: ${funnel}\n\nNow, language?\n\n` + `1️⃣ Both (EN + ID)\n2️⃣ EN only\n3️⃣ ID 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: ${lang}\n\nWhat's the core message you want to convey?\n\n` + `(e.g. "AI can save 10 hours/week" or type skip 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, `🚀 Generating content...\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, `✅ Draft Created!\n\n` + `Title: ${v1.title}\n` + `Pillar: ${v1.pillar}\n` + `Stage: ${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, '⏱ Write session expired. Please send /write again.'); 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, `📋 QUANTA Proposal Generator\n\n/proposal [RFQ text] — Generate bilingual proposal\n/proposal status [id] — Check project status\n/proposal list — 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, '📋 QUANTA Pipeline\n[■□□□□□] 15% Receiving RFQ details...'); const msgId = initRes.messageId; if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, '📋 QUANTA Pipeline\n[■■■□□□] 45% Quanta is architecting your proposal...'); const data = await quantaBridge.submitRFQ(args, chatId, from); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, '📋 QUANTA Pipeline\n[■■■■■□] 85% Generating final summary...'); await apiCaller.sendTelegramMessage(chatId, quantaBridge.formatProposalSummary(data)); } catch (e) { console.error(`[QUANTA] Error for ${chatId}:`, e.message); let errorMsg = `⚠️ QUANTA Error\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 /proposal list 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, `🔍 Web Scraper\n\n/scrape [url] — Scrape website\n/scrape yt [video-url] — YouTube transcript\n/scrape ig [username] — Instagram profile\n/scrape tt [username] — TikTok profile\n/scrape audit [domain] — 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, "🚀 Infrastructure Sync Started..."); const res = await hfDeployer.syncCode(); await apiCaller.sendTelegramMessage(chatId, res.success ? "✅ Deployment Successful." : `❌ Sync Failed: ${res.error}`); return; } if (userText === '/social' || userText === '/post') { const socialGuide = `📱 Social Media Instructions (v1.0) To get the highest engagement from the AI Remixer, feed it raw text or ideas using this framework: 1. The Hook (First 1-3 lines) Use a pattern interrupt, a bold claim, or an open loop. Example: "Stop posting daily. It's killing your reach. Do this instead:" 2. The Value (The Meat) Provide 3-5 specific, actionable points or a storytelling arc (Situation → Challenge → Insight). 3. The Format Request (Optional) Tell the AI what emotion or 'edge' you want. Example: "Remix this into a contrarian take on AI productivity." 4. Image Prompting • 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). Try sending me a URL or text paragraph right now to brainstorm!`; 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 = `🔍 Research Auto-Pilot — v2.0 This engine researches any URL or topic and generates content with your options. Usage: /research [URL or topic] /research [URL or topic] smart — use Sonar Pro (deeper) Supported: URLs (X, IG, Threads, blogs) OR text topics Flow: 1. Research → 2. TOFU/MOFU/BOFU → 3. Language → 4. Core message → 5. Generate Try: /research AI productivity tips`; 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, `🔍 Research: ${input.substring(0, 50)}${input.length > 50 ? '...' : ''}\n` + `Tier: ${tier === 'smart' ? 'Smart (Sonar Pro)' : 'Fast (Sonar)'}\n\n` + `What's the funnel stage?\n\n` + `1️⃣ TOFU — Awareness (grow audience)\n` + `2️⃣ MOFU — Consideration (educate, build trust)\n` + `3️⃣ BOFU — Conversion (drive action)\n\n` + `Reply: 1, 2, 3, or TOFU/MOFU/BOFU` ); 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, `🤖 Social Content Model\n\nCurrent: ${tierInfo.name} (${tierName})\nCost: ${tierInfo.cost}\n\nSwitch with:\n/socialmodel premium — Claude Haiku 4.5 (SOTA human tone)\n/socialmodel standard — Gemini Flash Lite (good daily driver)\n/socialmodel free — Llama 3.3 70B (default, zero cost)`); return; } const tier = tierMap[arg]; if (!tier) { await apiCaller.sendTelegramMessage(chatId, '❌ Invalid tier. Use: /socialmodel premium|standard|free'); return; } db.user_profile_snapshot.social_content_model = tier.id; memory.writeDB(db); await apiCaller.sendTelegramMessage(chatId, `✅ Social Model → ${tier.name}\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, '🔍 Competitor Research\n\nUsage: /compete [URL or topic]\n\nAnalyzes top creators and extracts winning patterns.\n\nExamples:\n/compete https://instagram.com/creator\n/compete AI productivity tips'); 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 = `📊 Competitor Insights${result.cached ? ' (cached)' : ''}\n\n`; if (p.keyInsight) msg += `💡 Key Insight: ${p.keyInsight}\n\n`; if (p.topPostTypes) msg += `Top Post Types: ${p.topPostTypes.map(t => `${t.type} (${t.pct}%)`).join(', ')}\n`; if (p.bestHookStyles) msg += `Best Hooks: ${p.bestHookStyles.map(h => `${h.style} (${h.pct}%)`).join(', ')}\n`; if (p.bestTimes) msg += `Best Times: ${p.bestTimes.join(', ')}\n`; if (p.engagementDrivers) msg += `\nEngagement Drivers:\n${p.engagementDrivers.map(d => `• ${d}`).join('\n')}\n`; if (p.recommendation) msg += `\nRecommendation: ${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, `⚙️ Pulse Configuration\n\n` + `Enabled: ${c.enabled ? 'Yes' : 'No'}\n` + `Time: ${c.time} WIB\n` + `Sections: ${(c.include || []).join(', ')}\n\n` + `Commands:\n` + `/pulseconfig on or off\n` + `/pulseconfig time 08:30\n` + `/pulseconfig include social_analytics,market_trends` ); 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 ${time} WIB.`); } 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: ${sections.join(', ')}`); 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, "⚡ Turbo Mode Activated!\nNow using Gemini 2.0 Flash Lite 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, "🧠 Reasoning Mode Activated!\nNow using NVIDIA Nemotron Nano 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 ? '⚡ Auto Mode ON — Intent actions run instantly without confirmation.' : '🛑 Auto Mode OFF — 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, `🐟 MiroFish Sentiment Engine\n\n` + `Usage:\n` + `/sentiment [topic]\n` + `/sentiment [topic] | [country] | [id/en]\n\n` + `Examples:\n` + `/sentiment TikTok Shop ban\n` + `/sentiment Shopee ads | Indonesia | id\n` + `/sentiment crypto regulation | USA | en\n\n` + `Angles: ads, sentiment, market, policy\n` + `/sentiment EV subsidies | Indonesia | id | policy\n\n` + `Default: Indonesia, Bahasa Indonesia, sentiment angle` ); 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, `🐟 MiroFish Sentiment Engine\n\n` + `📌 Topic: ${topic}\n` + `🌍 Market: ${country}\n` + `🗣️ Language: ${langLabel}\n` + `🎯 Angle: ${angle}\n\n` + `Running quick sentiment analysis...` ); // 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... (detailed PDF generating...)' : result.analysis; await apiCaller.sendTelegramMessage(chatId, summary); await apiCaller.sendTelegramMessage(chatId, `📄 Generating enhanced PDF with deep analysis...`); // 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, `✅ Sentiment analysis complete!\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, `🐟 MiroFish Deep Simulation Complete!\n\n` + `Project: ${simResult.project_id}\n` + `Simulation: ${simResult.simulation_id}\n\n` + `Full swarm intelligence report is ready in MiroFish dashboard.` ); } }) .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, `📤 /push — Manual HF Upload\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, `📤 Pushing to HF dataset...`); 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, `📤 Push Complete\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, "✅ Confirmed. 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, "🤝 Cancelled."); } } if (message.voice) { await apiCaller.sendTelegramMessage(chatId, "🎤 Transcribing..."); 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, `📸 Face Mode Activated!\nReference photo received. Generating style transfer with face preservation...\n${gashFaceArgs.substring(0, 80)}`); 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🔗 View on HF`; } 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', `✨ Face Mode Result!\n🔧 ${faceResult.source}\n${gashFaceArgs.substring(0, 100)}${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 = `🦍 VinOS Command Center — v4.1.0\n` + `Current Mode: ${modeEmoji} ${modeName}\n\n` + `📊 STRATEGY\n` + `/report — Interactive Research → PDF Report\n` + `Vin asks: topic → depth → focus → confirm\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` + `Output: McKinsey-style PDF sent to this chat\n\n` + `🎨 CREATE\n` + `/gash [prompt] — AI Image Creator\n\n` + `🧪 ENGINES\n` + `/turbo — Switch to Fast Mode\n` + `/reason — Switch to Think Mode\n\n` + `🚀 DEPLOY\n` + `/sync — Push code to HF Space\n` + `/push [db|reports|all] — Push data to HF Dataset\n\n` + `🧠 KNOWLEDGE\n` + `"remember [x]" — Save a playbook\n\n` + `🛠️ SYSTEM\n` + `/newskill [name] [desc] — Auto-code an agent\n` + `/agents — List active agent skills\n\n` + `📝 SEO ENGINE\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` + `💰 SALES ENGINE\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` + `🎬 VIDEO ENGINE\n` + `/videos create [topic] — AI-generate + render video\n` + `/videos templates — List video templates\n` + `/videos status — Render queue status\n\n` + `🔍 RESEARCH\n` + `/scout [keyword] — Market viability scout\n` + `/costs — API spend breakdown\n` + `/landing — Your link-in-bio page\n\n` + `🎯 CEO AGENT\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` + `📋 CRM MANAGER\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, `🛠️ Architecting Skill: ${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, `✅ Skill '${saveRes.name}' Created!\nCode saved to skills/${saveRes.name}.js\n\nRun /sync to push to cloud, then register it in server.js.`); } 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 => `• ${f}`).join('\n'); await apiCaller.sendTelegramMessage(chatId, `🤖 Active Skills (${files.length}):\n\n${list}`); } else if (userText.toLowerCase().startsWith('/gash')) { const gashArgs = userText.split(' ').slice(1).join(' '); if (!gashArgs) { await apiCaller.sendTelegramMessage(chatId, `🎨 /gash — AI Image Creator\n\n` + `Usage:\n` + `/gash [prompt] [ratio]\n\n` + `Ratios: 9:16, 16:9, 4:5, 4:3, 1:1 (default)\n\n` + `Examples:\n` + `/gash A futuristic city at sunset 16:9\n` + `/gash Professional headshot minimalistic 4:5\n\n` + `📸 Face Mode: Send a photo with caption /gash [prompt] 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, `🎨 Creating image (${ratio})...\n${gashPrompt.substring(0, 80)}`); // 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🔗 View on HF`; } 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', `✨ Image Created!\n📐 ${ratio}\n🔧 ${gashResult.source}\n${gashPrompt.substring(0, 100)}${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: /seo2offer [keyword]\nFull pipeline: scout → offer → article → publish → index`); return; } await apiCaller.sendTelegramMessage(chatId, `🚀 SEO-to-Revenue Pipeline\n[■□□□□□] Scouting: "${keyword}"...`); // 1. Scout market viability let scoutResult; try { scoutResult = await scoutAgent.runScout(keyword); } catch (e) { scoutResult = 'Scout unavailable'; } await apiCaller.sendTelegramMessage(chatId, `🚀 SEO-to-Revenue Pipeline\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, `🚀 SEO-to-Revenue Pipeline\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🕵️ Scout:\n${scoutResult}`); return; } await apiCaller.sendTelegramMessage(chatId, `🚀 SEO-to-Revenue Pipeline\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🕵️ Scout:\n${scoutResult}`); return; } await apiCaller.sendTelegramMessage(chatId, `🚀 SEO-to-Revenue Pipeline\n[■■■■■□] Indexing on Google...`); // 5. Index const idxRes = await googleIndexer.submitUrl(pubRes.url); // 6. Final report let finalMsg = `🎯 SEO-to-Revenue Complete!\n\n`; finalMsg += `🔗 Article: ${pubRes.url}\n`; finalMsg += `🔍 Google: ${idxRes.success ? 'Indexed 🟢' : 'Pending 🟡'}\n`; finalMsg += `${offerMsg}\n\n`; finalMsg += `🕵️ Scout Report:\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, `🗺️ SEO Engine: Mapping Pillar Strategy for "${keyword}"...`); const strategyRes = await seoWriter.architectSeoStrategy(keyword); if (strategyRes.success) { await apiCaller.sendTelegramMessage(chatId, `✅ SEO Blueprint Generated:\n\n${strategyRes.data}\n\nTo write an article for any of these clusters, reply with /write [Choose a Blog Title]`); } 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 = `⚠️ Missing Parameters for /write\n\n` + `To architect an elite SEO article, I need instructions. Please use quotes for multi-word parameters.\n\n` + `Structure:\n` + `/write [SiteID] "[Topic]" [Size] "[POV]" "[Intent]" "[Tone]"\n\n` + `Available Site IDs: ${siteKeys.join(', ') || 'Configure WP_SITES_JSON'}\n` + `Sizes: short, medium, dense\n` + `POV: "1st person singular", "1st person plural", "2nd person", "3rd person"\n\n` + `Example:\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, `🤔 Context Check for "${topic}"\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 "go" 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, `🔍 Research Auto-Pilot\nAnalyzing URL with focus: ${angle || 'General'}...`); 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', `✅ Research Draft Ready!\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, `✅ Research Draft Ready!\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, `📝 SEO Engine Pipeline\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, `📝 SEO Engine Pipeline\n[■■□□□□] 33% QC: Scoring EEAT Quality...`); const scoreRes = await seoWriter.scoreEEAT(articleRes.html); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■□□□] 50% Image Engine: Generating ${articleRes.imagePlaceholders?.length || 0} AI Images...`); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■□□] 66% Publishing: Uploading to [${targetSiteId}]...`); const pubRes = await wordpressPublisher.publishPost(articleRes, targetSiteId); if (pubRes.success) { if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■■□] 83% SEO Indexing: Google Search Console...`); const idxRes = await googleIndexer.submitUrl(pubRes.url); if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `📝 SEO Engine Pipeline\n[■■■■■■] 100% CRM Sync: Trello Workflow...`); const trelloRes = await trelloManager.logSeoPost(topic, targetSiteId || 'Default', pubRes.url, scoreRes.score || 'N/A'); let finalMsg = `🌟 Pipeline Complete!\n\n`; finalMsg += `🔗 Article: ${pubRes.url}\n`; if (scoreRes.success) finalMsg += `🧠 EEAT Score: ${scoreRes.score}/100\n`; if (idxRes.success) finalMsg += `🔍 Google: Indexed 🟢\n`; else finalMsg += `🔍 Google: Pending 🟡 (${idxRes.error})\n`; if (trelloRes.success) { finalMsg += `📊 Trello: View Card 📋\n`; } else { finalMsg += `📊 Trello: 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, `⚠️ Pipeline Failed\n❌ WordPress Error: ${pubRes.error}`); } } else { if (msgId) await apiCaller.editTelegramMessage(chatId, msgId, `⚠️ Pipeline Failed\n❌ Writer Error: ${articleRes.error}`); } } else if (userText.toLowerCase().startsWith('/updates')) { await apiCaller.sendTelegramMessage(chatId, `🔍 Checking Trello CRM...`); 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, `🚀 Attempting to move card...`); const moveRes = await trelloManager.moveCard(shortId, newListName); if (moveRes.success) { await apiCaller.sendTelegramMessage(chatId, `✅ Success!\nMoved card "${moveRes.cardName}" to 📁 ${moveRes.listName}.`); } 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, `✅ Successfully submitted to Google.`); } else { await apiCaller.sendTelegramMessage(chatId, `❌ Indexing Failed: ${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: /offer [topic]'); } else { const lines = dash.activeOffers.map(o => `• ${o.title}\n 💵 Rp ${(o.priceIDR || 0).toLocaleString()} | 📊 ${o.totalSales} sales | 💰 Rp ${(o.totalRevenue || 0).toLocaleString()}\n ${o.paymentUrl ? `🔗 ${o.paymentUrl}` : '⏳ Link pending'}\n ${o.id}` ).join('\n\n'); await apiCaller.sendTelegramMessage(chatId, `📦 Active Offers (${dash.activeCount})\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: ${result.offer.title}`); } 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: ${result.offer.title}`); } 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, `📋 Offer Pipeline\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, `🧪 Creating variant for ${offerId}...`); 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 `🔀 ${v.id}: ${v.title}\n 📊 ${v.sales} sales | ${v.clicks} clicks | ${convRate}% conv | ${v.isActive ? '🟢 Active' : '🔴 Paused'}`; }).join('\n\n'); await apiCaller.sendTelegramMessage(chatId, `🧪 Variants: ${result.offerTitle || offerId}\n\n${lines}`); } } else if (args.toLowerCase().startsWith('eval')) { // /offer eval — run evaluation await apiCaller.sendTelegramMessage(chatId, '📊 Evaluating offers...'); 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, `💰 Creating offer for: ${args}...\nAI copy → Trend check → Mayar link`); const result = await offerArchitect(args); if (result.success) { // Notification already sent by salesEngine } else { const msg = result.verdict === 'wait' ? `⏳ Topic queued for re-check\nScore: ${result.score}/100\n${result.reason}` : result.verdict === 'skip' ? `⏭️ Topic skipped\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, `📊 Today's Revenue\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') : 'No sales today yet.') ); } 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, `💰 Revenue Dashboard\n\n` + `📈 Total: Rp ${(rev.total || 0).toLocaleString()}\n` + `📅 This Month: Rp ${(rev.thisMonth || 0).toLocaleString()}\n` + `📦 Active Offers: ${dash.activeCount}\n` + `🛒 Total Transactions: ${dash.recentTransactions.length}\n\n` + (topOffer ? `🏆 Top Offer: ${topOffer.title} (${topOffer.totalSales} sales, Rp ${(topOffer.totalRevenue || 0).toLocaleString()})\n\n` : '') + (pillarLines ? `📊 By Pillar:\n${pillarLines}` : '') ); } } else if (userText.toLowerCase().startsWith('/costs')) { const costs = costTracker.getCosts(); let msg = `💸 API Cost Tracker\n\n`; msg += `📊 Total Spend: $${(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 += `By Model:\n`; for (const [model, cost] of models) { msg += `• ${model}: $${cost.toFixed(4)}\n`; } } else { msg += `No costs tracked yet.`; } 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: /scout [keyword]\nExample: /scout AI productivity tools`); return; } await apiCaller.sendTelegramMessage(chatId, `🔍 Scouting: "${keyword}"...\nAnalyzing competition, volume, and angles...`); const result = await scoutAgent.runScout(keyword); await apiCaller.sendTelegramMessage(chatId, `🕵️ Scout Report: ${keyword}\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, `🔗 Your Landing Page:\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();