kpihx-web 2.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,275 @@
1
+ Metadata-Version: 2.3
2
+ Name: kpihx-web
3
+ Version: 2.2.0
4
+ Summary: Private CLI for metasearch, verified retrieval, bounded crawling and media investigation
5
+ License: MIT
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Dist: beautifulsoup4
10
+ Requires-Dist: defusedxml>=0.7.1
11
+ Requires-Dist: fpdf2>=2.8.9
12
+ Requires-Dist: httpx[socks]
13
+ Requires-Dist: lxml
14
+ Requires-Dist: phonenumbers>=9.0.41
15
+ Requires-Dist: picimagesearch
16
+ Requires-Dist: pillow
17
+ Requires-Dist: pypdf
18
+ Requires-Dist: python-docx
19
+ Requires-Dist: regex>=2026.9.29
20
+ Requires-Dist: requests[socks]
21
+ Requires-Dist: rich
22
+ Requires-Dist: typer
23
+ Requires-Python: >=3.12
24
+ Project-URL: Repository, https://github.com/kpihx-labs/web
25
+ Project-URL: GitLab, https://gitlab.com/kpihx-labs/web
26
+ Project-URL: Issues, https://github.com/kpihx-labs/web/issues
27
+ Provides-Extra: dev
28
+ Description-Content-Type: text/markdown
29
+
30
+ # web (kpihx-web)
31
+
32
+ Local-first research CLI: discover indexed sources, retrieve content, verify
33
+ evidence and preserve provenance. Python distribution: kpihx-web; executable: web.
34
+ The previous implementation and documentation remain under references/ for audit.
35
+
36
+ ## Installation
37
+
38
+ Use `make install` from a clone, or `uv tool install kpihx-web` then `web setup`.
39
+ Run `make help` for dependency installation guidance, backend health/recovery,
40
+ testing and uninstall/purge targets. uv manages Python dependencies and uv_build
41
+ builds the package. No Hatch backend, paid recognition provider or transcription.
42
+
43
+ Clone either SSH remote, keeping the projects directly under your project root:
44
+
45
+ ```sh
46
+ git clone git@github.com:kpihx-labs/web.git
47
+ # Alternative: git clone git@gitlab.com:kpihx-labs/web.git
48
+ make -C web install
49
+ make -C web backend-clone HOST=github # or HOST=gitlab; retains an existing sibling
50
+ make -C web backend-up
51
+ web doctor --live
52
+ ```
53
+
54
+ ## Discover the CLI without guessing (2.2)
55
+
56
+ Every command level has a purpose/limits explanation, at least three varied
57
+ examples, and descriptions with examples for every argument and long/short flag.
58
+ The help is offline: it does not query providers, record audio, inspect files or
59
+ purge state. Explore the whole tree, including map's distinct API operations:
60
+
61
+ ```sh
62
+ web --help
63
+ web search --help
64
+ web doc --help
65
+ web map --help
66
+ web map search --help
67
+ web map query --help
68
+ web map route --help
69
+ web map request --help
70
+ ```
71
+
72
+ The recursive help contract and example parser tests fail if a new command ships
73
+ an undocumented option, missing short/long alias, or fewer than three examples.
74
+ Generated help remains English, like the code. Detailed shared descriptions live
75
+ in help_text.py, with explicit references at each Typer declaration.
76
+
77
+ ## Search precision, budgets and provenance
78
+
79
+ ```sh
80
+ web search '"Lado Saha" site:lig-membres.imag.fr -annuaire' -e 'google cse' -P 2 -n 30
81
+ web search '("memory scheduling" OR "unified memory") filetype:pdf after:2023' -P 3 -n 0 -f json
82
+ web search @query.txt -X '{"time_range":"year"}' -c science -l en -m 1
83
+ web search 'python logo' -c images -f json
84
+ web engines -r 'google|bing|duck'
85
+ ```
86
+
87
+ Quotes, Boolean operators, exclusions, site:, filetype:, intitle:, inurl: and date
88
+ operators pass through unchanged. Support depends on the selected engine; there
89
+ is no universal Google syntax translator. `-X/--params` accepts arbitrary
90
+ SearXNG JSON or @file, while preserving the query/JSON response contract.
91
+ Unknown engine names fail rather than silently querying defaults. `provider_fields`
92
+ retains raw result metadata, including image URLs and provider-specific fields.
93
+
94
+ `-n 30` is a best-effort collection target: fetch further pages until 30 unique
95
+ filtered candidates, source exhaustion/failure or the page-request budget. Both
96
+ `-p` and legacy `-P` now mean --max-pages (default 3); --start-page/-s is separate.
97
+ This corrects 2.0's confusing -p=start/-P=count distinction. `-n 0` collects within
98
+ the full explicit budget. Output reports actual requests, counts and stop_reason;
99
+ it never pads or invents results when the target cannot be reached.
100
+ Default relevance ranking orders term coverage then SearXNG's numeric aggregate
101
+ score. `-k provider` sorts by that score alone (not API array/arrival order);
102
+ `-k lexical` uses term coverage only. None establishes identity or semantic truth.
103
+ Regex/domain filtering
104
+ precedes the display limit. Regular expressions use a configurable regex_timeout
105
+ budget rather than unbounded backtracking. URL dedup preserves meaningful query/path case.
106
+ SafeSearch `-m 0/1/2` is requested upstream; off does not remove upstream censorship.
107
+
108
+ Repeated identical searches reuse a labelled private snapshot for 300 seconds,
109
+ independent of table/JSON format. --fresh/-F forces live collection and forbids
110
+ stale fallback. --no-save also disables query-cache persistence/reuse. On failure,
111
+ a default request may return a clearly marked stale snapshot up to 24 hours old,
112
+ with partial status and the error; it is never represented as a new live result.
113
+ TTL, max pages, page delay and fallback_engines are plain configurable SETTINGS.
114
+ If defaults stall and a page-request budget remains, keyless Yahoo/Yep/MWMBL are
115
+ queried once as an explicit alternative batch. No IP/account switching, CAPTCHA
116
+ bypass or rate-limit evasion occurs. Explicit -e choices are not silently replaced.
117
+
118
+ The table shows **Sources**, a full contributor list, and folds complete URLs
119
+ without ellipses. Engine coverage distinguishes contributions, CAPTCHA, rate
120
+ limits and no-results-reported; the latter is not proof an engine was queried or
121
+ is healthy. Provider refusal does not recommend restarting Docker/VPN. Search
122
+ reads index snippets; use fetch separately to inspect actual page content.
123
+
124
+ ```sh
125
+ web search 'KAMDEM Ivann' -n 30 -p 5
126
+ web show /tmp/web-results/SEARCH_SNAPSHOT.json -f table
127
+ web show /tmp/web-results/SEARCH_SNAPSHOT.json -f json
128
+ ```
129
+ `show` renders exactly the saved snapshot without any network request, allowing
130
+ an honest table/JSON comparison. Different live requests may receive different
131
+ provider responses, even when their query text is identical.
132
+
133
+ ## Fetch, inspection, sessions and exact downloads
134
+
135
+ ```sh
136
+ web fetch https://example.org -s 'article h2' -r 'privacy|research' -f json
137
+ web fetch https://example.org/document.pdf -d /tmp/document.pdf
138
+ web fetch https://authorized.example/private -C /private/cookies.txt -H @/private/headers.json
139
+ web crawl https://quotes.toscrape.com -D 3 -n 4 -d /tmp/quotes-pages
140
+ ```
141
+
142
+ Fetch downloads once, records MIME/byte SHA256 and extracts links from those same
143
+ bytes. Binary responses are not parsed as HTML. `--download` preserves exact
144
+ response bytes; existing files are refused. CSS and regex inspection use the
145
+ retrieved body, without extra requests. HTTP 200 login/registration/challenge
146
+ pages are classified as partial access, not useful source reads. These heuristics
147
+ can miss unusual walls; inspect access_state and content, not status alone.
148
+
149
+ Cookies accept Netscape including HttpOnly comments, browser JSON records or a
150
+ flat dictionary scoped to the seed host. Domain/path/secure/expiry restrictions
151
+ are honored. Custom headers are limited to the original origin across redirects;
152
+ header files avoid leaking tokens in shell history. Never send actual session
153
+ values to agents or paste them into reports. Requests ignore environment proxies;
154
+ `--proxy` explicitly selects HTTP/SOCKS. No TLS verification bypass is provided.
155
+
156
+ Crawl is same-host, paced (`--delay`), depth/page bounded and robots-aware by
157
+ default. `--no-robots` is explicit, not a bypass of authorization or terms.
158
+ Next/rel=next links are prioritized; JS scroll/click/API pagination is not executed.
159
+ `--match` restricts downloads, not traversal. Errors and remaining frontier are
160
+ retained in the manifest. New output directories avoid collisions on reruns.
161
+ Private/local destinations need `--allow-private` for your own services.
162
+ `--exclude` is a URL-frontier regex; its configurable default guards common
163
+ logout/delete/checkout GET links. An explicit empty expression disables it after
164
+ review. This heuristic is not proof GET is side-effect-free: use a dedicated
165
+ authorized session and inspect the frontier before crawling sensitive accounts.
166
+ DNS checks are best effort, not a security sandbox against hostile DNS rebinding.
167
+
168
+ For JS, authenticated DOM or browser challenges, use browser-proxy independently
169
+ with your authorization. A cookie handoff can help ordinary sessions but cannot
170
+ reproduce every browser/TLS fingerprint. This CLI never promises to bypass all
171
+ CAPTCHAs, paywalls, permissions or account login.
172
+
173
+ ## Documents, images and audio
174
+
175
+ ```sh
176
+ web doc report.pdf --compare other.pdf --ocr --pages 10 --report /tmp/evidence.pdf
177
+ web doc public-report.md --online --samples 3 --limit 5
178
+ web image photo.jpg # local metadata only
179
+ web image https://public.example/logo.png # remote input, still no reverse upload
180
+ web image public-logo.png --online -e yandex,google,bing --text-search
181
+ web audio excerpt.mp3
182
+ web audio --record 12 --mode hum
183
+ web archive https://public.example/report.pdf -P '{"timestamp":"20240101"}'
184
+ ```
185
+
186
+ Doc is offline by default: --online discloses sampled quotes to search engines.
187
+ PDF/DOCX tables/ODT/PPTX and UTF-8 TXT/MD/CSV/JSON/HTML/XML/RST/TeX are supported;
188
+ unknown binary must be exported, not decoded as garbage. Scanned PDFs require
189
+ explicit OCR and are rendered to temporary images before Tesseract.
190
+ These parsers are not a malware sandbox; use OS isolation for hostile files.
191
+ Page limit 0 selects all pages and can be expensive. Comparisons provide exact byte equality
192
+ and lexical matching spans, NOT semantic equivalence or a plagiarism verdict.
193
+ Online links are candidates; downloaded content is checked separately. A hash
194
+ search result alone is not proof a file leaked. Archive uses Wayback's closest
195
+ snapshot API only; no Archive.today API or exhaustive historical search is claimed.
196
+
197
+ Image uploads require --online and omit original EXIF/GPS. Providers are fragile
198
+ unofficial adapters: failures or empty responses are not proof of absent copies.
199
+ --text-search explicitly discloses local OCR words as a complementary text search;
200
+ those results are not verified visual matches. GPS/device/time are local metadata.
201
+
202
+ Audio uses free SongRec/Shazam; FFmpeg normalizes/records an excerpt. `hum` is a
203
+ qualified best-effort fingerprint attempt and returns partial status: SongRec is
204
+ not a melody recognizer and reliable free humming is not established. ACRCloud,
205
+ paid API logic and credential loading are removed. No transcription is performed.
206
+
207
+ ## Hidden networks and limits
208
+
209
+ Tor fetch uses SOCKS5h (managed optional profile: 127.0.0.1:19050; override
210
+ tor_proxy for an existing external daemon, commonly 9050); once selected, redirects do not
211
+ downgrade to direct or resolve through local DNS. I2P fetch requires an actual
212
+ HTTP proxy (default 127.0.0.1:4444). There is no fake I2P search adapter. For
213
+ indexed onion discovery use `web search QUERY -e ahmia -c onions`; coverage and
214
+ uptime depend on the index and Tor. Private/unindexed content has no universal
215
+ search engine. No tool can enumerate your complete digital footprint or prove
216
+ absence of a leak from absent results. No arbitrary third-party kill switch
217
+ compatibility or perfect anonymity is promised.
218
+
219
+ Surface SearXNG startup is independent of Tor. `make -C ../searxng onion-up`
220
+ explicitly starts an isolated optional onion worker; a failed Tor check does not
221
+ crash the surface worker. `web engines -z onion` inspects it. `web search QUERY
222
+ -e bing,ahmia` partitions those engines between backends and reports missing
223
+ coverage rather than losing working surface results. `-z surface,onion` explicitly
224
+ selects both, or use custom `SETTINGS['search_backends']` entries with url/socket
225
+ fields for another SearXNG-compatible backend. Budgets apply per backend, so total
226
+ requests grow with the number of selected backends. Protocols other than SearXNG
227
+ still need an adapter; arbitrary services are not interchangeable by naming them.
228
+
229
+ ## Configuration, dependencies and lifecycle
230
+
231
+ `web setup` creates ~/.config/web/config.py only if absent. SETTINGS is plain
232
+ trusted Python, not a loader framework. WEB_CONFIG_DIR and standard XDG roots
233
+ control configuration where supported. Existing xsearch configuration/cache were
234
+ moved, not destroyed; dormant old .env credentials are never loaded. Defaults in
235
+ config.py include response_max_bytes, local_max_bytes, xml_max_bytes and
236
+ max_redirects; put overrides in SETTINGS.
237
+ Set phone_region (for example FR) to validate national-format numbers; without
238
+ a region hint, international numbers are preferred rather than guessing dates.
239
+ Local SearXNG prefers its owner-only
240
+ Unix socket to avoid host VPN TCP routing; remote endpoints remain explicit.
241
+
242
+ Python libraries are resolved with bare uv add and uv.lock, never pip or manual
243
+ dependency arrays. System dependencies are deliberately separate/shared:
244
+
245
+ | Feature | Native dependency / installation guidance |
246
+ | --- | --- |
247
+ | Audio | ffmpeg; SongRec via Cargo: cargo install songrec --no-default-features --features ffmpeg |
248
+ | SongRec source build | Rust/Cargo, pkg-config, Clang/libclang, libsoup-3.0-dev, ALSA/PipeWire development headers as required upstream |
249
+ | PDF OCR | poppler-utils (pdftoppm), tesseract-ocr and desired language data |
250
+ | Unicode PDF report | fontconfig (fc-match), fonts-dejavu-core; fpdf2 is managed by uv |
251
+ | Backend | Docker Engine and Compose plugin; sibling searxng repository documents images/ports/volumes |
252
+ | Tor/I2P | Your own reachable Tor SOCKS5h/I2P HTTP proxy; never silently started |
253
+
254
+ Use upstream package instructions for your OS; dependency install commands evolve.
255
+ `make deps` reports installed native capabilities and guidance rather than a rigid
256
+ root installer. Cold builds need network or cached packages/images. Docker logs
257
+ are bounded by the backend; web has no background daemon or persistent log file.
258
+ JSON evidence is not silently rotated/deleted; /tmp is volatile. Explicit
259
+ download/report destinations are user evidence and not automatically purged.
260
+
261
+ `make uninstall` removes the uv tool only. `make purge` stops the sibling backend,
262
+ trashes owned runtime paths, uninstalls the tool, and retains source/shared native
263
+ tools. `make backend-purge CONFIRM=searxng` backs up named volumes under a private
264
+ /tmp directory (.bak files), then removes only that stack's volumes. Review its
265
+ Make recipe first. Never run global Docker prune. A clone is retained so reinstall
266
+ works from either GitHub or GitLab. `make purge-all CONFIRM=searxng` sequences
267
+ the confirmed backend backup/removal and app purge. Install from PyPI using
268
+ uv tool install kpihx-web.
269
+
270
+ All commands use an action/timestamp/status/data envelope. Formats: table, json,
271
+ jsonl, csv, md; --report creates escaped offline HTML or a real PDF. Autosaves are
272
+ unique 0600 JSON in /tmp/web-results; -S overrides and -N disables. Exit 2 is
273
+ failure, exit 3 is partial/access-wall coverage, exit 0 is completion. Inspect
274
+ non_exhaustive, budgets and source errors before drawing conclusions. Source
275
+ quality, identity attribution and correctness require human corroboration.
@@ -0,0 +1,246 @@
1
+ # web (kpihx-web)
2
+
3
+ Local-first research CLI: discover indexed sources, retrieve content, verify
4
+ evidence and preserve provenance. Python distribution: kpihx-web; executable: web.
5
+ The previous implementation and documentation remain under references/ for audit.
6
+
7
+ ## Installation
8
+
9
+ Use `make install` from a clone, or `uv tool install kpihx-web` then `web setup`.
10
+ Run `make help` for dependency installation guidance, backend health/recovery,
11
+ testing and uninstall/purge targets. uv manages Python dependencies and uv_build
12
+ builds the package. No Hatch backend, paid recognition provider or transcription.
13
+
14
+ Clone either SSH remote, keeping the projects directly under your project root:
15
+
16
+ ```sh
17
+ git clone git@github.com:kpihx-labs/web.git
18
+ # Alternative: git clone git@gitlab.com:kpihx-labs/web.git
19
+ make -C web install
20
+ make -C web backend-clone HOST=github # or HOST=gitlab; retains an existing sibling
21
+ make -C web backend-up
22
+ web doctor --live
23
+ ```
24
+
25
+ ## Discover the CLI without guessing (2.2)
26
+
27
+ Every command level has a purpose/limits explanation, at least three varied
28
+ examples, and descriptions with examples for every argument and long/short flag.
29
+ The help is offline: it does not query providers, record audio, inspect files or
30
+ purge state. Explore the whole tree, including map's distinct API operations:
31
+
32
+ ```sh
33
+ web --help
34
+ web search --help
35
+ web doc --help
36
+ web map --help
37
+ web map search --help
38
+ web map query --help
39
+ web map route --help
40
+ web map request --help
41
+ ```
42
+
43
+ The recursive help contract and example parser tests fail if a new command ships
44
+ an undocumented option, missing short/long alias, or fewer than three examples.
45
+ Generated help remains English, like the code. Detailed shared descriptions live
46
+ in help_text.py, with explicit references at each Typer declaration.
47
+
48
+ ## Search precision, budgets and provenance
49
+
50
+ ```sh
51
+ web search '"Lado Saha" site:lig-membres.imag.fr -annuaire' -e 'google cse' -P 2 -n 30
52
+ web search '("memory scheduling" OR "unified memory") filetype:pdf after:2023' -P 3 -n 0 -f json
53
+ web search @query.txt -X '{"time_range":"year"}' -c science -l en -m 1
54
+ web search 'python logo' -c images -f json
55
+ web engines -r 'google|bing|duck'
56
+ ```
57
+
58
+ Quotes, Boolean operators, exclusions, site:, filetype:, intitle:, inurl: and date
59
+ operators pass through unchanged. Support depends on the selected engine; there
60
+ is no universal Google syntax translator. `-X/--params` accepts arbitrary
61
+ SearXNG JSON or @file, while preserving the query/JSON response contract.
62
+ Unknown engine names fail rather than silently querying defaults. `provider_fields`
63
+ retains raw result metadata, including image URLs and provider-specific fields.
64
+
65
+ `-n 30` is a best-effort collection target: fetch further pages until 30 unique
66
+ filtered candidates, source exhaustion/failure or the page-request budget. Both
67
+ `-p` and legacy `-P` now mean --max-pages (default 3); --start-page/-s is separate.
68
+ This corrects 2.0's confusing -p=start/-P=count distinction. `-n 0` collects within
69
+ the full explicit budget. Output reports actual requests, counts and stop_reason;
70
+ it never pads or invents results when the target cannot be reached.
71
+ Default relevance ranking orders term coverage then SearXNG's numeric aggregate
72
+ score. `-k provider` sorts by that score alone (not API array/arrival order);
73
+ `-k lexical` uses term coverage only. None establishes identity or semantic truth.
74
+ Regex/domain filtering
75
+ precedes the display limit. Regular expressions use a configurable regex_timeout
76
+ budget rather than unbounded backtracking. URL dedup preserves meaningful query/path case.
77
+ SafeSearch `-m 0/1/2` is requested upstream; off does not remove upstream censorship.
78
+
79
+ Repeated identical searches reuse a labelled private snapshot for 300 seconds,
80
+ independent of table/JSON format. --fresh/-F forces live collection and forbids
81
+ stale fallback. --no-save also disables query-cache persistence/reuse. On failure,
82
+ a default request may return a clearly marked stale snapshot up to 24 hours old,
83
+ with partial status and the error; it is never represented as a new live result.
84
+ TTL, max pages, page delay and fallback_engines are plain configurable SETTINGS.
85
+ If defaults stall and a page-request budget remains, keyless Yahoo/Yep/MWMBL are
86
+ queried once as an explicit alternative batch. No IP/account switching, CAPTCHA
87
+ bypass or rate-limit evasion occurs. Explicit -e choices are not silently replaced.
88
+
89
+ The table shows **Sources**, a full contributor list, and folds complete URLs
90
+ without ellipses. Engine coverage distinguishes contributions, CAPTCHA, rate
91
+ limits and no-results-reported; the latter is not proof an engine was queried or
92
+ is healthy. Provider refusal does not recommend restarting Docker/VPN. Search
93
+ reads index snippets; use fetch separately to inspect actual page content.
94
+
95
+ ```sh
96
+ web search 'KAMDEM Ivann' -n 30 -p 5
97
+ web show /tmp/web-results/SEARCH_SNAPSHOT.json -f table
98
+ web show /tmp/web-results/SEARCH_SNAPSHOT.json -f json
99
+ ```
100
+ `show` renders exactly the saved snapshot without any network request, allowing
101
+ an honest table/JSON comparison. Different live requests may receive different
102
+ provider responses, even when their query text is identical.
103
+
104
+ ## Fetch, inspection, sessions and exact downloads
105
+
106
+ ```sh
107
+ web fetch https://example.org -s 'article h2' -r 'privacy|research' -f json
108
+ web fetch https://example.org/document.pdf -d /tmp/document.pdf
109
+ web fetch https://authorized.example/private -C /private/cookies.txt -H @/private/headers.json
110
+ web crawl https://quotes.toscrape.com -D 3 -n 4 -d /tmp/quotes-pages
111
+ ```
112
+
113
+ Fetch downloads once, records MIME/byte SHA256 and extracts links from those same
114
+ bytes. Binary responses are not parsed as HTML. `--download` preserves exact
115
+ response bytes; existing files are refused. CSS and regex inspection use the
116
+ retrieved body, without extra requests. HTTP 200 login/registration/challenge
117
+ pages are classified as partial access, not useful source reads. These heuristics
118
+ can miss unusual walls; inspect access_state and content, not status alone.
119
+
120
+ Cookies accept Netscape including HttpOnly comments, browser JSON records or a
121
+ flat dictionary scoped to the seed host. Domain/path/secure/expiry restrictions
122
+ are honored. Custom headers are limited to the original origin across redirects;
123
+ header files avoid leaking tokens in shell history. Never send actual session
124
+ values to agents or paste them into reports. Requests ignore environment proxies;
125
+ `--proxy` explicitly selects HTTP/SOCKS. No TLS verification bypass is provided.
126
+
127
+ Crawl is same-host, paced (`--delay`), depth/page bounded and robots-aware by
128
+ default. `--no-robots` is explicit, not a bypass of authorization or terms.
129
+ Next/rel=next links are prioritized; JS scroll/click/API pagination is not executed.
130
+ `--match` restricts downloads, not traversal. Errors and remaining frontier are
131
+ retained in the manifest. New output directories avoid collisions on reruns.
132
+ Private/local destinations need `--allow-private` for your own services.
133
+ `--exclude` is a URL-frontier regex; its configurable default guards common
134
+ logout/delete/checkout GET links. An explicit empty expression disables it after
135
+ review. This heuristic is not proof GET is side-effect-free: use a dedicated
136
+ authorized session and inspect the frontier before crawling sensitive accounts.
137
+ DNS checks are best effort, not a security sandbox against hostile DNS rebinding.
138
+
139
+ For JS, authenticated DOM or browser challenges, use browser-proxy independently
140
+ with your authorization. A cookie handoff can help ordinary sessions but cannot
141
+ reproduce every browser/TLS fingerprint. This CLI never promises to bypass all
142
+ CAPTCHAs, paywalls, permissions or account login.
143
+
144
+ ## Documents, images and audio
145
+
146
+ ```sh
147
+ web doc report.pdf --compare other.pdf --ocr --pages 10 --report /tmp/evidence.pdf
148
+ web doc public-report.md --online --samples 3 --limit 5
149
+ web image photo.jpg # local metadata only
150
+ web image https://public.example/logo.png # remote input, still no reverse upload
151
+ web image public-logo.png --online -e yandex,google,bing --text-search
152
+ web audio excerpt.mp3
153
+ web audio --record 12 --mode hum
154
+ web archive https://public.example/report.pdf -P '{"timestamp":"20240101"}'
155
+ ```
156
+
157
+ Doc is offline by default: --online discloses sampled quotes to search engines.
158
+ PDF/DOCX tables/ODT/PPTX and UTF-8 TXT/MD/CSV/JSON/HTML/XML/RST/TeX are supported;
159
+ unknown binary must be exported, not decoded as garbage. Scanned PDFs require
160
+ explicit OCR and are rendered to temporary images before Tesseract.
161
+ These parsers are not a malware sandbox; use OS isolation for hostile files.
162
+ Page limit 0 selects all pages and can be expensive. Comparisons provide exact byte equality
163
+ and lexical matching spans, NOT semantic equivalence or a plagiarism verdict.
164
+ Online links are candidates; downloaded content is checked separately. A hash
165
+ search result alone is not proof a file leaked. Archive uses Wayback's closest
166
+ snapshot API only; no Archive.today API or exhaustive historical search is claimed.
167
+
168
+ Image uploads require --online and omit original EXIF/GPS. Providers are fragile
169
+ unofficial adapters: failures or empty responses are not proof of absent copies.
170
+ --text-search explicitly discloses local OCR words as a complementary text search;
171
+ those results are not verified visual matches. GPS/device/time are local metadata.
172
+
173
+ Audio uses free SongRec/Shazam; FFmpeg normalizes/records an excerpt. `hum` is a
174
+ qualified best-effort fingerprint attempt and returns partial status: SongRec is
175
+ not a melody recognizer and reliable free humming is not established. ACRCloud,
176
+ paid API logic and credential loading are removed. No transcription is performed.
177
+
178
+ ## Hidden networks and limits
179
+
180
+ Tor fetch uses SOCKS5h (managed optional profile: 127.0.0.1:19050; override
181
+ tor_proxy for an existing external daemon, commonly 9050); once selected, redirects do not
182
+ downgrade to direct or resolve through local DNS. I2P fetch requires an actual
183
+ HTTP proxy (default 127.0.0.1:4444). There is no fake I2P search adapter. For
184
+ indexed onion discovery use `web search QUERY -e ahmia -c onions`; coverage and
185
+ uptime depend on the index and Tor. Private/unindexed content has no universal
186
+ search engine. No tool can enumerate your complete digital footprint or prove
187
+ absence of a leak from absent results. No arbitrary third-party kill switch
188
+ compatibility or perfect anonymity is promised.
189
+
190
+ Surface SearXNG startup is independent of Tor. `make -C ../searxng onion-up`
191
+ explicitly starts an isolated optional onion worker; a failed Tor check does not
192
+ crash the surface worker. `web engines -z onion` inspects it. `web search QUERY
193
+ -e bing,ahmia` partitions those engines between backends and reports missing
194
+ coverage rather than losing working surface results. `-z surface,onion` explicitly
195
+ selects both, or use custom `SETTINGS['search_backends']` entries with url/socket
196
+ fields for another SearXNG-compatible backend. Budgets apply per backend, so total
197
+ requests grow with the number of selected backends. Protocols other than SearXNG
198
+ still need an adapter; arbitrary services are not interchangeable by naming them.
199
+
200
+ ## Configuration, dependencies and lifecycle
201
+
202
+ `web setup` creates ~/.config/web/config.py only if absent. SETTINGS is plain
203
+ trusted Python, not a loader framework. WEB_CONFIG_DIR and standard XDG roots
204
+ control configuration where supported. Existing xsearch configuration/cache were
205
+ moved, not destroyed; dormant old .env credentials are never loaded. Defaults in
206
+ config.py include response_max_bytes, local_max_bytes, xml_max_bytes and
207
+ max_redirects; put overrides in SETTINGS.
208
+ Set phone_region (for example FR) to validate national-format numbers; without
209
+ a region hint, international numbers are preferred rather than guessing dates.
210
+ Local SearXNG prefers its owner-only
211
+ Unix socket to avoid host VPN TCP routing; remote endpoints remain explicit.
212
+
213
+ Python libraries are resolved with bare uv add and uv.lock, never pip or manual
214
+ dependency arrays. System dependencies are deliberately separate/shared:
215
+
216
+ | Feature | Native dependency / installation guidance |
217
+ | --- | --- |
218
+ | Audio | ffmpeg; SongRec via Cargo: cargo install songrec --no-default-features --features ffmpeg |
219
+ | SongRec source build | Rust/Cargo, pkg-config, Clang/libclang, libsoup-3.0-dev, ALSA/PipeWire development headers as required upstream |
220
+ | PDF OCR | poppler-utils (pdftoppm), tesseract-ocr and desired language data |
221
+ | Unicode PDF report | fontconfig (fc-match), fonts-dejavu-core; fpdf2 is managed by uv |
222
+ | Backend | Docker Engine and Compose plugin; sibling searxng repository documents images/ports/volumes |
223
+ | Tor/I2P | Your own reachable Tor SOCKS5h/I2P HTTP proxy; never silently started |
224
+
225
+ Use upstream package instructions for your OS; dependency install commands evolve.
226
+ `make deps` reports installed native capabilities and guidance rather than a rigid
227
+ root installer. Cold builds need network or cached packages/images. Docker logs
228
+ are bounded by the backend; web has no background daemon or persistent log file.
229
+ JSON evidence is not silently rotated/deleted; /tmp is volatile. Explicit
230
+ download/report destinations are user evidence and not automatically purged.
231
+
232
+ `make uninstall` removes the uv tool only. `make purge` stops the sibling backend,
233
+ trashes owned runtime paths, uninstalls the tool, and retains source/shared native
234
+ tools. `make backend-purge CONFIRM=searxng` backs up named volumes under a private
235
+ /tmp directory (.bak files), then removes only that stack's volumes. Review its
236
+ Make recipe first. Never run global Docker prune. A clone is retained so reinstall
237
+ works from either GitHub or GitLab. `make purge-all CONFIRM=searxng` sequences
238
+ the confirmed backend backup/removal and app purge. Install from PyPI using
239
+ uv tool install kpihx-web.
240
+
241
+ All commands use an action/timestamp/status/data envelope. Formats: table, json,
242
+ jsonl, csv, md; --report creates escaped offline HTML or a real PDF. Autosaves are
243
+ unique 0600 JSON in /tmp/web-results; -S overrides and -N disables. Exit 2 is
244
+ failure, exit 3 is partial/access-wall coverage, exit 0 is completion. Inspect
245
+ non_exhaustive, budgets and source errors before drawing conclusions. Source
246
+ quality, identity attribution and correctness require human corroboration.
@@ -0,0 +1,61 @@
1
+ [project]
2
+ name = "kpihx-web"
3
+ version = "2.2.0"
4
+ description = "Private CLI for metasearch, verified retrieval, bounded crawling and media investigation"
5
+ readme = "README.md"
6
+ license = {text = "MIT"}
7
+ requires-python = ">=3.12"
8
+ dependencies = [
9
+ "beautifulsoup4",
10
+ "defusedxml>=0.7.1",
11
+ "fpdf2>=2.8.9",
12
+ "httpx[socks]",
13
+ "lxml",
14
+ "phonenumbers>=9.0.41",
15
+ "picimagesearch",
16
+ "pillow",
17
+ "pypdf",
18
+ "python-docx",
19
+ "regex>=2026.9.29",
20
+ "requests[socks]",
21
+ "rich",
22
+ "typer",
23
+ ]
24
+ classifiers = [
25
+ "Programming Language :: Python :: 3",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Operating System :: OS Independent",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = []
32
+
33
+ [project.scripts]
34
+ web = "k_web.cli:main"
35
+
36
+ [project.urls]
37
+ Repository = "https://github.com/kpihx-labs/web"
38
+ GitLab = "https://gitlab.com/kpihx-labs/web"
39
+ Issues = "https://github.com/kpihx-labs/web/issues"
40
+
41
+ [build-system]
42
+ requires = ["uv_build>=0.10.3,<0.11.0"]
43
+ build-backend = "uv_build"
44
+
45
+ [tool.uv.build-backend]
46
+ module-name = "k_web"
47
+
48
+ [tool.ruff.lint.flake8-bugbear]
49
+ extend-immutable-calls = ["typer.Argument", "typer.Option"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E4", "E7", "E9", "F", "I", "B", "UP"]
56
+
57
+ [dependency-groups]
58
+ dev = [
59
+ "pytest>=9.1.1",
60
+ "reportlab>=5.1.0",
61
+ ]
@@ -0,0 +1,8 @@
1
+ """Local-first web investigation primitives."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("kpihx-web")
7
+ except PackageNotFoundError:
8
+ __version__ = "uninstalled"
@@ -0,0 +1,65 @@
1
+ """Free SongRec fingerprint adapter; humming is explicitly best-effort, not STT."""
2
+
3
+ import json
4
+ import math
5
+ import shutil
6
+ import subprocess
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ from .http import WebError
11
+
12
+
13
+ def identify(
14
+ path, record=None, mode="recording", duration=12.0, source="default", offset=0.0
15
+ ):
16
+ if mode not in {"recording", "hum"}:
17
+ raise WebError("Mode must be recording or hum")
18
+ if not math.isfinite(offset) or offset < 0 or (record is not None and offset):
19
+ raise WebError("Offset must be finite, nonnegative and used only with a file")
20
+ if not math.isfinite(duration) or (
21
+ record is not None and not math.isfinite(record)
22
+ ):
23
+ raise WebError("Audio durations must be finite")
24
+ if (
25
+ (path is None) == (record is None)
26
+ or duration <= 0
27
+ or (record is not None and record <= 0)
28
+ ):
29
+ raise WebError("Provide one file or --record SECONDS, with positive duration")
30
+ for command in ("ffmpeg", "songrec"):
31
+ if not shutil.which(command):
32
+ raise WebError(
33
+ f"Missing {command}; run make deps for installation guidance"
34
+ )
35
+ with tempfile.TemporaryDirectory(prefix="web-audio-") as folder:
36
+ sample = Path(folder) / "sample.wav"
37
+ args = ["ffmpeg", "-nostdin", "-v", "error"]
38
+ args += (
39
+ ["-f", "pulse", "-i", source, "-t", str(record)]
40
+ if record
41
+ else ["-ss", str(offset), "-i", str(path), "-t", str(duration)]
42
+ )
43
+ subprocess.run(
44
+ [*args, "-ac", "1", "-ar", "16000", str(sample)],
45
+ check=True,
46
+ capture_output=True,
47
+ timeout=60 + (record or 0),
48
+ )
49
+ process = subprocess.run(
50
+ ["songrec", "audio-file-to-recognized-song", str(sample)],
51
+ capture_output=True,
52
+ text=True,
53
+ timeout=30,
54
+ check=True,
55
+ )
56
+ result = json.loads(process.stdout)
57
+ return {
58
+ "requested_offset": offset,
59
+ "provider": "songrec",
60
+ "mode": mode,
61
+ "matched": bool(result.get("track")),
62
+ "result": result,
63
+ "partial": mode == "hum",
64
+ "capability": "Original-recording fingerprint, not query-by-humming; melody-only matches are not reliable",
65
+ }