From exa-pack
Implements scheduled monitors and content alerts for Exa search API to enable event-driven integrations like competitive intelligence and new content notifications via webhooks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/exa-pack:exa-webhooks-eventsThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Build event-driven integrations around Exa neural search. Exa is a synchronous search API (no native webhooks), so this skill covers building async patterns: scheduled content monitoring with `searchAndContents`, similarity alerts with `findSimilarAndContents`, new content detection using date filters, and webhook-style notification delivery.
Build event-driven integrations around Exa neural search. Exa is a synchronous search API (no native webhooks), so this skill covers building async patterns: scheduled content monitoring with searchAndContents, similarity alerts with findSimilarAndContents, new content detection using date filters, and webhook-style notification delivery.
exa-js installed and EXA_API_KEY configured| Pattern | Mechanism | Use Case |
|---|---|---|
| Content monitor | Scheduled searchAndContents with startPublishedDate | New article alerts |
| Similarity alert | Periodic findSimilarAndContents + diff | Competitive monitoring |
| Content change | Re-search + compare result sets | Update tracking |
| Research digest | Scheduled answer + email/Slack | Daily briefings |
import Exa from "exa-js";
import { Queue, Worker } from "bullmq";
const exa = new Exa(process.env.EXA_API_KEY!);
interface SearchMonitor {
id: string;
query: string;
webhookUrl: string;
lastResultUrls: Set<string>;
intervalMinutes: number;
searchType: "auto" | "neural" | "keyword";
}
const monitorQueue = new Queue("exa-monitors", {
connection: { host: "localhost", port: 6379 },
});
async function createMonitor(config: Omit<SearchMonitor, "lastResultUrls">) {
await monitorQueue.add("check-search", config, {
repeat: { every: config.intervalMinutes * 60 * 1000 },
jobId: config.id,
});
console.log(`Monitor created: ${config.id} (every ${config.intervalMinutes} min)`);
}
const worker = new Worker("exa-monitors", async (job) => {
const monitor = job.data;
// Search for new content published since last check
const results = await exa.searchAndContents(monitor.query, {
type: monitor.searchType || "auto",
numResults: 10,
text: { maxCharacters: 500 },
highlights: { maxCharacters: 300, query: monitor.query },
// Only find content published in the monitoring window
startPublishedDate: getLastCheckDate(monitor.id),
});
// Filter to genuinely new results
const newResults = results.results.filter(
r => !monitor.lastResultUrls?.has(r.url)
);
if (newResults.length > 0) {
await sendWebhook(monitor.webhookUrl, {
event: "exa.new_results",
monitorId: monitor.id,
query: monitor.query,
timestamp: new Date().toISOString(),
results: newResults.map(r => ({
title: r.title,
url: r.url,
snippet: r.text?.substring(0, 200),
highlights: r.highlights,
publishedDate: r.publishedDate,
score: r.score,
})),
});
// Update tracked URLs
await updateLastResultUrls(monitor.id, newResults.map(r => r.url));
}
}, { connection: { host: "localhost", port: 6379 } });
async function monitorSimilarContent(
seedUrl: string,
webhookUrl: string,
checkIntervalHours = 24
) {
const results = await exa.findSimilarAndContents(seedUrl, {
numResults: 5,
text: { maxCharacters: 300 },
excludeSourceDomain: true,
// Only find content from the last check period
startPublishedDate: new Date(
Date.now() - checkIntervalHours * 60 * 60 * 1000
).toISOString(),
});
if (results.results.length > 0) {
await sendWebhook(webhookUrl, {
event: "exa.similar_content_found",
seedUrl,
matchCount: results.results.length,
matches: results.results.map(r => ({
title: r.title,
url: r.url,
snippet: r.text?.substring(0, 200),
score: r.score,
})),
});
}
return results.results.length;
}
async function sendWebhook(url: string, payload: any, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Exa-Event": payload.event,
},
body: JSON.stringify(payload),
});
if (response.ok) return;
console.warn(`Webhook ${response.status}: ${url}`);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
}
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}
async function generateDailyDigest(
topics: string[],
webhookUrl: string
) {
const digest = [];
for (const topic of topics) {
const results = await exa.searchAndContents(topic, {
type: "neural",
numResults: 3,
summary: { query: `Latest developments in: ${topic}` },
startPublishedDate: new Date(
Date.now() - 24 * 60 * 60 * 1000
).toISOString(),
});
digest.push({
topic,
articles: results.results.map(r => ({
title: r.title,
url: r.url,
summary: r.summary,
})),
});
}
await sendWebhook(webhookUrl, {
event: "exa.daily_digest",
date: new Date().toISOString().split("T")[0],
topics: digest,
});
}
| Issue | Cause | Solution |
|---|---|---|
| Rate limited monitors | Too many concurrent checks | Stagger monitor intervals |
| Empty results | Date filter too narrow | Widen to 48-hour windows |
| Duplicate alerts | Missing URL dedup | Track result URLs between runs |
| Webhook delivery fails | Endpoint down | Retry with exponential backoff |
await createMonitor({
id: "competitor-watch",
query: "AI code review tools launch announcement",
webhookUrl: "https://api.myapp.com/webhooks/exa-alerts",
intervalMinutes: 60,
searchType: "neural",
});
For deployment setup, see exa-deploy-integration.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin exa-packImplements event-driven patterns for Perplexity Sonar API: SSE streaming, batch queues, cron monitoring, and citation processing in Node.js apps.
Executes Exa neural search with content extraction, date/domain filters for RAG pipelines, semantic queries, and web search features.
Sets up Yutori Scouts for continuous web monitoring to track news, competitors, product updates, funding rounds, price changes, or recurring web information.