Skill: ashmonmc/insta-content-engine (version 1.0.0)

--- name: social-search description: Find trending topics, create editorial-style social media graphics, and post to X/Twitter and Instagram. Includes image generation with photographic backgrounds, dark gradient overlays, and bold typography. No paid social APIs needed. --- # Social Search & Content Creator Find → Create → Post pipeline for X/Twitter and Instagram content. ## Prerequisites - **X/Twitter**: `bird` CLI installed and authenticated via browser cookies - **Instagram Search**: Brave Search API key (set `BRAVE_API_KEY` or configure in OpenClaw) - **Image Generation**: OpenAI API key (set `OPENAI_API_KEY`) — uses gpt-image-1 - **Instagram Posting**: `pip3 install instagrapi pillow` + IG_USERNAME/IG_PASSWORD env vars ## 1. FIND — Research Trending Topics ### X/Twitter (bird CLI) ```bash bird search "query" -n 10 # Search tweets bird read <url-or-id> # Read a specific tweet bird thread <url-or-id> # Full thread bird mentions # Check mentions bird user-tweets <username> # User's tweets ``` ### Instagram (Brave-powered scraper) ```bash node {baseDir}/scripts/instagram-search.js "query" [--limit 10] [--type posts|profiles|reels|all] ``` Types: `posts` (inurl:/p/), `profiles` (user pages), `reels` (inurl:/reel/), `all` (default) ### Viral/Trending Search (recommended starting point) ```bash # Find viral content about a topic (sorted by engagement) node {baseDir}/scripts/viral-search.js "query" [options] # Options: # --platform x|instagram|both (default: both) # --min-likes N Min likes to include (default: 50) # --min-retweets N Min retweets (default: 10) # --sort engagement|recent (default: engagement) # --limit N Results per platform (default: 10) # --trending What's blowing up RIGHT NOW on X # --days N Only last N days (default: 7) ``` Examples: ```bash # What's viral about AI trading this week node {baseDir}/scripts/viral-search.js "AI trading" --min-likes 100 --days 7 # What's trending across crypto right now (no query needed) node {baseDir}/scripts/viral-search.js --trending # Instagram-only viral search node {baseDir}/scripts/viral-search.js "DeFi" --platform instagram --limit 10 # Lower the bar for niche topics node {baseDir}/scripts/viral-search.js "delta neutral farming" --min-likes 10 --min-retweets 3 ``` **How engagement scoring works:** - Score = likes × 1 + retweets × 3 + replies × 2 - Retweets weighted highest (distribution signal) - Replies weighted over likes (conversation signal) - Results sorted by score descending ## 2. CREATE — Generate Editorial Graphics ### The System: Photographic Background + Dark Gradient + Bold Typography **This is what makes the images look professional, not just text on a background.** The approach: - **Top 60%**: Cinematic photographic scene (real subjects, dramatic lighting) - **Bottom 40%**: Heavy dark gradient overlay (95% black → transparent) - **Text**: Bold condensed typography (Anton-style) sitting on the gradient - **Result**: Looks like Bloomberg/Kalshi editorial cards ### Generate Images ```bash OPENAI_API_KEY="your-key" python3 {baseDir}/scripts/gen_image.py "prompt" output.png [1024x1536] ``` Sizes: `1024x1024` (square), `1024x1536` (portrait/Instagram), `1536x1024` (landscape/Twitter) ### ⚠️ CRITICAL IMAGE RULES (learned the hard way) 1. **NEVER just put text on a solid dark background** — it looks flat and amateur 2. **ALWAYS use a photographic/cinematic background** — real scenes, real subjects 3. **ALWAYS use dark gradient overlay** on the lower 40% for text readability 4. **Keep subject visible** in the top 60% — face, setting, context 5. **Text lives in the bottom 40% only** — never cover the subject 6. **Use bold condensed typography** (Anton-style) — not regular weight fonts 7. **Include branded elements** — badge, branding URL, slide counter 8. **Cinematic lighting** — moody, dramatic, not flat studio lighting ### Master Prompt Template Copy this template exactly. Only change the parts in [BRACKETS]. ``` Instagram carousel post design. EXACTLY 1080 pixels wide by 1350 pixels tall (4:5 vertical ratio). Background: [DESCRIBE THE SCENE - who/what is shown, setting, lighting, mood]. Keep subject clearly visible in top 60% of image. Cinematic lighting, dramatic. Overlay: Heavy dark gradient ONLY on bottom 40% of image - solid black (95% opacity) at very bottom, fading to transparent at 60% height. Top portion stays clear. Text positioning (bottom area): - 65% from top: Small rectangular badge with [COLOR] background and [TEXT COLOR] bold text: [BADGE TEXT] - 70-90% from top: Huge white bold text in Anton font, uppercase, centered: [MAIN HEADLINE SPLIT ACROSS 2-3 LINES] - [Optional: Below headline in smaller white text, any supporting details] - Bottom right corner (95% from top): [X/Y] in white, small - Bottom left corner (95% from top): [brand.url] in white, Montserrat Bold, small, lowercase Font: Anton for headline. Montserrat Bold for branding. Style: Clean Instagram carousel slide, vertical format, text at bottom like Kalshi/Bloomberg editorial style. Cinematic, high contrast, professional. ``` ### Badge Color System - 🔵 Cyan (#00D4FF): "EXPLAINED", "GUIDE", educational content - 🟠 Orange (#FF6B00): "HOW IT WORKS", "DEEP DIVE", breakdowns - 🟢 Green (#22c55e): "THE SHIFT", "SOLUTION", "REAL CASE", positive takes - 🔴 Red (#e63946): "WARNING", "UNFAIR", "MYTH", negative/urgent - ⚫ Black (#000000): "TRENDING", "BREAKING", news ### Background Scene Ideas by Topic **Crypto/Trading**: Trader at multiple monitors, trading floor, candlestick charts on screens **Tech/AI**: Neural network visualizations, server rooms, holographic displays **Finance**: City skyline at night, office with Bloomberg terminals, stock exchange **Startup**: Founder at whiteboard, team in modern office, product on screen **Education**: Person studying, lecture hall, books with highlighted text ### Carousel Structure (3 slides) - **Slide 1 — The Hook**: Sets the scene, asks the question (e.g., "What is X?") - **Slide 2 — The Breakdown**: How it works, the layers, the details - **Slide 3 — The Shift**: Why it matters, the bigger picture, call to action ### Example: Crypto Educational Carousel **Slide 1 prompt background**: "A young male trader in a dark room lit by multiple monitors showing crypto charts. Blue and green screen glow on his face. Hoodie, leaning forward, focused." **Slide 2 prompt background**: "Close-up of an AI neural network visualization on a large curved monitor in a dark trading room. Neon blue data streams flowing across the screen." **Slide 3 prompt background**: "Aerial view of a massive trading floor with hundreds of screens running autonomously, seats empty. No humans present. Dramatic overhead shot, neon blue glow." ## 3. POST — Publish Content ### X/Twitter (bird CLI) ```bash bird tweet "Your tweet text" # Text only bird tweet "Text" --media image.jpg # With image bird tweet "Text" --media img1.jpg --media img2.jpg # Multiple bird reply <tweet-id-or-url> "Reply text" # Reply ``` ### Instagram (instagrapi) ```bash # Photo post node {baseDir}/scripts/instagram-post.js --action photo --caption "Caption" image.jpg # Reel node {baseDir}/scripts/instagram-post.js --action reel --caption "Caption" video.mp4 # Story node {baseDir}/scripts/instagram-post.js --action story --caption "Caption" image.jpg # Carousel node {baseDir}/scripts/instagram-post.js --action carousel --caption "Caption" img1.jpg img2.jpg img3.jpg ``` Instagram auth: Set `IG_USERNAME` and `IG_PASSWORD` env vars. Google sign-in won't work — need a password set on the account. ## Full Workflow Example ```bash # 1. Research what's trending bird search "agentic trading crypto" -n 10 node {baseDir}/scripts/instagram-search.js "AI crypto trading" --limit 10 # 2. Generate 3-slide carousel OPENAI_API_KEY="key" python3 {baseDir}/scripts/gen_image.py "slide 1 prompt..." slide1.png 1024x1536 OPENAI_API_KEY="key" python3 {baseDir}/scripts/gen_image.py "slide 2 prompt..." slide2.png 1024x1536 OPENAI_API_KEY="key" python3 {baseDir}/scripts/gen_image.py "slide 3 prompt..." slide3.png 1024x1536 # 3. Post to X bird tweet "your caption" --media slide1.png --media slide2.png --media slide3.png # 4. Post to Instagram node {baseDir}/scripts/instagram-post.js --action carousel --caption "your caption" slide1.png slide2.png slide3.png ```

path: scripts/gen_image.py
content: #!/usr/bin/env python3
"""Generate images using OpenAI's gpt-image-1 model.

Usage:
    python3 gen_image.py "your prompt here" output.png
    python3 gen_image.py "your prompt here" output.png 1024x1536

Sizes: 1024x1024 (square), 1024x1536 (portrait/IG), 1536x1024 (landscape/Twitter)

Requires OPENAI_API_KEY environment variable.
"""
import sys, os, json, base64, urllib.request

if len(sys.argv) < 3:
    print("Usage: gen_image.py \"prompt\" output.png [size]")
    print("Sizes: 1024x1024, 1024x1536 (portrait), 1536x1024 (landscape)")
    sys.exit(1)

prompt = sys.argv[1]
output = sys.argv[2]
size = sys.argv[3] if len(sys.argv) > 3 else "1024x1536"

[REDACTED_SECRET]"OPENAI_API_KEY")
if not [REDACTED_SECRET]"Error: OPENAI_API_KEY not set")
    print("Set it: export OPENAI_API_KEY='your-key-here'")
    sys.exit(1)

valid_sizes = ["1024x1024", "1024x1536", "1536x1024"]
if size not in valid_sizes:
    print(f"Error: Invalid size '{size}'. Use: {', '.join(valid_sizes)}")
    sys.exit(1)

data = json.dumps({
    "model": "gpt-image-1",
    "prompt": prompt,
    "n": 1,
    "size": size,
    "quality": "high"
}).encode()

req = urllib.request.Request(
    "https://api.openai.com/v1/images/generations",
    data=data,
    headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}"
    }
)

print(f"Generating image ({size})...")
try:
    resp = urllib.request.urlopen(req, timeout=120)
    result = json.loads(resp.read())
    b64 = result["data"][0]["b64_json"]
    with open(output, "wb") as f:
        f.write(base64.b64decode(b64))
    print(f"MEDIA:{output}")
    print(f"Saved to {output}")
except urllib.error.HTTPError as e:
    error_body = e.read().decode()
    print(f"Error: {e.code} - {error_body}")
    sys.exit(1)
except Exception as e:
    print(f"Error: {e}")
    sys.exit(1)


path: scripts/instagram-post.js
content: #!/usr/bin/env node
/**
 * Instagram Post via instagrapi (Python)
 * Posts photos, reels, carousels, and stories to Instagram
 * 
 * Requires: pip3 install instagrapi pillow
 * Auth: Set IG_USERNAME and IG_PASSWORD env vars, or pass --username/--password
 */

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

// Parse args
const args = process.argv.slice(2);
let action = 'photo'; // photo, reel, story, carousel
let caption = '';
let mediaFiles = [];
let username = process.env.IG_USERNAME || '';
let [REDACTED_SECRET] || '';
let sessionFile = path.join(process.env.HOME || '', '.openclaw', 'ig_session.json');

for (let i = 0; i < args.length; i++) {
  if (args[i] === '--action' && args[i + 1]) { action = args[i + 1]; i++; }
  else if (args[i] === '--caption' && args[i + 1]) { caption = args[i + 1]; i++; }
  else if (args[i] === '--media' && args[i + 1]) { mediaFiles.push(args[i + 1]); i++; }
  else if (args[i] === '--username' && args[i + 1]) { username = args[i + 1]; i++; }
  else if (args[i] === '--password' && args[i + 1]) { [REDACTED_SECRET] + 1]; i++; }
  else if (args[i] === '--session' && args[i + 1]) { sessionFile = args[i + 1]; i++; }
  else if (!args[i].startsWith('--')) { mediaFiles.push(args[i]); }
}

if (mediaFiles.length === 0) {
  console.error(`Usage: instagram-post.js [--action photo|reel|story|carousel] [--caption "text"] [--username x] [--password x] <media_file(s)>`);
  console.error(`\nActions:`);
  console.error(`  photo     - Post a single photo (default)`);
  console.error(`  reel      - Post a video reel`);
  console.error(`  story     - Post a story (photo or video)`);
  console.error(`  carousel  - Post multiple photos as carousel`);
  console.error(`\nEnv vars: IG_USERNAME, IG_PASSWORD`);
  process.exit(1);
}

if (!username || !password) {
  console.error('❌ Instagram credentials required.');
  console.error('   Set IG_USERNAME and IG_PASSWORD env vars');
  console.error('   Or use --username and --password flags');
  process.exit(1);
}

// Validate media files exist
for (const f of mediaFiles) {
  if (!fs.existsSync(f)) {
    console.error(`❌ File not found: ${f}`);
    process.exit(1);
  }
}

// Build Python script
const escapePy = (s) => s.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n');
const mediaListPy = mediaFiles.map(f => `'${escapePy(path.resolve(f))}'`).join(', ');

const pyScript = `
import json, os, sys
from pathlib import Path
from instagrapi import Client

cl = Client()
session_file = '${escapePy(sessionFile)}'

# Try loading session
if os.path.exists(session_file):
    try:
        cl.load_settings(session_file)
        cl.login('${escapePy(username)}', '${escapePy(password)}')
        cl.get_timeline_feed()  # test session
        print("✅ Logged in via saved session")
    except Exception as e:
        print(f"⚠️ Session expired, re-logging: {e}")
        cl = Client()
        cl.login('${escapePy(username)}', '${escapePy(password)}')
        cl.dump_settings(session_file)
else:
    cl.login('${escapePy(username)}', '${escapePy(password)}')
    cl.dump_settings(session_file)
    print("✅ Logged in fresh, session saved")

caption = '''${escapePy(caption)}'''
media_files = [${mediaListPy}]
action = '${action}'

try:
    if action == 'photo':
        result = cl.photo_upload(media_files[0], caption)
        print(f"📸 Photo posted! https://instagram.com/p/{result.code}")
    elif action == 'reel':
        result = cl.clip_upload(media_files[0], caption)
        print(f"🎬 Reel posted! https://instagram.com/reel/{result.code}")
    elif action == 'story':
        ext = Path(media_files[0]).suffix.lower()
        if ext in ['.mp4', '.mov']:
            result = cl.video_upload_to_story(media_files[0], caption)
        else:
            result = cl.photo_upload_to_story(media_files[0], caption)
        print(f"📖 Story posted!")
    elif action == 'carousel':
        result = cl.album_upload(media_files, caption)
        print(f"🎠 Carousel posted! https://instagram.com/p/{result.code}")
    else:
        print(f"❌ Unknown action: {action}")
        sys.exit(1)
except Exception as e:
    print(f"❌ Post failed: {e}")
    sys.exit(1)
`;

console.log(`📤 Posting ${action} to Instagram...`);

try {
  const result = execSync(`python3 -c '${pyScript.replace(/'/g, "'\"'\"'")}'`, {
    encoding: 'utf8',
    timeout: 120000,
    stdio: ['pipe', 'pipe', 'pipe'],
  });
  console.log(result);
} catch (e) {
  console.error('❌ Failed:', e.stderr || e.message);
  process.exit(1);
}


path: scripts/instagram-search.js
content: #!/usr/bin/env node
/**
 * Instagram Search via Brave Search API
 * Searches site:instagram.com for posts, profiles, and reels
 * No Instagram API or paid keys needed — uses Brave Search
 */

const https = require('https');

// Parse args
const args = process.argv.slice(2);
let query = '';
let limit = 10;
let type = 'all'; // posts, profiles, reels, all

for (let i = 0; i < args.length; i++) {
  if (args[i] === '--limit' && args[i + 1]) {
    limit = parseInt(args[i + 1], 10);
    i++;
  } else if (args[i] === '--type' && args[i + 1]) {
    type = args[i + 1];
    i++;
  } else if (!args[i].startsWith('--')) {
    query += (query ? ' ' : '') + args[i];
  }
}

if (!query) {
  console.error('Usage: instagram-search.js "query" [--limit N] [--type posts|profiles|reels|all]');
  process.exit(1);
}

// Build site-scoped query
let siteQuery;
switch (type) {
  case 'profiles':
    siteQuery = `site:instagram.com inurl:/ -inurl:/p/ -inurl:/reel/ ${query}`;
    break;
  case 'reels':
    siteQuery = `site:instagram.com inurl:/reel/ ${query}`;
    break;
  case 'posts':
    siteQuery = `site:instagram.com inurl:/p/ ${query}`;
    break;
  default:
    siteQuery = `site:instagram.com ${query}`;
}

// Find API key from env or OpenClaw config
function getApiKey() {
  if (process.env.BRAVE_API_KEY) return process.env.BRAVE_API_KEY;
  
  // Try reading from OpenClaw config
  try {
    const fs = require('fs');
    const path = require('path');
    const home = process.env.HOME || process.env.USERPROFILE;
    const configPath = path.join(home, '.openclaw', 'clawdbot.json');
    if (fs.existsSync(configPath)) {
      const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
      const key = config?.tools?.web?.search?.apiKey;
      if (key) return key;
    }
  } catch (e) {}
  
  return null;
}

const [REDACTED_SECRET]);
if (!apiKey) {
  console.error('❌ No Brave API key found.');
  console.error('   Set BRAVE_API_KEY env var or configure tools.web.search.apiKey in OpenClaw.');
  process.exit(1);
}

// Brave Search API call
const params = new URLSearchParams({
  q: siteQuery,
  count: Math.min(limit, 20).toString(),
});

const options = {
  hostname: 'api.search.brave.com',
  path: `/res/v1/web/search?${params}`,
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Accept-Encoding': 'identity',
    'X-Subscription-Token': apiKey,
  },
};

console.log(`🔍 Searching Instagram: "${query}" (type: ${type})\n`);

const req = https.request(options, (res) => {
  let data = '';
  res.on('data', (chunk) => data += chunk);
  res.on('end', () => {
    if (res.statusCode !== 200) {
      console.error(`❌ Brave API error: ${res.statusCode}`);
      console.error(data);
      process.exit(1);
    }

    try {
      const json = JSON.parse(data);
      const results = json.web?.results || [];

      if (results.length === 0) {
        console.log('No results found.');
        return;
      }

      let count = 0;
      for (const r of results) {
        count++;
        const url = r.url || '';
        
        // Determine type
        let resultType = '📄';
        if (url.includes('/reel/')) resultType = '🎬 Reel';
        else if (url.includes('/p/')) resultType = '📸 Post';
        else if (url.match(/instagram\.com\/[^\/]+\/?$/)) resultType = '👤 Profile';
        else resultType = '📄 Page';

        // Extract username from URL
        const usernameMatch = url.match(/instagram\.com\/([^\/\?]+)/);
        const username = usernameMatch ? `@${usernameMatch[1]}` : '';

        console.log(`${count}. ${resultType} ${username ? `(${username})` : ''}`);
        console.log(`   ${r.title || 'No title'}`);
        if (r.description) {
          console.log(`   ${r.description.substring(0, 200)}`);
        }
        console.log(`   🔗 ${url}`);
        console.log();
      }

      console.log(`---\n${count} results found for "${query}" on Instagram`);
    } catch (e) {
      console.error('❌ Failed to parse response:', e.message);
    }
  });
});

req.on('error', (e) => {
  console.error('❌ Request failed:', e.message);
  process.exit(1);
});

req.end();


path: scripts/viral-search.js
content: #!/usr/bin/env node
/**
 * Viral Content Search — finds trending/high-engagement content on X and Instagram
 * 
 * X/Twitter: Uses bird CLI with --json, filters and sorts by engagement
 * Instagram: Uses Brave Search with viral/popular modifiers
 * 
 * Usage:
 *   node viral-search.js "query" [options]
 * 
 * Options:
 *   --platform x|instagram|both    (default: both)
 *   --min-likes N                  Min likes to include (X only, default: 50)
 *   --min-retweets N               Min retweets (X only, default: 10)
 *   --sort engagement|recent       (default: engagement)
 *   --limit N                      Results per platform (default: 10)
 *   --trending                     Find what's trending right now (X only)
 *   --days N                       Only show content from last N days (default: 7)
 */

const { execSync } = require('child_process');
const path = require('path');

// Parse args
const args = process.argv.slice(2);
let query = '';
let platform = 'both';
let minLikes = 50;
let minRetweets = 10;
let sort = 'engagement';
let limit = 10;
let trending = false;
let days = 7;

for (let i = 0; i < args.length; i++) {
  if (args[i] === '--platform' && args[i + 1]) { platform = args[i + 1]; i++; }
  else if (args[i] === '--min-likes' && args[i + 1]) { minLikes = parseInt(args[i + 1]); i++; }
  else if (args[i] === '--min-retweets' && args[i + 1]) { minRetweets = parseInt(args[i + 1]); i++; }
  else if (args[i] === '--sort' && args[i + 1]) { sort = args[i + 1]; i++; }
  else if (args[i] === '--limit' && args[i + 1]) { limit = parseInt(args[i + 1]); i++; }
  else if (args[i] === '--trending') { trending = true; }
  else if (args[i] === '--days' && args[i + 1]) { days = parseInt(args[i + 1]); i++; }
  else if (!args[i].startsWith('--')) { query += (query ? ' ' : '') + args[i]; }
}

if (!query && !trending) {
  console.error('Usage: viral-search.js "query" [--platform x|instagram|both] [--min-likes 50] [--min-retweets 10] [--sort engagement|recent] [--limit 10] [--trending] [--days 7]');
  process.exit(1);
}

// ==================== X/TWITTER SEARCH ====================
function searchX(query, limit, minLikes, minRetweets, sort, days) {
  console.log(`\n🐦 X/Twitter — searching "${query}" (min ${minLikes} likes, ${minRetweets} RTs)\n`);
  
  try {
    // Use bird's advanced search operators for better results
    // min_faves and min_retweets are X search operators
    let searchQuery = query;
    if (minLikes >= 100) {
      searchQuery += ` min_faves:${minLikes}`;
    }
    if (minRetweets >= 50) {
      searchQuery += ` min_retweets:${minRetweets}`;
    }
    
    // Fetch more than needed so we can filter
    const fetchCount = Math.min(limit * 3, 40);
    const raw = execSync(`bird search "${searchQuery.replace(/"/g, '\\"')}" -n ${fetchCount} --json`, {
      encoding: 'utf8',
      timeout: 30000,
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    let tweets = JSON.parse(raw);
    
    // Filter by date
    const cutoff = Date.now() - (days * 24 * 60 * 60 * 1000);
    tweets = tweets.filter(t => {
      const created = new Date(t.createdAt).getTime();
      return created >= cutoff;
    });

    // Filter by engagement
    tweets = tweets.filter(t => {
      return (t.likeCount || 0) >= minLikes || (t.retweetCount || 0) >= minRetweets;
    });

    // Sort
    if (sort === 'engagement') {
      tweets.sort((a, b) => {
        const scoreA = (a.likeCount || 0) * 1 + (a.retweetCount || 0) * 3 + (a.replyCount || 0) * 2;
        const scoreB = (b.likeCount || 0) * 1 + (b.retweetCount || 0) * 3 + (b.replyCount || 0) * 2;
        return scoreB - scoreA;
      });
    }

    // Trim to limit
    tweets = tweets.slice(0, limit);

    if (tweets.length === 0) {
      console.log(`  No viral tweets found. Try lowering --min-likes or --min-retweets.\n`);
      return;
    }

    tweets.forEach((t, i) => {
      const engagement = `❤️ ${t.likeCount || 0} | 🔄 ${t.retweetCount || 0} | 💬 ${t.replyCount || 0}`;
      const score = (t.likeCount || 0) + (t.retweetCount || 0) * 3 + (t.replyCount || 0) * 2;
      const date = new Date(t.createdAt).toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });
      
      console.log(`${i + 1}. @${t.author?.username || 'unknown'} (${date}) — Score: ${score}`);
      console.log(`   ${engagement}`);
      console.log(`   ${t.text?.substring(0, 200).replace(/\n/g, ' ')}`);
      console.log(`   🔗 https://x.com/${t.author?.username}/status/${t.id}`);
      console.log();
    });

    console.log(`--- ${tweets.length} viral tweets found ---\n`);
  } catch (e) {
    console.error(`  ❌ X search failed: ${e.message}\n`);
  }
}

// ==================== INSTAGRAM SEARCH ====================
function searchInstagram(query, limit) {
  console.log(`\n📸 Instagram — searching "${query}" (viral/popular)\n`);
  
  const scriptDir = path.dirname(process.argv[1]);
  
  try {
    // Use viral modifiers in Brave search
    const viralQueries = [
      `site:instagram.com "${query}" viral popular`,
      `site:instagram.com "${query}" trending`,
    ];
    
    const raw = execSync(
      `node "${path.join(scriptDir, 'instagram-search.js')}" "${query} viral popular trending" --limit ${limit}`,
      { encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] }
    );
    
    console.log(raw);
  } catch (e) {
    // Fallback to regular search with popular modifier
    try {
      const raw = execSync(
        `node "${path.join(scriptDir, 'instagram-search.js')}" "${query} popular" --limit ${limit}`,
        { encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'] }
      );
      console.log(raw);
    } catch (e2) {
      console.error(`  ❌ Instagram search failed: ${e2.message}\n`);
    }
  }
}

// ==================== TRENDING MODE ====================
function searchTrending() {
  console.log(`\n🔥 TRENDING on X right now\n`);
  
  // Search for high-engagement recent content across crypto/defi/ai
  const trendQueries = [
    'crypto min_faves:1000',
    'DeFi min_faves:500',
    'AI trading min_faves:500',
  ];
  
  let allTweets = [];
  
  for (const q of trendQueries) {
    try {
      const raw = execSync(`bird search "${q}" -n 5 --json`, {
        encoding: 'utf8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'],
      });
      const tweets = JSON.parse(raw);
      allTweets = allTweets.concat(tweets);
    } catch (e) {}
  }
  
  // Dedupe by ID
  const seen = new Set();
  allTweets = allTweets.filter(t => {
    if (seen.has(t.id)) return false;
    seen.add(t.id);
    return true;
  });
  
  // Sort by engagement
  allTweets.sort((a, b) => {
    const scoreA = (a.likeCount || 0) + (a.retweetCount || 0) * 3;
    const scoreB = (b.likeCount || 0) + (b.retweetCount || 0) * 3;
    return scoreB - scoreA;
  });
  
  allTweets.slice(0, 10).forEach((t, i) => {
    const engagement = `❤️ ${t.likeCount || 0} | 🔄 ${t.retweetCount || 0} | 💬 ${t.replyCount || 0}`;
    console.log(`${i + 1}. @${t.author?.username || '?'} — ${engagement}`);
    console.log(`   ${t.text?.substring(0, 200).replace(/\n/g, ' ')}`);
    console.log(`   🔗 https://x.com/${t.author?.username}/status/${t.id}\n`);
  });
}

// ==================== RUN ====================
if (trending) {
  searchTrending();
}

if (query) {
  if (platform === 'x' || platform === 'both') {
    searchX(query, limit, minLikes, minRetweets, sort, days);
  }
  if (platform === 'instagram' || platform === 'both') {
    searchInstagram(query, limit);
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: stale | SkillSpector: suspicious (CRITICAL)