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.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin exa-packThis skill is limited to using the following tools:
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.
Monitors user-defined topics via scheduled web searches, AI importance scoring, proactive alerts, weekly digests, and memory-aware summaries. Use for tracking product releases, news, or tech updates.
Implements 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.
Share bugs, ideas, or general feedback.
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.