hte-cli 0.2.30__tar.gz → 0.2.31__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 (32) hide show
  1. {hte_cli-0.2.30 → hte_cli-0.2.31}/PKG-INFO +1 -1
  2. {hte_cli-0.2.30 → hte_cli-0.2.31}/pyproject.toml +1 -1
  3. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/api_client.py +24 -0
  4. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/cli.py +73 -20
  5. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/image_utils.py +1 -4
  6. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/unit/test_image_utils.py +3 -1
  7. {hte_cli-0.2.30 → hte_cli-0.2.31}/.gitignore +0 -0
  8. {hte_cli-0.2.30 → hte_cli-0.2.31}/README.md +0 -0
  9. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/__init__.py +0 -0
  10. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/__main__.py +0 -0
  11. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/config.py +0 -0
  12. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/errors.py +0 -0
  13. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/events.py +0 -0
  14. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/runner.py +0 -0
  15. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/scorers.py +0 -0
  16. {hte_cli-0.2.30 → hte_cli-0.2.31}/src/hte_cli/version_check.py +0 -0
  17. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/__init__.py +0 -0
  18. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/__init__.py +0 -0
  19. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/automated_runner.py +0 -0
  20. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/conftest.py +0 -0
  21. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/e2e_test.py +0 -0
  22. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/test_benchmark_flows.py +0 -0
  23. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/test_eval_logs.py +0 -0
  24. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/test_infrastructure.py +0 -0
  25. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/test_runtime_imports.py +0 -0
  26. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/test_session_lifecycle.py +0 -0
  27. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/e2e/verify_docker_deps.py +0 -0
  28. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/unit/__init__.py +0 -0
  29. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/unit/conftest.py +0 -0
  30. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/unit/test_runner.py +0 -0
  31. {hte_cli-0.2.30 → hte_cli-0.2.31}/tests/unit/test_scorers.py +0 -0
  32. {hte_cli-0.2.30 → hte_cli-0.2.31}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hte-cli
3
- Version: 0.2.30
3
+ Version: 0.2.31
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.30"
3
+ version = "0.2.31"
4
4
  description = "Human Time-to-Completion Evaluation CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -265,3 +265,27 @@ class APIClient:
265
265
  json=payload,
266
266
  timeout=UPLOAD_TIMEOUT,
267
267
  )
268
+
269
+ def upload_partial_log(
270
+ self,
271
+ session_id: str,
272
+ eval_log_bytes: bytes,
273
+ ) -> dict:
274
+ """Upload partial eval log for interrupted sessions.
275
+
276
+ Args:
277
+ session_id: The session ID
278
+ eval_log_bytes: Partial eval log content
279
+
280
+ Returns:
281
+ Response dict with status and log_path
282
+ """
283
+ payload = {
284
+ "eval_log_base64": base64.b64encode(eval_log_bytes).decode("ascii"),
285
+ }
286
+
287
+ return self.post(
288
+ f"/sessions/{session_id}/partial-log",
289
+ json=payload,
290
+ timeout=UPLOAD_TIMEOUT,
291
+ )
@@ -23,6 +23,42 @@ console = Console()
23
23
  SUPPORT_EMAIL = "jacktpayne51@gmail.com"
24
24
 
25
25
 
26
+ def _find_eval_log_bytes(runner) -> bytes | None:
27
+ """Find and read eval log bytes from runner's work directory.
28
+
29
+ Used for interrupted sessions to upload partial logs.
30
+ """
31
+ try:
32
+ # Look for eval logs in the work directory
33
+ if not runner.work_dir.exists():
34
+ return None
35
+
36
+ # Find any .eval files in the work directory tree
37
+ eval_files = list(runner.work_dir.rglob("*.eval"))
38
+ if not eval_files:
39
+ return None
40
+
41
+ # Get the most recent one
42
+ eval_files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
43
+ return eval_files[0].read_bytes()
44
+ except Exception:
45
+ return None
46
+
47
+
48
+ def _upload_partial_log(api: APIClient, session_id: str, eval_log_bytes: bytes, console: Console) -> None:
49
+ """Upload partial eval log for interrupted session.
50
+
51
+ Best-effort: silently handles failures to not block exit.
52
+ """
53
+ try:
54
+ size_kb = len(eval_log_bytes) / 1024
55
+ console.print(f"[dim]Uploading partial eval log ({size_kb:.0f} KB)...[/dim]")
56
+ api.upload_partial_log(session_id, eval_log_bytes)
57
+ console.print("[dim]Partial eval log uploaded.[/dim]")
58
+ except Exception as e:
59
+ console.print(f"[dim]Could not upload partial log: {e}[/dim]")
60
+
61
+
26
62
  @click.group()
27
63
  @click.version_option(__version__, prog_name="hte-cli")
28
64
  @click.pass_context
@@ -338,15 +374,11 @@ def session_join(ctx, session_id: str, force_setup: bool):
338
374
  host_platform = get_host_docker_platform()
339
375
 
340
376
  if is_linux_arm:
341
- console.print(
342
- f"[yellow]![/yellow] Detected [bold]Linux ARM64[/bold] environment"
343
- )
377
+ console.print("[yellow]![/yellow] Detected [bold]Linux ARM64[/bold] environment")
344
378
  console.print(
345
379
  f" [dim]Will verify cached images match host architecture ({host_platform})[/dim]"
346
380
  )
347
- console.print(
348
- f" [dim]Mismatched images will be automatically re-pulled[/dim]"
349
- )
381
+ console.print(" [dim]Mismatched images will be automatically re-pulled[/dim]")
350
382
  console.print()
351
383
 
352
384
  console.print(f"[bold]Step 2:[/bold] Pulling {len(images)} Docker image(s)...")
@@ -382,7 +414,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
382
414
  f" [dim]Cached image: {image_arch} | Host: {host_arch}[/dim]"
383
415
  )
384
416
  console.print(
385
- f" [dim]Removing cached image and re-pulling correct architecture...[/dim]"
417
+ " [dim]Removing cached image and re-pulling correct architecture...[/dim]"
386
418
  )
387
419
 
388
420
  needed_fix, fix_msg = fix_image_architecture(img)
@@ -396,7 +428,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
396
428
  # No ARM variant available - this is an x86-only image
397
429
  # Re-pull the amd64 version and warn about QEMU
398
430
  console.print(
399
- f" [dim]No ARM variant available - re-pulling x86 version...[/dim]"
431
+ " [dim]No ARM variant available - re-pulling x86 version...[/dim]"
400
432
  )
401
433
  success = pull_image_with_progress(img)
402
434
  if success:
@@ -407,7 +439,9 @@ def session_join(ctx, session_id: str, force_setup: bool):
407
439
  pulled_images.append(img)
408
440
  continue
409
441
  else:
410
- console.print(f" [red]✗[/red] {short_name} [dim](failed to pull)[/dim]")
442
+ console.print(
443
+ f" [red]✗[/red] {short_name} [dim](failed to pull)[/dim]"
444
+ )
411
445
  failed_images.append(img)
412
446
  pull_errors[img] = "failed to pull x86 fallback"
413
447
  continue
@@ -431,12 +465,12 @@ def session_join(ctx, session_id: str, force_setup: bool):
431
465
 
432
466
  def show_progress(image: str, line: str):
433
467
  # Show docker output directly - includes MB progress from PTY
434
- # Lines look like: "abc123: Downloading 360.9MB/4.075GB"
468
+ # Lines look like: "abc123: Downloading [======> ] 360.9MB/4.075GB"
435
469
  if ": " in line:
436
470
  parts = line.split(": ", 1)
437
471
  if len(parts) == 2:
438
472
  layer_id = parts[0][-8:]
439
- layer_status = parts[1][:45]
473
+ layer_status = parts[1][:70] # Increased to include size info
440
474
  display = f"{layer_id}: {layer_status}"
441
475
  if display != last_status[0]:
442
476
  last_status[0] = display
@@ -455,6 +489,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
455
489
  # On Linux ARM64, verify pulled image architecture
456
490
  if is_linux_arm:
457
491
  from hte_cli.image_utils import get_image_architecture
492
+
458
493
  pulled_arch = get_image_architecture(img)
459
494
 
460
495
  if pulled_arch == "arm64":
@@ -467,7 +502,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
467
502
  f" [yellow]![/yellow] {short_name} [dim](downloaded, arch: amd64)[/dim]"
468
503
  )
469
504
  console.print(
470
- f" [yellow]This is an x86 image - requires QEMU emulation on ARM[/yellow]"
505
+ " [yellow]This is an x86 image - requires QEMU emulation on ARM[/yellow]"
471
506
  )
472
507
  x86_images_on_arm.append(img)
473
508
  else:
@@ -499,9 +534,7 @@ def session_join(ctx, session_id: str, force_setup: bool):
499
534
  console.print(
500
535
  f"[yellow]⚠ Warning:[/yellow] {len(x86_images_on_arm)} x86 image(s) detected on ARM host"
501
536
  )
502
- console.print(
503
- " These require QEMU emulation. If container fails to start, run:"
504
- )
537
+ console.print(" These require QEMU emulation. If container fails to start, run:")
505
538
  console.print(
506
539
  " [bold]docker run --privileged --rm tonistiigi/binfmt --install all[/bold]"
507
540
  )
@@ -518,14 +551,21 @@ def session_join(ctx, session_id: str, force_setup: bool):
518
551
 
519
552
  # Architecture-specific advice
520
553
  if is_linux_arm:
521
- console.print(f" 2. You're on Linux ARM64 - try: docker pull <image> --platform linux/arm64")
522
- console.print(" 3. For x86-only images, enable QEMU: docker run --privileged --rm tonistiigi/binfmt --install all")
554
+ console.print(
555
+ " 2. You're on Linux ARM64 - try: docker pull <image> --platform linux/arm64"
556
+ )
557
+ console.print(
558
+ " 3. For x86-only images, enable QEMU: docker run --privileged --rm tonistiigi/binfmt --install all"
559
+ )
523
560
  else:
524
561
  console.print(" 2. Try manual pull: docker pull <image>")
525
562
 
526
563
  console.print(" 4. Check network connectivity")
527
564
  console.print()
528
- console.print("Session remains active - you can retry with: hte-cli session join " + session_id)
565
+ console.print(
566
+ "Session remains active - you can retry with: hte-cli session join "
567
+ + session_id
568
+ )
529
569
  sys.exit(1)
530
570
 
531
571
  # Send setup_completed - THIS STARTS THE TIMER ON SERVER
@@ -579,6 +619,10 @@ def session_join(ctx, session_id: str, force_setup: bool):
579
619
  eval_log_bytes = result.eval_log_path.read_bytes()
580
620
  except KeyboardInterrupt:
581
621
  events.docker_stopped(exit_code=130)
622
+ # Try to find and upload any partial eval log before exiting
623
+ eval_log_bytes = _find_eval_log_bytes(runner)
624
+ if eval_log_bytes:
625
+ _upload_partial_log(api, session_id, eval_log_bytes, console)
582
626
  console.print()
583
627
  console.print(
584
628
  "[yellow]Interrupted. Session remains active - you can reconnect later.[/yellow]"
@@ -586,6 +630,10 @@ def session_join(ctx, session_id: str, force_setup: bool):
586
630
  sys.exit(0)
587
631
  except Exception as e:
588
632
  events.docker_stopped(exit_code=1)
633
+ # Try to upload partial log on failure too
634
+ eval_log_bytes = _find_eval_log_bytes(runner)
635
+ if eval_log_bytes:
636
+ _upload_partial_log(api, session_id, eval_log_bytes, console)
589
637
  console.print(f"[red]Task execution failed: {e}[/red]")
590
638
  sys.exit(1)
591
639
  finally:
@@ -935,7 +983,9 @@ def diagnose_cmd():
935
983
  console.print(" [red]✗[/red] QEMU x86 emulation NOT working")
936
984
  console.print()
937
985
  console.print(" [yellow]To enable QEMU emulation, run:[/yellow]")
938
- console.print(" [bold]docker run --privileged --rm tonistiigi/binfmt --install all[/bold]")
986
+ console.print(
987
+ " [bold]docker run --privileged --rm tonistiigi/binfmt --install all[/bold]"
988
+ )
939
989
  except subprocess.TimeoutExpired:
940
990
  console.print(" [yellow]![/yellow] QEMU test timed out")
941
991
  except Exception as e:
@@ -979,7 +1029,10 @@ def _check_docker() -> tuple[bool, str | None]:
979
1029
  timeout=10,
980
1030
  )
981
1031
  if result.returncode != 0:
982
- return False, "Docker is not running. Start Docker (Docker Desktop, colima, or dockerd)."
1032
+ return (
1033
+ False,
1034
+ "Docker is not running. Start Docker (Docker Desktop, colima, or dockerd).",
1035
+ )
983
1036
  except FileNotFoundError:
984
1037
  return False, "Docker is not installed. Install from https://docs.docker.com/get-docker/"
985
1038
  except Exception as e:
@@ -124,6 +124,7 @@ def is_running_in_linux_vm_on_arm() -> bool:
124
124
  True if running Linux on ARM64
125
125
  """
126
126
  import sys
127
+
127
128
  return sys.platform == "linux" and get_host_architecture() in ("aarch64", "arm64")
128
129
 
129
130
 
@@ -313,10 +314,6 @@ def pull_image_with_progress(
313
314
 
314
315
  # Read output from master with timeout
315
316
  output_buffer = ""
316
- # Regex to parse docker progress: "abc123: Downloading [===> ] 10.5MB/50MB"
317
- progress_pattern = re.compile(
318
- r"([a-f0-9]+):\s*(Downloading|Extracting|Verifying Checksum|Download complete|Pull complete|Already exists|Waiting)(?:\s+\[.*?\]\s+)?(\d+\.?\d*\s*[kMG]?B)?(?:/(\d+\.?\d*\s*[kMG]?B))?"
319
- )
320
317
 
321
318
  while True:
322
319
  # Check if process is done
@@ -610,7 +610,9 @@ class TestFixImageArchitecture:
610
610
  @patch("hte_cli.image_utils.remove_image")
611
611
  @patch("hte_cli.image_utils.check_image_architecture_matches_host")
612
612
  @patch("hte_cli.image_utils.platform.machine")
613
- def test_returns_false_when_repull_fails(self, mock_machine, mock_check, mock_remove, mock_pull):
613
+ def test_returns_false_when_repull_fails(
614
+ self, mock_machine, mock_check, mock_remove, mock_pull
615
+ ):
614
616
  """Returns (False, message) when re-pull fails."""
615
617
  mock_machine.return_value = "aarch64"
616
618
  mock_check.return_value = (False, "amd64", "aarch64")
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