@tiny-fish/hermes 0.2.3 → 0.2.4

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.
package/README.md CHANGED
@@ -92,6 +92,10 @@ tinyfish:
92
92
 
93
93
  When the `tinyfish` MCP server is also configured in Hermes (`mcp_servers.tinyfish.url: https://agent.tinyfish.ai/mcp`), the plugin injects once-per-context guidance steering the model between the generic web tools and the native MCP tools. Disable with `tinyfish.routing_context: false`.
94
94
 
95
+ ## Telemetry
96
+
97
+ Each `hermes tinyfish setup` run sends one event to TinyFish, whether it succeeds or fails: the outcome (for example `completed` or `key_rejected`), where the key came from (`flag`, `prompt`, `env` or `none`), the plugin version and OS. It sends a one-way hash of the API key, never the key itself, so the event joins your account. Set `TINYFISH_NO_TELEMETRY=1` in your shell or in `~/.hermes/.env` to turn it off.
98
+
95
99
  ## Development
96
100
 
97
101
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiny-fish/hermes",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "TinyFish web provider plugin for Hermes Agent, delivered via npm for the TinyFish CLI. Installed by `tinyfish connect hermes`; not a JavaScript library.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.tinyfish.ai",
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: tinyfish
2
- version: 0.2.3
2
+ version: 0.2.4
3
3
  description: "TinyFish: the web, built into Hermes. Free Search and Fetch power Hermes's own web_search and web_extract, and when a site needs clicks, forms or a login, a remote browser and the tinyfish_agent web agent handle it."
4
4
  author: TinyFish
5
5
  kind: backend
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "tinyfish-hermes"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "TinyFish plugin for Hermes Agent - Search, Fetch, Browser, and the tinyfish_agent web agent"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -2,8 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import hashlib
5
6
  import math
7
+ import os
8
+ import sys
6
9
  import time
10
+ import uuid
7
11
  from typing import Any, NoReturn, cast
8
12
 
9
13
  import httpx
@@ -17,6 +21,7 @@ AGENT_URL = "https://agent.tinyfish.ai"
17
21
  WALLET_URL = f"{AGENT_URL}/v1/wallet"
18
22
  SEARCH_USAGE_URL = f"{SEARCH_URL}/usage"
19
23
  FETCH_USAGE_URL = f"{FETCH_URL}/usage"
24
+ TELEMETRY_URL = f"{AGENT_URL}/api/cli/connect-event"
20
25
 
21
26
  _BROWSER_CLOSE_MAX_ATTEMPTS = 3
22
27
  _BROWSER_CLOSE_RETRYABLE_STATUSES = frozenset({409, 429, 500, 503, 504})
@@ -334,6 +339,36 @@ def start_run(
334
339
  )
335
340
 
336
341
 
342
+ def telemetry_disabled(value: str | None = None) -> bool:
343
+ # Matches the TinyFish CLI: anything but empty, 0 or false opts out.
344
+ if value is None:
345
+ value = os.environ.get("TINYFISH_NO_TELEMETRY", "")
346
+ return value.strip().lower() not in ("", "0", "false")
347
+
348
+
349
+ def report_setup(outcome: str, *, key_source: str, api_key: str = "") -> None:
350
+ """Record a ``hermes tinyfish setup`` outcome; never raises."""
351
+
352
+ if telemetry_disabled():
353
+ return
354
+ headers = {"Content-Type": "application/json"}
355
+ if api_key:
356
+ headers["x-api-key-lookup"] = hashlib.sha256(api_key.encode()).hexdigest()
357
+ body = {
358
+ "event": "harness_plugin_setup",
359
+ "harness": "hermes",
360
+ "plugin_version": __version__,
361
+ "outcome": outcome,
362
+ "key_source": key_source,
363
+ "runtime_platform": sys.platform,
364
+ "setup_id": str(uuid.uuid4()),
365
+ }
366
+ try:
367
+ httpx.post(TELEMETRY_URL, headers=headers, json=body, timeout=2.0)
368
+ except Exception:
369
+ pass
370
+
371
+
337
372
  def get_run(run_id: str, *, api_key: str) -> dict[str, Any]:
338
373
  return _agent_request("GET", f"/v1/runs/{run_id}", api_key=api_key)
339
374
 
@@ -257,20 +257,36 @@ def cmd_setup(
257
257
  provider: TinyFishWebSearchProvider | None = None,
258
258
  ) -> int:
259
259
  api_key = (getattr(args, "api_key", None) or "").strip()
260
+ key_source = "flag" if api_key else "none"
260
261
  existing = _api_key_env_var()
261
262
  if not api_key and existing:
262
263
  print(f"Using the TinyFish API key from {existing}")
264
+ key_source = "env"
263
265
  elif not api_key and sys.stdin.isatty():
264
266
  api_key = _prompt_for_key()
267
+ key_source = "prompt" if api_key else "none"
268
+
269
+ # Catalog installs skip `tinyfish connect`; this is their install signal.
270
+ def report(outcome: str) -> None:
271
+ # Hermes keeps settings in its .env, which never reaches os.environ.
272
+ if rest_client.telemetry_disabled(_get_env("TINYFISH_NO_TELEMETRY")):
273
+ return
274
+ rest_client.report_setup(
275
+ outcome, key_source=key_source, api_key=api_key or _api_key()
276
+ )
277
+
265
278
  if api_key:
266
279
  rejection = _validate_key(api_key)
267
280
  if rejection:
268
281
  print(rejection, file=sys.stderr)
282
+ report("key_rejected")
269
283
  return 1
270
284
  if not _store_api_key(api_key):
285
+ report("save_failed")
271
286
  return 1
272
287
  elif not existing:
273
288
  print(MISSING_KEY_ERROR, file=sys.stderr)
289
+ report("no_key")
274
290
  return 1
275
291
 
276
292
  # Route web tools only once a usable key exists, so a failed setup changes nothing.
@@ -284,8 +300,10 @@ def cmd_setup(
284
300
  _save_config(config)
285
301
  print("Configured Hermes web backends to use TinyFish")
286
302
  if not _enable_toolset("web"):
303
+ report("backend_failed")
287
304
  return 1
288
305
 
306
+ report("completed")
289
307
  if getattr(args, "live", False):
290
308
  doctor_args = argparse.Namespace(json=False, live=True, live_paid=False)
291
309
  return cmd_doctor(doctor_args, provider=provider)