hud-python 0.4.56__tar.gz → 0.4.57__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.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- {hud_python-0.4.56 → hud_python-0.4.57}/PKG-INFO +1 -1
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/dev.py +24 -4
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/flows/dev.py +6 -17
- hud_python-0.4.57/hud/cli/tests/test_convert.py +367 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/version_check.py +7 -6
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/base.py +28 -2
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/fastmcp.py +2 -2
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/mcp_use.py +2 -2
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/tests/test_protocol.py +9 -3
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/server.py +59 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_version.py +1 -1
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/version.py +1 -1
- {hud_python-0.4.56 → hud_python-0.4.57}/pyproject.toml +1 -1
- {hud_python-0.4.56 → hud_python-0.4.57}/.gitignore +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/LICENSE +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/blank/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/blank/environment/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/blank/environment/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/blank/server/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/blank/server/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/2048/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/2048/backend/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/todo/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/environment/todo/backend/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/browser/server/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/deepresearch/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/deepresearch/environment/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/deepresearch/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/deepresearch/server/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/remote_browser/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/remote_browser/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/remote_browser/src/hud_controller/providers/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/rubrics/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/rubrics/environment/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/rubrics/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/rubrics/server/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/text_2048/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/environments/text_2048/pyproject.toml +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/examples/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/__main__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/base.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/claude.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/grounded_openai.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/langchain.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/lite_llm.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/misc/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/misc/integration_test_agent.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/misc/response_agent.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/openai.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/openai_chat_generic.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_base.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_base_runtime.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_claude.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_client.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_grounded_openai_agent.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/tests/test_openai.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/agents/utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/__main__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/analyze.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/build.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/clone.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/debug.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/eval.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/flows/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/flows/tasks.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/get.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/list_func.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/pull.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/push.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/remove.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/celebrate.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/display.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/gpu.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/gpu_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/local_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/presets.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/remote_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/rl_api.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/viewer.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/vllm.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/rl/wait_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_analyze.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_analyze_metadata.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_analyze_module.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_build.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_build_failure.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_build_module.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_cli_init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_cli_main.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_cli_more_wrappers.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_cli_root.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_clone.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_cursor.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_debug.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_eval.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_list_func.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_main_module.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_mcp_server.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_pull.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_push.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_push_happy.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_push_wrapper.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_registry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/tests/test_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/cursor.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/docker.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/env_check.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/environment.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/interactive.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/local_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/logging.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/metadata.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/package_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/registry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/remote_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/server.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/source_hash.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tasks.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_docker.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_docker_hints.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_env_check.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_environment.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_interactive_module.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_local_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_logging_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_metadata.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_package_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_registry_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_remote_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_runner_modules.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_source_hash.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/cli/utils/tests/test_tasks.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/tests/test_client_integration.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/tests/test_fastmcp.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/tests/test_mcp_use_retry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/utils/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/utils/mcp_use_retry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/utils/retry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/clients/utils/retry_transport.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/parallel.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/tests/test_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/tests/test_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/datasets/utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/misc/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/misc/claude_plays_pokemon.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/native/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/native/comparator.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/native/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/native/tests/test_comparator.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/native/tests/test_native_init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/collector.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/context.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/exporters.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/instrumentation.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/processors.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/tests/test_instrumentation.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/otel/tests/test_processors.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/py.typed +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/README.md +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/actor.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/buffer.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/chat_template.jinja +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/distributed.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/learner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/tests/test_learner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/train.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/types.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/utils/start_vllm_server.sh +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/rl/vllm_adapter.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/samples/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/samples/browser.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/context.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/helper/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/low_level.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/router.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_add_tool.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_context.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_mcp_server_handlers.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_mcp_server_integration.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_mcp_server_more.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_run_wrapper.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_server_extra.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/server/tests/test_sigterm_runner.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/settings.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/exceptions.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/hints.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/requests.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/tests/test_exceptions.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/tests/test_hints.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/shared/tests/test_requests.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/async_context.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/instrument.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/job.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/replay.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/test_async_context.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/test_instrument.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/test_job.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/test_replay.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/tests/test_trace.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/telemetry/trace.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/base.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/bash.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/anthropic.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/hud.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/openai.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/qwen.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/computer/settings.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/edit.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/base.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/pyautogui.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/tests/test_base_executor.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/tests/test_pyautogui_executor.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/executors/xdo.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/config.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/grounded_tool.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/grounder.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/grounding/tests/test_grounded_tool.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/playwright.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/response.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/submit.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_base.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_bash.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_bash_extended.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_computer.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_computer_actions.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_edit.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_playwright_tool.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_response.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_submit.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_tools.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_tools_init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_types.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/tests/test_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/types.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/tools/utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/types.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/agent_factories.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/async_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/group_eval.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/hud_console.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/mcp.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/pretty_errors.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/progress.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/task_tracking.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tasks.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/telemetry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/__init__.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_agent_factories.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_async_utils.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_init.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_mcp.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_pretty_errors.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_progress.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_tasks.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_telemetry.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tests/test_tool_shorthand.py +0 -0
- {hud_python-0.4.56 → hud_python-0.4.57}/hud/utils/tool_shorthand.py +0 -0
|
@@ -237,7 +237,7 @@ async def run_mcp_module(
|
|
|
237
237
|
|
|
238
238
|
from hud.cli.flows.dev import create_dynamic_trace
|
|
239
239
|
|
|
240
|
-
live_trace_url = await create_dynamic_trace(
|
|
240
|
+
_, live_trace_url = await create_dynamic_trace(
|
|
241
241
|
mcp_config=local_mcp_config,
|
|
242
242
|
build_status=False,
|
|
243
243
|
environment_name=mcp_server.name or "mcp-server",
|
|
@@ -608,7 +608,7 @@ def run_docker_dev_server(
|
|
|
608
608
|
"headers": {},
|
|
609
609
|
}
|
|
610
610
|
}
|
|
611
|
-
live_trace_url = _asy.run(
|
|
611
|
+
_, live_trace_url = _asy.run(
|
|
612
612
|
create_dynamic_trace(
|
|
613
613
|
mcp_config=local_mcp_config,
|
|
614
614
|
build_status=True,
|
|
@@ -661,13 +661,33 @@ def run_docker_dev_server(
|
|
|
661
661
|
# Create and run proxy with HUD helpers
|
|
662
662
|
async def run_proxy() -> None:
|
|
663
663
|
from fastmcp import FastMCP
|
|
664
|
+
from fastmcp.server.proxy import ProxyClient
|
|
664
665
|
|
|
665
|
-
# Create
|
|
666
|
-
|
|
666
|
+
# Create ProxyClient without custom log handler since we capture Docker logs directly
|
|
667
|
+
proxy_client = ProxyClient(mcp_config, name="HUD Docker Dev Proxy")
|
|
668
|
+
|
|
669
|
+
# Extract container name from docker args and store for logs endpoint
|
|
670
|
+
docker_cmd = mcp_config["docker"]["args"]
|
|
671
|
+
container_name = None
|
|
672
|
+
for i, arg in enumerate(docker_cmd):
|
|
673
|
+
if arg == "--name" and i + 1 < len(docker_cmd):
|
|
674
|
+
container_name = docker_cmd[i + 1]
|
|
675
|
+
break
|
|
676
|
+
|
|
677
|
+
if container_name:
|
|
678
|
+
# Store container name for logs endpoint to use
|
|
679
|
+
os.environ["_HUD_DEV_DOCKER_CONTAINER"] = container_name
|
|
680
|
+
hud_console.debug(f"Docker container: {container_name}")
|
|
681
|
+
|
|
682
|
+
# Create FastMCP proxy using the ProxyClient
|
|
683
|
+
fastmcp_proxy = FastMCP.as_proxy(proxy_client)
|
|
667
684
|
|
|
668
685
|
# Wrap in MCPServer to get /docs and REST wrappers
|
|
669
686
|
proxy = MCPServer(name="HUD Docker Dev Proxy")
|
|
670
687
|
|
|
688
|
+
# Enable logs endpoint on HTTP server
|
|
689
|
+
os.environ["_HUD_DEV_LOGS_PROVIDER"] = "enabled"
|
|
690
|
+
|
|
671
691
|
# Import all tools from the FastMCP proxy
|
|
672
692
|
await proxy.import_server(fastmcp_proxy)
|
|
673
693
|
|
|
@@ -18,7 +18,7 @@ async def create_dynamic_trace(
|
|
|
18
18
|
mcp_config: dict[str, dict[str, Any]],
|
|
19
19
|
build_status: bool,
|
|
20
20
|
environment_name: str,
|
|
21
|
-
) -> str | None:
|
|
21
|
+
) -> tuple[str | None, str | None]:
|
|
22
22
|
"""
|
|
23
23
|
Create a dynamic trace for HUD dev sessions when running in HTTP mode.
|
|
24
24
|
|
|
@@ -43,27 +43,16 @@ async def create_dynamic_trace(
|
|
|
43
43
|
api_key = settings.api_key
|
|
44
44
|
if not api_key:
|
|
45
45
|
logger.warning("Skipping dynamic trace creation; missing HUD_API_KEY")
|
|
46
|
-
return None
|
|
46
|
+
return None, None
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
resp = await make_request("POST", url=url, json=payload, api_key=api_key)
|
|
50
50
|
# New API returns an id; construct the URL as https://hud.so/trace/{id}
|
|
51
|
-
trace_id =
|
|
52
|
-
if isinstance(resp, dict):
|
|
53
|
-
trace_id = resp.get("id")
|
|
54
|
-
if trace_id is None:
|
|
55
|
-
data = resp.get("data", {}) or {}
|
|
56
|
-
if isinstance(data, dict):
|
|
57
|
-
trace_id = data.get("id")
|
|
58
|
-
# Backcompat: if url is provided directly
|
|
59
|
-
if not trace_id:
|
|
60
|
-
direct_url = resp.get("url") or (resp.get("data", {}) or {}).get("url")
|
|
61
|
-
if isinstance(direct_url, str) and direct_url:
|
|
62
|
-
return direct_url
|
|
51
|
+
trace_id = resp.get("id")
|
|
63
52
|
|
|
64
53
|
if isinstance(trace_id, str) and trace_id:
|
|
65
|
-
return f"https://hud.so/trace/{trace_id}"
|
|
66
|
-
return None
|
|
54
|
+
return trace_id, f"https://hud.so/trace/{trace_id}"
|
|
55
|
+
return None, None
|
|
67
56
|
except Exception as e:
|
|
68
57
|
# Do not interrupt dev flow
|
|
69
58
|
try:
|
|
@@ -71,7 +60,7 @@ async def create_dynamic_trace(
|
|
|
71
60
|
logger.warning("Failed to create dynamic dev trace: %s | payload=%s", e, preview)
|
|
72
61
|
except Exception:
|
|
73
62
|
logger.warning("Failed to create dynamic dev trace: %s", e)
|
|
74
|
-
return None
|
|
63
|
+
return None, None
|
|
75
64
|
|
|
76
65
|
|
|
77
66
|
def show_dev_ui(
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Tests for the convert command."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from hud.cli.flows.tasks import convert_tasks_to_remote
|
|
11
|
+
from hud.types import Task
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestConvertCommand:
|
|
15
|
+
"""Test the convert command functionality."""
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def temp_tasks_file(self, tmp_path):
|
|
19
|
+
"""Create a temporary tasks file."""
|
|
20
|
+
tasks = [
|
|
21
|
+
{
|
|
22
|
+
"prompt": "Test task 1",
|
|
23
|
+
"mcp_config": {
|
|
24
|
+
"local": {
|
|
25
|
+
"command": "docker",
|
|
26
|
+
"args": ["run", "--rm", "-i", "test-image:latest"],
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
tasks_file = tmp_path / "tasks.json"
|
|
32
|
+
tasks_file.write_text(json.dumps(tasks))
|
|
33
|
+
return tasks_file
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_env_dir(self, tmp_path):
|
|
37
|
+
"""Create a mock environment directory with lock file."""
|
|
38
|
+
env_dir = tmp_path / "env"
|
|
39
|
+
env_dir.mkdir()
|
|
40
|
+
|
|
41
|
+
# Create lock file
|
|
42
|
+
lock_data = {
|
|
43
|
+
"images": {
|
|
44
|
+
"remote": "registry.hud.so/test-org/test-env:v1.0.0",
|
|
45
|
+
"local": "test-env:latest",
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
lock_file = env_dir / "hud.lock.yaml"
|
|
49
|
+
import yaml
|
|
50
|
+
|
|
51
|
+
lock_file.write_text(yaml.dump(lock_data))
|
|
52
|
+
|
|
53
|
+
return env_dir
|
|
54
|
+
|
|
55
|
+
@patch("hud.cli.flows.tasks._derive_remote_image")
|
|
56
|
+
@patch("hud.cli.flows.tasks._ensure_pushed")
|
|
57
|
+
@patch("hud.cli.flows.tasks.find_environment_dir")
|
|
58
|
+
@patch("hud.cli.flows.tasks.load_tasks")
|
|
59
|
+
@patch("hud.settings.settings")
|
|
60
|
+
def test_convert_tasks_basic(
|
|
61
|
+
self,
|
|
62
|
+
mock_settings,
|
|
63
|
+
mock_load_tasks,
|
|
64
|
+
mock_find_env,
|
|
65
|
+
mock_ensure_pushed,
|
|
66
|
+
mock_derive_remote,
|
|
67
|
+
temp_tasks_file,
|
|
68
|
+
mock_env_dir,
|
|
69
|
+
):
|
|
70
|
+
"""Test basic task conversion from local to remote."""
|
|
71
|
+
# Setup mocks
|
|
72
|
+
mock_settings.api_key = "test-api-key"
|
|
73
|
+
mock_find_env.return_value = mock_env_dir
|
|
74
|
+
|
|
75
|
+
# Mock the push check to return updated lock data
|
|
76
|
+
mock_ensure_pushed.return_value = {
|
|
77
|
+
"images": {
|
|
78
|
+
"remote": "registry.hud.so/test-org/test-env:v1.0.0",
|
|
79
|
+
"local": "test-env:v1.0.0",
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Mock derive remote image
|
|
84
|
+
mock_derive_remote.return_value = "registry.hud.so/test-org/test-env:v1.0.0"
|
|
85
|
+
|
|
86
|
+
task = Task(
|
|
87
|
+
prompt="Test task",
|
|
88
|
+
mcp_config={
|
|
89
|
+
"local": {"command": "docker", "args": ["run", "--rm", "-i", "test-image:latest"]}
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
raw_task = {
|
|
93
|
+
"prompt": "Test task",
|
|
94
|
+
"mcp_config": {
|
|
95
|
+
"local": {"command": "docker", "args": ["run", "--rm", "-i", "test-image:latest"]}
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
mock_load_tasks.side_effect = [[task], [raw_task]]
|
|
100
|
+
|
|
101
|
+
# Run conversion
|
|
102
|
+
result_path = convert_tasks_to_remote(str(temp_tasks_file))
|
|
103
|
+
|
|
104
|
+
# Check result
|
|
105
|
+
assert result_path.endswith("remote_tasks.json")
|
|
106
|
+
assert Path(result_path).exists()
|
|
107
|
+
|
|
108
|
+
# Verify converted content
|
|
109
|
+
with open(result_path) as f:
|
|
110
|
+
converted_tasks = json.load(f)
|
|
111
|
+
|
|
112
|
+
assert len(converted_tasks) == 1
|
|
113
|
+
assert "hud" in converted_tasks[0]["mcp_config"]
|
|
114
|
+
assert converted_tasks[0]["mcp_config"]["hud"]["url"] == "https://mcp.hud.so/v3/mcp"
|
|
115
|
+
|
|
116
|
+
@patch("hud.settings.settings")
|
|
117
|
+
def test_convert_missing_api_key(self, mock_settings, temp_tasks_file):
|
|
118
|
+
"""Test that conversion fails without API key."""
|
|
119
|
+
mock_settings.api_key = ""
|
|
120
|
+
|
|
121
|
+
with pytest.raises(typer.Exit):
|
|
122
|
+
convert_tasks_to_remote(str(temp_tasks_file))
|
|
123
|
+
|
|
124
|
+
@patch("hud.cli.flows.tasks.find_environment_dir")
|
|
125
|
+
@patch("hud.cli.flows.tasks.load_tasks")
|
|
126
|
+
@patch("hud.settings.settings")
|
|
127
|
+
def test_convert_already_remote(
|
|
128
|
+
self, mock_settings, mock_load_tasks, mock_find_env, temp_tasks_file
|
|
129
|
+
):
|
|
130
|
+
"""Test that already remote tasks are not converted again."""
|
|
131
|
+
mock_settings.api_key = "test-api-key"
|
|
132
|
+
mock_find_env.return_value = None # No env dir needed for remote tasks
|
|
133
|
+
|
|
134
|
+
# Create task that's already remote
|
|
135
|
+
task = Task(
|
|
136
|
+
prompt="Test task",
|
|
137
|
+
mcp_config={
|
|
138
|
+
"remote": {
|
|
139
|
+
"url": "https://mcp.hud.so",
|
|
140
|
+
"headers": {"Mcp-Image": "registry.hud.so/test/image:v1"},
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
mock_load_tasks.return_value = [task]
|
|
146
|
+
|
|
147
|
+
# Should return original path without modification
|
|
148
|
+
result_path = convert_tasks_to_remote(str(temp_tasks_file))
|
|
149
|
+
assert result_path == str(temp_tasks_file)
|
|
150
|
+
|
|
151
|
+
@patch("hud.cli.flows.tasks.find_environment_dir")
|
|
152
|
+
@patch("hud.cli.flows.tasks.load_tasks")
|
|
153
|
+
@patch("hud.settings.settings")
|
|
154
|
+
def test_convert_no_environment(
|
|
155
|
+
self, mock_settings, mock_load_tasks, mock_find_env, temp_tasks_file
|
|
156
|
+
):
|
|
157
|
+
"""Test that conversion fails when no environment is found."""
|
|
158
|
+
mock_settings.api_key = "test-api-key"
|
|
159
|
+
mock_find_env.return_value = None
|
|
160
|
+
|
|
161
|
+
task = Task(
|
|
162
|
+
prompt="Test task",
|
|
163
|
+
mcp_config={
|
|
164
|
+
"local": {"command": "docker", "args": ["run", "--rm", "-i", "test-image:latest"]}
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
mock_load_tasks.return_value = [task]
|
|
169
|
+
|
|
170
|
+
with pytest.raises(typer.Exit):
|
|
171
|
+
convert_tasks_to_remote(str(temp_tasks_file))
|
|
172
|
+
|
|
173
|
+
@patch("hud.utils.hud_console.hud_console.confirm")
|
|
174
|
+
@patch("hud.cli.flows.tasks._derive_remote_image")
|
|
175
|
+
@patch("hud.cli.flows.tasks._ensure_pushed")
|
|
176
|
+
@patch("hud.cli.flows.tasks.find_environment_dir")
|
|
177
|
+
@patch("hud.cli.flows.tasks.load_tasks")
|
|
178
|
+
@patch("hud.settings.settings")
|
|
179
|
+
def test_convert_with_env_vars(
|
|
180
|
+
self,
|
|
181
|
+
mock_settings,
|
|
182
|
+
mock_load_tasks,
|
|
183
|
+
mock_find_env,
|
|
184
|
+
mock_ensure_pushed,
|
|
185
|
+
mock_derive_remote,
|
|
186
|
+
mock_confirm,
|
|
187
|
+
temp_tasks_file,
|
|
188
|
+
mock_env_dir,
|
|
189
|
+
):
|
|
190
|
+
"""Test conversion includes environment variables as headers."""
|
|
191
|
+
mock_settings.api_key = "test-api-key"
|
|
192
|
+
mock_find_env.return_value = mock_env_dir
|
|
193
|
+
mock_confirm.return_value = True # Always confirm in tests
|
|
194
|
+
|
|
195
|
+
# Mock the push check to return updated lock data
|
|
196
|
+
mock_ensure_pushed.return_value = {
|
|
197
|
+
"images": {
|
|
198
|
+
"remote": "registry.hud.so/test-org/test-env:v1.0.0",
|
|
199
|
+
"local": "test-env:v1.0.0",
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Mock derive remote image
|
|
204
|
+
mock_derive_remote.return_value = "registry.hud.so/test-org/test-env:v1.0.0"
|
|
205
|
+
|
|
206
|
+
# Add .env file with API keys
|
|
207
|
+
env_file = mock_env_dir / ".env"
|
|
208
|
+
env_file.write_text("OPENAI_API_KEY=sk-test123\nANTHROPIC_API_KEY=sk-ant456")
|
|
209
|
+
|
|
210
|
+
task = Task(
|
|
211
|
+
prompt="Test task",
|
|
212
|
+
mcp_config={
|
|
213
|
+
"local": {
|
|
214
|
+
"command": "docker",
|
|
215
|
+
"args": ["run", "--rm", "-i", "-e", "OPENAI_API_KEY", "test-image:latest"],
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
raw_task = task.model_dump()
|
|
220
|
+
|
|
221
|
+
mock_load_tasks.side_effect = [[task], [raw_task]]
|
|
222
|
+
|
|
223
|
+
# Run conversion
|
|
224
|
+
result_path = convert_tasks_to_remote(str(temp_tasks_file))
|
|
225
|
+
|
|
226
|
+
# Verify headers include env vars
|
|
227
|
+
with open(result_path) as f:
|
|
228
|
+
converted_tasks = json.load(f)
|
|
229
|
+
|
|
230
|
+
headers = converted_tasks[0]["mcp_config"]["hud"]["headers"]
|
|
231
|
+
assert "Env-Openai-Api-Key" in headers
|
|
232
|
+
assert headers["Env-Openai-Api-Key"] == "${OPENAI_API_KEY}"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestConvertHelperFunctions:
|
|
236
|
+
"""Test helper functions used by convert command."""
|
|
237
|
+
|
|
238
|
+
def test_env_var_to_header_key(self):
|
|
239
|
+
"""Test environment variable name conversion to header format."""
|
|
240
|
+
from hud.cli.flows.tasks import _env_var_to_header_key
|
|
241
|
+
|
|
242
|
+
assert _env_var_to_header_key("OPENAI_API_KEY") == "Env-Openai-Api-Key"
|
|
243
|
+
assert _env_var_to_header_key("ANTHROPIC_API_KEY") == "Env-Anthropic-Api-Key"
|
|
244
|
+
assert _env_var_to_header_key("SIMPLE") == "Env-Simple"
|
|
245
|
+
assert _env_var_to_header_key("MULTIPLE_WORD_VAR") == "Env-Multiple-Word-Var"
|
|
246
|
+
|
|
247
|
+
def test_extract_dotenv_api_key_vars(self):
|
|
248
|
+
"""Test extraction of API-like variables from .env file."""
|
|
249
|
+
# Create test env directory with .env file
|
|
250
|
+
import tempfile
|
|
251
|
+
|
|
252
|
+
from hud.cli.flows.tasks import _extract_dotenv_api_key_vars
|
|
253
|
+
|
|
254
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
255
|
+
env_dir = Path(tmpdir)
|
|
256
|
+
env_file = env_dir / ".env"
|
|
257
|
+
env_file.write_text("""
|
|
258
|
+
# Test .env file
|
|
259
|
+
OPENAI_API_KEY=sk-test123
|
|
260
|
+
ANTHROPIC_API_KEY=sk-ant456
|
|
261
|
+
SOME_TOKEN=abc123
|
|
262
|
+
CLIENT_SECRET=secret789
|
|
263
|
+
USER_PASSWORD=pass123
|
|
264
|
+
REGULAR_VAR=not_included
|
|
265
|
+
HUD_API_URL=https://api.hud.so
|
|
266
|
+
""")
|
|
267
|
+
|
|
268
|
+
result = _extract_dotenv_api_key_vars(env_dir)
|
|
269
|
+
|
|
270
|
+
# Should include only API-like variables
|
|
271
|
+
assert "OPENAI_API_KEY" in result
|
|
272
|
+
assert "ANTHROPIC_API_KEY" in result
|
|
273
|
+
assert "SOME_TOKEN" in result
|
|
274
|
+
assert "CLIENT_SECRET" in result
|
|
275
|
+
assert "USER_PASSWORD" in result
|
|
276
|
+
assert "REGULAR_VAR" not in result
|
|
277
|
+
assert "HUD_API_URL" in result # API in name, so it's included
|
|
278
|
+
|
|
279
|
+
def test_is_remote_url(self):
|
|
280
|
+
"""Test remote URL detection."""
|
|
281
|
+
from hud.cli.flows.tasks import _is_remote_url
|
|
282
|
+
|
|
283
|
+
# This function matches URLs with domain names (not localhost or IPs)
|
|
284
|
+
assert _is_remote_url("https://mcp.hud.so")
|
|
285
|
+
assert _is_remote_url("http://mcp.hud.so")
|
|
286
|
+
assert _is_remote_url("https://mcp.hud.so/some/path")
|
|
287
|
+
assert _is_remote_url("https://example.com") # Also matches
|
|
288
|
+
assert not _is_remote_url("http://localhost:8000") # localhost doesn't match
|
|
289
|
+
assert not _is_remote_url("file:///path/to/file") # file:// doesn't match
|
|
290
|
+
|
|
291
|
+
def test_extract_env_vars_from_docker_args(self):
|
|
292
|
+
"""Test extraction of environment variables from docker arguments."""
|
|
293
|
+
from hud.cli.flows.tasks import _extract_env_vars_from_docker_args
|
|
294
|
+
|
|
295
|
+
# Test with various docker arg formats
|
|
296
|
+
args = [
|
|
297
|
+
"run",
|
|
298
|
+
"--rm",
|
|
299
|
+
"-i",
|
|
300
|
+
"-e",
|
|
301
|
+
"VAR1",
|
|
302
|
+
"-e",
|
|
303
|
+
"VAR2=value",
|
|
304
|
+
"--env",
|
|
305
|
+
"VAR3",
|
|
306
|
+
"--env=VAR4",
|
|
307
|
+
# Note: -eFOO compact form is not supported by the implementation
|
|
308
|
+
"--env-file",
|
|
309
|
+
".env",
|
|
310
|
+
"-p",
|
|
311
|
+
"8080:80",
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
result = _extract_env_vars_from_docker_args(args)
|
|
315
|
+
|
|
316
|
+
assert "VAR1" in result
|
|
317
|
+
assert "VAR2" in result
|
|
318
|
+
assert "VAR3" in result
|
|
319
|
+
assert "VAR4" in result
|
|
320
|
+
# FOO is not extracted because -eFOO compact form is not supported
|
|
321
|
+
assert len(result) == 4
|
|
322
|
+
|
|
323
|
+
def test_derive_remote_image(self):
|
|
324
|
+
"""Test deriving remote image from lock data."""
|
|
325
|
+
from hud.cli.flows.tasks import _derive_remote_image
|
|
326
|
+
|
|
327
|
+
# The function derives remote image from images.local, not images.remote
|
|
328
|
+
lock_data = {"images": {"local": "test-env:v1.0.0"}}
|
|
329
|
+
result = _derive_remote_image(lock_data)
|
|
330
|
+
assert result == "test-env:v1.0.0"
|
|
331
|
+
|
|
332
|
+
# Test fallback to legacy format
|
|
333
|
+
lock_data = {
|
|
334
|
+
"image": "test-org/test-env:v1.0.0",
|
|
335
|
+
}
|
|
336
|
+
result = _derive_remote_image(lock_data)
|
|
337
|
+
assert result == "test-org/test-env:v1.0.0"
|
|
338
|
+
|
|
339
|
+
def test_extract_vars_from_task_configs(self):
|
|
340
|
+
"""Test extraction of env vars from task configurations."""
|
|
341
|
+
from hud.cli.flows.tasks import _extract_vars_from_task_configs
|
|
342
|
+
|
|
343
|
+
raw_tasks = [
|
|
344
|
+
{
|
|
345
|
+
"prompt": "Task 1",
|
|
346
|
+
"mcp_config": {
|
|
347
|
+
"local": {"command": "docker", "args": ["run", "-e", "API_KEY1", "image1"]}
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"prompt": "Task 2",
|
|
352
|
+
"mcp_config": {
|
|
353
|
+
"local": {
|
|
354
|
+
"command": "docker",
|
|
355
|
+
"args": ["run", "-e", "API_KEY2", "--env", "API_KEY3", "image2"],
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{"prompt": "Task 3", "mcp_config": {"remote": {"url": "https://mcp.hud.so"}}},
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
result = _extract_vars_from_task_configs(raw_tasks)
|
|
363
|
+
|
|
364
|
+
assert "API_KEY1" in result
|
|
365
|
+
assert "API_KEY2" in result
|
|
366
|
+
assert "API_KEY3" in result
|
|
367
|
+
assert len(result) == 3
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
import contextlib
|
|
19
19
|
import json
|
|
20
|
+
import logging
|
|
20
21
|
import os
|
|
21
22
|
import time
|
|
22
23
|
from pathlib import Path
|
|
@@ -27,6 +28,9 @@ from packaging import version
|
|
|
27
28
|
|
|
28
29
|
from hud.utils.hud_console import HUDConsole
|
|
29
30
|
|
|
31
|
+
# Logger for version checking
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
30
34
|
# Cache location for version check data
|
|
31
35
|
CACHE_DIR = Path.home() / ".hud" / ".cache"
|
|
32
36
|
VERSION_CACHE_FILE = CACHE_DIR / "version_check.json"
|
|
@@ -218,7 +222,7 @@ def display_update_prompt(console: HUDConsole | None = None) -> None:
|
|
|
218
222
|
console: HUDConsole instance for output. If None, creates a new one.
|
|
219
223
|
"""
|
|
220
224
|
if console is None:
|
|
221
|
-
console = HUDConsole()
|
|
225
|
+
console = HUDConsole(logger=logger)
|
|
222
226
|
|
|
223
227
|
try:
|
|
224
228
|
info = check_for_updates()
|
|
@@ -231,11 +235,8 @@ def display_update_prompt(console: HUDConsole | None = None) -> None:
|
|
|
231
235
|
f" Run: [bold yellow]uv tool upgrade hud-python[/bold yellow] to update"
|
|
232
236
|
)
|
|
233
237
|
|
|
234
|
-
# Display
|
|
235
|
-
console.
|
|
236
|
-
f"\n[yellow]{update_msg}[/yellow]\n",
|
|
237
|
-
highlight=False,
|
|
238
|
-
)
|
|
238
|
+
# Display using console info
|
|
239
|
+
console.info(f"[yellow]{update_msg}[/yellow]")
|
|
239
240
|
except Exception: # noqa: S110
|
|
240
241
|
# Never let version checking disrupt the user's workflow
|
|
241
242
|
pass
|
|
@@ -104,6 +104,7 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
104
104
|
|
|
105
105
|
self._initialized = False
|
|
106
106
|
self._telemetry_data = {} # Initialize telemetry data
|
|
107
|
+
self._cached_resources: list[types.Resource] = [] # Cache for resources
|
|
107
108
|
|
|
108
109
|
if self.verbose:
|
|
109
110
|
self._setup_verbose_logging()
|
|
@@ -170,6 +171,7 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
170
171
|
if self._initialized:
|
|
171
172
|
await self._disconnect()
|
|
172
173
|
self._initialized = False
|
|
174
|
+
self._cached_resources.clear()
|
|
173
175
|
hud_console.info("Environment Shutdown completed")
|
|
174
176
|
else:
|
|
175
177
|
hud_console.debug("Client was not initialized, skipping disconnect")
|
|
@@ -211,9 +213,22 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
211
213
|
"""List all available tools."""
|
|
212
214
|
raise NotImplementedError
|
|
213
215
|
|
|
214
|
-
@abstractmethod
|
|
215
216
|
async def list_resources(self) -> list[types.Resource]:
|
|
216
|
-
"""List all available resources.
|
|
217
|
+
"""List all available resources.
|
|
218
|
+
|
|
219
|
+
Uses cached resources if available, otherwise fetches from the server.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
List of available resources.
|
|
223
|
+
"""
|
|
224
|
+
# If cache is empty, populate it
|
|
225
|
+
if not self._cached_resources:
|
|
226
|
+
self._cached_resources = await self._list_resources_impl()
|
|
227
|
+
return self._cached_resources
|
|
228
|
+
|
|
229
|
+
@abstractmethod
|
|
230
|
+
async def _list_resources_impl(self) -> list[types.Resource]:
|
|
231
|
+
"""Implementation-specific resource listing. Subclasses must implement this."""
|
|
217
232
|
raise NotImplementedError
|
|
218
233
|
|
|
219
234
|
@abstractmethod
|
|
@@ -270,6 +285,17 @@ class BaseHUDClient(AgentMCPClient):
|
|
|
270
285
|
async def _fetch_telemetry(self) -> None:
|
|
271
286
|
"""Common telemetry fetching for all hud clients."""
|
|
272
287
|
try:
|
|
288
|
+
# Get resources (will use cache if available, otherwise fetch)
|
|
289
|
+
resources = await self.list_resources()
|
|
290
|
+
telemetry_available = any(
|
|
291
|
+
str(resource.uri) == "telemetry://live" for resource in resources
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if not telemetry_available:
|
|
295
|
+
if self.verbose:
|
|
296
|
+
hud_console.debug("Telemetry resource not available from server")
|
|
297
|
+
return
|
|
298
|
+
|
|
273
299
|
# Try to read telemetry resource directly
|
|
274
300
|
result = await self.read_resource("telemetry://live")
|
|
275
301
|
if result and result.contents:
|
|
@@ -143,8 +143,8 @@ class FastMCPHUDClient(BaseHUDClient):
|
|
|
143
143
|
structuredContent=result.structured_content,
|
|
144
144
|
)
|
|
145
145
|
|
|
146
|
-
async def
|
|
147
|
-
"""
|
|
146
|
+
async def _list_resources_impl(self) -> list[types.Resource]:
|
|
147
|
+
"""Implementation of resource listing for FastMCP client."""
|
|
148
148
|
if self._client is None:
|
|
149
149
|
raise ValueError("Client is not connected, call initialize() first")
|
|
150
150
|
return await self._client.list_resources()
|
|
@@ -243,8 +243,8 @@ class MCPUseHUDClient(BaseHUDClient):
|
|
|
243
243
|
structuredContent=result.structuredContent,
|
|
244
244
|
)
|
|
245
245
|
|
|
246
|
-
async def
|
|
247
|
-
"""
|
|
246
|
+
async def _list_resources_impl(self) -> list[types.Resource]:
|
|
247
|
+
"""Implementation of resource listing for MCP-use client."""
|
|
248
248
|
if self._client is None or not self._sessions:
|
|
249
249
|
raise ValueError("Client is not connected, call initialize() first")
|
|
250
250
|
|
|
@@ -35,9 +35,15 @@ class MockClient(BaseHUDClient):
|
|
|
35
35
|
raise RuntimeError("Not connected")
|
|
36
36
|
return self._mock_tools
|
|
37
37
|
|
|
38
|
-
async def
|
|
39
|
-
"""Minimal
|
|
40
|
-
|
|
38
|
+
async def _list_resources_impl(self) -> list[types.Resource]:
|
|
39
|
+
"""Minimal resource listing implementation for tests."""
|
|
40
|
+
from pydantic import AnyUrl
|
|
41
|
+
|
|
42
|
+
return [
|
|
43
|
+
types.Resource(
|
|
44
|
+
uri=AnyUrl("telemetry://live"), name="telemetry", description="Live telemetry data"
|
|
45
|
+
)
|
|
46
|
+
]
|
|
41
47
|
|
|
42
48
|
async def _call_tool(self, tool_call: MCPToolCall) -> MCPToolResult:
|
|
43
49
|
if tool_call.name == "test_tool":
|