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.
Files changed (55) hide show
  1. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CHANGELOG.md +37 -0
  2. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CONTRIBUTING.md +1 -1
  3. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/PKG-INFO +65 -11
  4. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/README.md +64 -10
  5. hermes_plugin_tinyfish-0.4.0/__init__.py +19 -0
  6. hermes_plugin_tinyfish-0.4.0/after-install.md +12 -0
  7. hermes_plugin_tinyfish-0.4.0/hermes/after-install.md +21 -0
  8. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/__init__.py +4 -2
  9. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/browser_provider.py +22 -8
  10. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/config.py +35 -9
  11. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/normalize.py +120 -27
  12. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/provider.py +133 -101
  13. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/rest_client.py +24 -1
  14. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/routing_context.py +3 -3
  15. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/setup_cli.py +81 -12
  16. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/plugin.yaml +1 -1
  17. hermes_plugin_tinyfish-0.4.0/plugin.yaml +11 -0
  18. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/pyproject.toml +13 -4
  19. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/scripts/prepare_release.py +4 -3
  20. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/scripts/validate_release.py +11 -9
  21. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_browser_provider.py +44 -0
  22. hermes_plugin_tinyfish-0.4.0/tests/test_config.py +77 -0
  23. hermes_plugin_tinyfish-0.4.0/tests/test_distribution_layout.py +30 -0
  24. hermes_plugin_tinyfish-0.4.0/tests/test_normalize.py +169 -0
  25. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_prepare_release.py +3 -0
  26. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_provider.py +132 -0
  27. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_register.py +31 -0
  28. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_rest_client.py +37 -0
  29. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_routing_context.py +2 -2
  30. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_setup_cli.py +76 -1
  31. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_validate_release.py +2 -0
  32. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  33. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
  34. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/generate_report.py +0 -326
  35. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/improve_description.py +0 -247
  36. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/package_skill.py +0 -136
  37. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/quick_validate.py +0 -103
  38. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/run_eval.py +0 -310
  39. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/run_loop.py +0 -328
  40. hermes_plugin_tinyfish-0.3.4/.agents/skills/skill-creator/scripts/utils.py +0 -47
  41. hermes_plugin_tinyfish-0.3.4/tests/test_normalize.py +0 -74
  42. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/.gitignore +0 -0
  43. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  44. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/LICENSE +0 -0
  45. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/SECURITY.md +0 -0
  46. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/__init__.py +0 -0
  47. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/credit_policy.py +0 -0
  48. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/health.py +0 -0
  49. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0/hermes}/hermes_plugin_tinyfish/update_check.py +0 -0
  50. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/conftest.py +0 -0
  51. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_credit_policy.py +0 -0
  52. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_health.py +0 -0
  53. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_live.py +0 -0
  54. {hermes_plugin_tinyfish-0.3.4 → hermes_plugin_tinyfish-0.4.0}/tests/test_update_check.py +0 -0
  55. {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
@@ -11,7 +11,7 @@ python -m pip install -U pip
11
11
  python -m pip install -e ".[dev]"
12
12
  ruff format .
13
13
  ruff check .
14
- mypy hermes_plugin_tinyfish
14
+ mypy hermes/hermes_plugin_tinyfish
15
15
  pytest
16
16
  python -m build
17
17
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hermes-plugin-tinyfish
3
- Version: 0.3.4
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 may save
134
- `TINYFISH_API_KEY` to `~/.hermes/.env` if you choose API-key fallback.
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 REST fallback defaults can be configured in `config.yaml`:
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 options apply only to
235
- REST fallback calls. They are fixed fallback defaults, not required settings
236
- for ordinary requests and not a substitute for plain-language, per-request MCP
237
- controls. The plugin does not add persistent include-domain, exclude-selector,
238
- or similar parity settings.
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 may save
102
- `TINYFISH_API_KEY` to `~/.hermes/.env` if you choose API-key fallback.
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 REST fallback defaults can be configured in `config.yaml`:
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 options apply only to
203
- REST fallback calls. They are fixed fallback defaults, not required settings
204
- for ordinary requests and not a substitute for plain-language, per-request MCP
205
- controls. The plugin does not add persistent include-domain, exclude-selector,
206
- or similar parity settings.
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 _provider_env
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 _provider_env("TINYFISH_API_KEY"))
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 = _provider_env("TINYFISH_API_KEY")
41
+ api_key = _api_key()
41
42
  if not api_key:
42
- raise ValueError("TINYFISH_API_KEY is required for TinyFish Browser sessions.")
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
- api_key = _provider_env("TINYFISH_API_KEY")
76
- if not api_key:
77
- logger.warning("Cannot close TinyFish browser session: missing API key")
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 ("location", "language", "after_date", "before_date", "domain_type", "purpose"):
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
- value = _int_option(section.get(key))
161
- if value is not None:
162
- options[key] = value
163
- for key in ("links", "image_links"):
164
- value = _bool_option(section.get(key))
165
- if value is not None:
166
- options[key] = value
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