hud-python 0.4.22__py3-none-any.whl → 0.4.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hud-python might be problematic. Click here for more details.
- hud/agents/base.py +85 -59
- hud/agents/claude.py +5 -1
- hud/agents/grounded_openai.py +3 -1
- hud/agents/misc/response_agent.py +3 -2
- hud/agents/openai.py +2 -2
- hud/agents/openai_chat_generic.py +3 -1
- hud/cli/__init__.py +34 -24
- hud/cli/analyze.py +27 -26
- hud/cli/build.py +50 -46
- hud/cli/debug.py +7 -7
- hud/cli/dev.py +107 -99
- hud/cli/eval.py +31 -29
- hud/cli/hf.py +53 -53
- hud/cli/init.py +28 -28
- hud/cli/list_func.py +22 -22
- hud/cli/pull.py +36 -36
- hud/cli/push.py +76 -74
- hud/cli/remove.py +42 -40
- hud/cli/rl/__init__.py +2 -2
- hud/cli/rl/init.py +41 -41
- hud/cli/rl/pod.py +97 -91
- hud/cli/rl/ssh.py +42 -40
- hud/cli/rl/train.py +75 -73
- hud/cli/rl/utils.py +10 -10
- hud/cli/tests/test_analyze.py +1 -1
- hud/cli/tests/test_analyze_metadata.py +2 -2
- hud/cli/tests/test_pull.py +45 -45
- hud/cli/tests/test_push.py +31 -29
- hud/cli/tests/test_registry.py +15 -15
- hud/cli/utils/environment.py +11 -11
- hud/cli/utils/interactive.py +17 -17
- hud/cli/utils/logging.py +12 -12
- hud/cli/utils/metadata.py +12 -12
- hud/cli/utils/registry.py +5 -5
- hud/cli/utils/runner.py +23 -23
- hud/cli/utils/server.py +16 -16
- hud/clients/mcp_use.py +19 -5
- hud/clients/utils/__init__.py +25 -0
- hud/clients/utils/retry.py +186 -0
- hud/datasets/execution/parallel.py +71 -46
- hud/shared/hints.py +7 -7
- hud/tools/grounding/grounder.py +2 -1
- hud/types.py +4 -4
- hud/utils/__init__.py +3 -3
- hud/utils/{design.py → hud_console.py} +39 -33
- hud/utils/pretty_errors.py +6 -6
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/METADATA +3 -1
- {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/RECORD +53 -52
- {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/WHEEL +0 -0
- {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.22.dist-info → hud_python-0.4.24.dist-info}/licenses/LICENSE +0 -0
hud/cli/tests/test_push.py
CHANGED
|
@@ -110,24 +110,24 @@ class TestGetDockerImageLabels:
|
|
|
110
110
|
class TestPushEnvironment:
|
|
111
111
|
"""Test the main push_environment function."""
|
|
112
112
|
|
|
113
|
-
@mock.patch("hud.cli.push.
|
|
114
|
-
def test_push_no_lock_file(self,
|
|
113
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
114
|
+
def test_push_no_lock_file(self, mock_hud_console_class, tmp_path):
|
|
115
115
|
"""Test pushing when no lock file exists."""
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
mock_hud_console = mock.Mock()
|
|
117
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
118
118
|
|
|
119
119
|
with pytest.raises(typer.Exit) as exc_info:
|
|
120
120
|
push_environment(str(tmp_path))
|
|
121
121
|
|
|
122
122
|
assert exc_info.value.exit_code == 1
|
|
123
|
-
|
|
123
|
+
mock_hud_console.error.assert_called()
|
|
124
124
|
|
|
125
|
-
@mock.patch("hud.cli.push.
|
|
125
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
126
126
|
@mock.patch("hud.settings.settings")
|
|
127
|
-
def test_push_no_api_key(self, mock_settings,
|
|
127
|
+
def test_push_no_api_key(self, mock_settings, mock_hud_console_class, tmp_path):
|
|
128
128
|
"""Test pushing without API key."""
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
mock_hud_console = mock.Mock()
|
|
130
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
131
131
|
mock_settings.api_key = None
|
|
132
132
|
|
|
133
133
|
# Create lock file
|
|
@@ -144,10 +144,10 @@ class TestPushEnvironment:
|
|
|
144
144
|
@mock.patch("subprocess.run")
|
|
145
145
|
@mock.patch("hud.cli.push.get_docker_username")
|
|
146
146
|
@mock.patch("hud.settings.settings")
|
|
147
|
-
@mock.patch("hud.cli.push.
|
|
147
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
148
148
|
def test_push_auto_detect_username(
|
|
149
149
|
self,
|
|
150
|
-
|
|
150
|
+
mock_hud_console_class,
|
|
151
151
|
mock_settings,
|
|
152
152
|
mock_get_username,
|
|
153
153
|
mock_run,
|
|
@@ -157,8 +157,8 @@ class TestPushEnvironment:
|
|
|
157
157
|
):
|
|
158
158
|
"""Test auto-detecting Docker username and pushing."""
|
|
159
159
|
# Setup mocks
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
mock_hud_console = mock.Mock()
|
|
161
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
162
162
|
mock_settings.api_key = "test-key"
|
|
163
163
|
mock_settings.hud_telemetry_url = "https://api.hud.test"
|
|
164
164
|
mock_get_username.return_value = "testuser"
|
|
@@ -206,11 +206,11 @@ class TestPushEnvironment:
|
|
|
206
206
|
|
|
207
207
|
@mock.patch("subprocess.run")
|
|
208
208
|
@mock.patch("hud.settings.settings")
|
|
209
|
-
@mock.patch("hud.cli.push.
|
|
210
|
-
def test_push_explicit_image(self,
|
|
209
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
210
|
+
def test_push_explicit_image(self, mock_hud_console_class, mock_settings, mock_run, tmp_path):
|
|
211
211
|
"""Test pushing with explicit image name."""
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
mock_hud_console = mock.Mock()
|
|
213
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
214
214
|
mock_settings.api_key = "test-key"
|
|
215
215
|
|
|
216
216
|
# Create lock file
|
|
@@ -227,11 +227,13 @@ class TestPushEnvironment:
|
|
|
227
227
|
@mock.patch("subprocess.Popen")
|
|
228
228
|
@mock.patch("subprocess.run")
|
|
229
229
|
@mock.patch("hud.settings.settings")
|
|
230
|
-
@mock.patch("hud.cli.push.
|
|
231
|
-
def test_push_with_tag(
|
|
230
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
231
|
+
def test_push_with_tag(
|
|
232
|
+
self, mock_hud_console_class, mock_settings, mock_run, mock_popen, tmp_path
|
|
233
|
+
):
|
|
232
234
|
"""Test pushing with explicit tag."""
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
mock_hud_console = mock.Mock()
|
|
236
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
235
237
|
mock_settings.api_key = "test-key"
|
|
236
238
|
|
|
237
239
|
# Create lock file
|
|
@@ -269,11 +271,11 @@ class TestPushEnvironment:
|
|
|
269
271
|
assert "user/test:v2.0" in tag_call[0][0][0]
|
|
270
272
|
|
|
271
273
|
@mock.patch("subprocess.Popen")
|
|
272
|
-
@mock.patch("hud.cli.push.
|
|
273
|
-
def test_push_docker_failure(self,
|
|
274
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
275
|
+
def test_push_docker_failure(self, mock_hud_console_class, mock_popen):
|
|
274
276
|
"""Test handling Docker push failure."""
|
|
275
|
-
|
|
276
|
-
|
|
277
|
+
mock_hud_console = mock.Mock()
|
|
278
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
277
279
|
|
|
278
280
|
# Mock docker push failure
|
|
279
281
|
mock_process = mock.Mock()
|
|
@@ -293,13 +295,13 @@ class TestPushEnvironment:
|
|
|
293
295
|
@mock.patch("hud.cli.push.get_docker_image_labels")
|
|
294
296
|
@mock.patch("subprocess.run")
|
|
295
297
|
@mock.patch("hud.settings.settings")
|
|
296
|
-
@mock.patch("hud.cli.push.
|
|
298
|
+
@mock.patch("hud.cli.push.HUDConsole")
|
|
297
299
|
def test_push_with_labels(
|
|
298
|
-
self,
|
|
300
|
+
self, mock_hud_console_class, mock_settings, mock_run, mock_get_labels, tmp_path
|
|
299
301
|
):
|
|
300
302
|
"""Test pushing with image labels."""
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
mock_hud_console = mock.Mock()
|
|
304
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
303
305
|
mock_settings.api_key = "test-key"
|
|
304
306
|
|
|
305
307
|
# Create lock file
|
hud/cli/tests/test_registry.py
CHANGED
|
@@ -118,11 +118,11 @@ class TestExtractNameAndTag:
|
|
|
118
118
|
class TestSaveToRegistry:
|
|
119
119
|
"""Test saving to local registry."""
|
|
120
120
|
|
|
121
|
-
@mock.patch("hud.cli.utils.registry.
|
|
122
|
-
def test_save_success(self,
|
|
121
|
+
@mock.patch("hud.cli.utils.registry.HUDConsole")
|
|
122
|
+
def test_save_success(self, mock_hud_console_class, tmp_path):
|
|
123
123
|
"""Test successful save to registry."""
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
mock_hud_console = mock.Mock()
|
|
125
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
126
126
|
|
|
127
127
|
# Mock home directory
|
|
128
128
|
with mock.patch("pathlib.Path.home", return_value=tmp_path):
|
|
@@ -142,13 +142,13 @@ class TestSaveToRegistry:
|
|
|
142
142
|
# Verify directory structure
|
|
143
143
|
assert result.parent.name == "abc123def456"
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
mock_hud_console.success.assert_called_once()
|
|
146
146
|
|
|
147
|
-
@mock.patch("hud.cli.utils.registry.
|
|
148
|
-
def test_save_verbose(self,
|
|
147
|
+
@mock.patch("hud.cli.utils.registry.HUDConsole")
|
|
148
|
+
def test_save_verbose(self, mock_hud_console_class, tmp_path):
|
|
149
149
|
"""Test save with verbose output."""
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
mock_hud_console = mock.Mock()
|
|
151
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
152
152
|
|
|
153
153
|
with mock.patch("pathlib.Path.home", return_value=tmp_path):
|
|
154
154
|
lock_data = {"image": "test:v1"}
|
|
@@ -157,13 +157,13 @@ class TestSaveToRegistry:
|
|
|
157
157
|
|
|
158
158
|
assert result is not None
|
|
159
159
|
# Should show verbose info
|
|
160
|
-
assert
|
|
160
|
+
assert mock_hud_console.info.call_count >= 1
|
|
161
161
|
|
|
162
|
-
@mock.patch("hud.cli.utils.registry.
|
|
163
|
-
def test_save_failure(self,
|
|
162
|
+
@mock.patch("hud.cli.utils.registry.HUDConsole")
|
|
163
|
+
def test_save_failure(self, mock_hud_console_class):
|
|
164
164
|
"""Test handling save failure."""
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
mock_hud_console = mock.Mock()
|
|
166
|
+
mock_hud_console_class.return_value = mock_hud_console
|
|
167
167
|
|
|
168
168
|
# Mock file operations to fail
|
|
169
169
|
with (
|
|
@@ -175,7 +175,7 @@ class TestSaveToRegistry:
|
|
|
175
175
|
result = save_to_registry(lock_data, "test:latest", verbose=True)
|
|
176
176
|
|
|
177
177
|
assert result is None
|
|
178
|
-
|
|
178
|
+
mock_hud_console.warning.assert_called_once()
|
|
179
179
|
|
|
180
180
|
|
|
181
181
|
class TestLoadFromRegistry:
|
hud/cli/utils/environment.py
CHANGED
|
@@ -7,9 +7,9 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
import toml
|
|
9
9
|
|
|
10
|
-
from hud.utils.
|
|
10
|
+
from hud.utils.hud_console import HUDConsole
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
hud_console = HUDConsole()
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def get_image_name(directory: str | Path, image_override: str | None = None) -> tuple[str, str]:
|
|
@@ -31,7 +31,7 @@ def get_image_name(directory: str | Path, image_override: str | None = None) ->
|
|
|
31
31
|
if config.get("tool", {}).get("hud", {}).get("image"):
|
|
32
32
|
return config["tool"]["hud"]["image"], "cache"
|
|
33
33
|
except Exception:
|
|
34
|
-
|
|
34
|
+
hud_console.error("Error loading pyproject.toml")
|
|
35
35
|
|
|
36
36
|
# Auto-generate with :dev tag
|
|
37
37
|
dir_path = Path(directory).resolve() # Get absolute path first
|
|
@@ -65,10 +65,10 @@ def update_pyproject_toml(directory: str | Path, image_name: str, silent: bool =
|
|
|
65
65
|
toml.dump(config, f)
|
|
66
66
|
|
|
67
67
|
if not silent:
|
|
68
|
-
|
|
68
|
+
hud_console.success(f"Updated pyproject.toml with image: {image_name}")
|
|
69
69
|
except Exception as e:
|
|
70
70
|
if not silent:
|
|
71
|
-
|
|
71
|
+
hud_console.warning(f"Could not update pyproject.toml: {e}")
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def build_environment(directory: str | Path, image_name: str, no_cache: bool = False) -> bool:
|
|
@@ -82,20 +82,20 @@ def build_environment(directory: str | Path, image_name: str, no_cache: bool = F
|
|
|
82
82
|
build_cmd.append("--no-cache")
|
|
83
83
|
build_cmd.append(str(directory))
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
hud_console.info(f"🔨 Building image: {image_name}{' (no cache)' if no_cache else ''}")
|
|
86
|
+
hud_console.info("") # Empty line before Docker output
|
|
87
87
|
|
|
88
88
|
# Just run Docker build directly - it has its own nice live display
|
|
89
89
|
result = subprocess.run(build_cmd) # noqa: S603
|
|
90
90
|
|
|
91
91
|
if result.returncode == 0:
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
hud_console.info("") # Empty line after Docker output
|
|
93
|
+
hud_console.success(f"Build successful! Image: {image_name}")
|
|
94
94
|
# Update pyproject.toml (silently since we already showed success)
|
|
95
95
|
update_pyproject_toml(directory, image_name, silent=True)
|
|
96
96
|
return True
|
|
97
97
|
else:
|
|
98
|
-
|
|
98
|
+
hud_console.error("Build failed!")
|
|
99
99
|
return False
|
|
100
100
|
|
|
101
101
|
|
|
@@ -127,7 +127,7 @@ def is_environment_directory(path: str | Path) -> bool:
|
|
|
127
127
|
|
|
128
128
|
# Must have pyproject.toml
|
|
129
129
|
if not (dir_path / "pyproject.toml").exists():
|
|
130
|
-
|
|
130
|
+
hud_console.error("pyproject.toml not found")
|
|
131
131
|
return False
|
|
132
132
|
|
|
133
133
|
return True
|
hud/cli/utils/interactive.py
CHANGED
|
@@ -14,7 +14,7 @@ from rich.syntax import Syntax
|
|
|
14
14
|
from rich.tree import Tree
|
|
15
15
|
|
|
16
16
|
from hud.clients import MCPClient
|
|
17
|
-
from hud.utils.
|
|
17
|
+
from hud.utils.hud_console import HUDConsole
|
|
18
18
|
|
|
19
19
|
console = Console()
|
|
20
20
|
|
|
@@ -33,7 +33,7 @@ class InteractiveMCPTester:
|
|
|
33
33
|
self.verbose = verbose
|
|
34
34
|
self.client: MCPClient | None = None
|
|
35
35
|
self.tools: list[Any] = []
|
|
36
|
-
self.
|
|
36
|
+
self.console = HUDConsole()
|
|
37
37
|
|
|
38
38
|
async def connect(self) -> bool:
|
|
39
39
|
"""Connect to the MCP server."""
|
|
@@ -53,7 +53,7 @@ class InteractiveMCPTester:
|
|
|
53
53
|
|
|
54
54
|
return True
|
|
55
55
|
except Exception as e:
|
|
56
|
-
self.
|
|
56
|
+
self.console.error(f"Failed to connect: {e}")
|
|
57
57
|
return False
|
|
58
58
|
|
|
59
59
|
async def disconnect(self) -> None:
|
|
@@ -361,7 +361,7 @@ class InteractiveMCPTester:
|
|
|
361
361
|
|
|
362
362
|
async def run(self) -> None:
|
|
363
363
|
"""Run the interactive testing loop."""
|
|
364
|
-
self.
|
|
364
|
+
self.console.header("Interactive MCP Tester")
|
|
365
365
|
|
|
366
366
|
# Connect to server
|
|
367
367
|
console.print(f"[cyan]Connecting to {self.server_url}...[/cyan]")
|
|
@@ -397,19 +397,19 @@ class InteractiveMCPTester:
|
|
|
397
397
|
await self.disconnect()
|
|
398
398
|
|
|
399
399
|
# Show next steps tutorial
|
|
400
|
-
self.
|
|
401
|
-
self.
|
|
402
|
-
self.
|
|
403
|
-
self.
|
|
404
|
-
self.
|
|
405
|
-
self.
|
|
406
|
-
self.
|
|
407
|
-
self.
|
|
408
|
-
self.
|
|
409
|
-
self.
|
|
410
|
-
self.
|
|
411
|
-
self.
|
|
412
|
-
self.
|
|
400
|
+
self.console.section_title("Next Steps")
|
|
401
|
+
self.console.info("🏗️ Ready to test with real agents? Run:")
|
|
402
|
+
self.console.info(" [cyan]hud build[/cyan]")
|
|
403
|
+
self.console.info("")
|
|
404
|
+
self.console.info("This will:")
|
|
405
|
+
self.console.info(" 1. Build your environment image")
|
|
406
|
+
self.console.info(" 2. Generate a hud.lock.yaml file")
|
|
407
|
+
self.console.info(" 3. Prepare it for testing with agents")
|
|
408
|
+
self.console.info("")
|
|
409
|
+
self.console.info("Then you can:")
|
|
410
|
+
self.console.info(" • Test locally: [cyan]hud run <image>[/cyan]")
|
|
411
|
+
self.console.info(" • Push to registry: [cyan]hud push --image <registry/name>[/cyan]")
|
|
412
|
+
self.console.info(" • Use with agents via the lock file")
|
|
413
413
|
|
|
414
414
|
console.print("\n[dim]Happy testing! 🎉[/dim]")
|
|
415
415
|
|
hud/cli/utils/logging.py
CHANGED
|
@@ -15,18 +15,18 @@ if sys.platform == "win32":
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Colors:
|
|
18
|
-
"""ANSI color codes for terminal output."""
|
|
19
|
-
|
|
20
|
-
HEADER = "\033[95m"
|
|
21
|
-
BLUE = "\033[94m"
|
|
22
|
-
CYAN = "\033[96m"
|
|
23
|
-
GREEN = "\033[92m"
|
|
24
|
-
YELLOW = "\033[93m"
|
|
25
|
-
GOLD = "\033[33m"
|
|
26
|
-
RED = "\033[91m"
|
|
27
|
-
GRAY = "\033[
|
|
28
|
-
ENDC = "\033[0m"
|
|
29
|
-
BOLD = "\033[1m"
|
|
18
|
+
"""ANSI color codes for terminal output - optimized for both light and dark modes."""
|
|
19
|
+
|
|
20
|
+
HEADER = "\033[95m" # Light magenta
|
|
21
|
+
BLUE = "\033[94m" # Light blue
|
|
22
|
+
CYAN = "\033[96m" # Light cyan
|
|
23
|
+
GREEN = "\033[92m" # Light green
|
|
24
|
+
YELLOW = "\033[93m" # Light yellow
|
|
25
|
+
GOLD = "\033[33m" # Gold/orange
|
|
26
|
+
RED = "\033[91m" # Light red
|
|
27
|
+
GRAY = "\033[37m" # Light gray
|
|
28
|
+
ENDC = "\033[0m" # Reset
|
|
29
|
+
BOLD = "\033[1m" # Bold
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class CaptureLogger:
|
hud/cli/utils/metadata.py
CHANGED
|
@@ -10,7 +10,7 @@ from rich.console import Console
|
|
|
10
10
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
11
|
|
|
12
12
|
from hud.settings import settings
|
|
13
|
-
from hud.utils.
|
|
13
|
+
from hud.utils.hud_console import HUDConsole
|
|
14
14
|
|
|
15
15
|
from .registry import (
|
|
16
16
|
extract_digest_from_image,
|
|
@@ -19,7 +19,7 @@ from .registry import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
console = Console()
|
|
22
|
-
|
|
22
|
+
hud_console = HUDConsole()
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
@@ -81,7 +81,7 @@ def check_local_cache(reference: str) -> dict | None:
|
|
|
81
81
|
if ref_base in img_base or img_base in ref_base:
|
|
82
82
|
return lock_data
|
|
83
83
|
except Exception:
|
|
84
|
-
|
|
84
|
+
hud_console.error("Error loading lock file")
|
|
85
85
|
|
|
86
86
|
return None
|
|
87
87
|
|
|
@@ -92,9 +92,9 @@ async def analyze_from_metadata(reference: str, output_format: str, verbose: boo
|
|
|
92
92
|
|
|
93
93
|
from hud.cli.analyze import display_interactive, display_markdown
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
hud_console.header("MCP Environment Analysis", icon="🔍")
|
|
96
|
+
hud_console.info(f"Looking up: {reference}")
|
|
97
|
+
hud_console.info("")
|
|
98
98
|
|
|
99
99
|
lock_data = None
|
|
100
100
|
source = None
|
|
@@ -155,7 +155,7 @@ async def analyze_from_metadata(reference: str, output_format: str, verbose: boo
|
|
|
155
155
|
progress.update(task, description="[red]✗ Not found[/red]")
|
|
156
156
|
|
|
157
157
|
if not lock_data:
|
|
158
|
-
|
|
158
|
+
hud_console.error("Environment metadata not found")
|
|
159
159
|
console.print("\n[yellow]This environment hasn't been analyzed yet.[/yellow]")
|
|
160
160
|
console.print("\nOptions:")
|
|
161
161
|
console.print(f" 1. Pull it first: [cyan]hud pull {reference}[/cyan]")
|
|
@@ -205,16 +205,16 @@ async def analyze_from_metadata(reference: str, output_format: str, verbose: boo
|
|
|
205
205
|
)
|
|
206
206
|
|
|
207
207
|
# Display results
|
|
208
|
-
|
|
208
|
+
hud_console.info("")
|
|
209
209
|
if source == "local":
|
|
210
|
-
|
|
210
|
+
hud_console.dim_info("Source:", "Local cache")
|
|
211
211
|
else:
|
|
212
|
-
|
|
212
|
+
hud_console.dim_info("Source:", "HUD registry")
|
|
213
213
|
|
|
214
214
|
if "image" in analysis:
|
|
215
|
-
|
|
215
|
+
hud_console.dim_info("Image:", analysis["image"])
|
|
216
216
|
|
|
217
|
-
|
|
217
|
+
hud_console.info("")
|
|
218
218
|
|
|
219
219
|
# Display results based on format
|
|
220
220
|
if output_format == "json":
|
hud/cli/utils/registry.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import yaml
|
|
9
9
|
|
|
10
|
-
from hud.utils.
|
|
10
|
+
from hud.utils.hud_console import HUDConsole
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_registry_dir() -> Path:
|
|
@@ -84,7 +84,7 @@ def save_to_registry(
|
|
|
84
84
|
Returns:
|
|
85
85
|
Path to the saved lock file, or None if save failed
|
|
86
86
|
"""
|
|
87
|
-
|
|
87
|
+
hud_console = HUDConsole()
|
|
88
88
|
|
|
89
89
|
try:
|
|
90
90
|
# Extract digest for registry storage
|
|
@@ -98,14 +98,14 @@ def save_to_registry(
|
|
|
98
98
|
with open(local_lock_path, "w") as f:
|
|
99
99
|
yaml.dump(lock_data, f, default_flow_style=False, sort_keys=False)
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
hud_console.success(f"Added to local registry: {digest}")
|
|
102
102
|
if verbose:
|
|
103
|
-
|
|
103
|
+
hud_console.info(f"Registry location: {local_lock_path}")
|
|
104
104
|
|
|
105
105
|
return local_lock_path
|
|
106
106
|
except Exception as e:
|
|
107
107
|
if verbose:
|
|
108
|
-
|
|
108
|
+
hud_console.warning(f"Failed to save to registry: {e}")
|
|
109
109
|
return None
|
|
110
110
|
|
|
111
111
|
|
hud/cli/utils/runner.py
CHANGED
|
@@ -6,7 +6,7 @@ import asyncio
|
|
|
6
6
|
import subprocess
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
-
from hud.utils.
|
|
9
|
+
from hud.utils.hud_console import HUDConsole
|
|
10
10
|
|
|
11
11
|
from .logging import find_free_port
|
|
12
12
|
from .server import MCPServerManager, run_server_with_interactive
|
|
@@ -14,29 +14,29 @@ from .server import MCPServerManager, run_server_with_interactive
|
|
|
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
|
-
|
|
17
|
+
hud_console = HUDConsole() # Use stderr for stdio mode
|
|
18
18
|
|
|
19
19
|
# Build docker command
|
|
20
20
|
docker_cmd = ["docker", "run", "--rm", "-i", *docker_args, image]
|
|
21
21
|
|
|
22
22
|
if verbose:
|
|
23
|
-
|
|
23
|
+
hud_console.info(f"🐳 Running: {' '.join(docker_cmd)}")
|
|
24
24
|
|
|
25
25
|
# Run docker directly with stdio passthrough
|
|
26
26
|
try:
|
|
27
27
|
result = subprocess.run(docker_cmd, stdin=sys.stdin) # noqa: S603
|
|
28
28
|
sys.exit(result.returncode)
|
|
29
29
|
except KeyboardInterrupt:
|
|
30
|
-
|
|
30
|
+
hud_console.info("\n👋 Shutting down...")
|
|
31
31
|
sys.exit(0)
|
|
32
32
|
except Exception as e:
|
|
33
|
-
|
|
33
|
+
hud_console.error(f"Error: {e}")
|
|
34
34
|
sys.exit(1)
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
async def run_http_server(image: str, docker_args: list[str], port: int, verbose: bool) -> None:
|
|
38
38
|
"""Run Docker image as HTTP MCP server (proxy mode)."""
|
|
39
|
-
|
|
39
|
+
hud_console = HUDConsole()
|
|
40
40
|
|
|
41
41
|
# Create server manager
|
|
42
42
|
server_manager = MCPServerManager(image, docker_args)
|
|
@@ -44,11 +44,11 @@ async def run_http_server(image: str, docker_args: list[str], port: int, verbose
|
|
|
44
44
|
# Find available port
|
|
45
45
|
actual_port = find_free_port(port)
|
|
46
46
|
if actual_port is None:
|
|
47
|
-
|
|
47
|
+
hud_console.error(f"No available ports found starting from {port}")
|
|
48
48
|
return
|
|
49
49
|
|
|
50
50
|
if actual_port != port:
|
|
51
|
-
|
|
51
|
+
hud_console.warning(f"Port {port} in use, using port {actual_port} instead")
|
|
52
52
|
|
|
53
53
|
# Clean up any existing container
|
|
54
54
|
server_manager.cleanup_container()
|
|
@@ -63,21 +63,21 @@ async def run_http_server(image: str, docker_args: list[str], port: int, verbose
|
|
|
63
63
|
proxy = server_manager.create_proxy(config)
|
|
64
64
|
|
|
65
65
|
# Show header
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
hud_console.info("") # Empty line
|
|
67
|
+
hud_console.header("HUD MCP Server", icon="🌐")
|
|
68
68
|
|
|
69
69
|
# Show configuration
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
hud_console.section_title("Server Information")
|
|
71
|
+
hud_console.info(f"Port: {actual_port}")
|
|
72
|
+
hud_console.info(f"URL: http://localhost:{actual_port}/mcp")
|
|
73
|
+
hud_console.info(f"Container: {server_manager.container_name}")
|
|
74
|
+
hud_console.info("")
|
|
75
|
+
hud_console.progress_message("Press Ctrl+C to stop")
|
|
76
76
|
|
|
77
77
|
try:
|
|
78
78
|
await server_manager.run_http_server(proxy, actual_port, verbose)
|
|
79
79
|
except KeyboardInterrupt:
|
|
80
|
-
|
|
80
|
+
hud_console.info("\n👋 Shutting down...")
|
|
81
81
|
finally:
|
|
82
82
|
# Clean up container
|
|
83
83
|
server_manager.cleanup_container()
|
|
@@ -105,8 +105,8 @@ def run_mcp_server(
|
|
|
105
105
|
"""Run Docker image as MCP server with specified transport."""
|
|
106
106
|
if transport == "stdio":
|
|
107
107
|
if interactive:
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
hud_console = HUDConsole()
|
|
109
|
+
hud_console.error("Interactive mode requires HTTP transport")
|
|
110
110
|
sys.exit(1)
|
|
111
111
|
run_stdio_server(image, docker_args, verbose)
|
|
112
112
|
elif transport == "http":
|
|
@@ -126,9 +126,9 @@ def run_mcp_server(
|
|
|
126
126
|
"Application shutdown complete",
|
|
127
127
|
]
|
|
128
128
|
):
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
hud_console = HUDConsole()
|
|
130
|
+
hud_console.error(f"Unexpected error: {e}")
|
|
131
131
|
else:
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
hud_console = HUDConsole()
|
|
133
|
+
hud_console.error(f"Unknown transport: {transport}")
|
|
134
134
|
sys.exit(1)
|
hud/cli/utils/server.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from fastmcp import FastMCP
|
|
9
9
|
|
|
10
|
-
from hud.utils.
|
|
10
|
+
from hud.utils.hud_console import HUDConsole
|
|
11
11
|
|
|
12
12
|
from .docker import generate_container_name, remove_container
|
|
13
13
|
|
|
@@ -24,7 +24,7 @@ class MCPServerManager:
|
|
|
24
24
|
"""
|
|
25
25
|
self.image = image
|
|
26
26
|
self.docker_args = docker_args or []
|
|
27
|
-
self.
|
|
27
|
+
self.console = HUDConsole()
|
|
28
28
|
self.container_name = self._generate_container_name()
|
|
29
29
|
|
|
30
30
|
def _generate_container_name(self) -> str:
|
|
@@ -155,7 +155,7 @@ class MCPServerManager:
|
|
|
155
155
|
pass # Normal cancellation
|
|
156
156
|
except Exception as e:
|
|
157
157
|
if verbose:
|
|
158
|
-
self.
|
|
158
|
+
self.console.error(f"Server error: {e}")
|
|
159
159
|
raise
|
|
160
160
|
|
|
161
161
|
|
|
@@ -174,16 +174,16 @@ async def run_server_with_interactive(
|
|
|
174
174
|
from .interactive import run_interactive_mode
|
|
175
175
|
from .logging import find_free_port
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
hud_console = HUDConsole()
|
|
178
178
|
|
|
179
179
|
# Find available port
|
|
180
180
|
actual_port = find_free_port(port)
|
|
181
181
|
if actual_port is None:
|
|
182
|
-
|
|
182
|
+
hud_console.error(f"No available ports found starting from {port}")
|
|
183
183
|
return
|
|
184
184
|
|
|
185
185
|
if actual_port != port:
|
|
186
|
-
|
|
186
|
+
hud_console.warning(f"Port {port} in use, using port {actual_port} instead")
|
|
187
187
|
|
|
188
188
|
# Clean up any existing container
|
|
189
189
|
server_manager.cleanup_container()
|
|
@@ -198,16 +198,16 @@ async def run_server_with_interactive(
|
|
|
198
198
|
proxy = server_manager.create_proxy(config, f"HUD Interactive - {server_manager.image}")
|
|
199
199
|
|
|
200
200
|
# Show header
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
hud_console.info("") # Empty line
|
|
202
|
+
hud_console.header("HUD MCP Server - Interactive Mode", icon="🎮")
|
|
203
203
|
|
|
204
204
|
# Show configuration
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
hud_console.section_title("Server Information")
|
|
206
|
+
hud_console.info(f"Image: {server_manager.image}")
|
|
207
|
+
hud_console.info(f"Port: {actual_port}")
|
|
208
|
+
hud_console.info(f"URL: http://localhost:{actual_port}/mcp")
|
|
209
|
+
hud_console.info(f"Container: {server_manager.container_name}")
|
|
210
|
+
hud_console.info("")
|
|
211
211
|
|
|
212
212
|
# Create event to signal server is ready
|
|
213
213
|
server_ready = asyncio.Event()
|
|
@@ -236,7 +236,7 @@ async def run_server_with_interactive(
|
|
|
236
236
|
await run_interactive_mode(server_url, verbose=verbose)
|
|
237
237
|
|
|
238
238
|
except KeyboardInterrupt:
|
|
239
|
-
|
|
239
|
+
hud_console.info("\n👋 Shutting down...")
|
|
240
240
|
finally:
|
|
241
241
|
# Cancel server task
|
|
242
242
|
if server_task and not server_task.done():
|
|
@@ -244,7 +244,7 @@ async def run_server_with_interactive(
|
|
|
244
244
|
try:
|
|
245
245
|
await server_task
|
|
246
246
|
except asyncio.CancelledError:
|
|
247
|
-
|
|
247
|
+
hud_console.error("Server task cancelled")
|
|
248
248
|
|
|
249
249
|
# Clean up container
|
|
250
250
|
server_manager.cleanup_container()
|