
Exa Search
Call Exa search, contents, findsimilar, answer, and research endpoints from a Node CLI wrapper when your agent needs live web intelligence beyond static training data.
Overview
Exa Search is an agent skill most often used in Idea (also Launch, Grow) that wraps Exa’s search, contents, answer, and research HTTP APIs behind a Node CLI for agent workflows.
Install
npx skills add https://github.com/benedictking/exa-search --skill exa-searchWhat is this skill?
- CLI exa-api.js supporting search, contents, findsimilar, answer, and research commands
- Flexible input via JSON arg, stdin pipe, or --file payload path
- EXA_API_KEY loaded from environment or local .env next to the script
- HTTPS client aimed at api.exa.ai base URL for skill-driven workflows
- Documented .env and node_modules ignore patterns for safe repo hygiene
- 5 Exa CLI subcommands: search, contents, findsimilar, answer, research
Adoption & trust: 576 installs on skills.sh; 5 GitHub stars; 1/3 security scanners passed (skills.sh audits).
What problem does it solve?
You need fresh, citation-friendly web results in an agent session but lack a repeatable way to call Exa with structured JSON payloads.
Who is it for?
Builders who already have or will create an Exa API key and want scripted search inside Claude Code, Cursor, or Codex automation.
Skip if: Offline-only workflows, teams forbidden from sending queries to third-party search APIs, or users who need a GUI research tool with no shell access.
When should I use this skill?
When the agent needs live Exa web search, page contents, similar pages, answers, or research jobs via structured CLI calls.
What do I get? / Deliverables
You can pipe research queries through exa-api.js and feed returned snippets or answers into specs, content briefs, or validation notes.
- JSON API responses from Exa endpoints
- Repeatable CLI invocations documented in skill README
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
Canonical shelf is idea/research because Exa powers competitor scans, market discovery, and sourced answers before you commit—but the same integration supports launch SEO research and grow content ideation. Research subphase matches external retrieval and synthesis; the skill is an API bridge, not a code generator for your product binary.
Where it fits
Run Exa search on niche keywords before choosing what to build this quarter.
Pull contents and findsimilar for rival landing pages to map positioning gaps.
Use answer and research modes to draft FAQ-backed copy clusters for a new site section.
Feed research JSON into a weekly newsletter outline with sourced URLs.
How it compares
Skill-packaged Exa HTTP CLI integration—not the same as installing a separate MCP server or running only built-in model browsing.
Common Questions / FAQ
Who is exa-search for?
Solo builders and agent authors who ground product, SEO, or competitive decisions on live Exa retrieval without hand-rolling HTTP calls each time.
When should I use exa-search?
During idea research for market scans; in validate when checking claims on a landing page; at launch for SEO and GEO topic discovery; in grow when researching content angles.
Is exa-search safe to install?
It requires an API key and outbound HTTPS—store secrets outside git, limit key scope, and review the Security Audits panel on this Prism page before production automation.
SKILL.md
READMESKILL.md - Exa Search
# Exa API Key Configuration # Get your API key from: https://dashboard.exa.ai/api-keys EXA_API_KEY=your_api_key_here .env node_modules/ #!/usr/bin/env node /** * Exa API Helper Script * Provides a CLI wrapper around Exa endpoints for skill integration. * * Usage: * node exa-api.js <search|contents|findsimilar|answer|research> [<json-string>] * cat payload.json | node exa-api.js search * node exa-api.js search --file ./payload.json */ const https = require('https'); const fs = require('fs'); const path = require('path'); const API_BASE = 'https://api.exa.ai'; function loadApiKey() { if (process.env.EXA_API_KEY) { return process.env.EXA_API_KEY; } const envPath = path.join(__dirname, '.env'); if (!fs.existsSync(envPath)) { return null; } const envContent = fs.readFileSync(envPath, 'utf8'); const match = envContent.match(/EXA_API_KEY\s*=\s*(.+)/); if (!match) { return null; } return match[1].trim().replace(/^[\"']|[\"']$/g, ''); } function usage() { const cmd = path.basename(process.argv[1] || 'exa-api.js'); console.error( [ 'Usage:', ` node ${cmd} <search|contents|findsimilar|answer|research> [<json-string>]`, ` cat payload.json | node ${cmd} search`, ` node ${cmd} search --file ./payload.json`, '', 'Env:', ' EXA_API_KEY (env var) or .env file next to this script', ].join('\n'), ); } function readStdin() { return new Promise((resolve, reject) => { let data = ''; process.stdin.setEncoding('utf8'); process.stdin.on('data', (chunk) => { data += chunk; }); process.stdin.on('end', () => resolve(data)); process.stdin.on('error', reject); }); } async function readPayload(args) { const fileFlagIndex = args.findIndex((arg) => arg === '--file'); if (fileFlagIndex !== -1) { const filePath = args[fileFlagIndex + 1]; if (!filePath) { throw new Error('Missing value for --file'); } const content = fs.readFileSync(filePath, 'utf8'); return JSON.parse(content); } const dataFlagIndex = args.findIndex((arg) => arg === '--data'); if (dataFlagIndex !== -1) { const json = args[dataFlagIndex + 1]; if (!json) { throw new Error('Missing value for --data'); } return JSON.parse(json); } if (args[0] && !args[0].startsWith('-')) { return JSON.parse(args[0]); } if (process.stdin.isTTY) { throw new Error('No payload provided (pass JSON arg, --data, --file, or pipe via stdin)'); } const stdin = await readStdin(); if (!stdin.trim()) { throw new Error('Empty stdin payload'); } return JSON.parse(stdin); } function postJson(endpointPath, apiKey, payload) { return new Promise((resolve, reject) => { const body = JSON.stringify(payload); const url = new URL(endpointPath, API_BASE); const req = https.request( url, { method: 'POST', headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), 'User-Agent': 'Exa-Skill/1.0', }, timeout: 60_000, }, (res) => { let data = ''; res.setEncoding('utf8'); res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { const ok = res.statusCode && res.statusCode >= 200 && res.statusCode < 300; if (!ok) { reject(new Error(`API Error ${res.statusCode}: ${data}`)); return; } try { resolve(JSON.parse(data)); } catch { resolve(data); } }); }, ); req.on('error', reject); req.on('timeout', () => { req.destroy(new Error('Request timed out')); }); req.write(body); req.end(); }); } const ENDPOINT_BY_COMMAND = { search: '/search', contents: '/contents', findsimilar: '/findSimilar', answer: '/answer', research: '/research