machineconfig 1.97__py3-none-any.whl → 2.1__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.

Potentially problematic release.


This version of machineconfig might be problematic. Click here for more details.

Files changed (268) hide show
  1. machineconfig/cluster/cloud_manager.py +22 -29
  2. machineconfig/cluster/data_transfer.py +2 -3
  3. machineconfig/cluster/distribute.py +0 -2
  4. machineconfig/cluster/file_manager.py +4 -5
  5. machineconfig/cluster/job_params.py +1 -4
  6. machineconfig/cluster/loader_runner.py +8 -11
  7. machineconfig/cluster/remote_machine.py +4 -5
  8. machineconfig/cluster/script_execution.py +2 -2
  9. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  10. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +4 -6
  11. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  12. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +35 -75
  13. machineconfig/cluster/sessions_managers/wt_local.py +113 -185
  14. machineconfig/cluster/sessions_managers/wt_local_manager.py +127 -197
  15. machineconfig/cluster/sessions_managers/wt_remote.py +60 -67
  16. machineconfig/cluster/sessions_managers/wt_remote_manager.py +110 -149
  17. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +61 -64
  18. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +72 -172
  19. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +27 -60
  20. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +58 -137
  21. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +46 -74
  22. machineconfig/cluster/sessions_managers/zellij_local.py +91 -147
  23. machineconfig/cluster/sessions_managers/zellij_local_manager.py +165 -190
  24. machineconfig/cluster/sessions_managers/zellij_remote.py +51 -58
  25. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +40 -46
  26. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +19 -17
  27. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +30 -31
  28. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +64 -134
  29. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +7 -11
  30. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +27 -55
  31. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +14 -13
  32. machineconfig/cluster/templates/cli_click.py +0 -1
  33. machineconfig/cluster/templates/cli_gooey.py +0 -2
  34. machineconfig/cluster/templates/cli_trogon.py +0 -1
  35. machineconfig/cluster/templates/run_cloud.py +0 -1
  36. machineconfig/cluster/templates/run_cluster.py +0 -1
  37. machineconfig/cluster/templates/run_remote.py +0 -1
  38. machineconfig/cluster/templates/utils.py +27 -11
  39. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  40. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  41. machineconfig/jobs/python/check_installations.py +9 -9
  42. machineconfig/jobs/python/create_bootable_media.py +0 -2
  43. machineconfig/jobs/python/python_cargo_build_share.py +2 -2
  44. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  45. machineconfig/jobs/python/tasks.py +0 -1
  46. machineconfig/jobs/python/vscode/api.py +5 -5
  47. machineconfig/jobs/python/vscode/link_ve.py +20 -21
  48. machineconfig/jobs/python/vscode/select_interpreter.py +28 -29
  49. machineconfig/jobs/python/vscode/sync_code.py +14 -18
  50. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  51. machineconfig/jobs/python_custom_installers/archive/ngrok.py +15 -15
  52. machineconfig/jobs/python_custom_installers/dev/aider.py +10 -18
  53. machineconfig/jobs/python_custom_installers/dev/alacritty.py +12 -21
  54. machineconfig/jobs/python_custom_installers/dev/brave.py +13 -22
  55. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +13 -20
  56. machineconfig/jobs/python_custom_installers/dev/code.py +17 -24
  57. machineconfig/jobs/python_custom_installers/dev/cursor.py +10 -21
  58. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +12 -11
  59. machineconfig/jobs/python_custom_installers/dev/espanso.py +19 -23
  60. machineconfig/jobs/python_custom_installers/dev/goes.py +9 -16
  61. machineconfig/jobs/python_custom_installers/dev/lvim.py +13 -21
  62. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +15 -22
  63. machineconfig/jobs/python_custom_installers/dev/redis.py +15 -23
  64. machineconfig/jobs/python_custom_installers/dev/wezterm.py +15 -22
  65. machineconfig/jobs/python_custom_installers/dev/winget.py +32 -50
  66. machineconfig/jobs/python_custom_installers/docker.py +15 -24
  67. machineconfig/jobs/python_custom_installers/gh.py +18 -26
  68. machineconfig/jobs/python_custom_installers/hx.py +33 -17
  69. machineconfig/jobs/python_custom_installers/warp-cli.py +15 -23
  70. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  71. machineconfig/jobs/python_generic_installers/config.json +412 -389
  72. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  73. machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
  74. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  75. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  76. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  77. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  78. machineconfig/logger.py +50 -0
  79. machineconfig/profile/create.py +50 -36
  80. machineconfig/profile/create_hardlinks.py +33 -26
  81. machineconfig/profile/shell.py +87 -60
  82. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  83. machineconfig/scripts/cloud/init.sh +2 -2
  84. machineconfig/scripts/linux/checkout_versions +1 -1
  85. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  86. machineconfig/scripts/linux/cloud_copy +1 -1
  87. machineconfig/scripts/linux/cloud_manager +1 -1
  88. machineconfig/scripts/linux/cloud_mount +1 -1
  89. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  90. machineconfig/scripts/linux/cloud_sync +1 -1
  91. machineconfig/scripts/linux/croshell +1 -1
  92. machineconfig/scripts/linux/devops +3 -5
  93. machineconfig/scripts/linux/fire +2 -1
  94. machineconfig/scripts/linux/fire_agents +3 -3
  95. machineconfig/scripts/linux/ftpx +1 -1
  96. machineconfig/scripts/linux/gh_models +1 -1
  97. machineconfig/scripts/linux/kill_process +1 -1
  98. machineconfig/scripts/linux/mcinit +2 -2
  99. machineconfig/scripts/linux/repos +1 -1
  100. machineconfig/scripts/linux/scheduler +1 -1
  101. machineconfig/scripts/linux/start_slidev +1 -1
  102. machineconfig/scripts/linux/start_terminals +1 -1
  103. machineconfig/scripts/linux/url2md +1 -1
  104. machineconfig/scripts/linux/warp-cli.sh +122 -0
  105. machineconfig/scripts/linux/wifi_conn +1 -1
  106. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  107. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  112. machineconfig/scripts/python/ai/__init__.py +0 -0
  113. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  114. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  115. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  116. machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
  117. machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
  118. machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
  119. machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
  120. machineconfig/scripts/python/ai/generate_files.py +84 -0
  121. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
  122. machineconfig/scripts/python/ai/mcinit.py +107 -0
  123. machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
  124. machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
  125. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +52 -0
  126. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  127. machineconfig/scripts/python/archive/tmate_start.py +3 -3
  128. machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
  129. machineconfig/scripts/python/cloud_copy.py +20 -19
  130. machineconfig/scripts/python/cloud_mount.py +10 -8
  131. machineconfig/scripts/python/cloud_repo_sync.py +15 -15
  132. machineconfig/scripts/python/cloud_sync.py +1 -1
  133. machineconfig/scripts/python/croshell.py +18 -16
  134. machineconfig/scripts/python/devops.py +6 -6
  135. machineconfig/scripts/python/devops_add_identity.py +9 -7
  136. machineconfig/scripts/python/devops_add_ssh_key.py +19 -19
  137. machineconfig/scripts/python/devops_backup_retrieve.py +14 -14
  138. machineconfig/scripts/python/devops_devapps_install.py +3 -3
  139. machineconfig/scripts/python/devops_update_repos.py +141 -53
  140. machineconfig/scripts/python/dotfile.py +3 -3
  141. machineconfig/scripts/python/fire_agents.py +202 -41
  142. machineconfig/scripts/python/fire_jobs.py +20 -21
  143. machineconfig/scripts/python/ftpx.py +4 -3
  144. machineconfig/scripts/python/gh_models.py +94 -94
  145. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  146. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  147. machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
  148. machineconfig/scripts/python/helpers/helpers2.py +3 -3
  149. machineconfig/scripts/python/helpers/helpers4.py +8 -7
  150. machineconfig/scripts/python/helpers/helpers5.py +7 -7
  151. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  152. machineconfig/scripts/python/mount_nfs.py +4 -3
  153. machineconfig/scripts/python/mount_nw_drive.py +4 -4
  154. machineconfig/scripts/python/mount_ssh.py +4 -3
  155. machineconfig/scripts/python/repos.py +9 -9
  156. machineconfig/scripts/python/scheduler.py +1 -1
  157. machineconfig/scripts/python/start_slidev.py +9 -8
  158. machineconfig/scripts/python/start_terminals.py +1 -1
  159. machineconfig/scripts/python/viewer.py +40 -40
  160. machineconfig/scripts/python/wifi_conn.py +65 -66
  161. machineconfig/scripts/python/wsl_windows_transfer.py +2 -2
  162. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  163. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  164. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  165. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  166. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  167. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  168. machineconfig/scripts/windows/croshell.ps1 +2 -2
  169. machineconfig/scripts/windows/devops.ps1 +1 -4
  170. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  171. machineconfig/scripts/windows/fire.ps1 +1 -1
  172. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  173. machineconfig/scripts/windows/gpt.ps1 +1 -1
  174. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  175. machineconfig/scripts/windows/mcinit.ps1 +2 -2
  176. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  177. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  178. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  179. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  180. machineconfig/scripts/windows/repos.ps1 +1 -1
  181. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  182. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  183. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  184. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  185. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  186. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  187. machineconfig/settings/lf/linux/lfrc +1 -1
  188. machineconfig/settings/linters/.ruff.toml +2 -2
  189. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  190. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  191. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  192. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
  193. machineconfig/settings/shells/wt/settings.json +8 -8
  194. machineconfig/settings/svim/linux/init.toml +1 -1
  195. machineconfig/settings/svim/windows/init.toml +1 -1
  196. machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
  197. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  198. machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
  199. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  200. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  201. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  202. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  203. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  204. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +75 -18
  205. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +52 -42
  206. machineconfig/utils/ai/browser_user_wrapper.py +5 -5
  207. machineconfig/utils/ai/generate_file_checklist.py +19 -22
  208. machineconfig/utils/ai/url2md.py +5 -3
  209. machineconfig/utils/cloud/onedrive/setup_oauth.py +5 -4
  210. machineconfig/utils/cloud/onedrive/transaction.py +192 -227
  211. machineconfig/utils/code.py +71 -43
  212. machineconfig/utils/installer.py +77 -85
  213. machineconfig/utils/installer_utils/installer_abc.py +29 -17
  214. machineconfig/utils/installer_utils/installer_class.py +188 -83
  215. machineconfig/utils/io_save.py +3 -15
  216. machineconfig/utils/links.py +22 -11
  217. machineconfig/utils/notifications.py +197 -0
  218. machineconfig/utils/options.py +38 -25
  219. machineconfig/utils/path.py +18 -6
  220. machineconfig/utils/path_reduced.py +637 -316
  221. machineconfig/utils/procs.py +69 -63
  222. machineconfig/utils/scheduling.py +11 -13
  223. machineconfig/utils/ssh.py +351 -0
  224. machineconfig/utils/terminal.py +225 -0
  225. machineconfig/utils/utils.py +13 -12
  226. machineconfig/utils/utils2.py +43 -10
  227. machineconfig/utils/utils5.py +242 -46
  228. machineconfig/utils/ve.py +11 -6
  229. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/METADATA +15 -9
  230. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/RECORD +232 -235
  231. machineconfig/cluster/self_ssh.py +0 -57
  232. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  233. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  234. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  235. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  236. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  237. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  238. machineconfig/jobs/python_generic_installers/update.py +0 -3
  239. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  240. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  241. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  242. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  243. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  244. machineconfig/scripts/linux/activate_ve +0 -87
  245. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  246. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  247. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  248. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  249. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  250. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  251. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  252. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  253. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  254. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  255. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  256. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  257. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  258. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  259. machineconfig/scripts/python/ai/init.py +0 -56
  260. machineconfig/scripts/python/ai/rules/python/dev.md +0 -31
  261. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  262. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  263. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  264. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  265. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  266. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  267. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
  268. {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ import json
4
4
  import uuid
5
5
  import logging
6
6
  import subprocess
7
+ import time
7
8
  from pathlib import Path
8
9
  from typing import Optional, Dict, List, Any
9
10
 
@@ -21,11 +22,11 @@ TMP_SERIALIZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "
21
22
 
22
23
  class ZellijLocalManager:
23
24
  """Manages multiple local zellij sessions and monitors their tabs and processes."""
24
-
25
+
25
26
  def __init__(self, session2zellij_tabs: Dict[str, Dict[str, tuple[str, str]]], session_name_prefix: str = "LocalJobMgr"):
26
27
  """
27
28
  Initialize the local zellij manager.
28
-
29
+
29
30
  Args:
30
31
  session2zellij_tabs: Dict mapping session names to their tab configs
31
32
  Format: {session_name: {tab_name: (cwd, command), ...}, ...}
@@ -34,14 +35,14 @@ class ZellijLocalManager:
34
35
  self.session_name_prefix = session_name_prefix
35
36
  self.session2zellij_tabs = session2zellij_tabs # Store the original config
36
37
  self.managers: List[ZellijLayoutGenerator] = []
37
-
38
+
38
39
  # Create a ZellijLayoutGenerator for each session
39
40
  for session_name, tab_config in session2zellij_tabs.items():
40
41
  manager = ZellijLayoutGenerator()
41
42
  full_session_name = f"{self.session_name_prefix}_{session_name}"
42
43
  manager.create_zellij_layout(tab_config=tab_config, session_name=full_session_name)
43
44
  self.managers.append(manager)
44
-
45
+
45
46
  # Enhanced Rich logging for initialization
46
47
  console.print(f"[bold green]🔧 Initialized ZellijLocalManager[/bold green] [dim]with[/dim] [bright_green]{len(self.managers)} sessions[/bright_green]")
47
48
 
@@ -49,55 +50,70 @@ class ZellijLocalManager:
49
50
  """Get all managed session names."""
50
51
  return [manager.session_name for manager in self.managers if manager.session_name is not None]
51
52
 
52
- def start_all_sessions(self) -> Dict[str, Any]:
53
- """Start all zellij sessions with their layouts."""
54
- results = {}
53
+ def start_all_sessions(self, poll_seconds: float = 5.0, poll_interval: float = 0.25) -> Dict[str, Any]:
54
+ """Start all zellij sessions with their layouts without blocking on the interactive TUI.
55
+
56
+ Rationale:
57
+ Previous implementation used subprocess.run(... timeout=30) on an "attach" command
58
+ which never returns (interactive) causing a timeout. We now:
59
+ 1. Ensure any old session is deleted (best-effort, short timeout)
60
+ 2. Launch new session in background with Popen (no wait)
61
+ 3. Poll 'zellij list-sessions' to confirm creation
62
+
63
+ Args:
64
+ poll_seconds: Total seconds to wait for session to appear
65
+ poll_interval: Delay between polls
66
+ Returns:
67
+ Dict mapping session name to success metadata.
68
+ """
69
+ results: Dict[str, Any] = {}
55
70
  for manager in self.managers:
71
+ session_name = manager.session_name
56
72
  try:
57
- session_name = manager.session_name
58
73
  if session_name is None:
59
- continue # Skip managers without a session name
60
-
74
+ continue
61
75
  layout_path = manager.layout_path
62
-
63
76
  if not layout_path:
64
- results[session_name] = {
65
- "success": False,
66
- "error": "No layout file path available"
67
- }
77
+ results[session_name] = {"success": False, "error": "No layout file path available"}
78
+ continue
79
+
80
+ # 1. Best-effort delete existing session
81
+ delete_cmd = ["zellij", "delete-session", "--force", session_name]
82
+ try:
83
+ subprocess.run(delete_cmd, capture_output=True, text=True, timeout=5)
84
+ except subprocess.TimeoutExpired:
85
+ logger.warning(f"Timeout deleting session {session_name}; continuing")
86
+ except FileNotFoundError:
87
+ results[session_name] = {"success": False, "error": "'zellij' executable not found in PATH"}
68
88
  continue
69
-
70
- # Delete existing session if it exists, then start with layout
71
- cmd = f"zellij delete-session --force {session_name}; zellij --layout {layout_path} attach {session_name} --create"
72
-
73
- console.print(f"[bold cyan]🚀 Starting session[/bold cyan] [yellow]'{session_name}'[/yellow] [dim]with layout:[/dim] [blue]{layout_path}[/blue]")
74
- result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
75
-
76
- if result.returncode == 0:
77
- results[session_name] = {
78
- "success": True,
79
- "message": f"Session '{session_name}' started successfully"
80
- }
81
- console.print(f"[bold green] Session[/bold green] [yellow]'{session_name}'[/yellow] [green]started successfully[/green]")
89
+
90
+ # 2. Launch new session. We intentionally do NOT wait for completion.
91
+ # Using the same pattern as before (attach --create) but detached via env var.
92
+ # ZELLIJ_AUTO_ATTACH=0 prevents auto-attach if compiled with that feature; harmless otherwise.
93
+ start_cmd = ["bash", "-lc", f"ZELLIJ_AUTO_ATTACH=0 zellij --layout {layout_path} attach {session_name} --create >/dev/null 2>&1 &"]
94
+ console.print(f"[bold cyan]🚀 Starting session[/bold cyan] [yellow]'{session_name}'[/yellow] with layout [blue]{layout_path}[/blue] (non-blocking)...")
95
+ subprocess.Popen(start_cmd)
96
+
97
+ # 3. Poll for presence
98
+ deadline = time.time() + poll_seconds
99
+ appeared = False
100
+ while time.time() < deadline:
101
+ list_res = subprocess.run(["zellij", "list-sessions"], capture_output=True, text=True)
102
+ if list_res.returncode == 0 and session_name in list_res.stdout:
103
+ appeared = True
104
+ break
105
+ time.sleep(poll_interval)
106
+
107
+ if appeared:
108
+ results[session_name] = {"success": True, "message": f"Session '{session_name}' started"}
109
+ console.print(f"[bold green]✅ Session[/bold green] [yellow]'{session_name}'[/yellow] [green]is up[/green]")
82
110
  else:
83
- results[session_name] = {
84
- "success": False,
85
- "error": result.stderr or result.stdout
86
- }
87
- console.print(f"[bold red]❌ Failed to start session[/bold red] [yellow]'{session_name}'[/yellow][red]:[/red] [dim]{result.stderr}[/dim]")
88
-
111
+ results[session_name] = {"success": False, "error": "Session did not appear within poll window"}
112
+ console.print(f"[bold red]❌ Session '{session_name}' did not appear after {poll_seconds:.1f}s[/bold red]")
89
113
  except Exception as e:
90
- # Use a fallback key since session_name might not be defined here
91
- try:
92
- key = getattr(manager, 'session_name', None) or f"manager_{self.managers.index(manager)}"
93
- except Exception:
94
- key = f"manager_{self.managers.index(manager)}"
95
- results[key] = {
96
- "success": False,
97
- "error": str(e)
98
- }
114
+ key = session_name or f"manager_{self.managers.index(manager)}"
115
+ results[key] = {"success": False, "error": str(e)}
99
116
  logger.error(f"❌ Exception starting session '{key}': {e}")
100
-
101
117
  return results
102
118
 
103
119
  def kill_all_sessions(self) -> Dict[str, Any]:
@@ -108,34 +124,28 @@ class ZellijLocalManager:
108
124
  session_name = manager.session_name
109
125
  if session_name is None:
110
126
  continue # Skip managers without a session name
111
-
127
+
112
128
  cmd = f"zellij delete-session --force {session_name}"
113
-
129
+
114
130
  logger.info(f"Killing session '{session_name}'")
115
131
  result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
116
-
117
- results[session_name] = {
118
- "success": result.returncode == 0,
119
- "message": "Session killed" if result.returncode == 0 else result.stderr
120
- }
121
-
132
+
133
+ results[session_name] = {"success": result.returncode == 0, "message": "Session killed" if result.returncode == 0 else result.stderr}
134
+
122
135
  except Exception as e:
123
136
  # Use a fallback key since session_name might not be defined here
124
- key = getattr(manager, 'session_name', None) or f"manager_{self.managers.index(manager)}"
125
- results[key] = {
126
- "success": False,
127
- "error": str(e)
128
- }
129
-
137
+ key = getattr(manager, "session_name", None) or f"manager_{self.managers.index(manager)}"
138
+ results[key] = {"success": False, "error": str(e)}
139
+
130
140
  return results
131
141
 
132
142
  def attach_to_session(self, session_name: Optional[str] = None) -> str:
133
143
  """
134
144
  Generate command to attach to a specific session or list attachment commands for all.
135
-
145
+
136
146
  Args:
137
147
  session_name: Specific session to attach to, or None for all sessions
138
-
148
+
139
149
  Returns:
140
150
  Command string to attach to session(s)
141
151
  """
@@ -157,47 +167,39 @@ class ZellijLocalManager:
157
167
  def check_all_sessions_status(self) -> Dict[str, Dict[str, Any]]:
158
168
  """Check the status of all sessions and their commands."""
159
169
  status_report = {}
160
-
170
+
161
171
  for manager in self.managers:
162
172
  session_name = manager.session_name
163
173
  if session_name is None:
164
174
  continue # Skip managers without a session name
165
-
175
+
166
176
  # Get session status
167
177
  session_status = ZellijLayoutGenerator.check_zellij_session_status(session_name)
168
-
178
+
169
179
  # Get commands status for this session
170
180
  commands_status = manager.check_all_commands_status()
171
-
181
+
172
182
  # Calculate summary for this session
173
183
  running_count = sum(1 for status in commands_status.values() if status.get("running", False))
174
184
  total_count = len(commands_status)
175
-
185
+
176
186
  status_report[session_name] = {
177
187
  "session_status": session_status,
178
188
  "commands_status": commands_status,
179
- "summary": {
180
- "total_commands": total_count,
181
- "running_commands": running_count,
182
- "stopped_commands": total_count - running_count,
183
- "session_healthy": session_status.get("session_exists", False)
184
- }
189
+ "summary": {"total_commands": total_count, "running_commands": running_count, "stopped_commands": total_count - running_count, "session_healthy": session_status.get("session_exists", False)},
185
190
  }
186
-
191
+
187
192
  return status_report
188
193
 
189
194
  def get_global_summary(self) -> Dict[str, Any]:
190
195
  """Get a global summary across all sessions."""
191
196
  all_status = self.check_all_sessions_status()
192
-
197
+
193
198
  total_sessions = len(all_status)
194
- healthy_sessions = sum(1 for status in all_status.values()
195
- if status["summary"]["session_healthy"])
196
- total_commands = sum(status["summary"]["total_commands"]
197
- for status in all_status.values())
198
- total_running = sum(status["summary"]["running_commands"]
199
- for status in all_status.values())
200
-
199
+ healthy_sessions = sum(1 for status in all_status.values() if status["summary"]["session_healthy"])
200
+ total_commands = sum(status["summary"]["total_commands"] for status in all_status.values())
201
+ total_running = sum(status["summary"]["running_commands"] for status in all_status.values())
202
+
201
203
  return {
202
204
  "total_sessions": total_sessions,
203
205
  "healthy_sessions": healthy_sessions,
@@ -206,18 +208,18 @@ class ZellijLocalManager:
206
208
  "running_commands": total_running,
207
209
  "stopped_commands": total_commands - total_running,
208
210
  "all_sessions_healthy": healthy_sessions == total_sessions,
209
- "all_commands_running": total_running == total_commands
211
+ "all_commands_running": total_running == total_commands,
210
212
  }
211
213
 
212
214
  def print_status_report(self) -> None:
213
215
  """Print a comprehensive status report for all sessions."""
214
216
  all_status = self.check_all_sessions_status()
215
217
  global_summary = self.get_global_summary()
216
-
218
+
217
219
  print("=" * 80)
218
220
  print("🔍 ZELLIJ LOCAL MANAGER STATUS REPORT")
219
221
  print("=" * 80)
220
-
222
+
221
223
  # Global summary
222
224
  print("🌐 GLOBAL SUMMARY:")
223
225
  print(f" Total sessions: {global_summary['total_sessions']}")
@@ -226,62 +228,65 @@ class ZellijLocalManager:
226
228
  print(f" Running commands: {global_summary['running_commands']}")
227
229
  print(f" All healthy: {'✅' if global_summary['all_sessions_healthy'] else '❌'}")
228
230
  print()
229
-
231
+
230
232
  # Per-session details
231
233
  for session_name, status in all_status.items():
232
234
  session_status = status["session_status"]
233
235
  commands_status = status["commands_status"]
234
236
  summary = status["summary"]
235
-
237
+
236
238
  print(f"📋 SESSION: {session_name}")
237
239
  print("-" * 60)
238
-
240
+
239
241
  # Session health
240
242
  if session_status.get("session_exists", False):
241
243
  print("✅ Session is running")
242
244
  else:
243
245
  print(f"❌ Session not found: {session_status.get('error', 'Unknown error')}")
244
-
246
+
245
247
  # Commands in this session
246
248
  print(f" Commands ({summary['running_commands']}/{summary['total_commands']} running):")
247
249
  for tab_name, cmd_status in commands_status.items():
248
250
  status_icon = "✅" if cmd_status.get("running", False) else "❌"
249
251
  print(f" {status_icon} {tab_name}: {cmd_status.get('command', 'Unknown')}")
250
-
252
+
251
253
  if cmd_status.get("processes"):
252
254
  for proc in cmd_status["processes"][:2]: # Show first 2 processes
253
255
  print(f" └─ PID {proc['pid']}: {proc['name']} ({proc['status']})")
254
256
  print()
255
-
257
+
256
258
  print("=" * 80)
257
259
 
258
260
  def run_monitoring_routine(self, wait_ms: int = 30000) -> None:
259
261
  """
260
262
  Run a continuous monitoring routine that checks status periodically.
261
-
263
+
262
264
  Args:
263
265
  wait_ms: How long to wait between checks in milliseconds (default: 30000ms = 30s)
264
266
  """
267
+
265
268
  def routine(scheduler: Scheduler):
266
269
  print(f"\n⏰ Monitoring cycle {scheduler.cycle} at {datetime.now()}")
267
270
  print("-" * 50)
268
-
271
+
269
272
  if scheduler.cycle % 2 == 0:
270
273
  # Detailed status check every other cycle
271
274
  all_status = self.check_all_sessions_status()
272
-
275
+
273
276
  # Create DataFrame for easier viewing
274
277
  status_data = []
275
278
  for session_name, status in all_status.items():
276
279
  for tab_name, cmd_status in status["commands_status"].items():
277
- status_data.append({
278
- "session": session_name,
279
- "tab": tab_name,
280
- "running": cmd_status.get("running", False),
281
- "command": cmd_status.get("command", "Unknown")[:50] + "..." if len(cmd_status.get("command", "")) > 50 else cmd_status.get("command", ""),
282
- "processes": len(cmd_status.get("processes", []))
283
- })
284
-
280
+ status_data.append(
281
+ {
282
+ "session": session_name,
283
+ "tab": tab_name,
284
+ "running": cmd_status.get("running", False),
285
+ "command": cmd_status.get("command", "Unknown")[:50] + "..." if len(cmd_status.get("command", "")) > 50 else cmd_status.get("command", ""),
286
+ "processes": len(cmd_status.get("processes", [])),
287
+ }
288
+ )
289
+
285
290
  if status_data:
286
291
  # Format data as table
287
292
  if status_data:
@@ -294,7 +299,7 @@ class ZellijLocalManager:
294
299
  for row in status_data:
295
300
  values = [str(row.get(h, ""))[:15] for h in headers]
296
301
  print(" | ".join(f"{v:<15}" for v in values))
297
-
302
+
298
303
  # Check if all sessions have stopped
299
304
  running_count = sum(1 for row in status_data if row.get("running", False))
300
305
  if running_count == 0:
@@ -307,103 +312,93 @@ class ZellijLocalManager:
307
312
  # Quick summary check
308
313
  global_summary = self.get_global_summary()
309
314
  print(f"📊 Quick Summary: {global_summary['running_commands']}/{global_summary['total_commands']} commands running across {global_summary['healthy_sessions']}/{global_summary['total_sessions']} sessions")
310
-
315
+
311
316
  logger.info(f"Starting monitoring routine with {wait_ms}ms intervals")
312
- sched = Scheduler(routine=routine, wait_ms=wait_ms)
317
+ sched = Scheduler(routine=routine, wait_ms=wait_ms, logger=logger)
313
318
  sched.run()
314
319
 
315
320
  def save(self, session_id: Optional[str] = None) -> str:
316
321
  """Save the manager state to disk."""
317
322
  if session_id is None:
318
323
  session_id = str(uuid.uuid4())[:8]
319
-
324
+
320
325
  # Create session directory
321
326
  session_dir = TMP_SERIALIZATION_DIR / session_id
322
327
  session_dir.mkdir(parents=True, exist_ok=True)
323
-
328
+
324
329
  # Save the session2zellij_tabs configuration
325
330
  config_file = session_dir / "session2zellij_tabs.json"
326
- with open(config_file, 'w', encoding='utf-8') as f:
327
- json.dump(self.session2zellij_tabs, f, indent=2, ensure_ascii=False)
328
-
331
+ text = json.dumps(self.session2zellij_tabs, indent=2, ensure_ascii=False)
332
+ config_file.write_text(text, encoding="utf-8")
333
+
329
334
  # Save metadata
330
- metadata = {
331
- "session_name_prefix": self.session_name_prefix,
332
- "created_at": str(datetime.now()),
333
- "num_managers": len(self.managers),
334
- "sessions": list(self.session2zellij_tabs.keys()),
335
- "manager_type": "ZellijLocalManager"
336
- }
335
+ metadata = {"session_name_prefix": self.session_name_prefix, "created_at": str(datetime.now()), "num_managers": len(self.managers), "sessions": list(self.session2zellij_tabs.keys()), "manager_type": "ZellijLocalManager"}
337
336
  metadata_file = session_dir / "metadata.json"
338
- with open(metadata_file, 'w', encoding='utf-8') as f:
339
- json.dump(metadata, f, indent=2, ensure_ascii=False)
340
-
337
+ text = json.dumps(metadata, indent=2, ensure_ascii=False)
338
+ metadata_file.write_text(text, encoding="utf-8")
339
+
341
340
  # Save each manager's state
342
341
  managers_dir = session_dir / "managers"
343
342
  managers_dir.mkdir(exist_ok=True)
344
-
343
+
345
344
  for i, manager in enumerate(self.managers):
346
- manager_data = {
347
- "session_name": manager.session_name,
348
- "tab_config": manager.tab_config,
349
- "layout_path": manager.layout_path
350
- }
345
+ manager_data = {"session_name": manager.session_name, "tab_config": manager.tab_config, "layout_path": manager.layout_path}
351
346
  manager_file = managers_dir / f"manager_{i}_{manager.session_name}.json"
352
- with open(manager_file, 'w', encoding='utf-8') as f:
353
- json.dump(manager_data, f, indent=2, ensure_ascii=False)
354
-
347
+ text = json.dumps(manager_data, indent=2, ensure_ascii=False)
348
+ manager_file.write_text(text, encoding="utf-8")
349
+
355
350
  logger.info(f"✅ Saved ZellijLocalManager session to: {session_dir}")
356
351
  return session_id
357
352
 
358
353
  @classmethod
359
- def load(cls, session_id: str) -> 'ZellijLocalManager':
354
+ def load(cls, session_id: str) -> "ZellijLocalManager":
360
355
  """Load a saved manager state from disk."""
361
356
  session_dir = TMP_SERIALIZATION_DIR / session_id
362
-
357
+
363
358
  if not session_dir.exists():
364
359
  raise FileNotFoundError(f"Session directory not found: {session_dir}")
365
-
360
+
366
361
  # Load configuration
367
362
  config_file = session_dir / "session2zellij_tabs.json"
368
363
  if not config_file.exists():
369
364
  raise FileNotFoundError(f"Configuration file not found: {config_file}")
370
-
371
- with open(config_file, 'r', encoding='utf-8') as f:
365
+
366
+ with open(config_file, "r", encoding="utf-8") as f:
372
367
  session2zellij_tabs = json.load(f)
373
-
368
+
374
369
  # Load metadata
375
370
  metadata_file = session_dir / "metadata.json"
376
371
  session_name_prefix = "LocalJobMgr" # default fallback
377
372
  if metadata_file.exists():
378
- with open(metadata_file, 'r', encoding='utf-8') as f:
373
+ with open(metadata_file, "r", encoding="utf-8") as f:
379
374
  metadata = json.load(f)
380
375
  session_name_prefix = metadata.get("session_name_prefix", "LocalJobMgr")
381
-
376
+
382
377
  # Create new instance
383
378
  instance = cls(session2zellij_tabs=session2zellij_tabs, session_name_prefix=session_name_prefix)
384
-
379
+
385
380
  # Load saved manager states
386
381
  managers_dir = session_dir / "managers"
387
382
  if managers_dir.exists():
388
383
  instance.managers = []
389
384
  manager_files = sorted(managers_dir.glob("manager_*.json"))
390
-
385
+
391
386
  for manager_file in manager_files:
392
387
  try:
393
- with open(manager_file, 'r', encoding='utf-8') as f:
388
+ with open(manager_file, "r", encoding="utf-8") as f:
394
389
  manager_data = json.load(f)
395
-
390
+
396
391
  # Recreate the manager
397
392
  manager = ZellijLayoutGenerator()
398
393
  manager.session_name = manager_data["session_name"]
399
394
  manager.tab_config = manager_data["tab_config"]
400
395
  manager.layout_path = manager_data["layout_path"]
401
-
396
+
402
397
  instance.managers.append(manager)
403
-
398
+
404
399
  except Exception as e:
405
400
  logger.warning(f"Failed to load manager from {manager_file}: {e}")
406
-
401
+
407
402
  logger.info(f"✅ Loaded ZellijLocalManager session from: {session_dir}")
408
403
  return instance
409
404
 
@@ -412,25 +407,26 @@ class ZellijLocalManager:
412
407
  """List all saved session IDs."""
413
408
  if not TMP_SERIALIZATION_DIR.exists():
414
409
  return []
415
-
410
+
416
411
  sessions = []
417
412
  for item in TMP_SERIALIZATION_DIR.iterdir():
418
413
  if item.is_dir() and (item / "metadata.json").exists():
419
414
  sessions.append(item.name)
420
-
415
+
421
416
  return sorted(sessions)
422
417
 
423
418
  @staticmethod
424
419
  def delete_session(session_id: str) -> bool:
425
420
  """Delete a saved session."""
426
421
  session_dir = TMP_SERIALIZATION_DIR / session_id
427
-
422
+
428
423
  if not session_dir.exists():
429
424
  logger.warning(f"Session directory not found: {session_dir}")
430
425
  return False
431
-
426
+
432
427
  try:
433
428
  import shutil
429
+
434
430
  shutil.rmtree(session_dir)
435
431
  logger.info(f"✅ Deleted session: {session_id}")
436
432
  return True
@@ -441,93 +437,72 @@ class ZellijLocalManager:
441
437
  def list_active_sessions(self) -> List[Dict[str, Any]]:
442
438
  """List currently active zellij sessions managed by this instance."""
443
439
  active_sessions = []
444
-
440
+
445
441
  try:
446
442
  # Get all running zellij sessions
447
- result = subprocess.run(
448
- ['zellij', 'list-sessions'],
449
- capture_output=True,
450
- text=True,
451
- timeout=10
452
- )
453
-
443
+ result = subprocess.run(["zellij", "list-sessions"], capture_output=True, text=True, timeout=10)
444
+
454
445
  if result.returncode == 0:
455
- all_sessions = result.stdout.strip().split('\n') if result.stdout.strip() else []
456
-
446
+ all_sessions = result.stdout.strip().split("\n") if result.stdout.strip() else []
447
+
457
448
  # Filter to only our managed sessions
458
449
  for manager in self.managers:
459
450
  session_name = manager.session_name
460
451
  if session_name is None:
461
452
  continue # Skip managers without a session name
462
453
  is_active = any(session_name in session for session in all_sessions)
463
-
464
- active_sessions.append({
465
- "session_name": session_name,
466
- "is_active": is_active,
467
- "tab_count": len(manager.tab_config),
468
- "tabs": list(manager.tab_config.keys())
469
- })
470
-
454
+
455
+ active_sessions.append({"session_name": session_name, "is_active": is_active, "tab_count": len(manager.tab_config), "tabs": list(manager.tab_config.keys())})
456
+
471
457
  except Exception as e:
472
458
  logger.error(f"Error listing active sessions: {e}")
473
-
459
+
474
460
  return active_sessions
475
461
 
476
462
 
477
463
  if __name__ == "__main__":
478
464
  # Example usage
479
465
  sample_sessions = {
480
- "development": {
481
- "🚀Frontend": ("~/code/myapp/frontend", "npm run dev"),
482
- "⚙️Backend": ("~/code/myapp/backend", "python manage.py runserver"),
483
- "📊Monitor": ("~", "htop")
484
- },
485
- "testing": {
486
- "🧪Tests": ("~/code/myapp", "pytest --watch"),
487
- "🔍Coverage": ("~/code/myapp", "coverage run --source=. -m pytest"),
488
- "📝Logs": ("~/logs", "tail -f app.log")
489
- },
490
- "deployment": {
491
- "🐳Docker": ("~/code/myapp", "docker-compose up"),
492
- "☸️K8s": ("~/k8s", "kubectl get pods --watch"),
493
- "📈Metrics": ("~", "k9s")
494
- }
466
+ "development": {"🚀Frontend": ("~/code/myapp/frontend", "npm run dev"), "⚙️Backend": ("~/code/myapp/backend", "python manage.py runserver"), "📊Monitor": ("~", "htop")},
467
+ "testing": {"🧪Tests": ("~/code/myapp", "pytest --watch"), "🔍Coverage": ("~/code/myapp", "coverage run --source=. -m pytest"), "📝Logs": ("~/logs", "tail -f app.log")},
468
+ "deployment": {"🐳Docker": ("~/code/myapp", "docker-compose up"), "☸️K8s": ("~/k8s", "kubectl get pods --watch"), "📈Metrics": ("~", "k9s")},
495
469
  }
496
-
470
+
497
471
  try:
498
472
  # Create the local manager
499
473
  manager = ZellijLocalManager(sample_sessions, session_name_prefix="DevEnv")
500
474
  print(f"✅ Local manager created with {len(manager.managers)} sessions")
501
-
475
+
502
476
  # Show session names
503
477
  print(f"📋 Sessions: {manager.get_all_session_names()}")
504
-
478
+
505
479
  # Print attachment commands (without actually starting)
506
480
  print("\n📎 Attachment commands:")
507
481
  print(manager.attach_to_session())
508
-
482
+
509
483
  # Show current status
510
484
  print("\n🔍 Current status:")
511
485
  manager.print_status_report()
512
-
486
+
513
487
  # Demonstrate save/load
514
488
  print("\n💾 Demonstrating save/load...")
515
489
  session_id = manager.save()
516
490
  print(f"✅ Saved session: {session_id}")
517
-
491
+
518
492
  # List saved sessions
519
493
  saved_sessions = ZellijLocalManager.list_saved_sessions()
520
494
  print(f"📋 Saved sessions: {saved_sessions}")
521
-
495
+
522
496
  # Load and verify
523
497
  loaded_manager = ZellijLocalManager.load(session_id)
524
498
  print(f"✅ Loaded session with {len(loaded_manager.managers)} sessions")
525
-
499
+
526
500
  # Show how to start monitoring (commented out to prevent infinite loop in demo)
527
501
  print("\n⏰ To start monitoring, run:")
528
502
  print("manager.run_monitoring_routine(wait_ms=30000) # 30 seconds")
529
-
503
+
530
504
  except Exception as e:
531
505
  print(f"❌ Error: {e}")
532
506
  import traceback
533
- traceback.print_exc()
507
+
508
+ traceback.print_exc()