Files
openclaw-skill/skills/tavily-tool/scripts/tavily_search.js
Selig f1a6df4ca4 add 6 skills to repo + update skill-review for xiaoming
- Add code-interpreter, kokoro-tts, remotion-best-practices,
  research-to-paper-slides, summarize, tavily-tool to source repo
- skill-review: add main/xiaoming agent mapping in handler.ts + SKILL.md
- tts-voice: handler.ts updates from agent workspace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:59:43 +08:00

162 lines
4.4 KiB
JavaScript

#!/usr/bin/env node
/**
* Tavily Search CLI
*
* - Reads TAVILY_API_KEY from env only.
* - Prints full JSON response to stdout.
* - Prints a simple list of URLs to stderr by default (can be disabled).
*/
const TAVILY_ENDPOINT = 'https://api.tavily.com/search';
function usage(msg) {
if (msg) console.error(`Error: ${msg}\n`);
console.error(`Usage:
tavily_search.js --query "..." [--max_results 5] [--include_domains a.com,b.com] [--exclude_domains x.com,y.com]
Options:
--query, -q Search query (required)
--max_results, -n Max results (default: 5; clamped to 0..20)
--include_domains Comma-separated domains to include
--exclude_domains Comma-separated domains to exclude
--urls-stderr Print URL list to stderr (default: true)
--no-urls-stderr Disable URL list to stderr
--urls-only Print URLs (one per line) to stdout instead of JSON
--help, -h Show help
Env:
TAVILY_API_KEY (required) Tavily API key
Exit codes:
0 success
2 usage / missing required inputs
3 network / HTTP error
4 invalid JSON response
`);
}
function parseArgs(argv) {
const out = {
query: null,
max_results: 5,
include_domains: null,
exclude_domains: null,
urls_stderr: true,
urls_only: false,
help: false,
};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--help' || a === '-h') out.help = true;
else if (a === '--query' || a === '-q') out.query = argv[++i];
else if (a === '--max_results' || a === '-n') out.max_results = Number(argv[++i]);
else if (a === '--include_domains') out.include_domains = argv[++i];
else if (a === '--exclude_domains') out.exclude_domains = argv[++i];
else if (a === '--urls-stderr') out.urls_stderr = true;
else if (a === '--no-urls-stderr') out.urls_stderr = false;
else if (a === '--urls-only') out.urls_only = true;
else return { error: `Unknown arg: ${a}` };
}
if (Number.isNaN(out.max_results) || !Number.isFinite(out.max_results)) {
return { error: `--max_results must be a number` };
}
// Tavily allows 0..20; clamp to stay in range.
out.max_results = Math.max(0, Math.min(20, Math.trunc(out.max_results)));
const csvToArray = (s) => {
if (!s) return null;
const arr = s.split(',').map(x => x.trim()).filter(Boolean);
return arr.length ? arr : null;
};
out.include_domains = csvToArray(out.include_domains);
out.exclude_domains = csvToArray(out.exclude_domains);
return out;
}
async function main() {
const args = parseArgs(process.argv.slice(2));
if (args.error) {
usage(args.error);
process.exit(2);
}
if (args.help) {
usage();
process.exit(0);
}
const apiKey = process.env.TAVILY_API_KEY;
if (!apiKey) {
usage('TAVILY_API_KEY env var is required');
process.exit(2);
}
if (!args.query) {
usage('--query is required');
process.exit(2);
}
const payload = {
query: args.query,
max_results: args.max_results,
};
if (args.include_domains) payload.include_domains = args.include_domains;
if (args.exclude_domains) payload.exclude_domains = args.exclude_domains;
let res;
try {
res = await fetch(TAVILY_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify(payload),
});
} catch (e) {
console.error(`Network error calling Tavily: ${e?.message || String(e)}`);
process.exit(3);
}
if (!res.ok) {
let bodyText = '';
try { bodyText = await res.text(); } catch {}
console.error(`Tavily HTTP error: ${res.status} ${res.statusText}`);
if (bodyText) console.error(bodyText);
process.exit(3);
}
let data;
try {
data = await res.json();
} catch (e) {
console.error(`Invalid JSON response from Tavily: ${e?.message || String(e)}`);
process.exit(4);
}
const urls = Array.isArray(data?.results)
? data.results.map(r => r?.url).filter(Boolean)
: [];
if (args.urls_only) {
for (const u of urls) process.stdout.write(`${u}\n`);
process.exit(0);
}
process.stdout.write(JSON.stringify(data, null, 2));
process.stdout.write('\n');
if (args.urls_stderr && urls.length) {
console.error('\nURLs:');
for (const u of urls) console.error(u);
}
}
main().catch((e) => {
console.error(`Unexpected error: ${e?.stack || e?.message || String(e)}`);
process.exit(1);
});