hud-python 0.4.22__py3-none-any.whl → 0.4.24__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.

Files changed (53) hide show
  1. hud/agents/base.py +85 -59
  2. hud/agents/claude.py +5 -1
  3. hud/agents/grounded_openai.py +3 -1
  4. hud/agents/misc/response_agent.py +3 -2
  5. hud/agents/openai.py +2 -2
  6. hud/agents/openai_chat_generic.py +3 -1
  7. hud/cli/__init__.py +34 -24
  8. hud/cli/analyze.py +27 -26
  9. hud/cli/build.py +50 -46
  10. hud/cli/debug.py +7 -7
  11. hud/cli/dev.py +107 -99
  12. hud/cli/eval.py +31 -29
  13. hud/cli/hf.py +53 -53
  14. hud/cli/init.py +28 -28
  15. hud/cli/list_func.py +22 -22
  16. hud/cli/pull.py +36 -36
  17. hud/cli/push.py +76 -74
  18. hud/cli/remove.py +42 -40
  19. hud/cli/rl/__init__.py +2 -2
  20. hud/cli/rl/init.py +41 -41
  21. hud/cli/rl/pod.py +97 -91
  22. hud/cli/rl/ssh.py +42 -40
  23. hud/cli/rl/train.py +75 -73
  24. hud/cli/rl/utils.py +10 -10
  25. hud/cli/tests/test_analyze.py +1 -1
  26. hud/cli/tests/test_analyze_metadata.py +2 -2
  27. hud/cli/tests/test_pull.py +45 -45
  28. hud/cli/tests/test_push.py +31 -29
  29. hud/cli/tests/test_registry.py +15 -15
  30. hud/cli/utils/environment.py +11 -11
  31. hud/cli/utils/interactive.py +17 -17
  32. hud/cli/utils/logging.py +12 -12
  33. hud/cli/utils/metadata.py +12 -12
  34. hud/cli/utils/registry.py +5 -5
  35. hud/cli/utils/runner.py +23 -23
  36. hud/cli/utils/server.py +16 -16
  37. hud/clients/mcp_use.py +19 -5
  38. hud/clients/utils/__init__.py +25 -0
  39. hud/clients/utils/retry.py +186 -0
  40. hud/datasets/execution/parallel.py +71 -46
  41. hud/shared/hints.py +7 -7
  42. hud/tools/grounding/grounder.py +2 -1
  43. hud/types.py +4 -4
  44. hud/utils/__init__.py +3 -3
  45. hud/utils/{design.py → hud_console.py} +39 -33
  46. hud/utils/pretty_errors.py +6 -6
  47. hud/utils/tests/test_version.py +1 -1
  48. hud/version.py +1 -1
  49. {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/METADATA +3 -1
  50. {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/RECORD +53 -52
  51. {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/WHEEL +0 -0
  52. {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/entry_points.txt +0 -0
  53. {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/licenses/LICENSE +0 -0
hud/cli/analyze.py CHANGED
@@ -13,10 +13,10 @@ from rich.table import Table
13
13
  from rich.tree import Tree
14
14
 
15
15
  from hud.clients import MCPClient
16
- from hud.utils.design import HUDDesign
16
+ from hud.utils.hud_console import HUDConsole
17
17
 
18
18
  console = Console()
19
- design = HUDDesign()
19
+ hud_console = HUDConsole()
20
20
 
21
21
 
22
22
  def parse_docker_command(docker_cmd: list[str]) -> dict:
@@ -28,14 +28,14 @@ def parse_docker_command(docker_cmd: list[str]) -> dict:
28
28
 
29
29
  async def analyze_environment(docker_cmd: list[str], output_format: str, verbose: bool) -> None:
30
30
  """Analyze MCP environment and display results."""
31
- design.header("MCP Environment Analysis", icon="🔍")
31
+ hud_console.header("MCP Environment Analysis", icon="🔍")
32
32
 
33
33
  # Convert Docker command to MCP config
34
34
  mcp_config = parse_docker_command(docker_cmd)
35
35
 
36
36
  # Display command being analyzed
37
- design.dim_info("Command:", " ".join(docker_cmd))
38
- design.info("") # Empty line
37
+ hud_console.dim_info("Command:", " ".join(docker_cmd))
38
+ hud_console.info("") # Empty line
39
39
 
40
40
  # Create client
41
41
  with Progress(
@@ -85,9 +85,9 @@ async def analyze_environment(docker_cmd: list[str], output_format: str, verbose
85
85
  def display_interactive(analysis: dict) -> None:
86
86
  """Display analysis results in interactive format."""
87
87
  # Server metadata
88
- design.section_title("📊 Environment Overview")
88
+ hud_console.section_title("📊 Environment Overview")
89
89
  meta_table = Table(show_header=False, box=None)
90
- meta_table.add_column("Property", style="dim")
90
+ meta_table.add_column("Property", style="bright_black")
91
91
  meta_table.add_column("Value")
92
92
 
93
93
  # Check if this is a live analysis (has metadata) or metadata-only analysis
@@ -126,19 +126,19 @@ def display_interactive(analysis: dict) -> None:
126
126
  console.print(meta_table)
127
127
 
128
128
  # Tools
129
- design.section_title("🔧 Available Tools")
130
- tools_tree = Tree("Tools")
129
+ hud_console.section_title("🔧 Available Tools")
130
+ tools_tree = Tree("[bold bright_white]Tools[/bold bright_white]")
131
131
 
132
132
  # Check if we have hub_tools info (live analysis) or not (metadata-only)
133
133
  if "hub_tools" in analysis:
134
134
  # Live analysis format - separate regular and hub tools
135
135
  # Regular tools
136
- regular_tools = tools_tree.add("Regular Tools")
136
+ regular_tools = tools_tree.add("[bright_white]Regular Tools[/bright_white]")
137
137
  for tool in analysis["tools"]:
138
138
  if tool["name"] not in analysis["hub_tools"]:
139
- tool_node = regular_tools.add(f"[default]{tool['name']}[/default]")
139
+ tool_node = regular_tools.add(f"[bright_white]{tool['name']}[/bright_white]")
140
140
  if tool["description"]:
141
- tool_node.add(f"[dim]{tool['description']}[/dim]")
141
+ tool_node.add(f"[bright_black]{tool['description']}[/bright_black]")
142
142
 
143
143
  # Show input schema if verbose
144
144
  if analysis.get("verbose") and tool.get("input_schema"):
@@ -148,17 +148,17 @@ def display_interactive(analysis: dict) -> None:
148
148
 
149
149
  # Hub tools
150
150
  if analysis["hub_tools"]:
151
- hub_tools = tools_tree.add("Hub Tools")
151
+ hub_tools = tools_tree.add("[bright_white]Hub Tools[/bright_white]")
152
152
  for hub_name, functions in analysis["hub_tools"].items():
153
- hub_node = hub_tools.add(f"[yellow]{hub_name}[/yellow]")
153
+ hub_node = hub_tools.add(f"[rgb(181,137,0)]{hub_name}[/rgb(181,137,0)]")
154
154
  for func in functions:
155
- hub_node.add(f"[default]{func}[/default]")
155
+ hub_node.add(f"[bright_white]{func}[/bright_white]")
156
156
  else:
157
157
  # Metadata-only format - just list all tools
158
158
  for tool in analysis["tools"]:
159
- tool_node = tools_tree.add(f"[default]{tool['name']}[/default]")
159
+ tool_node = tools_tree.add(f"[bright_white]{tool['name']}[/bright_white]")
160
160
  if tool.get("description"):
161
- tool_node.add(f"[dim]{tool['description']}[/dim]")
161
+ tool_node.add(f"[bright_black]{tool['description']}[/bright_black]")
162
162
 
163
163
  # Show input schema if verbose
164
164
  if tool.get("inputSchema"):
@@ -170,11 +170,11 @@ def display_interactive(analysis: dict) -> None:
170
170
 
171
171
  # Resources
172
172
  if analysis["resources"]:
173
- design.section_title("📚 Available Resources")
173
+ hud_console.section_title("📚 Available Resources")
174
174
  resources_table = Table()
175
- resources_table.add_column("URI", style="default")
176
- resources_table.add_column("Name", style="white")
177
- resources_table.add_column("Type", style="dim")
175
+ resources_table.add_column("URI", style="bright_white")
176
+ resources_table.add_column("Name", style="bright_white")
177
+ resources_table.add_column("Type", style="bright_black")
178
178
 
179
179
  for resource in analysis["resources"][:10]:
180
180
  resources_table.add_row(
@@ -184,11 +184,12 @@ def display_interactive(analysis: dict) -> None:
184
184
  console.print(resources_table)
185
185
 
186
186
  if len(analysis["resources"]) > 10:
187
- console.print(f"[dim]... and {len(analysis['resources']) - 10} more resources[/dim]")
187
+ remaining = len(analysis["resources"]) - 10
188
+ console.print(f"[bright_black]... and {remaining} more resources[/bright_black]")
188
189
 
189
190
  # Telemetry (only for live analysis)
190
191
  if analysis.get("telemetry"):
191
- design.section_title("📡 Telemetry Data")
192
+ hud_console.section_title("📡 Telemetry Data")
192
193
  telemetry_table = Table(show_header=False, box=None)
193
194
  telemetry_table.add_column("Key", style="dim")
194
195
  telemetry_table.add_column("Value")
@@ -206,7 +207,7 @@ def display_interactive(analysis: dict) -> None:
206
207
 
207
208
  # Environment variables (for metadata-only analysis)
208
209
  if analysis.get("env_vars"):
209
- design.section_title("🔑 Environment Variables")
210
+ hud_console.section_title("🔑 Environment Variables")
210
211
  env_table = Table(show_header=False, box=None)
211
212
  env_table.add_column("Type", style="dim")
212
213
  env_table.add_column("Variables")
@@ -309,7 +310,7 @@ async def analyze_environment_from_config(
309
310
  config_path: Path, output_format: str, verbose: bool
310
311
  ) -> None:
311
312
  """Analyze MCP environment from a JSON config file."""
312
- design.header("MCP Environment Analysis", icon="🔍")
313
+ hud_console.header("MCP Environment Analysis", icon="🔍")
313
314
 
314
315
  # Load config from file
315
316
  try:
@@ -327,7 +328,7 @@ async def analyze_environment_from_mcp_config(
327
328
  mcp_config: dict[str, Any], output_format: str, verbose: bool
328
329
  ) -> None:
329
330
  """Analyze MCP environment from MCP config dict."""
330
- design.header("MCP Environment Analysis", icon="🔍")
331
+ hud_console.header("MCP Environment Analysis", icon="🔍")
331
332
  await _analyze_with_config(mcp_config, output_format, verbose)
332
333
 
333
334
 
hud/cli/build.py CHANGED
@@ -14,7 +14,7 @@ import typer
14
14
  import yaml
15
15
 
16
16
  from hud.clients import MCPClient
17
- from hud.utils.design import HUDDesign
17
+ from hud.utils.hud_console import HUDConsole
18
18
  from hud.version import __version__ as hud_version
19
19
 
20
20
  from .utils.registry import save_to_registry
@@ -156,7 +156,7 @@ async def analyze_mcp_environment(
156
156
  image: str, verbose: bool = False, env_vars: dict[str, str] | None = None
157
157
  ) -> dict[str, Any]:
158
158
  """Analyze an MCP environment to extract metadata."""
159
- design = HUDDesign()
159
+ hud_console = HUDConsole()
160
160
  env_vars = env_vars or {}
161
161
 
162
162
  # Build Docker command to run the image
@@ -180,7 +180,7 @@ async def analyze_mcp_environment(
180
180
 
181
181
  try:
182
182
  if verbose:
183
- design.info(f"Initializing MCP client with command: {' '.join(docker_cmd)}")
183
+ hud_console.info(f"Initializing MCP client with command: {' '.join(docker_cmd)}")
184
184
 
185
185
  await client.initialize()
186
186
  initialized = True
@@ -215,7 +215,7 @@ async def analyze_mcp_environment(
215
215
  await client.shutdown()
216
216
  except Exception:
217
217
  # Ignore shutdown errors
218
- design.warning("Failed to shutdown MCP client")
218
+ hud_console.warning("Failed to shutdown MCP client")
219
219
 
220
220
 
221
221
  def build_docker_image(
@@ -226,13 +226,13 @@ def build_docker_image(
226
226
  build_args: dict[str, str] | None = None,
227
227
  ) -> bool:
228
228
  """Build a Docker image from a directory."""
229
- design = HUDDesign()
229
+ hud_console = HUDConsole()
230
230
  build_args = build_args or {}
231
231
 
232
232
  # Check if Dockerfile exists
233
233
  dockerfile = directory / "Dockerfile"
234
234
  if not dockerfile.exists():
235
- design.error(f"No Dockerfile found in {directory}")
235
+ hud_console.error(f"No Dockerfile found in {directory}")
236
236
  return False
237
237
 
238
238
  # Build command
@@ -247,14 +247,14 @@ def build_docker_image(
247
247
  cmd.append(str(directory))
248
248
 
249
249
  # Always show build output
250
- design.info(f"Running: {' '.join(cmd)}")
250
+ hud_console.info(f"Running: {' '.join(cmd)}")
251
251
 
252
252
  try:
253
253
  # Use Docker's native output formatting - no capture, let Docker handle display
254
254
  result = subprocess.run(cmd, check=False) # noqa: S603
255
255
  return result.returncode == 0
256
256
  except Exception as e:
257
- design.error(f"Build error: {e}")
257
+ hud_console.error(f"Build error: {e}")
258
258
  return False
259
259
 
260
260
 
@@ -266,20 +266,20 @@ def build_environment(
266
266
  env_vars: dict[str, str] | None = None,
267
267
  ) -> None:
268
268
  """Build a HUD environment and generate lock file."""
269
- design = HUDDesign()
269
+ hud_console = HUDConsole()
270
270
  env_vars = env_vars or {}
271
- design.header("HUD Environment Build")
271
+ hud_console.header("HUD Environment Build")
272
272
 
273
273
  # Resolve directory
274
274
  env_dir = Path(directory).resolve()
275
275
  if not env_dir.exists():
276
- design.error(f"Directory not found: {directory}")
276
+ hud_console.error(f"Directory not found: {directory}")
277
277
  raise typer.Exit(1)
278
278
 
279
279
  # Check for pyproject.toml
280
280
  pyproject_path = env_dir / "pyproject.toml"
281
281
  if not pyproject_path.exists():
282
- design.error(f"No pyproject.toml found in {directory}")
282
+ hud_console.error(f"No pyproject.toml found in {directory}")
283
283
  raise typer.Exit(1)
284
284
 
285
285
  # Read pyproject.toml to get image name
@@ -301,17 +301,17 @@ def build_environment(
301
301
  # Build temporary image first
302
302
  temp_tag = f"hud-build-temp:{int(time.time())}"
303
303
 
304
- design.progress_message(f"Building Docker image: {temp_tag}")
304
+ hud_console.progress_message(f"Building Docker image: {temp_tag}")
305
305
 
306
306
  # Build the image (env vars are for runtime, not build time)
307
307
  if not build_docker_image(env_dir, temp_tag, no_cache, verbose):
308
- design.error("Docker build failed")
308
+ hud_console.error("Docker build failed")
309
309
  raise typer.Exit(1)
310
310
 
311
- design.success(f"Built temporary image: {temp_tag}")
311
+ hud_console.success(f"Built temporary image: {temp_tag}")
312
312
 
313
313
  # Analyze the environment
314
- design.progress_message("Analyzing MCP environment...")
314
+ hud_console.progress_message("Analyzing MCP environment...")
315
315
 
316
316
  loop = asyncio.new_event_loop()
317
317
  asyncio.set_event_loop(loop)
@@ -320,7 +320,7 @@ def build_environment(
320
320
  finally:
321
321
  loop.close()
322
322
 
323
- design.success(f"Analyzed environment: {analysis['toolCount']} tools found")
323
+ hud_console.success(f"Analyzed environment: {analysis['toolCount']} tools found")
324
324
 
325
325
  # Extract environment variables from Dockerfile
326
326
  dockerfile_path = env_dir / "Dockerfile"
@@ -335,14 +335,18 @@ def build_environment(
335
335
  missing_required = [e for e in required_env if e not in env_vars]
336
336
 
337
337
  # Show what env vars were provided
338
- design.success(f"Using provided environment variables: {', '.join(env_vars.keys())}")
338
+ hud_console.success(f"Using provided environment variables: {', '.join(env_vars.keys())}")
339
339
  else:
340
340
  missing_required = required_env[:]
341
341
 
342
342
  # Warn about missing required variables
343
343
  if missing_required:
344
- design.warning(f"Missing required environment variables: {', '.join(missing_required)}")
345
- design.info("These can be added to the lock file after build or provided with -e flags")
344
+ hud_console.warning(
345
+ f"Missing required environment variables: {', '.join(missing_required)}"
346
+ )
347
+ hud_console.info(
348
+ "These can be added to the lock file after build or provided with -e flags"
349
+ )
346
350
 
347
351
  # Check for existing version and increment
348
352
  lock_path = env_dir / "hud.lock.yaml"
@@ -351,11 +355,11 @@ def build_environment(
351
355
  if existing_version:
352
356
  # Increment existing version
353
357
  new_version = increment_version(existing_version)
354
- design.info(f"Incrementing version: {existing_version} → {new_version}")
358
+ hud_console.info(f"Incrementing version: {existing_version} → {new_version}")
355
359
  else:
356
360
  # Start with 0.1.0 for new environments
357
361
  new_version = "0.1.0"
358
- design.info(f"Setting initial version: {new_version}")
362
+ hud_console.info(f"Setting initial version: {new_version}")
359
363
 
360
364
  # Create lock file content - minimal and useful
361
365
  lock_content = {
@@ -406,7 +410,7 @@ def build_environment(
406
410
  with open(lock_path, "w") as f:
407
411
  yaml.dump(lock_content, f, default_flow_style=False, sort_keys=False)
408
412
 
409
- design.success("Created lock file: hud.lock.yaml")
413
+ hud_console.success("Created lock file: hud.lock.yaml")
410
414
 
411
415
  # Calculate lock file hash
412
416
  lock_content_str = yaml.dump(lock_content, default_flow_style=False, sort_keys=True)
@@ -414,7 +418,7 @@ def build_environment(
414
418
  lock_size = len(lock_content_str)
415
419
 
416
420
  # Rebuild with label containing lock file hash
417
- design.progress_message("Rebuilding with lock file metadata...")
421
+ hud_console.progress_message("Rebuilding with lock file metadata...")
418
422
 
419
423
  # Build final image with label (uses cache from first build)
420
424
  # Also tag with version
@@ -447,10 +451,10 @@ def build_environment(
447
451
  )
448
452
 
449
453
  if result.returncode != 0:
450
- design.error("Failed to rebuild with label")
454
+ hud_console.error("Failed to rebuild with label")
451
455
  raise typer.Exit(1)
452
456
 
453
- design.success("Built final image with lock file metadata")
457
+ hud_console.success("Built final image with lock file metadata")
454
458
 
455
459
  # NOW get the image ID after the final build
456
460
  image_id = get_docker_image_id(tag) # type: ignore
@@ -466,9 +470,9 @@ def build_environment(
466
470
  with open(lock_path, "w") as f:
467
471
  yaml.dump(lock_content, f, default_flow_style=False, sort_keys=False)
468
472
 
469
- design.success("Updated lock file with image ID")
473
+ hud_console.success("Updated lock file with image ID")
470
474
  else:
471
- design.warning("Could not retrieve image ID for lock file")
475
+ hud_console.warning("Could not retrieve image ID for lock file")
472
476
 
473
477
  # Remove temp image after we're done
474
478
  subprocess.run(["docker", "rmi", temp_tag], capture_output=True) # noqa: S603, S607
@@ -479,30 +483,30 @@ def build_environment(
479
483
  save_to_registry(lock_content, lock_content.get("image", tag), verbose)
480
484
 
481
485
  # Print summary
482
- design.section_title("Build Complete")
486
+ hud_console.section_title("Build Complete")
483
487
 
484
488
  # Show the version tag as primary since that's what will be pushed
485
- design.status_item("Built image", version_tag, primary=True)
489
+ hud_console.status_item("Built image", version_tag, primary=True)
486
490
  if tag:
487
- design.status_item("Also tagged", tag)
488
- design.status_item("Version", new_version)
489
- design.status_item("Lock file", "hud.lock.yaml")
490
- design.status_item("Tools found", str(analysis["toolCount"]))
491
+ hud_console.status_item("Also tagged", tag)
492
+ hud_console.status_item("Version", new_version)
493
+ hud_console.status_item("Lock file", "hud.lock.yaml")
494
+ hud_console.status_item("Tools found", str(analysis["toolCount"]))
491
495
 
492
496
  # Show the digest info separately if we have it
493
497
  if image_id:
494
- design.dim_info("\nImage digest", image_id)
495
-
496
- design.section_title("Next Steps")
497
- design.info("Test locally:")
498
- design.command_example("hud dev", "Hot-reload development")
499
- design.command_example(f"hud run {tag}", "Run the built image")
500
- design.info("")
501
- design.info("Publish to registry:")
502
- design.command_example("hud push", f"Push as {version_tag}")
503
- design.command_example("hud push --tag latest", "Push with custom tag")
504
- design.info("")
505
- design.info("The lock file can be used to reproduce this exact environment.")
498
+ hud_console.dim_info("\nImage digest", image_id)
499
+
500
+ hud_console.section_title("Next Steps")
501
+ hud_console.info("Test locally:")
502
+ hud_console.command_example("hud dev", "Hot-reload development")
503
+ hud_console.command_example(f"hud run {tag}", "Run the built image")
504
+ hud_console.info("")
505
+ hud_console.info("Publish to registry:")
506
+ hud_console.command_example("hud push", f"Push as {version_tag}")
507
+ hud_console.command_example("hud push --tag latest", "Push with custom tag")
508
+ hud_console.info("")
509
+ hud_console.info("The lock file can be used to reproduce this exact environment.")
506
510
 
507
511
 
508
512
  def build_command(
hud/cli/debug.py CHANGED
@@ -12,7 +12,7 @@ import time
12
12
  from rich.console import Console
13
13
 
14
14
  from hud.clients import MCPClient
15
- from hud.utils.design import HUDDesign
15
+ from hud.utils.hud_console import HUDConsole
16
16
 
17
17
  from .utils.logging import CaptureLogger, Colors, analyze_error_for_hints
18
18
 
@@ -31,15 +31,15 @@ async def debug_mcp_stdio(command: list[str], logger: CaptureLogger, max_phase:
31
31
  Returns:
32
32
  Number of phases completed (0-5)
33
33
  """
34
- # Create design instance for initial output (before logger takes over)
34
+ # Create hud_console instance for initial output (before logger takes over)
35
35
  if logger.print_output:
36
- design = HUDDesign()
37
- design.header("MCP Server Debugger", icon="🔍")
38
- design.dim_info("Command:", " ".join(command))
39
- design.dim_info("Time:", time.strftime("%Y-%m-%d %H:%M:%S"))
36
+ hud_console = HUDConsole()
37
+ hud_console.header("MCP Server Debugger", icon="🔍")
38
+ hud_console.dim_info("Command:", " ".join(command))
39
+ hud_console.dim_info("Time:", time.strftime("%Y-%m-%d %H:%M:%S"))
40
40
 
41
41
  # Explain color coding using Rich formatting
42
- design.info("\nColor Key:")
42
+ hud_console.info("\nColor Key:")
43
43
  console.print(" [bold]■[/bold] Commands (bold)")
44
44
  console.print(" [rgb(192,150,12)]■[/rgb(192,150,12)] STDIO (MCP protocol)")
45
45
  console.print(" [dim]■[/dim] STDERR (server logs)")