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.
- machineconfig/cluster/cloud_manager.py +22 -29
- machineconfig/cluster/data_transfer.py +2 -3
- machineconfig/cluster/distribute.py +0 -2
- machineconfig/cluster/file_manager.py +4 -5
- machineconfig/cluster/job_params.py +1 -4
- machineconfig/cluster/loader_runner.py +8 -11
- machineconfig/cluster/remote_machine.py +4 -5
- machineconfig/cluster/script_execution.py +2 -2
- machineconfig/cluster/script_notify_upon_completion.py +0 -1
- machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +4 -6
- machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
- machineconfig/cluster/sessions_managers/enhanced_command_runner.py +35 -75
- machineconfig/cluster/sessions_managers/wt_local.py +113 -185
- machineconfig/cluster/sessions_managers/wt_local_manager.py +127 -197
- machineconfig/cluster/sessions_managers/wt_remote.py +60 -67
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +110 -149
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +61 -64
- machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +72 -172
- machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +27 -60
- machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +58 -137
- machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +46 -74
- machineconfig/cluster/sessions_managers/zellij_local.py +91 -147
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +165 -190
- machineconfig/cluster/sessions_managers/zellij_remote.py +51 -58
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +40 -46
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +19 -17
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +30 -31
- machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +64 -134
- machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +7 -11
- machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +27 -55
- machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +14 -13
- machineconfig/cluster/templates/cli_click.py +0 -1
- machineconfig/cluster/templates/cli_gooey.py +0 -2
- machineconfig/cluster/templates/cli_trogon.py +0 -1
- machineconfig/cluster/templates/run_cloud.py +0 -1
- machineconfig/cluster/templates/run_cluster.py +0 -1
- machineconfig/cluster/templates/run_remote.py +0 -1
- machineconfig/cluster/templates/utils.py +27 -11
- machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
- machineconfig/jobs/python/check_installations.py +9 -9
- machineconfig/jobs/python/create_bootable_media.py +0 -2
- machineconfig/jobs/python/python_cargo_build_share.py +2 -2
- machineconfig/jobs/python/python_ve_symlink.py +9 -11
- machineconfig/jobs/python/tasks.py +0 -1
- machineconfig/jobs/python/vscode/api.py +5 -5
- machineconfig/jobs/python/vscode/link_ve.py +20 -21
- machineconfig/jobs/python/vscode/select_interpreter.py +28 -29
- machineconfig/jobs/python/vscode/sync_code.py +14 -18
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_custom_installers/archive/ngrok.py +15 -15
- machineconfig/jobs/python_custom_installers/dev/aider.py +10 -18
- machineconfig/jobs/python_custom_installers/dev/alacritty.py +12 -21
- machineconfig/jobs/python_custom_installers/dev/brave.py +13 -22
- machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +13 -20
- machineconfig/jobs/python_custom_installers/dev/code.py +17 -24
- machineconfig/jobs/python_custom_installers/dev/cursor.py +10 -21
- machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +12 -11
- machineconfig/jobs/python_custom_installers/dev/espanso.py +19 -23
- machineconfig/jobs/python_custom_installers/dev/goes.py +9 -16
- machineconfig/jobs/python_custom_installers/dev/lvim.py +13 -21
- machineconfig/jobs/python_custom_installers/dev/nerdfont.py +15 -22
- machineconfig/jobs/python_custom_installers/dev/redis.py +15 -23
- machineconfig/jobs/python_custom_installers/dev/wezterm.py +15 -22
- machineconfig/jobs/python_custom_installers/dev/winget.py +32 -50
- machineconfig/jobs/python_custom_installers/docker.py +15 -24
- machineconfig/jobs/python_custom_installers/gh.py +18 -26
- machineconfig/jobs/python_custom_installers/hx.py +33 -17
- machineconfig/jobs/python_custom_installers/warp-cli.py +15 -23
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_generic_installers/config.json +412 -389
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/jobs/python_windows_installers/dev/config.json +1 -1
- machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
- machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
- machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
- machineconfig/jobs/windows/start_terminal.ps1 +1 -1
- machineconfig/logger.py +50 -0
- machineconfig/profile/create.py +50 -36
- machineconfig/profile/create_hardlinks.py +33 -26
- machineconfig/profile/shell.py +87 -60
- machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/cloud/init.sh +2 -2
- machineconfig/scripts/linux/checkout_versions +1 -1
- machineconfig/scripts/linux/choose_wezterm_theme +1 -1
- machineconfig/scripts/linux/cloud_copy +1 -1
- machineconfig/scripts/linux/cloud_manager +1 -1
- machineconfig/scripts/linux/cloud_mount +1 -1
- machineconfig/scripts/linux/cloud_repo_sync +1 -1
- machineconfig/scripts/linux/cloud_sync +1 -1
- machineconfig/scripts/linux/croshell +1 -1
- machineconfig/scripts/linux/devops +3 -5
- machineconfig/scripts/linux/fire +2 -1
- machineconfig/scripts/linux/fire_agents +3 -3
- machineconfig/scripts/linux/ftpx +1 -1
- machineconfig/scripts/linux/gh_models +1 -1
- machineconfig/scripts/linux/kill_process +1 -1
- machineconfig/scripts/linux/mcinit +2 -2
- machineconfig/scripts/linux/repos +1 -1
- machineconfig/scripts/linux/scheduler +1 -1
- machineconfig/scripts/linux/start_slidev +1 -1
- machineconfig/scripts/linux/start_terminals +1 -1
- machineconfig/scripts/linux/url2md +1 -1
- machineconfig/scripts/linux/warp-cli.sh +122 -0
- machineconfig/scripts/linux/wifi_conn +1 -1
- machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__init__.py +0 -0
- machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/chatmodes/Thinking-Beast-Mode.chatmode.md +337 -0
- machineconfig/scripts/python/ai/chatmodes/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +644 -0
- machineconfig/scripts/python/ai/chatmodes/deepResearch.chatmode.md +81 -0
- machineconfig/scripts/python/ai/configs/.gemini/settings.json +81 -0
- machineconfig/scripts/python/ai/generate_files.py +84 -0
- machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +45 -0
- machineconfig/scripts/python/ai/mcinit.py +107 -0
- machineconfig/scripts/python/ai/prompts/allLintersAndTypeCheckers.prompt.md +5 -0
- machineconfig/scripts/python/ai/prompts/research-report-skeleton.prompt.md +38 -0
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +52 -0
- machineconfig/scripts/python/archive/tmate_conn.py +5 -5
- machineconfig/scripts/python/archive/tmate_start.py +3 -3
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/cloud_copy.py +20 -19
- machineconfig/scripts/python/cloud_mount.py +10 -8
- machineconfig/scripts/python/cloud_repo_sync.py +15 -15
- machineconfig/scripts/python/cloud_sync.py +1 -1
- machineconfig/scripts/python/croshell.py +18 -16
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/devops_add_identity.py +9 -7
- machineconfig/scripts/python/devops_add_ssh_key.py +19 -19
- machineconfig/scripts/python/devops_backup_retrieve.py +14 -14
- machineconfig/scripts/python/devops_devapps_install.py +3 -3
- machineconfig/scripts/python/devops_update_repos.py +141 -53
- machineconfig/scripts/python/dotfile.py +3 -3
- machineconfig/scripts/python/fire_agents.py +202 -41
- machineconfig/scripts/python/fire_jobs.py +20 -21
- machineconfig/scripts/python/ftpx.py +4 -3
- machineconfig/scripts/python/gh_models.py +94 -94
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
- machineconfig/scripts/python/helpers/cloud_helpers.py +3 -3
- machineconfig/scripts/python/helpers/helpers2.py +3 -3
- machineconfig/scripts/python/helpers/helpers4.py +8 -7
- machineconfig/scripts/python/helpers/helpers5.py +7 -7
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
- machineconfig/scripts/python/mount_nfs.py +4 -3
- machineconfig/scripts/python/mount_nw_drive.py +4 -4
- machineconfig/scripts/python/mount_ssh.py +4 -3
- machineconfig/scripts/python/repos.py +9 -9
- machineconfig/scripts/python/scheduler.py +1 -1
- machineconfig/scripts/python/start_slidev.py +9 -8
- machineconfig/scripts/python/start_terminals.py +1 -1
- machineconfig/scripts/python/viewer.py +40 -40
- machineconfig/scripts/python/wifi_conn.py +65 -66
- machineconfig/scripts/python/wsl_windows_transfer.py +2 -2
- machineconfig/scripts/windows/checkout_version.ps1 +1 -3
- machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
- machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
- machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
- machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
- machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
- machineconfig/scripts/windows/croshell.ps1 +2 -2
- machineconfig/scripts/windows/devops.ps1 +1 -4
- machineconfig/scripts/windows/dotfile.ps1 +1 -3
- machineconfig/scripts/windows/fire.ps1 +1 -1
- machineconfig/scripts/windows/ftpx.ps1 +2 -2
- machineconfig/scripts/windows/gpt.ps1 +1 -1
- machineconfig/scripts/windows/kill_process.ps1 +1 -2
- machineconfig/scripts/windows/mcinit.ps1 +2 -2
- machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
- machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/pomodoro.ps1 +1 -1
- machineconfig/scripts/windows/py2exe.ps1 +1 -3
- machineconfig/scripts/windows/repos.ps1 +1 -1
- machineconfig/scripts/windows/scheduler.ps1 +1 -1
- machineconfig/scripts/windows/snapshot.ps1 +2 -2
- machineconfig/scripts/windows/start_slidev.ps1 +1 -1
- machineconfig/scripts/windows/start_terminals.ps1 +1 -1
- machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
- machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
- machineconfig/settings/lf/linux/lfrc +1 -1
- machineconfig/settings/linters/.ruff.toml +2 -2
- machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
- machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
- machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
- machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +71 -71
- machineconfig/settings/shells/wt/settings.json +8 -8
- machineconfig/settings/svim/linux/init.toml +1 -1
- machineconfig/settings/svim/windows/init.toml +1 -1
- machineconfig/setup_linux/web_shortcuts/croshell.sh +0 -54
- machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
- machineconfig/setup_linux/web_shortcuts/tmp.sh +2 -0
- machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
- machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
- machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
- machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +75 -18
- machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +52 -42
- machineconfig/utils/ai/browser_user_wrapper.py +5 -5
- machineconfig/utils/ai/generate_file_checklist.py +19 -22
- machineconfig/utils/ai/url2md.py +5 -3
- machineconfig/utils/cloud/onedrive/setup_oauth.py +5 -4
- machineconfig/utils/cloud/onedrive/transaction.py +192 -227
- machineconfig/utils/code.py +71 -43
- machineconfig/utils/installer.py +77 -85
- machineconfig/utils/installer_utils/installer_abc.py +29 -17
- machineconfig/utils/installer_utils/installer_class.py +188 -83
- machineconfig/utils/io_save.py +3 -15
- machineconfig/utils/links.py +22 -11
- machineconfig/utils/notifications.py +197 -0
- machineconfig/utils/options.py +38 -25
- machineconfig/utils/path.py +18 -6
- machineconfig/utils/path_reduced.py +637 -316
- machineconfig/utils/procs.py +69 -63
- machineconfig/utils/scheduling.py +11 -13
- machineconfig/utils/ssh.py +351 -0
- machineconfig/utils/terminal.py +225 -0
- machineconfig/utils/utils.py +13 -12
- machineconfig/utils/utils2.py +43 -10
- machineconfig/utils/utils5.py +242 -46
- machineconfig/utils/ve.py +11 -6
- {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/METADATA +15 -9
- {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/RECORD +232 -235
- machineconfig/cluster/self_ssh.py +0 -57
- machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python/archive/python_tools.txt +0 -12
- machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
- machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/jobs/python_generic_installers/update.py +0 -3
- machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
- machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
- machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/linux/activate_ve +0 -87
- machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
- machineconfig/scripts/python/ai/init.py +0 -56
- machineconfig/scripts/python/ai/rules/python/dev.md +0 -31
- machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
- machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
- machineconfig/scripts/windows/activate_ve.ps1 +0 -54
- {machineconfig-1.97.dist-info → machineconfig-2.1.dist-info}/WHEEL +0 -0
- {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(
|
|
120
|
-
file_path_obj = file_path_obj.with_suffix(
|
|
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
|
-
|
|
129
|
-
|
|
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]) ->
|
|
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(
|
|
140
|
-
file_path = file_path.with_suffix(
|
|
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,
|
|
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(
|
|
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[
|
|
156
|
-
if
|
|
157
|
-
session_name_prefix =
|
|
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[
|
|
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[
|
|
165
|
-
instance.tab_config = data[
|
|
166
|
-
instance.layout_path = data[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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) ->
|
|
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,
|
|
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,
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
logger.info(f"✅ Deleted session: {session_id}")
|
|
185
179
|
return True
|
|
186
180
|
except Exception as e:
|
|
187
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
75
|
-
tab_section +=
|
|
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
|
-
|
|
118
|
-
|
|
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
|