Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Search CLI — Web Search for AI Agents

**One binary, 13 providers, 13 modes, rank-fused results. The web search tool your AI agent is missing.**
**One binary, 14 providers, 13 modes, rank-fused results. The web search tool your AI agent is missing.**

<br />

Expand All @@ -19,7 +19,7 @@

---

A single Rust binary that aggregates Brave, Serper, Exa, Linkup, Jina, Firecrawl, Tavily, SerpApi, Perplexity, Parallel, xAI, and more into one search interface. Built for AI agents from day one: structured JSON, semantic exit codes, self-describing `agent-info`, reciprocal rank fusion across providers, and a `usage` command that reports remaining API credits.
A single Rust binary that aggregates Brave, Serper, Exa, Linkup, Jina, Firecrawl, Tavily, SerpApi, Perplexity, Parallel, You.com, xAI, and more into one search interface. Built for AI agents from day one: structured JSON, semantic exit codes, self-describing `agent-info`, reciprocal rank fusion across providers, and a `usage` command that reports remaining API credits.

[Install](#install) | [How It Works](#how-it-works) | [Features](#features) | [Providers](#providers) | [Contributing](#contributing)

Expand Down Expand Up @@ -125,11 +125,11 @@ same routing registry the engine uses.

| Mode | Use when | `-q` is | Providers used |
|------|----------|---------|----------------|
| `general` | Any web lookup not covered below (default) | query | Parallel + Brave + Serper + Exa + Jina + Linkup + Tavily + Perplexity |
| `news` | Current events; add `-f day`/`-f week` | query | Parallel + Brave + Serper + Linkup + Tavily + Perplexity (news endpoints) |
| `general` | Any web lookup not covered below (default) | query | Parallel + Brave + Serper + Exa + Jina + Linkup + Tavily + You.com + Perplexity |
| `news` | Current events; add `-f day`/`-f week` | query | Parallel + Brave + Serper + Linkup + Tavily + You.com + Perplexity (news endpoints) |
| `academic` | Papers/studies by topic (semantic + web) | query | Exa + Serper + Tavily + Perplexity |
| `scholar` | Google Scholar records: citations, PDFs | query | Serper + SerpApi |
| `deep` | Max coverage; waits for all providers — use `-c 30` | query | Parallel + Brave (web + LLM Context) + Serper + Exa + Linkup + Tavily + Perplexity + xAI |
| `deep` | Max coverage; waits for all providers — use `-c 30` | query | Parallel + Brave (web + LLM Context) + Serper + Exa + Linkup + Tavily + You.com + Perplexity + xAI |
| `people` | A person, their role, LinkedIn profile | query | Exa |
| `social` | What's being said on X/Twitter | query | xAI (Grok) |
| `patents` | Prior art, patent families | query | Serper |
Expand Down Expand Up @@ -230,6 +230,7 @@ search "query" 2>/dev/null # suppress diagnostics
| **[Tavily](https://tavily.com/)** | General + deep search, research-focused | Broad coverage, research queries |
| **[SerpApi](https://serpapi.com/)** | Many engines: Google, Bing, YouTube, Baidu | Multi-engine coverage; only provider with a real balance API |
| **[Perplexity](https://perplexity.ai/)** | LLM-synthesized answer with citations (Sonar) | When you want an answer with sources, not raw pages |
| **You.com** | Web + news search with freshness controls | Fresh web research, news queries |
| **Browserless** | Cloud browser for Cloudflare/JS-heavy pages | Anti-bot bypass, pages that need a real browser |
| **Stealth** | Built-in anti-bot scraper | Protected pages, no API key needed |
| **[xAI](https://x.ai/)** | Only provider with native real-time X/Twitter search (Grok) | Live social signal, trending topics, account activity |
Expand Down Expand Up @@ -303,11 +304,12 @@ export BRAVE_API_KEY=your-key
export SERPER_API_KEY=your-key
export EXA_API_KEY=your-key
# ...also PERPLEXITY_API_KEY, JINA_API_KEY, LINKUP_API_KEY, FIRECRAWL_API_KEY,
# TAVILY_API_KEY, SERPAPI_API_KEY, BROWSERLESS_API_KEY, XAI_API_KEY,
# TAVILY_API_KEY, SERPAPI_API_KEY, YOUCOM_API_KEY, BROWSERLESS_API_KEY, XAI_API_KEY,
# PARALLEL_API_KEY

# Or the SEARCH_KEYS_ prefixed form:
export SEARCH_KEYS_BRAVE=your-key
export SEARCH_KEYS_YOUCOM=your-key
```

Precedence (highest first): `<PROVIDER>_API_KEY` env → `SEARCH_KEYS_*` env → config file.
Expand Down
4 changes: 3 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use clap::{Parser, Subcommand};
name = "search",
version,
about = "Agent-friendly multi-provider search CLI",
long_about = "Aggregates 13 search providers across 13 explicit search modes.\n\
long_about = "Aggregates 14 search providers across 13 explicit search modes.\n\
You choose the mode (-m) and/or providers (-p); the CLI does not guess\n\
intent. Run `search agent-info` for the machine-readable capability map.\n\
Outputs colored tables for humans, JSON when piped to other tools.\n\n\
Expand All @@ -22,6 +22,7 @@ use clap::{Parser, Subcommand};
serpapi Many engines (Google, Bing, YouTube, Baidu, Scholar)\n \
perplexity LLM-synthesized answer with citations (Sonar)\n \
browserless Cloud browser for Cloudflare/JS-heavy pages\n \
youcom You.com web search with live news results\n \
stealth Local anti-bot scraper (no API key)\n \
xai Only real-time X/Twitter search (Grok agentic)\n\n\
EXAMPLES:\n \
Expand All @@ -32,6 +33,7 @@ use clap::{Parser, Subcommand};
search search -q \"trending on twitter\" -m social # X/Twitter search\n \
search search -q \"query\" -p exa # force Exa only\n \
search search -q \"query\" -p exa,brave # only Exa + Brave\n \
search search -q \"query\" -p youcom # force You.com only\n \
search --x \"AI agents\" # search X (Twitter) only\n \
search \"query\" --json | jq '.results[].url' # pipe JSON to jq"
)]
Expand Down
11 changes: 11 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ pub struct ApiKeys {
#[serde(default)]
pub browserless: String,
#[serde(default)]
pub youcom: String,
#[serde(default)]
pub xai: String,
}

Expand Down Expand Up @@ -71,6 +73,7 @@ impl Default for AppConfig {
serpapi: String::new(),
perplexity: String::new(),
browserless: String::new(),
youcom: String::new(),
xai: String::new(),
},
settings: Settings {
Expand Down Expand Up @@ -135,6 +138,7 @@ pub const PROVIDER_KEYS: &[&str] = &[
"serpapi",
"perplexity",
"browserless",
"youcom",
"xai",
];

Expand Down Expand Up @@ -191,6 +195,7 @@ pub fn config_show(config: &AppConfig) {
&config.keys.browserless,
"BROWSERLESS_API_KEY",
),
("youcom", &config.keys.youcom, "YOUCOM_API_KEY"),
("xai", &config.keys.xai, "XAI_API_KEY"),
];

Expand Down Expand Up @@ -406,6 +411,12 @@ pub fn config_check(config: &AppConfig) {
"BROWSERLESS_API_KEY",
"Cloud browser for Cloudflare/JS-heavy pages",
),
(
"youcom",
&config.keys.youcom,
"YOUCOM_API_KEY",
"You.com web search with live news results",
),
(
"xai",
&config.keys.xai,
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ async fn main() {
"api.exa.ai:443",
"api.jina.ai:443",
"api.tavily.com:443",
"ydc-index.io:443",
"api.perplexity.ai:443",
];
for domain in domains {
Expand Down Expand Up @@ -191,6 +192,7 @@ async fn main() {
"https://api.search.brave.com/res/v1/web/search",
"https://google.serper.dev/search",
"https://api.exa.ai/search",
"https://ydc-index.io/v1/search",
];
for url in urls {
let _ = app_c.client.head(url).send().await;
Expand Down
2 changes: 2 additions & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub mod serper;
pub mod stealth;
pub mod tavily;
pub mod xai;
pub mod youcom;

use crate::context::AppContext;
use crate::errors::SearchError;
Expand Down Expand Up @@ -192,6 +193,7 @@ pub fn build_providers(ctx: &Arc<AppContext>) -> Vec<Box<dyn Provider>> {
Box::new(stealth::Stealth::new(ctx.clone())),
Box::new(firecrawl::Firecrawl::new(ctx.clone())),
Box::new(tavily::Tavily::new(ctx.clone())),
Box::new(youcom::YouCom::new(ctx.clone())),
Box::new(browserless::Browserless::new(ctx.clone())),
Box::new(perplexity::Perplexity::new(ctx.clone())),
Box::new(serpapi::SerpApi::new(ctx.clone())),
Expand Down
Loading