From 24f245b35bcbf3a4840ad9799d775e4562be6ec5 Mon Sep 17 00:00:00 2001 From: zlei9 Date: Sun, 29 Mar 2026 13:03:17 +0800 Subject: [PATCH] Initial commit with translated description --- SKILL.md | 92 +++++++++++++++++++++++++++ _meta.json | 6 ++ scripts/search.mjs | 150 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 SKILL.md create mode 100644 _meta.json create mode 100644 scripts/search.mjs diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..b85a224 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,92 @@ +--- +name: tavily-search +description: "使用Tavily的LLM优化API进行网络搜索。" +homepage: https://tavily.com +metadata: {"openclaw":{"emoji":"🔍","requires":{"bins":["node"],"env":["TAVILY_API_KEY"]},"primaryEnv":"TAVILY_API_KEY"}} +--- + +# Tavily Search + +Search the web and get relevant results optimized for LLM consumption. + +## Authentication + +Get your API key at https://tavily.com and add to your OpenClaw config: + +```json +{ + "skills": { + "entries": { + "tavily-search": { + "enabled": true, + "apiKey": "tvly-YOUR_API_KEY_HERE" + } + } + } +} +``` + +Or set the environment variable: +```bash +export TAVILY_API_KEY="tvly-YOUR_API_KEY_HERE" +``` + +## Quick Start + +### Using the Script + +```bash +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 +``` + +### Examples + +```bash +# Basic search +node {baseDir}/scripts/search.mjs "python async patterns" + +# With more results +node {baseDir}/scripts/search.mjs "React hooks tutorial" -n 10 + +# Advanced search +node {baseDir}/scripts/search.mjs "machine learning" --deep + +# News search +node {baseDir}/scripts/search.mjs "AI news" --topic news + +# Domain-filtered search +node {baseDir}/scripts/search.mjs "Python docs" --include-domains docs.python.org +``` + +## Options + +| Option | Description | Default | +|--------|-------------|---------| +| `-n ` | Number of results (1-20) | 10 | +| `--depth ` | Search depth: `ultra-fast`, `fast`, `basic`, `advanced` | `basic` | +| `--topic ` | Topic: `general` or `news` | `general` | +| `--time-range ` | Time range: `day`, `week`, `month`, `year` | - | +| `--include-domains ` | Comma-separated domains to include | - | +| `--exclude-domains ` | Comma-separated domains to exclude | - | +| `--raw-content` | Include full page content | false | +| `--json` | Output raw JSON | false | + +## Search Depth + +| Depth | Latency | Relevance | Use Case | +|-------|---------|-----------|----------| +| `ultra-fast` | Lowest | Lower | Real-time chat, autocomplete | +| `fast` | Low | Good | Need chunks but latency matters | +| `basic` | Medium | High | General-purpose, balanced | +| `advanced` | Higher | Highest | Precision matters, research | + +## Tips + +- **Keep queries under 400 characters** - Think search query, not prompt +- **Break complex queries into sub-queries** - Better results than one massive query +- **Use `--include-domains`** to focus on trusted sources +- **Use `--time-range`** for recent information +- **Filter by `score`** (0-1) to get highest relevance results \ No newline at end of file diff --git a/_meta.json b/_meta.json new file mode 100644 index 0000000..847ac74 --- /dev/null +++ b/_meta.json @@ -0,0 +1,6 @@ +{ + "ownerId": "kn724ben7jjrcy3pebw2jbak3s8201cp", + "slug": "liang-tavily-search", + "version": "1.0.1", + "publishedAt": 1772418920934 +} \ No newline at end of file diff --git a/scripts/search.mjs b/scripts/search.mjs new file mode 100644 index 0000000..55be407 --- /dev/null +++ b/scripts/search.mjs @@ -0,0 +1,150 @@ +#!/usr/bin/env node + +function usage() { + console.error(`Usage: search.mjs "query" [options] + +Options: + -n Number of results (1-20, default: 10) + --depth Search depth: ultra-fast, fast, basic, advanced (default: basic) + --topic Topic: general or news (default: general) + --time-range Time range: day, week, month, year + --include-domains Comma-separated domains to include + --exclude-domains Comma-separated domains to exclude + --raw-content Include full page content + --json Output raw JSON + +Examples: + search.mjs "python async patterns" + search.mjs "React hooks tutorial" -n 10 + search.mjs "AI news" --topic news --time-range week + search.mjs "Python docs" --include-domains docs.python.org,realpython.com`); + 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 maxResults = 10; +let searchDepth = "basic"; +let topic = "general"; +let timeRange = null; +let includeDomains = []; +let excludeDomains = []; +let includeRawContent = false; +let outputJson = false; + +for (let i = 1; i < args.length; i++) { + const a = args[i]; + if (a === "-n") { + maxResults = Number.parseInt(args[i + 1] ?? "10", 10); + i++; + continue; + } + if (a === "--depth") { + searchDepth = args[i + 1] ?? "basic"; + i++; + continue; + } + if (a === "--topic") { + topic = args[i + 1] ?? "general"; + i++; + continue; + } + if (a === "--time-range") { + timeRange = args[i + 1]; + i++; + continue; + } + if (a === "--include-domains") { + includeDomains = (args[i + 1] ?? "").split(",").map(d => d.trim()).filter(Boolean); + i++; + continue; + } + if (a === "--exclude-domains") { + excludeDomains = (args[i + 1] ?? "").split(",").map(d => d.trim()).filter(Boolean); + i++; + continue; + } + if (a === "--raw-content") { + includeRawContent = true; + continue; + } + if (a === "--json") { + outputJson = true; + continue; + } + console.error(`Unknown arg: ${a}`); + usage(); +} + +const apiKey = (process.env.TAVILY_API_KEY ?? "").trim(); +if (!apiKey) { + console.error("Error: TAVILY_API_KEY not set"); + console.error("Get your API key at https://tavily.com"); + process.exit(1); +} + +const body = { + query: query, + max_results: Math.max(1, Math.min(maxResults, 20)), + search_depth: searchDepth, + topic: topic, + include_raw_content: includeRawContent, +}; + +if (timeRange) body.time_range = timeRange; +if (includeDomains.length > 0) body.include_domains = includeDomains; +if (excludeDomains.length > 0) body.exclude_domains = excludeDomains; + +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(() => ""); + throw new Error(`Tavily Search failed (${resp.status}): ${text}`); +} + +const data = await resp.json(); + +if (outputJson) { + console.log(JSON.stringify(data, null, 2)); + process.exit(0); +} + +// Print AI answer if available +if (data.answer) { + console.log("## Answer\n"); + console.log(data.answer); + console.log("\n---\n"); +} + +// Print results +const results = (data.results ?? []).slice(0, maxResults); +console.log(`## Sources (${results.length} results)\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(); +} + +if (data.response_time) { + console.log(`\nResponse time: ${data.response_time}s`); +}