hermes-plugin-tinyfish 0.3.2__tar.gz → 0.3.3__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 (48) hide show
  1. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/CHANGELOG.md +8 -0
  2. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/PKG-INFO +4 -3
  3. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/README.md +3 -2
  4. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/setup_cli.py +123 -9
  5. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/plugin.yaml +1 -1
  6. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/pyproject.toml +1 -1
  7. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_setup_cli.py +97 -0
  8. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/__init__.py +0 -0
  9. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
  10. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/generate_report.py +0 -0
  11. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/improve_description.py +0 -0
  12. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/package_skill.py +0 -0
  13. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/quick_validate.py +0 -0
  14. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/run_eval.py +0 -0
  15. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/run_loop.py +0 -0
  16. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.agents/skills/skill-creator/scripts/utils.py +0 -0
  17. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/.gitignore +0 -0
  18. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/CODE_OF_CONDUCT.md +0 -0
  19. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/CONTRIBUTING.md +0 -0
  20. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/LICENSE +0 -0
  21. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/SECURITY.md +0 -0
  22. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/__init__.py +0 -0
  23. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/__init__.py +0 -0
  24. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/browser_provider.py +0 -0
  25. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/config.py +0 -0
  26. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/credit_policy.py +0 -0
  27. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/health.py +0 -0
  28. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/normalize.py +0 -0
  29. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/provider.py +0 -0
  30. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/rest_client.py +0 -0
  31. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/routing_context.py +0 -0
  32. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/hermes_plugin_tinyfish/update_check.py +0 -0
  33. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/scripts/prepare_release.py +0 -0
  34. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/scripts/validate_release.py +0 -0
  35. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/conftest.py +0 -0
  36. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_browser_provider.py +0 -0
  37. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_credit_policy.py +0 -0
  38. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_health.py +0 -0
  39. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_live.py +0 -0
  40. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_normalize.py +0 -0
  41. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_prepare_release.py +0 -0
  42. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_provider.py +0 -0
  43. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_register.py +0 -0
  44. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_rest_client.py +0 -0
  45. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_routing_context.py +0 -0
  46. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_update_check.py +0 -0
  47. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_validate_release.py +0 -0
  48. {hermes_plugin_tinyfish-0.3.2 → hermes_plugin_tinyfish-0.3.3}/tests/test_version.py +0 -0
@@ -7,6 +7,14 @@ and this project uses semantic versioning.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [0.3.3] - 2026-08-12
11
+
12
+ ### Fixed
13
+
14
+ - Replaced the unreadable raw dictionary output from `hermes tinyfish usage`
15
+ with labeled Search and Fetch histories, pagination summaries, and clear
16
+ partial-failure reporting while preserving `--json` for automation.
17
+
10
18
  ## [0.3.2] - 2026-08-11
11
19
 
12
20
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: hermes-plugin-tinyfish
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -301,8 +301,9 @@ hermes tinyfish doctor --live-paid
301
301
  - `/tinyfish-status` shows non-networked status inside CLI or gateway sessions;
302
302
  `/tinyfish-status live` explicitly runs Search and Fetch checks.
303
303
  - `usage` reads TinyFish Search and Fetch operation history independently and
304
- reports per-surface success or failure; it is not Agent or Browser billing
305
- data.
304
+ prints a labeled, paginated history for people. Use `usage --json` for the raw
305
+ machine-readable response. It reports per-surface success or failure and is
306
+ not Agent or Browser billing data.
306
307
  - `doctor --live-paid` only creates and closes a TinyFish Browser session. It
307
308
  refuses under `deny`, requests approval under `request`, and never prints
308
309
  connection URLs or credentials.
@@ -269,8 +269,9 @@ hermes tinyfish doctor --live-paid
269
269
  - `/tinyfish-status` shows non-networked status inside CLI or gateway sessions;
270
270
  `/tinyfish-status live` explicitly runs Search and Fetch checks.
271
271
  - `usage` reads TinyFish Search and Fetch operation history independently and
272
- reports per-surface success or failure; it is not Agent or Browser billing
273
- data.
272
+ prints a labeled, paginated history for people. Use `usage --json` for the raw
273
+ machine-readable response. It reports per-surface success or failure and is
274
+ not Agent or Browser billing data.
274
275
  - `doctor --live-paid` only creates and closes a TinyFish Browser session. It
275
276
  refuses under `deny`, requests approval under `request`, and never prints
276
277
  connection URLs or credentials.
@@ -603,14 +603,6 @@ def tinyfish_status_command(
603
603
  return "\n".join(_status_lines(status))
604
604
 
605
605
 
606
- def _print_json_or_text(payload: dict[str, Any], *, as_json: bool) -> None:
607
- if as_json:
608
- print(json.dumps(payload, indent=2, sort_keys=True))
609
- else:
610
- for key, value in payload.items():
611
- print(f"{key}: {value}")
612
-
613
-
614
606
  def _api_key_or_error() -> str | None:
615
607
  api_key = _get_env("TINYFISH_API_KEY")
616
608
  if not api_key:
@@ -721,6 +713,125 @@ def _usage_surface(
721
713
  return {"success": False, "error": str(exc)}
722
714
 
723
715
 
716
+ _USAGE_FIELD_ORDER = (
717
+ "created_at",
718
+ "status",
719
+ "query",
720
+ "url",
721
+ "final_url",
722
+ "title",
723
+ "description",
724
+ "domain_type",
725
+ "location",
726
+ "language",
727
+ "author",
728
+ "published_date",
729
+ "format",
730
+ "results_count",
731
+ "total_results",
732
+ "text_length",
733
+ "links_count",
734
+ "image_links_count",
735
+ "latency_ms",
736
+ "request_origin",
737
+ "id",
738
+ "request_id",
739
+ "error",
740
+ )
741
+
742
+ _USAGE_FIELD_LABELS = {
743
+ "id": "ID",
744
+ "url": "URL",
745
+ "final_url": "Final URL",
746
+ "latency_ms": "Latency (ms)",
747
+ "request_id": "Request ID",
748
+ }
749
+
750
+
751
+ def _usage_field_label(key: str) -> str:
752
+ return _USAGE_FIELD_LABELS.get(key, key.replace("_", " ").capitalize())
753
+
754
+
755
+ def _usage_value_lines(value: Any) -> list[str]:
756
+ if value is None:
757
+ return ["-"]
758
+ if isinstance(value, bool):
759
+ return ["yes" if value else "no"]
760
+ if isinstance(value, list) and all(
761
+ item is None or isinstance(item, (str, int, float, bool)) for item in value
762
+ ):
763
+ return [", ".join(str(item) for item in value) if value else "-"]
764
+ if isinstance(value, (dict, list)):
765
+ return json.dumps(value, indent=2, sort_keys=True, ensure_ascii=False).splitlines()
766
+ return [str(value)]
767
+
768
+
769
+ def _usage_items(data: dict[str, Any]) -> list[Any]:
770
+ for key in ("items", "operations"):
771
+ value = data.get(key)
772
+ if isinstance(value, list):
773
+ return value
774
+ return []
775
+
776
+
777
+ def _usage_summary(data: dict[str, Any], *, shown: int) -> str:
778
+ noun = "operation" if shown == 1 else "operations"
779
+ parts = [f"{shown} {noun}"]
780
+ total = data.get("total")
781
+ if isinstance(total, int) and not isinstance(total, bool):
782
+ parts[0] = f"{shown} shown / {total} total"
783
+ page = data.get("page")
784
+ total_pages = data.get("total_pages")
785
+ if isinstance(page, int) and isinstance(total_pages, int):
786
+ parts.append(f"page {page} of {total_pages}")
787
+ limit = data.get("limit")
788
+ if isinstance(limit, int) and not isinstance(limit, bool):
789
+ parts.append(f"limit {limit}")
790
+ if isinstance(data.get("has_more"), bool):
791
+ parts.append(f"more: {'yes' if data['has_more'] else 'no'}")
792
+ return " | ".join(parts)
793
+
794
+
795
+ def _usage_text_lines(payload: dict[str, Any]) -> list[str]:
796
+ lines = ["TinyFish usage"]
797
+ surfaces = payload.get("surfaces")
798
+ if not isinstance(surfaces, dict):
799
+ return [*lines, " Invalid usage response."]
800
+
801
+ for surface_name in ("search", "fetch"):
802
+ lines.extend(("", surface_name.capitalize()))
803
+ result = surfaces.get(surface_name)
804
+ if not isinstance(result, dict):
805
+ lines.append(" No response.")
806
+ continue
807
+ if not result.get("success"):
808
+ lines.append(f" Error: {result.get('error') or 'Unknown error'}")
809
+ continue
810
+
811
+ data = result.get("data")
812
+ if not isinstance(data, dict):
813
+ lines.append(" Invalid usage response.")
814
+ continue
815
+ items = _usage_items(data)
816
+ lines.append(f" {_usage_summary(data, shown=len(items))}")
817
+ if not items:
818
+ lines.append(" No operations found on this page.")
819
+ continue
820
+
821
+ for index, item in enumerate(items, start=1):
822
+ lines.extend(("", f" Operation {index}"))
823
+ if not isinstance(item, dict):
824
+ lines.append(f" Value: {_usage_value_lines(item)[0]}")
825
+ continue
826
+ ordered_keys = [key for key in _USAGE_FIELD_ORDER if key in item]
827
+ ordered_keys.extend(sorted(str(key) for key in item if key not in ordered_keys))
828
+ for key in ordered_keys:
829
+ value_lines = _usage_value_lines(item[key])
830
+ lines.append(f" {_usage_field_label(key)}: {value_lines[0]}")
831
+ lines.extend(f" {line}" for line in value_lines[1:])
832
+ return lines
833
+
834
+
724
835
  def cmd_usage(args: argparse.Namespace) -> int:
725
836
  api_key = _api_key_or_error()
726
837
  if not api_key:
@@ -733,5 +844,8 @@ def cmd_usage(args: argparse.Namespace) -> int:
733
844
  "success": all(result["success"] for result in surfaces.values()),
734
845
  "surfaces": surfaces,
735
846
  }
736
- _print_json_or_text(payload, as_json=bool(getattr(args, "json", False)))
847
+ if bool(getattr(args, "json", False)):
848
+ print(json.dumps(payload, indent=2, sort_keys=True))
849
+ else:
850
+ print("\n".join(_usage_text_lines(payload)))
737
851
  return 0 if payload["success"] else 1
@@ -1,5 +1,5 @@
1
1
  name: web-tinyfish
2
- version: 0.3.2
2
+ version: 0.3.3
3
3
  description: "Independent TinyFish provider plugin for Hermes Agent. Adds MCP-first Search/Fetch, plain-language tool routing, update awareness, and optional default-deny Browser infrastructure."
4
4
  author: gabeosx
5
5
  kind: backend
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hermes-plugin-tinyfish"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "TinyFish Search, Fetch, plain-language routing, update awareness, and optional Browser for Hermes Agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -753,6 +753,103 @@ def test_usage_calls_search_and_fetch_usage(
753
753
  assert "tf_secret" not in json.dumps(payload)
754
754
 
755
755
 
756
+ def test_usage_text_output_formats_operation_history_for_people(
757
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
758
+ ) -> None:
759
+ monkeypatch.setattr(cli, "_get_env", lambda name: "tf_secret")
760
+ monkeypatch.setattr(
761
+ cli.rest_client,
762
+ "search_usage",
763
+ lambda *, api_key: {
764
+ "items": [
765
+ {
766
+ "id": "search-1",
767
+ "query": "Hermes Agent",
768
+ "status": "completed",
769
+ "results_count": 10,
770
+ "latency_ms": 125,
771
+ "created_at": "2026-08-12T20:00:00Z",
772
+ }
773
+ ],
774
+ "total": 3,
775
+ "limit": 1,
776
+ "page": 1,
777
+ "total_pages": 3,
778
+ "has_more": True,
779
+ },
780
+ )
781
+ monkeypatch.setattr(
782
+ cli.rest_client,
783
+ "fetch_usage",
784
+ lambda *, api_key: {
785
+ "operations": [
786
+ {
787
+ "url": "https://example.com",
788
+ "status": "failed",
789
+ "error": "timeout",
790
+ "created_at": "2026-08-12T20:01:00Z",
791
+ }
792
+ ],
793
+ "total": 1,
794
+ "limit": 100,
795
+ "page": 1,
796
+ "total_pages": 1,
797
+ "has_more": False,
798
+ },
799
+ )
800
+
801
+ result = cli.cmd_usage(argparse.Namespace(json=False))
802
+ output = capsys.readouterr().out
803
+
804
+ assert result == 0
805
+ assert (
806
+ output
807
+ == """TinyFish usage
808
+
809
+ Search
810
+ 1 shown / 3 total | page 1 of 3 | limit 1 | more: yes
811
+
812
+ Operation 1
813
+ Created at: 2026-08-12T20:00:00Z
814
+ Status: completed
815
+ Query: Hermes Agent
816
+ Results count: 10
817
+ Latency (ms): 125
818
+ ID: search-1
819
+
820
+ Fetch
821
+ 1 shown / 1 total | page 1 of 1 | limit 100 | more: no
822
+
823
+ Operation 1
824
+ Created at: 2026-08-12T20:01:00Z
825
+ Status: failed
826
+ URL: https://example.com
827
+ Error: timeout
828
+ """
829
+ )
830
+ assert "{'" not in output
831
+ assert "tf_secret" not in output
832
+
833
+
834
+ def test_usage_text_output_keeps_partial_failure_readable(
835
+ monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
836
+ ) -> None:
837
+ monkeypatch.setattr(cli, "_get_env", lambda name: "tf_secret")
838
+ monkeypatch.setattr(cli.rest_client, "search_usage", lambda *, api_key: {"items": []})
839
+
840
+ def fail_usage(*, api_key: str) -> dict[str, Any]:
841
+ raise RuntimeError("usage unavailable")
842
+
843
+ monkeypatch.setattr(cli.rest_client, "fetch_usage", fail_usage)
844
+
845
+ result = cli.cmd_usage(argparse.Namespace(json=False))
846
+ output = capsys.readouterr().out
847
+
848
+ assert result == 1
849
+ assert "Search\n 0 operations\n No operations found on this page." in output
850
+ assert "Fetch\n Error: usage unavailable" in output
851
+
852
+
756
853
  def test_usage_partial_failure_returns_both_surface_results_and_failure(
757
854
  monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
758
855
  ) -> None: