import http from 'http'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = 8895; const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY; if (!ANTHROPIC_API_KEY) { console.error('❌ ANTHROPIC_API_KEY environment variable is required'); console.error(' Run: export ANTHROPIC_API_KEY=$(op item get "Anthropic API Key" --fields password --reveal)'); process.exit(1); } const server = http.createServer(async (req, res) => { // CORS headers res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } // Serve static files if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) { const html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf-8'); res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(html); return; } // Proxy: fetch a URL's content if (req.method === 'POST' && req.url === '/api/fetch-url') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', async () => { try { const { url } = JSON.parse(body); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 10000); const response = await fetch(url, { signal: controller.signal, headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }); clearTimeout(timeout); let html = await response.text(); // Strip scripts, styles, and extract meaningful text html = html .replace(//gi, '') .replace(//gi, '') .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim() .slice(0, 12000); // Limit context size res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ content: html, success: true })); } catch (err) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ content: '', success: false, error: err.message })); } }); return; } // Proxy: Claude API call if (req.method === 'POST' && req.url === '/api/generate') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', async () => { try { const { websiteContent, url } = JSON.parse(body); const systemPrompt = `You are a world-class creative director and brand strategist working at an elite AI advertising agency. Your job is to analyze a brand's website and generate brilliant ad creative concepts. You MUST respond with valid JSON only — no markdown, no code fences, no explanation outside the JSON. The JSON must match this exact schema: { "brand": { "name": "Brand Name", "tagline": "Their core tagline or value prop", "voice": "Description of brand voice (2-3 sentences)", "positioning": "Market positioning statement", "primaryColor": "#hexcolor (best guess from brand)", "secondaryColor": "#hexcolor", "accentColor": "#hexcolor", "industry": "Industry/category", "targetAudience": "Who they're targeting", "keyBenefits": ["benefit1", "benefit2", "benefit3"], "emotionalTone": "The emotional tone they use" }, "ads": { "meme": { "topText": "TOP TEXT FOR MEME (punchy, funny, relatable)", "bottomText": "BOTTOM TEXT PUNCHLINE", "context": "Brief description of what image would show" }, "iMessage": { "messages": [ {"sender": "friend", "text": "message text"}, {"sender": "user", "text": "response text"}, {"sender": "friend", "text": "another message"}, {"sender": "user", "text": "final response mentioning the brand naturally"} ] }, "tweet": { "handle": "@brandhandle", "displayName": "Display Name", "text": "Tweet text (max 280 chars, make it viral-worthy, include emoji)", "likes": "realistic number as string like 4.2K", "retweets": "realistic number as string like 1.1K", "replies": "realistic number as string", "views": "realistic number as string like 847K" }, "statCard": { "bigNumber": "A bold stat (e.g., '10x', '93%', '2.4M')", "label": "What the stat represents", "subtext": "Supporting context sentence", "source": "Source attribution" }, "ugc": { "reviewerName": "Realistic first name + last initial", "rating": 5, "title": "Review title", "body": "Authentic-sounding review (3-4 sentences, conversational, specific details)", "platform": "Where this review would appear", "verified": true }, "billboard": { "headline": "BOLD STATEMENT (5-8 words max, all caps impact)", "subline": "Supporting line underneath", "cta": "Call to action text" } } } Make the ad copy INCREDIBLE — it should feel like it came from a top creative agency. Each format should tell a different angle of the brand story. Be specific to the actual brand, not generic. Make the meme actually funny, the tweet actually viral-worthy, and the UGC review feel genuinely authentic.`; const userPrompt = `Analyze this website and generate ad creative concepts. Website URL: ${url} Website Content: ${websiteContent} Generate the JSON response with brand analysis and ad concepts. Remember: ONLY valid JSON, no other text.`; const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 4096, messages: [ { role: 'user', content: userPrompt } ], system: systemPrompt }) }); const data = await response.json(); if (data.error) { throw new Error(data.error.message || 'API error'); } // Extract text content from Claude's response const textContent = data.content.find(c => c.type === 'text'); let resultText = textContent?.text || ''; // Try to parse JSON from the response (handle markdown code fences) let parsed; try { // Strip markdown code fences if present resultText = resultText.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim(); parsed = JSON.parse(resultText); } catch (e) { // Try to find JSON in the response const jsonMatch = resultText.match(/\{[\s\S]*\}/); if (jsonMatch) { parsed = JSON.parse(jsonMatch[0]); } else { throw new Error('Failed to parse Claude response as JSON'); } } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true, data: parsed })); } catch (err) { console.error('Generate error:', err.message); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: false, error: err.message })); } }); return; } // 404 res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not found'); }); server.listen(PORT, () => { console.log(` ╔══════════════════════════════════════════════╗ ║ 🎨 Vibe Ads Creative Engine ║ ║ Running on http://localhost:${PORT} ║ ║ ║ ║ Open in your browser to start generating ║ ╚══════════════════════════════════════════════╝ `); });