hermes-plugin-tinyfish 0.3.4__tar.gz → 0.4.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.
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CHANGELOG.md +37 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CONTRIBUTING.md +1 -1
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/PKG-INFO +65 -11
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/README.md +64 -10
- hermes_plugin_tinyfish-0.4.0/__init__.py +19 -0
- hermes_plugin_tinyfish-0.4.0/after-install.md +12 -0
- hermes_plugin_tinyfish-0.4.0/hermes/after-install.md +21 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/__init__.py +4 -2
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/browser_provider.py +22 -8
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/config.py +35 -9
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/normalize.py +120 -27
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/provider.py +133 -101
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/rest_client.py +24 -1
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/routing_context.py +3 -3
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/setup_cli.py +81 -12
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/plugin.yaml +1 -1
- hermes_plugin_tinyfish-0.4.0/plugin.yaml +11 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/pyproject.toml +13 -4
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/scripts/prepare_release.py +4 -3
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/scripts/validate_release.py +11 -9
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_browser_provider.py +44 -0
- hermes_plugin_tinyfish-0.4.0/tests/test_config.py +77 -0
- hermes_plugin_tinyfish-0.4.0/tests/test_distribution_layout.py +30 -0
- hermes_plugin_tinyfish-0.4.0/tests/test_normalize.py +169 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_prepare_release.py +3 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_provider.py +132 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_register.py +31 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_rest_client.py +37 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_routing_context.py +2 -2
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_setup_cli.py +76 -1
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_validate_release.py +2 -0
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/generate_report.py +0 -326
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/improve_description.py +0 -247
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/package_skill.py +0 -136
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/quick_validate.py +0 -103
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/run_eval.py +0 -310
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/run_loop.py +0 -328
- hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/utils.py +0 -47
- hermes_plugin_tinyfish-0.3.4/tests/test_normalize.py +0 -74
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/.gitignore +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/LICENSE +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/SECURITY.md +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/__init__.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/credit_policy.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/health.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/update_check.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/conftest.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_credit_policy.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_health.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_live.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_update_check.py +0 -0
- {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_version.py +0 -0
|
@@ -7,6 +7,43 @@ and this project uses semantic versioning.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Added current TinyFish Search filters (`include_domains`,
|
|
15
|
+
`exclude_domains`, `pub_year_min`, and `pub_year_max`) and Fetch controls
|
|
16
|
+
(`purpose`, CSS include/exclude selectors, conditional validators, and
|
|
17
|
+
validator-return metadata) across both MCP and REST transports.
|
|
18
|
+
- Added compatibility with TinyFish CLI-seeded `MCP_TINYFISH_API_KEY`, while
|
|
19
|
+
preserving `TINYFISH_API_KEY` precedence.
|
|
20
|
+
- Added a Hermes `after-install.md` guide that routes new installs through
|
|
21
|
+
OAuth-first setup, diagnostics, and explicit paid-Browser policy.
|
|
22
|
+
- Added a minimal `hermes/` Git-install distribution so current Hermes security
|
|
23
|
+
scanning evaluates runtime plugin files instead of repository development
|
|
24
|
+
workflows and agent-maintenance tooling.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Forwarded operator Search/Fetch defaults consistently through MCP and REST,
|
|
29
|
+
increased the REST Fetch timeout to TinyFish's documented 150-second batch
|
|
30
|
+
recommendation, chunked direct calls at the 10-URL API cap, and restored
|
|
31
|
+
output to input URL order.
|
|
32
|
+
- Expanded normalization to preserve news/research fields, selector recovery
|
|
33
|
+
hints, conditional/cache metadata, authorship, links/images, and latency;
|
|
34
|
+
JSON Fetch document trees now remain valid JSON.
|
|
35
|
+
- Bumped the tool-routing context to version 3 so it distinguishes transport-
|
|
36
|
+
invariant operator defaults from one-off controls that require native MCP
|
|
37
|
+
schemas.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Closed billable Browser sessions when TinyFish returns an ID without a CDP
|
|
42
|
+
URL, prevented cleanup exceptions from escaping the provider contract, and
|
|
43
|
+
withheld Browser registration on Hermes hosts lacking approval hooks.
|
|
44
|
+
- Adopted Hermes's managed-secret credential writer with persisted-value
|
|
45
|
+
verification while retaining the legacy writer on older supported hosts.
|
|
46
|
+
|
|
10
47
|
## [0.3.4] - 2026-08-13
|
|
11
48
|
|
|
12
49
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hermes-plugin-tinyfish
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: TinyFish Search, Fetch, plain-language routing, update awareness, and optional Browser for Hermes Agent
|
|
5
5
|
Project-URL: Homepage, https://github.com/gabeosx/hermes-plugin-tinyfish
|
|
6
6
|
Project-URL: Documentation, https://github.com/gabeosx/hermes-plugin-tinyfish#readme
|
|
@@ -59,7 +59,7 @@ TinyFish documentation and may change; Browser defaults to `deny`.
|
|
|
59
59
|
Hermes Git plugin install:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
hermes plugins install gabeosx/hermes-plugin-tinyfish --enable
|
|
62
|
+
hermes plugins install gabeosx/hermes-plugin-tinyfish/hermes --enable
|
|
63
63
|
hermes tinyfish setup
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -81,6 +81,16 @@ hermes tinyfish status
|
|
|
81
81
|
hermes tinyfish doctor
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
Installations created before `0.4.0` from the repository root should migrate
|
|
85
|
+
once to the minimal scanner-friendly distribution before updating:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
hermes plugins install gabeosx/hermes-plugin-tinyfish/hermes --force --enable
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This replaces only the plugin checkout; Hermes configuration, OAuth state, and
|
|
92
|
+
externally managed credentials remain in the active Hermes profile.
|
|
93
|
+
|
|
84
94
|
If the plugin was installed as a Python package instead, update it with:
|
|
85
95
|
|
|
86
96
|
```bash
|
|
@@ -130,8 +140,10 @@ tinyfish:
|
|
|
130
140
|
```
|
|
131
141
|
|
|
132
142
|
The plugin prefers TinyFish's hosted OAuth MCP server and falls back to REST
|
|
133
|
-
with `TINYFISH_API_KEY` when MCP OAuth is unavailable. It
|
|
134
|
-
`
|
|
143
|
+
with `TINYFISH_API_KEY` when MCP OAuth is unavailable. It also recognizes
|
|
144
|
+
`MCP_TINYFISH_API_KEY`, which the TinyFish CLI may seed during its Hermes
|
|
145
|
+
connection flow; an explicit `TINYFISH_API_KEY` takes precedence. The plugin
|
|
146
|
+
may save `TINYFISH_API_KEY` to `~/.hermes/.env` if you choose API-key fallback.
|
|
135
147
|
|
|
136
148
|
Verify Search and Fetch:
|
|
137
149
|
|
|
@@ -214,28 +226,70 @@ compressed.
|
|
|
214
226
|
|
|
215
227
|
## Search and Fetch Options
|
|
216
228
|
|
|
217
|
-
Optional
|
|
229
|
+
Optional operator defaults can be configured in `config.yaml`:
|
|
218
230
|
|
|
219
231
|
```yaml
|
|
220
232
|
tinyfish:
|
|
221
233
|
search:
|
|
222
234
|
location: US
|
|
223
235
|
language: en
|
|
236
|
+
include_domains: docs.tinyfish.ai,github.com
|
|
237
|
+
exclude_domains: example.com
|
|
224
238
|
recency_minutes: 1440
|
|
225
239
|
domain_type: web
|
|
240
|
+
# pub_year_min: 2024 # research_paper mode only
|
|
241
|
+
# pub_year_max: 2026
|
|
226
242
|
page: 0
|
|
243
|
+
purpose: Find primary documentation for implementation decisions
|
|
227
244
|
fetch:
|
|
228
245
|
format: markdown
|
|
229
246
|
links: false
|
|
230
247
|
image_links: false
|
|
231
248
|
ttl: 3600
|
|
249
|
+
per_url_timeout_ms: 45000
|
|
250
|
+
purpose: Extract the main documentation content
|
|
251
|
+
include_selectors: [main, article]
|
|
252
|
+
exclude_selectors: [nav, .comments, .newsletter-signup]
|
|
253
|
+
include_etag_and_last_modified: true
|
|
232
254
|
```
|
|
233
255
|
|
|
234
|
-
MCP remains the preferred path when configured. These
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
256
|
+
MCP remains the preferred path when configured. These defaults are forwarded
|
|
257
|
+
consistently through MCP and REST, so failover does not silently discard the
|
|
258
|
+
operator's domain, date, intent, selector, cache, or extraction constraints.
|
|
259
|
+
They are not required for ordinary requests and do not replace plain-language,
|
|
260
|
+
per-request MCP controls: Hermes's generic `web_search` and `web_extract`
|
|
261
|
+
schemas cannot express these options for one call, so the routing note steers
|
|
262
|
+
one-off advanced requests to native TinyFish `search` or `fetch_content`.
|
|
263
|
+
|
|
264
|
+
Fetch also supports the URL-specific `if_none_match` and
|
|
265
|
+
`if_modified_since` settings. TinyFish permits either validator only for a
|
|
266
|
+
single URL, and the plugin fails clearly instead of sending an invalid batch.
|
|
267
|
+
Selector lists accept up to 20 CSS selectors. A total include-selector miss is
|
|
268
|
+
returned as `selector_not_matched` with TinyFish's unmatched selectors and
|
|
269
|
+
candidate-selector retry hints preserved in document metadata. Conditional
|
|
270
|
+
validators, `not_modified`, links, images, author/date, latency, output format,
|
|
271
|
+
and other current Fetch metadata are preserved as well. JSON-format document
|
|
272
|
+
trees are serialized as valid JSON rather than Python object representations.
|
|
273
|
+
|
|
274
|
+
Direct provider callers may pass the same Fetch options per call. Requests over
|
|
275
|
+
TinyFish's 10-URL API limit are split into bounded chunks, and results are
|
|
276
|
+
restored to input URL order even when TinyFish separates successes and errors.
|
|
277
|
+
|
|
278
|
+
## Why Choose This Community Plugin
|
|
279
|
+
|
|
280
|
+
- OAuth-first MCP setup avoids requiring an API key, while config-aware REST
|
|
281
|
+
fallback and TinyFish CLI-seeded keys remain supported.
|
|
282
|
+
- Search and Fetch use the same advanced option set on both transports, with
|
|
283
|
+
categorized MCP health, explicit MCP-only/REST-only diagnostics, safe
|
|
284
|
+
reauthorization, and observable failover.
|
|
285
|
+
- Browser is fail-closed by default. It is not exposed on older Hermes hosts
|
|
286
|
+
that cannot install the approval hook, incomplete billable sessions are
|
|
287
|
+
cleaned up, and close failures never escape Hermes's cleanup contract.
|
|
288
|
+
- GitHub and PyPI distribution, nonblocking update awareness, migration
|
|
289
|
+
reporting, and an automated Hermes compatibility matrix make upgrades
|
|
290
|
+
predictable.
|
|
291
|
+
- The scope stays Hermes-native: no competing delegated agent loop, no hidden
|
|
292
|
+
expansion of paid capabilities, and clear independent/pricing disclosures.
|
|
239
293
|
|
|
240
294
|
## Optional Browser Provider
|
|
241
295
|
|
|
@@ -344,7 +398,7 @@ python -m pip install -U pip
|
|
|
344
398
|
python -m pip install -e ".[dev]"
|
|
345
399
|
ruff format .
|
|
346
400
|
ruff check .
|
|
347
|
-
mypy hermes_plugin_tinyfish
|
|
401
|
+
mypy hermes/hermes_plugin_tinyfish
|
|
348
402
|
pytest --cov=hermes_plugin_tinyfish --cov-fail-under=70
|
|
349
403
|
python -m build
|
|
350
404
|
```
|
|
@@ -27,7 +27,7 @@ TinyFish documentation and may change; Browser defaults to `deny`.
|
|
|
27
27
|
Hermes Git plugin install:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
hermes plugins install gabeosx/hermes-plugin-tinyfish --enable
|
|
30
|
+
hermes plugins install gabeosx/hermes-plugin-tinyfish/hermes --enable
|
|
31
31
|
hermes tinyfish setup
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -49,6 +49,16 @@ hermes tinyfish status
|
|
|
49
49
|
hermes tinyfish doctor
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
Installations created before `0.4.0` from the repository root should migrate
|
|
53
|
+
once to the minimal scanner-friendly distribution before updating:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
hermes plugins install gabeosx/hermes-plugin-tinyfish/hermes --force --enable
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This replaces only the plugin checkout; Hermes configuration, OAuth state, and
|
|
60
|
+
externally managed credentials remain in the active Hermes profile.
|
|
61
|
+
|
|
52
62
|
If the plugin was installed as a Python package instead, update it with:
|
|
53
63
|
|
|
54
64
|
```bash
|
|
@@ -98,8 +108,10 @@ tinyfish:
|
|
|
98
108
|
```
|
|
99
109
|
|
|
100
110
|
The plugin prefers TinyFish's hosted OAuth MCP server and falls back to REST
|
|
101
|
-
with `TINYFISH_API_KEY` when MCP OAuth is unavailable. It
|
|
102
|
-
`
|
|
111
|
+
with `TINYFISH_API_KEY` when MCP OAuth is unavailable. It also recognizes
|
|
112
|
+
`MCP_TINYFISH_API_KEY`, which the TinyFish CLI may seed during its Hermes
|
|
113
|
+
connection flow; an explicit `TINYFISH_API_KEY` takes precedence. The plugin
|
|
114
|
+
may save `TINYFISH_API_KEY` to `~/.hermes/.env` if you choose API-key fallback.
|
|
103
115
|
|
|
104
116
|
Verify Search and Fetch:
|
|
105
117
|
|
|
@@ -182,28 +194,70 @@ compressed.
|
|
|
182
194
|
|
|
183
195
|
## Search and Fetch Options
|
|
184
196
|
|
|
185
|
-
Optional
|
|
197
|
+
Optional operator defaults can be configured in `config.yaml`:
|
|
186
198
|
|
|
187
199
|
```yaml
|
|
188
200
|
tinyfish:
|
|
189
201
|
search:
|
|
190
202
|
location: US
|
|
191
203
|
language: en
|
|
204
|
+
include_domains: docs.tinyfish.ai,github.com
|
|
205
|
+
exclude_domains: example.com
|
|
192
206
|
recency_minutes: 1440
|
|
193
207
|
domain_type: web
|
|
208
|
+
# pub_year_min: 2024 # research_paper mode only
|
|
209
|
+
# pub_year_max: 2026
|
|
194
210
|
page: 0
|
|
211
|
+
purpose: Find primary documentation for implementation decisions
|
|
195
212
|
fetch:
|
|
196
213
|
format: markdown
|
|
197
214
|
links: false
|
|
198
215
|
image_links: false
|
|
199
216
|
ttl: 3600
|
|
217
|
+
per_url_timeout_ms: 45000
|
|
218
|
+
purpose: Extract the main documentation content
|
|
219
|
+
include_selectors: [main, article]
|
|
220
|
+
exclude_selectors: [nav, .comments, .newsletter-signup]
|
|
221
|
+
include_etag_and_last_modified: true
|
|
200
222
|
```
|
|
201
223
|
|
|
202
|
-
MCP remains the preferred path when configured. These
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
224
|
+
MCP remains the preferred path when configured. These defaults are forwarded
|
|
225
|
+
consistently through MCP and REST, so failover does not silently discard the
|
|
226
|
+
operator's domain, date, intent, selector, cache, or extraction constraints.
|
|
227
|
+
They are not required for ordinary requests and do not replace plain-language,
|
|
228
|
+
per-request MCP controls: Hermes's generic `web_search` and `web_extract`
|
|
229
|
+
schemas cannot express these options for one call, so the routing note steers
|
|
230
|
+
one-off advanced requests to native TinyFish `search` or `fetch_content`.
|
|
231
|
+
|
|
232
|
+
Fetch also supports the URL-specific `if_none_match` and
|
|
233
|
+
`if_modified_since` settings. TinyFish permits either validator only for a
|
|
234
|
+
single URL, and the plugin fails clearly instead of sending an invalid batch.
|
|
235
|
+
Selector lists accept up to 20 CSS selectors. A total include-selector miss is
|
|
236
|
+
returned as `selector_not_matched` with TinyFish's unmatched selectors and
|
|
237
|
+
candidate-selector retry hints preserved in document metadata. Conditional
|
|
238
|
+
validators, `not_modified`, links, images, author/date, latency, output format,
|
|
239
|
+
and other current Fetch metadata are preserved as well. JSON-format document
|
|
240
|
+
trees are serialized as valid JSON rather than Python object representations.
|
|
241
|
+
|
|
242
|
+
Direct provider callers may pass the same Fetch options per call. Requests over
|
|
243
|
+
TinyFish's 10-URL API limit are split into bounded chunks, and results are
|
|
244
|
+
restored to input URL order even when TinyFish separates successes and errors.
|
|
245
|
+
|
|
246
|
+
## Why Choose This Community Plugin
|
|
247
|
+
|
|
248
|
+
- OAuth-first MCP setup avoids requiring an API key, while config-aware REST
|
|
249
|
+
fallback and TinyFish CLI-seeded keys remain supported.
|
|
250
|
+
- Search and Fetch use the same advanced option set on both transports, with
|
|
251
|
+
categorized MCP health, explicit MCP-only/REST-only diagnostics, safe
|
|
252
|
+
reauthorization, and observable failover.
|
|
253
|
+
- Browser is fail-closed by default. It is not exposed on older Hermes hosts
|
|
254
|
+
that cannot install the approval hook, incomplete billable sessions are
|
|
255
|
+
cleaned up, and close failures never escape Hermes's cleanup contract.
|
|
256
|
+
- GitHub and PyPI distribution, nonblocking update awareness, migration
|
|
257
|
+
reporting, and an automated Hermes compatibility matrix make upgrades
|
|
258
|
+
predictable.
|
|
259
|
+
- The scope stays Hermes-native: no competing delegated agent loop, no hidden
|
|
260
|
+
expansion of paid capabilities, and clear independent/pricing disclosures.
|
|
207
261
|
|
|
208
262
|
## Optional Browser Provider
|
|
209
263
|
|
|
@@ -312,7 +366,7 @@ python -m pip install -U pip
|
|
|
312
366
|
python -m pip install -e ".[dev]"
|
|
313
367
|
ruff format .
|
|
314
368
|
ruff check .
|
|
315
|
-
mypy hermes_plugin_tinyfish
|
|
369
|
+
mypy hermes/hermes_plugin_tinyfish
|
|
316
370
|
pytest --cov=hermes_plugin_tinyfish --cov-fail-under=70
|
|
317
371
|
python -m build
|
|
318
372
|
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Compatibility entry point for pre-0.4 root-directory Git installs.
|
|
2
|
+
|
|
3
|
+
New Hermes installations use the scanner-friendly ``hermes/`` subdirectory.
|
|
4
|
+
Existing root-directory installations still load this shim after an update.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
_INSTALL_ROOT = Path(__file__).resolve().parent / "hermes"
|
|
13
|
+
_install_root_text = str(_INSTALL_ROOT)
|
|
14
|
+
if _install_root_text not in sys.path:
|
|
15
|
+
sys.path.insert(0, _install_root_text)
|
|
16
|
+
|
|
17
|
+
from hermes_plugin_tinyfish import register # noqa: E402
|
|
18
|
+
|
|
19
|
+
__all__ = ["register"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# TinyFish for Hermes has moved to a minimal install surface
|
|
2
|
+
|
|
3
|
+
This compatibility entry point keeps existing installations working. For
|
|
4
|
+
future scanner-clean updates, reinstall from the dedicated subdirectory:
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
hermes plugins install gabeosx/hermes-plugin-tinyfish/hermes --force --enable
|
|
8
|
+
hermes tinyfish setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The reinstall preserves your Hermes configuration, OAuth state, and externally
|
|
12
|
+
managed API keys.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TinyFish for Hermes is installed
|
|
2
|
+
|
|
3
|
+
Finish setup with:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
hermes tinyfish setup
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
The setup flow prefers TinyFish OAuth, can use `TINYFISH_API_KEY` or the
|
|
10
|
+
TinyFish CLI's `MCP_TINYFISH_API_KEY`, and verifies the active transport.
|
|
11
|
+
|
|
12
|
+
Then check the installation with:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
hermes tinyfish doctor
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Search and Fetch are free and enabled through Hermes. Paid Browser sessions
|
|
19
|
+
remain disabled until you explicitly choose an approval policy during setup.
|
|
20
|
+
|
|
21
|
+
Documentation: https://github.com/gabeosx/hermes-plugin-tinyfish#readme
|
|
@@ -52,9 +52,11 @@ def register(ctx: Any) -> None:
|
|
|
52
52
|
update_checker = UpdateChecker(resolve_install_info(ctx, __version__))
|
|
53
53
|
turn_context = TinyFishTurnContext(update_checker)
|
|
54
54
|
ctx.register_web_search_provider(provider)
|
|
55
|
-
if hasattr(ctx, "register_browser_provider"):
|
|
56
|
-
ctx.register_browser_provider(TinyFishBrowserProvider())
|
|
57
55
|
if hasattr(ctx, "register_hook"):
|
|
56
|
+
# Browser policy "request" depends on the pre_tool_call approval hook.
|
|
57
|
+
# Do not expose the paid provider on older hosts that cannot install it.
|
|
58
|
+
if hasattr(ctx, "register_browser_provider"):
|
|
59
|
+
ctx.register_browser_provider(TinyFishBrowserProvider())
|
|
58
60
|
ctx.register_hook("pre_tool_call", pre_tool_call_policy)
|
|
59
61
|
ctx.register_hook("pre_llm_call", turn_context)
|
|
60
62
|
update_checker.start()
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import uuid
|
|
7
|
+
from contextlib import suppress
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
9
10
|
try:
|
|
@@ -17,7 +18,7 @@ except Exception: # pragma: no cover - lets package import outside Hermes
|
|
|
17
18
|
from . import rest_client
|
|
18
19
|
from .config import credit_policy, tinyfish_config
|
|
19
20
|
from .credit_policy import INDEPENDENT_NOTICE, PRICING_NOTICE
|
|
20
|
-
from .provider import
|
|
21
|
+
from .provider import _api_key
|
|
21
22
|
|
|
22
23
|
logger = logging.getLogger(__name__)
|
|
23
24
|
|
|
@@ -34,12 +35,14 @@ class TinyFishBrowserProvider(_HermesBrowserProvider): # type: ignore[misc]
|
|
|
34
35
|
return "TinyFish"
|
|
35
36
|
|
|
36
37
|
def is_available(self) -> bool:
|
|
37
|
-
return bool(credit_policy("browser") in {"request", "allow"} and
|
|
38
|
+
return bool(credit_policy("browser") in {"request", "allow"} and _api_key())
|
|
38
39
|
|
|
39
40
|
def create_session(self, task_id: str) -> dict[str, object]:
|
|
40
|
-
api_key =
|
|
41
|
+
api_key = _api_key()
|
|
41
42
|
if not api_key:
|
|
42
|
-
raise ValueError(
|
|
43
|
+
raise ValueError(
|
|
44
|
+
"TINYFISH_API_KEY or MCP_TINYFISH_API_KEY is required for TinyFish Browser sessions."
|
|
45
|
+
)
|
|
43
46
|
if credit_policy("browser") == "deny":
|
|
44
47
|
raise ValueError(
|
|
45
48
|
"TinyFish Browser is credit-policy denied. Run "
|
|
@@ -51,12 +54,18 @@ class TinyFishBrowserProvider(_HermesBrowserProvider): # type: ignore[misc]
|
|
|
51
54
|
try:
|
|
52
55
|
timeout_seconds = int(browser_cfg["timeout_seconds"])
|
|
53
56
|
except (TypeError, ValueError):
|
|
57
|
+
logger.warning("Ignoring malformed TinyFish Browser timeout configuration")
|
|
54
58
|
timeout_seconds = None
|
|
55
59
|
|
|
56
60
|
data = rest_client.create_browser_session(api_key=api_key, timeout_seconds=timeout_seconds)
|
|
57
61
|
session_id = str(data.get("session_id") or data.get("id") or "")
|
|
58
62
|
cdp_url = str(data.get("cdp_url") or data.get("cdpUrl") or "")
|
|
59
63
|
if not session_id or not cdp_url:
|
|
64
|
+
if session_id:
|
|
65
|
+
# Avoid leaving a billable half-created session running when
|
|
66
|
+
# TinyFish returns an incomplete connection payload.
|
|
67
|
+
with suppress(Exception):
|
|
68
|
+
rest_client.close_browser_session(session_id, api_key=api_key)
|
|
60
69
|
raise RuntimeError("TinyFish Browser did not return session_id and cdp_url.")
|
|
61
70
|
session_name = f"tinyfish_{task_id}_{uuid.uuid4().hex[:8]}"
|
|
62
71
|
logger.info("Created TinyFish browser session")
|
|
@@ -72,11 +81,16 @@ class TinyFishBrowserProvider(_HermesBrowserProvider): # type: ignore[misc]
|
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
def close_session(self, session_id: str) -> bool:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
# Hermes' BrowserProvider contract treats cleanup as best effort.
|
|
85
|
+
try:
|
|
86
|
+
api_key = _api_key()
|
|
87
|
+
if not api_key:
|
|
88
|
+
logger.warning("Cannot close TinyFish browser session: missing API key")
|
|
89
|
+
return False
|
|
90
|
+
return rest_client.close_browser_session(session_id, api_key=api_key)
|
|
91
|
+
except Exception as exc:
|
|
92
|
+
logger.debug("TinyFish browser close failed (%s)", type(exc).__name__)
|
|
78
93
|
return False
|
|
79
|
-
return rest_client.close_browser_session(session_id, api_key=api_key)
|
|
80
94
|
|
|
81
95
|
def emergency_cleanup(self, session_id: str) -> None:
|
|
82
96
|
try:
|
|
@@ -135,16 +135,34 @@ def _bool_option(value: Any) -> bool | None:
|
|
|
135
135
|
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
|
136
136
|
|
|
137
137
|
|
|
138
|
+
def _string_list_option(value: Any) -> list[str] | None:
|
|
139
|
+
if value in (None, ""):
|
|
140
|
+
return None
|
|
141
|
+
if not isinstance(value, (list, tuple)):
|
|
142
|
+
return None
|
|
143
|
+
items = [str(item).strip() for item in value if str(item).strip()]
|
|
144
|
+
return items or None
|
|
145
|
+
|
|
146
|
+
|
|
138
147
|
def search_options(config: dict[str, Any] | None = None) -> SearchOptions:
|
|
139
148
|
section = tinyfish_config(config).get("search") or {}
|
|
140
149
|
if not isinstance(section, dict):
|
|
141
150
|
return {}
|
|
142
151
|
options: SearchOptions = {}
|
|
143
|
-
for key in (
|
|
152
|
+
for key in (
|
|
153
|
+
"location",
|
|
154
|
+
"language",
|
|
155
|
+
"include_domains",
|
|
156
|
+
"exclude_domains",
|
|
157
|
+
"after_date",
|
|
158
|
+
"before_date",
|
|
159
|
+
"domain_type",
|
|
160
|
+
"purpose",
|
|
161
|
+
):
|
|
144
162
|
value = section.get(key)
|
|
145
163
|
if value not in (None, ""):
|
|
146
164
|
options[key] = str(value)
|
|
147
|
-
for key in ("recency_minutes", "page"):
|
|
165
|
+
for key in ("recency_minutes", "pub_year_min", "pub_year_max", "page"):
|
|
148
166
|
value = _int_option(section.get(key))
|
|
149
167
|
if value is not None:
|
|
150
168
|
options[key] = value
|
|
@@ -157,13 +175,21 @@ def fetch_options(config: dict[str, Any] | None = None) -> FetchOptions:
|
|
|
157
175
|
return {}
|
|
158
176
|
options: FetchOptions = {}
|
|
159
177
|
for key in ("ttl", "per_url_timeout_ms"):
|
|
160
|
-
|
|
161
|
-
if
|
|
162
|
-
options[key] =
|
|
163
|
-
for key in ("links", "image_links"):
|
|
164
|
-
|
|
165
|
-
if
|
|
166
|
-
options[key] =
|
|
178
|
+
int_value = _int_option(section.get(key))
|
|
179
|
+
if int_value is not None:
|
|
180
|
+
options[key] = int_value
|
|
181
|
+
for key in ("links", "image_links", "include_etag_and_last_modified"):
|
|
182
|
+
bool_value = _bool_option(section.get(key))
|
|
183
|
+
if bool_value is not None:
|
|
184
|
+
options[key] = bool_value
|
|
185
|
+
for key in ("purpose", "if_none_match", "if_modified_since"):
|
|
186
|
+
text_value = section.get(key)
|
|
187
|
+
if text_value not in (None, ""):
|
|
188
|
+
options[key] = str(text_value)
|
|
189
|
+
for key in ("include_selectors", "exclude_selectors"):
|
|
190
|
+
list_value = _string_list_option(section.get(key))
|
|
191
|
+
if list_value is not None:
|
|
192
|
+
options[key] = list_value
|
|
167
193
|
return options
|
|
168
194
|
|
|
169
195
|
|