hte-cli 0.2.9__py3-none-any.whl → 0.2.11__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.
hte_cli/cli.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
408
|
+
display = line[:40]
|
|
409
|
+
else:
|
|
410
|
+
return
|
|
345
411
|
|
|
346
|
-
if
|
|
347
|
-
|
|
348
|
-
status.update(f"[yellow]↓[/yellow] {short_name} [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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
hte_cli/__init__.py,sha256=fDGXp-r8bIoLtlQnn5xJ_CpwMhonvk9bGjZQsjA2mDI,914
|
|
2
2
|
hte_cli/__main__.py,sha256=63n0gNGfskidWDU0aAIF2N8lylVCLYKVIkrN9QiORoo,107
|
|
3
3
|
hte_cli/api_client.py,sha256=m42kfFZS72Nu_VuDwxRsLNy4ziCcvgk7KNWBh9gwqy0,9257
|
|
4
|
-
hte_cli/cli.py,sha256=
|
|
4
|
+
hte_cli/cli.py,sha256=2o5spfX8QSVGvRSWZ9J3MD95hN_2wTOFrz1XPr-2m80,46054
|
|
5
5
|
hte_cli/config.py,sha256=42Xv__YMSeRLs2zhGukJkIXFKtnBtYCHnONfViGyt2g,3387
|
|
6
6
|
hte_cli/errors.py,sha256=1J5PpxcUKBu6XjigMMCPOq4Zc12tnv8LhAsiaVFWLQM,2762
|
|
7
7
|
hte_cli/events.py,sha256=Zn-mroqaLHNzdT4DFf8st1Qclglshihdc09dBfCN070,5522
|
|
@@ -9,7 +9,7 @@ hte_cli/image_utils.py,sha256=TLwJdswUQrSD2bQcAXW03R8j8WG2pbHzd12TWcE7zy4,6418
|
|
|
9
9
|
hte_cli/runner.py,sha256=DhC8FMjHwfLR193iP4thLDRZrNssYA9KH1WYKU2JKeg,13535
|
|
10
10
|
hte_cli/scorers.py,sha256=sFoPJePRt-K191-Ga4cVmrldruJclYXTOLkU_C9nCDI,6025
|
|
11
11
|
hte_cli/version_check.py,sha256=WVZyGy2XfAghQYdd2N9-0Qfg-7pgp9gt4761-PnmacI,1708
|
|
12
|
-
hte_cli-0.2.
|
|
13
|
-
hte_cli-0.2.
|
|
14
|
-
hte_cli-0.2.
|
|
15
|
-
hte_cli-0.2.
|
|
12
|
+
hte_cli-0.2.11.dist-info/METADATA,sha256=lNmuVPuej71UXnUA3TcKeOeXQGaQm5xB1hv-F-Jceg0,3768
|
|
13
|
+
hte_cli-0.2.11.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
hte_cli-0.2.11.dist-info/entry_points.txt,sha256=XbyEEi1H14DFAt0Kdl22e_IRVEGzimSzYSh5HlhKlFA,41
|
|
15
|
+
hte_cli-0.2.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|