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