hte-cli 0.2.9__tar.gz → 0.2.11__tar.gz

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.
Files changed (27) hide show
  1. {hte_cli-0.2.9 → hte_cli-0.2.11}/PKG-INFO +1 -1
  2. {hte_cli-0.2.9 → hte_cli-0.2.11}/pyproject.toml +1 -1
  3. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/cli.py +98 -32
  4. {hte_cli-0.2.9 → hte_cli-0.2.11}/.gitignore +0 -0
  5. {hte_cli-0.2.9 → hte_cli-0.2.11}/README.md +0 -0
  6. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/__init__.py +0 -0
  7. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/__main__.py +0 -0
  8. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/api_client.py +0 -0
  9. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/config.py +0 -0
  10. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/errors.py +0 -0
  11. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/events.py +0 -0
  12. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/image_utils.py +0 -0
  13. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/runner.py +0 -0
  14. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/scorers.py +0 -0
  15. {hte_cli-0.2.9 → hte_cli-0.2.11}/src/hte_cli/version_check.py +0 -0
  16. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/__init__.py +0 -0
  17. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/__init__.py +0 -0
  18. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/automated_runner.py +0 -0
  19. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/conftest.py +0 -0
  20. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/e2e_test.py +0 -0
  21. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/test_benchmark_flows.py +0 -0
  22. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/test_eval_logs.py +0 -0
  23. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/test_infrastructure.py +0 -0
  24. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/test_runtime_imports.py +0 -0
  25. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/test_session_lifecycle.py +0 -0
  26. {hte_cli-0.2.9 → hte_cli-0.2.11}/tests/e2e/verify_docker_deps.py +0 -0
  27. {hte_cli-0.2.9 → hte_cli-0.2.11}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hte-cli
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Summary: Human Time-to-Completion Evaluation CLI
5
5
  Project-URL: Homepage, https://github.com/sean-peters-au/lyptus-mono
6
6
  Author: Lyptus Research
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hte-cli"
3
- version = "0.2.9"
3
+ version = "0.2.11"
4
4
  description = "Human Time-to-Completion Evaluation CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -310,42 +310,108 @@ def session_join(ctx, session_id: str, force_setup: bool):
310
310
  cached_images.append(img)
311
311
  continue
312
312
 
313
- # Need to pull - show progress
314
- last_status = ["connecting..."] # Use list for closure mutability
313
+ # Need to pull - show aggregated progress across all layers
314
+ import re
315
+ layer_progress: dict[str, dict] = {} # layer_id -> {current, total, phase}
316
+ last_display = [""]
317
+
318
+ def parse_size(s: str) -> float:
319
+ """Parse size string like '10.5MB' or '1.2GB' to bytes."""
320
+ if not s:
321
+ return 0
322
+ s = s.strip()
323
+ multipliers = {"B": 1, "KB": 1024, "MB": 1024**2, "GB": 1024**3}
324
+ for suffix, mult in multipliers.items():
325
+ if s.upper().endswith(suffix):
326
+ try:
327
+ return float(s[:-len(suffix)].strip()) * mult
328
+ except ValueError:
329
+ return 0
330
+ try:
331
+ return float(s)
332
+ except ValueError:
333
+ return 0
334
+
335
+ def format_size(b: float) -> str:
336
+ """Format bytes to human readable."""
337
+ if b >= 1024**3:
338
+ return f"{b/1024**3:.1f}GB"
339
+ elif b >= 1024**2:
340
+ return f"{b/1024**2:.0f}MB"
341
+ elif b >= 1024:
342
+ return f"{b/1024:.0f}KB"
343
+ return f"{b:.0f}B"
344
+
315
345
  with console.status(f"[yellow]↓[/yellow] {short_name} [dim]connecting...[/dim]") as status:
316
346
  def show_progress(image: str, line: str):
317
- # Parse docker pull output for layer progress
318
- # Lines look like: "abc123: Downloading [====> ] 10MB/50MB"
319
- # Or: "abc123: Extracting [====> ] 10MB/50MB"
320
- # Or: "abc123: Pull complete", "Digest: sha256:...", "Status: ..."
321
- display_text = None
322
- if ": " in line:
323
- parts = line.split(": ", 1)
324
- if len(parts) == 2:
325
- layer_id = parts[0][-8:] # Last 8 chars of layer ID
326
- layer_status = parts[1] # Don't truncate - keep full progress
327
- # Show progress bars for Downloading/Extracting with MB info
328
- if "Downloading" in layer_status or "Extracting" in layer_status:
329
- # Keep progress: "[====> ] 10.5MB/50MB"
330
- display_text = f"{layer_id}: {layer_status[:50]}"
331
- elif "Pull complete" in layer_status:
332
- display_text = f"{layer_id}: done"
333
- elif "Download complete" in layer_status:
334
- display_text = f"{layer_id}: download done"
335
- elif "Already exists" in layer_status:
336
- display_text = f"{layer_id}: cached"
337
- elif "Waiting" in layer_status:
338
- display_text = f"{layer_id}: waiting"
339
- elif "Verifying" in layer_status:
340
- display_text = f"{layer_id}: verifying"
341
- else:
342
- display_text = line[:55]
347
+ # Parse: "abc123: Downloading 10.5MB/50MB" or "abc123: Extracting 10MB/50MB"
348
+ size_match = re.search(
349
+ r"([a-f0-9]+):\s*(Downloading|Extracting)\s+(\d+\.?\d*\s*[kMGT]?B)/(\d+\.?\d*\s*[kMGT]?B)",
350
+ line, re.IGNORECASE
351
+ )
352
+ if size_match:
353
+ layer_id = size_match.group(1)
354
+ phase = size_match.group(2)
355
+ current = parse_size(size_match.group(3))
356
+ total = parse_size(size_match.group(4))
357
+ layer_progress[layer_id] = {"current": current, "total": total, "phase": phase}
358
+
359
+ # Check for completed layers
360
+ complete_match = re.search(r"([a-f0-9]+):\s*(Pull complete|Download complete|Already exists)", line)
361
+ if complete_match:
362
+ layer_id = complete_match.group(1)
363
+ if layer_id in layer_progress:
364
+ layer_progress[layer_id]["current"] = layer_progress[layer_id]["total"]
365
+ layer_progress[layer_id]["phase"] = "done"
366
+
367
+ # Track waiting layers
368
+ waiting_match = re.search(r"([a-f0-9]+):\s*Waiting", line)
369
+ if waiting_match:
370
+ layer_id = waiting_match.group(1)
371
+ if layer_id not in layer_progress:
372
+ layer_progress[layer_id] = {"current": 0, "total": 0, "phase": "waiting"}
373
+
374
+ # Aggregate progress
375
+ total_current = sum(lp.get("current", 0) for lp in layer_progress.values())
376
+ total_size = sum(lp.get("total", 0) for lp in layer_progress.values())
377
+ downloading = sum(1 for lp in layer_progress.values() if lp.get("phase") == "Downloading")
378
+ extracting = sum(1 for lp in layer_progress.values() if lp.get("phase") == "Extracting")
379
+ done_layers = sum(1 for lp in layer_progress.values() if lp.get("phase") == "done")
380
+ waiting = sum(1 for lp in layer_progress.values() if lp.get("phase") == "waiting")
381
+ total_layers = len(layer_progress)
382
+
383
+ # Only show size progress if we have meaningful data (>1MB)
384
+ if total_size > 1024 * 1024:
385
+ display = f"{format_size(total_current)}/{format_size(total_size)}"
386
+ if downloading > 0:
387
+ display += f" ({downloading} downloading"
388
+ if extracting > 0:
389
+ display += f", {extracting} extracting"
390
+ display += ")"
391
+ elif extracting > 0:
392
+ display += f" ({extracting} extracting)"
393
+ elif total_layers > 0:
394
+ # Show layer counts instead
395
+ parts = []
396
+ if downloading > 0:
397
+ parts.append(f"{downloading} downloading")
398
+ if extracting > 0:
399
+ parts.append(f"{extracting} extracting")
400
+ if done_layers > 0:
401
+ parts.append(f"{done_layers} done")
402
+ if waiting > 0:
403
+ parts.append(f"{waiting} waiting")
404
+ display = ", ".join(parts) if parts else "starting..."
405
+ elif "Pulling" in line:
406
+ display = "preparing..."
343
407
  elif line.strip():
344
- display_text = line[:55]
408
+ display = line[:40]
409
+ else:
410
+ return
345
411
 
346
- if display_text and display_text != last_status[0]:
347
- last_status[0] = display_text
348
- status.update(f"[yellow]↓[/yellow] {short_name} [dim]{display_text}[/dim]")
412
+ if display != last_display[0]:
413
+ last_display[0] = display
414
+ status.update(f"[yellow]↓[/yellow] {short_name} [dim]{display}[/dim]")
349
415
 
350
416
  success = pull_image_with_progress(img, on_progress=show_progress)
351
417
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes