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.
@@ -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
@@ -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).