runtime-sdk 0.4.5__tar.gz → 0.4.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtime-sdk
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Runtime Python SDK and CLI
5
5
  Project-URL: Repository, https://github.com/The-Money-Company-Limited/runtimevm
6
6
  Project-URL: Issues, https://github.com/The-Money-Company-Limited/runtimevm/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "runtime-sdk"
3
- version = "0.4.5"
3
+ version = "0.4.6"
4
4
  description = "Runtime Python SDK and CLI"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -10,6 +10,7 @@ import sys
10
10
  import termios
11
11
  import threading
12
12
  import tty
13
+ from datetime import datetime, timezone
13
14
  from typing import Any, Callable
14
15
  from urllib.parse import urljoin, urlparse
15
16
 
@@ -109,10 +110,12 @@ class _UI:
109
110
  @classmethod
110
111
  def rich(cls) -> dict[str, Any]:
111
112
  if cls._rich_mods is None:
113
+ from rich import box
112
114
  from rich.panel import Panel
115
+ from rich.table import Table
113
116
  from rich.text import Text
114
117
 
115
- cls._rich_mods = {"Panel": Panel, "Text": Text}
118
+ cls._rich_mods = {"Panel": Panel, "Text": Text, "Table": Table, "box": box}
116
119
  return cls._rich_mods
117
120
 
118
121
 
@@ -425,6 +428,43 @@ def _resolve_computer_ref(client: RuntimeClient, ref: str) -> str:
425
428
 
426
429
 
427
430
 
431
+ def _computer_name(c: dict[str, Any]) -> str:
432
+ return str(c.get("slug") or c.get("id") or "?")
433
+
434
+
435
+
436
+ def _computer_url_label(c: dict[str, Any]) -> str:
437
+ public_url = str(c.get("public_url") or "").strip()
438
+ if not public_url:
439
+ return ""
440
+ parsed = urlparse(public_url)
441
+ return parsed.netloc or public_url
442
+
443
+
444
+
445
+ def _computer_created_label(c: dict[str, Any]) -> str:
446
+ raw = str(c.get("created_at") or "").strip()
447
+ if not raw:
448
+ return ""
449
+ try:
450
+ created = datetime.fromisoformat(raw.replace("Z", "+00:00"))
451
+ except ValueError:
452
+ return raw
453
+ delta = datetime.now(timezone.utc) - created.astimezone(timezone.utc)
454
+ seconds = max(0, int(delta.total_seconds()))
455
+ if seconds < 60:
456
+ return f"{seconds}s ago"
457
+ minutes = seconds // 60
458
+ if minutes < 60:
459
+ return f"{minutes}m ago"
460
+ hours = minutes // 60
461
+ if hours < 24:
462
+ return f"{hours}h ago"
463
+ days = hours // 24
464
+ return f"{days}d ago"
465
+
466
+
467
+
428
468
  def _computer_table_layout(computers: list[dict[str, Any]]) -> dict[str, int]:
429
469
  term_width = shutil.get_terminal_size((100, 20)).columns
430
470
  slug_width = max(
@@ -456,10 +496,10 @@ def _computer_table_layout(computers: list[dict[str, Any]]) -> dict[str, int]:
456
496
  def _format_computer_row(c: dict[str, Any], widths: dict[str, int]) -> str:
457
497
  return " │ ".join(
458
498
  [
459
- _fit_cell(c.get("slug") or c.get("id") or "?", widths["slug"]),
499
+ _fit_cell(_computer_name(c), widths["slug"]),
460
500
  _fit_cell(_computer_status_label(c), widths["status"]),
461
- _fit_cell(c.get("public_url") or "", widths["public_url"]),
462
- _fit_cell(c.get("created_at") or "", widths["created_at"]),
501
+ _fit_cell(_computer_url_label(c), widths["public_url"]),
502
+ _fit_cell(_computer_created_label(c), widths["created_at"]),
463
503
  ]
464
504
  )
465
505
 
@@ -468,9 +508,9 @@ def _computer_table_prompt(prompt: str, computers: list[dict[str, Any]]) -> tupl
468
508
  widths = _computer_table_layout(computers)
469
509
  header = " │ ".join(
470
510
  [
471
- _fit_cell("slug", widths["slug"]),
511
+ _fit_cell("name", widths["slug"]),
472
512
  _fit_cell("status", widths["status"]),
473
- _fit_cell("public url", widths["public_url"]),
513
+ _fit_cell("url", widths["public_url"]),
474
514
  _fit_cell("created", widths["created_at"]),
475
515
  ]
476
516
  )
@@ -478,6 +518,12 @@ def _computer_table_prompt(prompt: str, computers: list[dict[str, Any]]) -> tupl
478
518
  return f"{prompt}\n{header}\n{divider}", widths
479
519
 
480
520
 
521
+
522
+ def _computer_choice_title(c: dict[str, Any], widths: dict[str, int]) -> str:
523
+ return _format_computer_row(c, widths)
524
+
525
+
526
+
481
527
  def _render_computer_summary(computers: list[dict[str, Any]]) -> None:
482
528
  mods = _UI.rich()
483
529
  Panel, Text = mods["Panel"], mods["Text"]
@@ -517,6 +563,48 @@ def _render_computer_summary(computers: list[dict[str, Any]]) -> None:
517
563
  )
518
564
 
519
565
 
566
+
567
+ def _render_computer_table(computers: list[dict[str, Any]], *, title: str | None = None) -> None:
568
+ mods = _UI.rich()
569
+ Table, box = mods["Table"], mods["box"]
570
+ table = Table(
571
+ title=title,
572
+ box=box.SIMPLE_HEAVY,
573
+ expand=False,
574
+ show_lines=False,
575
+ header_style="bold cyan",
576
+ title_style="bold white",
577
+ pad_edge=False,
578
+ )
579
+ table.add_column("Name", style="bold white", no_wrap=True)
580
+ table.add_column("Status", no_wrap=True)
581
+ table.add_column("URL", overflow="fold")
582
+ table.add_column("Created", style="dim", no_wrap=True, justify="right")
583
+
584
+ for computer in computers:
585
+ status = _computer_status_label(computer)
586
+ status_style = "white"
587
+ if "cold" in status:
588
+ status_style = "bold cyan"
589
+ elif "warm" in status or "running" in status:
590
+ status_style = "bold green"
591
+ elif "archiving" in status or "restoring" in status:
592
+ status_style = "bold yellow"
593
+ elif "creating" in status:
594
+ status_style = "bold magenta"
595
+ elif "orphaned" in status:
596
+ status_style = "bold red"
597
+
598
+ table.add_row(
599
+ _computer_name(computer),
600
+ f"[{status_style}]{status}[/{status_style}]",
601
+ _computer_url_label(computer),
602
+ _computer_created_label(computer),
603
+ )
604
+
605
+ _UI.console().print(table)
606
+
607
+
520
608
  def _select_prompt(message: str, choices: list[Any]) -> Any:
521
609
  return _UI.q().select(
522
610
  message,
@@ -551,12 +639,13 @@ def _pick_computer(
551
639
  return None
552
640
 
553
641
  questionary = _UI.q()
554
- message, widths = _computer_table_prompt(prompt, computers)
642
+ widths = _computer_table_layout(computers)
643
+ _render_computer_table(computers)
555
644
  choices = [
556
- questionary.Choice(title=_format_computer_row(c, widths), value=c) for c in computers
645
+ questionary.Choice(title=_computer_choice_title(c, widths), value=c) for c in computers
557
646
  ]
558
647
  choices.append(questionary.Choice(title="← cancel", value=None))
559
- picked = _select_prompt(message, choices).unsafe_ask()
648
+ picked = _select_prompt(prompt, choices).unsafe_ask()
560
649
  return picked if isinstance(picked, dict) else None
561
650
 
562
651
 
@@ -1220,16 +1309,17 @@ def _interactive_list(config: RuntimeConfig) -> int:
1220
1309
  return 0
1221
1310
 
1222
1311
  _render_computer_summary(computers)
1223
- message, widths = _computer_table_prompt("Pick a computer", computers)
1312
+ widths = _computer_table_layout(computers)
1313
+ _render_computer_table(computers, title="computers")
1224
1314
  choices = [
1225
- questionary.Choice(title=_format_computer_row(c, widths), value=c)
1315
+ questionary.Choice(title=_computer_choice_title(c, widths), value=c)
1226
1316
  for c in computers
1227
1317
  ]
1228
1318
  choices.append(questionary.Separator())
1229
1319
  choices.append(questionary.Choice(title="↻ refresh", value="__refresh__"))
1230
1320
  choices.append(questionary.Choice(title="✕ quit", value="__quit__"))
1231
1321
 
1232
- picked = _select_prompt(message, choices).unsafe_ask()
1322
+ picked = _select_prompt("Pick a computer", choices).unsafe_ask()
1233
1323
 
1234
1324
  if picked == "__quit__":
1235
1325
  return 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runtime-sdk
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Summary: Runtime Python SDK and CLI
5
5
  Project-URL: Repository, https://github.com/The-Money-Company-Limited/runtimevm
6
6
  Project-URL: Issues, https://github.com/The-Money-Company-Limited/runtimevm/issues
File without changes
File without changes