hud-python 0.4.9__py3-none-any.whl → 0.4.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/cli/__init__.py +69 -12
- hud/cli/analyze_metadata.py +4 -1
- hud/cli/env_utils.py +133 -0
- hud/cli/eval.py +84 -8
- hud/cli/list_func.py +5 -4
- hud/cli/mcp_server.py +3 -79
- hud/cli/pull.py +16 -2
- hud/cli/runner.py +1 -1
- hud/tools/response.py +7 -0
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.9.dist-info → hud_python-0.4.11.dist-info}/METADATA +1 -1
- {hud_python-0.4.9.dist-info → hud_python-0.4.11.dist-info}/RECORD +16 -15
- {hud_python-0.4.9.dist-info → hud_python-0.4.11.dist-info}/WHEEL +0 -0
- {hud_python-0.4.9.dist-info → hud_python-0.4.11.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.9.dist-info → hud_python-0.4.11.dist-info}/licenses/LICENSE +0 -0
hud/cli/__init__.py
CHANGED
|
@@ -29,7 +29,6 @@ from .pull import pull_command
|
|
|
29
29
|
from .push import push_command
|
|
30
30
|
from .remove import remove_command
|
|
31
31
|
from .utils import CaptureLogger
|
|
32
|
-
from .eval import eval_command
|
|
33
32
|
|
|
34
33
|
# Create the main Typer app
|
|
35
34
|
app = typer.Typer(
|
|
@@ -132,7 +131,7 @@ def analyze(
|
|
|
132
131
|
def debug(
|
|
133
132
|
params: list[str] = typer.Argument( # type: ignore[arg-type] # noqa: B008
|
|
134
133
|
None,
|
|
135
|
-
help="Docker image followed by optional Docker
|
|
134
|
+
help="Docker image, environment directory, or config file followed by optional Docker arguments", # noqa: E501
|
|
136
135
|
),
|
|
137
136
|
config: Path = typer.Option( # noqa: B008
|
|
138
137
|
None,
|
|
@@ -148,6 +147,12 @@ def debug(
|
|
|
148
147
|
"--cursor",
|
|
149
148
|
help="Debug a server from Cursor config",
|
|
150
149
|
),
|
|
150
|
+
build: bool = typer.Option(
|
|
151
|
+
False,
|
|
152
|
+
"--build",
|
|
153
|
+
"-b",
|
|
154
|
+
help="Build image before debugging (for directory mode)",
|
|
155
|
+
),
|
|
151
156
|
max_phase: int = typer.Option(
|
|
152
157
|
5,
|
|
153
158
|
"--max-phase",
|
|
@@ -160,15 +165,24 @@ def debug(
|
|
|
160
165
|
"""🐛 Debug MCP environment - test initialization, tools, and readiness.
|
|
161
166
|
|
|
162
167
|
Examples:
|
|
163
|
-
hud debug
|
|
164
|
-
hud debug
|
|
168
|
+
hud debug . # Debug current directory
|
|
169
|
+
hud debug environments/browser # Debug specific directory
|
|
170
|
+
hud debug . --build # Build then debug
|
|
171
|
+
hud debug hud-text-2048:latest # Debug Docker image
|
|
172
|
+
hud debug my-mcp-server:v1 -e API_KEY=xxx
|
|
165
173
|
hud debug --config mcp-config.json
|
|
166
174
|
hud debug --cursor text-2048-dev
|
|
167
|
-
hud debug
|
|
175
|
+
hud debug . --max-phase 3 # Stop after phase 3
|
|
168
176
|
"""
|
|
169
|
-
|
|
177
|
+
# Import here to avoid circular imports
|
|
178
|
+
from .env_utils import get_image_name, is_environment_directory, build_environment, image_exists
|
|
179
|
+
from hud.utils.design import HUDDesign
|
|
180
|
+
|
|
181
|
+
design = HUDDesign()
|
|
182
|
+
|
|
170
183
|
# Determine the command to run
|
|
171
184
|
command = None
|
|
185
|
+
docker_args = []
|
|
172
186
|
|
|
173
187
|
if config:
|
|
174
188
|
# Load config from JSON file
|
|
@@ -186,13 +200,44 @@ def debug(
|
|
|
186
200
|
console.print(f"[red]❌ {error or 'Failed to parse cursor config'}[/red]")
|
|
187
201
|
raise typer.Exit(1)
|
|
188
202
|
elif params:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
203
|
+
first_param = params[0]
|
|
204
|
+
docker_args = params[1:] if len(params) > 1 else []
|
|
205
|
+
|
|
206
|
+
# Check if it's a directory
|
|
207
|
+
if Path(first_param).exists() and is_environment_directory(first_param):
|
|
208
|
+
# Directory mode - like hud dev
|
|
209
|
+
directory = first_param
|
|
210
|
+
|
|
211
|
+
# Get or generate image name
|
|
212
|
+
image_name, source = get_image_name(directory)
|
|
213
|
+
|
|
214
|
+
if source == "auto":
|
|
215
|
+
design.info(f"Auto-generated image name: {image_name}")
|
|
216
|
+
|
|
217
|
+
# Build if requested or if image doesn't exist
|
|
218
|
+
if build or not image_exists(image_name):
|
|
219
|
+
if not build and not image_exists(image_name):
|
|
220
|
+
if typer.confirm(f"Image {image_name} not found. Build it now?"):
|
|
221
|
+
build = True
|
|
222
|
+
else:
|
|
223
|
+
raise typer.Exit(1)
|
|
224
|
+
|
|
225
|
+
if build:
|
|
226
|
+
if not build_environment(directory, image_name):
|
|
227
|
+
raise typer.Exit(1)
|
|
228
|
+
|
|
229
|
+
# Build Docker command
|
|
230
|
+
command = ["docker", "run", "--rm", "-i", *docker_args, image_name]
|
|
231
|
+
else:
|
|
232
|
+
# Assume it's an image name
|
|
233
|
+
image = first_param
|
|
234
|
+
command = ["docker", "run", "--rm", "-i", *docker_args, image]
|
|
192
235
|
else:
|
|
193
|
-
console.print("[red]Error: Must specify
|
|
236
|
+
console.print("[red]Error: Must specify a directory, Docker image, --config, or --cursor[/red]")
|
|
194
237
|
console.print("\nExamples:")
|
|
195
|
-
console.print(" hud debug
|
|
238
|
+
console.print(" hud debug . # Debug current directory")
|
|
239
|
+
console.print(" hud debug environments/browser # Debug specific directory")
|
|
240
|
+
console.print(" hud debug hud-text-2048:latest # Debug Docker image")
|
|
196
241
|
console.print(" hud debug --config mcp-config.json")
|
|
197
242
|
console.print(" hud debug --cursor my-server")
|
|
198
243
|
raise typer.Exit(1)
|
|
@@ -699,7 +744,19 @@ def eval(
|
|
|
699
744
|
design.error(f"Invalid agent: {agent}. Must be one of: {', '.join(valid_agents)}")
|
|
700
745
|
raise typer.Exit(1)
|
|
701
746
|
|
|
702
|
-
# Import
|
|
747
|
+
# Import eval_command lazily to avoid importing agent dependencies
|
|
748
|
+
try:
|
|
749
|
+
from .eval import eval_command
|
|
750
|
+
except ImportError as e:
|
|
751
|
+
from hud.utils.design import HUDDesign
|
|
752
|
+
design = HUDDesign()
|
|
753
|
+
design.error(
|
|
754
|
+
"Evaluation dependencies are not installed. "
|
|
755
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
756
|
+
)
|
|
757
|
+
raise typer.Exit(1) from e
|
|
758
|
+
|
|
759
|
+
# Run the command
|
|
703
760
|
eval_command(
|
|
704
761
|
source=source,
|
|
705
762
|
full=full,
|
hud/cli/analyze_metadata.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from pathlib import Path
|
|
6
|
+
from urllib.parse import quote
|
|
6
7
|
|
|
7
8
|
import requests
|
|
8
9
|
import yaml
|
|
@@ -26,7 +27,9 @@ def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
|
26
27
|
if "/" in reference and ":" not in reference:
|
|
27
28
|
reference = f"{reference}:latest"
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
# URL-encode the path segments to handle special characters in tags
|
|
31
|
+
url_safe_path = "/".join(quote(part, safe="") for part in reference.split("/"))
|
|
32
|
+
registry_url = f"{settings.hud_telemetry_url.rstrip('/')}/registry/envs/{url_safe_path}"
|
|
30
33
|
|
|
31
34
|
headers = {}
|
|
32
35
|
if settings.api_key:
|
hud/cli/env_utils.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Shared utilities for environment directory handling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import toml
|
|
10
|
+
|
|
11
|
+
from hud.utils.design import HUDDesign
|
|
12
|
+
|
|
13
|
+
design = HUDDesign()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_image_name(directory: str | Path, image_override: str | None = None) -> tuple[str, str]:
|
|
17
|
+
"""
|
|
18
|
+
Resolve image name with source tracking.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Tuple of (image_name, source) where source is "override", "cache", or "auto"
|
|
22
|
+
"""
|
|
23
|
+
if image_override:
|
|
24
|
+
return image_override, "override"
|
|
25
|
+
|
|
26
|
+
# Check pyproject.toml
|
|
27
|
+
pyproject_path = Path(directory) / "pyproject.toml"
|
|
28
|
+
if pyproject_path.exists():
|
|
29
|
+
try:
|
|
30
|
+
with open(pyproject_path) as f:
|
|
31
|
+
config = toml.load(f)
|
|
32
|
+
if config.get("tool", {}).get("hud", {}).get("image"):
|
|
33
|
+
return config["tool"]["hud"]["image"], "cache"
|
|
34
|
+
except Exception:
|
|
35
|
+
pass # Silent failure, will use auto-generated name
|
|
36
|
+
|
|
37
|
+
# Auto-generate with :dev tag
|
|
38
|
+
dir_path = Path(directory).resolve() # Get absolute path first
|
|
39
|
+
dir_name = dir_path.name
|
|
40
|
+
if not dir_name or dir_name == ".":
|
|
41
|
+
# If we're in root or have empty name, use parent directory
|
|
42
|
+
dir_name = dir_path.parent.name
|
|
43
|
+
clean_name = dir_name.replace("_", "-")
|
|
44
|
+
return f"hud-{clean_name}:dev", "auto"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def update_pyproject_toml(directory: str | Path, image_name: str, silent: bool = False) -> None:
|
|
48
|
+
"""Update pyproject.toml with image name."""
|
|
49
|
+
pyproject_path = Path(directory) / "pyproject.toml"
|
|
50
|
+
if pyproject_path.exists():
|
|
51
|
+
try:
|
|
52
|
+
with open(pyproject_path) as f:
|
|
53
|
+
config = toml.load(f)
|
|
54
|
+
|
|
55
|
+
# Ensure [tool.hud] exists
|
|
56
|
+
if "tool" not in config:
|
|
57
|
+
config["tool"] = {}
|
|
58
|
+
if "hud" not in config["tool"]:
|
|
59
|
+
config["tool"]["hud"] = {}
|
|
60
|
+
|
|
61
|
+
# Update image name
|
|
62
|
+
config["tool"]["hud"]["image"] = image_name
|
|
63
|
+
|
|
64
|
+
# Write back
|
|
65
|
+
with open(pyproject_path, "w") as f:
|
|
66
|
+
toml.dump(config, f)
|
|
67
|
+
|
|
68
|
+
if not silent:
|
|
69
|
+
design.success(f"Updated pyproject.toml with image: {image_name}")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
if not silent:
|
|
72
|
+
design.warning(f"Could not update pyproject.toml: {e}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def build_environment(directory: str | Path, image_name: str, no_cache: bool = False) -> bool:
|
|
76
|
+
"""Build Docker image for an environment.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
True if build succeeded, False otherwise
|
|
80
|
+
"""
|
|
81
|
+
build_cmd = ["docker", "build", "-t", image_name]
|
|
82
|
+
if no_cache:
|
|
83
|
+
build_cmd.append("--no-cache")
|
|
84
|
+
build_cmd.append(str(directory))
|
|
85
|
+
|
|
86
|
+
design.info(f"🔨 Building image: {image_name}{' (no cache)' if no_cache else ''}")
|
|
87
|
+
design.info("") # Empty line before Docker output
|
|
88
|
+
|
|
89
|
+
# Just run Docker build directly - it has its own nice live display
|
|
90
|
+
result = subprocess.run(build_cmd) # noqa: S603
|
|
91
|
+
|
|
92
|
+
if result.returncode == 0:
|
|
93
|
+
design.info("") # Empty line after Docker output
|
|
94
|
+
design.success(f"Build successful! Image: {image_name}")
|
|
95
|
+
# Update pyproject.toml (silently since we already showed success)
|
|
96
|
+
update_pyproject_toml(directory, image_name, silent=True)
|
|
97
|
+
return True
|
|
98
|
+
else:
|
|
99
|
+
design.error("Build failed!")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def image_exists(image_name: str) -> bool:
|
|
104
|
+
"""Check if a Docker image exists locally."""
|
|
105
|
+
result = subprocess.run( # noqa: S603
|
|
106
|
+
["docker", "image", "inspect", image_name], # noqa: S607
|
|
107
|
+
stdout=subprocess.DEVNULL,
|
|
108
|
+
stderr=subprocess.DEVNULL,
|
|
109
|
+
)
|
|
110
|
+
return result.returncode == 0
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def is_environment_directory(path: str | Path) -> bool:
|
|
114
|
+
"""Check if a path looks like an environment directory.
|
|
115
|
+
|
|
116
|
+
An environment directory should have:
|
|
117
|
+
- A Dockerfile
|
|
118
|
+
- A pyproject.toml file
|
|
119
|
+
- Optionally a src directory
|
|
120
|
+
"""
|
|
121
|
+
dir_path = Path(path)
|
|
122
|
+
if not dir_path.is_dir():
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
# Must have Dockerfile
|
|
126
|
+
if not (dir_path / "Dockerfile").exists():
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
# Must have pyproject.toml
|
|
130
|
+
if not (dir_path / "pyproject.toml").exists():
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
return True
|
hud/cli/eval.py
CHANGED
|
@@ -6,17 +6,18 @@ import asyncio
|
|
|
6
6
|
import json
|
|
7
7
|
import logging
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any, Literal
|
|
9
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
10
10
|
|
|
11
11
|
import typer
|
|
12
|
-
from datasets import load_dataset
|
|
13
12
|
|
|
14
13
|
import hud
|
|
15
|
-
from hud.agents import ClaudeAgent, OperatorAgent
|
|
16
|
-
from hud.agents.misc.response_agent import ResponseAgent
|
|
17
|
-
from hud.datasets import Task, run_dataset
|
|
18
14
|
from hud.utils.design import HUDDesign
|
|
19
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from datasets import Dataset
|
|
18
|
+
from hud.agents import ClaudeAgent, OperatorAgent
|
|
19
|
+
from hud.agents.misc.response_agent import ResponseAgent
|
|
20
|
+
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
design = HUDDesign()
|
|
22
23
|
|
|
@@ -26,10 +27,29 @@ def build_agent(
|
|
|
26
27
|
*,
|
|
27
28
|
model: str | None = None,
|
|
28
29
|
allowed_tools: list[str] | None = None,
|
|
29
|
-
) ->
|
|
30
|
+
) -> Any:
|
|
30
31
|
"""Create and return the requested agent type."""
|
|
31
32
|
|
|
33
|
+
# Import agents lazily to avoid dependency issues
|
|
34
|
+
try:
|
|
35
|
+
from hud.agents.misc.response_agent import ResponseAgent
|
|
36
|
+
except ImportError as e:
|
|
37
|
+
design.error(
|
|
38
|
+
"Agent dependencies are not installed. "
|
|
39
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
40
|
+
)
|
|
41
|
+
raise typer.Exit(1) from e
|
|
42
|
+
|
|
32
43
|
if agent_type == "openai":
|
|
44
|
+
try:
|
|
45
|
+
from hud.agents import OperatorAgent
|
|
46
|
+
except ImportError as e:
|
|
47
|
+
design.error(
|
|
48
|
+
"OpenAI agent dependencies are not installed. "
|
|
49
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
50
|
+
)
|
|
51
|
+
raise typer.Exit(1) from e
|
|
52
|
+
|
|
33
53
|
allowed_tools = allowed_tools or ["openai_computer"]
|
|
34
54
|
|
|
35
55
|
return OperatorAgent(
|
|
@@ -38,6 +58,15 @@ def build_agent(
|
|
|
38
58
|
)
|
|
39
59
|
|
|
40
60
|
# Fallback Claude agent (Anthropic)
|
|
61
|
+
try:
|
|
62
|
+
from hud.agents import ClaudeAgent
|
|
63
|
+
except ImportError as e:
|
|
64
|
+
design.error(
|
|
65
|
+
"Claude agent dependencies are not installed. "
|
|
66
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
67
|
+
)
|
|
68
|
+
raise typer.Exit(1) from e
|
|
69
|
+
|
|
41
70
|
model = model or "claude-sonnet-4-20250514"
|
|
42
71
|
allowed_tools = allowed_tools or ["anthropic_computer"]
|
|
43
72
|
|
|
@@ -60,6 +89,16 @@ async def run_single_task(
|
|
|
60
89
|
|
|
61
90
|
design.info("📊 Loading dataset…")
|
|
62
91
|
|
|
92
|
+
# Import Task lazily
|
|
93
|
+
try:
|
|
94
|
+
from hud.datasets import Task
|
|
95
|
+
except ImportError as e:
|
|
96
|
+
design.error(
|
|
97
|
+
"Dataset dependencies are not installed. "
|
|
98
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
99
|
+
)
|
|
100
|
+
raise typer.Exit(1) from e
|
|
101
|
+
|
|
63
102
|
# Check if it's a single task JSON file
|
|
64
103
|
path = Path(source)
|
|
65
104
|
if path.exists() and path.suffix == ".json":
|
|
@@ -68,6 +107,15 @@ async def run_single_task(
|
|
|
68
107
|
task = Task(**task_data)
|
|
69
108
|
else:
|
|
70
109
|
# Load from HuggingFace dataset
|
|
110
|
+
try:
|
|
111
|
+
from datasets import load_dataset
|
|
112
|
+
except ImportError as e:
|
|
113
|
+
design.error(
|
|
114
|
+
"Datasets library is not installed. "
|
|
115
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
116
|
+
)
|
|
117
|
+
raise typer.Exit(1) from e
|
|
118
|
+
|
|
71
119
|
dataset = load_dataset(source, split="train")
|
|
72
120
|
|
|
73
121
|
# Get first task from dataset
|
|
@@ -98,14 +146,42 @@ async def run_full_dataset(
|
|
|
98
146
|
) -> list[Any]:
|
|
99
147
|
"""Run evaluation across the entire dataset using hud.datasets.run_dataset."""
|
|
100
148
|
|
|
149
|
+
# Import run_dataset lazily
|
|
150
|
+
try:
|
|
151
|
+
from hud.datasets import run_dataset
|
|
152
|
+
except ImportError as e:
|
|
153
|
+
design.error(
|
|
154
|
+
"Dataset dependencies are not installed. "
|
|
155
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
156
|
+
)
|
|
157
|
+
raise typer.Exit(1) from e
|
|
158
|
+
|
|
101
159
|
# Build agent class + config for run_dataset
|
|
102
160
|
if agent_type == "openai":
|
|
103
|
-
|
|
161
|
+
try:
|
|
162
|
+
from hud.agents import OperatorAgent
|
|
163
|
+
agent_class = OperatorAgent
|
|
164
|
+
except ImportError as e:
|
|
165
|
+
design.error(
|
|
166
|
+
"OpenAI agent dependencies are not installed. "
|
|
167
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
168
|
+
)
|
|
169
|
+
raise typer.Exit(1) from e
|
|
170
|
+
|
|
104
171
|
agent_config: dict[str, Any] = {
|
|
105
172
|
"allowed_tools": allowed_tools or ["openai_computer"],
|
|
106
173
|
}
|
|
107
174
|
else:
|
|
108
|
-
|
|
175
|
+
try:
|
|
176
|
+
from hud.agents import ClaudeAgent
|
|
177
|
+
agent_class = ClaudeAgent
|
|
178
|
+
except ImportError as e:
|
|
179
|
+
design.error(
|
|
180
|
+
"Claude agent dependencies are not installed. "
|
|
181
|
+
"Please install with: pip install 'hud-python[agent]'"
|
|
182
|
+
)
|
|
183
|
+
raise typer.Exit(1) from e
|
|
184
|
+
|
|
109
185
|
agent_config = {
|
|
110
186
|
"model": model or "claude-sonnet-4-20250514",
|
|
111
187
|
"allowed_tools": allowed_tools or ["anthropic_computer"],
|
hud/cli/list_func.py
CHANGED
|
@@ -132,8 +132,7 @@ def list_environments(
|
|
|
132
132
|
|
|
133
133
|
# Create table
|
|
134
134
|
table = Table(title=f"Found {len(environments)} environment{'s' if len(environments) != 1 else ''}")
|
|
135
|
-
table.add_column("
|
|
136
|
-
table.add_column("Tag", style="green")
|
|
135
|
+
table.add_column("Environment", style="cyan", no_wrap=True)
|
|
137
136
|
table.add_column("Description", style="white")
|
|
138
137
|
table.add_column("Tools", justify="right", style="yellow")
|
|
139
138
|
table.add_column("Pulled", style="dim")
|
|
@@ -148,9 +147,11 @@ def list_environments(
|
|
|
148
147
|
if desc and len(desc) > 50 and not verbose:
|
|
149
148
|
desc = desc[:47] + "..."
|
|
150
149
|
|
|
150
|
+
# Combine name and tag for easy copying
|
|
151
|
+
full_ref = f"{env['name']}:{env['tag']}"
|
|
152
|
+
|
|
151
153
|
row = [
|
|
152
|
-
|
|
153
|
-
env["tag"],
|
|
154
|
+
full_ref,
|
|
154
155
|
desc or "[dim]No description[/dim]",
|
|
155
156
|
str(env["tools_count"]),
|
|
156
157
|
format_timestamp(env["pulled_time"]),
|
hud/cli/mcp_server.py
CHANGED
|
@@ -13,92 +13,16 @@ import toml
|
|
|
13
13
|
from fastmcp import FastMCP
|
|
14
14
|
|
|
15
15
|
from hud.utils.design import HUDDesign
|
|
16
|
-
from .docker_utils import get_docker_cmd,
|
|
16
|
+
from .docker_utils import get_docker_cmd, inject_supervisor
|
|
17
|
+
from .env_utils import get_image_name, update_pyproject_toml, build_environment, image_exists
|
|
17
18
|
|
|
18
19
|
# Global design instance
|
|
19
20
|
design = HUDDesign()
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def get_image_name(directory: str | Path, image_override: str | None = None) -> tuple[str, str]:
|
|
23
|
-
"""
|
|
24
|
-
Resolve image name with source tracking.
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
Tuple of (image_name, source) where source is "override", "cache", or "auto"
|
|
28
|
-
"""
|
|
29
|
-
if image_override:
|
|
30
|
-
return image_override, "override"
|
|
31
|
-
|
|
32
|
-
# Check pyproject.toml
|
|
33
|
-
pyproject_path = Path(directory) / "pyproject.toml"
|
|
34
|
-
if pyproject_path.exists():
|
|
35
|
-
try:
|
|
36
|
-
with open(pyproject_path) as f:
|
|
37
|
-
config = toml.load(f)
|
|
38
|
-
if config.get("tool", {}).get("hud", {}).get("image"):
|
|
39
|
-
return config["tool"]["hud"]["image"], "cache"
|
|
40
|
-
except Exception:
|
|
41
|
-
pass # Silent failure, will use auto-generated name
|
|
42
|
-
|
|
43
|
-
# Auto-generate with :dev tag
|
|
44
|
-
dir_path = Path(directory).resolve() # Get absolute path first
|
|
45
|
-
dir_name = dir_path.name
|
|
46
|
-
if not dir_name or dir_name == ".":
|
|
47
|
-
# If we're in root or have empty name, use parent directory
|
|
48
|
-
dir_name = dir_path.parent.name
|
|
49
|
-
clean_name = dir_name.replace("_", "-")
|
|
50
|
-
return f"hud-{clean_name}:dev", "auto"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def update_pyproject_toml(directory: str | Path, image_name: str, silent: bool = False) -> None:
|
|
54
|
-
"""Update pyproject.toml with image name."""
|
|
55
|
-
pyproject_path = Path(directory) / "pyproject.toml"
|
|
56
|
-
if pyproject_path.exists():
|
|
57
|
-
try:
|
|
58
|
-
with open(pyproject_path) as f:
|
|
59
|
-
config = toml.load(f)
|
|
60
|
-
|
|
61
|
-
# Ensure [tool.hud] exists
|
|
62
|
-
if "tool" not in config:
|
|
63
|
-
config["tool"] = {}
|
|
64
|
-
if "hud" not in config["tool"]:
|
|
65
|
-
config["tool"]["hud"] = {}
|
|
66
|
-
|
|
67
|
-
# Update image name
|
|
68
|
-
config["tool"]["hud"]["image"] = image_name
|
|
69
|
-
|
|
70
|
-
# Write back
|
|
71
|
-
with open(pyproject_path, "w") as f:
|
|
72
|
-
toml.dump(config, f)
|
|
73
|
-
|
|
74
|
-
if not silent:
|
|
75
|
-
design.success(f"Updated pyproject.toml with image: {image_name}")
|
|
76
|
-
except Exception as e:
|
|
77
|
-
if not silent:
|
|
78
|
-
design.warning(f"Could not update pyproject.toml: {e}")
|
|
79
|
-
|
|
80
|
-
|
|
81
23
|
def build_and_update(directory: str | Path, image_name: str, no_cache: bool = False) -> None:
|
|
82
24
|
"""Build Docker image and update pyproject.toml."""
|
|
83
|
-
|
|
84
|
-
build_cmd = ["docker", "build", "-t", image_name]
|
|
85
|
-
if no_cache:
|
|
86
|
-
build_cmd.append("--no-cache")
|
|
87
|
-
build_cmd.append(str(directory))
|
|
88
|
-
|
|
89
|
-
design.info(f"🔨 Building image: {image_name}{' (no cache)' if no_cache else ''}")
|
|
90
|
-
design.info("") # Empty line before Docker output
|
|
91
|
-
|
|
92
|
-
# Just run Docker build directly - it has its own nice live display
|
|
93
|
-
result = subprocess.run(build_cmd) # noqa: S603
|
|
94
|
-
|
|
95
|
-
if result.returncode == 0:
|
|
96
|
-
design.info("") # Empty line after Docker output
|
|
97
|
-
design.success(f"Build successful! Image: {image_name}")
|
|
98
|
-
# Update pyproject.toml (silently since we already showed success)
|
|
99
|
-
update_pyproject_toml(directory, image_name, silent=True)
|
|
100
|
-
else:
|
|
101
|
-
design.error("Build failed!")
|
|
25
|
+
if not build_environment(directory, image_name, no_cache):
|
|
102
26
|
raise click.Abort
|
|
103
27
|
|
|
104
28
|
|
hud/cli/pull.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import subprocess
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from urllib.parse import quote
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import requests
|
|
@@ -61,7 +62,9 @@ def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
|
61
62
|
if "/" in reference and ":" not in reference:
|
|
62
63
|
reference = f"{reference}:latest"
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
# URL-encode the path segments to handle special characters in tags
|
|
66
|
+
url_safe_path = "/".join(quote(part, safe="") for part in reference.split("/"))
|
|
67
|
+
registry_url = f"{settings.hud_telemetry_url.rstrip('/')}/registry/envs/{url_safe_path}"
|
|
65
68
|
|
|
66
69
|
headers = {}
|
|
67
70
|
if settings.api_key:
|
|
@@ -79,7 +82,18 @@ def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
|
79
82
|
else:
|
|
80
83
|
# Try to treat the whole response as lock data
|
|
81
84
|
return data
|
|
82
|
-
|
|
85
|
+
elif response.status_code == 404:
|
|
86
|
+
# Not found - expected error, return None silently
|
|
87
|
+
return None
|
|
88
|
+
elif response.status_code == 401:
|
|
89
|
+
# Authentication issue - might be a private environment
|
|
90
|
+
return None
|
|
91
|
+
else:
|
|
92
|
+
# Other errors - also return None but could log if verbose
|
|
93
|
+
return None
|
|
94
|
+
except requests.exceptions.Timeout:
|
|
95
|
+
return None
|
|
96
|
+
except requests.exceptions.ConnectionError:
|
|
83
97
|
return None
|
|
84
98
|
except Exception:
|
|
85
99
|
return None
|
hud/cli/runner.py
CHANGED
|
@@ -14,7 +14,7 @@ from hud.utils.design import HUDDesign
|
|
|
14
14
|
|
|
15
15
|
def run_stdio_server(image: str, docker_args: list[str], verbose: bool) -> None:
|
|
16
16
|
"""Run Docker image as stdio MCP server (direct passthrough)."""
|
|
17
|
-
design = HUDDesign(
|
|
17
|
+
design = HUDDesign() # Use stderr for stdio mode
|
|
18
18
|
|
|
19
19
|
# Build docker command
|
|
20
20
|
docker_cmd = ["docker", "run", "--rm", "-i", *docker_args, image]
|
hud/tools/response.py
CHANGED
|
@@ -36,6 +36,13 @@ class ResponseTool(BaseTool):
|
|
|
36
36
|
return blocks
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
|
+
def __init__(self, name: str, title: str, description: str):
|
|
40
|
+
super().__init__(
|
|
41
|
+
name=name or "response",
|
|
42
|
+
title=title or "Response Tool",
|
|
43
|
+
description=description or "Send a text response or list of messages to the environment",
|
|
44
|
+
)
|
|
45
|
+
|
|
39
46
|
@abstractmethod
|
|
40
47
|
async def __call__(
|
|
41
48
|
self,
|
hud/utils/tests/test_version.py
CHANGED
hud/version.py
CHANGED
|
@@ -2,7 +2,7 @@ hud/__init__.py,sha256=BjAhZtsHbGN371Q8t3o4v4jltedkmDE85xW0yOILU9g,397
|
|
|
2
2
|
hud/datasets.py,sha256=8lqC840kcNx01D2CcWZCd1j0eZTpepILmQrvohZIZYU,12056
|
|
3
3
|
hud/settings.py,sha256=WIJDsyrfwBZGcaGT46YUOpW8xjBZl3siXXprd92ASAg,2039
|
|
4
4
|
hud/types.py,sha256=pQWOPYXUZ2hhK0h-AHBc3DCj5tkbRXHqKZnsQQIcSFA,4237
|
|
5
|
-
hud/version.py,sha256=
|
|
5
|
+
hud/version.py,sha256=rJC2LSEkxuxl1MmKAIVTGUVWSRNnH7oyOTTIv6ayiJY,105
|
|
6
6
|
hud/agents/__init__.py,sha256=UoIkljWdbq4bM0LD-mSaw6w826EqdEjOk7r6glNYwYQ,286
|
|
7
7
|
hud/agents/base.py,sha256=qN9D7hozC45lA6wAwKgCr0xH6FXTsxeX0Hg9ttV-HlM,24682
|
|
8
8
|
hud/agents/claude.py,sha256=snbYFPW-KAkw4n9Rdz7dC2f46RuSHJKC53HPm8SucFM,14273
|
|
@@ -16,26 +16,27 @@ hud/agents/tests/test_base.py,sha256=F39ajSqASGUbPyPoWSY9KARFav62qNTK74W11Tr1Tg4
|
|
|
16
16
|
hud/agents/tests/test_claude.py,sha256=wqEKlzEvx8obz1sSm4NY0j-Zyt1qWNfDOmRqYIuAEd0,13069
|
|
17
17
|
hud/agents/tests/test_client.py,sha256=Sk5bGZw2hL5GsVi2LMp9tsLngl5ZQ18pkpeeQmts0ao,13908
|
|
18
18
|
hud/agents/tests/test_openai.py,sha256=VcAUMcmwWttOxy9CCSu8QFIvAHG8ZMLUdQMeP04oK9Q,9026
|
|
19
|
-
hud/cli/__init__.py,sha256=
|
|
19
|
+
hud/cli/__init__.py,sha256=quOlqQ5lHvDuR9JOTn2pePtR1sbG1rGB1_h6a2AFcDc,27925
|
|
20
20
|
hud/cli/__main__.py,sha256=fDH7XITyuDITwSDIVwRso06aouADO0CzTHKqp5TOwJE,143
|
|
21
21
|
hud/cli/analyze.py,sha256=G-tjT1xLPLcYhDhZEaI7TAIS0z0OACUksnGFoAWd2ag,14416
|
|
22
|
-
hud/cli/analyze_metadata.py,sha256=
|
|
22
|
+
hud/cli/analyze_metadata.py,sha256=jxhM8i3-OgDDcQsRH7KefMMNXBwUPbNdGJr-4HdQfnk,7816
|
|
23
23
|
hud/cli/build.py,sha256=eQG-nxgTzyLOCQsFyXzP4_SLh_IjZLtE60bmV9X9KH8,16909
|
|
24
24
|
hud/cli/clone.py,sha256=AwVDIuhr8mHb1oT2Af2HrD25SiTdwATpE6zd93vzLgA,6099
|
|
25
25
|
hud/cli/cursor.py,sha256=fy850p0rVp5k_1wwOCI7rK1SggbselJrywFInSQ2gio,3009
|
|
26
26
|
hud/cli/debug.py,sha256=kKGuv_KoX9oGFCKFkrfSAAvuGH1q7Rd7hGELig7tHQI,14159
|
|
27
27
|
hud/cli/docker_utils.py,sha256=5k5sXtI4V5d4VWRJR6lzVRy_Hu8Wf1nLsmW-dBNW0cM,2695
|
|
28
|
-
hud/cli/
|
|
28
|
+
hud/cli/env_utils.py,sha256=PTS93UyZgAXjmcR21DK4n49DGnRis_U_850ROfbMKpY,4235
|
|
29
|
+
hud/cli/eval.py,sha256=McIoMwSscmPzi2lOsaVtBULyL-uhGdt0870ZBjYx4zA,9414
|
|
29
30
|
hud/cli/init.py,sha256=Jb9h4MJPMNiUhr9GmmJjwjjmGQQOOMG1GadXIEsvKKk,7702
|
|
30
31
|
hud/cli/interactive.py,sha256=w1fAoefizNKqjcl5Fi5EgEsUdPpXKcCFuU-Z-S3CL9o,12863
|
|
31
|
-
hud/cli/list_func.py,sha256=
|
|
32
|
-
hud/cli/mcp_server.py,sha256=
|
|
33
|
-
hud/cli/pull.py,sha256=
|
|
32
|
+
hud/cli/list_func.py,sha256=NKDwcipzrEYV28GqG3OJfeMjk5RzD2QBPL2Xx95rPPY,7002
|
|
33
|
+
hud/cli/mcp_server.py,sha256=cb73hHagmUqn0WmKbCYi26AGGZB9XhdxMoxvFnFhYgM,25501
|
|
34
|
+
hud/cli/pull.py,sha256=qVxJ4yJKUPVGWqrf4gyg6yucDsZGU3WhsNvyxitftqE,11976
|
|
34
35
|
hud/cli/push.py,sha256=vr0S-0pEcoyEuDoiqrNS8rwtRsAIqnmou8XVEE_jcqU,17879
|
|
35
36
|
hud/cli/registry.py,sha256=CNLI-e7EeMxND2MOz6uEIwbEbItURZcoMCZR_5L4Q50,4540
|
|
36
37
|
hud/cli/remote_runner.py,sha256=X01x6DeVkyc9HgxVCGEZJxEhhVPvHpAMoeYR44R8_BQ,9405
|
|
37
38
|
hud/cli/remove.py,sha256=DGYtkgzWQHeoZo3BIDHPRc5PKipkwR2942TOJPFmWPc,6723
|
|
38
|
-
hud/cli/runner.py,sha256=
|
|
39
|
+
hud/cli/runner.py,sha256=XpZ_4Dc4P94WHW2vJtDyWoqJTNlR5yeyr79VVuGgCkU,4877
|
|
39
40
|
hud/cli/utils.py,sha256=ZgjjKVPAa7dcfJ6SMBrdfZ63d1UnnhYC-zeh7gFBXsI,8841
|
|
40
41
|
hud/cli/tests/__init__.py,sha256=ZrGVkmH7DHXGqOvjOSNGZeMYaFIRB2K8c6hwr8FPJ-8,68
|
|
41
42
|
hud/cli/tests/test_analyze.py,sha256=1VGIjpvZXD6N4j2yRoVddug2jQ6tJn41NWw-ScAVztw,10971
|
|
@@ -96,7 +97,7 @@ hud/tools/base.py,sha256=avk6dLHEmlOMfozTzga4SbjCuAN0n5zyLwIn6OfLN60,14984
|
|
|
96
97
|
hud/tools/bash.py,sha256=LJViMGb3lTGBm_gequVVTM7ySh1Xh9bOOIZXU29Lmrw,5209
|
|
97
98
|
hud/tools/edit.py,sha256=N0AYFXp07-vAJy2li7lvHOL6hfgJOU4LL3iLSZrbRWU,12745
|
|
98
99
|
hud/tools/playwright.py,sha256=lF7NxyEu8YbB7tpmCoTf8p9HxIrejahC67x3Xs0Jjb4,15007
|
|
99
|
-
hud/tools/response.py,sha256=
|
|
100
|
+
hud/tools/response.py,sha256=aETnwrrzft3NgBM3ZV6orD_lR15gHLC8Wj3esV7sALs,2089
|
|
100
101
|
hud/tools/types.py,sha256=g-CWnUUDSxxIfUy54S1bpY1nfTzdYO1R_nPKYReABjQ,2734
|
|
101
102
|
hud/tools/utils.py,sha256=bfVyYMcBOJvr1QdptCjVb6jaHVGIL5WUxmY59kzMekQ,1447
|
|
102
103
|
hud/tools/computer/__init__.py,sha256=3tQBXPtGX0WPCwFXzEs3-duwg0rPEgj_0-K7aHskeQE,367
|
|
@@ -133,10 +134,10 @@ hud/utils/tests/test_async_utils.py,sha256=RkdSnYErRV3Jn7dfg6CPlcE1RSUL__2B627oI
|
|
|
133
134
|
hud/utils/tests/test_init.py,sha256=2QLQSGgyP9wJhOvPCusm_zjJad0qApOZi1BXpxcdHXQ,383
|
|
134
135
|
hud/utils/tests/test_progress.py,sha256=QSF7Kpi03Ff_l3mAeqW9qs1nhK50j9vBiSobZq7T4f4,7394
|
|
135
136
|
hud/utils/tests/test_telemetry.py,sha256=5jl7bEx8C8b-FfFUko5pf4UY-mPOR-9HaeL98dGtVHM,2781
|
|
136
|
-
hud/utils/tests/test_version.py,sha256=
|
|
137
|
+
hud/utils/tests/test_version.py,sha256=7xPjaAidQyVH50si92ZApISbx0GOBzRx3ZIWWEy43QE,160
|
|
137
138
|
hud/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
138
|
-
hud_python-0.4.
|
|
139
|
-
hud_python-0.4.
|
|
140
|
-
hud_python-0.4.
|
|
141
|
-
hud_python-0.4.
|
|
142
|
-
hud_python-0.4.
|
|
139
|
+
hud_python-0.4.11.dist-info/METADATA,sha256=d0lDFyvwDvhKR1kC05SK9FzwbfDih2SBewuHABs3ae0,20165
|
|
140
|
+
hud_python-0.4.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
141
|
+
hud_python-0.4.11.dist-info/entry_points.txt,sha256=jJbodNFg1m0-CDofe5AHvB4zKBq7sSdP97-ohaQ3ae4,63
|
|
142
|
+
hud_python-0.4.11.dist-info/licenses/LICENSE,sha256=yIzBheVUf86FC1bztAcr7RYWWNxyd3B-UJQ3uddg1HA,1078
|
|
143
|
+
hud_python-0.4.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|