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>
This commit is contained in:
161
skills/tavily-tool/scripts/tavily_search.js
Normal file
161
skills/tavily-tool/scripts/tavily_search.js
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user