machineconfig 5.21__py3-none-any.whl → 5.23__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/sessions_managers/helpers/zellij_local_helper.py +298 -0
- machineconfig/cluster/sessions_managers/helpers/zellij_local_helper_restart.py +77 -0
- machineconfig/cluster/sessions_managers/helpers/zellij_local_helper_with_panes.py +228 -0
- machineconfig/cluster/sessions_managers/helpers/zellij_local_manager_helper.py +165 -0
- machineconfig/cluster/sessions_managers/wt_local.py +100 -75
- machineconfig/cluster/sessions_managers/wt_local_manager.py +17 -21
- machineconfig/cluster/sessions_managers/wt_remote.py +51 -43
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +16 -8
- machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +6 -19
- machineconfig/cluster/sessions_managers/zellij_local.py +79 -371
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +20 -168
- machineconfig/cluster/sessions_managers/zellij_remote.py +38 -39
- machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -10
- machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -1
- machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +5 -20
- machineconfig/profile/shell.py +1 -1
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.ps1 +17 -17
- machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +17 -17
- machineconfig/scripts/python/ai/solutions/copilot/instructions/python/dev.instructions.md +1 -1
- machineconfig/scripts/python/ai/solutions/copilot/prompts/pyright_fix.md +16 -0
- machineconfig/scripts/python/ai/solutions/generic.py +15 -4
- machineconfig/scripts/python/cloud_repo_sync.py +26 -25
- machineconfig/scripts/python/count_lines.py +6 -6
- machineconfig/scripts/python/croshell.py +2 -4
- machineconfig/scripts/python/devops.py +7 -2
- machineconfig/scripts/python/devops_status.py +521 -0
- machineconfig/scripts/python/devops_update_repos.py +1 -1
- machineconfig/scripts/python/fire_agents_help_launch.py +6 -1
- machineconfig/scripts/python/fire_jobs_args_helper.py +4 -1
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +0 -43
- machineconfig/scripts/python/onetimeshare.py +0 -1
- machineconfig/scripts/windows/share_smb.ps1 +0 -6
- machineconfig/setup_linux/repos.sh +1 -29
- machineconfig/setup_windows/repos.ps1 +0 -12
- machineconfig/utils/files/read.py +4 -6
- machineconfig/utils/notifications.py +1 -1
- machineconfig/utils/source_of_truth.py +1 -1
- machineconfig/utils/ssh.py +2 -13
- {machineconfig-5.21.dist-info → machineconfig-5.23.dist-info}/METADATA +1 -1
- {machineconfig-5.21.dist-info → machineconfig-5.23.dist-info}/RECORD +43 -39
- machineconfig/cluster/sessions_managers/ffile.py +0 -4
- machineconfig/scripts/python/ai/solutions/copilot/prompts/allLintersAndTypeCheckers.prompt.md +0 -5
- {machineconfig-5.21.dist-info → machineconfig-5.23.dist-info}/WHEEL +0 -0
- {machineconfig-5.21.dist-info → machineconfig-5.23.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.21.dist-info → machineconfig-5.23.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Optional
|
|
9
|
+
|
|
10
|
+
from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import ActiveSessionInfo, StartResult
|
|
11
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from machineconfig.cluster.sessions_managers.zellij_local import ZellijLayoutGenerator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
TMP_SERIALIZATION_DIR = Path.home() / "tmp_results" / "zellij_sessions" / "serialized"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def list_saved_sessions() -> list[str]:
|
|
22
|
+
"""List all saved session IDs."""
|
|
23
|
+
if not TMP_SERIALIZATION_DIR.exists():
|
|
24
|
+
return []
|
|
25
|
+
sessions = []
|
|
26
|
+
for item in TMP_SERIALIZATION_DIR.iterdir():
|
|
27
|
+
if item.is_dir() and (item / "metadata.json").exists():
|
|
28
|
+
sessions.append(item.name)
|
|
29
|
+
return sorted(sessions)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def delete_session(session_id: str) -> bool:
|
|
33
|
+
"""Delete a saved session."""
|
|
34
|
+
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
35
|
+
if not session_dir.exists():
|
|
36
|
+
logger.warning(f"Session directory not found: {session_dir}")
|
|
37
|
+
return False
|
|
38
|
+
try:
|
|
39
|
+
shutil.rmtree(session_dir)
|
|
40
|
+
logger.info(f"✅ Deleted session: {session_id}")
|
|
41
|
+
return True
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"Failed to delete session {session_id}: {e}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_all_session_names(managers: list["ZellijLayoutGenerator"]) -> list[str]:
|
|
48
|
+
"""Get all managed session names."""
|
|
49
|
+
return [manager.session_name for manager in managers]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def attach_to_session(managers: list["ZellijLayoutGenerator"], session_name: Optional[str]) -> str:
|
|
53
|
+
"""
|
|
54
|
+
Generate command to attach to a specific session or list attachment commands for all.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
managers: List of ZellijLayoutGenerator instances
|
|
58
|
+
session_name: Specific session to attach to, or None for all sessions
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Command string to attach to session(s)
|
|
62
|
+
"""
|
|
63
|
+
if session_name:
|
|
64
|
+
for manager in managers:
|
|
65
|
+
if manager.session_name == session_name:
|
|
66
|
+
return f"zellij attach {session_name}"
|
|
67
|
+
raise ValueError(f"Session '{session_name}' not found")
|
|
68
|
+
else:
|
|
69
|
+
commands: list[str] = []
|
|
70
|
+
for manager in managers:
|
|
71
|
+
commands.append(f"# Attach to session '{manager.session_name}':")
|
|
72
|
+
commands.append(f"zellij attach {manager.session_name}")
|
|
73
|
+
commands.append("")
|
|
74
|
+
return "\n".join(commands)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def list_active_sessions(managers: list["ZellijLayoutGenerator"]) -> list[ActiveSessionInfo]:
|
|
78
|
+
"""List currently active zellij sessions managed by this instance."""
|
|
79
|
+
active_sessions: list[ActiveSessionInfo] = []
|
|
80
|
+
try:
|
|
81
|
+
result = subprocess.run(["zellij", "list-sessions"], capture_output=True, text=True, timeout=10)
|
|
82
|
+
if result.returncode == 0:
|
|
83
|
+
all_sessions = result.stdout.strip().split("\n") if result.stdout.strip() else []
|
|
84
|
+
for manager in managers:
|
|
85
|
+
session_name = manager.session_name
|
|
86
|
+
is_active = any(session_name in session for session in all_sessions)
|
|
87
|
+
tab_info = []
|
|
88
|
+
tab_count = 0
|
|
89
|
+
if manager.layout_config:
|
|
90
|
+
tab_count = len(manager.layout_config["layoutTabs"])
|
|
91
|
+
tab_info = [tab["tabName"] for tab in manager.layout_config["layoutTabs"]]
|
|
92
|
+
active_sessions.append({"session_name": session_name, "is_active": is_active, "tab_count": tab_count, "tabs": tab_info})
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Error listing active sessions: {e}")
|
|
95
|
+
return active_sessions
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def save_manager(session_layouts: list[LayoutConfig], managers: list["ZellijLayoutGenerator"], session_name_prefix: str, session_id: Optional[str]) -> str:
|
|
99
|
+
"""Save the manager state to disk."""
|
|
100
|
+
if session_id is None:
|
|
101
|
+
import uuid
|
|
102
|
+
session_id = str(uuid.uuid4())[:8]
|
|
103
|
+
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
104
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
config_file = session_dir / "session_layouts.json"
|
|
106
|
+
text = json.dumps(session_layouts, indent=2, ensure_ascii=False)
|
|
107
|
+
config_file.write_text(text, encoding="utf-8")
|
|
108
|
+
metadata = {"session_name_prefix": session_name_prefix, "created_at": str(datetime.now()), "num_managers": len(managers), "sessions": [item["layoutName"] for item in session_layouts], "manager_type": "ZellijLocalManager"}
|
|
109
|
+
metadata_file = session_dir / "metadata.json"
|
|
110
|
+
text = json.dumps(metadata, indent=2, ensure_ascii=False)
|
|
111
|
+
metadata_file.write_text(text, encoding="utf-8")
|
|
112
|
+
managers_dir = session_dir / "managers"
|
|
113
|
+
managers_dir.mkdir(exist_ok=True)
|
|
114
|
+
for i, manager in enumerate(managers):
|
|
115
|
+
manager_data = {"session_name": manager.session_name, "layout_config": manager.layout_config, "layout_path": manager.layout_path}
|
|
116
|
+
manager_file = managers_dir / f"manager_{i}_{manager.session_name}.json"
|
|
117
|
+
text = json.dumps(manager_data, indent=2, ensure_ascii=False)
|
|
118
|
+
manager_file.write_text(text, encoding="utf-8")
|
|
119
|
+
logger.info(f"✅ Saved ZellijLocalManager session to: {session_dir}")
|
|
120
|
+
return session_id
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def load_manager(session_id: str):
|
|
124
|
+
"""Load a saved manager state from disk and return the data needed to reconstruct ZellijLocalManager."""
|
|
125
|
+
from machineconfig.cluster.sessions_managers.zellij_local import ZellijLayoutGenerator
|
|
126
|
+
|
|
127
|
+
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
128
|
+
if not session_dir.exists():
|
|
129
|
+
raise FileNotFoundError(f"Session directory not found: {session_dir}")
|
|
130
|
+
config_file = session_dir / "session_layouts.json"
|
|
131
|
+
if not config_file.exists():
|
|
132
|
+
raise FileNotFoundError(f"Configuration file not found: {config_file}")
|
|
133
|
+
text = config_file.read_text(encoding="utf-8")
|
|
134
|
+
session_layouts = json.loads(text)
|
|
135
|
+
managers = []
|
|
136
|
+
managers_dir = session_dir / "managers"
|
|
137
|
+
if managers_dir.exists():
|
|
138
|
+
manager_files = sorted(managers_dir.glob("manager_*.json"))
|
|
139
|
+
for manager_file in manager_files:
|
|
140
|
+
try:
|
|
141
|
+
text = manager_file.read_text(encoding="utf-8")
|
|
142
|
+
manager_data = json.loads(text)
|
|
143
|
+
manager = ZellijLayoutGenerator(layout_config=manager_data["layout_config"], session_name=manager_data["session_name"])
|
|
144
|
+
manager.layout_path = manager_data["layout_path"]
|
|
145
|
+
managers.append(manager)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.warning(f"Failed to load manager from {manager_file}: {e}")
|
|
148
|
+
logger.info(f"✅ Loaded ZellijLocalManager session from: {session_dir}")
|
|
149
|
+
return session_layouts, managers
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def kill_all_sessions(managers: list["ZellijLayoutGenerator"]) -> dict[str, StartResult]:
|
|
153
|
+
"""Kill all managed zellij sessions."""
|
|
154
|
+
results: dict[str, StartResult] = {}
|
|
155
|
+
for manager in managers:
|
|
156
|
+
try:
|
|
157
|
+
session_name = manager.session_name
|
|
158
|
+
cmd = f"zellij delete-session --force {session_name}"
|
|
159
|
+
logger.info(f"Killing session '{session_name}'")
|
|
160
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
|
|
161
|
+
results[session_name] = {"success": result.returncode == 0, "message": "Session killed" if result.returncode == 0 else result.stderr}
|
|
162
|
+
except Exception as e:
|
|
163
|
+
key = getattr(manager, "session_name", None) or f"manager_{managers.index(manager)}"
|
|
164
|
+
results[key] = {"success": False, "error": str(e)}
|
|
165
|
+
return results
|
|
@@ -13,27 +13,30 @@ import random
|
|
|
13
13
|
import string
|
|
14
14
|
import json
|
|
15
15
|
import platform
|
|
16
|
-
from typing import
|
|
16
|
+
from typing import Optional, Any
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
import logging
|
|
19
|
+
|
|
19
20
|
from rich.console import Console
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
from rich.table import Table
|
|
20
23
|
|
|
21
24
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
22
25
|
|
|
23
26
|
logging.basicConfig(level=logging.INFO)
|
|
24
27
|
logger = logging.getLogger(__name__)
|
|
25
28
|
console = Console()
|
|
26
|
-
TMP_LAYOUT_DIR = Path.home().joinpath("tmp_results", "session_manager", "wt", "layout_manager")
|
|
27
29
|
|
|
28
30
|
# Check if we're on Windows
|
|
29
31
|
IS_WINDOWS = platform.system().lower() == "windows"
|
|
30
32
|
POWERSHELL_CMD = "powershell" if IS_WINDOWS else "pwsh" # Use pwsh on non-Windows systems
|
|
33
|
+
TMP_LAYOUT_DIR = Path.home() / "tmp_results" / "wt_layouts"
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class WTLayoutGenerator:
|
|
34
|
-
def __init__(self):
|
|
35
|
-
self.session_name:
|
|
36
|
-
self.layout_config:
|
|
37
|
+
def __init__(self, layout_config: LayoutConfig, session_name: str):
|
|
38
|
+
self.session_name: str = session_name
|
|
39
|
+
self.layout_config: LayoutConfig = layout_config.copy()
|
|
37
40
|
self.script_path: Optional[str] = None # Store the full path to the PowerShell script
|
|
38
41
|
|
|
39
42
|
@staticmethod
|
|
@@ -42,7 +45,7 @@ class WTLayoutGenerator:
|
|
|
42
45
|
return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
|
43
46
|
|
|
44
47
|
@staticmethod
|
|
45
|
-
def _parse_command(command: str) -> tuple[str,
|
|
48
|
+
def _parse_command(command: str) -> tuple[str, list[str]]:
|
|
46
49
|
try:
|
|
47
50
|
parts = shlex.split(command)
|
|
48
51
|
if not parts:
|
|
@@ -77,41 +80,35 @@ class WTLayoutGenerator:
|
|
|
77
80
|
if not tab["startDir"].strip():
|
|
78
81
|
raise ValueError(f"Invalid startDir for tab '{tab['tabName']}': {tab['startDir']}")
|
|
79
82
|
|
|
80
|
-
def
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
def create_layout_file(self) -> bool:
|
|
84
|
+
"""Create Windows Terminal layout file and return success status."""
|
|
85
|
+
WTLayoutGenerator._validate_layout_config(self.layout_config)
|
|
86
|
+
tab_count = len(self.layout_config['layoutTabs'])
|
|
87
|
+
layout_name = self.layout_config['layoutName']
|
|
88
|
+
console.print(f"[bold cyan]📋 Creating Windows Terminal layout[/bold cyan] [bright_green]'{layout_name}' with {tab_count} tabs[/bright_green]")
|
|
89
|
+
|
|
90
|
+
for tab in self.layout_config['layoutTabs']:
|
|
91
|
+
console.print(f" [yellow]→[/yellow] [bold]{tab['tabName']}[/bold] [dim]in[/dim] [blue]{tab['startDir']}[/blue]")
|
|
87
92
|
|
|
88
93
|
# Generate Windows Terminal command
|
|
89
|
-
wt_command = self._generate_wt_command_string(layout_config, self.session_name)
|
|
90
|
-
|
|
91
|
-
try:
|
|
92
|
-
random_suffix = WTLayoutGenerator._generate_random_suffix(8)
|
|
93
|
-
if output_dir:
|
|
94
|
-
output_path = Path(output_dir)
|
|
95
|
-
output_path.mkdir(parents=True, exist_ok=True)
|
|
96
|
-
script_file = output_path / f"wt_layout_{random_suffix}.ps1"
|
|
97
|
-
else:
|
|
98
|
-
# Use the predefined TMP_LAYOUT_DIR for temporary files
|
|
99
|
-
TMP_LAYOUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
100
|
-
script_file = TMP_LAYOUT_DIR / f"wt_layout_{self.session_name}_{random_suffix}.ps1"
|
|
94
|
+
wt_command = self._generate_wt_command_string(self.layout_config, self.session_name)
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
random_suffix = WTLayoutGenerator._generate_random_suffix(8)
|
|
97
|
+
# Create PowerShell script content
|
|
98
|
+
script_content = f"""# Windows Terminal layout for {self.session_name}
|
|
104
99
|
# Generated with random suffix: {random_suffix}
|
|
105
100
|
{wt_command}
|
|
106
101
|
"""
|
|
107
|
-
|
|
102
|
+
# Write to file
|
|
103
|
+
random_suffix = WTLayoutGenerator._generate_random_suffix(8)
|
|
104
|
+
tmp_dir = Path(TMP_LAYOUT_DIR)
|
|
105
|
+
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
script_file = tmp_dir / f"wt_layout_{self.session_name}_{random_suffix}.ps1"
|
|
107
|
+
script_file.write_text(script_content, encoding="utf-8")
|
|
108
|
+
self.script_path = str(script_file.absolute())
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return self.script_path
|
|
112
|
-
except OSError as e:
|
|
113
|
-
logger.error(f"Failed to create PowerShell script: {e}")
|
|
114
|
-
raise
|
|
110
|
+
console.print(f"[bold green]✅ Layout created successfully:[/bold green] [cyan]{self.script_path}[/cyan]")
|
|
111
|
+
return True
|
|
115
112
|
|
|
116
113
|
def _generate_wt_command_string(self, layout_config: LayoutConfig, window_name: str) -> str:
|
|
117
114
|
"""Generate complete Windows Terminal command string."""
|
|
@@ -154,7 +151,7 @@ class WTLayoutGenerator:
|
|
|
154
151
|
WTLayoutGenerator._validate_layout_config(layout_config)
|
|
155
152
|
return self._generate_wt_command_string(layout_config, "preview")
|
|
156
153
|
|
|
157
|
-
def check_all_commands_status(self) ->
|
|
154
|
+
def check_all_commands_status(self) -> dict[str, dict[str, Any]]:
|
|
158
155
|
if not self.layout_config:
|
|
159
156
|
logger.warning("No layout config tracked. Make sure to create a layout first.")
|
|
160
157
|
return {}
|
|
@@ -167,7 +164,7 @@ class WTLayoutGenerator:
|
|
|
167
164
|
return status_report
|
|
168
165
|
|
|
169
166
|
@staticmethod
|
|
170
|
-
def check_wt_session_status(session_name: str) ->
|
|
167
|
+
def check_wt_session_status(session_name: str) -> dict[str, Any]:
|
|
171
168
|
try:
|
|
172
169
|
# Simplified Windows Terminal process check
|
|
173
170
|
ps_script = """
|
|
@@ -223,7 +220,7 @@ try {
|
|
|
223
220
|
return {"wt_running": False, "error": str(e), "session_name": session_name}
|
|
224
221
|
|
|
225
222
|
@staticmethod
|
|
226
|
-
def check_command_status(tab_name: str, layout_config: LayoutConfig) ->
|
|
223
|
+
def check_command_status(tab_name: str, layout_config: LayoutConfig) -> dict[str, Any]:
|
|
227
224
|
"""Check if a command is running by looking for processes."""
|
|
228
225
|
# Find the tab with the given name
|
|
229
226
|
tab_config = None
|
|
@@ -290,7 +287,7 @@ try {{
|
|
|
290
287
|
logger.error(f"Error checking command status for tab '{tab_name}': {e}")
|
|
291
288
|
return {"status": "error", "error": str(e), "running": False, "command": command, "tab_name": tab_name}
|
|
292
289
|
|
|
293
|
-
def get_status_report(self) ->
|
|
290
|
+
def get_status_report(self) -> dict[str, Any]:
|
|
294
291
|
"""Get status report for this layout including Windows Terminal and commands."""
|
|
295
292
|
wt_status = WTLayoutGenerator.check_wt_session_status(self.session_name or "default")
|
|
296
293
|
commands_status = self.check_all_commands_status()
|
|
@@ -311,64 +308,90 @@ try {{
|
|
|
311
308
|
commands = status["commands"]
|
|
312
309
|
summary = status["summary"]
|
|
313
310
|
|
|
314
|
-
print(
|
|
315
|
-
print("
|
|
316
|
-
print("=" * 80)
|
|
311
|
+
console.print()
|
|
312
|
+
console.print(Panel.fit("� WINDOWS TERMINAL LAYOUT STATUS REPORT", style="bold cyan"))
|
|
317
313
|
|
|
318
314
|
# Windows Terminal session status
|
|
319
|
-
print(f"🪟 Session: {self.session_name}")
|
|
320
315
|
if wt_session.get("wt_running", False):
|
|
321
316
|
if wt_session.get("session_exists", False):
|
|
322
317
|
session_windows = wt_session.get("session_windows", [])
|
|
323
318
|
all_windows = wt_session.get("all_windows", [])
|
|
324
|
-
print("✅ Windows Terminal is running")
|
|
325
|
-
print(f" Session windows: {len(session_windows)}")
|
|
326
|
-
print(f" Total WT windows: {len(all_windows)}")
|
|
319
|
+
console.print(f"[bold green]✅ Windows Terminal session[/bold green] [yellow]'{self.session_name}'[/yellow] [green]is running[/green]")
|
|
320
|
+
console.print(f" Session windows: {len(session_windows)}")
|
|
321
|
+
console.print(f" Total WT windows: {len(all_windows)}")
|
|
327
322
|
else:
|
|
328
|
-
print("⚠️ Windows Terminal is running but
|
|
323
|
+
console.print(f"[bold yellow]⚠️ Windows Terminal is running but session[/bold yellow] [yellow]'{self.session_name}'[/yellow] [yellow]not found[/yellow]")
|
|
329
324
|
else:
|
|
330
325
|
error_msg = wt_session.get("error", "Unknown error")
|
|
331
|
-
print(f"❌ Windows Terminal session issue: {error_msg}")
|
|
326
|
+
console.print(f"[bold red]❌ Windows Terminal session issue:[/bold red] [red]{error_msg}[/red]")
|
|
332
327
|
|
|
333
|
-
print()
|
|
328
|
+
console.print()
|
|
329
|
+
|
|
330
|
+
# Commands status table
|
|
331
|
+
table = Table(title="📋 COMMAND STATUS", show_header=True, header_style="bold magenta")
|
|
332
|
+
table.add_column("Tab", style="cyan", no_wrap=True)
|
|
333
|
+
table.add_column("Status", justify="center")
|
|
334
|
+
table.add_column("PID", justify="center", style="dim")
|
|
335
|
+
table.add_column("Command", style="green", max_width=40)
|
|
334
336
|
|
|
335
|
-
# Commands in this layout
|
|
336
|
-
print(f"📋 COMMANDS ({summary['running_commands']}/{summary['total_commands']} running):")
|
|
337
337
|
for tab_name, cmd_status in commands.items():
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
338
|
+
if cmd_status.get("running", False):
|
|
339
|
+
status_text = "[bold green]✅ Running[/bold green]"
|
|
340
|
+
processes = cmd_status.get("processes", [])
|
|
341
|
+
if processes:
|
|
342
|
+
proc = processes[0]
|
|
343
|
+
pid = str(proc.get("pid", "N/A"))
|
|
344
|
+
else:
|
|
345
|
+
pid = "N/A"
|
|
346
|
+
else:
|
|
347
|
+
status_text = "[bold red]❌ Stopped[/bold red]"
|
|
348
|
+
pid = "N/A"
|
|
342
349
|
|
|
343
|
-
|
|
350
|
+
command = cmd_status.get("command", "Unknown")
|
|
351
|
+
if len(command) > 35:
|
|
352
|
+
command = command[:32] + "..."
|
|
344
353
|
|
|
345
|
-
|
|
346
|
-
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
347
|
-
pid = proc.get("pid", "Unknown")
|
|
348
|
-
name = proc.get("name", "Unknown")
|
|
349
|
-
console.print(f" [dim]└─[/dim] PID {pid}: {name}")
|
|
350
|
-
print()
|
|
354
|
+
table.add_row(tab_name, status_text, pid, command)
|
|
351
355
|
|
|
352
|
-
print(
|
|
356
|
+
console.print(table)
|
|
357
|
+
console.print()
|
|
353
358
|
|
|
359
|
+
# Summary panel
|
|
360
|
+
summary_text = f"""[bold]Total commands:[/bold] {summary['total_commands']}
|
|
361
|
+
[green]Running:[/green] {summary['running_commands']}
|
|
362
|
+
[red]Stopped:[/red] {summary['stopped_commands']}
|
|
363
|
+
[yellow]Session healthy:[/yellow] {"✅" if summary['session_healthy'] else "❌"}"""
|
|
354
364
|
|
|
355
|
-
|
|
356
|
-
generator = WTLayoutGenerator()
|
|
357
|
-
return generator.create_wt_layout(layout_config, output_dir)
|
|
365
|
+
console.print(Panel(summary_text, title="📊 Summary", style="blue"))
|
|
358
366
|
|
|
359
367
|
|
|
360
|
-
def
|
|
368
|
+
def create_wt_layout(layout_config: LayoutConfig, output_path: str) -> str:
|
|
369
|
+
session_name = layout_config["layoutName"]
|
|
370
|
+
generator = WTLayoutGenerator(layout_config=layout_config, session_name=session_name)
|
|
371
|
+
generator.create_layout_file()
|
|
372
|
+
|
|
373
|
+
if generator.script_path is None:
|
|
374
|
+
raise RuntimeError("Script path was not set after creating layout file")
|
|
375
|
+
|
|
376
|
+
logger.info(f"Windows Terminal PowerShell script created: {generator.script_path}")
|
|
377
|
+
return generator.script_path
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def run_wt_layout(layout_config: LayoutConfig) -> None:
|
|
361
381
|
"""Create and run a Windows Terminal layout."""
|
|
362
|
-
|
|
363
|
-
|
|
382
|
+
session_name = layout_config["layoutName"]
|
|
383
|
+
generator = WTLayoutGenerator(layout_config=layout_config, session_name=session_name)
|
|
384
|
+
generator.create_layout_file()
|
|
385
|
+
|
|
386
|
+
if generator.script_path is None:
|
|
387
|
+
raise RuntimeError("Script path was not set after creating layout file")
|
|
364
388
|
|
|
365
389
|
# Execute the script
|
|
366
|
-
cmd = f'powershell -ExecutionPolicy Bypass -File "{script_path}"'
|
|
390
|
+
cmd = f'powershell -ExecutionPolicy Bypass -File "{generator.script_path}"'
|
|
367
391
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
368
392
|
|
|
369
393
|
if result.returncode == 0:
|
|
370
394
|
print(f"Windows Terminal layout is running @ {layout_config['layoutName']}")
|
|
371
|
-
return script_path
|
|
372
395
|
else:
|
|
373
396
|
logger.error(f"Failed to run Windows Terminal layout: {result.stderr}")
|
|
374
397
|
raise RuntimeError(f"Failed to run layout: {result.stderr}")
|
|
@@ -391,15 +414,17 @@ if __name__ == "__main__":
|
|
|
391
414
|
{"tabName": "Monitor", "startDir": "~", "command": "lf"}
|
|
392
415
|
]}
|
|
393
416
|
try:
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
417
|
+
session_name = sample_layout["layoutName"]
|
|
418
|
+
generator = WTLayoutGenerator(layout_config=sample_layout, session_name=session_name)
|
|
419
|
+
generator.create_layout_file()
|
|
420
|
+
|
|
421
|
+
print(f"✅ Windows Terminal layout created: {generator.script_path}")
|
|
422
|
+
preview = generator.get_wt_layout_preview(generator.layout_config)
|
|
398
423
|
print(f"📋 Command Preview:\n{preview}")
|
|
399
424
|
print("🔍 Current status:")
|
|
400
425
|
generator.print_status_report()
|
|
401
426
|
print("▶️ To run this layout, execute:")
|
|
402
|
-
print(f' powershell -ExecutionPolicy Bypass -File "{script_path}"')
|
|
427
|
+
print(f' powershell -ExecutionPolicy Bypass -File "{generator.script_path}"')
|
|
403
428
|
except Exception as e:
|
|
404
429
|
print(f"❌ Error: {e}")
|
|
405
430
|
import traceback
|
|
@@ -5,26 +5,23 @@ import uuid
|
|
|
5
5
|
import logging
|
|
6
6
|
import subprocess
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Optional, Any
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from machineconfig.utils.scheduler import Scheduler
|
|
11
11
|
from machineconfig.cluster.sessions_managers.wt_local import WTLayoutGenerator
|
|
12
12
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
13
13
|
from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import (
|
|
14
|
-
StartResult, GlobalSummary, ActiveSessionInfo
|
|
14
|
+
StartResult, GlobalSummary, ActiveSessionInfo
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
session_status: Dict[str, Any] # WT-specific session status
|
|
20
|
-
commands_status: Dict[str, CommandStatus]
|
|
21
|
-
summary: CommandSummary
|
|
18
|
+
|
|
22
19
|
|
|
23
20
|
logging.basicConfig(level=logging.INFO)
|
|
24
21
|
logger = logging.getLogger(__name__)
|
|
25
22
|
console = Console()
|
|
23
|
+
TMP_SERIALIZATION_DIR = Path.home() / "tmp_results" / "wt_sessions" / "serialized"
|
|
26
24
|
|
|
27
|
-
TMP_SERIALIZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "wt", "local_manager")
|
|
28
25
|
|
|
29
26
|
|
|
30
27
|
class WTLocalManager:
|
|
@@ -41,21 +38,22 @@ class WTLocalManager:
|
|
|
41
38
|
"""
|
|
42
39
|
self.session_name_prefix = session_name_prefix
|
|
43
40
|
self.session_layouts = session_layouts # Store the original config
|
|
44
|
-
self.managers:
|
|
41
|
+
self.managers: list[WTLayoutGenerator] = []
|
|
45
42
|
|
|
46
43
|
# Create a WTLayoutGenerator for each session
|
|
47
44
|
for layout_config in session_layouts:
|
|
48
|
-
|
|
49
|
-
manager
|
|
45
|
+
session_name = layout_config["layoutName"]
|
|
46
|
+
manager = WTLayoutGenerator(layout_config=layout_config, session_name=session_name)
|
|
47
|
+
manager.create_layout_file()
|
|
50
48
|
self.managers.append(manager)
|
|
51
49
|
|
|
52
50
|
logger.info(f"Initialized WTLocalManager with {len(self.managers)} sessions")
|
|
53
51
|
|
|
54
|
-
def get_all_session_names(self) ->
|
|
52
|
+
def get_all_session_names(self) -> list[str]:
|
|
55
53
|
"""Get all managed session names."""
|
|
56
|
-
return [manager.session_name for manager in self.managers
|
|
54
|
+
return [manager.session_name for manager in self.managers]
|
|
57
55
|
|
|
58
|
-
def start_all_sessions(self) ->
|
|
56
|
+
def start_all_sessions(self) -> dict[str, StartResult]:
|
|
59
57
|
"""Start all Windows Terminal sessions with their layouts."""
|
|
60
58
|
results = {}
|
|
61
59
|
for manager in self.managers:
|
|
@@ -86,7 +84,7 @@ class WTLocalManager:
|
|
|
86
84
|
|
|
87
85
|
return results
|
|
88
86
|
|
|
89
|
-
def kill_all_sessions(self) ->
|
|
87
|
+
def kill_all_sessions(self) -> dict[str, StartResult]:
|
|
90
88
|
"""Kill all managed Windows Terminal sessions."""
|
|
91
89
|
results = {}
|
|
92
90
|
for manager in self.managers:
|
|
@@ -130,7 +128,7 @@ class WTLocalManager:
|
|
|
130
128
|
commands.append("")
|
|
131
129
|
return "\n".join(commands)
|
|
132
130
|
|
|
133
|
-
def check_all_sessions_status(self) ->
|
|
131
|
+
def check_all_sessions_status(self) -> dict[str, dict[str, Any]]:
|
|
134
132
|
"""Check the status of all sessions and their commands."""
|
|
135
133
|
status_report = {}
|
|
136
134
|
|
|
@@ -361,9 +359,7 @@ class WTLocalManager:
|
|
|
361
359
|
manager_data = json.loads(text)
|
|
362
360
|
|
|
363
361
|
# Recreate the manager
|
|
364
|
-
manager = WTLayoutGenerator()
|
|
365
|
-
manager.session_name = manager_data["session_name"]
|
|
366
|
-
manager.layout_config = manager_data["layout_config"]
|
|
362
|
+
manager = WTLayoutGenerator(layout_config=manager_data["layout_config"], session_name=manager_data["session_name"])
|
|
367
363
|
manager.script_path = manager_data["script_path"]
|
|
368
364
|
|
|
369
365
|
instance.managers.append(manager)
|
|
@@ -375,7 +371,7 @@ class WTLocalManager:
|
|
|
375
371
|
return instance
|
|
376
372
|
|
|
377
373
|
@staticmethod
|
|
378
|
-
def list_saved_sessions() ->
|
|
374
|
+
def list_saved_sessions() -> list[str]:
|
|
379
375
|
"""List all saved session IDs."""
|
|
380
376
|
if not TMP_SERIALIZATION_DIR.exists():
|
|
381
377
|
return []
|
|
@@ -406,7 +402,7 @@ class WTLocalManager:
|
|
|
406
402
|
logger.error(f"Failed to delete session {session_id}: {e}")
|
|
407
403
|
return False
|
|
408
404
|
|
|
409
|
-
def list_active_sessions(self) ->
|
|
405
|
+
def list_active_sessions(self) -> list[ActiveSessionInfo]:
|
|
410
406
|
"""List currently active Windows Terminal sessions managed by this instance."""
|
|
411
407
|
active_sessions = []
|
|
412
408
|
|
|
@@ -448,7 +444,7 @@ class WTLocalManager:
|
|
|
448
444
|
|
|
449
445
|
return active_sessions
|
|
450
446
|
|
|
451
|
-
def get_wt_overview(self) ->
|
|
447
|
+
def get_wt_overview(self) -> dict[str, Any]:
|
|
452
448
|
"""Get overview of all Windows Terminal windows and processes."""
|
|
453
449
|
try:
|
|
454
450
|
result = subprocess.run(
|