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
@@ -21,20 +21,19 @@ TMP_LAYOUT_DIR = Path.home().joinpath("tmp_results", "zellij_layouts", "layout_m
21
21
 
22
22
 
23
23
  class ZellijRemoteLayoutGenerator:
24
-
25
24
  def __init__(self, remote_name: str, session_name_prefix: str):
26
25
  self.remote_name = remote_name
27
26
  self.session_name = session_name_prefix + "_" + LayoutGenerator.generate_random_suffix()
28
27
  self.tab_config: Dict[str, Tuple[str, str]] = {}
29
28
  self.layout_path: Optional[str] = None
30
-
29
+
31
30
  # Initialize modular components
32
31
  self.remote_executor = RemoteExecutor(remote_name)
33
32
  self.layout_generator = LayoutGenerator()
34
33
  self.process_monitor = ProcessMonitor(self.remote_executor)
35
34
  self.session_manager = SessionManager(self.remote_executor, self.session_name, TMP_LAYOUT_DIR)
36
35
  self.status_reporter = StatusReporter(self.process_monitor, self.session_manager)
37
-
36
+
38
37
  def copy_layout_to_remote(self, local_layout_file: Path, random_suffix: str) -> str:
39
38
  return self.session_manager.copy_layout_to_remote(local_layout_file, random_suffix)
40
39
 
@@ -42,22 +41,22 @@ class ZellijRemoteLayoutGenerator:
42
41
  # Enhanced Rich logging for remote layout creation
43
42
  tab_count = len(tab_config)
44
43
  console.print(f"[bold cyan]📋 Creating Zellij layout[/bold cyan] [bright_green]with {tab_count} tabs[/bright_green] [magenta]for remote[/magenta] [bold yellow]'{self.remote_name}'[/bold yellow]")
45
-
44
+
46
45
  # Display tab summary for remote
47
46
  for tab_name, (cwd, _) in tab_config.items():
48
47
  console.print(f" [yellow]→[/yellow] [bold]{tab_name}[/bold] [dim]in[/dim] [blue]{cwd}[/blue] [dim]on[/dim] [yellow]{self.remote_name}[/yellow]")
49
-
48
+
50
49
  self.tab_config = tab_config.copy()
51
50
  if output_dir:
52
51
  output_path = Path(output_dir)
53
52
  else:
54
53
  output_path = TMP_LAYOUT_DIR
55
- self.layout_path = self.layout_generator.create_layout_file(tab_config, output_path, self.session_name)
54
+ self.layout_path = self.layout_generator.create_layout_file(tab_config, output_path, self.session_name)
56
55
  return self.layout_path
57
-
56
+
58
57
  def get_layout_preview(self, tab_config: Dict[str, Tuple[str, str]]) -> str:
59
58
  return self.layout_generator.generate_layout_content(tab_config)
60
-
59
+
61
60
  def check_command_status(self, tab_name: str, use_verification: bool = True) -> Dict[str, Any]:
62
61
  return self.process_monitor.check_command_status(tab_name, self.tab_config, use_verification)
63
62
 
@@ -96,14 +95,7 @@ class ZellijRemoteLayoutGenerator:
96
95
  return executor.run_command(command, timeout)
97
96
 
98
97
  def to_dict(self) -> Dict[str, Any]:
99
- return {
100
- "remote_name": self.remote_name,
101
- "session_name": self.session_name,
102
- "tab_config": self.tab_config,
103
- "layout_path": self.layout_path,
104
- "created_at": datetime.now().isoformat(),
105
- "class_name": self.__class__.__name__
106
- }
98
+ return {"remote_name": self.remote_name, "session_name": self.session_name, "tab_config": self.tab_config, "layout_path": self.layout_path, "created_at": datetime.now().isoformat(), "class_name": self.__class__.__name__}
107
99
 
108
100
  def to_json(self, file_path: Optional[Union[str, Path]] = None) -> str:
109
101
  # Generate file path if not provided
@@ -114,57 +106,57 @@ class ZellijRemoteLayoutGenerator:
114
106
  file_path_obj = default_dir / f"zellij_session_{random_id}.json"
115
107
  else:
116
108
  file_path_obj = Path(file_path)
117
-
109
+
118
110
  # Ensure .json extension
119
- if not str(file_path_obj).endswith('.json'):
120
- file_path_obj = file_path_obj.with_suffix('.json')
121
-
111
+ if not str(file_path_obj).endswith(".json"):
112
+ file_path_obj = file_path_obj.with_suffix(".json")
113
+
122
114
  # Ensure parent directory exists
123
115
  file_path_obj.parent.mkdir(parents=True, exist_ok=True)
124
-
116
+
125
117
  # Serialize to JSON
126
118
  data = self.to_dict()
127
-
128
- with open(file_path_obj, 'w', encoding='utf-8') as f:
129
- json.dump(data, f, indent=2, ensure_ascii=False)
130
-
119
+
120
+ text = json.dumps(data, indent=2, ensure_ascii=False)
121
+ file_path_obj.write_text(text, encoding="utf-8")
122
+
131
123
  logger.info(f"✅ Serialized ZellijRemoteLayoutGenerator to: {file_path_obj}")
132
124
  return str(file_path_obj)
133
125
 
134
126
  @classmethod
135
- def from_json(cls, file_path: Union[str, Path]) -> 'ZellijRemoteLayoutGenerator':
127
+ def from_json(cls, file_path: Union[str, Path]) -> "ZellijRemoteLayoutGenerator":
136
128
  file_path = Path(file_path)
137
-
129
+
138
130
  # Ensure .json extension
139
- if not str(file_path).endswith('.json'):
140
- file_path = file_path.with_suffix('.json')
141
-
131
+ if not str(file_path).endswith(".json"):
132
+ file_path = file_path.with_suffix(".json")
133
+
142
134
  if not file_path.exists():
143
135
  raise FileNotFoundError(f"JSON file not found: {file_path}")
144
-
136
+
145
137
  # Load JSON data
146
- with open(file_path, 'r', encoding='utf-8') as f:
138
+ with open(file_path, "r", encoding="utf-8") as f:
147
139
  data = json.load(f)
148
-
140
+
149
141
  # Validate that it's the correct class
150
- if data.get('class_name') != cls.__name__:
142
+ if data.get("class_name") != cls.__name__:
151
143
  logger.warning(f"Class name mismatch: expected {cls.__name__}, got {data.get('class_name')}")
152
-
144
+
153
145
  # Create new instance
154
146
  # Extract session name prefix by removing the suffix
155
- session_name = data['session_name']
156
- if '_' in session_name:
157
- session_name_prefix = '_'.join(session_name.split('_')[:-1])
147
+ session_name = data["session_name"]
148
+ if "_" in session_name:
149
+ session_name_prefix = "_".join(session_name.split("_")[:-1])
158
150
  else:
159
151
  session_name_prefix = session_name
160
-
161
- instance = cls(remote_name=data['remote_name'], session_name_prefix=session_name_prefix)
162
-
152
+
153
+ instance = cls(remote_name=data["remote_name"], session_name_prefix=session_name_prefix)
154
+
163
155
  # Restore state
164
- instance.session_name = data['session_name']
165
- instance.tab_config = data['tab_config']
166
- instance.layout_path = data['layout_path']
167
-
156
+ instance.session_name = data["session_name"]
157
+ instance.tab_config = data["tab_config"]
158
+ instance.layout_path = data["layout_path"]
159
+
168
160
  logger.info(f"✅ Loaded ZellijRemoteLayoutGenerator from: {file_path}")
169
161
  return instance
170
162
 
@@ -174,56 +166,57 @@ class ZellijRemoteLayoutGenerator:
174
166
  directory_path = Path.home() / "tmp_results" / "zellij_sessions" / "serialized"
175
167
  else:
176
168
  directory_path = Path(directory_path)
177
-
169
+
178
170
  if not directory_path.exists():
179
171
  return []
180
-
172
+
181
173
  json_files = [f.name for f in directory_path.glob("*.json")]
182
174
  return sorted(json_files)
183
175
 
176
+
184
177
  if __name__ == "__main__":
185
178
  # Example usage
186
179
  sample_tabs = {
187
180
  "🤖Bot1": ("~/code/bytesense/bithence", "~/scripts/fire -mO go1.py bot1 --kw create_new_bot True"),
188
- "🤖Bot2": ("~/code/bytesense/bithence", "~/scripts/fire -mO go2.py bot2 --kw create_new_bot True"),
181
+ "🤖Bot2": ("~/code/bytesense/bithence", "~/scripts/fire -mO go2.py bot2 --kw create_new_bot True"),
189
182
  "📊Monitor": ("~", "htop"),
190
- "📝Logs": ("/var/log", "tail -f /var/log/app.log")
183
+ "📝Logs": ("/var/log", "tail -f /var/log/app.log"),
191
184
  }
192
-
185
+
193
186
  # Replace 'myserver' with an actual SSH config alias
194
187
  remote_name = "myserver" # This should be in ~/.ssh/config
195
188
  session_name = "test_remote_session"
196
-
189
+
197
190
  try:
198
191
  # Create layout using the remote generator
199
192
  generator = ZellijRemoteLayoutGenerator(remote_name=remote_name, session_name_prefix=session_name)
200
193
  layout_path = generator.create_zellij_layout(sample_tabs)
201
194
  print(f"✅ Remote layout created successfully: {layout_path}")
202
-
195
+
203
196
  # Demonstrate serialization
204
197
  print("\n💾 Demonstrating serialization...")
205
198
  saved_path = generator.to_json()
206
199
  print(f"✅ Session saved to: {saved_path}")
207
-
200
+
208
201
  # List all saved sessions
209
202
  saved_sessions = ZellijRemoteLayoutGenerator.list_saved_sessions()
210
203
  print(f"📋 Available saved sessions: {saved_sessions}")
211
-
204
+
212
205
  # Demonstrate loading (using the full path)
213
206
  loaded_generator = ZellijRemoteLayoutGenerator.from_json(saved_path)
214
207
  print(f"✅ Session loaded successfully: {loaded_generator.session_name}")
215
208
  print(f"📊 Loaded tabs: {list(loaded_generator.tab_config.keys())}")
216
-
209
+
217
210
  # Demonstrate status checking
218
211
  print(f"\n🔍 Checking command status on remote '{remote_name}':")
219
212
  generator.print_status_report()
220
-
213
+
221
214
  # Start the session (uncomment to actually start)
222
215
  # start_result = generator.start_zellij_session()
223
216
  # print(f"Session start result: {start_result}")
224
-
217
+
225
218
  # Attach to session (uncomment to attach)
226
219
  # generator.attach_to_session()
227
-
220
+
228
221
  except Exception as e:
229
222
  print(f"❌ Error: {e}")
@@ -1,15 +1,16 @@
1
1
  from datetime import datetime
2
2
  import json
3
3
  import uuid
4
- import logging
5
4
  from pathlib import Path
6
5
  from typing import Optional
7
6
  from machineconfig.utils.utils5 import Scheduler
8
7
  from machineconfig.cluster.sessions_managers.zellij_local import run_command_in_zellij_tab
9
8
  from machineconfig.cluster.sessions_managers.zellij_remote import ZellijRemoteLayoutGenerator
9
+ from machineconfig.logger import get_logger
10
10
 
11
11
 
12
12
  TMP_SERIALIAZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "zellij", "remote_manager")
13
+ logger = get_logger("cluster.sessions_managers.zellij_remote_manager")
13
14
 
14
15
 
15
16
  class ZellijSessionManager:
@@ -35,14 +36,11 @@ class ZellijSessionManager:
35
36
  a_cmd = run_command_in_zellij_tab(command=ssh_cmd, tab_name=hostname, cwd=None)
36
37
  cmds += a_cmd + "\n"
37
38
  return cmds
38
-
39
+
39
40
  def kill_all_sessions(self) -> None:
40
41
  for an_m in self.managers:
41
- ZellijRemoteLayoutGenerator.run_remote_command(
42
- remote_name=an_m.remote_name,
43
- command="zellij kill-all-sessions --yes"
44
- )
45
-
42
+ ZellijRemoteLayoutGenerator.run_remote_command(remote_name=an_m.remote_name, command="zellij kill-all-sessions --yes")
43
+
46
44
  def start_zellij_sessions(self) -> None:
47
45
  for an_m in self.managers:
48
46
  an_m.start_zellij_session()
@@ -65,12 +63,12 @@ class ZellijSessionManager:
65
63
  for i, key in enumerate(keys):
66
64
  if i < len(values):
67
65
  status_data.append({"tabName": key, "status": values[i]})
68
-
66
+
69
67
  # Check if all stopped
70
68
  running_count = sum(1 for item in status_data if item.get("status", {}).get("running", False))
71
69
  if running_count == 0: # they all stopped
72
70
  sched.max_cycles = sched.cycle # stop the scheduler from calling this routine again
73
-
71
+
74
72
  # Print status
75
73
  for item in status_data:
76
74
  print(f"Tab: {item['tabName']}, Status: {item['status']}")
@@ -79,67 +77,62 @@ class ZellijSessionManager:
79
77
  for _idx, an_m in enumerate(self.managers):
80
78
  a_status = an_m.check_zellij_session_status()
81
79
  statuses.append(a_status)
82
-
80
+
83
81
  # Print statuses
84
82
  for i, status in enumerate(statuses):
85
83
  print(f"Manager {i}: {status}")
86
- sched = Scheduler(routine=routine, wait_ms=60000) # 60 seconds
84
+
85
+ sched = Scheduler(routine=routine, wait_ms=60_000, logger=logger)
87
86
  sched.run()
88
87
 
89
88
  def save(self, session_id: Optional[str] = None) -> str:
90
89
  if session_id is None:
91
90
  session_id = str(uuid.uuid4())[:8]
92
-
91
+
93
92
  # Create session directory
94
93
  session_dir = TMP_SERIALIAZATION_DIR / session_id
95
94
  session_dir.mkdir(parents=True, exist_ok=True)
96
-
95
+
97
96
  # Save the machine2zellij_tabs configuration
98
97
  config_file = session_dir / "machine2zellij_tabs.json"
99
- with open(config_file, 'w', encoding='utf-8') as f:
100
- json.dump(self.machine2zellij_tabs, f, indent=2, ensure_ascii=False)
101
-
98
+ text = json.dumps(self.machine2zellij_tabs, indent=2, ensure_ascii=False)
99
+ config_file.write_text(text, encoding="utf-8")
100
+
102
101
  # Save session metadata
103
- metadata = {
104
- "session_name_prefix": self.session_name_prefix,
105
- "created_at": str(datetime.now()),
106
- "num_managers": len(self.managers),
107
- "machines": list(self.machine2zellij_tabs.keys())
108
- }
102
+ metadata = {"session_name_prefix": self.session_name_prefix, "created_at": str(datetime.now()), "num_managers": len(self.managers), "machines": list(self.machine2zellij_tabs.keys())}
109
103
  metadata_file = session_dir / "metadata.json"
110
- with open(metadata_file, 'w', encoding='utf-8') as f:
111
- json.dump(metadata, f, indent=2, ensure_ascii=False)
112
-
104
+ text = json.dumps(metadata, indent=2, ensure_ascii=False)
105
+ metadata_file.write_text(text, encoding="utf-8")
106
+
113
107
  # Save each ZellijRemoteLayoutGenerator
114
108
  managers_dir = session_dir / "managers"
115
109
  managers_dir.mkdir(exist_ok=True)
116
-
110
+
117
111
  for i, manager in enumerate(self.managers):
118
112
  manager_file = managers_dir / f"manager_{i}_{manager.remote_name}.json"
119
113
  manager.to_json(str(manager_file))
120
-
121
- logging.info(f"✅ Saved ZellijSessionManager session to: {session_dir}")
114
+ logger.info(f"✅ Saved ZellijSessionManager session to: {session_dir}")
122
115
  return session_id
123
116
 
124
117
  @classmethod
125
- def load(cls, session_id: str) -> 'ZellijSessionManager':
118
+ def load(cls, session_id: str) -> "ZellijSessionManager":
126
119
  session_dir = TMP_SERIALIAZATION_DIR / session_id
127
-
120
+
128
121
  if not session_dir.exists():
129
- raise FileNotFoundError(f"Session directory not found: {session_dir}")
122
+ raise FileNotFoundError(f"Session directory not found: {session_dir}")
130
123
  config_file = session_dir / "machine2zellij_tabs.json"
131
124
  if not config_file.exists():
132
- raise FileNotFoundError(f"Configuration file not found: {config_file}")
133
- with open(config_file, 'r', encoding='utf-8') as f:
125
+ raise FileNotFoundError(f"Configuration file not found: {config_file}")
126
+ with open(config_file, "r", encoding="utf-8") as f:
134
127
  machine2zellij_tabs = json.load(f)
135
-
128
+
136
129
  # Load metadata
137
130
  metadata_file = session_dir / "metadata.json"
138
131
  session_name_prefix = "JobMgr" # default fallback
139
132
  if metadata_file.exists():
140
- with open(metadata_file, 'r', encoding='utf-8') as f:
133
+ with open(metadata_file, "r", encoding="utf-8") as f:
141
134
  metadata = json.load(f)
142
- session_name_prefix = metadata.get("session_name_prefix", "JobMgr")
135
+ session_name_prefix = metadata.get("session_name_prefix", "JobMgr")
143
136
  # Create new instance (this will create new managers)
144
137
  instance = cls(machine2zellij_tabs=machine2zellij_tabs, session_name_prefix=session_name_prefix)
145
138
  # Load saved managers to restore their states
@@ -148,41 +141,42 @@ class ZellijSessionManager:
148
141
  # Clear the auto-created managers and load the saved ones
149
142
  instance.managers = []
150
143
  # Get all manager files and sort them
151
- manager_files = sorted(managers_dir.glob("manager_*.json"))
144
+ manager_files = sorted(managers_dir.glob("manager_*.json"))
152
145
  for manager_file in manager_files:
153
146
  try:
154
147
  loaded_manager = ZellijRemoteLayoutGenerator.from_json(str(manager_file))
155
148
  instance.managers.append(loaded_manager)
156
149
  except Exception as e:
157
- logging.warning(f"Failed to load manager from {manager_file}: {e}")
158
- logging.info(f"✅ Loaded ZellijSessionManager session from: {session_dir}")
150
+ logger.warning(f"Failed to load manager from {manager_file}: {e}")
151
+ logger.info(f"✅ Loaded ZellijSessionManager session from: {session_dir}")
159
152
  return instance
160
153
 
161
154
  @staticmethod
162
155
  def list_saved_sessions() -> list[str]:
163
156
  if not TMP_SERIALIAZATION_DIR.exists():
164
157
  return []
165
-
158
+
166
159
  sessions = []
167
160
  for item in TMP_SERIALIAZATION_DIR.iterdir():
168
161
  if item.is_dir() and (item / "metadata.json").exists():
169
162
  sessions.append(item.name)
170
-
163
+
171
164
  return sorted(sessions)
172
165
 
173
166
  @staticmethod
174
167
  def delete_session(session_id: str) -> bool:
175
168
  session_dir = TMP_SERIALIAZATION_DIR / session_id
176
-
169
+
177
170
  if not session_dir.exists():
178
- logging.warning(f"Session directory not found: {session_dir}")
171
+ logger.warning(f"Session directory not found: {session_dir}")
179
172
  return False
180
-
173
+
181
174
  try:
182
175
  import shutil
176
+
183
177
  shutil.rmtree(session_dir)
184
- logging.info(f"✅ Deleted session: {session_id}")
178
+ logger.info(f"✅ Deleted session: {session_id}")
185
179
  return True
186
180
  except Exception as e:
187
- logging.error(f"Failed to delete session {session_id}: {e}")
181
+ logger.error(f"Failed to delete session {session_id}: {e}")
188
182
  return False
@@ -5,60 +5,62 @@ Example usage of the modularized Zellij remote layout generator.
5
5
 
6
6
  from machineconfig.cluster.sessions_managers.zellij_remote import ZellijRemoteLayoutGenerator
7
7
 
8
+
8
9
  def example_usage():
9
10
  """Demonstrate the refactored modular usage."""
10
-
11
+
11
12
  # Sample tab configuration
12
13
  sample_tabs = {
13
14
  "🤖Bot1": ("~/code/bytesense/bithence", "~/scripts/fire -mO go1.py bot1 --kw create_new_bot True"),
14
- "🤖Bot2": ("~/code/bytesense/bithence", "~/scripts/fire -mO go2.py bot2 --kw create_new_bot True"),
15
+ "🤖Bot2": ("~/code/bytesense/bithence", "~/scripts/fire -mO go2.py bot2 --kw create_new_bot True"),
15
16
  "📊Monitor": ("~", "htop"),
16
- "📝Logs": ("/var/log", "tail -f /var/log/app.log")
17
+ "📝Logs": ("/var/log", "tail -f /var/log/app.log"),
17
18
  }
18
-
19
+
19
20
  # Replace 'myserver' with an actual SSH config alias
20
21
  remote_name = "myserver" # This should be in ~/.ssh/config
21
22
  session_name = "test_remote_session"
22
-
23
+
23
24
  try:
24
25
  # Create layout using the remote generator
25
26
  generator = ZellijRemoteLayoutGenerator(remote_name=remote_name, session_name_prefix=session_name)
26
-
27
+
27
28
  # Create layout file
28
29
  layout_path = generator.create_zellij_layout(sample_tabs)
29
30
  print(f"✅ Remote layout created successfully: {layout_path}")
30
-
31
+
31
32
  # Preview the layout content
32
33
  preview = generator.get_layout_preview(sample_tabs)
33
34
  print(f"📄 Layout preview:\n{preview}")
34
-
35
+
35
36
  # Check status using the modular components
36
37
  print(f"\n🔍 Checking command status on remote '{remote_name}':")
37
38
  generator.print_status_report()
38
-
39
+
39
40
  # The individual components can also be used directly:
40
- print(f"\n🔧 Direct component usage examples:")
41
-
41
+ print("\n🔧 Direct component usage examples:")
42
+
42
43
  # Use remote executor directly
43
44
  print(f"Remote executor: {generator.remote_executor.remote_name}")
44
-
45
- # Use layout generator directly
45
+
46
+ # Use layout generator directly
46
47
  layout_content = generator.layout_generator.generate_layout_content(sample_tabs)
47
48
  print(f"Layout content length: {len(layout_content)} characters")
48
-
49
+
49
50
  # Use process monitor directly
50
51
  status = generator.process_monitor.check_all_commands_status(sample_tabs)
51
52
  print(f"Command status check completed for {len(status)} commands")
52
-
53
+
53
54
  print("\n✅ All modular components working correctly!")
54
-
55
+
55
56
  # Uncomment these to actually start and attach to the session:
56
57
  # start_result = generator.start_zellij_session()
57
58
  # print(f"Session start result: {start_result}")
58
59
  # generator.attach_to_session()
59
-
60
+
60
61
  except Exception as e:
61
62
  print(f"❌ Error: {e}")
62
63
 
64
+
63
65
  if __name__ == "__main__":
64
66
  example_usage()
@@ -2,6 +2,7 @@
2
2
  """
3
3
  Zellij layout generation utilities for creating KDL layout files.
4
4
  """
5
+
5
6
  import shlex
6
7
  import random
7
8
  import string
@@ -17,7 +18,7 @@ console = Console()
17
18
 
18
19
  class LayoutGenerator:
19
20
  """Handles generation of Zellij KDL layout files."""
20
-
21
+
21
22
  LAYOUT_TEMPLATE = """layout {
22
23
  default_tab_template {
23
24
  // the default zellij tab-bar and status bar plugins
@@ -27,39 +28,39 @@ class LayoutGenerator:
27
28
  children
28
29
  }
29
30
  """
30
-
31
+
31
32
  @staticmethod
32
33
  def generate_random_suffix(length: int = 8) -> str:
33
34
  """Generate a random string suffix for unique layout file names."""
34
- return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
35
-
35
+ return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
36
+
36
37
  @staticmethod
37
38
  def parse_command(command: str) -> Tuple[str, List[str]]:
38
39
  """Parse a command string into command and arguments."""
39
40
  try:
40
41
  parts = shlex.split(command)
41
- if not parts:
42
+ if not parts:
42
43
  raise ValueError("Empty command provided")
43
44
  return parts[0], parts[1:] if len(parts) > 1 else []
44
45
  except ValueError as e:
45
46
  logger.error(f"Error parsing command '{command}': {e}")
46
47
  parts = command.split()
47
48
  return parts[0] if parts else "", parts[1:] if len(parts) > 1 else []
48
-
49
+
49
50
  @staticmethod
50
51
  def format_args_for_kdl(args: List[str]) -> str:
51
52
  """Format command arguments for KDL syntax."""
52
- if not args:
53
+ if not args:
53
54
  return ""
54
55
  formatted_args = []
55
56
  for arg in args:
56
- if ' ' in arg or '"' in arg or "'" in arg:
57
+ if " " in arg or '"' in arg or "'" in arg:
57
58
  escaped_arg = arg.replace('"', '\\"')
58
59
  formatted_args.append(f'"{escaped_arg}"')
59
60
  else:
60
61
  formatted_args.append(f'"{arg}"')
61
62
  return " ".join(formatted_args)
62
-
63
+
63
64
  @staticmethod
64
65
  def create_tab_section(tab_name: str, cwd: str, command: str) -> str:
65
66
  """Create a KDL tab section for the layout."""
@@ -67,60 +68,58 @@ class LayoutGenerator:
67
68
  args_str = LayoutGenerator.format_args_for_kdl(args)
68
69
  tab_cwd = cwd or "~"
69
70
  escaped_tab_name = tab_name.replace('"', '\\"')
70
-
71
+
71
72
  tab_section = f' tab name="{escaped_tab_name}" cwd="{tab_cwd}" {{\n'
72
73
  tab_section += f' pane command="{cmd}" {{\n'
73
- if args_str:
74
- tab_section += f' args {args_str}\n'
75
- tab_section += ' }\n }\n'
74
+ if args_str:
75
+ tab_section += f" args {args_str}\n"
76
+ tab_section += " }\n }\n"
76
77
  return tab_section
77
-
78
+
78
79
  @staticmethod
79
80
  def validate_tab_config(tab_config: Dict[str, Tuple[str, str]]) -> None:
80
81
  """Validate tab configuration format and content."""
81
- if not tab_config:
82
+ if not tab_config:
82
83
  raise ValueError("Tab configuration cannot be empty")
83
84
  for tab_name, (cwd, command) in tab_config.items():
84
- if not tab_name.strip():
85
+ if not tab_name.strip():
85
86
  raise ValueError(f"Invalid tab name: {tab_name}")
86
- if not command.strip():
87
+ if not command.strip():
87
88
  raise ValueError(f"Invalid command for tab '{tab_name}': {command}")
88
- if not cwd.strip():
89
+ if not cwd.strip():
89
90
  raise ValueError(f"Invalid cwd for tab '{tab_name}': {cwd}")
90
-
91
+
91
92
  def generate_layout_content(self, tab_config: Dict[str, Tuple[str, str]]) -> str:
92
93
  """Generate complete KDL layout content."""
93
94
  self.validate_tab_config(tab_config)
94
-
95
+
95
96
  layout_content = self.LAYOUT_TEMPLATE
96
97
  for tab_name, (cwd, command) in tab_config.items():
97
98
  layout_content += "\n" + self.create_tab_section(tab_name, cwd, command)
98
99
  layout_content += "\n}\n"
99
-
100
+
100
101
  return layout_content
101
-
102
- def create_layout_file(self, tab_config: Dict[str, Tuple[str, str]],
103
- output_dir: Path, session_name: str) -> str:
102
+
103
+ def create_layout_file(self, tab_config: Dict[str, Tuple[str, str]], output_dir: Path, session_name: str) -> str:
104
104
  """Create a layout file and return its absolute path."""
105
105
  self.validate_tab_config(tab_config)
106
-
106
+
107
107
  # Generate unique suffix for this layout
108
108
  random_suffix = self.generate_random_suffix()
109
109
  layout_content = self.generate_layout_content(tab_config)
110
-
110
+
111
111
  try:
112
112
  # Create output directory if it doesn't exist
113
113
  output_dir.mkdir(parents=True, exist_ok=True)
114
114
  layout_file = output_dir / f"zellij_layout_{session_name}_{random_suffix}.kdl"
115
-
115
+
116
116
  # Write layout file
117
- with open(layout_file, 'w', encoding='utf-8') as f:
118
- f.write(layout_content)
119
-
117
+ layout_file.write_text(layout_content, encoding="utf-8")
118
+
120
119
  # Enhanced Rich logging
121
120
  console.print(f"[bold green]✅ Zellij layout file created:[/bold green] [cyan]{layout_file.absolute()}[/cyan]")
122
121
  return str(layout_file.absolute())
123
-
122
+
124
123
  except OSError as e:
125
124
  logger.error(f"Failed to create layout file: {e}")
126
125
  raise