surfx 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- surfx-0.2.0/.github/workflows/publish.yml +56 -0
- surfx-0.2.0/.github/workflows/tests.yml +59 -0
- surfx-0.2.0/.gitignore +45 -0
- surfx-0.2.0/.python-version +1 -0
- surfx-0.2.0/CHANGELOG.md +61 -0
- surfx-0.2.0/CONTRIBUTING.md +61 -0
- surfx-0.2.0/LICENSE +21 -0
- surfx-0.2.0/PKG-INFO +290 -0
- surfx-0.2.0/README.md +252 -0
- surfx-0.2.0/SECURITY.md +58 -0
- surfx-0.2.0/pyproject.toml +78 -0
- surfx-0.2.0/src/surfx/__init__.py +5 -0
- surfx-0.2.0/src/surfx/__main__.py +4 -0
- surfx-0.2.0/src/surfx/browser.py +25 -0
- surfx-0.2.0/src/surfx/cli.py +337 -0
- surfx-0.2.0/src/surfx/config.py +213 -0
- surfx-0.2.0/src/surfx/errors.py +64 -0
- surfx-0.2.0/src/surfx/models.py +63 -0
- surfx-0.2.0/src/surfx/providers/__init__.py +16 -0
- surfx-0.2.0/src/surfx/providers/base.py +28 -0
- surfx-0.2.0/src/surfx/providers/searxng.py +191 -0
- surfx-0.2.0/src/surfx/services/__init__.py +1 -0
- surfx-0.2.0/src/surfx/services/cache.py +111 -0
- surfx-0.2.0/src/surfx/services/search.py +70 -0
- surfx-0.2.0/src/surfx/terminal.py +60 -0
- surfx-0.2.0/tests/conftest.py +24 -0
- surfx-0.2.0/tests/test_browser.py +39 -0
- surfx-0.2.0/tests/test_cli.py +204 -0
- surfx-0.2.0/tests/test_config.py +103 -0
- surfx-0.2.0/tests/test_models.py +60 -0
- surfx-0.2.0/tests/test_search.py +123 -0
- surfx-0.2.0/tests/test_searxng.py +239 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
name: Build distribution
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Check out repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
|
|
23
|
+
- name: Install build tooling
|
|
24
|
+
run: python -m pip install build twine
|
|
25
|
+
|
|
26
|
+
- name: Build sdist and wheel
|
|
27
|
+
run: python -m build
|
|
28
|
+
|
|
29
|
+
- name: Check distribution metadata
|
|
30
|
+
run: python -m twine check dist/*
|
|
31
|
+
|
|
32
|
+
- name: Upload build artifacts
|
|
33
|
+
uses: actions/upload-artifact@v4
|
|
34
|
+
with:
|
|
35
|
+
name: dist
|
|
36
|
+
path: dist/
|
|
37
|
+
|
|
38
|
+
publish:
|
|
39
|
+
name: Publish to PyPI
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
needs: build
|
|
42
|
+
environment:
|
|
43
|
+
name: pypi
|
|
44
|
+
url: https://pypi.org/project/surfx/
|
|
45
|
+
permissions:
|
|
46
|
+
# Required for PyPI Trusted Publishing (OIDC) - no API token needed.
|
|
47
|
+
id-token: write
|
|
48
|
+
steps:
|
|
49
|
+
- name: Download build artifacts
|
|
50
|
+
uses: actions/download-artifact@v4
|
|
51
|
+
with:
|
|
52
|
+
name: dist
|
|
53
|
+
path: dist/
|
|
54
|
+
|
|
55
|
+
- name: Publish to PyPI
|
|
56
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install package with dev dependencies
|
|
28
|
+
run: python -m pip install -e ".[dev]"
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: pytest
|
|
32
|
+
|
|
33
|
+
- name: Lint with Ruff
|
|
34
|
+
run: ruff check .
|
|
35
|
+
|
|
36
|
+
- name: Check formatting with Ruff
|
|
37
|
+
run: ruff format --check .
|
|
38
|
+
|
|
39
|
+
build:
|
|
40
|
+
name: Build distribution
|
|
41
|
+
runs-on: ubuntu-latest
|
|
42
|
+
needs: test
|
|
43
|
+
steps:
|
|
44
|
+
- name: Check out repository
|
|
45
|
+
uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
- name: Set up Python
|
|
48
|
+
uses: actions/setup-python@v5
|
|
49
|
+
with:
|
|
50
|
+
python-version: "3.12"
|
|
51
|
+
|
|
52
|
+
- name: Install build tooling
|
|
53
|
+
run: python -m pip install build twine
|
|
54
|
+
|
|
55
|
+
- name: Build sdist and wheel
|
|
56
|
+
run: python -m build
|
|
57
|
+
|
|
58
|
+
- name: Check distribution metadata
|
|
59
|
+
run: python -m twine check dist/*
|
surfx-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
wheels/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# Testing
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
htmlcov/
|
|
23
|
+
.tox/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
|
|
26
|
+
# Type checkers
|
|
27
|
+
.mypy_cache/
|
|
28
|
+
.pyright/
|
|
29
|
+
|
|
30
|
+
# Distribution / packaging
|
|
31
|
+
*.egg
|
|
32
|
+
MANIFEST
|
|
33
|
+
|
|
34
|
+
# Editors
|
|
35
|
+
.vscode/
|
|
36
|
+
.idea/
|
|
37
|
+
*.swp
|
|
38
|
+
|
|
39
|
+
# OS
|
|
40
|
+
.DS_Store
|
|
41
|
+
Thumbs.db
|
|
42
|
+
|
|
43
|
+
# Surfx runtime data (never commit local config or cache)
|
|
44
|
+
.surfx-cache.json
|
|
45
|
+
config.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
surfx-0.2.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **Breaking:** replaced the Google Programmable Search (Custom Search
|
|
13
|
+
JSON API) provider with a new `SearXNGProvider`, backed by
|
|
14
|
+
[SearXNG](https://docs.searxng.org/), an open-source metasearch engine.
|
|
15
|
+
Surfx no longer requires an API key or cloud account for search - only
|
|
16
|
+
the URL of a SearXNG instance.
|
|
17
|
+
- `searxng` is now the default (and only built-in) provider.
|
|
18
|
+
- `surfx doctor` now checks that a SearXNG instance URL is configured and
|
|
19
|
+
performs a live reachability/JSON-validity check against it, in addition
|
|
20
|
+
to the existing Python/config/network checks.
|
|
21
|
+
- `SearchResult` gained optional `engine` and `category` fields, populated
|
|
22
|
+
from SearXNG's per-result metadata (which upstream engine produced a
|
|
23
|
+
result, and its category) when available. These are included in
|
|
24
|
+
`--json` output and shown as a small "via <engine>" tag in normal
|
|
25
|
+
output.
|
|
26
|
+
|
|
27
|
+
### Removed
|
|
28
|
+
|
|
29
|
+
- `SURFX_GOOGLE_API_KEY` and `SURFX_GOOGLE_CX` environment variables, and
|
|
30
|
+
all Google-specific setup steps, are gone. Nothing in Surfx requires a
|
|
31
|
+
Google Cloud account anymore.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- `SURFX_SEARXNG_URL` environment variable, and a `searxng_url` config
|
|
36
|
+
file key (via `surfx config set searxng_url <url>`), to point Surfx at
|
|
37
|
+
a self-hosted or public SearXNG instance.
|
|
38
|
+
|
|
39
|
+
### Migration notes
|
|
40
|
+
|
|
41
|
+
- Unset `SURFX_GOOGLE_API_KEY` / `SURFX_GOOGLE_CX` (they're now ignored)
|
|
42
|
+
and set `SURFX_SEARXNG_URL` to a SearXNG instance you control or trust.
|
|
43
|
+
See the README's "What is SearXNG" section for how to get one.
|
|
44
|
+
|
|
45
|
+
## [0.1.0] - 2026-09-02
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- Initial release of `surfx`.
|
|
50
|
+
- `surfx "query"` search command backed by the Google Programmable Search
|
|
51
|
+
(Custom Search JSON API) provider.
|
|
52
|
+
- Provider abstraction (`SearchProvider` protocol) so new backends can be
|
|
53
|
+
added without changing the CLI.
|
|
54
|
+
- Interactive mode (`surfx` with no query) with `/help`, `/clear`, `/exit`,
|
|
55
|
+
`/quit`.
|
|
56
|
+
- `--limit/-n`, `--provider`, `--json`, `--no-color`, `--open`, `--debug`
|
|
57
|
+
options.
|
|
58
|
+
- `surfx config` and `surfx config set <key> <value>` for non-secret settings.
|
|
59
|
+
- `surfx doctor` environment/configuration check.
|
|
60
|
+
- Optional, conservative local result cache with short TTL.
|
|
61
|
+
- Full offline test suite with mocked HTTP responses.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Contributing to Surfx
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in improving Surfx! Contributions of all sizes are
|
|
4
|
+
welcome, from typo fixes to new search providers.
|
|
5
|
+
|
|
6
|
+
## Getting set up
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/Sam3360/surfx.git
|
|
10
|
+
cd surfx
|
|
11
|
+
python -m venv .venv
|
|
12
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
13
|
+
pip install -e ".[dev]"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Running the test suite
|
|
17
|
+
|
|
18
|
+
The full test suite runs completely offline - no real SearXNG instance or
|
|
19
|
+
network access is required. All HTTP calls are mocked.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pytest
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Linting and formatting
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
ruff check .
|
|
29
|
+
ruff format --check .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Please run both before opening a pull request.
|
|
33
|
+
|
|
34
|
+
## Adding a new search provider
|
|
35
|
+
|
|
36
|
+
1. Create `src/surfx/providers/<name>.py` implementing the
|
|
37
|
+
`SearchProvider` protocol from `src/surfx/providers/base.py`:
|
|
38
|
+
- `validate_config()` should raise a `surfx.errors.SurfxError` subclass
|
|
39
|
+
when required configuration is missing.
|
|
40
|
+
- `search(query, limit)` should return a list of provider-independent
|
|
41
|
+
`surfx.models.SearchResult` objects, and must never leak the raw
|
|
42
|
+
provider response format to callers.
|
|
43
|
+
2. Register the provider in `src/surfx/providers/__init__.py`'s
|
|
44
|
+
`PROVIDERS` mapping.
|
|
45
|
+
3. If your provider needs custom construction arguments, extend
|
|
46
|
+
`build_provider()` in `src/surfx/services/search.py`.
|
|
47
|
+
4. Add tests that mock all HTTP calls (see `tests/test_searxng.py` for the
|
|
48
|
+
pattern using `httpx.MockTransport`).
|
|
49
|
+
5. Update the README's provider list.
|
|
50
|
+
|
|
51
|
+
## Pull requests
|
|
52
|
+
|
|
53
|
+
- Keep changes focused and include tests.
|
|
54
|
+
- Update `CHANGELOG.md` under an "Unreleased" heading.
|
|
55
|
+
- Describe what you changed and why in the PR description.
|
|
56
|
+
|
|
57
|
+
## Reporting bugs
|
|
58
|
+
|
|
59
|
+
Please open a GitHub issue with steps to reproduce, what you expected, and
|
|
60
|
+
what actually happened. For security issues, see `SECURITY.md` instead of
|
|
61
|
+
filing a public issue.
|
surfx-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam3360
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
surfx-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: surfx
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A fast, clean, terminal-first web search CLI powered by SearXNG
|
|
5
|
+
Project-URL: Homepage, https://github.com/Sam3360/surfx
|
|
6
|
+
Project-URL: Repository, https://github.com/Sam3360/surfx
|
|
7
|
+
Project-URL: Issues, https://github.com/Sam3360/surfx/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Sam3360/surfx/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Samarth Chugh (Sam3360)
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cli,metasearch,productivity,search,searxng,terminal
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
|
+
Requires-Dist: httpx>=0.27.0
|
|
27
|
+
Requires-Dist: rich>=13.7.1
|
|
28
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
29
|
+
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
|
|
30
|
+
Requires-Dist: typer>=0.12.3
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: build>=1.2.1; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=5.1.1; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Surfx
|
|
40
|
+
|
|
41
|
+
A fast, clean, terminal-first web search CLI for developers and power users.
|
|
42
|
+
|
|
43
|
+
Search the web without leaving your terminal - powered by
|
|
44
|
+
[SearXNG](https://docs.searxng.org/), an open-source, privacy-respecting
|
|
45
|
+
**metasearch engine**. Surfx queries a SearXNG *instance* over its
|
|
46
|
+
documented JSON API; it does not scrape Google (or any other engine)
|
|
47
|
+
directly, and it never requires you to create an API key or a cloud
|
|
48
|
+
account.
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
╭──────────────────────────────────────────╮
|
|
52
|
+
│ Surfx │
|
|
53
|
+
│ Search results for "python asyncio" │
|
|
54
|
+
╰──────────────────────────────────────────╯
|
|
55
|
+
|
|
56
|
+
[1] Asyncio — Python documentation
|
|
57
|
+
https://docs.python.org/3/library/asyncio.html
|
|
58
|
+
Asyncio is a library to write concurrent code using async/await syntax.
|
|
59
|
+
via duckduckgo
|
|
60
|
+
|
|
61
|
+
[2] Async IO in Python: A Complete Walkthrough
|
|
62
|
+
https://realpython.com/async-io-python/
|
|
63
|
+
...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What is Surfx?
|
|
67
|
+
|
|
68
|
+
Surfx is a small, well-tested CLI that lets you run real web searches from
|
|
69
|
+
the terminal and get clean, readable, Rich-formatted results (or raw JSON
|
|
70
|
+
for scripting). It's built around a provider abstraction, so the underlying
|
|
71
|
+
search backend can change without touching the CLI itself.
|
|
72
|
+
|
|
73
|
+
## What is SearXNG, and why does Surfx need an instance URL?
|
|
74
|
+
|
|
75
|
+
[SearXNG](https://docs.searxng.org/) is open-source software that runs a
|
|
76
|
+
"metasearch" service: it queries a number of upstream search engines on
|
|
77
|
+
your behalf (Google, Bing, DuckDuckGo, and many others, depending on how
|
|
78
|
+
the instance is configured) and returns aggregated results, without
|
|
79
|
+
tracking you. Anyone can run a SearXNG instance, so Surfx needs to know
|
|
80
|
+
*which* instance to talk to - that's the one thing you configure.
|
|
81
|
+
|
|
82
|
+
You have two options:
|
|
83
|
+
|
|
84
|
+
1. **Self-host an instance** (recommended for regular use, and the most
|
|
85
|
+
private option) - see the
|
|
86
|
+
[SearXNG installation docs](https://docs.searxng.org/admin/installation.html).
|
|
87
|
+
A basic Docker setup takes a few minutes.
|
|
88
|
+
2. **Use a public instance** that has JSON output enabled - browse
|
|
89
|
+
[searx.space](https://searx.space) for currently active public
|
|
90
|
+
instances and their capabilities. Public instances are run by
|
|
91
|
+
volunteers, can disappear or change their settings at any time, and see
|
|
92
|
+
the queries you send them - don't rely on one for anything sensitive.
|
|
93
|
+
|
|
94
|
+
Surfx does **not** ship with a hardcoded default instance: public
|
|
95
|
+
instances are unreliable enough (many disable the JSON API entirely) that
|
|
96
|
+
guessing one for you would just produce confusing failures. Point Surfx at
|
|
97
|
+
an instance you trust instead.
|
|
98
|
+
|
|
99
|
+
## Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install surfx
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Requires Python 3.11+.
|
|
106
|
+
|
|
107
|
+
## Configure a SearXNG instance
|
|
108
|
+
|
|
109
|
+
Set the instance URL as an environment variable:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
export SURFX_SEARXNG_URL="https://your-searxng-instance.example"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Windows PowerShell:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
$env:SURFX_SEARXNG_URL="https://your-searxng-instance.example"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or store it in Surfx's config file instead (see [Configuration](#configuration)):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
surfx config set searxng_url https://your-searxng-instance.example
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Then run:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
surfx "python httpx tutorial"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
No API keys, cloud accounts, or billing are involved - the instance URL is
|
|
134
|
+
the only thing Surfx needs. (No secrets ever end up in the example
|
|
135
|
+
commands above, and Surfx never writes secrets to its config file or
|
|
136
|
+
cache in any case - see [Security](SECURITY.md).)
|
|
137
|
+
|
|
138
|
+
## Usage
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Basic search
|
|
142
|
+
surfx "python asyncio tutorial"
|
|
143
|
+
|
|
144
|
+
# Control how many results come back (1-50)
|
|
145
|
+
surfx "python asyncio tutorial" --limit 20
|
|
146
|
+
surfx "python asyncio tutorial" -n 20
|
|
147
|
+
|
|
148
|
+
# Choose a provider explicitly (currently: searxng)
|
|
149
|
+
surfx "python asyncio tutorial" --provider searxng
|
|
150
|
+
|
|
151
|
+
# Machine-readable output (stdout is valid JSON only; errors go to stderr)
|
|
152
|
+
surfx "python asyncio tutorial" --json
|
|
153
|
+
|
|
154
|
+
# Disable colors/styling, e.g. for logging pipelines
|
|
155
|
+
surfx "python asyncio tutorial" --no-color
|
|
156
|
+
|
|
157
|
+
# Open a specific result in your default browser
|
|
158
|
+
surfx "python asyncio tutorial" --open 1
|
|
159
|
+
|
|
160
|
+
# Skip the "open this?" confirmation prompt
|
|
161
|
+
surfx "python asyncio tutorial" --open 1 --yes
|
|
162
|
+
|
|
163
|
+
# Show full tracebacks for debugging
|
|
164
|
+
surfx "python asyncio tutorial" --debug
|
|
165
|
+
|
|
166
|
+
# Check your setup, including that your SearXNG instance is reachable
|
|
167
|
+
surfx doctor
|
|
168
|
+
|
|
169
|
+
# Inspect configuration
|
|
170
|
+
surfx config
|
|
171
|
+
surfx config set searxng_url https://your-searxng-instance.example
|
|
172
|
+
surfx config set limit 10
|
|
173
|
+
|
|
174
|
+
surfx --version
|
|
175
|
+
surfx --help
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Interactive mode
|
|
179
|
+
|
|
180
|
+
Running `surfx` with no query drops you into an interactive search prompt:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
Surfx
|
|
184
|
+
|
|
185
|
+
Search > python decorators
|
|
186
|
+
|
|
187
|
+
[1] ...
|
|
188
|
+
[2] ...
|
|
189
|
+
|
|
190
|
+
Search > machine learning tutorials
|
|
191
|
+
|
|
192
|
+
[1] ...
|
|
193
|
+
|
|
194
|
+
Search > /exit
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Supported interactive commands: `/help`, `/clear`, `/exit`, `/quit`.
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
Surfx resolves settings using this precedence, highest first:
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
CLI arguments
|
|
205
|
+
↓
|
|
206
|
+
environment variables
|
|
207
|
+
↓
|
|
208
|
+
config file (~/.config/surfx/config.toml)
|
|
209
|
+
↓
|
|
210
|
+
built-in defaults
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The config file may contain:
|
|
214
|
+
|
|
215
|
+
```toml
|
|
216
|
+
provider = "searxng"
|
|
217
|
+
limit = 10
|
|
218
|
+
searxng_url = "https://your-searxng-instance.example"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Secret-shaped values (API keys, tokens, etc.) can never be stored in the
|
|
222
|
+
config file - only environment variables are accepted for those. The
|
|
223
|
+
SearXNG URL isn't a secret, so it may live in either place.
|
|
224
|
+
|
|
225
|
+
| Environment variable | Purpose |
|
|
226
|
+
| -------------------------- | ------------------------------------------ |
|
|
227
|
+
| `SURFX_SEARXNG_URL` | SearXNG instance URL (e.g. `https://...`) |
|
|
228
|
+
| `SURFX_PROVIDER` | Default provider (`searxng`) |
|
|
229
|
+
| `SURFX_LIMIT` | Default result limit (1-50) |
|
|
230
|
+
| `SURFX_NO_COLOR` | Disable styled output when truthy |
|
|
231
|
+
| `SURFX_CACHE_ENABLED` | Enable the local result cache |
|
|
232
|
+
| `SURFX_CACHE_TTL_SECONDS` | Cache entry lifetime in seconds |
|
|
233
|
+
|
|
234
|
+
## Architecture
|
|
235
|
+
|
|
236
|
+
Surfx separates concerns into a few small layers:
|
|
237
|
+
|
|
238
|
+
- **`providers/`** - one module per search backend, each implementing the
|
|
239
|
+
`SearchProvider` protocol (`validate_config()`, `search(query, limit)`)
|
|
240
|
+
and returning provider-independent `SearchResult` objects. The CLI and
|
|
241
|
+
service layer never see a provider's raw response format. The current
|
|
242
|
+
implementation is `SearXNGProvider`, which talks to a SearXNG instance's
|
|
243
|
+
`/search?format=json` endpoint.
|
|
244
|
+
- **`services/search.py`** - orchestrates provider selection, limit
|
|
245
|
+
validation, and the optional cache.
|
|
246
|
+
- **`services/cache.py`** - a small, conservative, optional local cache
|
|
247
|
+
(disabled by default, short TTL, never stores credentials).
|
|
248
|
+
- **`cli.py`** - the Typer-based command-line interface and interactive
|
|
249
|
+
mode.
|
|
250
|
+
- **`terminal.py`** - Rich-based rendering, kept separate from business
|
|
251
|
+
logic so it's easy to test the rest of the app without a real terminal.
|
|
252
|
+
|
|
253
|
+
Adding a new provider means adding one file under `providers/` and
|
|
254
|
+
registering it in `providers/__init__.py` - no changes to the CLI
|
|
255
|
+
required. See `CONTRIBUTING.md` for the full walkthrough.
|
|
256
|
+
|
|
257
|
+
## Development
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
git clone https://github.com/Sam3360/surfx.git
|
|
261
|
+
cd surfx
|
|
262
|
+
python -m venv .venv
|
|
263
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
264
|
+
pip install -e ".[dev]"
|
|
265
|
+
|
|
266
|
+
pytest
|
|
267
|
+
ruff check .
|
|
268
|
+
ruff format --check .
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The test suite runs entirely offline - all HTTP calls to SearXNG are
|
|
272
|
+
mocked, so no real instance or network access is required.
|
|
273
|
+
|
|
274
|
+
## Publishing
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
python -m build
|
|
278
|
+
python -m twine check dist/*
|
|
279
|
+
python -m twine upload dist/*
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Releases to PyPI are automated via GitHub Actions
|
|
283
|
+
(`.github/workflows/publish.yml`) using
|
|
284
|
+
[PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC),
|
|
285
|
+
triggered by pushing a `v*` tag - no long-lived PyPI token is stored in this
|
|
286
|
+
repository.
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
MIT - see [LICENSE](LICENSE).
|