webscour 0.1.1__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.
webscour-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Furkan Cevik
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.
@@ -0,0 +1,324 @@
1
+ Metadata-Version: 2.4
2
+ Name: webscour
3
+ Version: 0.1.1
4
+ Summary: One keyless CLI for web search, page text, PDFs, sitemaps and a polite crawl
5
+ Author: Furkan Cevik
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/FurBlood344324/webscour
8
+ Project-URL: Issues, https://github.com/FurBlood344324/webscour/issues
9
+ Keywords: cli,search,web,scraper,pdf,searxng,crawler
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Dynamic: license-file
24
+
25
+ <p align="center">
26
+ <img src="assets/logo.svg" alt="webscour" width="460">
27
+ </p>
28
+
29
+ <p align="center">
30
+ <b>webscour</b> is one keyless CLI for the web: search, page text, PDFs, sitemaps and a polite crawl.<br>
31
+ No API key. No account. No log.
32
+ </p>
33
+
34
+ <p align="center">
35
+ <a href="https://pypi.org/project/webscour/"><img src="https://img.shields.io/pypi/v/webscour?color=black" alt="PyPI"></a>
36
+ <a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-black" alt="MIT"></a>
37
+ <img src="https://img.shields.io/badge/python-3.11%2B-black" alt="Python 3.11+">
38
+ <img src="https://img.shields.io/badge/platform-linux-black" alt="Linux">
39
+ <img src="https://img.shields.io/badge/dependencies-none-black" alt="No dependencies">
40
+ </p>
41
+
42
+ `wsc` (the command webscour installs) asks a local SearXNG first, falls back to scrapers and
43
+ structured APIs, and prints text sized for a terminal instead of raw HTML. The standard library is the
44
+ whole stack; the only system binaries are the ones you choose to install for rendering and documents.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pipx install webscour # recommended: isolated, on PATH as `wsc`
50
+ uvx webscour search "pyproject vs setup.py" # no install at all
51
+ uv tool install webscour
52
+ pip install --user webscour
53
+ ```
54
+
55
+ Or, on Linux, the installer that also sets up the search backend:
56
+
57
+ ```bash
58
+ curl -LsSf https://wsc.ashina.live/install.sh | sh
59
+ wsc setup # installs SearXNG as a systemd user unit
60
+ wsc doctor # one line: OK, DEGRADED or DOWN
61
+ ```
62
+
63
+ ### Install with an agent
64
+
65
+ Paste this into Claude Code, pi, dsh, or any agent that can run a shell:
66
+
67
+ ```text
68
+ Install webscour, a keyless web CLI. Run:
69
+ curl -LsSf https://wsc.ashina.live/install.sh | sh
70
+ Then run `wsc doctor`. A good report says lightpanda present and the SearXNG unit active, with
71
+ one VERDICT line ending in OK. If the installer is unavailable, fall back in this order:
72
+ `uv tool install webscour`, then `pipx install webscour`, then `python3 -m pip install --user
73
+ webscour` (skip the last one on Debian or Ubuntu if it refuses with externally-managed-environment).
74
+
75
+ Ask the user before running `wsc setup`, because it installs a systemd user unit on their machine.
76
+ Use exactly one install method. The skill file, if your agent supports skills, is at
77
+ https://raw.githubusercontent.com/FurBlood344324/webscour/main/skill/SKILL.md
78
+ ```
79
+
80
+ From a checkout, the same thing without a build step:
81
+
82
+ ```bash
83
+ cd webscour
84
+ python3 -m webscour.cli search "pyproject vs setup.py"
85
+ ```
86
+
87
+ **System requirements: python 3.11+, lightpanda, linux.** No Python dependencies are installed, by design:
88
+ `dependencies = []` in `pyproject.toml` is a decision, not an omission. Everything else is a system binary,
89
+ which pip cannot manage and should not pretend to.
90
+
91
+ - **lightpanda** is required. It is the render engine, and `wsc doctor` fails when it is missing.
92
+ - Linux only because `wsc setup` installs a systemd **user** unit for SearXNG. The crawler itself runs on
93
+ macOS, and lightpanda ships for it too; the limit is the service, not the scraper.
94
+ - Optional: `pdftotext` (poppler) for PDF, `pandoc` for docx/epub, `markitdown` as a last resort, and
95
+ playwright as a render fallback.
96
+
97
+ Install lightpanda from one of these, in the order that usually applies:
98
+
99
+ ```bash
100
+ yay -S lightpanda-bin # Arch / AUR; nightly variant: lightpanda-nightly-bin
101
+ curl -fsSL https://pkg.lightpanda.io/install.sh | bash -s "<version>" # any Linux; needs curl, jq, sha256sum
102
+ # Debian/Ubuntu: the .deb published with each tagged release
103
+ brew install lightpanda-io/browser/lightpanda # Homebrew
104
+ ```
105
+
106
+ The one-liner installs to `~/.local/bin/lightpanda`; `LIGHTPANDA_DIR` overrides the directory. `wsc setup`
107
+ verifies lightpanda on PATH and never installs it, because a package install that downloads and runs a
108
+ binary is something a user has to audit rather than trust.
109
+
110
+ ### What is required, and what is optional
111
+
112
+ | piece | needed for | installed by |
113
+ |---|---|---|
114
+ | python 3.11+ | everything | you |
115
+ | SearXNG | `search --source web` | `wsc setup` (SearXNG only) |
116
+ | lightpanda | `--render`, and the retry of a hollow page | you, from the list above |
117
+ | playwright | the render fallback, only for a page lightpanda defeats | `wsc setup --playwright` |
118
+ | pdftotext / pandoc / markitdown | `wsc pdf`, and docx/epub | you, optional |
119
+
120
+ playwright is the one Python package this project can use, and the tool installs it into a directory
121
+ it owns rather than into your environment:
122
+
123
+ ```bash
124
+ wsc setup --playwright # ~/.local/share/webscour/render-venv, then chromium
125
+ wsc setup --playwright --with-deps # also let playwright install chromium's OS libraries (needs root)
126
+ ```
127
+
128
+ `pip install webscour` never does this: setup is a command you run, not a post-install hook. The lookup
129
+ order for the fallback is `WEBSCOUR_PLAYWRIGHT_PYTHON`, that venv, the running interpreter, then
130
+ `python3`; it never searches your environment manager. `wsc doctor` reports the venv, its health, and
131
+ whether its chromium build still matches the installed playwright package. A missing playwright is a
132
+ note. A missing lightpanda is a failure.
133
+
134
+ `wsc setup` alone still means "install SearXNG"; `--all` does both installs in one run.
135
+
136
+ ### Agent skill
137
+
138
+ The repo is also a pi package and a Claude Code marketplace.
139
+
140
+ ```bash
141
+ pi install git:github.com/FurBlood344324/webscour # adds skill/SKILL.md as a skill
142
+ ```
143
+
144
+ `package.json` at the repo root declares the skill, because a git source is cloned whole and pi has no
145
+ subdirectory syntax; `pi/package.json` declares the same skill when `pi/` is the source instead.
146
+
147
+ Claude Code, one step (2.1.275 or later):
148
+
149
+ ```bash
150
+ /plugin install webscour --marketplace FurBlood344324/webscour
151
+ ```
152
+
153
+ `.claude-plugin/marketplace.json` lists one plugin whose `source` is `./skill`, with `skills: ["."]` because
154
+ that directory is the skill itself rather than a folder of `<name>/SKILL.md` subdirectories.
155
+
156
+ ## First run
157
+
158
+ ```bash
159
+ wsc setup # installs the local SearXNG unit, then checks lightpanda
160
+ wsc doctor
161
+ ```
162
+
163
+ ```
164
+ unit active (enabled at boot)
165
+ listen 127.0.0.1:8888
166
+ engines 10 loaded
167
+ blocked abcnyheter, brave, quark, yacy
168
+ answered bing, mwmbl, naver, rawweb, seznam, yacy
169
+ silent abcnyheter, brave, quark, wikibooks
170
+ backends lightpanda=ok playwright=ok pdftotext=ok pandoc=ok markitdown=ok
171
+ VERDICT OK (6/10 engines answering, 4 blocked)
172
+ ```
173
+
174
+ `wsc setup` is idempotent: a venv under `~/.local/share/searxng`, settings under `~/.config/searxng`, one
175
+ systemd user unit on loopback, no docker and no sudo. `--check` reports state without touching anything,
176
+ `--force` rebuilds the venv, `--no-start` installs without starting.
177
+
178
+ Then:
179
+
180
+ ```bash
181
+ wsc search "python packaging pyproject" --limit 3
182
+ ```
183
+
184
+ ```
185
+ 1. Packaging Python Projects - Python Packaging User Guide - https://packaging.python.org/en/latest/tutorials/packaging-projects/ [naver,seznam]
186
+ This tutorial walks you through how to package a simple Python project. It will show you how to add the necessary files and structure to create the package, how to build the package, and how to uploa…
187
+ 2. PEP 609 – Python Packaging Authority (PyPA) Governance | peps.py… - https://www.python.org/dev/peps/pep-0609 [mwmbl]
188
+ This document describes a governance model for the Python Packaging Authority (PyPA). The model is closely based on existing informal practices and the …
189
+ ```
190
+
191
+ Each row names the engines that produced it, so a result from one weak upstream is visible as such.
192
+ `--json` prints the same rows as objects. Exit codes: 0 answered, 2 nothing answered or a bad verb.
193
+
194
+ ## Verbs
195
+
196
+ | Verb | What it does |
197
+ |---|---|
198
+ | `wsc search "<q>"` | one query, at most 30 rows, engine tags, optional `--json`, `--source` selects the backend |
199
+ | `wsc fetch <url>` | a page as text; `--max-chars`, `--grep` (context lines around a match), `--render`, `--render-engine`, `-v` |
200
+ | `wsc pdf <url\|path>` | a paper or manual as text, through pdftotext, pandoc or markitdown; `-v` names the backend |
201
+ | `wsc map <url>` | the URL list from sitemap, robots, llms.txt; `--limit` (default 200); `-v` names the source |
202
+ | `wsc crawl <url>` | links from that seed, same origin by default, `--depth`, `--limit`, `--any-origin`, `-v` |
203
+ | `wsc setup` | installs SearXNG and verifies lightpanda; `--playwright` installs the render fallback into its own venv, `--all` does both, `--check` reports without changing anything |
204
+ | `wsc doctor` | unit, listener, engine health, render backends, the render venv, one `VERDICT OK\|DEGRADED\|DOWN` line, `--json` |
205
+ | `wsc probe` | measures which engines this IP can use; `--quality --write` stores the survivors |
206
+ | `wsc cache list\|get\|prune\|path` | the on-disk captures under `./.webscour/`; `--save` writes one, `--fresh` bypasses a hit |
207
+ | `wsc mcp` | the same tool over stdio as a JSON-RPC MCP server, one tool named `web_scour` |
208
+ | `wsc tool-argv --json '<params>'` | the argv builder the MCP server and the pi extension both call, so neither owns a flag string |
209
+ | `wsc --self-test` | 29 offline checks of the parsers, the extraction rules, the router and the shell heuristic |
210
+
211
+ Output is the answer. By default stderr carries failures only, so `wsc fetch url > page.txt` leaves it
212
+ empty; `-v` explains every choice. When a page really is a JavaScript shell, plain HTTP comes back hollow
213
+ and one render is attempted; that fallback prints exactly one line, `render: lightpanda` or
214
+ `render: playwright`. A short page with real text is complete at any length, so `example.com` costs no
215
+ browser launch.
216
+
217
+ Two more stderr lines a caller can rely on: `route: <name>` when a known site is read through its own API
218
+ rather than scraped, and `cache: hit (2h ago)` when a capture is replayed instead of fetched again.
219
+
220
+ Exit codes: `0` the question was answered, `1` a usable answer was not produced and the reason is on
221
+ stderr (a 404, a block, an empty document), `2` the command line was wrong. A non-zero exit is a real
222
+ failure, never an empty result.
223
+
224
+ ## Sources
225
+
226
+ | `--source` | Service | Notes |
227
+ |---|---|---|
228
+ | `web` (default) | local SearXNG, then DuckDuckGo lite, DuckDuckGo html, Bing | each row tagged `[naver,bing]`; a fallback prints one `backend <name>: <reason>` line |
229
+ | `github` | api.github.com repositories and issues | unauthenticated, 10 requests/min, remaining budget printed |
230
+ | `so` | Stack Exchange API, `site=stackoverflow` | answered/open, tags |
231
+ | `hn` | hn.algolia.com | story points, comment counts |
232
+ | `arxiv` | export.arxiv.org Atom | title, id URL, abstract |
233
+ | `discourse:<base>` | `<base>/search.json` | e.g. `discourse:https://forums.developer.nvidia.com` |
234
+
235
+ A `fetch` on a forum topic URL (`forums.developer.nvidia.com/t/...`) reads the topic's `.json` form and
236
+ prints `#post author` with plain text, which is far cleaner than the HTML and needs no render.
237
+
238
+ ## Privacy
239
+
240
+ Only the query leaves the machine, through the local SearXNG to the upstream engines. No cwd, no host, no
241
+ user, no project name, no identifiers, no telemetry, and no log file: the tool writes nothing except its
242
+ one-second pacing stamp in `~/.cache/web/last-live`.
243
+
244
+ The SearXNG unit drops the query from its own logs. Access log off, level WARNING, autocomplete off (no
245
+ keystroke leaves the box), and the bundled `serve.py` filters every log record so that any URL query
246
+ string becomes `?<query withheld>`. The journal keeps engine errors and none of the terms. That is a
247
+ feature, and the reason the unit runs a wrapper instead of stock SearXNG.
248
+
249
+ This is not anonymity. Your IP talks to whichever upstream engines are enabled; a local proxy does not
250
+ change that. Send generic technical terms and keep private context out of a query.
251
+
252
+ `map` and `crawl` send only a URL to the target site, never a query, and every live request is paced to one
253
+ per second across processes, so a shell loop cannot look like a scraper.
254
+
255
+ ## How it differs from firecrawl, and from an MCP server
256
+
257
+ firecrawl is a hosted pipeline for turning sites into clean markdown at scale: crawl jobs, JS rendering
258
+ farms, structured extraction, and an API key. webscour is the opposite shape, a local tool for one page or
259
+ one query, with no account, no credits and no state, and it tells you which engine or backend answered
260
+ instead of hiding the plumbing. An MCP server is a transport: it puts the same kind of tool behind a
261
+ protocol so a chat client can call it, and then something still has to be installed, run and authenticated.
262
+ `wsc` is a plain executable that an agent, a shell script or a human calls the same way, which is why it has
263
+ no server, no registered tool, and no process to keep alive. If you already run an MCP server for this, the
264
+ only thing webscour adds is the local search backend and the pacing.
265
+
266
+ ## Limitations
267
+
268
+ - **Linux only**, because of the systemd user unit. Without the unit the scrapers answer from a residential
269
+ IP, and mostly they answer CAPTCHA.
270
+ - **The scrapers degrade on residential IPs.** Measured on 2026-11-13 with 30 generic queries, per engine;
271
+ a meta-search needs coverage (how often an engine helps) and precision (share of its rows that are on
272
+ topic), kept means coverage >= 10 % and precision >= 60 %:
273
+
274
+ | engine | cover | prec | rows | | engine | cover | prec | rows |
275
+ |---|---|---|---|---|---|---|---|---|
276
+ | naver | 90.0 % | 80.1 % | 403 | | wikibooks | 20.0 % | 93.8 % | 16 |
277
+ | abcnyheter | 70.0 % | 95.3 % | 214 | | wiby | 13.3 % | 42.9 % | 21 (dropped) |
278
+ | rawweb | 53.3 % | 98.8 % | 82 | | wikiversity, wikiquote | <=6.7 % | 100 % | 1-2 (dropped) |
279
+ | bing | 36.7 % | 60.0 % | 180 | | encyclosearch, wikisource | 3.3 % | 50 % | 2-14 (dropped) |
280
+ | yacy | 30.0 % | 96.7 % | 90 | | brave | 0 % this pass | n/a | kept by name |
281
+
282
+ duckduckgo, google, mojeek, qwant, startpage, searchmysite and yandex answered CAPTCHA, "access denied"
283
+ or a parsing error from a residential IP and are not in the set. Median merged query 5.0 s. Kept:
284
+ naver, abcnyheter, rawweb, bing, yacy, wikibooks, brave. `wsc probe --quality --write` repeats the
285
+ measurement on your own IP; the table above is one machine on one day, not a promise.
286
+ - **No render backend is installed by pip.** lightpanda is the one to install, and playwright is the
287
+ fallback for a page that defeats it. `wsc doctor` says which is present.
288
+ - **Scanned PDFs have no text layer.** `wsc pdf` says so and stops; OCR is out of scope.
289
+ - **Snippets are quotes from a scraped page.** Open the URL before relying on a claim, and treat a row whose
290
+ title and url lack every query term as drift, which the tool drops.
291
+ - **GitHub is unauthenticated**: 10 requests/min per IP, and `--source github` makes two calls.
292
+
293
+ ## Development
294
+
295
+ Everything a release needs runs on this machine, offline:
296
+
297
+ ```bash
298
+ python3 -m venv .venv-dev && .venv-dev/bin/pip install ruff build twine
299
+ ./scripts/check.sh # lint, format, tests, --self-test, build, twine, guards
300
+ BENCH=1 ./scripts/check.sh # also the extraction benchmark (needs the network)
301
+ ./scripts/release.sh 0.2.0 # local: gate, stamp, changelog, tag. Never pushes or publishes
302
+ ```
303
+
304
+ `scripts/check.sh` is the only definition of "releasable": it prints one `PASS <name>` line per
305
+ check and a summary, and the workflows in `.github/workflows/` are thin wrappers that call it. It
306
+ runs with no network, no SearXNG and `LIGHTPANDA=0`.
307
+
308
+ The extraction benchmark compares our reading text with trafilatura's on six pages, from captured
309
+ fixtures in `tests/fixtures/bench/`, and fails when a page regresses past `tests/baselines/`. The
310
+ reference extractor is a throwaway venv, never a dependency:
311
+
312
+ ```bash
313
+ python3 -m venv /tmp/tfvenv && /tmp/tfvenv/bin/pip install trafilatura
314
+ python3 tests/bench_extract.py # table, and a check against the baseline
315
+ python3 tests/bench_extract.py --live # re-fetch the pages first
316
+ python3 tests/bench_extract.py --save-baseline tests/baselines/0.2.0.json
317
+ ```
318
+
319
+ Design decisions live in `docs/decisions/`, one short record each, with the evidence and the
320
+ condition that would reverse it.
321
+
322
+ ## License
323
+
324
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,300 @@
1
+ <p align="center">
2
+ <img src="assets/logo.svg" alt="webscour" width="460">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <b>webscour</b> is one keyless CLI for the web: search, page text, PDFs, sitemaps and a polite crawl.<br>
7
+ No API key. No account. No log.
8
+ </p>
9
+
10
+ <p align="center">
11
+ <a href="https://pypi.org/project/webscour/"><img src="https://img.shields.io/pypi/v/webscour?color=black" alt="PyPI"></a>
12
+ <a href="LICENSE"><img src="https://img.shields.io/badge/licence-MIT-black" alt="MIT"></a>
13
+ <img src="https://img.shields.io/badge/python-3.11%2B-black" alt="Python 3.11+">
14
+ <img src="https://img.shields.io/badge/platform-linux-black" alt="Linux">
15
+ <img src="https://img.shields.io/badge/dependencies-none-black" alt="No dependencies">
16
+ </p>
17
+
18
+ `wsc` (the command webscour installs) asks a local SearXNG first, falls back to scrapers and
19
+ structured APIs, and prints text sized for a terminal instead of raw HTML. The standard library is the
20
+ whole stack; the only system binaries are the ones you choose to install for rendering and documents.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ pipx install webscour # recommended: isolated, on PATH as `wsc`
26
+ uvx webscour search "pyproject vs setup.py" # no install at all
27
+ uv tool install webscour
28
+ pip install --user webscour
29
+ ```
30
+
31
+ Or, on Linux, the installer that also sets up the search backend:
32
+
33
+ ```bash
34
+ curl -LsSf https://wsc.ashina.live/install.sh | sh
35
+ wsc setup # installs SearXNG as a systemd user unit
36
+ wsc doctor # one line: OK, DEGRADED or DOWN
37
+ ```
38
+
39
+ ### Install with an agent
40
+
41
+ Paste this into Claude Code, pi, dsh, or any agent that can run a shell:
42
+
43
+ ```text
44
+ Install webscour, a keyless web CLI. Run:
45
+ curl -LsSf https://wsc.ashina.live/install.sh | sh
46
+ Then run `wsc doctor`. A good report says lightpanda present and the SearXNG unit active, with
47
+ one VERDICT line ending in OK. If the installer is unavailable, fall back in this order:
48
+ `uv tool install webscour`, then `pipx install webscour`, then `python3 -m pip install --user
49
+ webscour` (skip the last one on Debian or Ubuntu if it refuses with externally-managed-environment).
50
+
51
+ Ask the user before running `wsc setup`, because it installs a systemd user unit on their machine.
52
+ Use exactly one install method. The skill file, if your agent supports skills, is at
53
+ https://raw.githubusercontent.com/FurBlood344324/webscour/main/skill/SKILL.md
54
+ ```
55
+
56
+ From a checkout, the same thing without a build step:
57
+
58
+ ```bash
59
+ cd webscour
60
+ python3 -m webscour.cli search "pyproject vs setup.py"
61
+ ```
62
+
63
+ **System requirements: python 3.11+, lightpanda, linux.** No Python dependencies are installed, by design:
64
+ `dependencies = []` in `pyproject.toml` is a decision, not an omission. Everything else is a system binary,
65
+ which pip cannot manage and should not pretend to.
66
+
67
+ - **lightpanda** is required. It is the render engine, and `wsc doctor` fails when it is missing.
68
+ - Linux only because `wsc setup` installs a systemd **user** unit for SearXNG. The crawler itself runs on
69
+ macOS, and lightpanda ships for it too; the limit is the service, not the scraper.
70
+ - Optional: `pdftotext` (poppler) for PDF, `pandoc` for docx/epub, `markitdown` as a last resort, and
71
+ playwright as a render fallback.
72
+
73
+ Install lightpanda from one of these, in the order that usually applies:
74
+
75
+ ```bash
76
+ yay -S lightpanda-bin # Arch / AUR; nightly variant: lightpanda-nightly-bin
77
+ curl -fsSL https://pkg.lightpanda.io/install.sh | bash -s "<version>" # any Linux; needs curl, jq, sha256sum
78
+ # Debian/Ubuntu: the .deb published with each tagged release
79
+ brew install lightpanda-io/browser/lightpanda # Homebrew
80
+ ```
81
+
82
+ The one-liner installs to `~/.local/bin/lightpanda`; `LIGHTPANDA_DIR` overrides the directory. `wsc setup`
83
+ verifies lightpanda on PATH and never installs it, because a package install that downloads and runs a
84
+ binary is something a user has to audit rather than trust.
85
+
86
+ ### What is required, and what is optional
87
+
88
+ | piece | needed for | installed by |
89
+ |---|---|---|
90
+ | python 3.11+ | everything | you |
91
+ | SearXNG | `search --source web` | `wsc setup` (SearXNG only) |
92
+ | lightpanda | `--render`, and the retry of a hollow page | you, from the list above |
93
+ | playwright | the render fallback, only for a page lightpanda defeats | `wsc setup --playwright` |
94
+ | pdftotext / pandoc / markitdown | `wsc pdf`, and docx/epub | you, optional |
95
+
96
+ playwright is the one Python package this project can use, and the tool installs it into a directory
97
+ it owns rather than into your environment:
98
+
99
+ ```bash
100
+ wsc setup --playwright # ~/.local/share/webscour/render-venv, then chromium
101
+ wsc setup --playwright --with-deps # also let playwright install chromium's OS libraries (needs root)
102
+ ```
103
+
104
+ `pip install webscour` never does this: setup is a command you run, not a post-install hook. The lookup
105
+ order for the fallback is `WEBSCOUR_PLAYWRIGHT_PYTHON`, that venv, the running interpreter, then
106
+ `python3`; it never searches your environment manager. `wsc doctor` reports the venv, its health, and
107
+ whether its chromium build still matches the installed playwright package. A missing playwright is a
108
+ note. A missing lightpanda is a failure.
109
+
110
+ `wsc setup` alone still means "install SearXNG"; `--all` does both installs in one run.
111
+
112
+ ### Agent skill
113
+
114
+ The repo is also a pi package and a Claude Code marketplace.
115
+
116
+ ```bash
117
+ pi install git:github.com/FurBlood344324/webscour # adds skill/SKILL.md as a skill
118
+ ```
119
+
120
+ `package.json` at the repo root declares the skill, because a git source is cloned whole and pi has no
121
+ subdirectory syntax; `pi/package.json` declares the same skill when `pi/` is the source instead.
122
+
123
+ Claude Code, one step (2.1.275 or later):
124
+
125
+ ```bash
126
+ /plugin install webscour --marketplace FurBlood344324/webscour
127
+ ```
128
+
129
+ `.claude-plugin/marketplace.json` lists one plugin whose `source` is `./skill`, with `skills: ["."]` because
130
+ that directory is the skill itself rather than a folder of `<name>/SKILL.md` subdirectories.
131
+
132
+ ## First run
133
+
134
+ ```bash
135
+ wsc setup # installs the local SearXNG unit, then checks lightpanda
136
+ wsc doctor
137
+ ```
138
+
139
+ ```
140
+ unit active (enabled at boot)
141
+ listen 127.0.0.1:8888
142
+ engines 10 loaded
143
+ blocked abcnyheter, brave, quark, yacy
144
+ answered bing, mwmbl, naver, rawweb, seznam, yacy
145
+ silent abcnyheter, brave, quark, wikibooks
146
+ backends lightpanda=ok playwright=ok pdftotext=ok pandoc=ok markitdown=ok
147
+ VERDICT OK (6/10 engines answering, 4 blocked)
148
+ ```
149
+
150
+ `wsc setup` is idempotent: a venv under `~/.local/share/searxng`, settings under `~/.config/searxng`, one
151
+ systemd user unit on loopback, no docker and no sudo. `--check` reports state without touching anything,
152
+ `--force` rebuilds the venv, `--no-start` installs without starting.
153
+
154
+ Then:
155
+
156
+ ```bash
157
+ wsc search "python packaging pyproject" --limit 3
158
+ ```
159
+
160
+ ```
161
+ 1. Packaging Python Projects - Python Packaging User Guide - https://packaging.python.org/en/latest/tutorials/packaging-projects/ [naver,seznam]
162
+ This tutorial walks you through how to package a simple Python project. It will show you how to add the necessary files and structure to create the package, how to build the package, and how to uploa…
163
+ 2. PEP 609 – Python Packaging Authority (PyPA) Governance | peps.py… - https://www.python.org/dev/peps/pep-0609 [mwmbl]
164
+ This document describes a governance model for the Python Packaging Authority (PyPA). The model is closely based on existing informal practices and the …
165
+ ```
166
+
167
+ Each row names the engines that produced it, so a result from one weak upstream is visible as such.
168
+ `--json` prints the same rows as objects. Exit codes: 0 answered, 2 nothing answered or a bad verb.
169
+
170
+ ## Verbs
171
+
172
+ | Verb | What it does |
173
+ |---|---|
174
+ | `wsc search "<q>"` | one query, at most 30 rows, engine tags, optional `--json`, `--source` selects the backend |
175
+ | `wsc fetch <url>` | a page as text; `--max-chars`, `--grep` (context lines around a match), `--render`, `--render-engine`, `-v` |
176
+ | `wsc pdf <url\|path>` | a paper or manual as text, through pdftotext, pandoc or markitdown; `-v` names the backend |
177
+ | `wsc map <url>` | the URL list from sitemap, robots, llms.txt; `--limit` (default 200); `-v` names the source |
178
+ | `wsc crawl <url>` | links from that seed, same origin by default, `--depth`, `--limit`, `--any-origin`, `-v` |
179
+ | `wsc setup` | installs SearXNG and verifies lightpanda; `--playwright` installs the render fallback into its own venv, `--all` does both, `--check` reports without changing anything |
180
+ | `wsc doctor` | unit, listener, engine health, render backends, the render venv, one `VERDICT OK\|DEGRADED\|DOWN` line, `--json` |
181
+ | `wsc probe` | measures which engines this IP can use; `--quality --write` stores the survivors |
182
+ | `wsc cache list\|get\|prune\|path` | the on-disk captures under `./.webscour/`; `--save` writes one, `--fresh` bypasses a hit |
183
+ | `wsc mcp` | the same tool over stdio as a JSON-RPC MCP server, one tool named `web_scour` |
184
+ | `wsc tool-argv --json '<params>'` | the argv builder the MCP server and the pi extension both call, so neither owns a flag string |
185
+ | `wsc --self-test` | 29 offline checks of the parsers, the extraction rules, the router and the shell heuristic |
186
+
187
+ Output is the answer. By default stderr carries failures only, so `wsc fetch url > page.txt` leaves it
188
+ empty; `-v` explains every choice. When a page really is a JavaScript shell, plain HTTP comes back hollow
189
+ and one render is attempted; that fallback prints exactly one line, `render: lightpanda` or
190
+ `render: playwright`. A short page with real text is complete at any length, so `example.com` costs no
191
+ browser launch.
192
+
193
+ Two more stderr lines a caller can rely on: `route: <name>` when a known site is read through its own API
194
+ rather than scraped, and `cache: hit (2h ago)` when a capture is replayed instead of fetched again.
195
+
196
+ Exit codes: `0` the question was answered, `1` a usable answer was not produced and the reason is on
197
+ stderr (a 404, a block, an empty document), `2` the command line was wrong. A non-zero exit is a real
198
+ failure, never an empty result.
199
+
200
+ ## Sources
201
+
202
+ | `--source` | Service | Notes |
203
+ |---|---|---|
204
+ | `web` (default) | local SearXNG, then DuckDuckGo lite, DuckDuckGo html, Bing | each row tagged `[naver,bing]`; a fallback prints one `backend <name>: <reason>` line |
205
+ | `github` | api.github.com repositories and issues | unauthenticated, 10 requests/min, remaining budget printed |
206
+ | `so` | Stack Exchange API, `site=stackoverflow` | answered/open, tags |
207
+ | `hn` | hn.algolia.com | story points, comment counts |
208
+ | `arxiv` | export.arxiv.org Atom | title, id URL, abstract |
209
+ | `discourse:<base>` | `<base>/search.json` | e.g. `discourse:https://forums.developer.nvidia.com` |
210
+
211
+ A `fetch` on a forum topic URL (`forums.developer.nvidia.com/t/...`) reads the topic's `.json` form and
212
+ prints `#post author` with plain text, which is far cleaner than the HTML and needs no render.
213
+
214
+ ## Privacy
215
+
216
+ Only the query leaves the machine, through the local SearXNG to the upstream engines. No cwd, no host, no
217
+ user, no project name, no identifiers, no telemetry, and no log file: the tool writes nothing except its
218
+ one-second pacing stamp in `~/.cache/web/last-live`.
219
+
220
+ The SearXNG unit drops the query from its own logs. Access log off, level WARNING, autocomplete off (no
221
+ keystroke leaves the box), and the bundled `serve.py` filters every log record so that any URL query
222
+ string becomes `?<query withheld>`. The journal keeps engine errors and none of the terms. That is a
223
+ feature, and the reason the unit runs a wrapper instead of stock SearXNG.
224
+
225
+ This is not anonymity. Your IP talks to whichever upstream engines are enabled; a local proxy does not
226
+ change that. Send generic technical terms and keep private context out of a query.
227
+
228
+ `map` and `crawl` send only a URL to the target site, never a query, and every live request is paced to one
229
+ per second across processes, so a shell loop cannot look like a scraper.
230
+
231
+ ## How it differs from firecrawl, and from an MCP server
232
+
233
+ firecrawl is a hosted pipeline for turning sites into clean markdown at scale: crawl jobs, JS rendering
234
+ farms, structured extraction, and an API key. webscour is the opposite shape, a local tool for one page or
235
+ one query, with no account, no credits and no state, and it tells you which engine or backend answered
236
+ instead of hiding the plumbing. An MCP server is a transport: it puts the same kind of tool behind a
237
+ protocol so a chat client can call it, and then something still has to be installed, run and authenticated.
238
+ `wsc` is a plain executable that an agent, a shell script or a human calls the same way, which is why it has
239
+ no server, no registered tool, and no process to keep alive. If you already run an MCP server for this, the
240
+ only thing webscour adds is the local search backend and the pacing.
241
+
242
+ ## Limitations
243
+
244
+ - **Linux only**, because of the systemd user unit. Without the unit the scrapers answer from a residential
245
+ IP, and mostly they answer CAPTCHA.
246
+ - **The scrapers degrade on residential IPs.** Measured on 2026-11-13 with 30 generic queries, per engine;
247
+ a meta-search needs coverage (how often an engine helps) and precision (share of its rows that are on
248
+ topic), kept means coverage >= 10 % and precision >= 60 %:
249
+
250
+ | engine | cover | prec | rows | | engine | cover | prec | rows |
251
+ |---|---|---|---|---|---|---|---|---|
252
+ | naver | 90.0 % | 80.1 % | 403 | | wikibooks | 20.0 % | 93.8 % | 16 |
253
+ | abcnyheter | 70.0 % | 95.3 % | 214 | | wiby | 13.3 % | 42.9 % | 21 (dropped) |
254
+ | rawweb | 53.3 % | 98.8 % | 82 | | wikiversity, wikiquote | <=6.7 % | 100 % | 1-2 (dropped) |
255
+ | bing | 36.7 % | 60.0 % | 180 | | encyclosearch, wikisource | 3.3 % | 50 % | 2-14 (dropped) |
256
+ | yacy | 30.0 % | 96.7 % | 90 | | brave | 0 % this pass | n/a | kept by name |
257
+
258
+ duckduckgo, google, mojeek, qwant, startpage, searchmysite and yandex answered CAPTCHA, "access denied"
259
+ or a parsing error from a residential IP and are not in the set. Median merged query 5.0 s. Kept:
260
+ naver, abcnyheter, rawweb, bing, yacy, wikibooks, brave. `wsc probe --quality --write` repeats the
261
+ measurement on your own IP; the table above is one machine on one day, not a promise.
262
+ - **No render backend is installed by pip.** lightpanda is the one to install, and playwright is the
263
+ fallback for a page that defeats it. `wsc doctor` says which is present.
264
+ - **Scanned PDFs have no text layer.** `wsc pdf` says so and stops; OCR is out of scope.
265
+ - **Snippets are quotes from a scraped page.** Open the URL before relying on a claim, and treat a row whose
266
+ title and url lack every query term as drift, which the tool drops.
267
+ - **GitHub is unauthenticated**: 10 requests/min per IP, and `--source github` makes two calls.
268
+
269
+ ## Development
270
+
271
+ Everything a release needs runs on this machine, offline:
272
+
273
+ ```bash
274
+ python3 -m venv .venv-dev && .venv-dev/bin/pip install ruff build twine
275
+ ./scripts/check.sh # lint, format, tests, --self-test, build, twine, guards
276
+ BENCH=1 ./scripts/check.sh # also the extraction benchmark (needs the network)
277
+ ./scripts/release.sh 0.2.0 # local: gate, stamp, changelog, tag. Never pushes or publishes
278
+ ```
279
+
280
+ `scripts/check.sh` is the only definition of "releasable": it prints one `PASS <name>` line per
281
+ check and a summary, and the workflows in `.github/workflows/` are thin wrappers that call it. It
282
+ runs with no network, no SearXNG and `LIGHTPANDA=0`.
283
+
284
+ The extraction benchmark compares our reading text with trafilatura's on six pages, from captured
285
+ fixtures in `tests/fixtures/bench/`, and fails when a page regresses past `tests/baselines/`. The
286
+ reference extractor is a throwaway venv, never a dependency:
287
+
288
+ ```bash
289
+ python3 -m venv /tmp/tfvenv && /tmp/tfvenv/bin/pip install trafilatura
290
+ python3 tests/bench_extract.py # table, and a check against the baseline
291
+ python3 tests/bench_extract.py --live # re-fetch the pages first
292
+ python3 tests/bench_extract.py --save-baseline tests/baselines/0.2.0.json
293
+ ```
294
+
295
+ Design decisions live in `docs/decisions/`, one short record each, with the evidence and the
296
+ condition that would reverse it.
297
+
298
+ ## License
299
+
300
+ MIT. See [LICENSE](LICENSE).