hud-python 0.4.48__py3-none-any.whl → 0.4.50__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 +40 -34
- hud/agents/grounded_openai.py +1 -1
- hud/cli/__init__.py +78 -213
- hud/cli/build.py +105 -45
- hud/cli/dev.py +614 -743
- hud/cli/flows/tasks.py +98 -17
- hud/cli/init.py +18 -14
- hud/cli/push.py +27 -9
- hud/cli/rl/local_runner.py +3 -3
- hud/cli/tests/test_eval.py +168 -119
- hud/cli/tests/test_mcp_server.py +6 -95
- hud/cli/utils/env_check.py +9 -9
- hud/cli/utils/source_hash.py +1 -1
- hud/server/__init__.py +2 -1
- hud/server/router.py +160 -0
- hud/server/server.py +246 -79
- hud/tools/base.py +9 -1
- hud/tools/bash.py +2 -2
- hud/tools/edit.py +3 -7
- hud/utils/hud_console.py +43 -0
- hud/utils/tests/test_version.py +1 -1
- hud/version.py +1 -1
- {hud_python-0.4.48.dist-info → hud_python-0.4.50.dist-info}/METADATA +1 -1
- {hud_python-0.4.48.dist-info → hud_python-0.4.50.dist-info}/RECORD +27 -26
- {hud_python-0.4.48.dist-info → hud_python-0.4.50.dist-info}/WHEEL +0 -0
- {hud_python-0.4.48.dist-info → hud_python-0.4.50.dist-info}/entry_points.txt +0 -0
- {hud_python-0.4.48.dist-info → hud_python-0.4.50.dist-info}/licenses/LICENSE +0 -0
hud/cli/build.py
CHANGED
|
@@ -7,7 +7,7 @@ import contextlib
|
|
|
7
7
|
import hashlib
|
|
8
8
|
import subprocess
|
|
9
9
|
import time
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import UTC, datetime
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
13
|
|
|
@@ -184,7 +184,8 @@ async def analyze_mcp_environment(
|
|
|
184
184
|
if verbose:
|
|
185
185
|
hud_console.info(f"Initializing MCP client with command: {' '.join(docker_cmd)}")
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
# Add timeout to fail fast instead of hanging (30 seconds)
|
|
188
|
+
await asyncio.wait_for(client.initialize(), timeout=60.0)
|
|
188
189
|
initialized = True
|
|
189
190
|
initialize_ms = int((time.time() - start_time) * 1000)
|
|
190
191
|
|
|
@@ -205,6 +206,14 @@ async def analyze_mcp_environment(
|
|
|
205
206
|
"tools": tool_info,
|
|
206
207
|
"success": True,
|
|
207
208
|
}
|
|
209
|
+
except TimeoutError:
|
|
210
|
+
from hud.shared.exceptions import HudException
|
|
211
|
+
|
|
212
|
+
hud_console.error("MCP server initialization timed out after 60 seconds")
|
|
213
|
+
hud_console.info(
|
|
214
|
+
"The server likely crashed during startup - check stderr logs with 'hud debug'"
|
|
215
|
+
)
|
|
216
|
+
raise HudException("MCP server initialization timeout") from None
|
|
208
217
|
except Exception as e:
|
|
209
218
|
from hud.shared.exceptions import HudException
|
|
210
219
|
|
|
@@ -286,26 +295,46 @@ def build_environment(
|
|
|
286
295
|
hud_console.error(f"Directory not found: {directory}")
|
|
287
296
|
raise typer.Exit(1)
|
|
288
297
|
|
|
289
|
-
# Check for
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
298
|
+
# Step 1: Check for hud.lock.yaml (previous build)
|
|
299
|
+
lock_path = env_dir / "hud.lock.yaml"
|
|
300
|
+
base_name = None
|
|
301
|
+
|
|
302
|
+
if lock_path.exists():
|
|
303
|
+
try:
|
|
304
|
+
with open(lock_path) as f:
|
|
305
|
+
lock_data = yaml.safe_load(f)
|
|
306
|
+
# Get base name from lock file (strip version/digest)
|
|
307
|
+
lock_image = lock_data.get("images", {}).get("local") or lock_data.get("image", "")
|
|
308
|
+
if lock_image:
|
|
309
|
+
# Remove @sha256:... digest if present
|
|
310
|
+
if "@" in lock_image:
|
|
311
|
+
lock_image = lock_image.split("@")[0]
|
|
312
|
+
# Extract base name (remove :version tag)
|
|
313
|
+
base_name = lock_image.split(":")[0] if ":" in lock_image else lock_image
|
|
314
|
+
hud_console.info(f"Using base name from lock file: {base_name}")
|
|
315
|
+
except Exception as e:
|
|
316
|
+
hud_console.warning(f"Could not read lock file: {e}")
|
|
317
|
+
|
|
318
|
+
# Step 2: If no lock, check for Dockerfile
|
|
319
|
+
if not base_name:
|
|
320
|
+
dockerfile_path = env_dir / "Dockerfile"
|
|
321
|
+
if not dockerfile_path.exists():
|
|
322
|
+
hud_console.error(f"Not a valid environment directory: {directory}")
|
|
323
|
+
hud_console.info("Expected: Dockerfile or hud.lock.yaml")
|
|
324
|
+
raise typer.Exit(1)
|
|
325
|
+
|
|
326
|
+
# First build - use directory name
|
|
327
|
+
base_name = env_dir.name
|
|
328
|
+
hud_console.info(f"First build - using base name: {base_name}")
|
|
329
|
+
|
|
330
|
+
# If user provides --tag, respect it; otherwise use base name only (version added later)
|
|
331
|
+
if tag:
|
|
332
|
+
# User explicitly provided a tag
|
|
333
|
+
image_tag = tag
|
|
334
|
+
base_name = image_tag.split(":")[0] if ":" in image_tag else image_tag
|
|
335
|
+
else:
|
|
336
|
+
# No tag provided - we'll add version later
|
|
337
|
+
image_tag = None
|
|
309
338
|
|
|
310
339
|
# Build temporary image first
|
|
311
340
|
temp_tag = f"hud-build-temp:{int(time.time())}"
|
|
@@ -333,6 +362,13 @@ def build_environment(
|
|
|
333
362
|
asyncio.set_event_loop(loop)
|
|
334
363
|
try:
|
|
335
364
|
analysis = loop.run_until_complete(analyze_mcp_environment(temp_tag, verbose, env_vars))
|
|
365
|
+
except Exception as e:
|
|
366
|
+
hud_console.error(f"Failed to analyze MCP environment: {e}")
|
|
367
|
+
hud_console.info("")
|
|
368
|
+
hud_console.info("To debug this issue, run:")
|
|
369
|
+
hud_console.command_example(f"hud debug {temp_tag}")
|
|
370
|
+
hud_console.info("")
|
|
371
|
+
raise typer.Exit(1) from e
|
|
336
372
|
finally:
|
|
337
373
|
loop.close()
|
|
338
374
|
|
|
@@ -378,15 +414,23 @@ def build_environment(
|
|
|
378
414
|
new_version = "0.1.0"
|
|
379
415
|
hud_console.info(f"Setting initial version: {new_version}")
|
|
380
416
|
|
|
381
|
-
#
|
|
417
|
+
# Determine base name for image references
|
|
418
|
+
if image_tag:
|
|
419
|
+
base_name = image_tag.split(":")[0] if ":" in image_tag else image_tag
|
|
420
|
+
|
|
421
|
+
# Create lock file content with images subsection at top
|
|
382
422
|
lock_content = {
|
|
383
|
-
"version": "1.
|
|
384
|
-
"
|
|
423
|
+
"version": "1.1", # Lock file format version
|
|
424
|
+
"images": {
|
|
425
|
+
"local": f"{base_name}:{new_version}", # Local tag with version
|
|
426
|
+
"full": None, # Will be set with digest after build
|
|
427
|
+
"pushed": None, # Will be set by hud push
|
|
428
|
+
},
|
|
385
429
|
"build": {
|
|
386
|
-
"generatedAt": datetime.
|
|
430
|
+
"generatedAt": datetime.now(UTC).isoformat() + "Z",
|
|
387
431
|
"hudVersion": hud_version,
|
|
388
432
|
"directory": str(env_dir.name),
|
|
389
|
-
"version": new_version,
|
|
433
|
+
"version": new_version,
|
|
390
434
|
# Fast source fingerprint for change detection
|
|
391
435
|
"sourceHash": compute_source_hash(env_dir),
|
|
392
436
|
},
|
|
@@ -450,9 +494,9 @@ def build_environment(
|
|
|
450
494
|
hud_console.progress_message("Rebuilding with lock file metadata...")
|
|
451
495
|
|
|
452
496
|
# Build final image with label (uses cache from first build)
|
|
453
|
-
#
|
|
454
|
-
base_name = image_tag.split(":")[0] if ":" in image_tag else image_tag
|
|
497
|
+
# Create tags: versioned and latest (and custom tag if provided)
|
|
455
498
|
version_tag = f"{base_name}:{new_version}"
|
|
499
|
+
latest_tag = f"{base_name}:latest"
|
|
456
500
|
|
|
457
501
|
label_cmd = ["docker", "build"]
|
|
458
502
|
# Use same defaulting for the second build step
|
|
@@ -466,12 +510,16 @@ def build_environment(
|
|
|
466
510
|
"--label",
|
|
467
511
|
f"org.hud.version={new_version}",
|
|
468
512
|
"-t",
|
|
469
|
-
|
|
513
|
+
version_tag, # Always tag with new version
|
|
470
514
|
"-t",
|
|
471
|
-
|
|
515
|
+
latest_tag, # Always tag with latest
|
|
472
516
|
]
|
|
473
517
|
)
|
|
474
518
|
|
|
519
|
+
# Add custom tag if user provided one
|
|
520
|
+
if image_tag and image_tag not in [version_tag, latest_tag]:
|
|
521
|
+
label_cmd.extend(["-t", image_tag])
|
|
522
|
+
|
|
475
523
|
label_cmd.append(str(env_dir))
|
|
476
524
|
|
|
477
525
|
# Run rebuild using Docker's native output formatting
|
|
@@ -479,34 +527,40 @@ def build_environment(
|
|
|
479
527
|
# Show Docker's native output when verbose
|
|
480
528
|
result = subprocess.run(label_cmd, check=False) # noqa: S603
|
|
481
529
|
else:
|
|
482
|
-
#
|
|
530
|
+
# Capture output for error reporting, but don't show unless it fails
|
|
483
531
|
result = subprocess.run( # noqa: S603
|
|
484
|
-
label_cmd,
|
|
532
|
+
label_cmd, capture_output=True, text=True, check=False
|
|
485
533
|
)
|
|
486
534
|
|
|
487
535
|
if result.returncode != 0:
|
|
488
536
|
hud_console.error("Failed to rebuild with label")
|
|
537
|
+
if not verbose and result.stderr:
|
|
538
|
+
hud_console.info("Error output:")
|
|
539
|
+
hud_console.info(str(result.stderr))
|
|
540
|
+
if not verbose:
|
|
541
|
+
hud_console.info("")
|
|
542
|
+
hud_console.info("Run with --verbose to see full build output:")
|
|
543
|
+
hud_console.command_example("hud build --verbose")
|
|
489
544
|
raise typer.Exit(1)
|
|
490
545
|
|
|
491
546
|
hud_console.success("Built final image with lock file metadata")
|
|
492
547
|
|
|
493
548
|
# NOW get the image ID after the final build
|
|
494
|
-
image_id = get_docker_image_id(
|
|
549
|
+
image_id = get_docker_image_id(version_tag)
|
|
495
550
|
if image_id:
|
|
496
|
-
#
|
|
497
|
-
# Docker IDs come as sha256:hash, we want tag@sha256:hash
|
|
551
|
+
# Store full reference with digest
|
|
498
552
|
if image_id.startswith("sha256:"):
|
|
499
|
-
lock_content["
|
|
553
|
+
lock_content["images"]["full"] = f"{version_tag}@{image_id}"
|
|
500
554
|
else:
|
|
501
|
-
lock_content["
|
|
555
|
+
lock_content["images"]["full"] = f"{version_tag}@sha256:{image_id}"
|
|
502
556
|
|
|
503
|
-
# Update the lock file with the
|
|
557
|
+
# Update the lock file with the full image reference
|
|
504
558
|
with open(lock_path, "w") as f:
|
|
505
559
|
yaml.dump(lock_content, f, default_flow_style=False, sort_keys=False)
|
|
506
560
|
|
|
507
|
-
hud_console.success("Updated lock file with image
|
|
561
|
+
hud_console.success("Updated lock file with image digest")
|
|
508
562
|
else:
|
|
509
|
-
hud_console.warning("Could not retrieve image
|
|
563
|
+
hud_console.warning("Could not retrieve image digest")
|
|
510
564
|
|
|
511
565
|
# Remove temp image after we're done
|
|
512
566
|
subprocess.run(["docker", "rmi", "-f", temp_tag], capture_output=True) # noqa: S603, S607
|
|
@@ -514,15 +568,21 @@ def build_environment(
|
|
|
514
568
|
# Add to local registry
|
|
515
569
|
if image_id:
|
|
516
570
|
# Save to local registry using the helper
|
|
517
|
-
|
|
571
|
+
local_ref = lock_content.get("images", {}).get("local", version_tag)
|
|
572
|
+
save_to_registry(lock_content, local_ref, verbose)
|
|
518
573
|
|
|
519
574
|
# Print summary
|
|
520
575
|
hud_console.section_title("Build Complete")
|
|
521
576
|
|
|
522
577
|
# Show the version tag as primary since that's what will be pushed
|
|
523
578
|
hud_console.status_item("Built image", version_tag, primary=True)
|
|
524
|
-
|
|
525
|
-
|
|
579
|
+
|
|
580
|
+
# Show additional tags
|
|
581
|
+
additional_tags = [latest_tag]
|
|
582
|
+
if image_tag and image_tag not in [version_tag, latest_tag]:
|
|
583
|
+
additional_tags.append(image_tag)
|
|
584
|
+
hud_console.status_item("Also tagged", ", ".join(additional_tags))
|
|
585
|
+
|
|
526
586
|
hud_console.status_item("Version", new_version)
|
|
527
587
|
hud_console.status_item("Lock file", "hud.lock.yaml")
|
|
528
588
|
hud_console.status_item("Tools found", str(analysis["toolCount"]))
|
|
@@ -534,7 +594,7 @@ def build_environment(
|
|
|
534
594
|
hud_console.section_title("Next Steps")
|
|
535
595
|
hud_console.info("Test locally:")
|
|
536
596
|
hud_console.command_example("hud dev", "Hot-reload development")
|
|
537
|
-
hud_console.command_example(f"hud run {
|
|
597
|
+
hud_console.command_example(f"hud run {latest_tag}", "Run the built image")
|
|
538
598
|
hud_console.info("")
|
|
539
599
|
hud_console.info("Publish to registry:")
|
|
540
600
|
hud_console.command_example("hud push", f"Push as {version_tag}")
|