Skip to main content

Surfx

A fast, clean, terminal-first web search CLI for developers and power users.

Search the web without leaving your terminal - powered by SearXNG, an open-source, privacy-respecting metasearch engine. Surfx queries a SearXNG instance over its documented JSON API; it does not scrape Google (or any other engine) directly, and it never requires you to create an API key or a cloud account.

╭──────────────────────────────────────────╮
│ Surfx                                     │
│ Search results for "python asyncio"       │
╰──────────────────────────────────────────╯

[1] Asyncio — Python documentation
    https://docs.python.org/3/library/asyncio.html
    Asyncio is a library to write concurrent code using async/await syntax.
    via duckduckgo

[2] Async IO in Python: A Complete Walkthrough
    https://realpython.com/async-io-python/
    ...

What is Surfx?

Surfx is a small, well-tested CLI that lets you run real web searches from the terminal and get clean, readable, Rich-formatted results (or raw JSON for scripting). It's built around a provider abstraction, so the underlying search backend can change without touching the CLI itself.

What is SearXNG, and why does Surfx need an instance URL?

SearXNG is open-source software that runs a "metasearch" service: it queries a number of upstream search engines on your behalf (Google, Bing, DuckDuckGo, and many others, depending on how the instance is configured) and returns aggregated results, without tracking you. Anyone can run a SearXNG instance, so Surfx needs to know which instance to talk to - that's the one thing you configure.

You have two options:

  1. Self-host an instance (recommended for regular use, and the most private option) - see the SearXNG installation docs. A basic Docker setup takes a few minutes.
  2. Use a public instance that has JSON output enabled - browse searx.space for currently active public instances and their capabilities. Public instances are run by volunteers, can disappear or change their settings at any time, and see the queries you send them - don't rely on one for anything sensitive.

Surfx does not ship with a hardcoded default instance: public instances are unreliable enough (many disable the JSON API entirely) that guessing one for you would just produce confusing failures. Point Surfx at an instance you trust instead.

Installation

pip install surfx

Requires Python 3.11+.

Configure a SearXNG instance

Set the instance URL as an environment variable:

export SURFX_SEARXNG_URL="https://your-searxng-instance.example"

Windows PowerShell:

$env:SURFX_SEARXNG_URL="https://your-searxng-instance.example"

Or store it in Surfx's config file instead (see Configuration):

surfx config set searxng_url https://your-searxng-instance.example

Then run:

surfx "python httpx tutorial"

No API keys, cloud accounts, or billing are involved - the instance URL is the only thing Surfx needs. (No secrets ever end up in the example commands above, and Surfx never writes secrets to its config file or cache in any case - see Security.)

Usage

# Basic search
surfx "python asyncio tutorial"

# Control how many results come back (1-50)
surfx "python asyncio tutorial" --limit 20
surfx "python asyncio tutorial" -n 20

# Choose a provider explicitly (currently: searxng)
surfx "python asyncio tutorial" --provider searxng

# Machine-readable output (stdout is valid JSON only; errors go to stderr)
surfx "python asyncio tutorial" --json

# Disable colors/styling, e.g. for logging pipelines
surfx "python asyncio tutorial" --no-color

# Open a specific result in your default browser
surfx "python asyncio tutorial" --open 1

# Skip the "open this?" confirmation prompt
surfx "python asyncio tutorial" --open 1 --yes

# Show full tracebacks for debugging
surfx "python asyncio tutorial" --debug

# Check your setup, including that your SearXNG instance is reachable
surfx doctor

# Inspect configuration
surfx config
surfx config set searxng_url https://your-searxng-instance.example
surfx config set limit 10

surfx --version
surfx --help

Interactive mode

Running surfx with no query drops you into an interactive search prompt:

Surfx

Search > python decorators

[1] ...
[2] ...

Search > machine learning tutorials

[1] ...

Search > /exit

Supported interactive commands: /help, /clear, /exit, /quit.

Configuration

Surfx resolves settings using this precedence, highest first:

CLI arguments
    ↓
environment variables
    ↓
config file (~/.config/surfx/config.toml)
    ↓
built-in defaults

The config file may contain:

provider = "searxng"
limit = 10
searxng_url = "https://your-searxng-instance.example"

Secret-shaped values (API keys, tokens, etc.) can never be stored in the config file - only environment variables are accepted for those. The SearXNG URL isn't a secret, so it may live in either place.

Environment variable Purpose
SURFX_SEARXNG_URL SearXNG instance URL (e.g. https://...)
SURFX_PROVIDER Default provider (searxng)
SURFX_LIMIT Default result limit (1-50)
SURFX_NO_COLOR Disable styled output when truthy
SURFX_CACHE_ENABLED Enable the local result cache
SURFX_CACHE_TTL_SECONDS Cache entry lifetime in seconds

Architecture

Surfx separates concerns into a few small layers:

  • providers/ - one module per search backend, each implementing the SearchProvider protocol (validate_config(), search(query, limit)) and returning provider-independent SearchResult objects. The CLI and service layer never see a provider's raw response format. The current implementation is SearXNGProvider, which talks to a SearXNG instance's /search?format=json endpoint.
  • services/search.py - orchestrates provider selection, limit validation, and the optional cache.
  • services/cache.py - a small, conservative, optional local cache (disabled by default, short TTL, never stores credentials).
  • cli.py - the Typer-based command-line interface and interactive mode.
  • terminal.py - Rich-based rendering, kept separate from business logic so it's easy to test the rest of the app without a real terminal.

Adding a new provider means adding one file under providers/ and registering it in providers/__init__.py - no changes to the CLI required. See CONTRIBUTING.md for the full walkthrough.

Development

git clone https://github.com/Sam3360/surfx.git
cd surfx
python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

pytest
ruff check .
ruff format --check .

The test suite runs entirely offline - all HTTP calls to SearXNG are mocked, so no real instance or network access is required.

Publishing

python -m build
python -m twine check dist/*
python -m twine upload dist/*

Releases to PyPI are automated via GitHub Actions (.github/workflows/publish.yml) using PyPI Trusted Publishing (OIDC), triggered by pushing a v* tag - no long-lived PyPI token is stored in this repository.

License

MIT - see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

surfx-0.2.0.tar.gz (25.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

surfx-0.2.0-py3-none-any.whl (21.8 kB view details)

Uploaded Python 3

File details

Details for the file surfx-0.2.0.tar.gz.

File metadata

  • Download URL: surfx-0.2.0.tar.gz
  • Upload date:
  • Size: 25.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for surfx-0.2.0.tar.gz
Algorithm Hash digest
SHA256 61dab9bb999b757ccdab813ec7883ae64d2adc06a81c637c1f83b8b4bc90682c
MD5 989470520607c003bbfb1a5e3b32407b
BLAKE2b-256 c470ddd09b26e10ab90d573da1103601cfd391ed60d8cb39a5ae7e2d1da71e7e

See more details on using hashes here.

File details

Details for the file surfx-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: surfx-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.2

File hashes

Hashes for surfx-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f8bde8bd1a0d6adf081ecde0f4584d7848cb5237a352e385ba387e6d1bd6da3e
MD5 de2c25562e1528f921b006ecefd35a2b
BLAKE2b-256 464cd9894ce1523173dc1faee5986b1198af396885ebe743b399977e1230dcc5

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page