hud-python 0.4.1__py3-none-any.whl → 0.4.3__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/__init__.py +22 -22
- hud/agents/__init__.py +13 -15
- hud/agents/base.py +599 -599
- hud/agents/claude.py +373 -373
- hud/agents/langchain.py +261 -250
- hud/agents/misc/__init__.py +7 -7
- hud/agents/misc/response_agent.py +82 -80
- hud/agents/openai.py +352 -352
- hud/agents/openai_chat_generic.py +154 -154
- hud/agents/tests/__init__.py +1 -1
- hud/agents/tests/test_base.py +742 -742
- hud/agents/tests/test_claude.py +324 -324
- hud/agents/tests/test_client.py +363 -363
- hud/agents/tests/test_openai.py +237 -237
- hud/cli/__init__.py +617 -617
- hud/cli/__main__.py +8 -8
- hud/cli/analyze.py +371 -371
- hud/cli/analyze_metadata.py +230 -230
- hud/cli/build.py +498 -427
- hud/cli/clone.py +185 -185
- hud/cli/cursor.py +92 -92
- hud/cli/debug.py +392 -392
- hud/cli/docker_utils.py +83 -83
- hud/cli/init.py +280 -281
- hud/cli/interactive.py +353 -353
- hud/cli/mcp_server.py +764 -756
- hud/cli/pull.py +330 -336
- hud/cli/push.py +404 -370
- hud/cli/remote_runner.py +311 -311
- hud/cli/runner.py +160 -160
- hud/cli/tests/__init__.py +3 -3
- hud/cli/tests/test_analyze.py +284 -284
- hud/cli/tests/test_cli_init.py +265 -265
- hud/cli/tests/test_cli_main.py +27 -27
- hud/cli/tests/test_clone.py +142 -142
- hud/cli/tests/test_cursor.py +253 -253
- hud/cli/tests/test_debug.py +453 -453
- hud/cli/tests/test_mcp_server.py +139 -139
- hud/cli/tests/test_utils.py +388 -388
- hud/cli/utils.py +263 -263
- hud/clients/README.md +143 -143
- hud/clients/__init__.py +16 -16
- hud/clients/base.py +378 -379
- hud/clients/fastmcp.py +222 -222
- hud/clients/mcp_use.py +298 -278
- hud/clients/tests/__init__.py +1 -1
- hud/clients/tests/test_client_integration.py +111 -111
- hud/clients/tests/test_fastmcp.py +342 -342
- hud/clients/tests/test_protocol.py +188 -188
- hud/clients/utils/__init__.py +1 -1
- hud/clients/utils/retry_transport.py +160 -160
- hud/datasets.py +327 -322
- hud/misc/__init__.py +1 -1
- hud/misc/claude_plays_pokemon.py +292 -292
- hud/otel/__init__.py +35 -35
- hud/otel/collector.py +142 -142
- hud/otel/config.py +164 -164
- hud/otel/context.py +536 -536
- hud/otel/exporters.py +366 -366
- hud/otel/instrumentation.py +97 -97
- hud/otel/processors.py +118 -118
- hud/otel/tests/__init__.py +1 -1
- hud/otel/tests/test_processors.py +197 -197
- hud/server/__init__.py +5 -5
- hud/server/context.py +114 -114
- hud/server/helper/__init__.py +5 -5
- hud/server/low_level.py +132 -132
- hud/server/server.py +170 -166
- hud/server/tests/__init__.py +3 -3
- hud/settings.py +73 -73
- hud/shared/__init__.py +5 -5
- hud/shared/exceptions.py +180 -180
- hud/shared/requests.py +264 -264
- hud/shared/tests/test_exceptions.py +157 -157
- hud/shared/tests/test_requests.py +275 -275
- hud/telemetry/__init__.py +25 -25
- hud/telemetry/instrument.py +379 -379
- hud/telemetry/job.py +309 -309
- hud/telemetry/replay.py +74 -74
- hud/telemetry/trace.py +83 -83
- hud/tools/__init__.py +33 -33
- hud/tools/base.py +365 -365
- hud/tools/bash.py +161 -161
- hud/tools/computer/__init__.py +15 -15
- hud/tools/computer/anthropic.py +437 -437
- hud/tools/computer/hud.py +376 -376
- hud/tools/computer/openai.py +295 -295
- hud/tools/computer/settings.py +82 -82
- hud/tools/edit.py +314 -314
- hud/tools/executors/__init__.py +30 -30
- hud/tools/executors/base.py +539 -539
- hud/tools/executors/pyautogui.py +621 -621
- hud/tools/executors/tests/__init__.py +1 -1
- hud/tools/executors/tests/test_base_executor.py +338 -338
- hud/tools/executors/tests/test_pyautogui_executor.py +165 -165
- hud/tools/executors/xdo.py +511 -511
- hud/tools/playwright.py +412 -412
- hud/tools/tests/__init__.py +3 -3
- hud/tools/tests/test_base.py +282 -282
- hud/tools/tests/test_bash.py +158 -158
- hud/tools/tests/test_bash_extended.py +197 -197
- hud/tools/tests/test_computer.py +425 -425
- hud/tools/tests/test_computer_actions.py +34 -34
- hud/tools/tests/test_edit.py +259 -259
- hud/tools/tests/test_init.py +27 -27
- hud/tools/tests/test_playwright_tool.py +183 -183
- hud/tools/tests/test_tools.py +145 -145
- hud/tools/tests/test_utils.py +156 -156
- hud/tools/types.py +72 -72
- hud/tools/utils.py +50 -50
- hud/types.py +136 -136
- hud/utils/__init__.py +10 -10
- hud/utils/async_utils.py +65 -65
- hud/utils/design.py +236 -168
- hud/utils/mcp.py +55 -55
- hud/utils/progress.py +149 -149
- hud/utils/telemetry.py +66 -66
- hud/utils/tests/test_async_utils.py +173 -173
- hud/utils/tests/test_init.py +17 -17
- hud/utils/tests/test_progress.py +261 -261
- hud/utils/tests/test_telemetry.py +82 -82
- hud/utils/tests/test_version.py +8 -8
- hud/version.py +7 -7
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/METADATA +10 -8
- hud_python-0.4.3.dist-info/RECORD +131 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/licenses/LICENSE +21 -21
- hud/agents/art.py +0 -101
- hud_python-0.4.1.dist-info/RECORD +0 -132
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/WHEEL +0 -0
- {hud_python-0.4.1.dist-info → hud_python-0.4.3.dist-info}/entry_points.txt +0 -0
hud/cli/analyze_metadata.py
CHANGED
|
@@ -1,230 +1,230 @@
|
|
|
1
|
-
"""Fast metadata analysis functions for hud analyze."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
import requests
|
|
8
|
-
import yaml
|
|
9
|
-
from rich.console import Console
|
|
10
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
-
|
|
12
|
-
from hud.settings import settings
|
|
13
|
-
from hud.utils.design import HUDDesign
|
|
14
|
-
|
|
15
|
-
console = Console()
|
|
16
|
-
design = HUDDesign()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
20
|
-
"""Fetch lock file from HUD registry."""
|
|
21
|
-
try:
|
|
22
|
-
# Reference should be org/name:tag format
|
|
23
|
-
# If no tag specified, append :latest
|
|
24
|
-
if "/" in reference and ":" not in reference:
|
|
25
|
-
reference = f"{reference}:latest"
|
|
26
|
-
|
|
27
|
-
registry_url = f"{settings.hud_telemetry_url.rstrip('/')}/registry/envs/{reference}"
|
|
28
|
-
|
|
29
|
-
headers = {}
|
|
30
|
-
if settings.api_key:
|
|
31
|
-
headers["Authorization"] = f"Bearer {settings.api_key}"
|
|
32
|
-
|
|
33
|
-
response = requests.get(registry_url, headers=headers, timeout=10)
|
|
34
|
-
|
|
35
|
-
if response.status_code == 200:
|
|
36
|
-
data = response.json()
|
|
37
|
-
# Parse the lock YAML from the response
|
|
38
|
-
if "lock" in data:
|
|
39
|
-
return yaml.safe_load(data["lock"])
|
|
40
|
-
elif "lock_data" in data:
|
|
41
|
-
return data["lock_data"]
|
|
42
|
-
else:
|
|
43
|
-
# Try to treat the whole response as lock data
|
|
44
|
-
return data
|
|
45
|
-
|
|
46
|
-
return None
|
|
47
|
-
except Exception:
|
|
48
|
-
return None
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def check_local_cache(reference: str) -> dict | None:
|
|
52
|
-
"""Check local cache for lock file."""
|
|
53
|
-
# Extract digest if present
|
|
54
|
-
if "@sha256:" in reference:
|
|
55
|
-
digest = reference.split("@sha256:")[-1][:12]
|
|
56
|
-
elif "/" in reference:
|
|
57
|
-
# Try to find by name pattern
|
|
58
|
-
cache_dir = Path.home() / ".hud" / "envs"
|
|
59
|
-
if cache_dir.exists():
|
|
60
|
-
# Look for any cached version of this image
|
|
61
|
-
for env_dir in cache_dir.iterdir():
|
|
62
|
-
if env_dir.is_dir():
|
|
63
|
-
lock_file = env_dir / "hud.lock.yaml"
|
|
64
|
-
if lock_file.exists():
|
|
65
|
-
with open(lock_file) as f:
|
|
66
|
-
lock_data = yaml.safe_load(f)
|
|
67
|
-
# Check if this matches our reference
|
|
68
|
-
if lock_data and "image" in lock_data:
|
|
69
|
-
image = lock_data["image"]
|
|
70
|
-
# Match by name (ignoring tag/digest)
|
|
71
|
-
ref_base = reference.split("@")[0].split(":")[0]
|
|
72
|
-
img_base = image.split("@")[0].split(":")[0]
|
|
73
|
-
if ref_base in img_base or img_base in ref_base:
|
|
74
|
-
return lock_data
|
|
75
|
-
return None
|
|
76
|
-
else:
|
|
77
|
-
digest = "latest"
|
|
78
|
-
|
|
79
|
-
# Check specific digest directory
|
|
80
|
-
lock_file = Path.home() / ".hud" / "envs" / digest / "hud.lock.yaml"
|
|
81
|
-
if lock_file.exists():
|
|
82
|
-
with open(lock_file) as f:
|
|
83
|
-
return yaml.safe_load(f)
|
|
84
|
-
|
|
85
|
-
return None
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
async def analyze_from_metadata(reference: str, output_format: str, verbose: bool) -> None:
|
|
89
|
-
"""Analyze environment from cached or registry metadata."""
|
|
90
|
-
import json
|
|
91
|
-
|
|
92
|
-
from .analyze import display_interactive, display_markdown
|
|
93
|
-
|
|
94
|
-
design.header("MCP Environment Analysis", icon="🔍")
|
|
95
|
-
design.info(f"Looking up: {reference}")
|
|
96
|
-
design.info("")
|
|
97
|
-
|
|
98
|
-
lock_data = None
|
|
99
|
-
source = None
|
|
100
|
-
|
|
101
|
-
# 1. Check local cache first
|
|
102
|
-
with Progress(
|
|
103
|
-
SpinnerColumn(),
|
|
104
|
-
TextColumn("[progress.description]{task.description}"),
|
|
105
|
-
console=console,
|
|
106
|
-
) as progress:
|
|
107
|
-
task = progress.add_task("Checking local cache...", total=None)
|
|
108
|
-
|
|
109
|
-
lock_data = check_local_cache(reference)
|
|
110
|
-
if lock_data:
|
|
111
|
-
progress.update(task, description="[green]✓ Found in local cache[/green]")
|
|
112
|
-
source = "local"
|
|
113
|
-
else:
|
|
114
|
-
progress.update(
|
|
115
|
-
task, description="[yellow]→ Not in cache, checking registry...[/yellow]"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
# 2. Try HUD registry
|
|
119
|
-
# Parse reference to get org/name format
|
|
120
|
-
if "/" in reference and "@" not in reference and ":" not in reference:
|
|
121
|
-
# Already in org/name format
|
|
122
|
-
registry_ref = reference
|
|
123
|
-
elif "/" in reference:
|
|
124
|
-
# Extract org/name from full reference
|
|
125
|
-
parts = reference.split("/")
|
|
126
|
-
if len(parts) >= 2:
|
|
127
|
-
# Handle docker.io/org/name or just org/name
|
|
128
|
-
if parts[0] in ["docker.io", "registry-1.docker.io", "index.docker.io"]:
|
|
129
|
-
# Remove registry prefix but keep tag
|
|
130
|
-
registry_ref = "/".join(parts[1:]).split("@")[0]
|
|
131
|
-
else:
|
|
132
|
-
# Keep org/name:tag format
|
|
133
|
-
registry_ref = "/".join(parts[:2]).split("@")[0]
|
|
134
|
-
else:
|
|
135
|
-
registry_ref = reference
|
|
136
|
-
else:
|
|
137
|
-
registry_ref = reference
|
|
138
|
-
|
|
139
|
-
if not settings.api_key:
|
|
140
|
-
progress.update(
|
|
141
|
-
task, description="[yellow]→ No API key (checking public registry)...[/yellow]"
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
lock_data = fetch_lock_from_registry(registry_ref)
|
|
145
|
-
if lock_data:
|
|
146
|
-
progress.update(task, description="[green]✓ Found in HUD registry[/green]")
|
|
147
|
-
source = "registry"
|
|
148
|
-
|
|
149
|
-
# Save to local cache for next time
|
|
150
|
-
if "@sha256:" in lock_data.get("image", ""):
|
|
151
|
-
digest = lock_data["image"].split("@sha256:")[-1][:12]
|
|
152
|
-
else:
|
|
153
|
-
digest = "latest"
|
|
154
|
-
|
|
155
|
-
cache_dir = Path.home() / ".hud" / "envs" / digest
|
|
156
|
-
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
-
with open(cache_dir / "hud.lock.yaml", "w") as f: # noqa: ASYNC230
|
|
158
|
-
yaml.dump(lock_data, f, default_flow_style=False, sort_keys=False)
|
|
159
|
-
else:
|
|
160
|
-
progress.update(task, description="[red]✗ Not found[/red]")
|
|
161
|
-
|
|
162
|
-
if not lock_data:
|
|
163
|
-
design.error("Environment metadata not found")
|
|
164
|
-
console.print("\n[yellow]This environment hasn't been analyzed yet.[/yellow]")
|
|
165
|
-
console.print("\nOptions:")
|
|
166
|
-
console.print(f" 1. Pull it first: [cyan]hud pull {reference}[/cyan]")
|
|
167
|
-
console.print(f" 2. Run live analysis: [cyan]hud analyze {reference} --live[/cyan]")
|
|
168
|
-
if not settings.api_key:
|
|
169
|
-
console.print(" 3. Set HUD_API_KEY for private environments")
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
# Convert lock data to analysis format
|
|
173
|
-
analysis = {
|
|
174
|
-
"status": "metadata" if source == "local" else "registry",
|
|
175
|
-
"source": source,
|
|
176
|
-
"tools": [],
|
|
177
|
-
"resources": [],
|
|
178
|
-
"prompts": [],
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
# Add basic info
|
|
182
|
-
if "image" in lock_data:
|
|
183
|
-
analysis["image"] = lock_data["image"]
|
|
184
|
-
|
|
185
|
-
if "build" in lock_data:
|
|
186
|
-
analysis["build_info"] = lock_data["build"]
|
|
187
|
-
|
|
188
|
-
if "push" in lock_data:
|
|
189
|
-
analysis["push_info"] = lock_data["push"]
|
|
190
|
-
|
|
191
|
-
# Extract environment info
|
|
192
|
-
if "environment" in lock_data:
|
|
193
|
-
env = lock_data["environment"]
|
|
194
|
-
if "initializeMs" in env:
|
|
195
|
-
analysis["init_time"] = env["initializeMs"]
|
|
196
|
-
if "toolCount" in env:
|
|
197
|
-
analysis["tool_count"] = env["toolCount"]
|
|
198
|
-
if "variables" in env:
|
|
199
|
-
analysis["env_vars"] = env["variables"]
|
|
200
|
-
|
|
201
|
-
# Extract tools
|
|
202
|
-
if "tools" in lock_data:
|
|
203
|
-
for tool in lock_data["tools"]:
|
|
204
|
-
analysis["tools"].append(
|
|
205
|
-
{
|
|
206
|
-
"name": tool["name"],
|
|
207
|
-
"description": tool.get("description", ""),
|
|
208
|
-
"inputSchema": tool.get("inputSchema", {}) if verbose else None,
|
|
209
|
-
}
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
# Display results
|
|
213
|
-
design.info("")
|
|
214
|
-
if source == "local":
|
|
215
|
-
design.dim_info("Source:", "Local cache")
|
|
216
|
-
else:
|
|
217
|
-
design.dim_info("Source:", "HUD registry")
|
|
218
|
-
|
|
219
|
-
if "image" in analysis:
|
|
220
|
-
design.dim_info("Image:", analysis["image"])
|
|
221
|
-
|
|
222
|
-
design.info("")
|
|
223
|
-
|
|
224
|
-
# Display results based on format
|
|
225
|
-
if output_format == "json":
|
|
226
|
-
console.print_json(json.dumps(analysis, indent=2))
|
|
227
|
-
elif output_format == "markdown":
|
|
228
|
-
display_markdown(analysis)
|
|
229
|
-
else: # interactive
|
|
230
|
-
display_interactive(analysis)
|
|
1
|
+
"""Fast metadata analysis functions for hud analyze."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
import yaml
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
|
|
12
|
+
from hud.settings import settings
|
|
13
|
+
from hud.utils.design import HUDDesign
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
design = HUDDesign()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def fetch_lock_from_registry(reference: str) -> dict | None:
|
|
20
|
+
"""Fetch lock file from HUD registry."""
|
|
21
|
+
try:
|
|
22
|
+
# Reference should be org/name:tag format
|
|
23
|
+
# If no tag specified, append :latest
|
|
24
|
+
if "/" in reference and ":" not in reference:
|
|
25
|
+
reference = f"{reference}:latest"
|
|
26
|
+
|
|
27
|
+
registry_url = f"{settings.hud_telemetry_url.rstrip('/')}/registry/envs/{reference}"
|
|
28
|
+
|
|
29
|
+
headers = {}
|
|
30
|
+
if settings.api_key:
|
|
31
|
+
headers["Authorization"] = f"Bearer {settings.api_key}"
|
|
32
|
+
|
|
33
|
+
response = requests.get(registry_url, headers=headers, timeout=10)
|
|
34
|
+
|
|
35
|
+
if response.status_code == 200:
|
|
36
|
+
data = response.json()
|
|
37
|
+
# Parse the lock YAML from the response
|
|
38
|
+
if "lock" in data:
|
|
39
|
+
return yaml.safe_load(data["lock"])
|
|
40
|
+
elif "lock_data" in data:
|
|
41
|
+
return data["lock_data"]
|
|
42
|
+
else:
|
|
43
|
+
# Try to treat the whole response as lock data
|
|
44
|
+
return data
|
|
45
|
+
|
|
46
|
+
return None
|
|
47
|
+
except Exception:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def check_local_cache(reference: str) -> dict | None:
|
|
52
|
+
"""Check local cache for lock file."""
|
|
53
|
+
# Extract digest if present
|
|
54
|
+
if "@sha256:" in reference:
|
|
55
|
+
digest = reference.split("@sha256:")[-1][:12]
|
|
56
|
+
elif "/" in reference:
|
|
57
|
+
# Try to find by name pattern
|
|
58
|
+
cache_dir = Path.home() / ".hud" / "envs"
|
|
59
|
+
if cache_dir.exists():
|
|
60
|
+
# Look for any cached version of this image
|
|
61
|
+
for env_dir in cache_dir.iterdir():
|
|
62
|
+
if env_dir.is_dir():
|
|
63
|
+
lock_file = env_dir / "hud.lock.yaml"
|
|
64
|
+
if lock_file.exists():
|
|
65
|
+
with open(lock_file) as f:
|
|
66
|
+
lock_data = yaml.safe_load(f)
|
|
67
|
+
# Check if this matches our reference
|
|
68
|
+
if lock_data and "image" in lock_data:
|
|
69
|
+
image = lock_data["image"]
|
|
70
|
+
# Match by name (ignoring tag/digest)
|
|
71
|
+
ref_base = reference.split("@")[0].split(":")[0]
|
|
72
|
+
img_base = image.split("@")[0].split(":")[0]
|
|
73
|
+
if ref_base in img_base or img_base in ref_base:
|
|
74
|
+
return lock_data
|
|
75
|
+
return None
|
|
76
|
+
else:
|
|
77
|
+
digest = "latest"
|
|
78
|
+
|
|
79
|
+
# Check specific digest directory
|
|
80
|
+
lock_file = Path.home() / ".hud" / "envs" / digest / "hud.lock.yaml"
|
|
81
|
+
if lock_file.exists():
|
|
82
|
+
with open(lock_file) as f:
|
|
83
|
+
return yaml.safe_load(f)
|
|
84
|
+
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def analyze_from_metadata(reference: str, output_format: str, verbose: bool) -> None:
|
|
89
|
+
"""Analyze environment from cached or registry metadata."""
|
|
90
|
+
import json
|
|
91
|
+
|
|
92
|
+
from .analyze import display_interactive, display_markdown
|
|
93
|
+
|
|
94
|
+
design.header("MCP Environment Analysis", icon="🔍")
|
|
95
|
+
design.info(f"Looking up: {reference}")
|
|
96
|
+
design.info("")
|
|
97
|
+
|
|
98
|
+
lock_data = None
|
|
99
|
+
source = None
|
|
100
|
+
|
|
101
|
+
# 1. Check local cache first
|
|
102
|
+
with Progress(
|
|
103
|
+
SpinnerColumn(),
|
|
104
|
+
TextColumn("[progress.description]{task.description}"),
|
|
105
|
+
console=console,
|
|
106
|
+
) as progress:
|
|
107
|
+
task = progress.add_task("Checking local cache...", total=None)
|
|
108
|
+
|
|
109
|
+
lock_data = check_local_cache(reference)
|
|
110
|
+
if lock_data:
|
|
111
|
+
progress.update(task, description="[green]✓ Found in local cache[/green]")
|
|
112
|
+
source = "local"
|
|
113
|
+
else:
|
|
114
|
+
progress.update(
|
|
115
|
+
task, description="[yellow]→ Not in cache, checking registry...[/yellow]"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# 2. Try HUD registry
|
|
119
|
+
# Parse reference to get org/name format
|
|
120
|
+
if "/" in reference and "@" not in reference and ":" not in reference:
|
|
121
|
+
# Already in org/name format
|
|
122
|
+
registry_ref = reference
|
|
123
|
+
elif "/" in reference:
|
|
124
|
+
# Extract org/name from full reference
|
|
125
|
+
parts = reference.split("/")
|
|
126
|
+
if len(parts) >= 2:
|
|
127
|
+
# Handle docker.io/org/name or just org/name
|
|
128
|
+
if parts[0] in ["docker.io", "registry-1.docker.io", "index.docker.io"]:
|
|
129
|
+
# Remove registry prefix but keep tag
|
|
130
|
+
registry_ref = "/".join(parts[1:]).split("@")[0]
|
|
131
|
+
else:
|
|
132
|
+
# Keep org/name:tag format
|
|
133
|
+
registry_ref = "/".join(parts[:2]).split("@")[0]
|
|
134
|
+
else:
|
|
135
|
+
registry_ref = reference
|
|
136
|
+
else:
|
|
137
|
+
registry_ref = reference
|
|
138
|
+
|
|
139
|
+
if not settings.api_key:
|
|
140
|
+
progress.update(
|
|
141
|
+
task, description="[yellow]→ No API key (checking public registry)...[/yellow]"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
lock_data = fetch_lock_from_registry(registry_ref)
|
|
145
|
+
if lock_data:
|
|
146
|
+
progress.update(task, description="[green]✓ Found in HUD registry[/green]")
|
|
147
|
+
source = "registry"
|
|
148
|
+
|
|
149
|
+
# Save to local cache for next time
|
|
150
|
+
if "@sha256:" in lock_data.get("image", ""):
|
|
151
|
+
digest = lock_data["image"].split("@sha256:")[-1][:12]
|
|
152
|
+
else:
|
|
153
|
+
digest = "latest"
|
|
154
|
+
|
|
155
|
+
cache_dir = Path.home() / ".hud" / "envs" / digest
|
|
156
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
157
|
+
with open(cache_dir / "hud.lock.yaml", "w") as f: # noqa: ASYNC230
|
|
158
|
+
yaml.dump(lock_data, f, default_flow_style=False, sort_keys=False)
|
|
159
|
+
else:
|
|
160
|
+
progress.update(task, description="[red]✗ Not found[/red]")
|
|
161
|
+
|
|
162
|
+
if not lock_data:
|
|
163
|
+
design.error("Environment metadata not found")
|
|
164
|
+
console.print("\n[yellow]This environment hasn't been analyzed yet.[/yellow]")
|
|
165
|
+
console.print("\nOptions:")
|
|
166
|
+
console.print(f" 1. Pull it first: [cyan]hud pull {reference}[/cyan]")
|
|
167
|
+
console.print(f" 2. Run live analysis: [cyan]hud analyze {reference} --live[/cyan]")
|
|
168
|
+
if not settings.api_key:
|
|
169
|
+
console.print(" 3. Set HUD_API_KEY for private environments")
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
# Convert lock data to analysis format
|
|
173
|
+
analysis = {
|
|
174
|
+
"status": "metadata" if source == "local" else "registry",
|
|
175
|
+
"source": source,
|
|
176
|
+
"tools": [],
|
|
177
|
+
"resources": [],
|
|
178
|
+
"prompts": [],
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Add basic info
|
|
182
|
+
if "image" in lock_data:
|
|
183
|
+
analysis["image"] = lock_data["image"]
|
|
184
|
+
|
|
185
|
+
if "build" in lock_data:
|
|
186
|
+
analysis["build_info"] = lock_data["build"]
|
|
187
|
+
|
|
188
|
+
if "push" in lock_data:
|
|
189
|
+
analysis["push_info"] = lock_data["push"]
|
|
190
|
+
|
|
191
|
+
# Extract environment info
|
|
192
|
+
if "environment" in lock_data:
|
|
193
|
+
env = lock_data["environment"]
|
|
194
|
+
if "initializeMs" in env:
|
|
195
|
+
analysis["init_time"] = env["initializeMs"]
|
|
196
|
+
if "toolCount" in env:
|
|
197
|
+
analysis["tool_count"] = env["toolCount"]
|
|
198
|
+
if "variables" in env:
|
|
199
|
+
analysis["env_vars"] = env["variables"]
|
|
200
|
+
|
|
201
|
+
# Extract tools
|
|
202
|
+
if "tools" in lock_data:
|
|
203
|
+
for tool in lock_data["tools"]:
|
|
204
|
+
analysis["tools"].append(
|
|
205
|
+
{
|
|
206
|
+
"name": tool["name"],
|
|
207
|
+
"description": tool.get("description", ""),
|
|
208
|
+
"inputSchema": tool.get("inputSchema", {}) if verbose else None,
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Display results
|
|
213
|
+
design.info("")
|
|
214
|
+
if source == "local":
|
|
215
|
+
design.dim_info("Source:", "Local cache")
|
|
216
|
+
else:
|
|
217
|
+
design.dim_info("Source:", "HUD registry")
|
|
218
|
+
|
|
219
|
+
if "image" in analysis:
|
|
220
|
+
design.dim_info("Image:", analysis["image"])
|
|
221
|
+
|
|
222
|
+
design.info("")
|
|
223
|
+
|
|
224
|
+
# Display results based on format
|
|
225
|
+
if output_format == "json":
|
|
226
|
+
console.print_json(json.dumps(analysis, indent=2))
|
|
227
|
+
elif output_format == "markdown":
|
|
228
|
+
display_markdown(analysis)
|
|
229
|
+
else: # interactive
|
|
230
|
+
display_interactive(analysis)
|