#!/usr/bin/env node /** * xAI X Search Script - Uses Responses API with x_search tool * * Usage: * node search-x.js "Remotion best practices" * node search-x.js --days 30 "AI video creation" * node search-x.js --handles @remotion_dev,@jonnyburger "updates" */ const https = require('https'); const API_BASE = 'api.x.ai'; const DEFAULT_MODEL = 'grok-4-1-fast'; // Optimized for agentic search function getApiKey() { return process.env.XAI_API_KEY || null; } function parseArgs(args) { const result = { model: DEFAULT_MODEL, query: '', days: 30, handles: [], excludeHandles: [], json: false, requireCitations: true, }; let i = 0; while (i < args.length) { const arg = args[i]; if (arg === '--model' || arg === '-m') { result.model = args[++i]; } else if (arg === '--days' || arg === '-d') { result.days = parseInt(args[++i], 10); } else if (arg === '--handles' || arg === '-h') { result.handles = args[++i].split(',').map(h => h.trim()); } else if (arg === '--exclude') { result.excludeHandles = args[++i].split(',').map(h => h.trim()); } else if (arg === '--json' || arg === '-j') { result.json = true; } else if (arg === '--no-require-citations') { result.requireCitations = false; } else if (!arg.startsWith('-')) { result.query = args.slice(i).join(' '); break; } i++; } return result; } function getDateRange(days) { const to = new Date(); const from = new Date(); from.setDate(from.getDate() - days); return { from_date: from.toISOString().split('T')[0], to_date: to.toISOString().split('T')[0], }; } async function searchX(options) { const apiKey = getApiKey(); if (!apiKey) { console.error('āŒ No API key found. Set XAI_API_KEY or configure in clawdbot.'); process.exit(1); } const dateRange = getDateRange(options.days); // Build x_search tool config const xSearchTool = { type: 'x_search', x_search: { from_date: dateRange.from_date, to_date: dateRange.to_date, } }; if (options.handles.length > 0) { xSearchTool.x_search.allowed_x_handles = options.handles; } if (options.excludeHandles.length > 0) { xSearchTool.x_search.excluded_x_handles = options.excludeHandles; } const payload = { model: options.model, input: `Search X/Twitter and find real posts about: ${options.query} Give me actual tweets with: - Username/handle - The actual tweet content - Date if available - Link to the tweet Only include REAL posts you find. If you can't find any, say so.`, tools: [xSearchTool], }; return new Promise((resolve, reject) => { const req = https.request({ hostname: API_BASE, path: '/v1/responses', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode !== 200) { console.error(`āŒ API Error (${res.statusCode}):`, data); process.exit(1); } try { const response = JSON.parse(data); if (options.json) { console.log(JSON.stringify(response, null, 2)); resolve(response); return; } // Extract content from Responses API format let content = '(no response)'; if (response.output) { for (const item of response.output) { if (item.type === 'message' && item.content) { for (const c of item.content) { if (c.type === 'output_text' && c.text) { content = c.text; } } } } } // Extract citations from annotations let xCitations = []; if (response.output) { for (const item of response.output) { if (item.content) { for (const c of item.content) { if (c.annotations) { for (const ann of c.annotations) { if (ann.type === 'url_citation' && ann.url) { if (ann.url.includes('x.com') || ann.url.includes('twitter.com')) { xCitations.push(ann); } } } } } } } } // Dedupe by URL xCitations = [...new Map(xCitations.map(c => [c.url, c])).values()]; if (options.requireCitations && xCitations.length === 0) { console.error('āš ļø No X/Twitter citations found in response.'); console.error(' Grok may not have found relevant posts, or X search may not be enabled.'); console.error('\nResponse anyway:\n'); } console.log(content); if (xCitations.length > 0) { console.log('\nšŸ“Ž Citations:'); for (const cite of xCitations) { console.log(` ${cite.url}`); } } resolve(response); } catch (e) { console.error('āŒ Failed to parse response:', e.message); console.error('Raw:', data.slice(0, 500)); process.exit(1); } }); }); req.on('error', (e) => { console.error('āŒ Request failed:', e.message); process.exit(1); }); req.write(JSON.stringify(payload)); req.end(); }); } // Main const args = process.argv.slice(2); if (args.length === 0 || args.includes('--help')) { console.log(` šŸ” xAI X Search (Responses API) Usage: node search-x.js [options] "Your search query" Options: --model, -m Model (default: grok-4-1-fast) --days, -d Search last N days (default: 30) --handles Only search these handles (comma-separated) --exclude Exclude these handles (comma-separated) --json, -j Output full JSON response --no-require-citations Don't warn if no X citations found --help Show this help Examples: node search-x.js "Remotion video framework tips" node search-x.js --days 7 "Claude AI" node search-x.js --handles @remotion_dev "new features" node search-x.js --days 30 "AI video creation best practices" `); process.exit(0); } const options = parseArgs(args); if (!options.query) { console.error('āŒ Please provide a search query'); process.exit(1); } console.error(`šŸ” Searching X for: "${options.query}" (last ${options.days} days)...\n`); searchX(options);