hud-python 0.4.22__py3-none-any.whl → 0.4.23__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 +37 -39
- 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/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.23.dist-info}/METADATA +3 -1
- {hud_python-0.4.22.dist-info → hud_python-0.4.23.dist-info}/RECORD +48 -48
- {hud_python-0.4.22.dist-info → hud_python-0.4.23.dist-info}/WHEEL +0 -0
- {hud_python-0.4.22.dist-info → hud_python-0.4.23.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.22.dist-info → hud_python-0.4.23.dist-info}/licenses/LICENSE +0 -0
hud/cli/hf.py
CHANGED
|
@@ -9,9 +9,9 @@ from pathlib import Path
|
|
|
9
9
|
import typer
|
|
10
10
|
|
|
11
11
|
from hud.cli.rl.utils import get_mcp_config_from_lock, read_lock_file, write_lock_file
|
|
12
|
-
from hud.utils.
|
|
12
|
+
from hud.utils.hud_console import HUDConsole
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
hud_console = HUDConsole()
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def hf_command(
|
|
@@ -37,11 +37,11 @@ def hf_command(
|
|
|
37
37
|
hud hf tasks.json --name hud-evals/web-tasks --private
|
|
38
38
|
hud hf tasks.json --name local-dataset --no-push
|
|
39
39
|
"""
|
|
40
|
-
|
|
40
|
+
hud_console.header("HuggingFace Dataset Converter", icon="📊")
|
|
41
41
|
|
|
42
42
|
# Auto-detect task file if not provided
|
|
43
43
|
if tasks_file is None:
|
|
44
|
-
|
|
44
|
+
hud_console.info("Looking for task files...")
|
|
45
45
|
|
|
46
46
|
# Common task file patterns
|
|
47
47
|
patterns = [
|
|
@@ -60,30 +60,30 @@ def hf_command(
|
|
|
60
60
|
json_files = sorted(set(json_files))
|
|
61
61
|
|
|
62
62
|
if not json_files:
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
hud_console.error("No task files found in current directory")
|
|
64
|
+
hud_console.info("Create a task JSON file (e.g., tasks.json) or specify the file path")
|
|
65
65
|
raise typer.Exit(1)
|
|
66
66
|
elif len(json_files) == 1:
|
|
67
67
|
tasks_file = json_files[0]
|
|
68
|
-
|
|
68
|
+
hud_console.info(f"Found task file: {tasks_file}")
|
|
69
69
|
else:
|
|
70
70
|
# Multiple files found, let user choose
|
|
71
|
-
|
|
72
|
-
file_choice =
|
|
71
|
+
hud_console.info("Multiple task files found:")
|
|
72
|
+
file_choice = hud_console.select(
|
|
73
73
|
"Select a task file to convert:",
|
|
74
74
|
choices=[str(f) for f in json_files],
|
|
75
75
|
)
|
|
76
76
|
tasks_file = Path(file_choice)
|
|
77
|
-
|
|
77
|
+
hud_console.success(f"Selected: {tasks_file}")
|
|
78
78
|
|
|
79
79
|
# Validate inputs
|
|
80
80
|
if tasks_file and not tasks_file.exists():
|
|
81
|
-
|
|
81
|
+
hud_console.error(f"Tasks file not found: {tasks_file}")
|
|
82
82
|
raise typer.Exit(1)
|
|
83
83
|
|
|
84
84
|
# Suggest dataset name if not provided
|
|
85
85
|
if name is None:
|
|
86
|
-
|
|
86
|
+
hud_console.info("Generating dataset name suggestion...")
|
|
87
87
|
|
|
88
88
|
# Try to get HF username from environment or git config
|
|
89
89
|
hf_username = None
|
|
@@ -107,7 +107,7 @@ def hf_command(
|
|
|
107
107
|
if result.returncode == 0 and result.stdout.strip():
|
|
108
108
|
hf_username = result.stdout.strip().lower().replace(" ", "-")
|
|
109
109
|
except Exception:
|
|
110
|
-
|
|
110
|
+
hud_console.warning("Failed to get HF username from git config")
|
|
111
111
|
|
|
112
112
|
# Get environment name from current directory or lock file
|
|
113
113
|
env_name = Path.cwd().name
|
|
@@ -126,7 +126,7 @@ def hf_command(
|
|
|
126
126
|
if image_name and image_name != "local":
|
|
127
127
|
env_name = image_name
|
|
128
128
|
except Exception as e:
|
|
129
|
-
|
|
129
|
+
hud_console.warning(f"Failed to get HF username from lock file: {e}")
|
|
130
130
|
|
|
131
131
|
# Generate suggestions
|
|
132
132
|
suggestions = []
|
|
@@ -137,60 +137,60 @@ def hf_command(
|
|
|
137
137
|
suggestions.append(f"hud-evals/{env_name}-tasks")
|
|
138
138
|
|
|
139
139
|
# Let user choose or enter custom
|
|
140
|
-
|
|
140
|
+
hud_console.info("Dataset name suggestions:")
|
|
141
141
|
suggestions.append("Enter custom name...")
|
|
142
142
|
|
|
143
|
-
choice =
|
|
143
|
+
choice = hud_console.select("Select or enter a dataset name:", choices=suggestions)
|
|
144
144
|
|
|
145
145
|
if choice == "Enter custom name...":
|
|
146
146
|
name = typer.prompt("Enter dataset name (e.g., 'my-org/my-dataset')")
|
|
147
147
|
else:
|
|
148
148
|
name = choice
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
hud_console.success(f"Using dataset name: {name}")
|
|
151
151
|
|
|
152
152
|
# Validate dataset name format
|
|
153
153
|
if push and name and "/" not in name:
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
hud_console.error("Dataset name must include organization (e.g., 'my-org/my-dataset')")
|
|
155
|
+
hud_console.info("For local-only datasets, use --no-push")
|
|
156
156
|
raise typer.Exit(1)
|
|
157
157
|
|
|
158
158
|
# Load tasks
|
|
159
|
-
|
|
159
|
+
hud_console.info(f"Loading tasks from: {tasks_file}")
|
|
160
160
|
try:
|
|
161
161
|
if tasks_file is None:
|
|
162
162
|
raise ValueError("Tasks file is required")
|
|
163
163
|
with open(tasks_file) as f:
|
|
164
164
|
tasks_data = json.load(f)
|
|
165
165
|
except json.JSONDecodeError as e:
|
|
166
|
-
|
|
166
|
+
hud_console.error(f"Invalid JSON file: {e}")
|
|
167
167
|
raise typer.Exit(1) from e
|
|
168
168
|
|
|
169
169
|
# Handle both single task and list of tasks
|
|
170
170
|
if isinstance(tasks_data, dict):
|
|
171
171
|
tasks = [tasks_data]
|
|
172
|
-
|
|
172
|
+
hud_console.info("Found 1 task")
|
|
173
173
|
elif isinstance(tasks_data, list):
|
|
174
174
|
tasks = tasks_data
|
|
175
|
-
|
|
175
|
+
hud_console.info(f"Found {len(tasks)} tasks")
|
|
176
176
|
else:
|
|
177
|
-
|
|
177
|
+
hud_console.error("Tasks file must contain a JSON object or array")
|
|
178
178
|
raise typer.Exit(1)
|
|
179
179
|
|
|
180
180
|
# Validate task format
|
|
181
181
|
valid_tasks = []
|
|
182
182
|
for i, task in enumerate(tasks):
|
|
183
183
|
if not isinstance(task, dict):
|
|
184
|
-
|
|
184
|
+
hud_console.warning(f"Skipping task {i}: not a JSON object")
|
|
185
185
|
continue
|
|
186
186
|
|
|
187
187
|
# Required fields
|
|
188
188
|
if "prompt" not in task:
|
|
189
|
-
|
|
189
|
+
hud_console.warning(f"Skipping task {i}: missing 'prompt' field")
|
|
190
190
|
continue
|
|
191
191
|
|
|
192
192
|
if "evaluate_tool" not in task:
|
|
193
|
-
|
|
193
|
+
hud_console.warning(f"Skipping task {i}: missing 'evaluate_tool' field")
|
|
194
194
|
continue
|
|
195
195
|
|
|
196
196
|
# Add default values
|
|
@@ -203,23 +203,23 @@ def hf_command(
|
|
|
203
203
|
if mcp_config:
|
|
204
204
|
task["mcp_config"] = mcp_config
|
|
205
205
|
else:
|
|
206
|
-
|
|
206
|
+
hud_console.warning(f"Task {task['id']}: missing 'mcp_config' field")
|
|
207
207
|
continue
|
|
208
208
|
|
|
209
209
|
valid_tasks.append(task)
|
|
210
210
|
|
|
211
211
|
if not valid_tasks:
|
|
212
|
-
|
|
212
|
+
hud_console.error("No valid tasks found")
|
|
213
213
|
raise typer.Exit(1)
|
|
214
214
|
|
|
215
|
-
|
|
215
|
+
hud_console.success(f"Validated {len(valid_tasks)} tasks")
|
|
216
216
|
|
|
217
217
|
# Check if dataset is suitable for training
|
|
218
218
|
if len(valid_tasks) < 4:
|
|
219
|
-
|
|
219
|
+
hud_console.warning(
|
|
220
220
|
f"Dataset has only {len(valid_tasks)} task(s). RL training typically requires at least 4 tasks." # noqa: E501
|
|
221
221
|
)
|
|
222
|
-
use_for_training =
|
|
222
|
+
use_for_training = hud_console.select(
|
|
223
223
|
"Will this dataset be used for RL training?",
|
|
224
224
|
["Yes, duplicate tasks to reach 4", "No, keep as is"],
|
|
225
225
|
)
|
|
@@ -238,7 +238,7 @@ def hf_command(
|
|
|
238
238
|
)
|
|
239
239
|
valid_tasks.append(duplicated_task)
|
|
240
240
|
|
|
241
|
-
|
|
241
|
+
hud_console.info(f"Duplicated tasks: {original_count} → {len(valid_tasks)}")
|
|
242
242
|
|
|
243
243
|
# Check if MCP configs should be converted to remote
|
|
244
244
|
sample_mcp_config = valid_tasks[0].get("mcp_config", {})
|
|
@@ -266,10 +266,10 @@ def hf_command(
|
|
|
266
266
|
config_type = "local"
|
|
267
267
|
|
|
268
268
|
if config_type == "remote" and remote_image:
|
|
269
|
-
|
|
269
|
+
hud_console.info(f"Tasks already use remote MCP configs with image: {remote_image}")
|
|
270
270
|
|
|
271
271
|
if config_type == "local":
|
|
272
|
-
convert_to_remote =
|
|
272
|
+
convert_to_remote = hud_console.select(
|
|
273
273
|
"Tasks use local MCP configs. Convert to remote configs for training?",
|
|
274
274
|
["Yes, convert to remote (requires public image)", "No, keep local configs"],
|
|
275
275
|
)
|
|
@@ -281,38 +281,38 @@ def hf_command(
|
|
|
281
281
|
image = get_image_from_lock()
|
|
282
282
|
|
|
283
283
|
if not image:
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
hud_console.error("No image found in hud.lock.yaml")
|
|
285
|
+
hud_console.hint("Run 'hud build' first")
|
|
286
286
|
raise typer.Exit(1)
|
|
287
287
|
|
|
288
288
|
# Check if image contains registry prefix (indicates it's pushed)
|
|
289
289
|
if "/" not in image or image.startswith("local/"):
|
|
290
290
|
# Clean up image name for display (remove SHA if present)
|
|
291
291
|
display_image = image.split("@")[0] if "@" in image else image
|
|
292
|
-
|
|
293
|
-
push_image =
|
|
292
|
+
hud_console.warning(f"Image '{display_image}' appears to be local only")
|
|
293
|
+
push_image = hud_console.select(
|
|
294
294
|
"Would you like to push the image to make it publicly available?",
|
|
295
295
|
["Yes, push image", "No, cancel"],
|
|
296
296
|
)
|
|
297
297
|
|
|
298
298
|
if push_image == "Yes, push image":
|
|
299
|
-
|
|
299
|
+
hud_console.info("Running 'hud push' to publish image...")
|
|
300
300
|
# Import here to avoid circular imports
|
|
301
301
|
from hud.cli.push import push_command
|
|
302
302
|
|
|
303
303
|
# Run push command (it's synchronous)
|
|
304
304
|
push_command(directory=".", yes=True)
|
|
305
|
-
|
|
305
|
+
hud_console.success("Image pushed successfully")
|
|
306
306
|
|
|
307
307
|
# Re-read the image name as it may have changed
|
|
308
308
|
image = get_image_from_lock()
|
|
309
309
|
else:
|
|
310
|
-
|
|
310
|
+
hud_console.info("Keeping local MCP configs")
|
|
311
311
|
convert_to_remote = None
|
|
312
312
|
|
|
313
313
|
if convert_to_remote and image:
|
|
314
314
|
# Convert all task configs to remote
|
|
315
|
-
|
|
315
|
+
hud_console.info(f"Converting MCP configs to use remote image: {image}")
|
|
316
316
|
|
|
317
317
|
for task in valid_tasks:
|
|
318
318
|
# Create remote MCP config
|
|
@@ -327,7 +327,7 @@ def hf_command(
|
|
|
327
327
|
}
|
|
328
328
|
task["mcp_config"] = remote_config
|
|
329
329
|
|
|
330
|
-
|
|
330
|
+
hud_console.success("✓ Converted all tasks to use remote MCP configs")
|
|
331
331
|
|
|
332
332
|
# Convert to HuggingFace format
|
|
333
333
|
dataset_dict = {
|
|
@@ -352,11 +352,11 @@ def hf_command(
|
|
|
352
352
|
try:
|
|
353
353
|
from datasets import Dataset
|
|
354
354
|
except ImportError as e:
|
|
355
|
-
|
|
356
|
-
|
|
355
|
+
hud_console.error("datasets library not installed")
|
|
356
|
+
hud_console.info("Install with: pip install datasets")
|
|
357
357
|
raise typer.Exit(1) from e
|
|
358
358
|
|
|
359
|
-
|
|
359
|
+
hud_console.info(f"Creating HuggingFace dataset: {name}")
|
|
360
360
|
dataset = Dataset.from_dict(dataset_dict)
|
|
361
361
|
|
|
362
362
|
# Set up HF token
|
|
@@ -365,15 +365,15 @@ def hf_command(
|
|
|
365
365
|
|
|
366
366
|
os.environ["HF_TOKEN"] = token
|
|
367
367
|
|
|
368
|
-
|
|
368
|
+
hud_console.info(f"Pushing to Hub (private={private})...")
|
|
369
369
|
try:
|
|
370
370
|
if name is None:
|
|
371
371
|
raise ValueError("Dataset name is required")
|
|
372
372
|
dataset.push_to_hub(name, private=private)
|
|
373
|
-
|
|
373
|
+
hud_console.success(f"Dataset published: https://huggingface.co/datasets/{name}")
|
|
374
374
|
except Exception as e:
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
hud_console.error(f"Failed to push to Hub: {e}")
|
|
376
|
+
hud_console.hint("Make sure you're logged in: huggingface-cli login")
|
|
377
377
|
raise typer.Exit(1) from e
|
|
378
378
|
else:
|
|
379
379
|
# Save locally
|
|
@@ -382,7 +382,7 @@ def hf_command(
|
|
|
382
382
|
output_file = Path(f"{name.replace('/', '_')}_dataset.json")
|
|
383
383
|
with open(output_file, "w") as f:
|
|
384
384
|
json.dump(dataset_dict, f, indent=2)
|
|
385
|
-
|
|
385
|
+
hud_console.success(f"Dataset saved locally: {output_file}")
|
|
386
386
|
|
|
387
387
|
# Update hud.lock.yaml if requested
|
|
388
388
|
if update_lock:
|
|
@@ -403,4 +403,4 @@ def update_lock_file(dataset_name: str, task_count: int) -> None:
|
|
|
403
403
|
|
|
404
404
|
# Write back
|
|
405
405
|
if write_lock_file(lock_data):
|
|
406
|
-
|
|
406
|
+
hud_console.success(f"Updated hud.lock.yaml with dataset: {dataset_name}")
|
hud/cli/init.py
CHANGED
|
@@ -8,7 +8,7 @@ import typer
|
|
|
8
8
|
from rich.panel import Panel
|
|
9
9
|
from rich.syntax import Syntax
|
|
10
10
|
|
|
11
|
-
from hud.utils.
|
|
11
|
+
from hud.utils.hud_console import HUDConsole
|
|
12
12
|
|
|
13
13
|
# Embedded templates
|
|
14
14
|
DOCKERFILE_TEMPLATE = """FROM python:3.11-slim
|
|
@@ -541,7 +541,7 @@ def sanitize_name(name: str) -> str:
|
|
|
541
541
|
def create_environment(name: str | None, directory: str, force: bool) -> None:
|
|
542
542
|
"""Create a new HUD environment from templates."""
|
|
543
543
|
|
|
544
|
-
|
|
544
|
+
hud_console = HUDConsole()
|
|
545
545
|
|
|
546
546
|
# Determine environment name
|
|
547
547
|
if name is None:
|
|
@@ -549,7 +549,7 @@ def create_environment(name: str | None, directory: str, force: bool) -> None:
|
|
|
549
549
|
current_dir = Path.cwd()
|
|
550
550
|
name = current_dir.name
|
|
551
551
|
target_dir = current_dir
|
|
552
|
-
|
|
552
|
+
hud_console.info(f"Using current directory name: {name}")
|
|
553
553
|
else:
|
|
554
554
|
# Create new directory
|
|
555
555
|
target_dir = Path(directory) / name
|
|
@@ -557,16 +557,16 @@ def create_environment(name: str | None, directory: str, force: bool) -> None:
|
|
|
557
557
|
# Sanitize name for Python package
|
|
558
558
|
package_name = sanitize_name(name)
|
|
559
559
|
if package_name != name:
|
|
560
|
-
|
|
560
|
+
hud_console.warning(f"Package name adjusted for Python: {name} → {package_name}")
|
|
561
561
|
|
|
562
562
|
# Check if directory exists
|
|
563
563
|
if target_dir.exists() and any(target_dir.iterdir()):
|
|
564
564
|
if not force:
|
|
565
|
-
|
|
566
|
-
|
|
565
|
+
hud_console.error(f"Directory {target_dir} already exists and is not empty")
|
|
566
|
+
hud_console.info("Use --force to overwrite existing files")
|
|
567
567
|
raise typer.Exit(1)
|
|
568
568
|
else:
|
|
569
|
-
|
|
569
|
+
hud_console.warning(f"Overwriting existing files in {target_dir}")
|
|
570
570
|
|
|
571
571
|
# Create directory structure
|
|
572
572
|
src_dir = target_dir / "src" / "controller"
|
|
@@ -633,38 +633,38 @@ def create_environment(name: str | None, directory: str, force: bool) -> None:
|
|
|
633
633
|
files_created.append(".env")
|
|
634
634
|
|
|
635
635
|
# Success message
|
|
636
|
-
|
|
636
|
+
hud_console.header(f"Created HUD Environment: {name}")
|
|
637
637
|
|
|
638
|
-
|
|
638
|
+
hud_console.section_title("Files created")
|
|
639
639
|
for file in files_created:
|
|
640
|
-
|
|
640
|
+
hud_console.status_item(file, "created")
|
|
641
641
|
|
|
642
|
-
|
|
642
|
+
hud_console.section_title("Next steps")
|
|
643
643
|
|
|
644
644
|
# Show commands based on where we created the environment
|
|
645
645
|
if target_dir == Path.cwd():
|
|
646
|
-
|
|
647
|
-
|
|
646
|
+
hud_console.info("1. Start development server (with MCP inspector):")
|
|
647
|
+
hud_console.command_example("hud dev --inspector")
|
|
648
648
|
else:
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
649
|
+
hud_console.info("1. Enter the directory:")
|
|
650
|
+
hud_console.command_example(f"cd {target_dir}")
|
|
651
|
+
hud_console.info("\n2. Start development server (with MCP inspector):")
|
|
652
|
+
hud_console.command_example("hud dev --inspector")
|
|
653
653
|
|
|
654
|
-
|
|
655
|
-
|
|
654
|
+
hud_console.info("\n3. Connect from Cursor or test via the MCP inspector:")
|
|
655
|
+
hud_console.info(" Follow the instructions shown by hud dev --inspector")
|
|
656
656
|
|
|
657
|
-
|
|
658
|
-
|
|
657
|
+
hud_console.info("\n4. Test your environment:")
|
|
658
|
+
hud_console.command_example("python test_task.py")
|
|
659
659
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
660
|
+
hud_console.info("\n5. Customize your environment:")
|
|
661
|
+
hud_console.info(" - Add tools to src/controller/server.py")
|
|
662
|
+
hud_console.info(" - Add state to src/controller/env.py")
|
|
663
|
+
hud_console.info(" - Modify tasks in tasks.json")
|
|
664
|
+
hud_console.info(" - Experiment in test_env.ipynb")
|
|
665
665
|
|
|
666
666
|
# Show a sample of the server code
|
|
667
|
-
|
|
667
|
+
hud_console.section_title("Your MCP server")
|
|
668
668
|
sample_code = '''@mcp.tool()
|
|
669
669
|
async def act() -> str:
|
|
670
670
|
"""Perform an action that changes the environment state."""
|
|
@@ -674,4 +674,4 @@ async def act() -> str:
|
|
|
674
674
|
return f"Action #{count} performed. Current count: {count}"'''
|
|
675
675
|
|
|
676
676
|
syntax = Syntax(sample_code, "python", theme="monokai", line_numbers=False)
|
|
677
|
-
|
|
677
|
+
hud_console.console.print(Panel(syntax, border_style="dim"))
|
hud/cli/list_func.py
CHANGED
|
@@ -8,7 +8,7 @@ import typer
|
|
|
8
8
|
import yaml
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
|
-
from hud.utils.
|
|
11
|
+
from hud.utils.hud_console import HUDConsole
|
|
12
12
|
|
|
13
13
|
from .utils.registry import extract_name_and_tag, get_registry_dir, list_registry_entries
|
|
14
14
|
|
|
@@ -46,10 +46,10 @@ def list_environments(
|
|
|
46
46
|
verbose: bool = False,
|
|
47
47
|
) -> None:
|
|
48
48
|
"""List all HUD environments in the local registry."""
|
|
49
|
-
|
|
49
|
+
hud_console = HUDConsole()
|
|
50
50
|
|
|
51
51
|
if not json_output:
|
|
52
|
-
|
|
52
|
+
hud_console.header("HUD Environment Registry")
|
|
53
53
|
|
|
54
54
|
# Check for environment directory
|
|
55
55
|
env_dir = get_registry_dir()
|
|
@@ -57,10 +57,10 @@ def list_environments(
|
|
|
57
57
|
if json_output:
|
|
58
58
|
print("[]") # noqa: T201
|
|
59
59
|
else:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
hud_console.info("No environments found in local registry.")
|
|
61
|
+
hud_console.info("")
|
|
62
|
+
hud_console.info("Pull environments with: [cyan]hud pull <org/name:tag>[/cyan]")
|
|
63
|
+
hud_console.info("Build environments with: [cyan]hud build[/cyan]")
|
|
64
64
|
return
|
|
65
65
|
|
|
66
66
|
# Collect all environments using the registry helper
|
|
@@ -103,7 +103,7 @@ def list_environments(
|
|
|
103
103
|
|
|
104
104
|
except Exception as e:
|
|
105
105
|
if verbose:
|
|
106
|
-
|
|
106
|
+
hud_console.warning(f"Failed to read {lock_file}: {e}")
|
|
107
107
|
|
|
108
108
|
# Sort by pulled time (newest first)
|
|
109
109
|
environments.sort(key=lambda x: x["pulled_time"], reverse=True)
|
|
@@ -129,10 +129,10 @@ def list_environments(
|
|
|
129
129
|
return
|
|
130
130
|
|
|
131
131
|
if not environments:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
132
|
+
hud_console.info("No environments found matching criteria.")
|
|
133
|
+
hud_console.info("")
|
|
134
|
+
hud_console.info("Pull environments with: [cyan]hud pull <org/name:tag>[/cyan]")
|
|
135
|
+
hud_console.info("Build environments with: [cyan]hud build[/cyan]")
|
|
136
136
|
return
|
|
137
137
|
|
|
138
138
|
# Create table
|
|
@@ -169,26 +169,26 @@ def list_environments(
|
|
|
169
169
|
|
|
170
170
|
table.add_row(*row)
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
hud_console.print(table) # type: ignore
|
|
173
|
+
hud_console.info("")
|
|
174
174
|
|
|
175
175
|
# Show usage hints
|
|
176
|
-
|
|
176
|
+
hud_console.section_title("Usage")
|
|
177
177
|
if environments:
|
|
178
178
|
# Use the most recently pulled environment as example
|
|
179
179
|
example_env = environments[0]
|
|
180
180
|
example_ref = f"{example_env['name']}:{example_env['tag']}"
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
hud_console.info(f"Run an environment: [cyan]hud run {example_ref}[/cyan]")
|
|
183
|
+
hud_console.info(f"Analyze tools: [cyan]hud analyze {example_ref}[/cyan]")
|
|
184
|
+
hud_console.info(f"Debug server: [cyan]hud debug {example_ref}[/cyan]")
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
hud_console.info("Pull more environments: [cyan]hud pull <org/name:tag>[/cyan]")
|
|
187
|
+
hud_console.info("Build new environments: [cyan]hud build[/cyan]")
|
|
188
188
|
|
|
189
189
|
if verbose:
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
hud_console.info("")
|
|
191
|
+
hud_console.info(f"[dim]Registry location: {env_dir}[/dim]")
|
|
192
192
|
|
|
193
193
|
|
|
194
194
|
def list_command(
|