hud-python 0.5.40__tar.gz → 0.5.41__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 (357) hide show
  1. {hud_python-0.5.40 → hud_python-0.5.41}/PKG-INFO +1 -1
  2. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/init.py +114 -41
  3. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/environment.py +74 -33
  4. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_environment.py +251 -0
  5. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/gemini.py +37 -1
  6. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/tests/test_computer.py +67 -1
  7. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/base.py +29 -0
  8. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/pyautogui.py +13 -20
  9. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/tests/test_pyautogui_executor.py +10 -3
  10. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/xdo.py +8 -4
  11. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/test_version.py +1 -1
  12. {hud_python-0.5.40 → hud_python-0.5.41}/hud/version.py +1 -1
  13. {hud_python-0.5.40 → hud_python-0.5.41}/pyproject.toml +1 -1
  14. {hud_python-0.5.40 → hud_python-0.5.41}/.gitignore +0 -0
  15. {hud_python-0.5.40 → hud_python-0.5.41}/LICENSE +0 -0
  16. {hud_python-0.5.40 → hud_python-0.5.41}/README.md +0 -0
  17. {hud_python-0.5.40 → hud_python-0.5.41}/examples/README.md +0 -0
  18. {hud_python-0.5.40 → hud_python-0.5.41}/hud/__init__.py +0 -0
  19. {hud_python-0.5.40 → hud_python-0.5.41}/hud/__main__.py +0 -0
  20. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/__init__.py +0 -0
  21. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/base.py +0 -0
  22. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/claude.py +0 -0
  23. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/gateway.py +0 -0
  24. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/gemini.py +0 -0
  25. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/gemini_cua.py +0 -0
  26. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/grounded_openai.py +0 -0
  27. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/misc/__init__.py +0 -0
  28. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/misc/integration_test_agent.py +0 -0
  29. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/misc/response_agent.py +0 -0
  30. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/openai.py +0 -0
  31. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/openai_chat.py +0 -0
  32. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/operator.py +0 -0
  33. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/resolver.py +0 -0
  34. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/__init__.py +0 -0
  35. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/conftest.py +0 -0
  36. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_base.py +0 -0
  37. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_base_runtime.py +0 -0
  38. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_claude.py +0 -0
  39. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_gemini.py +0 -0
  40. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_grounded_openai_agent.py +0 -0
  41. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_integration_test_agent.py +0 -0
  42. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_openai.py +0 -0
  43. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_operator.py +0 -0
  44. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_resolver.py +0 -0
  45. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/tests/test_run_eval.py +0 -0
  46. {hud_python-0.5.40 → hud_python-0.5.41}/hud/agents/types.py +0 -0
  47. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/__init__.py +0 -0
  48. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/__main__.py +0 -0
  49. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/analyze.py +0 -0
  50. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/build.py +0 -0
  51. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/cancel.py +0 -0
  52. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/__init__.py +0 -0
  53. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/base.py +0 -0
  54. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/harbor.py +0 -0
  55. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/tests/__init__.py +0 -0
  56. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/tests/conftest.py +0 -0
  57. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/convert/tests/test_harbor.py +0 -0
  58. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/debug.py +0 -0
  59. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/deploy.py +0 -0
  60. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/dev.py +0 -0
  61. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/eval.py +0 -0
  62. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/__init__.py +0 -0
  63. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/dev.py +0 -0
  64. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/init.py +0 -0
  65. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/tasks.py +0 -0
  66. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/templates.py +0 -0
  67. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/tests/__init__.py +0 -0
  68. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/flows/tests/test_dev.py +0 -0
  69. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/link.py +0 -0
  70. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/login.py +0 -0
  71. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/models.py +0 -0
  72. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/push.py +0 -0
  73. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/rl.py +0 -0
  74. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/scenario.py +0 -0
  75. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/sync.py +0 -0
  76. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/__init__.py +0 -0
  77. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_analysis_utils.py +0 -0
  78. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_analyze.py +0 -0
  79. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_analyze_metadata.py +0 -0
  80. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_analyze_module.py +0 -0
  81. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_build.py +0 -0
  82. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_build_failure.py +0 -0
  83. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_build_module.py +0 -0
  84. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_cli_init.py +0 -0
  85. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_cli_main.py +0 -0
  86. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
  87. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_cli_root.py +0 -0
  88. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_convert.py +0 -0
  89. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_debug.py +0 -0
  90. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_debug_directory_mode.py +0 -0
  91. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_deploy.py +0 -0
  92. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_dev.py +0 -0
  93. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_eval.py +0 -0
  94. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_eval_bedrock.py +0 -0
  95. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_init.py +0 -0
  96. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_lockfile_utils.py +0 -0
  97. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_main_module.py +0 -0
  98. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_mcp_server.py +0 -0
  99. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_push.py +0 -0
  100. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_push_happy.py +0 -0
  101. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_push_wrapper.py +0 -0
  102. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_rl.py +0 -0
  103. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_scenario.py +0 -0
  104. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_sync.py +0 -0
  105. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/tests/test_utils.py +0 -0
  106. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/__init__.py +0 -0
  107. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/analysis.py +0 -0
  108. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/api.py +0 -0
  109. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/args.py +0 -0
  110. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/build_display.py +0 -0
  111. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/build_logs.py +0 -0
  112. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/collect.py +0 -0
  113. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/config.py +0 -0
  114. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/context.py +0 -0
  115. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/docker.py +0 -0
  116. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/env_check.py +0 -0
  117. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/environment.py +0 -0
  118. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/git.py +0 -0
  119. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/interactive.py +0 -0
  120. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/lockfile.py +0 -0
  121. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/logging.py +0 -0
  122. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/metadata.py +0 -0
  123. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/name_check.py +0 -0
  124. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/project_config.py +0 -0
  125. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/server.py +0 -0
  126. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/source_hash.py +0 -0
  127. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tasks.py +0 -0
  128. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/taskset.py +0 -0
  129. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/__init__.py +0 -0
  130. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_collect.py +0 -0
  131. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_config.py +0 -0
  132. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_docker.py +0 -0
  133. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_docker_hints.py +0 -0
  134. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_env_check.py +0 -0
  135. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_environment.py +0 -0
  136. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_git.py +0 -0
  137. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_interactive_module.py +0 -0
  138. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_logging_utils.py +0 -0
  139. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_metadata.py +0 -0
  140. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_source_hash.py +0 -0
  141. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/tests/test_tasks.py +0 -0
  142. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/validation.py +0 -0
  143. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/version_check.py +0 -0
  144. {hud_python-0.5.40 → hud_python-0.5.41}/hud/cli/utils/viewer.py +0 -0
  145. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/__init__.py +0 -0
  146. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/loader.py +0 -0
  147. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/runner.py +0 -0
  148. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/tests/__init__.py +0 -0
  149. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/tests/test_loader.py +0 -0
  150. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/tests/test_utils.py +0 -0
  151. {hud_python-0.5.40 → hud_python-0.5.41}/hud/datasets/utils.py +0 -0
  152. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/__init__.py +0 -0
  153. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connection.py +0 -0
  154. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/__init__.py +0 -0
  155. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/base.py +0 -0
  156. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/local.py +0 -0
  157. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/mcp_config.py +0 -0
  158. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/openai.py +0 -0
  159. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/connectors/remote.py +0 -0
  160. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/__init__.py +0 -0
  161. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/adk.py +0 -0
  162. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/anthropic.py +0 -0
  163. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/gemini.py +0 -0
  164. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/langchain.py +0 -0
  165. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/llamaindex.py +0 -0
  166. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/integrations/openai.py +0 -0
  167. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/mock.py +0 -0
  168. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/router.py +0 -0
  169. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/scenarios.py +0 -0
  170. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/__init__.py +0 -0
  171. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_connection.py +0 -0
  172. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_connectors.py +0 -0
  173. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_integrations.py +0 -0
  174. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_local_connectors.py +0 -0
  175. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_scenarios.py +0 -0
  176. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_session_id.py +0 -0
  177. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/tests/test_tools.py +0 -0
  178. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/types.py +0 -0
  179. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/utils/__init__.py +0 -0
  180. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/utils/formats.py +0 -0
  181. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/utils/schema.py +0 -0
  182. {hud_python-0.5.40 → hud_python-0.5.41}/hud/environment/utils/tool_wrappers.py +0 -0
  183. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/__init__.py +0 -0
  184. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/context.py +0 -0
  185. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/display.py +0 -0
  186. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/instrument.py +0 -0
  187. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/manager.py +0 -0
  188. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/parallel.py +0 -0
  189. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/task.py +0 -0
  190. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/__init__.py +0 -0
  191. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/test_context.py +0 -0
  192. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/test_eval.py +0 -0
  193. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/test_manager.py +0 -0
  194. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/test_parallel.py +0 -0
  195. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/tests/test_task.py +0 -0
  196. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/types.py +0 -0
  197. {hud_python-0.5.40 → hud_python-0.5.41}/hud/eval/utils.py +0 -0
  198. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/__init__.py +0 -0
  199. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/chat.py +0 -0
  200. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/graders.py +0 -0
  201. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/permissions.py +0 -0
  202. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/skills.py +0 -0
  203. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/tests/__init__.py +0 -0
  204. {hud_python-0.5.40 → hud_python-0.5.41}/hud/native/tests/test_graders.py +0 -0
  205. {hud_python-0.5.40 → hud_python-0.5.41}/hud/patches/__init__.py +0 -0
  206. {hud_python-0.5.40 → hud_python-0.5.41}/hud/patches/mcp_patches.py +0 -0
  207. {hud_python-0.5.40 → hud_python-0.5.41}/hud/patches/warnings.py +0 -0
  208. {hud_python-0.5.40 → hud_python-0.5.41}/hud/py.typed +0 -0
  209. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/__init__.py +0 -0
  210. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/context.py +0 -0
  211. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/helper/__init__.py +0 -0
  212. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/low_level.py +0 -0
  213. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/router.py +0 -0
  214. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/server.py +0 -0
  215. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/__init__.py +0 -0
  216. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_add_tool.py +0 -0
  217. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_context.py +0 -0
  218. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_handlers.py +0 -0
  219. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_integration.py +0 -0
  220. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_mcp_server_more.py +0 -0
  221. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_prefix_naming.py +0 -0
  222. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_run_wrapper.py +0 -0
  223. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_server_extra.py +0 -0
  224. {hud_python-0.5.40 → hud_python-0.5.41}/hud/server/tests/test_sigterm_runner.py +0 -0
  225. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/__init__.py +0 -0
  226. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/chat.py +0 -0
  227. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/chat_service.py +0 -0
  228. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/reply_metadata.py +0 -0
  229. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/tests/__init__.py +0 -0
  230. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/tests/test_chat.py +0 -0
  231. {hud_python-0.5.40 → hud_python-0.5.41}/hud/services/tests/test_chat_service.py +0 -0
  232. {hud_python-0.5.40 → hud_python-0.5.41}/hud/settings.py +0 -0
  233. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/__init__.py +0 -0
  234. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/exceptions.py +0 -0
  235. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/hints.py +0 -0
  236. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/requests.py +0 -0
  237. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/tests/__init__.py +0 -0
  238. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/tests/test_exceptions.py +0 -0
  239. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/tests/test_hints.py +0 -0
  240. {hud_python-0.5.40 → hud_python-0.5.41}/hud/shared/tests/test_requests.py +0 -0
  241. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/__init__.py +0 -0
  242. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/exporter.py +0 -0
  243. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/instrument.py +0 -0
  244. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/tests/__init__.py +0 -0
  245. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/tests/test_eval_telemetry.py +0 -0
  246. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/tests/test_exporter.py +0 -0
  247. {hud_python-0.5.40 → hud_python-0.5.41}/hud/telemetry/tests/test_instrument.py +0 -0
  248. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/__init__.py +0 -0
  249. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/agent.py +0 -0
  250. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/base.py +0 -0
  251. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/__init__.py +0 -0
  252. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/apply_patch.py +0 -0
  253. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/bash.py +0 -0
  254. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/edit.py +0 -0
  255. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/gemini_edit.py +0 -0
  256. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/gemini_shell.py +0 -0
  257. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/gemini_write.py +0 -0
  258. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/session.py +0 -0
  259. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/shell.py +0 -0
  260. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/__init__.py +0 -0
  261. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_apply_patch.py +0 -0
  262. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash.py +0 -0
  263. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash_extended.py +0 -0
  264. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_bash_integration.py +0 -0
  265. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_edit.py +0 -0
  266. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_gemini_tools.py +0 -0
  267. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/tests/test_shell.py +0 -0
  268. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/coding/utils.py +0 -0
  269. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/__init__.py +0 -0
  270. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/anthropic.py +0 -0
  271. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/glm.py +0 -0
  272. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/hud.py +0 -0
  273. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/openai.py +0 -0
  274. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/qwen.py +0 -0
  275. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/settings.py +0 -0
  276. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/tests/__init__.py +0 -0
  277. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/tests/test_compression.py +0 -0
  278. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/tests/test_computer_actions.py +0 -0
  279. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/computer/tests/test_glm_computer.py +0 -0
  280. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/elicitation.py +0 -0
  281. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/__init__.py +0 -0
  282. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/tests/__init__.py +0 -0
  283. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/executors/tests/test_base_executor.py +0 -0
  284. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/__init__.py +0 -0
  285. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/base.py +0 -0
  286. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/gemini.py +0 -0
  287. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/gemini_read_many.py +0 -0
  288. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/glob.py +0 -0
  289. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/grep.py +0 -0
  290. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/list.py +0 -0
  291. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/read.py +0 -0
  292. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/__init__.py +0 -0
  293. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_glob.py +0 -0
  294. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_grep.py +0 -0
  295. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_list.py +0 -0
  296. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_read.py +0 -0
  297. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/filesystem/tests/test_read_many.py +0 -0
  298. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/__init__.py +0 -0
  299. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/config.py +0 -0
  300. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/grounded_tool.py +0 -0
  301. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/grounder.py +0 -0
  302. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/tests/__init__.py +0 -0
  303. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/grounding/tests/test_grounded_tool.py +0 -0
  304. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/__init__.py +0 -0
  305. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/base.py +0 -0
  306. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/code_execution.py +0 -0
  307. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/google_search.py +0 -0
  308. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/tool_search.py +0 -0
  309. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/url_context.py +0 -0
  310. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/web_fetch.py +0 -0
  311. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/hosted/web_search.py +0 -0
  312. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/jupyter.py +0 -0
  313. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/__init__.py +0 -0
  314. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/base.py +0 -0
  315. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/claude.py +0 -0
  316. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/gemini.py +0 -0
  317. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/session.py +0 -0
  318. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/tests/__init__.py +0 -0
  319. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/tests/test_claude.py +0 -0
  320. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/tests/test_gemini.py +0 -0
  321. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/memory/tests/test_session.py +0 -0
  322. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/native_types.py +0 -0
  323. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/playwright.py +0 -0
  324. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/response.py +0 -0
  325. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/submit.py +0 -0
  326. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/__init__.py +0 -0
  327. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_agent_tool.py +0 -0
  328. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_base.py +0 -0
  329. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_elicitation.py +0 -0
  330. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_init.py +0 -0
  331. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_jupyter_tool.py +0 -0
  332. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_native_tool_e2e.py +0 -0
  333. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_native_types.py +0 -0
  334. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_playwright_tool.py +0 -0
  335. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_response.py +0 -0
  336. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_submit.py +0 -0
  337. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_tools.py +0 -0
  338. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_tools_init.py +0 -0
  339. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_types.py +0 -0
  340. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/tests/test_utils.py +0 -0
  341. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/types.py +0 -0
  342. {hud_python-0.5.40 → hud_python-0.5.41}/hud/tools/utils.py +0 -0
  343. {hud_python-0.5.40 → hud_python-0.5.41}/hud/types.py +0 -0
  344. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/__init__.py +0 -0
  345. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/env.py +0 -0
  346. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/hud_console.py +0 -0
  347. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/mcp.py +0 -0
  348. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/pretty_errors.py +0 -0
  349. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/serialization.py +0 -0
  350. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/strict_schema.py +0 -0
  351. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/__init__.py +0 -0
  352. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/test_init.py +0 -0
  353. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/test_pretty_errors.py +0 -0
  354. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/test_serialization.py +0 -0
  355. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tests/test_tool_shorthand.py +0 -0
  356. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/tool_shorthand.py +0 -0
  357. {hud_python-0.5.40 → hud_python-0.5.41}/hud/utils/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hud-python
3
- Version: 0.5.40
3
+ Version: 0.5.41
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
@@ -12,6 +12,8 @@ import httpx
12
12
  import questionary
13
13
  import typer
14
14
 
15
+ from hud.cli.utils.api import hud_headers
16
+ from hud.settings import settings
15
17
  from hud.utils.hud_console import HUDConsole
16
18
 
17
19
  # Presets mapping to public GitHub repositories under hud-evals org
@@ -22,6 +24,8 @@ PRESET_MAP: dict[str, str | None] = {
22
24
  "blank": "hud-blank",
23
25
  "deep-research": "hud-deepresearch",
24
26
  "browser": "hud-browser",
27
+ "remote-browser": "hud-remote-browser",
28
+ "coding": "coding-template",
25
29
  "rubrics": "hud-rubrics",
26
30
  "verilog-coding-template": "verilog-coding-template",
27
31
  "data-science-template": "data-science-template",
@@ -86,34 +90,53 @@ def _replace_placeholders(target_dir: Path, env_name: str) -> list[str]:
86
90
  return modified_files
87
91
 
88
92
 
89
- def _prompt_for_preset() -> str | None:
93
+ def _fetch_available_templates() -> tuple[list[dict], list[dict]]:
94
+ """Fetch available templates from the HUD API.
95
+
96
+ Returns (public_templates, private_templates). Falls back to empty
97
+ private list if the API is unreachable or the user has no API key.
98
+ """
99
+ if not settings.api_key:
100
+ return [], []
101
+
102
+ try:
103
+ with httpx.Client(timeout=10) as client:
104
+ resp = client.get(
105
+ f"{settings.hud_api_url}/templates/available",
106
+ headers=hud_headers(),
107
+ )
108
+ if resp.status_code != 200:
109
+ return [], []
110
+ data = resp.json()
111
+ return data.get("public_templates", []), data.get("private_templates", [])
112
+ except Exception:
113
+ return [], []
114
+
115
+
116
+ def _prompt_for_preset() -> tuple[str, bool] | None:
90
117
  """Ask the user to choose a preset when not provided.
91
118
 
92
- Returns None if the user cancels the selection.
119
+ Returns (preset_id, is_private) or None if the user cancels.
93
120
  """
121
+ # Fetch private templates from API
122
+ _, private_templates = _fetch_available_templates()
123
+
94
124
  try:
95
- choices = [
96
- {"name": "blank", "message": "blank"},
97
- {"name": "browser", "message": "browser"},
98
- {"name": "deep-research", "message": "deep-research"},
99
- {"name": "rubrics", "message": "rubrics"},
100
- {"name": "verilog-coding-template", "message": "verilog-coding-template"},
101
- {"name": "data-science-template", "message": "data-science-template"},
125
+ choices = [questionary.Choice(title=key, value=(key, False)) for key in PRESET_MAP] + [
126
+ questionary.Choice(title=t["id"], value=(t["id"], True)) for t in private_templates
102
127
  ]
103
- display_choices = [c["message"] for c in choices]
128
+
104
129
  selected = questionary.select(
105
- "Choose a preset", choices=display_choices, default=display_choices[0]
130
+ "Choose a preset",
131
+ choices=choices,
106
132
  ).ask()
107
133
  if not selected:
108
134
  return None # User cancelled
109
- for c in choices:
110
- if c["message"] == selected:
111
- return c["name"]
112
- return "blank"
135
+ return selected
113
136
  except KeyboardInterrupt:
114
137
  return None # User pressed Ctrl+C
115
138
  except Exception:
116
- return "blank"
139
+ return ("blank", False)
117
140
 
118
141
 
119
142
  def _download_tarball_repo(
@@ -142,6 +165,32 @@ def _download_tarball_repo(
142
165
  tmp_file.write(chunk)
143
166
  tmp_path = Path(tmp_file.name)
144
167
 
168
+ _extract_tarball(tmp_path, dest_dir, files_created)
169
+
170
+
171
+ def _download_private_template(template_id: str, dest_dir: Path, files_created: list[str]) -> None:
172
+ """Download a private template tarball from the HUD API."""
173
+ url = f"{settings.hud_api_url}/templates/private/{template_id}/download"
174
+
175
+ with (
176
+ tempfile.NamedTemporaryFile(delete=False) as tmp_file,
177
+ httpx.Client(timeout=120) as client,
178
+ client.stream("GET", url, headers=hud_headers()) as resp,
179
+ ):
180
+ if resp.status_code == 403:
181
+ raise RuntimeError("Access denied: your team does not have access to this template.")
182
+ if resp.status_code != 200:
183
+ raise RuntimeError(f"Failed to download private template (HTTP {resp.status_code})")
184
+ for chunk in resp.iter_bytes():
185
+ if chunk:
186
+ tmp_file.write(chunk)
187
+ tmp_path = Path(tmp_file.name)
188
+
189
+ _extract_tarball(tmp_path, dest_dir, files_created)
190
+
191
+
192
+ def _extract_tarball(tmp_path: Path, dest_dir: Path, files_created: list[str]) -> None:
193
+ """Extract a tarball into dest_dir, stripping the top-level directory."""
145
194
  try:
146
195
  with tarfile.open(tmp_path, mode="r:gz") as tar:
147
196
  members = tar.getmembers()
@@ -191,15 +240,26 @@ def create_environment(
191
240
 
192
241
  hud_console = HUDConsole()
193
242
 
243
+ is_private = False
244
+
194
245
  # Choose preset
195
246
  if preset:
196
- preset_normalized = preset.strip().lower()
247
+ preset_stripped = preset.strip()
248
+ preset_normalized = preset_stripped.lower()
249
+ # Check if the preset matches a private template (case-insensitive)
250
+ _, private_templates = _fetch_available_templates()
251
+ for t in private_templates:
252
+ if t["id"].lower() == preset_normalized:
253
+ # Preserve the original API ID for case-sensitive downstream use
254
+ preset_normalized = t["id"]
255
+ is_private = True
256
+ break
197
257
  else:
198
258
  preset_result = _prompt_for_preset()
199
259
  if preset_result is None:
200
260
  # User cancelled the selection
201
261
  raise typer.Exit(0)
202
- preset_normalized = preset_result
262
+ preset_normalized, is_private = preset_result
203
263
 
204
264
  # If no name is provided, use the preset name as the environment name
205
265
  if name is None:
@@ -209,7 +269,7 @@ def create_environment(
209
269
  # Always create a new directory based on the name
210
270
  target_dir = Path.cwd() / name if directory == "." else Path(directory) / name
211
271
 
212
- if preset_normalized not in PRESET_MAP:
272
+ if not is_private and preset_normalized not in PRESET_MAP:
213
273
  available = ", ".join(sorted(PRESET_MAP.keys()))
214
274
  hud_console.warning(
215
275
  f"Unknown preset '{preset_normalized}', defaulting to 'blank' (available: {available})"
@@ -225,32 +285,45 @@ def create_environment(
225
285
  else:
226
286
  hud_console.warning(f"Overwriting existing files in {target_dir}")
227
287
 
228
- # Download preset from GitHub
229
- repo_name = PRESET_MAP[preset_normalized]
230
- if repo_name is None:
231
- hud_console.error("Internal error: preset mapping missing repo name")
232
- raise typer.Exit(1)
233
-
234
288
  hud_console.header(f"Initializing HUD Environment: {name} (preset: {preset_normalized})")
235
- hud_console.section_title("Downloading template from GitHub")
236
- source_url = f"https://github.com/{GITHUB_OWNER}/{repo_name}"
237
- hud_console.info("Source: " + source_url)
238
-
239
289
  target_dir.mkdir(parents=True, exist_ok=True)
240
290
 
241
291
  started = time.time()
242
292
  files_created_dl: list[str] = []
243
- try:
244
- _download_tarball_repo(
245
- owner=GITHUB_OWNER,
246
- repo=repo_name,
247
- ref=GITHUB_BRANCH,
248
- dest_dir=target_dir,
249
- files_created=files_created_dl,
250
- )
251
- except Exception as e:
252
- hud_console.error(f"Failed to download preset '{preset_normalized}': {e}")
253
- raise typer.Exit(1) from None
293
+
294
+ if is_private:
295
+ hud_console.section_title("Downloading private template from HUD")
296
+ try:
297
+ _download_private_template(
298
+ template_id=preset_normalized,
299
+ dest_dir=target_dir,
300
+ files_created=files_created_dl,
301
+ )
302
+ except Exception as e:
303
+ hud_console.error(f"Failed to download private template '{preset_normalized}': {e}")
304
+ raise typer.Exit(1) from None
305
+ else:
306
+ # Download preset from GitHub
307
+ repo_name = PRESET_MAP[preset_normalized]
308
+ if repo_name is None:
309
+ hud_console.error("Internal error: preset mapping missing repo name")
310
+ raise typer.Exit(1)
311
+
312
+ hud_console.section_title("Downloading template from GitHub")
313
+ source_url = f"https://github.com/{GITHUB_OWNER}/{repo_name}"
314
+ hud_console.info("Source: " + source_url)
315
+
316
+ try:
317
+ _download_tarball_repo(
318
+ owner=GITHUB_OWNER,
319
+ repo=repo_name,
320
+ ref=GITHUB_BRANCH,
321
+ dest_dir=target_dir,
322
+ files_created=files_created_dl,
323
+ )
324
+ except Exception as e:
325
+ hud_console.error(f"Failed to download preset '{preset_normalized}': {e}")
326
+ raise typer.Exit(1) from None
254
327
 
255
328
  duration_ms = int((time.time() - started) * 1000)
256
329
  hud_console.success(
@@ -258,7 +331,7 @@ def create_environment(
258
331
  )
259
332
 
260
333
  # Replace placeholders in template files (only for blank preset)
261
- if preset_normalized == "blank":
334
+ if preset_normalized == "blank" and not is_private:
262
335
  hud_console.section_title("Customizing template files")
263
336
  modified_files = _replace_placeholders(target_dir, name)
264
337
  if modified_files:
@@ -195,6 +195,58 @@ class Environment(
195
195
  # Core Methods
196
196
  # =========================================================================
197
197
 
198
+ def _filtered_tools_for_session(self, session: Any) -> list[mcp_types.Tool]:
199
+ """Apply scenario-level tool filtering for a given session.
200
+
201
+ Filters in order:
202
+ 1. exclude_sources: remove tools from excluded connections
203
+ 2. exclude_tools: remove tools matching fnmatch patterns
204
+ 3. allowed_tools: rescue specific tools back from exclusions
205
+
206
+ Does NOT apply agent-level filtering (_agent_include/_agent_exclude).
207
+
208
+ Args:
209
+ session: The ScenarioSession to filter for, or None (no filtering).
210
+
211
+ Returns:
212
+ List of tools visible under the session's exclusions.
213
+ """
214
+ import fnmatch
215
+
216
+ tools = self._router.tools
217
+
218
+ if not session:
219
+ return tools
220
+
221
+ excluded_sources = set(session.exclude_sources) if session.exclude_sources else None
222
+ excluded_patterns = session.exclude_tools
223
+
224
+ if excluded_sources or excluded_patterns:
225
+ filtered = []
226
+ for tool in tools:
227
+ if excluded_sources:
228
+ source = self._router._tool_routing.get(tool.name, "")
229
+ if source in excluded_sources:
230
+ continue
231
+ if excluded_patterns and any(
232
+ fnmatch.fnmatch(tool.name, pat) for pat in excluded_patterns
233
+ ):
234
+ continue
235
+ filtered.append(tool)
236
+ tools = filtered
237
+
238
+ # Rescue: add back tools matching allowed_tools patterns
239
+ allowed_patterns = session.allowed_tools
240
+ if allowed_patterns:
241
+ visible_names = {t.name for t in tools}
242
+ for tool in self._router.tools:
243
+ if tool.name not in visible_names and any(
244
+ fnmatch.fnmatch(tool.name, pat) for pat in allowed_patterns
245
+ ):
246
+ tools.append(tool)
247
+
248
+ return tools
249
+
198
250
  def as_tools(self) -> list[mcp_types.Tool]:
199
251
  """Return tools in MCP format (base format).
200
252
 
@@ -207,37 +259,7 @@ class Environment(
207
259
  """
208
260
  import fnmatch
209
261
 
210
- tools = self._router.tools
211
-
212
- # Scenario-level exclusion (from @env.scenario(exclude_tools/exclude_sources))
213
- session = self._active_session
214
- if session:
215
- excluded_sources = set(session.exclude_sources) if session.exclude_sources else None
216
- excluded_patterns = session.exclude_tools
217
-
218
- if excluded_sources or excluded_patterns:
219
- filtered = []
220
- for tool in tools:
221
- if excluded_sources:
222
- source = self._router._tool_routing.get(tool.name, "")
223
- if source in excluded_sources:
224
- continue
225
- if excluded_patterns and any(
226
- fnmatch.fnmatch(tool.name, pat) for pat in excluded_patterns
227
- ):
228
- continue
229
- filtered.append(tool)
230
- tools = filtered
231
-
232
- # Rescue: add back tools matching allowed_tools patterns
233
- allowed_patterns = session.allowed_tools
234
- if allowed_patterns:
235
- visible_names = {t.name for t in tools}
236
- for tool in self._router.tools:
237
- if tool.name not in visible_names and any(
238
- fnmatch.fnmatch(tool.name, pat) for pat in allowed_patterns
239
- ):
240
- tools.append(tool)
262
+ tools = self._filtered_tools_for_session(self._active_session)
241
263
 
242
264
  # Apply agent-level filtering (from v4 allowed_tools/disallowed_tools)
243
265
  if self._agent_include is not None or self._agent_exclude is not None:
@@ -628,10 +650,16 @@ class Environment(
628
650
  return mcp_types.ReadResourceResult(contents=contents)
629
651
 
630
652
  async def _env_list_tools(self) -> list[mcp_types.Tool]:
631
- """Return all tools including those from connectors."""
653
+ """Return tools filtered by the active scenario session (if any).
654
+
655
+ When an MCP client has an active scenario session (set via get_prompt),
656
+ applies scenario-level tool exclusions so the agent only sees permitted tools.
657
+ """
632
658
  if not self._tool_routing_built:
633
659
  await self._build_tool_routing()
634
- return self._router.tools
660
+ session_id = _safe_session_id(None)
661
+ session = self._get_session(session_id)
662
+ return self._filtered_tools_for_session(session)
635
663
 
636
664
  async def _env_list_prompts(self) -> list[mcp_types.Prompt]:
637
665
  """Return all prompts including those from connectors."""
@@ -649,6 +677,19 @@ class Environment(
649
677
  """Route tool calls through our router (handles both local and connector tools)."""
650
678
  args = dict(arguments or {})
651
679
 
680
+ # Enforce scenario-level tool exclusions for MCP clients.
681
+ # Internal tools (underscore prefix, e.g. _hud_submit) are always allowed
682
+ # as they are infrastructure tools, not agent-facing.
683
+ if not name.startswith("_"):
684
+ session_id = _safe_session_id(None)
685
+ session = self._get_session(session_id)
686
+ if session:
687
+ if not self._tool_routing_built:
688
+ await self._build_tool_routing()
689
+ allowed_names = {t.name for t in self._filtered_tools_for_session(session)}
690
+ if name not in allowed_names:
691
+ raise ValueError(f"Tool '{name}' is not available in the current scenario.")
692
+
652
693
  # Extract trace context propagated via MCP request (meta or arguments)
653
694
  trace_id = args.pop("_hud_trace_id", None)
654
695
  meta = kwargs.get("_meta") or kwargs.get("meta")
@@ -740,3 +740,254 @@ class TestEnvironmentToolFiltering:
740
740
  assert "browser_navigate" in tool_names
741
741
  assert "browser_setup" not in tool_names # Excluded by *setup*
742
742
  assert "file_read" not in tool_names # Not included by browser_*
743
+
744
+
745
+ class TestMCPServerToolExclusion:
746
+ """Tests that scenario exclude_tools/exclude_sources/allowed_tools
747
+ are enforced on the MCP server path (_env_list_tools, _env_call_tool).
748
+ """
749
+
750
+ @pytest.mark.asyncio
751
+ async def test_env_list_tools_applies_scenario_filtering(self) -> None:
752
+ """_env_list_tools resolves the MCP session and applies scenario filtering.
753
+
754
+ The filtering logic itself (exclude_tools, exclude_sources, allowed_tools)
755
+ is tested thoroughly in test_scenarios.py::TestScenarioToolExclusion.
756
+ This test verifies the MCP server path wires up session lookup correctly.
757
+ """
758
+ from types import SimpleNamespace
759
+
760
+ import mcp.types as mcp_types
761
+ from mcp.server.lowlevel.server import request_ctx
762
+
763
+ from hud.environment import Environment
764
+ from hud.environment.connection import ConnectionConfig, ConnectionType, Connector
765
+
766
+ env = Environment("test-env")
767
+
768
+ @env.tool()
769
+ def browser_navigate(url: str) -> str:
770
+ """Navigate."""
771
+ return url
772
+
773
+ @env.tool()
774
+ def browser_screenshot() -> str:
775
+ """Screenshot."""
776
+ return "img"
777
+
778
+ @env.tool()
779
+ def bash(cmd: str) -> str:
780
+ """Run command."""
781
+ return cmd
782
+
783
+ connector = Connector(
784
+ transport={},
785
+ config=ConnectionConfig(),
786
+ name="remote-hub",
787
+ connection_type=ConnectionType.REMOTE,
788
+ )
789
+ connector._tools_cache = [
790
+ mcp_types.Tool(name="remote_a", inputSchema={"type": "object"}),
791
+ ]
792
+ env._connections["remote-hub"] = connector
793
+
794
+ @env.scenario(
795
+ "filtered",
796
+ exclude_tools=["browser_*"],
797
+ exclude_sources=["remote-hub"],
798
+ allowed_tools=["browser_navigate"],
799
+ )
800
+ async def filtered():
801
+ yield "Do it"
802
+ yield 1.0
803
+
804
+ await env._build_routing()
805
+
806
+ req = SimpleNamespace(
807
+ session=SimpleNamespace(),
808
+ request=SimpleNamespace(headers={"mcp-session-id": "test-session"}),
809
+ )
810
+ token = request_ctx.set(req) # type: ignore[arg-type]
811
+ try:
812
+ await env._env_get_prompt("test-env:filtered", {})
813
+ tools = await env._env_list_tools()
814
+ finally:
815
+ request_ctx.reset(token)
816
+
817
+ tool_names = [t.name for t in tools]
818
+ assert "bash" in tool_names
819
+ assert "browser_navigate" in tool_names # Rescued by allowed_tools
820
+ assert "browser_screenshot" not in tool_names # Excluded by pattern
821
+ assert "remote_a" not in tool_names # Excluded by source
822
+
823
+ @pytest.mark.asyncio
824
+ async def test_env_call_tool_rejects_excluded_tool(self) -> None:
825
+ """_env_call_tool raises ValueError for excluded tools."""
826
+ from types import SimpleNamespace
827
+
828
+ from mcp.server.lowlevel.server import request_ctx
829
+
830
+ from hud.environment import Environment
831
+
832
+ env = Environment("test-env")
833
+
834
+ @env.tool()
835
+ def browser_navigate(url: str) -> str:
836
+ """Navigate."""
837
+ return url
838
+
839
+ @env.tool()
840
+ def bash(cmd: str) -> str:
841
+ """Run command."""
842
+ return cmd
843
+
844
+ @env.scenario("headless", exclude_tools=["browser_*"])
845
+ async def headless():
846
+ yield "Do it"
847
+ yield 1.0
848
+
849
+ await env._build_routing()
850
+
851
+ req = SimpleNamespace(
852
+ session=SimpleNamespace(),
853
+ request=SimpleNamespace(headers={"mcp-session-id": "test-session-4"}),
854
+ )
855
+ token = request_ctx.set(req) # type: ignore[arg-type]
856
+ try:
857
+ await env._env_get_prompt("test-env:headless", {})
858
+ with pytest.raises(ValueError, match="not available"):
859
+ await env._env_call_tool("browser_navigate", {"url": "http://example.com"})
860
+ finally:
861
+ request_ctx.reset(token)
862
+
863
+ @pytest.mark.asyncio
864
+ async def test_env_call_tool_allows_non_excluded_tool(self) -> None:
865
+ """_env_call_tool succeeds for non-excluded tools."""
866
+ from types import SimpleNamespace
867
+
868
+ from mcp.server.lowlevel.server import request_ctx
869
+
870
+ from hud.environment import Environment
871
+
872
+ env = Environment("test-env")
873
+
874
+ @env.tool()
875
+ def browser_navigate(url: str) -> str:
876
+ """Navigate."""
877
+ return url
878
+
879
+ @env.tool()
880
+ def bash(cmd: str) -> str:
881
+ """Run command."""
882
+ return cmd
883
+
884
+ @env.scenario("headless", exclude_tools=["browser_*"])
885
+ async def headless():
886
+ yield "Do it"
887
+ yield 1.0
888
+
889
+ await env._build_routing()
890
+
891
+ req = SimpleNamespace(
892
+ session=SimpleNamespace(),
893
+ request=SimpleNamespace(headers={"mcp-session-id": "test-session-5"}, scope={}),
894
+ )
895
+ token = request_ctx.set(req) # type: ignore[arg-type]
896
+ try:
897
+ await env._env_get_prompt("test-env:headless", {})
898
+ # Should not raise - bash is not excluded
899
+ result = await env._env_call_tool("bash", {"cmd": "echo hi"})
900
+ assert result is not None
901
+ finally:
902
+ request_ctx.reset(token)
903
+
904
+ @pytest.mark.asyncio
905
+ async def test_env_call_tool_allows_internal_tools(self) -> None:
906
+ """_env_call_tool always allows underscore-prefixed internal tools."""
907
+ from types import SimpleNamespace
908
+
909
+ from mcp.server.lowlevel.server import request_ctx
910
+
911
+ from hud.environment import Environment
912
+
913
+ env = Environment("test-env")
914
+
915
+ @env.tool()
916
+ def browser_navigate(url: str) -> str:
917
+ """Navigate."""
918
+ return url
919
+
920
+ @env.scenario("headless", exclude_tools=["*"])
921
+ async def headless():
922
+ answer = yield "Do it"
923
+ yield 1.0 if answer == "ok" else 0.0
924
+
925
+ await env._build_routing()
926
+
927
+ req = SimpleNamespace(
928
+ session=SimpleNamespace(),
929
+ request=SimpleNamespace(headers={"mcp-session-id": "test-session-6"}, scope={}),
930
+ )
931
+ token = request_ctx.set(req) # type: ignore[arg-type]
932
+ try:
933
+ await env._env_get_prompt("test-env:headless", {})
934
+ # _hud_submit should always work even with exclude_tools=["*"]
935
+ result = await env._env_call_tool(
936
+ "_hud_submit", {"scenario": "headless", "answer": "ok"}
937
+ )
938
+ assert result is not None
939
+ finally:
940
+ request_ctx.reset(token)
941
+
942
+ @pytest.mark.asyncio
943
+ async def test_env_list_tools_no_session_returns_all(self) -> None:
944
+ """_env_list_tools returns all tools when no scenario session is active."""
945
+ from hud.environment import Environment
946
+
947
+ env = Environment("test-env")
948
+
949
+ @env.tool()
950
+ def browser_navigate(url: str) -> str:
951
+ """Navigate."""
952
+ return url
953
+
954
+ @env.tool()
955
+ def bash(cmd: str) -> str:
956
+ """Run command."""
957
+ return cmd
958
+
959
+ @env.scenario("headless", exclude_tools=["browser_*"])
960
+ async def headless():
961
+ yield "Do it"
962
+ yield 1.0
963
+
964
+ await env._build_routing()
965
+
966
+ # No scenario setup, no request_ctx - should return all tools
967
+ tools = await env._env_list_tools()
968
+ tool_names = [t.name for t in tools]
969
+ assert "browser_navigate" in tool_names
970
+ assert "bash" in tool_names
971
+
972
+ @pytest.mark.asyncio
973
+ async def test_env_call_tool_no_session_allows_all(self) -> None:
974
+ """_env_call_tool allows any tool when no scenario session is active."""
975
+ from hud.environment import Environment
976
+
977
+ env = Environment("test-env")
978
+
979
+ @env.tool()
980
+ def browser_navigate(url: str) -> str:
981
+ """Navigate."""
982
+ return url
983
+
984
+ @env.scenario("headless", exclude_tools=["browser_*"])
985
+ async def headless():
986
+ yield "Do it"
987
+ yield 1.0
988
+
989
+ await env._build_routing()
990
+
991
+ # No scenario setup - should allow any tool
992
+ result = await env._env_call_tool("browser_navigate", {"url": "http://example.com"})
993
+ assert result is not None
@@ -22,6 +22,9 @@ if TYPE_CHECKING:
22
22
 
23
23
  logger = logging.getLogger(__name__)
24
24
 
25
+ GEMINI_DRAG_INSET = 25
26
+ DISPLAY_DRAG_INSET_PIXELS = 20
27
+
25
28
  SUPPORTED_GEMINI_COMPUTER_USE_MODELS = (
26
29
  "gemini-2.5-computer-use-preview-10-2025",
27
30
  "gemini-3-flash-preview",
@@ -168,6 +171,30 @@ class GeminiComputerTool(HudComputerTool):
168
171
  **kwargs,
169
172
  )
170
173
 
174
+ def _inset_drag_coordinate(self, value: int) -> int:
175
+ """Keep Gemini normalized drag endpoints away from display edges."""
176
+ if (
177
+ self.coordinate_space is None
178
+ or not isinstance(value, int | float)
179
+ or not 0 <= value <= self.coordinate_space
180
+ ):
181
+ return value
182
+
183
+ max_value = max(self.coordinate_space - GEMINI_DRAG_INSET, GEMINI_DRAG_INSET)
184
+ return min(max(value, GEMINI_DRAG_INSET), max_value)
185
+
186
+ def _inset_scaled_drag_path(self, path: list[tuple[int, int]]) -> list[tuple[int, int]]:
187
+ """Keep scaled drag points inside the display so they do not hit OS/window edges."""
188
+ max_x = max(self.environment_width - 1 - DISPLAY_DRAG_INSET_PIXELS, 0)
189
+ max_y = max(self.environment_height - 1 - DISPLAY_DRAG_INSET_PIXELS, 0)
190
+ return [
191
+ (
192
+ min(max(int(x), DISPLAY_DRAG_INSET_PIXELS), max_x),
193
+ min(max(int(y), DISPLAY_DRAG_INSET_PIXELS), max_y),
194
+ )
195
+ for x, y in path
196
+ ]
197
+
171
198
  async def __call__(
172
199
  self,
173
200
  action: str = ACTION_FIELD,
@@ -381,7 +408,16 @@ class GeminiComputerTool(HudComputerTool):
381
408
  message="x, y, destination_x, and destination_y are required",
382
409
  )
383
410
  )
384
- path = self._scale_path([(x, y), (destination_x, destination_y)])
411
+ path = self._scale_path(
412
+ [
413
+ (self._inset_drag_coordinate(x), self._inset_drag_coordinate(y)),
414
+ (
415
+ self._inset_drag_coordinate(destination_x),
416
+ self._inset_drag_coordinate(destination_y),
417
+ ),
418
+ ]
419
+ )
420
+ path = self._inset_scaled_drag_path(path)
385
421
  result = await self.executor.drag(path=path)
386
422
  return await _finalize(result)
387
423