dayhoff-tools 1.9.1__py3-none-any.whl → 1.9.3__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.
@@ -319,9 +319,6 @@ def format_status(state: str, ready: Optional[bool]) -> str:
319
319
  return state
320
320
 
321
321
 
322
-
323
-
324
-
325
322
  def resolve_engine(name_or_id: str, engines: List[Dict]) -> Dict:
326
323
  """Resolve engine by name or ID with interactive selection."""
327
324
  # Exact ID match
@@ -776,23 +773,22 @@ def engine_status(
776
773
  run_disp = engine["state"].capitalize()
777
774
 
778
775
  # Compose Active/Idle header with extra detail when idle
779
- if idle_detector.get("status") == "active":
780
- active_disp = "[green]Active[/green]"
781
- else:
782
- # show Idle <elapsed>/<threshold> if available; else just N/A for stopped states
776
+ def _compute_active_disp(idle_info: Dict[str, Any]) -> str:
777
+ if idle_info.get("status") == "active":
778
+ return "[green]Active[/green]"
783
779
  if running_state in ("stopped", "stopping"):
784
- active_disp = "[dim]N/A[/dim]"
785
- else:
786
- idle_seconds = idle_detector.get("idle_seconds")
787
- thresh = idle_detector.get("idle_threshold")
788
- if isinstance(idle_seconds, (int, float)) and isinstance(
789
- thresh, (int, float)
790
- ):
791
- active_disp = (
792
- f"[yellow]Idle {int(idle_seconds)//60}m/{int(thresh)//60}m[/yellow]"
793
- )
794
- else:
795
- active_disp = "[yellow]Idle[/yellow]"
780
+ return "[dim]N/A[/dim]"
781
+ idle_seconds_v = idle_info.get("idle_seconds")
782
+ thresh_v = idle_info.get("idle_threshold")
783
+ if isinstance(idle_seconds_v, (int, float)) and isinstance(
784
+ thresh_v, (int, float)
785
+ ):
786
+ return (
787
+ f"[yellow]Idle {int(idle_seconds_v)//60}m/{int(thresh_v)//60}m[/yellow]"
788
+ )
789
+ return "[yellow]Idle[/yellow]"
790
+
791
+ active_disp = _compute_active_disp(idle_detector)
796
792
 
797
793
  top_lines = [
798
794
  f"[blue]{engine['name']}[/blue] {run_disp} {active_disp}\n",
@@ -927,23 +923,55 @@ def engine_status(
927
923
  if not content:
928
924
  return None
929
925
  data = json.loads(content)
930
- # Convert last_state schema to idle_detector schema used by CLI output
926
+ # Convert last_state schema (new or old) to idle_detector schema used by CLI output
931
927
  idle_info: Dict[str, Any] = {"available": True}
932
- idle_info["status"] = "active" if not data.get("idle", True) else "idle"
933
- # thresholds if present
928
+
929
+ # Active/idle
930
+ idle_flag = bool(data.get("idle", False))
931
+ idle_info["status"] = "idle" if idle_flag else "active"
932
+
933
+ # Threshold and elapsed
934
934
  if isinstance(data.get("timeout_sec"), (int, float)):
935
935
  idle_info["idle_threshold"] = int(data["timeout_sec"]) # seconds
936
- # keep raw reasons for sensor display
936
+ if isinstance(data.get("idle_seconds"), (int, float)):
937
+ idle_info["idle_seconds"] = int(data["idle_seconds"])
938
+
939
+ # Keep raw reasons for sensor display when available (new schema)
937
940
  if isinstance(data.get("reasons"), list):
938
941
  idle_info["_reasons_raw"] = data["reasons"]
939
- # derive details from sensors
940
- for r in data.get("reasons", []):
942
+ else:
943
+ # Fallback: synthesize reasons from the old forensics layout
944
+ f_all = data.get("forensics", {}) or {}
945
+ synthesized = []
946
+
947
+ def _mk(sensor_name: str, key: str):
948
+ entry = f_all.get(key, {}) or {}
949
+ synthesized.append(
950
+ {
951
+ "sensor": sensor_name,
952
+ "active": bool(entry.get("active", False)),
953
+ "reason": entry.get("reason", ""),
954
+ "forensic": entry.get("forensic", {}),
955
+ }
956
+ )
957
+
958
+ _mk("CoffeeLockSensor", "coffee")
959
+ _mk("ActiveLoginSensor", "ssh")
960
+ _mk("IDEConnectionSensor", "ide")
961
+ _mk("DockerWorkloadSensor", "docker")
962
+ idle_info["_reasons_raw"] = synthesized
963
+
964
+ # Derive details from sensors
965
+ for r in idle_info.get("_reasons_raw", []):
941
966
  if not r.get("active"):
942
967
  continue
943
968
  sensor = (r.get("sensor") or "").lower()
944
969
  forensic = r.get("forensic") or {}
945
970
  if sensor == "ideconnectionsensor":
946
- cnt = forensic.get("matches")
971
+ # Prefer unique_pid_count written by new detector
972
+ cnt = forensic.get("unique_pid_count")
973
+ if not isinstance(cnt, int):
974
+ cnt = forensic.get("matches")
947
975
  if isinstance(cnt, int):
948
976
  idle_info["ide_connections"] = {"connection_count": cnt}
949
977
  else:
@@ -955,12 +983,11 @@ def engine_status(
955
983
  timedelta(seconds=int(rem))
956
984
  )
957
985
  elif sensor == "activeloginsensor":
958
- # Provide a single summarized SSH session if available
959
986
  sess = {
960
- "tty": r.get("forensic", {}).get("tty", "pts/?"),
961
- "pid": r.get("forensic", {}).get("pid", "?"),
962
- "idle_time": r.get("forensic", {}).get("idle_sec", 0),
963
- "from_ip": r.get("forensic", {}).get("remote_addr", "unknown"),
987
+ "tty": forensic.get("tty", "pts/?"),
988
+ "pid": forensic.get("pid", "?"),
989
+ "idle_time": forensic.get("idle_sec", 0),
990
+ "from_ip": forensic.get("remote_addr", "unknown"),
964
991
  }
965
992
  idle_info.setdefault("ssh_sessions", []).append(sess)
966
993
  return idle_info
@@ -977,6 +1004,13 @@ def engine_status(
977
1004
  for k, v in overlay.items():
978
1005
  idle_detector.setdefault(k, v)
979
1006
 
1007
+ # Recompute header display using enriched overlay values
1008
+ try:
1009
+ active_disp = _compute_active_disp(idle_detector)
1010
+ top_lines[0] = f"[blue]{engine['name']}[/blue] {run_disp} {active_disp}\n"
1011
+ except Exception:
1012
+ pass
1013
+
980
1014
  # Activity Sensors (show all with YES/no)
981
1015
  if idle_detector.get("available"):
982
1016
  status_lines.append("")
@@ -1002,7 +1036,23 @@ def engine_status(
1002
1036
  status_lines.append(_sensor_line(" IDE ", "IDEConnectionSensor", "🖥"))
1003
1037
  status_lines.append(_sensor_line("Docker", "DockerWorkloadSensor", "🐳"))
1004
1038
 
1005
-
1039
+ # If we have elapsed idle seconds and threshold, reflect that in the header
1040
+ try:
1041
+ if idle_detector.get("status") == "idle":
1042
+ idle_secs = int(idle_detector.get("idle_seconds", 0))
1043
+ thresh_secs = int(idle_detector.get("idle_threshold", 0))
1044
+ if thresh_secs > 0:
1045
+ active_disp = (
1046
+ f"[yellow]Idle {idle_secs//60}m/{thresh_secs//60}m[/yellow]"
1047
+ )
1048
+ # Rewrite top header line (index 0) to include updated display
1049
+ all_header = top_lines[0]
1050
+ # Replace the portion after two spaces (name and running state fixed)
1051
+ top_lines[0] = (
1052
+ f"[blue]{engine['name']}[/blue] {run_disp} {active_disp}\n"
1053
+ )
1054
+ except Exception:
1055
+ pass
1006
1056
 
1007
1057
  # Combine top summary and details
1008
1058
  all_lines = top_lines + status_lines
@@ -1041,9 +1091,6 @@ def engine_status(
1041
1091
  console.print(f"[red]❌ Error fetching log: {e}[/red]")
1042
1092
 
1043
1093
 
1044
-
1045
-
1046
-
1047
1094
  @engine_app.command("stop")
1048
1095
  def stop_engine(
1049
1096
  name_or_id: str = typer.Argument(help="Engine name or instance ID"),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dayhoff-tools
3
- Version: 1.9.1
3
+ Version: 1.9.3
4
4
  Summary: Common tools for all the repos at Dayhoff Labs
5
5
  Author: Daniel Martin-Alarcon
6
6
  Author-email: dma@dayhofflabs.com
@@ -3,7 +3,7 @@ dayhoff_tools/chemistry/standardizer.py,sha256=uMn7VwHnx02nc404eO6fRuS4rsl4dvSPf
3
3
  dayhoff_tools/chemistry/utils.py,sha256=jt-7JgF-GeeVC421acX-bobKbLU_X94KNOW24p_P-_M,2257
4
4
  dayhoff_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  dayhoff_tools/cli/cloud_commands.py,sha256=33qcWLmq-FwEXMdL3F0OHm-5Stlh2r65CldyEZgQ1no,40904
6
- dayhoff_tools/cli/engine_commands.py,sha256=HcMjlfxHWGp8J9ai2pOel6RkT3W-BcaaNk1KBBDWmLg,104796
6
+ dayhoff_tools/cli/engine_commands.py,sha256=uq09BZJ4XEV6lOay2Zz2rylTU8AAAIzxeTDbKzNX-r4,107098
7
7
  dayhoff_tools/cli/main.py,sha256=LoFs3SI4fdCjP4pdxEAhri-_q0dmNYupmBCRE4KbBac,5933
8
8
  dayhoff_tools/cli/swarm_commands.py,sha256=5EyKj8yietvT5lfoz8Zx0iQvVaNgc3SJX1z2zQR6o6M,5614
9
9
  dayhoff_tools/cli/utility_commands.py,sha256=WQTHOh1MttuxaJjl2c6zMa4x7_JuaKMQgcyotYrU3GA,25883
@@ -27,7 +27,7 @@ dayhoff_tools/intake/uniprot.py,sha256=BZYJQF63OtPcBBnQ7_P9gulxzJtqyorgyuDiPeOJq
27
27
  dayhoff_tools/logs.py,sha256=DKdeP0k0kliRcilwvX0mUB2eipO5BdWUeHwh-VnsICs,838
28
28
  dayhoff_tools/sqlite.py,sha256=jV55ikF8VpTfeQqqlHSbY8OgfyfHj8zgHNpZjBLos_E,18672
29
29
  dayhoff_tools/warehouse.py,sha256=UETBtZD3r7WgvURqfGbyHlT7cxoiVq8isjzMuerKw8I,24475
30
- dayhoff_tools-1.9.1.dist-info/METADATA,sha256=m7wy7HkaFJon6aQO127tsXQ-GPYiK21NtYFS1FEdh58,2914
31
- dayhoff_tools-1.9.1.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
- dayhoff_tools-1.9.1.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
- dayhoff_tools-1.9.1.dist-info/RECORD,,
30
+ dayhoff_tools-1.9.3.dist-info/METADATA,sha256=cGFFtcWLIkHTYq9PFUgZkddSxAKoSOQTuS0Ix2eYfDs,2914
31
+ dayhoff_tools-1.9.3.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
32
+ dayhoff_tools-1.9.3.dist-info/entry_points.txt,sha256=iAf4jteNqW3cJm6CO6czLxjW3vxYKsyGLZ8WGmxamSc,49
33
+ dayhoff_tools-1.9.3.dist-info/RECORD,,