Metadata-Version: 2.5
Name: surfx
Version: 0.2.0
Summary: A fast, clean, terminal-first web search CLI powered by SearXNG
Project-URL: Homepage, https://github.com/Sam3360/surfx
Project-URL: Repository, https://github.com/Sam3360/surfx
Project-URL: Issues, https://github.com/Sam3360/surfx/issues
Project-URL: Changelog, https://github.com/Sam3360/surfx/blob/main/CHANGELOG.md
Author: Samarth Chugh (Sam3360)
License-Expression: MIT
License-File: LICENSE
Keywords: cli,metasearch,productivity,search,searxng,terminal
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: rich>=13.7.1
Requires-Dist: tomli-w>=1.0.0
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
Requires-Dist: typer>=0.12.3
Provides-Extra: dev
Requires-Dist: build>=1.2.1; extra == 'dev'
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff>=0.6.0; extra == 'dev'
Requires-Dist: twine>=5.1.1; extra == 'dev'
Description-Content-Type: text/markdown

# Surfx

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

Search the web without leaving your terminal - powered by
[SearXNG](https://docs.searxng.org/), 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.

```text
╭──────────────────────────────────────────╮
│ 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](https://docs.searxng.org/) 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](https://docs.searxng.org/admin/installation.html).
   A basic Docker setup takes a few minutes.
2. **Use a public instance** that has JSON output enabled - browse
   [searx.space](https://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

```bash
pip install surfx
```

Requires Python 3.11+.

## Configure a SearXNG instance

Set the instance URL as an environment variable:

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

Windows PowerShell:

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

Or store it in Surfx's config file instead (see [Configuration](#configuration)):

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

Then run:

```bash
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](SECURITY.md).)

## Usage

```bash
# 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:

```text
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:

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

The config file may contain:

```toml
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

```bash
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

```bash
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](https://docs.pypi.org/trusted-publishers/) (OIDC),
triggered by pushing a `v*` tag - no long-lived PyPI token is stored in this
repository.

## License

MIT - see [LICENSE](LICENSE).
