Skip to content

About

No description, website, or topics provided.

Resources

Stars

83 stars

Watchers

0 watching

Forks

Repository files navigation

bx — Brave Search CLI

A zero-dependency, token-efficient CLI for the Brave Search API, built for AI agents and LLMs.

One binary, JSON in/out, no runtime dependencies. bx context replaces search + scrape + extract in a single call with token-budgeted output — purpose-built for RAG and LLM grounding.

Quick Start

macOS/Linux (macOS 11.7.1 or later)

curl -fsSL https://raw.githubusercontent.com/brave/brave-search-cli/main/scripts/install.sh | sh

Windows (PowerShell)

powershell -ExecutionPolicy Bypass -c "irm https://raw.githubusercontent.com/brave/brave-search-cli/main/scripts/install.ps1 | iex"

npm (requires Node ≥ 16)

npm install -g @brave/brave-search-cli
bx config set-key YOUR_API_KEY    # get a key at https://api-dashboard.search.brave.com
bx "your search query"            # shorthand for: bx context "your search query"
bx --help                         # see all commands; bx <command> --help for flags
# To search for a word matching a subcommand name: bx -- web  or  bx context "web"

For AI Agents

Use context by default. It returns pre-extracted, relevance-scored web content ready for LLM prompt injection. One API call replaces the search → scrape → extract pipeline.

# RAG grounding with token budget
bx context "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096

# Direct AI answer (OpenAI-compatible, streams by default)
bx answers "explain Rust lifetimes with examples"

# Raw web search when you need site: scoping or result filtering
bx web "site:docs.rs axum middleware" --count 5

When to Use Which Command

Your need Command Why
Look up docs, errors, code patterns context Pre-extracted text, token-budgeted
Get a synthesized explanation answers AI-generated, cites sources
Search a specific site (site:) web Supports search operators
Find discussions/forums web --result-filter discussions Forums often have solutions
Check latest versions/releases context or news --freshness pd Fresh info beyond training data
Research security vulnerabilities context or news CVE details, advisories
Boost/filter specific domains --goggles on context/web/news Custom re-ranking, no other API has this

Response Shapes

bx context — RAG/grounding (recommended)

{
  "grounding": {
    "generic": [
      { "url": "...", "title": "...", "snippets": ["extracted content...", "..."] }
    ]
  }
}

bx answers — AI answer (streaming, one JSON chunk per line)

{"choices": [{"delta": {"content": "Rust lifetimes ensure references..."}}]}

bx answers --no-stream — AI answer (single response)

{"choices": [{"message": {"content": "Rust lifetimes ensure references..."}}]}

bx web — Full search results

{
  "web": { "results": [{"title": "...", "url": "...", "description": "..."}] },
  "news": { "results": [...] },
  "videos": { "results": [...] },
  "discussions": { "results": [...] }
}

Agent Workflow Examples

Debugging an error:

# Agent encounters: "TypeError: cannot unpack non-iterable NoneType"
bx "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096

Evaluating a dependency:

# Is this crate maintained? Any security issues?
bx context "reqwest crate security issues maintained 2026" --threshold strict
bx news "reqwest Rust crate" --freshness pm

Corrective RAG loop:

# 1. Broad search
bx "axum middleware authentication" --max-tokens 4096
# 2. Too general? Narrow down
bx "axum middleware tower layer authentication example" --threshold strict --max-tokens 4096
# 3. Still need synthesis? Ask for an answer
bx answers "how to implement JWT auth middleware in axum" --enable-research

Checking for breaking changes before upgrading:

bx context "Next.js 15 breaking changes migration guide" --max-tokens 8192
bx news "Next.js 15 release" --freshness pm

Focused search with Goggles (custom re-ranking):

# Boost official docs, prefer /docs/ paths, suppress SEO spam
bx "Python asyncio gather vs wait" \
  --goggles '$boost=3,site=docs.python.org
/docs/$boost=3
/blog/$downrank=2
$discard,site=geeksforgeeks.org
$discard,site=w3schools.com' --max-tokens 4096

Token budget control:

bx context "topic" --max-tokens 4096 --max-tokens-per-url 1024 --max-urls 5

Goggles — Custom Search Re-Ranking

Brave Goggles let you define custom re-ranking rules for search results. Boost domains, URL paths, or content patterns; downrank noise; discard SEO spam — from simple domain allow/deny lists to complex multi-rule ranking profiles. No other search API offers this. Supported on context, web, and news.

Domain Shortcuts — --include-site / --exclude-site

For the common case of restricting to or excluding specific domains, use the convenience flags (available on context, web, news):

# Only include results from these domains (allowlist)
bx "rust axum" --include-site docs.rs --include-site github.com

# Exclude specific domains
bx web "rust tutorial" --exclude-site w3schools.com --exclude-site medium.com

These generate Goggles rules internally. For more advanced re-ranking (boosting, path patterns, wildcards), use --goggles directly. The three flags are mutually exclusive.

Why Agents Should Use Goggles

  • Domain allow/deny lists: Boost, downrank, or discard entire domains — but that's just the start
  • URL path patterns: Boost /docs/ paths, downrank /blog/ paths, discard /ads/ — fine-grained URL targeting with wildcards
  • Full power of site: — Brave converts site: operators to Goggles internally, but explicit Goggles unlock the full DSL: hundreds of rules, path patterns, content matching, boost/downrank strengths — without bloating the query string
  • Keep queries clean: A single --goggles parameter (or URL) replaces long site:X OR site:Y chains in the query, saving tokens
  • Reusable & shareable: Host a .goggle file on GitHub — share among team members, agents, CI pipelines. Document your ranking preferences as code
  • Community-maintained: Leverage existing Goggles like Tech Blogs that are continuously updated

Inline Rules (zero setup)

# Domain boosting — prioritize official sources for a Python error
bx "TypeError cannot unpack non-iterable NoneType" \
  --goggles '$boost=3,site=docs.python.org
$boost=2,site=github.com
$discard,site=geeksforgeeks.org
$discard,site=w3schools.com'

# Path-based boosting — prefer /docs/ over /blog/ across all sites
bx context "axum middleware tower" \
  --goggles '/docs/$boost=5
/api/$boost=3
/blog/$downrank=3' --max-tokens 4096

# Ecosystem focus — boost Rust sources for crate research
bx context "serde custom deserializer" \
  --goggles '$boost=5,site=docs.rs
$boost=5,site=crates.io
$boost=3,site=github.com' --max-tokens 4096

# Downrank blog spam in news results
bx news "npm security advisory" --freshness pd \
  --goggles '$downrank=5,site=medium.com'

DSL Quick Reference

Rule Effect Example
$boost=N,site=DOMAIN Promote domain (N=1-10) $boost=3,site=docs.rs
$downrank=N,site=DOMAIN Demote domain (N=1-10) $downrank=5,site=medium.com
$discard,site=DOMAIN Remove domain entirely $discard,site=w3schools.com
/path/$boost=N Boost matching URL paths /docs/$boost=5
*pattern*$boost=N Wildcard URL matching *api*$boost=3
Generic $discard Allowlist mode — discard all unmatched $discard (as last rule)

Separate multiple rules with newlines. Full DSL + pattern syntax: goggles-quickstart.

From a File (@file) — ideal for agents

Agents can generate a .goggle file on the fly and reference it:

# Agent writes rules to a file, then uses it across multiple queries
cat > /tmp/rust.goggle << 'EOF'
$boost=5,site=docs.rs
$boost=5,site=crates.io
$boost=3,site=github.com
/blog/$downrank=3
$discard,site=w3schools.com
$discard,site=geeksforgeeks.org
EOF

bx context "axum middleware tower" --goggles @/tmp/rust.goggle --max-tokens 4096
bx context "serde custom deserializer" --goggles @/tmp/rust.goggle --max-tokens 4096

From stdin (@-) — pipe generated rules

# Pipe rules from another command
echo '$boost=5,site=docs.rs
$boost=3,site=github.com' | bx web "tokio runtime" --goggles @-

Hosted Goggles (reusable, shareable)

Host a .goggle file on GitHub/GitLab, submit it to Brave, then reference by URL:

# Use the official Tech Blogs goggle
bx web "distributed systems" \
  --goggles 'https://raw.githubusercontent.com/brave/goggles-quickstart/main/goggles/tech_blogs.goggle'

Community Goggles: brave/goggles-quickstart | Discover page

Getting an API Key

  1. Sign up at https://api-dashboard.search.brave.com/register
  2. Choose a plan — all plans include $5/month free credits (~1,000 free queries)
  3. Different endpoints may require different plans (e.g. Search vs Answers)
  4. Go to "API Keys" in the dashboard and generate a key

Configuration

Settings are resolved in priority order: CLI flag > environment variable > config file > default.

Setting CLI Flag Env Var Config Key Default
API key --api-key BRAVE_SEARCH_API_KEY api_key (interactive prompt)
Base URL --base-url BRAVE_SEARCH_BASE_URL base_url https://api.search.brave.com
Timeout --timeout — timeout 30 (300 for answers --enable-research; max 86400)

Streamed answers responses have no total deadline — every other command keeps one. The timeout bounds idle time: connecting, and then each read. A response that keeps arriving is never cut off however long it runs, and a server that only sends keep-alives never ends. Deep research pauses for minutes while it synthesises, so it defaults to 300 seconds of silence; --timeout or a timeout in your config file always wins, and applies to connecting too.

Config file (bx config path to see location):

  • Linux: ~/.config/brave-search/config.json
  • macOS: ~/Library/Application Support/brave-search/config.json
  • Windows: %APPDATA%\brave-search\config.json
{
  "api_key": "BSA...",
  "base_url": "https://api.search.brave.com"
}

Use --config <path> to load a different config file (e.g. one for dev, one for prod):

bx --config ~/.config/brave-search/dev.json web "test"

Security tip: Prefer the env var or config file over --api-key, which is visible in process listings. Use bx config set-key without an argument to enter the key interactively, avoiding shell history.

Saving a key writes a new file and renames it into place: mode 0600 on Linux and macOS, with the default directory ~/.config/brave-search kept 0700 (a directory you pass via --config is left as it is). Because the file is replaced rather than written in place, a symlink, hard link, or bind mount at the config path does not survive a save, and neither do extended attributes or ACLs on the old file — deliberate, so a key is never written through an indirection bx does not control. On Windows no ACL is set and the file inherits whatever %APPDATA%\brave-search grants — restrict that directory yourself if the machine has other users.

Local Proxy

You can point bx at a local reverse proxy instead of the Brave API directly. This is useful for sandboxed environments where credentials are managed outside the sandbox.

Allowed localhost forms:

  • http://127.0.0.1[:<port>][/path]
  • http://[::1][:<port>][/path]
  • http://localhost[:<port>][/path] (resolved and rewritten to a literal loopback IP)
# Point bx at a local proxy on port 8080 with path prefix /brave
BRAVE_SEARCH_BASE_URL=http://127.0.0.1:8080/brave bx "your query"

# Or in config.json
# { "base_url": "http://127.0.0.1:8080/brave" }

The proxy receives requests at the configured base URL (e.g. http://127.0.0.1:8080/brave/res/v1/web/search?q=...) and can inject the X-Subscription-Token header before forwarding to api.search.brave.com. Since bx still requires an API key, set a placeholder: BRAVE_SEARCH_API_KEY=unused.

Only loopback addresses are allowed for http:// URLs — non-loopback IPs and arbitrary hostnames are rejected. The localhost hostname is resolved at startup and rewritten to a literal IP to prevent DNS rebinding.

Commands

Command Description Output Shape
context RAG/LLM grounding — pre-extracted web content .grounding.generic[] → {url, title, snippets[]}
answers AI answers — OpenAI-compatible, streaming .choices[0].delta.content (stream)
web Full web search — all result types .web.results[], .news.results[], etc.
news News articles with freshness filters .results[] → {title, url, age}
images Image search (up to 200 results) .results[] → {title, url, thumbnail.src}
videos Video search with duration/views .results[] → {title, url, video.duration}
places Local place/POI search (200M+ POIs) .results[] → {title, postal_address, contact}
suggest Autocomplete/query suggestions .results[] → {query}
spellcheck Spell-check a query .results[0].query
pois POI details by ID (use IDs from places)
descriptions AI-generated POI descriptions .results[].description
config Manage configuration set-key, show-key, show, path

Usage Examples

# Default subcommand is context
bx "tokio spawn async task example"

# RAG grounding with token budget
bx context "Python TypeError cannot unpack non-iterable NoneType" --max-tokens 4096 --threshold strict

# AI answer (streaming)
bx answers "explain Rust lifetimes with examples"

# AI answer (non-streaming)
bx answers "compare SQLx and Diesel for Rust database access" --no-stream | jq .

# Research mode (multi-step, thorough)
bx answers "what changed in React 19 vs React 18?" --enable-research

# Web search with site: scoping
bx web "site:docs.rs axum middleware" | jq '.web.results[].url'

# News from the past day
bx news "npm security advisory" --freshness pd | jq '.results[].title'

# Image search
bx images "system architecture diagram microservices" | jq '.results[].thumbnail.src'

# Autocomplete
bx suggest "how to implement" --count 10 | jq '.results[].query'

# Local places
bx places --location "San Francisco CA US" -q "coffee" | jq '.results[].title'

# Location-aware web search
bx web "restaurants near me" --lat 37.7749 --long -122.4194 --city "San Francisco"

# Filter result types
bx web "rust" --result-filter "web,discussions"

Extra Parameters and Custom Endpoints

--extra KEY=VALUE

Pass additional API parameters not covered by named flags. Repeatable. Merged into the JSON body (POST endpoints) or query string (GET endpoints). Warns if the key collides with an existing flag.

For POST endpoints, values are auto-typed: integers and floats become JSON numbers, true/false become JSON booleans, everything else stays a string. GET values are always strings.

bx web "rust" --extra custom_param=value
bx web "rust" --extra count=5            # overrides --count (with warning)
bx images "sunset" --extra safesearch=off

--endpoint PATH

Override the default API path. For internal or beta Brave endpoints. Must start with /.

bx web "test" --endpoint /res/v2/web/search

Answers Endpoint

The answers command supports two modes:

Simple Mode

Pass a question directly:

bx answers "explain Rust lifetimes with examples"         # streaming (default)
bx answers "compare SQLx and Diesel" --no-stream          # single JSON response
bx answers "what changed in React 19?" --enable-research  # deep research
bx answers "Hello" --model brave                          # faster model

--enable-citations, --enable-entities, and --enable-research all require streaming, so none combines with --no-stream. Citations and entities are not supported in research mode, but are fine together. Rejected combinations exit 2, nothing sent:

bx answers "what changed in React 19?" --enable-research --enable-citations
# error: the argument '--enable-research' cannot be used with '--enable-citations'

Stdin Mode

Pass - to read a full JSON request body from stdin:

echo '{"messages":[{"role":"user","content":"review this code for security issues"}],"model":"brave-pro"}' | bx answers -
cat request.json | bx answers -

Exit Codes

Code Meaning Agent action
0 Success Process results
1 The request was made; the result is unusable Fix query/parameters
2 Usage error — nothing was sent Fix the CLI arguments, config, or input
3 Auth/permission error (401/403) Check API key or plan: bx config show-key
4 Rate limited (429) Retry after delay
5 Server/network error Retry with backoff
127 npm launcher could not start bx Reinstall, or check platform support

Error Handling

Errors are printed to stderr with a human-readable summary, recovery hints, and the full JSON error body:

error: rate limited (429) — Request rate limit exceeded for plan.
hint: retry after a short delay, or upgrade plan for higher rate limits
{"type":"ErrorResponse","error":{"code":"RATE_LIMITED","status":429,...}}

Exit codes are differentiated — see Exit Codes above. The line between 1 and 2 is whether a request was made: 2 means bx could not act on what it was given and never contacted the API, so retrying without changing the command will not help.

Building from Source

# Debug build
cargo build

# Release build (~2 MB)
cargo build --release

Static binaries via Docker

Requires Docker, GNU Make, and a POSIX shell (sh/bash). Produces fully static, cross-compiled binaries.

On Windows, run these targets from Git Bash or WSL. Running make from native cmd/PowerShell can fail because the Makefile uses POSIX utilities (grep, sed, shell redirection).

make linux-amd64     # → dist/bx-<version>-linux-amd64
make linux-arm64     # → dist/bx-<version>-linux-arm64
make darwin-amd64    # → dist/bx-<version>-darwin-amd64
make darwin-arm64    # → dist/bx-<version>-darwin-arm64
make windows-amd64   # → dist/bx-<version>-windows-amd64.exe
make windows-arm64   # → dist/bx-<version>-windows-arm64.exe
make dist-all        # → all of the above

Both macOS binaries require macOS 11.7.1 or later — zig sets that deployment target and MACOSX_DEPLOYMENT_TARGET does not override it. The smoke-darwin job asserts it, so a toolchain bump that raises the floor fails CI.

Testing

# End-to-end API smoke suite (requires bash + jq + BRAVE_SEARCH_API_KEY/BRAVE_API_KEY)
make test

# Unit tests (no network/API key required)
make test-unit

About

No description, website, or topics provided.

Resources

Stars

83 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages