← back to Rentv
PR intel: add Tavily as a 3rd free-tier search backend + PR_SEARCH_BACKEND override
050f7edc15f1b2c6f6431379aa0dd85cc32749d0 · 2026-08-05 16:57:41 -0700 · Steve
Brave's 2k/mo free tier is ~spent, so add Tavily (~1k/mo free, recurring) as a
fallback web-search backend for LinkedIn location. PR_SEARCH_BACKEND=tavily
forces it when Brave is capped. Adapter now: exa -> brave -> tavily. Enrich
tool loads the Tavily key from .env. Needs a free Tavily key (below).
Files touched
M src/pr/adapters/search.jsM src/pr/tools/linkedin-enrich.js
Diff
commit 050f7edc15f1b2c6f6431379aa0dd85cc32749d0
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 16:57:41 2026 -0700
PR intel: add Tavily as a 3rd free-tier search backend + PR_SEARCH_BACKEND override
Brave's 2k/mo free tier is ~spent, so add Tavily (~1k/mo free, recurring) as a
fallback web-search backend for LinkedIn location. PR_SEARCH_BACKEND=tavily
forces it when Brave is capped. Adapter now: exa -> brave -> tavily. Enrich
tool loads the Tavily key from .env. Needs a free Tavily key (below).
---
src/pr/adapters/search.js | 20 +++++++++++++++++++-
src/pr/tools/linkedin-enrich.js | 2 +-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/src/pr/adapters/search.js b/src/pr/adapters/search.js
index 63359f35..23278fc8 100644
--- a/src/pr/adapters/search.js
+++ b/src/pr/adapters/search.js
@@ -11,9 +11,15 @@ const { throttled, report, register } = require('./index');
// so the secrets-manager routing works without a PR_-prefixed alias.
const EXA_KEY = process.env.PR_EXA_API_KEY || process.env.EXA_API_KEY;
const BRAVE_KEY = process.env.PR_BRAVE_API_KEY || process.env.BRAVE_SEARCH_API_KEY;
+const TAVILY_KEY = process.env.PR_TAVILY_API_KEY || process.env.TAVILY_API_KEY; // free tier ~1k/mo
+const KEYS = { exa: EXA_KEY, brave: BRAVE_KEY, tavily: TAVILY_KEY };
function backend() {
+ // PR_SEARCH_BACKEND forces one (e.g. =tavily when Brave's monthly free tier is spent).
+ const forced = process.env.PR_SEARCH_BACKEND;
+ if (forced && KEYS[forced]) return forced;
if (EXA_KEY) return 'exa';
if (BRAVE_KEY) return 'brave';
+ if (TAVILY_KEY) return 'tavily';
return null;
}
@@ -40,6 +46,18 @@ async function braveSearch(query, { numResults = 10, cursor } = {}) {
return (((j.web || {}).results) || []).map((x) => ({ title: x.title, url: x.url, snippet: x.description || '', published_at: x.age || null }));
}
+async function tavilySearch(query, { numResults = 10 } = {}) {
+ const r = await fetch('https://api.tavily.com/search', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ api_key: TAVILY_KEY, query, max_results: numResults, search_depth: 'basic', include_answer: false }),
+ signal: AbortSignal.timeout(20000),
+ });
+ if (!r.ok) throw new Error('tavily ' + r.status + ': ' + (await r.text()).slice(0, 200));
+ const j = await r.json();
+ return (j.results || []).map((x) => ({ title: x.title, url: x.url, snippet: (x.content || '').slice(0, 300), published_at: x.published_date || null }));
+}
+
const adapter = register({
name: 'search',
kind: 'discovery',
@@ -61,7 +79,7 @@ const adapter = register({
}
const errors = []; let items = [];
try {
- items = await throttled('search', () => (be === 'exa' ? exaSearch(query, opts) : braveSearch(query, opts)));
+ items = await throttled('search', () => (be === 'exa' ? exaSearch(query, opts) : be === 'tavily' ? tavilySearch(query, opts) : braveSearch(query, opts)));
} catch (e) { errors.push({ error: e.message }); }
return report({
provider: 'search:' + be, query, errors, primary_source: false,
diff --git a/src/pr/tools/linkedin-enrich.js b/src/pr/tools/linkedin-enrich.js
index ed40b1c1..61b9540b 100644
--- a/src/pr/tools/linkedin-enrich.js
+++ b/src/pr/tools/linkedin-enrich.js
@@ -18,7 +18,7 @@ const path = require('path');
try {
const envTxt = fs.readFileSync(path.join(__dirname, '../../../.env'), 'utf8');
for (const line of envTxt.split('\n')) {
- const m = line.match(/^\s*(BRAVE_SEARCH_API_KEY|PR_BRAVE_API_KEY|PR_EXA_API_KEY|EXA_API_KEY)\s*=\s*(.+)\s*$/);
+ const m = line.match(/^\s*(BRAVE_SEARCH_API_KEY|PR_BRAVE_API_KEY|PR_EXA_API_KEY|EXA_API_KEY|TAVILY_API_KEY|PR_TAVILY_API_KEY)\s*=\s*(.+)\s*$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].trim();
}
} catch { /* env optional */ }
← 214a147e directory density: Cody gate fixes — hide slider on mobile (
·
back to Rentv
·
blog: add sort (Newest/Oldest/Title/Category) + density slid fc0ee30d →