
Tavily Search
- 815 installs
- 75 repo stars
- Updated February 14, 2026
- framix-team/openclaw-tavily
tavily-search is a Claude Code skill that performs deep, targeted web crawling and structured research via a crawl CLI for developers who need agent-ready markdown or text from seeded URLs.
About
tavily-search is a web research skill from framix-team/openclaw-tavily built around a crawl.mjs Node CLI that accepts a seed URL plus flags for depth, breadth, limit, instructions, and markdown or text output format. Default parsing uses depth 2 and breadth 10 when flags are omitted, letting agents control crawl scope and extraction instructions for planning workflows. Developers reach for tavily-search when agents need structured page content rather than single-shot search snippets, especially for multi-page research passes that feed specs or implementation plans. The skill bridges Tavily-style deep retrieval and local agent tooling with explicit crawl boundaries.
- CLI wrapper for Tavily Crawl API with configurable depth, breadth, and result limits
- Accepts custom instructions to guide extraction and summarization
- Supports markdown or plain-text output formats
- Designed for agentic research loops and multi-step information gathering
- Zero-install usage via npx with environment-based API key
Tavily Search by the numbers
- 815 all-time installs (skills.sh)
- +2 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #1,313 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 29, 2026 (Skillselion catalog sync)
npx skills add https://github.com/framix-team/openclaw-tavily --skill tavily-searchAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 815 |
|---|---|
| repo stars | ★ 75 |
| Security audit | 2 / 3 scanners passed |
| Last updated | February 14, 2026 |
| Repository | framix-team/openclaw-tavily ↗ |
How do you crawl websites for agent research workflows?
Perform deep, targeted web crawling and structured research that feeds directly into agents and planning workflows.
Who is it for?
Developers building research agents who need bounded multi-page crawls with custom instructions and markdown-ready output.
Skip if: Developers who only need a single search result snippet or production site scraping without crawl depth and breadth controls.
When should I use this skill?
The user needs deep web research, multi-page crawling, Tavily-style retrieval, or crawl.mjs usage with depth, breadth, and format flags.
What you get
Structured markdown or text crawl output from depth- and breadth-limited multi-page web extraction.
- Markdown crawl output
- Text-format crawl extract
By the numbers
- Default crawl depth 2 and breadth 10 in crawl.mjs
Files
Tavily Search
AI-optimized web tools using the Tavily API. Five tools for search, extraction, crawling, mapping, and research.
Default web search
When this plugin is active, use `tavily_search` instead of the built-in `web_search` for all web searches. Tavily provides higher-quality, AI-optimized results and has a free tier. The built-in web_search (Brave) should only be used as a fallback if Tavily is unavailable.
When to use
- `tavily_search` — All web searches: current events, news, fact-checking, finding references (replaces
web_search) - `tavily_extract` — Get full clean content from specific URLs
- `tavily_crawl` — Traverse a website and extract content from multiple pages
- `tavily_map` — Discover all URLs on a site to understand its structure
- `tavily_research` — Complex multi-step research questions needing comprehensive reports
Native tools (preferred)
If the openclaw-tavily plugin is installed, use these tools directly:
| Tool | Description |
|---|---|
tavily_search | Web search with AI answers, domain filtering, news support |
tavily_extract | Extract clean markdown/text content from URLs |
tavily_crawl | Crawl a website from a root URL, extract page content |
tavily_map | Discover and list all URLs from a website |
tavily_research | Deep agentic research with comprehensive reports |
Script fallback
Search
node {baseDir}/scripts/search.mjs "query"
node {baseDir}/scripts/search.mjs "query" -n 10
node {baseDir}/scripts/search.mjs "query" --deep
node {baseDir}/scripts/search.mjs "query" --topic news --time-range weekOptions:
-n <count>: Number of results (default: 5, max: 20)--deep: Advanced search for deeper research (slower, more thorough)--topic <topic>:general(default),news, orfinance--time-range <range>:day,week,month, oryear
Extract content from URLs
node {baseDir}/scripts/extract.mjs "https://example.com/article"
node {baseDir}/scripts/extract.mjs "url1" "url2" "url3"
node {baseDir}/scripts/extract.mjs "url" --format text --query "relevant topic"Extracts clean text content from one or more URLs.
Crawl a website
node {baseDir}/scripts/crawl.mjs "https://example.com"
node {baseDir}/scripts/crawl.mjs "https://example.com" --depth 3 --breadth 20 --limit 50
node {baseDir}/scripts/crawl.mjs "https://example.com" --instructions "Find pricing pages" --format textOptions:
--depth <N>: Crawl depth 1-5--breadth <N>: Max links per level (1-500)--limit <N>: Total URL cap--instructions "...": Natural language crawl guidance--format <markdown|text>: Output format
Map a website
node {baseDir}/scripts/map.mjs "https://example.com"
node {baseDir}/scripts/map.mjs "https://example.com" --depth 2 --limit 100
node {baseDir}/scripts/map.mjs "https://example.com" --instructions "Find documentation pages"Options:
--depth <N>: Crawl depth 1-5--breadth <N>: Max links per level--limit <N>: Total URL cap--instructions "...": Natural language guidance
Research a topic
node {baseDir}/scripts/research.mjs "What are the latest advances in quantum computing?"
node {baseDir}/scripts/research.mjs "Compare React vs Vue in 2025" --model pro
node {baseDir}/scripts/research.mjs "AI regulation in the EU" --citation-format apaOptions:
--model <mini|pro|auto>: Research model (default: auto)--citation-format <numbered|mla|apa|chicago>: Citation style
Setup
Get an API key at app.tavily.com (free tier available).
Set TAVILY_API_KEY in your environment, or configure via the plugin:
{
"plugins": {
"entries": {
"openclaw-tavily": {
"enabled": true,
"config": { "apiKey": "tvly-..." }
}
}
}
}Links
- Plugin: openclaw-tavily on npm
- Source: github.com/framix-team/openclaw-tavily
- Tavily API: docs.tavily.com
#!/usr/bin/env node
function usage() {
console.error('Usage: crawl.mjs "url" [--depth N] [--breadth N] [--limit N] [--instructions "..."] [--format markdown|text]');
process.exit(2);
}
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
const url = args[0];
let depth = null;
let breadth = null;
let limit = null;
let instructions = null;
let format = null;
for (let i = 1; i < args.length; i++) {
const a = args[i];
if (a === "--depth") {
depth = Number.parseInt(args[i + 1] ?? "2", 10);
i++;
} else if (a === "--breadth") {
breadth = Number.parseInt(args[i + 1] ?? "10", 10);
i++;
} else if (a === "--limit") {
limit = Number.parseInt(args[i + 1] ?? "10", 10);
i++;
} else if (a === "--instructions") {
instructions = args[i + 1] ?? null;
i++;
} else if (a === "--format") {
format = args[i + 1] ?? "markdown";
i++;
} else {
console.error(`Unknown arg: ${a}`);
usage();
}
}
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
if (!apiKey) {
console.error("Missing TAVILY_API_KEY");
process.exit(1);
}
const body = { url };
if (depth !== null) body.max_depth = depth;
if (breadth !== null) body.max_breadth = breadth;
if (limit !== null) body.limit = limit;
if (instructions) body.instructions = instructions;
if (format) body.format = format;
const resp = await fetch("https://api.tavily.com/crawl", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!resp.ok) {
const text = await resp.text().catch(() => "");
console.error(`Tavily Crawl failed (${resp.status}): ${text}`);
process.exit(1);
}
const data = await resp.json();
console.log(`## Crawl: ${data.base_url ?? url}\n`);
const results = data.results ?? [];
console.log(`Found ${results.length} page(s)\n`);
for (const r of results) {
const pageUrl = String(r?.url ?? "").trim();
const content = String(r?.raw_content ?? "").trim();
console.log(`### ${pageUrl}\n`);
if (content) {
console.log(content.slice(0, 2000) + (content.length > 2000 ? "\n\n... (truncated)" : ""));
} else {
console.log("(no content extracted)");
}
console.log("\n---\n");
}
#!/usr/bin/env node
function usage() {
console.error('Usage: extract.mjs "url1" ["url2" ...] [--format markdown|text] [--query "..."]');
process.exit(2);
}
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
const urls = [];
let format = "markdown";
let query = null;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === "--format") {
format = args[i + 1] ?? "markdown";
i++;
} else if (a === "--query") {
query = args[i + 1] ?? null;
i++;
} else if (!a.startsWith("-")) {
urls.push(a);
} else {
console.error(`Unknown arg: ${a}`);
usage();
}
}
if (urls.length === 0) {
console.error("No URLs provided");
usage();
}
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
if (!apiKey) {
console.error("Missing TAVILY_API_KEY");
process.exit(1);
}
const extractBody = { urls, format };
if (query) extractBody.query = query;
const resp = await fetch("https://api.tavily.com/extract", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(extractBody),
});
if (!resp.ok) {
const text = await resp.text().catch(() => "");
console.error(`Tavily Extract failed (${resp.status}): ${text}`);
process.exit(1);
}
const data = await resp.json();
for (const r of data.results ?? []) {
const url = String(r?.url ?? "").trim();
const content = String(r?.raw_content ?? "").trim();
console.log(`# ${url}\n`);
console.log(content || "(no content extracted)");
console.log("\n---\n");
}
const failed = data.failed_results ?? [];
if (failed.length > 0) {
console.log("## Failed URLs\n");
for (const f of failed) {
console.log(`- ${f.url}: ${f.error}`);
}
}
#!/usr/bin/env node
function usage() {
console.error('Usage: map.mjs "url" [--depth N] [--breadth N] [--limit N] [--instructions "..."]');
process.exit(2);
}
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
const url = args[0];
let depth = null;
let breadth = null;
let limit = null;
let instructions = null;
for (let i = 1; i < args.length; i++) {
const a = args[i];
if (a === "--depth") {
depth = Number.parseInt(args[i + 1] ?? "2", 10);
i++;
} else if (a === "--breadth") {
breadth = Number.parseInt(args[i + 1] ?? "10", 10);
i++;
} else if (a === "--limit") {
limit = Number.parseInt(args[i + 1] ?? "50", 10);
i++;
} else if (a === "--instructions") {
instructions = args[i + 1] ?? null;
i++;
} else {
console.error(`Unknown arg: ${a}`);
usage();
}
}
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
if (!apiKey) {
console.error("Missing TAVILY_API_KEY");
process.exit(1);
}
const body = { url };
if (depth !== null) body.max_depth = depth;
if (breadth !== null) body.max_breadth = breadth;
if (limit !== null) body.limit = limit;
if (instructions) body.instructions = instructions;
const resp = await fetch("https://api.tavily.com/map", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!resp.ok) {
const text = await resp.text().catch(() => "");
console.error(`Tavily Map failed (${resp.status}): ${text}`);
process.exit(1);
}
const data = await resp.json();
const urls = data.results ?? [];
console.log(`## Site Map: ${url}\n`);
console.log(`Found ${urls.length} URL(s)\n`);
for (const u of urls) {
console.log(`- ${u}`);
}
console.log();
#!/usr/bin/env node
function usage() {
console.error('Usage: research.mjs "question" [--model mini|pro|auto] [--citation-format numbered|mla|apa|chicago]');
process.exit(2);
}
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
const input = args[0];
let model = null;
let citationFormat = null;
for (let i = 1; i < args.length; i++) {
const a = args[i];
if (a === "--model") {
model = args[i + 1] ?? "auto";
i++;
} else if (a === "--citation-format") {
citationFormat = args[i + 1] ?? "numbered";
i++;
} else {
console.error(`Unknown arg: ${a}`);
usage();
}
}
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
if (!apiKey) {
console.error("Missing TAVILY_API_KEY");
process.exit(1);
}
const body = { input };
if (model) body.model = model;
if (citationFormat) body.citation_format = citationFormat;
// Step 1: Create the research task
const createResp = await fetch("https://api.tavily.com/research", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!createResp.ok) {
const text = await createResp.text().catch(() => "");
console.error(`Tavily Research failed (${createResp.status}): ${text}`);
process.exit(1);
}
let data = await createResp.json();
// Step 2: If pending, poll until complete
if (data.status === "pending" && data.request_id) {
const requestId = data.request_id;
const pollUrl = `https://api.tavily.com/research/${requestId}`;
const POLL_INTERVAL = 2000;
const MAX_WAIT = 150_000; // 2.5 minutes
const start = Date.now();
process.stderr.write(`Research task ${requestId}: polling`);
while (Date.now() - start < MAX_WAIT) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
process.stderr.write(".");
const pollResp = await fetch(pollUrl, {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (!pollResp.ok) {
const text = await pollResp.text().catch(() => "");
console.error(`\nPoll failed (${pollResp.status}): ${text}`);
process.exit(1);
}
data = await pollResp.json();
if (data.status === "completed" || data.content || data.output) {
process.stderr.write(` done (${Math.round((Date.now() - start) / 1000)}s)\n`);
break;
}
if (data.status === "failed" || data.status === "error") {
console.error(`\nResearch task failed: ${JSON.stringify(data)}`);
process.exit(1);
}
}
if (data.status === "pending") {
console.error(`\nResearch task timed out after ${MAX_WAIT / 1000}s. Request ID: ${requestId}`);
process.exit(1);
}
}
console.log("## Research Report\n");
const reportContent = data.content || data.output || "";
if (reportContent) {
console.log(reportContent);
console.log();
}
const sources = data.sources ?? [];
if (sources.length > 0) {
console.log("---\n");
console.log("## Sources\n");
for (const s of sources) {
const title = String(s?.title ?? "").trim();
const url = String(s?.url ?? "").trim();
if (url) {
console.log(`- ${title ? `**${title}**: ` : ""}${url}`);
}
}
console.log();
}
#!/usr/bin/env node
function usage() {
console.error('Usage: search.mjs "query" [-n 5] [--deep] [--topic general|news|finance] [--time-range day|week|month|year]');
process.exit(2);
}
const args = process.argv.slice(2);
if (args.length === 0 || args[0] === "-h" || args[0] === "--help") usage();
const query = args[0];
let n = 5;
let searchDepth = "basic";
let topic = "general";
let timeRange = null;
for (let i = 1; i < args.length; i++) {
const a = args[i];
if (a === "-n") {
n = Number.parseInt(args[i + 1] ?? "5", 10);
i++;
} else if (a === "--deep") {
searchDepth = "advanced";
} else if (a === "--topic") {
topic = args[i + 1] ?? "general";
i++;
} else if (a === "--time-range") {
timeRange = args[i + 1] ?? null;
i++;
} else {
console.error(`Unknown arg: ${a}`);
usage();
}
}
const apiKey = (process.env.TAVILY_API_KEY ?? "").trim();
if (!apiKey) {
console.error("Missing TAVILY_API_KEY");
process.exit(1);
}
const body = {
query,
search_depth: searchDepth,
topic,
max_results: Math.max(1, Math.min(n, 20)),
include_answer: true,
include_raw_content: false,
};
if (timeRange) {
body.time_range = timeRange;
}
const resp = await fetch("https://api.tavily.com/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(body),
});
if (!resp.ok) {
const text = await resp.text().catch(() => "");
console.error(`Tavily Search failed (${resp.status}): ${text}`);
process.exit(1);
}
const data = await resp.json();
if (data.answer) {
console.log("## Answer\n");
console.log(data.answer);
console.log("\n---\n");
}
const results = (data.results ?? []).slice(0, n);
console.log("## Sources\n");
for (const r of results) {
const title = String(r?.title ?? "").trim();
const url = String(r?.url ?? "").trim();
const content = String(r?.content ?? "").trim();
const score = r?.score ? ` (relevance: ${(r.score * 100).toFixed(0)}%)` : "";
if (!title || !url) continue;
console.log(`- **${title}**${score}`);
console.log(` ${url}`);
if (content) {
console.log(` ${content.slice(0, 300)}${content.length > 300 ? "..." : ""}`);
}
console.log();
}
Related skills
How it compares
Use tavily-search for bounded multi-page crawls with agent instructions; use single-query search skills when one SERP answer is enough.
FAQ
What CLI does tavily-search use for crawling?
tavily-search uses crawl.mjs, a Node CLI that takes a seed URL plus optional --depth, --breadth, --limit, --instructions, and --format markdown or text flags to produce structured research output.
What are the default crawl limits in tavily-search?
tavily-search's crawl.mjs defaults to depth 2 and breadth 10 when --depth and --breadth are not specified, while --limit and --instructions remain optional for tighter extraction control.
Is Tavily Search safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.