diff --git a/tools/REGISTRY.md b/tools/REGISTRY.md index 85a6cec..a1bb1db 100644 --- a/tools/REGISTRY.md +++ b/tools/REGISTRY.md @@ -74,6 +74,7 @@ Quick reference for AI agents to discover tool capabilities and integration meth | introw | Partner Ecosystem | - | ✓ | - | - | [introw.md](integrations/introw.md) | | pendo | Product Analytics | ✓ | - | [✓](clis/pendo.js) | - | [pendo.md](integrations/pendo.md) | | similarweb | Competitive Intelligence | ✓ | - | [✓](clis/similarweb.js) | - | [similarweb.md](integrations/similarweb.md) | +| exa | AI Search | ✓ | ✓ | [✓](clis/exa.js) | ✓ | [exa.md](integrations/exa.md) | | firehose | Competitive Intelligence | ✓ | - | - | - | [firehose.md](integrations/firehose.md) | | sparktoro | Audience Research | - | - | - | - | [sparktoro.md](integrations/sparktoro.md) | | rb2b | Visitor Identification | ✓ | - | - | - | [rb2b.md](integrations/rb2b.md) | @@ -390,6 +391,16 @@ AI-powered content generation and optimization platforms. **Agent recommendation**: AirOps for building AI content workflows that generate SEO-optimized content at scale. +### AI Search + +AI-powered web search APIs built for LLMs and agents. Return structured results with on-demand text, highlights, and summaries. + +| Tool | Best For | Notes | +|------|----------|-------| +| **exa** | Neural/semantic web search, content research, competitor discovery | Search + findSimilar + Contents; MCP and SDKs available | + +**Agent recommendation**: Exa for neural search over the open web — content research, competitor/similar-page discovery, link prospecting, news monitoring, and audience research. Pairs well with seo-audit, content-strategy, and competitor-profiling skills. + ### Partner Ecosystem Partner data sharing, co-sell, and ecosystem management. @@ -471,6 +482,7 @@ These tools have Model Context Protocol servers available, enabling direct agent - **outreach** - Sales engagement sequences - **crossbeam** - Partner ecosystem data - **introw** - Partner relationship management +- **exa** - AI-powered web search for LLMs and agents To use MCP tools, ensure the appropriate MCP server is configured in your environment. diff --git a/tools/clis/README.md b/tools/clis/README.md index 792b1b0..57f524f 100644 --- a/tools/clis/README.md +++ b/tools/clis/README.md @@ -50,6 +50,7 @@ Every CLI reads credentials from environment variables: | `dataforseo` | `DATAFORSEO_LOGIN`, `DATAFORSEO_PASSWORD` | | `demio` | `DEMIO_API_KEY`, `DEMIO_API_SECRET` | | `dub` | `DUB_API_KEY` | +| `exa` | `EXA_API_KEY` | | `g2` | `G2_API_TOKEN` | | `ga4` | `GA4_ACCESS_TOKEN` | | `google-ads` | `GOOGLE_ADS_TOKEN`, `GOOGLE_ADS_DEVELOPER_TOKEN`, `GOOGLE_ADS_CUSTOMER_ID` | @@ -148,6 +149,7 @@ DOMAINS=$(rewardful affiliates list | jq -r '.data[].email') | `dataforseo.js` | SEO | [DataForSEO](https://dataforseo.com) | | `demio.js` | Webinar | [Demio](https://demio.com) | | `dub.js` | Links | [Dub.co](https://dub.co) | +| `exa.js` | AI Search | [Exa](https://exa.ai) | | `g2.js` | Reviews | [G2](https://g2.com) | | `ga4.js` | Analytics | [Google Analytics 4](https://analytics.google.com) | | `google-ads.js` | Ads | [Google Ads](https://ads.google.com) | diff --git a/tools/clis/exa.js b/tools/clis/exa.js new file mode 100755 index 0000000..1a29b62 --- /dev/null +++ b/tools/clis/exa.js @@ -0,0 +1,147 @@ +#!/usr/bin/env node + +const API_KEY = process.env.EXA_API_KEY +const BASE_URL = 'https://api.exa.ai' + +if (!API_KEY) { + console.error(JSON.stringify({ error: 'EXA_API_KEY environment variable required' })) + process.exit(1) +} + +async function api(method, path, body) { + if (args['dry-run']) { + return { _dry_run: true, method, url: `${BASE_URL}${path}`, headers: { 'x-api-key': '***', 'Content-Type': 'application/json', 'x-exa-integration': 'marketingskills' }, body: body || undefined } + } + const res = await fetch(`${BASE_URL}${path}`, { + method, + headers: { + 'x-api-key': API_KEY, + 'Content-Type': 'application/json', + 'x-exa-integration': 'marketingskills', + }, + body: body ? JSON.stringify(body) : undefined, + }) + const text = await res.text() + try { + return JSON.parse(text) + } catch { + return { status: res.status, body: text } + } +} + +function parseArgs(args) { + const result = { _: [] } + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg.startsWith('--')) { + const key = arg.slice(2) + const next = args[i + 1] + if (next && !next.startsWith('--')) { + result[key] = next + i++ + } else { + result[key] = true + } + } else { + result._.push(arg) + } + } + return result +} + +function buildContents(args) { + const contents = {} + if (args.text) { + contents.text = args['max-chars'] + ? { maxCharacters: Number(args['max-chars']) } + : true + } + if (args.highlights) { + contents.highlights = args['highlight-query'] + ? { query: args['highlight-query'] } + : true + } + if (args.summary) { + contents.summary = args['summary-query'] + ? { query: args['summary-query'] } + : {} + } + return Object.keys(contents).length ? contents : null +} + +const args = parseArgs(process.argv.slice(2)) +const [cmd, ...rest] = args._ + +async function main() { + let result + + switch (cmd) { + case 'search': { + const query = args.query || rest.join(' ') + if (!query) { result = { error: '--query required' }; break } + const body = { query } + if (args.type) body.type = args.type + if (args.num) body.numResults = Number(args.num) + if (args.category) body.category = args.category + if (args['include-domains']) body.includeDomains = args['include-domains'].split(',').map(s => s.trim()) + if (args['exclude-domains']) body.excludeDomains = args['exclude-domains'].split(',').map(s => s.trim()) + if (args['include-text']) body.includeText = args['include-text'].split(',').map(s => s.trim()) + if (args['exclude-text']) body.excludeText = args['exclude-text'].split(',').map(s => s.trim()) + if (args['start-published']) body.startPublishedDate = args['start-published'] + if (args['end-published']) body.endPublishedDate = args['end-published'] + if (args['start-crawl']) body.startCrawlDate = args['start-crawl'] + if (args['end-crawl']) body.endCrawlDate = args['end-crawl'] + if (args['user-location']) body.userLocation = args['user-location'] + const contents = buildContents(args) + if (contents) body.contents = contents + result = await api('POST', '/search', body) + break + } + + case 'find-similar': { + const url = args.url + if (!url) { result = { error: '--url required' }; break } + const body = { url } + if (args.num) body.numResults = Number(args.num) + if (args['include-domains']) body.includeDomains = args['include-domains'].split(',').map(s => s.trim()) + if (args['exclude-domains']) body.excludeDomains = args['exclude-domains'].split(',').map(s => s.trim()) + if (args['start-published']) body.startPublishedDate = args['start-published'] + if (args['end-published']) body.endPublishedDate = args['end-published'] + if (args['start-crawl']) body.startCrawlDate = args['start-crawl'] + if (args['end-crawl']) body.endCrawlDate = args['end-crawl'] + const contents = buildContents(args) + if (contents) body.contents = contents + result = await api('POST', '/findSimilar', body) + break + } + + case 'contents': { + const urls = args.urls?.split(',').map(s => s.trim()) + if (!urls || !urls.length) { result = { error: '--urls required (comma-separated)' }; break } + const body = { urls } + const contents = buildContents(args) + if (contents) Object.assign(body, contents) + else body.text = true + result = await api('POST', '/contents', body) + break + } + + default: + result = { + error: 'Unknown command', + usage: { + search: 'search --query [--type neural|fast|auto|deep-lite|deep|deep-reasoning|instant] [--num ] [--category company|research paper|news|personal site|financial report|people] [--include-domains ] [--exclude-domains ] [--include-text ] [--exclude-text ] [--start-published ] [--end-published ] [--user-location ] [--text] [--highlights] [--summary] [--max-chars ] [--highlight-query ] [--summary-query ]', + 'find-similar': 'find-similar --url [--num ] [--include-domains ] [--exclude-domains ] [--start-published ] [--end-published ] [--text] [--highlights] [--summary]', + contents: 'contents --urls [--text] [--highlights] [--summary] [--max-chars ] [--highlight-query ] [--summary-query ]', + options: '--dry-run (preview request without sending)', + } + } + } + + console.log(JSON.stringify(result, null, 2)) +} + +main().catch(err => { + console.error(JSON.stringify({ error: err.message })) + process.exit(1) +}) diff --git a/tools/integrations/exa.md b/tools/integrations/exa.md new file mode 100644 index 0000000..a17816d --- /dev/null +++ b/tools/integrations/exa.md @@ -0,0 +1,145 @@ +# Exa + +AI-powered web search API built for LLMs and agents. Returns high-quality search results with neural and keyword matching, plus on-demand content retrieval (full text, highlights, and summaries) in a single request. + +## Capabilities + +| Integration | Available | Notes | +|-------------|-----------|-------| +| API | ✓ | Search, Find Similar, Contents | +| MCP | ✓ | Official MCP server available | +| CLI | ✓ | [exa.js](../clis/exa.js) | +| SDK | ✓ | `exa-py` (Python), `exa-js` (TypeScript) | + +## Authentication + +- **Type**: API Key +- **Header**: `x-api-key: {key}` +- **Get key**: https://dashboard.exa.ai + +## Endpoints + +Base URL: `https://api.exa.ai` + +| Endpoint | Purpose | +|----------|---------| +| `POST /search` | Search the web with neural, keyword-like, or auto-routed modes | +| `POST /findSimilar` | Find pages similar to a given URL | +| `POST /contents` | Fetch text, highlights, or summaries for one or more URLs | + +## Common Agent Operations + +### Web Search with Content + +```bash +POST https://api.exa.ai/search +{ + "query": "best B2B SaaS onboarding flows", + "type": "auto", + "numResults": 10, + "contents": { + "text": { "maxCharacters": 1000 }, + "highlights": true + } +} +``` + +### Competitor Content Discovery + +```bash +POST https://api.exa.ai/search +{ + "query": "landing page teardowns", + "includeDomains": ["goodui.org", "growth.design", "marketingexamples.com"], + "startPublishedDate": "2024-01-01T00:00:00Z", + "contents": { "highlights": true } +} +``` + +### Find Similar Pages + +```bash +POST https://api.exa.ai/findSimilar +{ + "url": "https://stripe.com/pricing", + "numResults": 20, + "contents": { "summary": { "query": "What pricing model and price points does this page use?" } } +} +``` + +### Category-Filtered Search + +```bash +POST https://api.exa.ai/search +{ + "query": "DTC beauty brand raising Series A", + "category": "news", + "numResults": 25, + "startPublishedDate": "2024-06-01T00:00:00Z" +} +``` + +### Fetch Contents for Known URLs + +```bash +POST https://api.exa.ai/contents +{ + "urls": ["https://example.com/post-1", "https://example.com/post-2"], + "text": true, + "summary": { "query": "Summarize this article's key argument in one paragraph." } +} +``` + +## Key Parameters + +### Search Types +- `auto` - Automatically routes between neural and keyword matching (default) +- `neural` - Embedding-based semantic search; best for concept/idea queries +- `fast` - Lower-latency neural search +- `instant` - Returns cached results near-instantly +- `deep-lite`, `deep`, `deep-reasoning` - Agentic search variants that plan multiple queries and synthesize + +### Categories +`company`, `research paper`, `news`, `personal site`, `financial report`, `people` + +### Filtering +- `includeDomains` / `excludeDomains` - Restrict to or exclude specific domains (up to 1200) +- `includeText` / `excludeText` - Require or forbid phrases in result pages +- `startPublishedDate` / `endPublishedDate` - ISO 8601 publication date range +- `startCrawlDate` / `endCrawlDate` - ISO 8601 crawl date range +- `userLocation` - Two-letter country code (e.g., `US`) + +### Contents (Mix and Match) +All three can be requested in the same call: +- `text: true` or `{ maxCharacters, includeHtmlTags, verbosity }` - Full or truncated page text +- `highlights: true` or `{ query, maxCharacters }` - LLM-selected relevant snippets +- `summary: { query, schema }` - LLM-generated summary, optionally conforming to a JSON schema + +## When to Use + +- **Content research** - Find high-quality long-form content on niche topics by meaning, not just keywords +- **Competitor discovery** - Find companies similar to one you've identified via `findSimilar` +- **SEO content gap analysis** - Search for topics your competitors rank for and pull highlights for quick review +- **Customer research** - Find forum threads, blog posts, and reviews about your product or category +- **Audience research** - Discover blogs, newsletters, and communities where your ICP publishes or comments +- **News monitoring** - Track mentions of your brand, competitors, or category with date-filtered news search +- **Link prospecting** - Find authoritative pages covering topics you write about, for outreach +- **Lead research** - Use the `company` and `people` categories to discover accounts or individuals matching criteria + +## Rate Limits + +- Varies by plan; see https://exa.ai/pricing +- Most production plans support hundreds of concurrent requests +- Content retrieval (text/highlights/summary) is billed separately from the base search + +## Relevant Skills + +- seo-audit +- ai-seo +- content-strategy +- competitor-profiling +- competitor-alternatives +- customer-research +- cold-email +- lead-magnets +- marketing-ideas