hud-python 0.4.57__py3-none-any.whl → 0.4.58__py3-none-any.whl

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.

Potentially problematic release.


This version of hud-python might be problematic. Click here for more details.

hud/cli/__init__.py CHANGED
@@ -253,10 +253,23 @@ def debug(
253
253
  else:
254
254
  # Assume it's an image name
255
255
  image = first_param
256
- from .utils.docker import build_run_command
256
+ from .utils.docker import create_docker_run_command
257
+
258
+ # For image mode, check if there's a .env file in current directory
259
+ # and use it if available (similar to hud dev behavior)
260
+ cwd = Path.cwd()
261
+ if (cwd / ".env").exists():
262
+ # Use create_docker_run_command to load .env from current directory
263
+ command = create_docker_run_command(
264
+ image,
265
+ docker_args=docker_args,
266
+ env_dir=cwd, # Load .env from current directory
267
+ )
268
+ else:
269
+ # No .env file, use basic command without env loading
270
+ from .utils.docker import build_run_command
257
271
 
258
- # Image-only mode: do not auto-inject local .env
259
- command = build_run_command(image, docker_args)
272
+ command = build_run_command(image, docker_args)
260
273
  else:
261
274
  console.print(
262
275
  "[red]Error: Must specify a directory, Docker image, --config, or --cursor[/red]"
@@ -741,14 +754,14 @@ def remove(
741
754
 
742
755
  @app.command()
743
756
  def init(
744
- name: str = typer.Argument(None, help="Environment name (default: current directory name)"),
757
+ name: str = typer.Argument(None, help="Environment name (default: chosen preset name)"),
745
758
  preset: str | None = typer.Option(
746
759
  None,
747
760
  "--preset",
748
761
  "-p",
749
762
  help="Preset to use: blank, deep-research, browser, rubrics. If omitted, you'll choose interactively.", # noqa: E501
750
763
  ),
751
- directory: str = typer.Option(".", "--dir", "-d", help="Target directory"),
764
+ directory: str = typer.Option(".", "--dir", "-d", help="Parent directory for the environment"),
752
765
  force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing files"),
753
766
  ) -> None:
754
767
  """🚀 Initialize a new HUD environment with minimal boilerplate.
@@ -760,8 +773,8 @@ def init(
760
773
  - Required setup/evaluate tools
761
774
 
762
775
  Examples:
763
- hud init # Use current directory name
764
- hud init my-env # Create in ./my-env/
776
+ hud init # Choose preset interactively, create ./preset-name/
777
+ hud init my-env # Create new directory ./my-env/
765
778
  hud init my-env --dir /tmp # Create in /tmp/my-env/
766
779
  """
767
780
  create_environment(name, directory, force, preset)
hud/cli/dev.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import asyncio
6
+ import contextlib
6
7
  import importlib
7
8
  import importlib.util
8
9
  import logging
@@ -13,6 +14,8 @@ import threading
13
14
  from pathlib import Path
14
15
  from typing import Any
15
16
 
17
+ import typer
18
+
16
19
  from hud.utils.hud_console import HUDConsole
17
20
 
18
21
  hud_console = HUDConsole()
@@ -26,6 +29,7 @@ def show_dev_server_info(
26
29
  interactive: bool,
27
30
  env_dir: Path | None = None,
28
31
  new: bool = False,
32
+ docker_mode: bool = False,
29
33
  ) -> str:
30
34
  """Show consistent server info for both Python and Docker modes.
31
35
 
@@ -54,7 +58,15 @@ def show_dev_server_info(
54
58
  if transport == "http":
55
59
  hud_console.section_title("Quick Links")
56
60
  hud_console.info(f"{hud_console.sym.ITEM} Docs: http://localhost:{port}/docs")
57
- hud_console.info(f"{hud_console.sym.ITEM} Cursor: {cursor_deeplink}")
61
+ hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
62
+ # Display the Cursor link on its own line to prevent wrapping
63
+ hud_console.link(cursor_deeplink)
64
+
65
+ # Show eval endpoint if in Docker mode
66
+ if docker_mode:
67
+ hud_console.info(
68
+ f"{hud_console.sym.ITEM} Eval API: http://localhost:{port}/eval (POST)"
69
+ )
58
70
 
59
71
  # Check for VNC (browser environment)
60
72
  if env_dir and (env_dir / "environment" / "server.py").exists():
@@ -510,6 +522,9 @@ def run_docker_dev_server(
510
522
  new: bool = False,
511
523
  ) -> None:
512
524
  """Run MCP server in Docker with volume mounts, expose via local HTTP proxy."""
525
+ import atexit
526
+ import signal
527
+
513
528
  import typer
514
529
  import yaml
515
530
 
@@ -522,6 +537,69 @@ def run_docker_dev_server(
522
537
 
523
538
  cwd = Path.cwd()
524
539
 
540
+ # Container name will be set later and used for cleanup
541
+ container_name: str | None = None
542
+ cleanup_done = False
543
+
544
+ def cleanup_container() -> None:
545
+ """Clean up Docker container on exit."""
546
+ nonlocal cleanup_done
547
+ if cleanup_done or not container_name:
548
+ return
549
+
550
+ cleanup_done = True
551
+ hud_console.debug(f"Cleaning up container: {container_name}")
552
+
553
+ # Check if container is still running
554
+ try:
555
+ result = subprocess.run( # noqa: S603
556
+ ["docker", "ps", "-q", "-f", f"name={container_name}"], # noqa: S607
557
+ stdout=subprocess.PIPE,
558
+ stderr=subprocess.DEVNULL,
559
+ text=True,
560
+ timeout=5,
561
+ )
562
+ if not result.stdout.strip():
563
+ # Container is not running, just try to remove it
564
+ subprocess.run( # noqa: S603
565
+ ["docker", "rm", "-f", container_name], # noqa: S607
566
+ stdout=subprocess.DEVNULL,
567
+ stderr=subprocess.DEVNULL,
568
+ timeout=5,
569
+ )
570
+ return
571
+ except Exception: # noqa: S110
572
+ pass
573
+
574
+ try:
575
+ # First try to stop gracefully
576
+ subprocess.run( # noqa: S603
577
+ ["docker", "stop", container_name], # noqa: S607
578
+ stdout=subprocess.DEVNULL,
579
+ stderr=subprocess.DEVNULL,
580
+ timeout=10,
581
+ )
582
+ hud_console.debug(f"Container {container_name} stopped successfully")
583
+ except subprocess.TimeoutExpired:
584
+ # Force kill if stop times out
585
+ hud_console.debug(f"Container {container_name} stop timeout, forcing kill")
586
+ with contextlib.suppress(Exception):
587
+ subprocess.run( # noqa: S603
588
+ ["docker", "kill", container_name], # noqa: S607
589
+ stdout=subprocess.DEVNULL,
590
+ stderr=subprocess.DEVNULL,
591
+ timeout=5,
592
+ )
593
+
594
+ # Set up signal handlers for cleanup
595
+ def signal_handler(signum: int, frame: Any) -> None:
596
+ cleanup_container()
597
+ sys.exit(0)
598
+
599
+ signal.signal(signal.SIGTERM, signal_handler)
600
+ if sys.platform != "win32":
601
+ signal.signal(signal.SIGHUP, signal_handler)
602
+
525
603
  # Find environment directory (current or parent with hud.lock.yaml)
526
604
  env_dir = cwd
527
605
  lock_path = env_dir / "hud.lock.yaml"
@@ -562,10 +640,14 @@ def run_docker_dev_server(
562
640
  base_name = image_name.replace(":", "-").replace("/", "-")
563
641
  container_name = f"{base_name}-dev-{pid}"
564
642
 
643
+ # Register cleanup function with atexit
644
+ atexit.register(cleanup_container)
645
+
565
646
  # Build docker run command with volume mounts and folder-mode envs
566
647
  from .utils.docker import create_docker_run_command
567
648
 
568
649
  base_args = [
650
+ "--rm", # Automatically remove container when it stops
569
651
  "--name",
570
652
  container_name,
571
653
  "-v",
@@ -643,6 +725,7 @@ def run_docker_dev_server(
643
725
  interactive=interactive,
644
726
  env_dir=env_dir,
645
727
  new=new,
728
+ docker_mode=True,
646
729
  )
647
730
  hud_console.dim_info(
648
731
  "",
@@ -679,6 +762,11 @@ def run_docker_dev_server(
679
762
  os.environ["_HUD_DEV_DOCKER_CONTAINER"] = container_name
680
763
  hud_console.debug(f"Docker container: {container_name}")
681
764
 
765
+ # Store the docker mcp_config for the eval endpoint
766
+ import json
767
+
768
+ os.environ["_HUD_DEV_DOCKER_MCP_CONFIG"] = json.dumps(mcp_config)
769
+
682
770
  # Create FastMCP proxy using the ProxyClient
683
771
  fastmcp_proxy = FastMCP.as_proxy(proxy_client)
684
772
 
@@ -713,7 +801,15 @@ def run_docker_dev_server(
713
801
  asyncio.run(run_proxy())
714
802
  except KeyboardInterrupt:
715
803
  hud_console.info("\n\nStopping...")
804
+ cleanup_container()
716
805
  raise typer.Exit(0) from None
806
+ except Exception:
807
+ # Ensure cleanup happens on any exception
808
+ cleanup_container()
809
+ raise
810
+ finally:
811
+ # Final cleanup attempt
812
+ cleanup_container()
717
813
 
718
814
 
719
815
  def run_mcp_dev_server(
@@ -732,6 +828,20 @@ def run_mcp_dev_server(
732
828
  docker_args = docker_args or []
733
829
  cwd = Path.cwd()
734
830
 
831
+ # Find an available port if not using stdio transport
832
+ if not stdio:
833
+ from hud.cli.utils.logging import find_free_port
834
+
835
+ actual_port = find_free_port(port)
836
+ if actual_port is None:
837
+ hud_console.error(f"No available ports found starting from {port}")
838
+ raise typer.Exit(1)
839
+
840
+ if actual_port != port:
841
+ hud_console.info(f"Port {port} is in use, using port {actual_port} instead")
842
+
843
+ port = actual_port
844
+
735
845
  # Auto-detect Docker mode if Dockerfile present and no module specified
736
846
  if not docker and module is None and should_use_docker_mode(cwd):
737
847
  hud_console.note("Detected Dockerfile - using Docker mode with volume mounts")
hud/cli/eval.py CHANGED
@@ -53,7 +53,7 @@ def get_available_models() -> list[dict[str, str | None]]:
53
53
  try:
54
54
  from hud.cli.rl import rl_api
55
55
 
56
- hud_console.info("Fetching your models from https://hud.so/models")
56
+ hud_console.info("Fetching your models from https://hud.ai/models")
57
57
  models = rl_api.list_models()
58
58
 
59
59
  # Filter for ready models only and sort by recency
@@ -771,7 +771,7 @@ def eval_command(
771
771
  # Check for HUD_API_KEY if using HUD services
772
772
  if not settings.api_key:
773
773
  hud_console.warning("HUD_API_KEY not set. Some features may be limited.")
774
- hud_console.info("Get your API key at: https://hud.so")
774
+ hud_console.info("Get your API key at: https://hud.ai")
775
775
  hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
776
776
 
777
777
  # Parse allowed tools
hud/cli/flows/dev.py CHANGED
@@ -47,11 +47,11 @@ async def create_dynamic_trace(
47
47
 
48
48
  try:
49
49
  resp = await make_request("POST", url=url, json=payload, api_key=api_key)
50
- # New API returns an id; construct the URL as https://hud.so/trace/{id}
50
+ # New API returns an id; construct the URL as https://hud.ai/trace/{id}
51
51
  trace_id = resp.get("id")
52
52
 
53
53
  if isinstance(trace_id, str) and trace_id:
54
- return trace_id, f"https://hud.so/trace/{trace_id}"
54
+ return trace_id, f"https://hud.ai/trace/{trace_id}"
55
55
  return None, None
56
56
  except Exception as e:
57
57
  # Do not interrupt dev flow
@@ -114,7 +114,9 @@ def show_dev_ui(
114
114
  label = "Base image" if is_docker else "Server"
115
115
  hud_console.info("")
116
116
  hud_console.info(f"{hud_console.sym.ITEM} {label}: {server_name}")
117
- hud_console.info(f"{hud_console.sym.ITEM} Cursor: {cursor_deeplink}")
117
+ hud_console.info(f"{hud_console.sym.ITEM} Cursor:")
118
+ # Display the Cursor link on its own line to prevent wrapping
119
+ hud_console.link(cursor_deeplink)
118
120
  hud_console.info("")
119
121
  hud_console.info(f"{hud_console.sym.SUCCESS} Hot-reload enabled")
120
122
  if is_docker:
hud/cli/init.py CHANGED
@@ -182,17 +182,17 @@ def create_environment(
182
182
 
183
183
  hud_console = HUDConsole()
184
184
 
185
- # Determine environment name/target directory
186
- if name is None:
187
- current_dir = Path.cwd()
188
- name = current_dir.name
189
- target_dir = current_dir
190
- hud_console.info(f"Using current directory name: {name}")
191
- else:
192
- target_dir = Path(directory) / name
193
-
194
185
  # Choose preset
195
186
  preset_normalized = (preset or "").strip().lower() if preset else _prompt_for_preset()
187
+
188
+ # If no name is provided, use the preset name as the environment name
189
+ if name is None:
190
+ name = preset_normalized
191
+ hud_console.info(f"Using preset name as environment name: {name}")
192
+
193
+ # Always create a new directory based on the name
194
+ target_dir = Path.cwd() / name if directory == "." else Path(directory) / name
195
+
196
196
  if preset_normalized not in PRESET_MAP:
197
197
  hud_console.warning(
198
198
  f"Unknown preset '{preset_normalized}', defaulting to 'blank' "
@@ -263,14 +263,10 @@ def create_environment(
263
263
  hud_console.status_item(entry, "added")
264
264
 
265
265
  hud_console.section_title("Next steps")
266
- if target_dir == Path.cwd():
267
- hud_console.info("1. Start development server (with MCP inspector):")
268
- hud_console.command_example("hud dev --inspector")
269
- else:
270
- hud_console.info("1. Enter the directory:")
271
- hud_console.command_example(f"cd {target_dir}")
272
- hud_console.info("\n2. Start development server (with MCP inspector):")
273
- hud_console.command_example("hud dev --inspector")
274
-
266
+ # Since we now almost always create a new directory, show cd command
267
+ hud_console.info("1. Enter the directory:")
268
+ hud_console.command_example(f"cd {target_dir.name}")
269
+ hud_console.info("\n2. Start development server (with MCP inspector):")
270
+ hud_console.command_example("hud dev --inspector")
275
271
  hud_console.info("\n3. Review the README in this preset for specific instructions.")
276
272
  hud_console.info("\n4. Customize as needed.")
hud/cli/push.py CHANGED
@@ -152,7 +152,7 @@ def push_environment(
152
152
  hud_console.error("No HUD API key found")
153
153
  hud_console.warning("A HUD API key is required to push environments.")
154
154
  hud_console.info("\nTo get started:")
155
- hud_console.info("1. Get your API key at: https://hud.so/settings")
155
+ hud_console.info("1. Get your API key at: https://hud.ai/settings")
156
156
  hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
157
157
  hud_console.command_example("hud push", "Try again")
158
158
  hud_console.info("")
@@ -440,7 +440,7 @@ def push_environment(
440
440
  elif response.status_code == 401:
441
441
  hud_console.error("Authentication failed")
442
442
  hud_console.info("Check your HUD_API_KEY is valid")
443
- hud_console.info("Get a new key at: https://hud.so/settings")
443
+ hud_console.info("Get a new key at: https://hud.ai/settings")
444
444
  hud_console.info("Set it in your environment or run: hud set HUD_API_KEY=your-key-here")
445
445
  elif response.status_code == 403:
446
446
  hud_console.error("Permission denied")
hud/cli/rl/__init__.py CHANGED
@@ -25,7 +25,7 @@ def rl_command(
25
25
  ),
26
26
  model: str | None = typer.Argument(
27
27
  None,
28
- help="Model to train from https://hud.so/models (default: interactive selection)",
28
+ help="Model to train from https://hud.ai/models (default: interactive selection)",
29
29
  ),
30
30
  config_file: Path | None = typer.Option( # noqa: B008
31
31
  None,
hud/cli/rl/celebrate.py CHANGED
@@ -133,7 +133,7 @@ def show_confetti(console: Console, seconds: float = 2.5) -> None:
133
133
  """
134
134
  # Show celebratory message first
135
135
  console.print(
136
- "[bold green]🎉 Starting training! See your model on https://hud.so/models[/bold green]"
136
+ "[bold green]🎉 Starting training! See your model on https://hud.ai/models[/bold green]"
137
137
  )
138
138
  time.sleep(0.3) # Brief pause to see the message
139
139
 
@@ -55,7 +55,7 @@ def ensure_vllm_deployed(
55
55
  hud_console.info("Waiting for vLLM server to be ready...")
56
56
  start_time = time.time()
57
57
  with hud_console.progress() as progress:
58
- progress.update("Checking deployment status (see live status on https://hud.so/models)")
58
+ progress.update("Checking deployment status (see live status on https://hud.ai/models)")
59
59
  while True:
60
60
  if time.time() - start_time > timeout:
61
61
  hud_console.error("Timeout waiting for vLLM deployment")
@@ -139,7 +139,7 @@ def run_remote_training(
139
139
  hud_console.section_title("Model Selection")
140
140
 
141
141
  # Fetch existing models
142
- hud_console.info("Fetching your models from https://hud.so/models")
142
+ hud_console.info("Fetching your models from https://hud.ai/models")
143
143
 
144
144
  try:
145
145
  models = rl_api.list_models()
@@ -312,7 +312,7 @@ def run_remote_training(
312
312
  # gpu_table.add_column("Price/hr", style="yellow")
313
313
 
314
314
  # for gpu, info in GPU_PRICING.items():
315
- # gpu_table.add_row(gpu, info["memory"], "see pricing on hud.so")
315
+ # gpu_table.add_row(gpu, info["memory"], "see pricing on hud.ai")
316
316
 
317
317
  # console.print(gpu_table)
318
318
 
hud/clients/base.py CHANGED
@@ -140,7 +140,7 @@ class BaseHUDClient(AgentMCPClient):
140
140
  raise HudAuthenticationError(
141
141
  f'Sending authorization "{headers.get("Authorization", "")}", which may'
142
142
  " be incomplete. Ensure HUD_API_KEY environment variable is set or send it"
143
- " as a header. You can get an API key at https://hud.so"
143
+ " as a header. You can get an API key at https://hud.ai"
144
144
  )
145
145
  # Subclasses implement connection
146
146
  await self._connect(self._mcp_config)
hud/clients/fastmcp.py CHANGED
@@ -95,7 +95,7 @@ class FastMCPHUDClient(BaseHUDClient):
95
95
  raise RuntimeError(
96
96
  "Authentication failed for HUD API. "
97
97
  "Please ensure your HUD_API_KEY environment variable is set correctly." # noqa: E501
98
- "You can get an API key at https://hud.so"
98
+ "You can get an API key at https://hud.ai"
99
99
  ) from e
100
100
  # Generic 401 error
101
101
  raise RuntimeError(
hud/otel/config.py CHANGED
@@ -113,7 +113,7 @@ def configure_telemetry(
113
113
  # Error if no exporters are configured
114
114
  raise ValueError(
115
115
  "No telemetry backend configured. Either:\n"
116
- "1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.so)\n"
116
+ "1. Set HUD_API_KEY environment variable for HUD telemetry (https://hud.ai)\n"
117
117
  "2. Use enable_otlp=True with configure_telemetry() for alternative backends (e.g., Jaeger)\n" # noqa: E501
118
118
  )
119
119
  elif not settings.telemetry_enabled:
hud/otel/context.py CHANGED
@@ -408,7 +408,7 @@ def _print_trace_url(task_run_id: str) -> None:
408
408
  if not (settings.telemetry_enabled and settings.api_key):
409
409
  return
410
410
 
411
- url = f"https://hud.so/trace/{task_run_id}"
411
+ url = f"https://hud.ai/trace/{task_run_id}"
412
412
  header = "🚀 See your agent live at:"
413
413
 
414
414
  # ANSI color codes
@@ -447,7 +447,7 @@ def _print_trace_complete_url(task_run_id: str, error_occurred: bool = False) ->
447
447
  if not (settings.telemetry_enabled and settings.api_key):
448
448
  return
449
449
 
450
- url = f"https://hud.so/trace/{task_run_id}"
450
+ url = f"https://hud.ai/trace/{task_run_id}"
451
451
 
452
452
  # ANSI color codes
453
453
  GREEN = "\033[92m"
hud/server/server.py CHANGED
@@ -16,7 +16,9 @@ from fastmcp.server.server import FastMCP, Transport
16
16
  from starlette.requests import Request
17
17
  from starlette.responses import JSONResponse, Response
18
18
 
19
+ from hud.cli.eval import run_full_dataset
19
20
  from hud.server.low_level import LowLevelServerWithInit
21
+ from hud.types import Task
20
22
 
21
23
  if TYPE_CHECKING:
22
24
  from collections.abc import AsyncGenerator, Callable
@@ -486,6 +488,84 @@ class MCPServer(FastMCP):
486
488
  self._prompt_manager._prompts[new_key] = prompt
487
489
  # await self.import_server(hidden_router, prefix=None, **kwargs)
488
490
 
491
+ def _get_docker_logs(
492
+ self,
493
+ tail: int = 100,
494
+ since: str | None = None,
495
+ until: str | None = None,
496
+ timestamps: bool = False,
497
+ ) -> dict[str, Any]:
498
+ """Helper function to get Docker container logs.
499
+
500
+ Args:
501
+ tail: Number of lines to show from the end of the logs
502
+ since: Show logs since timestamp or relative time
503
+ until: Show logs before a timestamp or relative time
504
+ timestamps: Show timestamps in log output
505
+
506
+ Returns:
507
+ Dictionary with logs data or error information
508
+ """
509
+ import subprocess
510
+
511
+ container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
512
+ if not container_name:
513
+ return {"items": [], "container_name": None, "error": "No container name found"}
514
+
515
+ # Build docker logs command
516
+ cmd = ["docker", "logs", "--tail", str(tail)]
517
+
518
+ if since:
519
+ cmd.extend(["--since", since])
520
+ if until:
521
+ cmd.extend(["--until", until])
522
+ if timestamps:
523
+ cmd.append("--timestamps")
524
+
525
+ cmd.append(container_name)
526
+
527
+ try:
528
+ # Run docker logs to get output
529
+ result = subprocess.run( # noqa: S603
530
+ cmd,
531
+ stdout=subprocess.PIPE,
532
+ stderr=subprocess.STDOUT,
533
+ text=True,
534
+ encoding="utf-8",
535
+ errors="replace",
536
+ timeout=5,
537
+ )
538
+
539
+ # Parse logs into items
540
+ items = []
541
+ lines = result.stdout.strip().split("\n") if result.stdout else []
542
+
543
+ for i, line in enumerate(lines):
544
+ if line.strip():
545
+ items.append(
546
+ {
547
+ "id": i,
548
+ "stream": "mixed",
549
+ "log": line,
550
+ "container_name": container_name,
551
+ }
552
+ )
553
+
554
+ return {
555
+ "items": items,
556
+ "container_name": container_name,
557
+ "total_lines": len(items),
558
+ }
559
+
560
+ except subprocess.TimeoutExpired:
561
+ return {"error": "Docker logs timeout", "container_name": container_name, "items": []}
562
+ except Exception as e:
563
+ return {
564
+ "error": f"Failed to get logs: {e!s}",
565
+ "container_name": container_name,
566
+ "items": [],
567
+ }
568
+
489
569
  def _register_hud_helpers(self) -> None:
490
570
  """Register development helper endpoints.
491
571
 
@@ -494,6 +574,7 @@ class MCPServer(FastMCP):
494
574
  - POST /api/tools/{name} - REST wrappers for MCP tools
495
575
  - GET /openapi.json - OpenAPI spec for REST endpoints
496
576
  - GET /logs - Development log endpoint (when provided by dev runtime)
577
+ - hud-logs tool - MCP tool for fetching logs (when in Docker mode)
497
578
  """
498
579
 
499
580
  # Register REST wrapper for each tool
@@ -544,7 +625,7 @@ class MCPServer(FastMCP):
544
625
  endpoint = create_tool_endpoint(tool_key)
545
626
  self.custom_route(f"/api/tools/{tool_key}", methods=["POST"])(endpoint)
546
627
 
547
- # Development log endpoint - only if dev runtime set a provider
628
+ # Development endpoints - only if dev runtime set a provider
548
629
  provider = os.environ.get("_HUD_DEV_LOGS_PROVIDER")
549
630
  if provider == "enabled":
550
631
 
@@ -556,50 +637,182 @@ class MCPServer(FastMCP):
556
637
  - limit: max number of lines to return (default 100)
557
638
  - tail: number of lines from end to return (default 100)
558
639
  """
559
- import subprocess
560
-
561
- # Get container name from environment
562
- container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
563
- if not container_name:
564
- return JSONResponse({"items": [], "next": None})
565
-
566
640
  # Get query params
567
641
  params = request.query_params
568
- tail = params.get("tail", "100")
642
+ tail = int(params.get("tail", "100"))
643
+
644
+ # Use helper function to get logs
645
+ result = self._get_docker_logs(tail=tail)
646
+
647
+ # Add 'next' field for compatibility with existing API
648
+ if "error" in result:
649
+ return JSONResponse(result, status_code=500)
650
+ else:
651
+ items = result.get("items", [])
652
+ return JSONResponse(
653
+ {
654
+ "items": items,
655
+ "next": len(items) - 1 if items else None,
656
+ }
657
+ )
658
+
659
+ # Import existing types from the codebase
660
+ from pydantic import BaseModel
661
+
662
+ from hud.types import AgentType
663
+
664
+ class EvalRequest(BaseModel):
665
+ """Request model for /eval endpoint."""
666
+
667
+ tasks: list[dict[str, Any]] = []
668
+ agent: str = "claude"
669
+ model: str | None = None
670
+ max_steps: int = 10
671
+ verbose: bool = False
672
+ group_size: int = 1
673
+ name: str | None = None
674
+
675
+ @self.custom_route("/eval", methods=["POST"])
676
+ async def run_eval(request: Request) -> Response:
677
+ """Run evaluation on tasks using the current Docker environment."""
678
+ import asyncio
679
+ import json
569
680
 
570
681
  try:
571
- # Run docker logs to get recent output
572
- result = subprocess.run( # noqa: S603, ASYNC221
573
- ["docker", "logs", "--tail", tail, container_name], # noqa: S607
574
- stdout=subprocess.PIPE,
575
- stderr=subprocess.STDOUT,
576
- text=True,
577
- encoding="utf-8",
578
- errors="replace",
579
- timeout=5,
580
- )
682
+ body = await request.body()
683
+ data = json.loads(body)
684
+
685
+ # Validate request using Pydantic model
686
+ try:
687
+ eval_request = EvalRequest(**data)
688
+ except Exception as e:
689
+ return JSONResponse({"error": f"Invalid request: {e!s}"}, status_code=400)
690
+
691
+ # Get the Docker MCP config from environment
692
+ docker_mcp_config = os.environ.get("_HUD_DEV_DOCKER_MCP_CONFIG")
693
+ if not docker_mcp_config:
694
+ return JSONResponse(
695
+ {"error": "Docker MCP config not available"}, status_code=500
696
+ )
581
697
 
582
- # Parse logs into items
583
- items = []
584
- lines = result.stdout.strip().split("\n") if result.stdout else []
585
-
586
- for i, line in enumerate(lines):
587
- if line.strip():
588
- items.append(
589
- {
590
- "id": i,
591
- "stream": "mixed",
592
- "log": line,
593
- "container_name": container_name,
594
- }
698
+ docker_config = json.loads(docker_mcp_config)
699
+
700
+ # Simplify Docker config for evaluation
701
+ if "docker" in docker_config and "args" in docker_config["docker"]:
702
+ original_args = docker_config["docker"]["args"]
703
+ filtered_args = []
704
+ i = 0
705
+
706
+ while i < len(original_args):
707
+ arg = original_args[i]
708
+
709
+ # Skip volume mounts and their values
710
+ if arg in ["-v", "--volume"]:
711
+ i += 2 # Skip the flag and its value
712
+ continue
713
+
714
+ # Skip combined volume mount args
715
+ if arg.startswith(("-v", "--volume=")):
716
+ i += 1
717
+ continue
718
+
719
+ # Skip explicit container name to avoid collisions
720
+ if arg == "--name" and i + 1 < len(original_args):
721
+ i += 2 # Skip the --name and its value
722
+ continue
723
+
724
+ # Skip dev-specific environment variables
725
+ if arg == "-e" and i + 1 < len(original_args):
726
+ next_arg = original_args[i + 1]
727
+ if next_arg in [
728
+ "PYTHONPATH=/app",
729
+ "HUD_DEV=1",
730
+ "PYTHONUNBUFFERED=1",
731
+ ]:
732
+ i += 2 # Skip the -e and its value
733
+ continue
734
+
735
+ filtered_args.append(arg)
736
+ i += 1
737
+
738
+ # Update the docker args with filtered version
739
+ docker_config["docker"]["args"] = filtered_args
740
+
741
+ try:
742
+ agent_type = AgentType(eval_request.agent.lower())
743
+ except ValueError:
744
+ valid_agents = [
745
+ a.value for a in AgentType if a != AgentType.INTEGRATION_TEST
746
+ ]
747
+ return JSONResponse(
748
+ {
749
+ "error": f"Invalid agent type: {eval_request.agent}",
750
+ "valid_agents": valid_agents,
751
+ },
752
+ status_code=400,
753
+ )
754
+
755
+ # Add MCP config to each task and validate basic structure
756
+ tasks = []
757
+ for task_data in eval_request.tasks:
758
+ task_data["mcp_config"] = docker_config
759
+ tasks.append(Task.model_validate(task_data).model_dump())
760
+
761
+ # Save tasks to temporary file
762
+ import tempfile
763
+
764
+ with tempfile.NamedTemporaryFile(
765
+ mode="w", prefix="hud-eval-", suffix=".json", delete=False
766
+ ) as f:
767
+ json.dump(tasks, f)
768
+ task_file = f.name
769
+
770
+ # Fire and forget - launch evaluation in background
771
+ async def run_eval_background() -> None:
772
+ try:
773
+ await run_full_dataset(
774
+ task_file,
775
+ agent_type=agent_type,
776
+ model=eval_request.model,
777
+ max_steps=eval_request.max_steps,
778
+ verbose=eval_request.verbose,
779
+ group_size=eval_request.group_size,
595
780
  )
781
+ except Exception as e:
782
+ raise e
783
+ finally:
784
+ # Clean up temp file
785
+ import os
786
+
787
+ if os.path.exists(task_file):
788
+ os.unlink(task_file)
789
+
790
+ # Start the evaluation in the background (fire and forget)
791
+ asyncio.create_task(run_eval_background()) # noqa: RUF006
792
+
793
+ # Return immediately
794
+ response_data = {
795
+ "status": "started",
796
+ "message": f"Evaluation launched with {len(tasks)} task(s)",
797
+ "agent": eval_request.agent,
798
+ "model": eval_request.model,
799
+ "max_steps": eval_request.max_steps,
800
+ "verbose": eval_request.verbose,
801
+ }
802
+
803
+ # Include group_size if > 1
804
+ if eval_request.group_size > 1:
805
+ response_data["group_size"] = eval_request.group_size
806
+ response_data["total_episodes"] = len(tasks) * eval_request.group_size
596
807
 
597
- return JSONResponse({"items": items, "next": len(items) - 1 if items else None})
808
+ return JSONResponse(response_data)
598
809
 
599
- except subprocess.TimeoutExpired:
600
- return JSONResponse({"error": "Docker logs timeout"}, status_code=500)
810
+ except json.JSONDecodeError:
811
+ return JSONResponse({"error": "Invalid JSON in request body"}, status_code=400)
601
812
  except Exception as e:
602
- return JSONResponse({"error": f"Failed to get logs: {e!s}"}, status_code=500)
813
+ return JSONResponse(
814
+ {"error": f"Failed to run evaluation: {e!s}"}, status_code=500
815
+ )
603
816
 
604
817
  @self.custom_route("/openapi.json", methods=["GET"])
605
818
  async def openapi_spec(request: Request) -> Response:
@@ -656,6 +869,40 @@ class MCPServer(FastMCP):
656
869
 
657
870
  return JSONResponse(spec)
658
871
 
872
+ # Register hud-logs tool when in Docker dev mode
873
+ container_name = os.environ.get("_HUD_DEV_DOCKER_CONTAINER")
874
+ if container_name:
875
+
876
+ @self.tool("hud-logs")
877
+ async def get_docker_logs(
878
+ tail: int = 100,
879
+ since: str | None = None,
880
+ until: str | None = None,
881
+ timestamps: bool = False,
882
+ ) -> dict[str, Any]:
883
+ """Get logs from the Docker container running the HUD environment.
884
+
885
+ Args:
886
+ tail: Number of lines to show from the end of the logs (default: 100)
887
+ since: Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (42m)
888
+ until: Show logs before timestamp (e.g. 2013-01-02T13:23:37Z) or relative (42m)
889
+ timestamps: Show timestamps in log output
890
+
891
+ Returns:
892
+ Dictionary with:
893
+ - items: List of log entries
894
+ - container_name: Name of the container
895
+ - total_lines: Total number of log lines returned
896
+ - error: Error message if logs could not be retrieved
897
+ """
898
+ # Use helper function to get logs
899
+ return self._get_docker_logs(
900
+ tail=tail,
901
+ since=since,
902
+ until=until,
903
+ timestamps=timestamps,
904
+ )
905
+
659
906
  @self.custom_route("/docs", methods=["GET"])
660
907
  async def docs_page(request: Request) -> Response:
661
908
  """Interactive documentation page."""
hud/shared/hints.py CHANGED
@@ -38,7 +38,7 @@ HUD_API_KEY_MISSING = Hint(
38
38
  message="Missing or invalid HUD_API_KEY.",
39
39
  tips=[
40
40
  "Set HUD_API_KEY in your environment or run: hud set HUD_API_KEY=your-key-here",
41
- "Get a key at https://hud.so",
41
+ "Get a key at https://hud.ai",
42
42
  "Check for whitespace or truncation",
43
43
  ],
44
44
  docs_url=None,
@@ -68,7 +68,7 @@ PRO_PLAN_REQUIRED = Hint(
68
68
  tips=[
69
69
  "Upgrade your plan to continue",
70
70
  ],
71
- docs_url="https://hud.so/project/billing",
71
+ docs_url="https://hud.ai/project/billing",
72
72
  command_examples=None,
73
73
  code="PRO_PLAN_REQUIRED",
74
74
  context=["billing", "plan"],
@@ -80,7 +80,7 @@ CREDITS_EXHAUSTED = Hint(
80
80
  tips=[
81
81
  "Top up credits or upgrade your plan",
82
82
  ],
83
- docs_url="https://hud.so/project/billing",
83
+ docs_url="https://hud.ai/project/billing",
84
84
  command_examples=None,
85
85
  code="CREDITS_EXHAUSTED",
86
86
  context=["billing", "credits"],
hud/telemetry/job.py CHANGED
@@ -170,7 +170,7 @@ def _print_job_url(job_id: str, job_name: str) -> None:
170
170
  if not (settings.telemetry_enabled and settings.api_key):
171
171
  return
172
172
 
173
- url = f"https://hud.so/jobs/{job_id}"
173
+ url = f"https://hud.ai/jobs/{job_id}"
174
174
  header = f"🚀 Job '{job_name}' started:"
175
175
 
176
176
  # ANSI color codes
@@ -209,7 +209,7 @@ def _print_job_complete_url(job_id: str, job_name: str, error_occurred: bool = F
209
209
  if not (settings.telemetry_enabled and settings.api_key):
210
210
  return
211
211
 
212
- url = f"https://hud.so/jobs/{job_id}"
212
+ url = f"https://hud.ai/jobs/{job_id}"
213
213
 
214
214
  # ANSI color codes
215
215
  GREEN = "\033[92m"
hud/tools/playwright.py CHANGED
@@ -225,7 +225,14 @@ class PlaywrightTool(BaseTool):
225
225
  if self._browser_context is None:
226
226
  raise RuntimeError("Browser context failed to initialize")
227
227
 
228
- self.page = await self._browser_context.new_page()
228
+ # Reuse existing page if available (for CDP connections), otherwise create new one
229
+ pages = self._browser_context.pages
230
+ if pages:
231
+ self.page = pages[0]
232
+ logger.info("Reusing existing browser page")
233
+ else:
234
+ self.page = await self._browser_context.new_page()
235
+ logger.info("Created new browser page")
229
236
  logger.info("Playwright browser launched successfully")
230
237
 
231
238
  async def navigate(
hud/types.py CHANGED
@@ -230,7 +230,7 @@ class AgentResponse(BaseModel):
230
230
  tool_calls: list[MCPToolCall] = Field(default_factory=list)
231
231
  done: bool = Field(default=False)
232
232
 
233
- # --- TELEMETRY [hud.so] ---
233
+ # --- TELEMETRY [hud.ai] ---
234
234
  # Responses
235
235
  content: str | None = Field(default=None)
236
236
  reasoning: str | None = Field(default=None)
@@ -5,4 +5,4 @@ def test_import():
5
5
  """Test that the package can be imported."""
6
6
  import hud
7
7
 
8
- assert hud.__version__ == "0.4.57"
8
+ assert hud.__version__ == "0.4.58"
hud/version.py CHANGED
@@ -4,4 +4,4 @@ Version information for the HUD SDK.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- __version__ = "0.4.57"
7
+ __version__ = "0.4.58"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hud-python
3
- Version: 0.4.57
3
+ Version: 0.4.58
4
4
  Summary: SDK for the HUD platform.
5
5
  Project-URL: Homepage, https://github.com/hud-evals/hud-python
6
6
  Project-URL: Bug Tracker, https://github.com/hud-evals/hud-python/issues
@@ -1,8 +1,8 @@
1
1
  hud/__init__.py,sha256=0LQ9PyuU6yZx7Fxu8YJXKC1i3TTHjg3UrInThh759QE,653
2
2
  hud/__main__.py,sha256=YR8Dq8OhINOsVfQ55PmRXXg4fEK84Rt_-rMtJ5rvhWo,145
3
3
  hud/settings.py,sha256=9SPEZcsZGqplw_LuV8RvJisDRdlsbQgxBqG8ifmjGNc,4117
4
- hud/types.py,sha256=jeC1npyPlno5CYNWBWH06Kxw7VMJld9YLEFBFNtjwoI,11608
5
- hud/version.py,sha256=5oonlS-3cemOdbkDddf3Eg6Wr5IkBtjvigjh9Y3A664,105
4
+ hud/types.py,sha256=XA15zkH25Kt8IQAjT3xfEPew6oCKsnkERKrrKrYmYbM,11608
5
+ hud/version.py,sha256=7lqYv2fdoclIKiUgO55ISA4NzOcYp9DBF4rdJcsoHYk,105
6
6
  hud/agents/__init__.py,sha256=UoIkljWdbq4bM0LD-mSaw6w826EqdEjOk7r6glNYwYQ,286
7
7
  hud/agents/base.py,sha256=nRRnK_kA_PZDT0fepMMTz3QDTHj0jpqvbTqdgRPKSlg,32514
8
8
  hud/agents/claude.py,sha256=Lf7p_h5H4sd2w16ZNc2ru3xuxNP7IDeFayrcdePOLSE,16030
@@ -22,32 +22,32 @@ hud/agents/tests/test_claude.py,sha256=YsI2TV87Sex2BJsLaO2l99WEL3PL3OhQDlTwUHn04
22
22
  hud/agents/tests/test_client.py,sha256=uikgh6yhjPPX2RBU4XJQMz1mNox9uXjuwsP8t93id18,13337
23
23
  hud/agents/tests/test_grounded_openai_agent.py,sha256=VK8lUvHIjWicMX00VKPE-FZyjiJqTEhb80MuRRa9fVc,5437
24
24
  hud/agents/tests/test_openai.py,sha256=hPkL_ANbkrB_HUwvd4wBONuWomCarhLJQcFRpmwSbEk,8016
25
- hud/cli/__init__.py,sha256=kfCsHOG94qIkWG_qipPJWEDqgNDG_qgqew4nf3pr8CE,43083
25
+ hud/cli/__init__.py,sha256=sb0EyBBGEBKbfu2Ph3Wns42plYmA1Hejne5lLZ1hn4A,43747
26
26
  hud/cli/__main__.py,sha256=fDH7XITyuDITwSDIVwRso06aouADO0CzTHKqp5TOwJE,143
27
27
  hud/cli/analyze.py,sha256=4u5oYfJMquOjT9PzzRTYVcTZDxDi0ilNP_g532_hpOU,14716
28
28
  hud/cli/build.py,sha256=EV6PsJ08p3mZZkfeUFjZ687ithxKGlUZ66yAF9lvtaI,28780
29
29
  hud/cli/clone.py,sha256=AwVDIuhr8mHb1oT2Af2HrD25SiTdwATpE6zd93vzLgA,6099
30
30
  hud/cli/debug.py,sha256=jtFW8J5F_3rhq1Hf1_SkJ7aLS3wjnyIs_LsC8k5cnzc,14200
31
- hud/cli/dev.py,sha256=dGA8p5DvKFUgcYA9awXbbIfJ8OV04ew_jd0fElfJRoU,26255
32
- hud/cli/eval.py,sha256=1guW_pNQqFLpF1RylZltBKdXt6iFd_jBNV3Rxy36QRE,29024
31
+ hud/cli/dev.py,sha256=Cg9BLUs4mcMR6UQw43vW68Tx548BPpzlawlC8NgR2Fk,30018
32
+ hud/cli/eval.py,sha256=8LC8kS4RNuvgEGGf_sP7lz0LOgD8mVYOtEEQMXN59Bw,29024
33
33
  hud/cli/get.py,sha256=sksKrdzBGZa7ZuSoQkc0haj-CvOGVSSikoVXeaUd3N4,6274
34
- hud/cli/init.py,sha256=D1NS4PBHxNqsl9hz2VsMTj6HSB_teJgCqsyY77T5lLs,10042
34
+ hud/cli/init.py,sha256=bkpby7vBDqRH3GUDunNA5PIkbQ-Jnpas5pEeN1ltK7k,9976
35
35
  hud/cli/list_func.py,sha256=EVi2Vc3Lb3glBNJxFx4MPnZknZ4xmuJz1OFg_dc8a_E,7177
36
36
  hud/cli/pull.py,sha256=XGEZ8n60tbzLQP_8d9h7XYmzyCW0e2-Rkr3_tLG7jvw,12449
37
- hud/cli/push.py,sha256=rWJIqHebvp3DchK-00L6G0olD3-klsobLutRW4PP_ts,19488
37
+ hud/cli/push.py,sha256=y-1xYup2M396wlJ15d_N2jqthofmlWWiqCbtdYfZyGc,19488
38
38
  hud/cli/remove.py,sha256=8vGQyXDqgtjz85_vtusoIG8zurH4RHz6z8UMevQRYM4,6861
39
39
  hud/cli/flows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- hud/cli/flows/dev.py,sha256=7-Dki2C8MbdcFcHewKhmRfkfjVdo6E4CGmItKVMaUSI,4760
40
+ hud/cli/flows/dev.py,sha256=aLF_hzra0MIin2_2jWi3--yIZdHm5wi2o2PoQlpNm6M,4846
41
41
  hud/cli/flows/tasks.py,sha256=SAQzS53g6vBPMvuEaMRsmkYOJMwvy28RQ-miVmVnP_8,18028
42
- hud/cli/rl/__init__.py,sha256=pGx4WGaL-yGdogJNzgEE7BtjFtT4I9CTI_UdCm49h98,5376
43
- hud/cli/rl/celebrate.py,sha256=trGEJn3xebexlHwFVKPJKhRujVVV8sy7TQTJvRd2p9A,5947
42
+ hud/cli/rl/__init__.py,sha256=Tn_dYNrpk0UlKiPx0VZjGlGWlRUYxGj5PThp5PqTWbc,5376
43
+ hud/cli/rl/celebrate.py,sha256=vFex4RtHTgbhdsRpF4qS2vo4gd_W6LRd0ksO1EIv6J4,5947
44
44
  hud/cli/rl/config.py,sha256=A-4WWwAS68GRKx1cP_DJ-NZD_96cFNnGwx0P3pQT1ps,3271
45
45
  hud/cli/rl/display.py,sha256=hqJVGmO9csYinladhZwjF-GMvppYWngxDHajTyIJ_gM,5214
46
46
  hud/cli/rl/gpu.py,sha256=peXS-NdUF5RyuSs0aZoCzGLboneBUpCy8f9f99WMrG0,2009
47
47
  hud/cli/rl/gpu_utils.py,sha256=0nFRrmJZzLOHh_0bjMhIsBj94PAuu95vwxLd_sa4Q5g,11202
48
48
  hud/cli/rl/local_runner.py,sha256=_4_Bs8UXPz5Is6PyVg_DnrG0zL_Mr-6DJ5_6jC9o0qA,23199
49
49
  hud/cli/rl/presets.py,sha256=DzOO82xL5QyzdVtlX-Do1CODMvDz9ILMPapjU92jcZg,3051
50
- hud/cli/rl/remote_runner.py,sha256=fKmOVKSBUWfakunfe9-HAllpUJDxfRNZwL00fPw-QTI,17837
50
+ hud/cli/rl/remote_runner.py,sha256=R8XXVrpO4l87nA8hlCcURmPfGzrszNLPuoi7-ZKPrhs,17837
51
51
  hud/cli/rl/rl_api.py,sha256=fvRMWQXhTSLM2zQaWWDas_u47RreH8erLgtXRKnQGeA,4350
52
52
  hud/cli/rl/viewer.py,sha256=ExQs1IX3T8x_9aBzc4JojZ779jmFvFTh7EjOYIHzYsU,4441
53
53
  hud/cli/rl/vllm.py,sha256=xTRvex9mmdatTnNaqwZFDBwUsX70bJP5pAGTpbWjmvM,6153
@@ -113,8 +113,8 @@ hud/cli/utils/tests/test_source_hash.py,sha256=GGFklT9xrVPMEpPwgiUzr94An66R-nhNn
113
113
  hud/cli/utils/tests/test_tasks.py,sha256=FT4smFGUHaJpZGIqj-yevBH8l9ARCu03RoedGxiLc2M,2624
114
114
  hud/clients/README.md,sha256=XNE3mch95ozDgVqfwCGcrhlHY9CwT1GKfNANNboowto,3826
115
115
  hud/clients/__init__.py,sha256=N5M_gZv4nP7dLRwpAiaqqaxyaLieGW6397FszeG7JGw,364
116
- hud/clients/base.py,sha256=QPFAjWKxczJWQEcsMGULQ9zj_xEMjMK52fWlTZ8rv9c,15274
117
- hud/clients/fastmcp.py,sha256=_dk1Iczvv_hKhti2z64KLE3cIY93W3vm3cvS8Og3C6w,9209
116
+ hud/clients/base.py,sha256=3U3AVLxKdtH6fw2B4V_CXxLv-J93jzlGsdc3ArxDq2I,15274
117
+ hud/clients/fastmcp.py,sha256=7yNk9SqOw-iBKPFJlt3hLAhR2Cl_QwVEln4OwXRd1Ww,9209
118
118
  hud/clients/mcp_use.py,sha256=Zq_NBFPAMb81qoQa_adCbdTm-yUFjDFCMFG7m8tOlr4,14781
119
119
  hud/clients/tests/__init__.py,sha256=sKOtJFFa4mDIXh1U6O8ZUHjigE8CiRMQ2PzJTIBZuVE,33
120
120
  hud/clients/tests/test_client_integration.py,sha256=kohU6jfCNfwSnAushHeB1_CmDlRfQc7VBL0GEdJYSeI,4198
@@ -141,8 +141,8 @@ hud/native/tests/test_comparator.py,sha256=pDch3r3xDi2o5YXF_bkoLfIdHcCjse3foAaqy
141
141
  hud/native/tests/test_native_init.py,sha256=Z-2dinbQYEkrbCcfBrBOLGdpXtWWOtkfPzp7ZKri68Y,2839
142
142
  hud/otel/__init__.py,sha256=ii17ayoWiS5vAhA7UAmZ8TkmP52gs2pWyHsD46-uYbE,1003
143
143
  hud/otel/collector.py,sha256=jLZymZ8r7xt2VDuWexfbnT7PY1-0aiyLMgjBy8KDY1M,4497
144
- hud/otel/config.py,sha256=rqw4gaQCqJNEFWO_AfyqwgZVia706qJP0UveNSgDVSg,7037
145
- hud/otel/context.py,sha256=pk73k0-ISpRdAuwp-2RDxvC85USggKlvzYUyxkf6I84,20403
144
+ hud/otel/config.py,sha256=eHim5S4QdtrLKq53hJSiq02ezaYevN7Js2QbZyjHdVo,7037
145
+ hud/otel/context.py,sha256=Lic5T4PFdyeM9xfQ1yqzRoK3jhmT4CsRR78mMmRP8co,20403
146
146
  hud/otel/exporters.py,sha256=k0yfUppMbcJ3IfNH3cc_u1hdR54qnnkT7lQjxO3I06g,21470
147
147
  hud/otel/instrumentation.py,sha256=fsFG9W89RdewFDxWKN9Ft4GUb7WbIKpfucTc16WxaZU,5093
148
148
  hud/otel/processors.py,sha256=-gGRbwifplcExDQBLfx_9tqWreDImULJNcENgO9q7VU,4700
@@ -170,7 +170,7 @@ hud/server/__init__.py,sha256=ZTxwhR7tMtSE14i1sONTz5UaMXURW1AYoFZMbWGBviU,134
170
170
  hud/server/context.py,sha256=6bCdSzv1FGyItu9472HbbYef279H7QuMGJDR8EtYg5Y,3210
171
171
  hud/server/low_level.py,sha256=XYs2pOJ9kN4OcJ6ahDmXM5mWkzq5wJLpKFInUYrWEok,4701
172
172
  hud/server/router.py,sha256=T9hLe08-W8SGvO14RfwY3j2zfcvB3lKWS5sR9h_4awM,5343
173
- hud/server/server.py,sha256=zj2FGv1SO8mR_3pjrBzhzN8LT1KpCls7_LCYwSjJ8Nw,30944
173
+ hud/server/server.py,sha256=G_tnVuEBhC5gDNPfXChsPimo8L8ArMjQ9v3c1CSg848,40863
174
174
  hud/server/helper/__init__.py,sha256=ZxO8VP3RZEBBp-q65VixuhzQgqEPSVzW0hEY9J9QqDA,116
175
175
  hud/server/tests/__init__.py,sha256=eEYYkxX5Hz9woXVOBJ2H2_CQoEih0vH6nRt3sH2Z8v8,49
176
176
  hud/server/tests/test_add_tool.py,sha256=9Y59LJpow3BQ31Jg7fowhV7nAeyqude9Tap9tEs_vBE,1863
@@ -183,7 +183,7 @@ hud/server/tests/test_server_extra.py,sha256=blIO0ne4Rq7R40y1-O-P8ANeRHnv2iKFWqS
183
183
  hud/server/tests/test_sigterm_runner.py,sha256=HTM_0DAxA2exGYj7LK4udxMGXHZhY9LDZaKkHhQMu_Y,2610
184
184
  hud/shared/__init__.py,sha256=IPxPCqtPLguryN-nBq78Sakypw2bRiE2iHv3SXG8YRk,139
185
185
  hud/shared/exceptions.py,sha256=9IIqwlGuaNmcshEiFnph1jceZpWKpCnWgoH2OH-NL7I,13260
186
- hud/shared/hints.py,sha256=1UViFu6CwDhaKn66SUvJ-mdFnTVifG6u6Y1n7jufqCg,5717
186
+ hud/shared/hints.py,sha256=aH9iIhGvC3Uo56z0H5H68Mh6HMMBMBqQyK8rvjv1Hc0,5717
187
187
  hud/shared/requests.py,sha256=FbvMU_dKSWu7OzNBKGAXlq-Q34zL1cULhEdGFa6rJyQ,9993
188
188
  hud/shared/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
189
  hud/shared/tests/test_exceptions.py,sha256=66k9Z1zQmYK4yPlG2NC7zggabpCQMFpGqRG3_lOWM8U,16376
@@ -192,7 +192,7 @@ hud/shared/tests/test_requests.py,sha256=nKFcSN1sjrOouVU2xik9lE5Wxapy3EWsO8iIXrM
192
192
  hud/telemetry/__init__.py,sha256=oYJ9fiaiwUGzfMH0qBixX74uSpAR_hyhl8Y-REjmNak,1396
193
193
  hud/telemetry/async_context.py,sha256=H3MBt1vi_ogxUB0U0zSkeO8EqjcU5Iv_04LVQWtTO-E,11550
194
194
  hud/telemetry/instrument.py,sha256=m3u6YK02PTk39Jr4L3se7l-cYyKx0maCaqf5Z5JqWNA,14096
195
- hud/telemetry/job.py,sha256=3W9KHLfBR13IYZvll496P9F6a9HUXCthFMY-MJLi7EA,13278
195
+ hud/telemetry/job.py,sha256=fhIWvsGdp1RbwINVPRUC47i4zQj4xGfe3pjft4mu8No,13278
196
196
  hud/telemetry/replay.py,sha256=YW17s314s5Wy6Rl8MXHqg1FU8EF9_XcHBMJI0rrkyS4,2306
197
197
  hud/telemetry/trace.py,sha256=Q8ypSKKjjMOV6BdNH65iMnya8ifd2dcOqA9-38Td15Y,5254
198
198
  hud/telemetry/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -205,7 +205,7 @@ hud/tools/__init__.py,sha256=i6lE0GxYcPnlLLd-55ryCCHo7o9anC4RfqkuYFXvzMQ,1009
205
205
  hud/tools/base.py,sha256=IxpnEHfxUtgz1k_w-09l65KKpDV3tQu4WdF3nZDsdwU,17854
206
206
  hud/tools/bash.py,sha256=1jl7cpB1ApGXn7Hy8zghJ2fXugEol6UeN0aYUSiM2EQ,5189
207
207
  hud/tools/edit.py,sha256=NYQL3coPIaG_-TP6DOpsVWFg1xcaMZwM5LtFye9WgNE,12644
208
- hud/tools/playwright.py,sha256=HY91JsC_6cvIa-9W14wIhjsAkAtkLPaXgUqMizTG-1c,14989
208
+ hud/tools/playwright.py,sha256=7ggBkylR71taQtrPr5UNwdxtQrHNXMFzKcHW8qDOgzA,15330
209
209
  hud/tools/response.py,sha256=t6Oc8NM4u951A1XMCBaIkFyu3VNEQ8dcWURyTygfZmA,2228
210
210
  hud/tools/submit.py,sha256=hJG2G3Oex4fz_3CsAUVhOhAA56UvDMhquB29xCT-C3M,1973
211
211
  hud/tools/types.py,sha256=g-CWnUUDSxxIfUy54S1bpY1nfTzdYO1R_nPKYReABjQ,2734
@@ -266,10 +266,10 @@ hud/utils/tests/test_progress.py,sha256=QSF7Kpi03Ff_l3mAeqW9qs1nhK50j9vBiSobZq7T
266
266
  hud/utils/tests/test_tasks.py,sha256=Rbbm51vZxygyWlhjunFq4IfFPefVB3qevM9_CZSt5w4,5774
267
267
  hud/utils/tests/test_telemetry.py,sha256=5jl7bEx8C8b-FfFUko5pf4UY-mPOR-9HaeL98dGtVHM,2781
268
268
  hud/utils/tests/test_tool_shorthand.py,sha256=1p3j3D0G93OXHqnUXbvTs3G4A8awrPvwhPpLi6YPeOM,5458
269
- hud/utils/tests/test_version.py,sha256=hsHAkfILDrqA97RyX0l2JapaS40Gi1kJnbThs2QFZHg,160
269
+ hud/utils/tests/test_version.py,sha256=3eFNjP_ugwRDZqUNNdWr6SX43UcvMw4739huirlrQ1o,160
270
270
  hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
271
- hud_python-0.4.57.dist-info/METADATA,sha256=sHE5ewR3U5SRv-JbKoxZth7mPbXzqn50W6ZR3vl_g6g,22338
272
- hud_python-0.4.57.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
273
- hud_python-0.4.57.dist-info/entry_points.txt,sha256=jJbodNFg1m0-CDofe5AHvB4zKBq7sSdP97-ohaQ3ae4,63
274
- hud_python-0.4.57.dist-info/licenses/LICENSE,sha256=yIzBheVUf86FC1bztAcr7RYWWNxyd3B-UJQ3uddg1HA,1078
275
- hud_python-0.4.57.dist-info/RECORD,,
271
+ hud_python-0.4.58.dist-info/METADATA,sha256=XasQ6ALt1lQPCHGOmu7h_bgDmPc3TghvlPP7O9N7Jhs,22338
272
+ hud_python-0.4.58.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
273
+ hud_python-0.4.58.dist-info/entry_points.txt,sha256=jJbodNFg1m0-CDofe5AHvB4zKBq7sSdP97-ohaQ3ae4,63
274
+ hud_python-0.4.58.dist-info/licenses/LICENSE,sha256=yIzBheVUf86FC1bztAcr7RYWWNxyd3B-UJQ3uddg1HA,1078
275
+ hud_python-0.4.58.dist-info/RECORD,,