machineconfig 6.84__py3-none-any.whl → 6.86__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/wt_local.py +16 -221
- machineconfig/cluster/sessions_managers/wt_local_manager.py +33 -174
- machineconfig/cluster/sessions_managers/wt_remote_manager.py +39 -197
- machineconfig/cluster/sessions_managers/wt_utils/manager_persistence.py +52 -0
- machineconfig/cluster/sessions_managers/wt_utils/monitoring_helpers.py +50 -0
- machineconfig/cluster/sessions_managers/wt_utils/status_reporting.py +76 -0
- machineconfig/cluster/sessions_managers/wt_utils/wt_helpers.py +199 -0
- machineconfig/scripts/python/agents.py +18 -7
- machineconfig/scripts/python/ai/vscode_tasks.py +7 -2
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +31 -66
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_crush.py +6 -5
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_cursor_agents.py +1 -1
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_gemini.py +2 -3
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_qwen.py +2 -2
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_help_launch.py +4 -4
- machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_helper_types.py +2 -2
- machineconfig/scripts/python/helpers_agents/templates/prompt.txt +6 -0
- machineconfig/scripts/python/helpers_agents/templates/template.sh +24 -0
- machineconfig/scripts/python/helpers_devops/cli_config.py +1 -1
- machineconfig/scripts/python/helpers_devops/cli_nw.py +50 -0
- machineconfig/scripts/python/helpers_devops/cli_self.py +3 -3
- machineconfig/scripts/python/helpers_devops/cli_utils.py +18 -1
- machineconfig/scripts/python/{helpers_fire/helpers4.py → helpers_fire_command/file_wrangler.py} +15 -0
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +1 -1
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +1 -1
- machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +1 -1
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/python/nw/wifi_conn.py +1 -53
- machineconfig/scripts/python/sessions.py +1 -1
- machineconfig/scripts/python/terminal.py +46 -17
- machineconfig/scripts/python/utils.py +10 -21
- machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
- machineconfig/scripts/windows/term.ps1 +48 -0
- machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
- machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
- machineconfig/utils/code.py +18 -13
- machineconfig/utils/installer.py +0 -1
- machineconfig/utils/meta.py +4 -3
- machineconfig/utils/path_helper.py +1 -1
- machineconfig/utils/scheduling.py +0 -2
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/ssh.py +2 -2
- machineconfig/utils/upgrade_packages.py +28 -24
- {machineconfig-6.84.dist-info → machineconfig-6.86.dist-info}/METADATA +1 -1
- {machineconfig-6.84.dist-info → machineconfig-6.86.dist-info}/RECORD +55 -52
- machineconfig/scripts/linux/warp-cli.sh +0 -122
- machineconfig/scripts/python/helpers_fire/prompt.txt +0 -2
- machineconfig/scripts/python/helpers_fire/template.sh +0 -15
- /machineconfig/scripts/python/{helpers_fire → helpers_agents}/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/__init__.py +0 -0
- /machineconfig/scripts/python/{helpers_fire → helpers_agents}/agentic_frameworks/fire_crush.json +0 -0
- /machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_help_search.py +0 -0
- /machineconfig/scripts/python/{helpers_fire → helpers_agents}/fire_agents_load_balancer.py +0 -0
- /machineconfig/scripts/python/{helpers_fire → helpers_agents/templates}/template.ps1 +0 -0
- {machineconfig-6.84.dist-info → machineconfig-6.86.dist-info}/WHEEL +0 -0
- {machineconfig-6.84.dist-info → machineconfig-6.86.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.84.dist-info → machineconfig-6.86.dist-info}/top_level.txt +0 -0
|
@@ -7,11 +7,7 @@ https://github.com/ruby9455/app_management/tree/main/app_management
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
import shlex
|
|
11
10
|
import subprocess
|
|
12
|
-
import random
|
|
13
|
-
import string
|
|
14
|
-
import json
|
|
15
11
|
import platform
|
|
16
12
|
from typing import Optional, Any
|
|
17
13
|
from pathlib import Path
|
|
@@ -22,6 +18,14 @@ from rich.panel import Panel
|
|
|
22
18
|
from rich.table import Table
|
|
23
19
|
|
|
24
20
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
21
|
+
from machineconfig.cluster.sessions_managers.wt_utils.wt_helpers import (
|
|
22
|
+
generate_random_suffix,
|
|
23
|
+
# escape_for_wt,
|
|
24
|
+
validate_layout_config,
|
|
25
|
+
generate_wt_command_string,
|
|
26
|
+
check_wt_session_status,
|
|
27
|
+
check_command_status,
|
|
28
|
+
)
|
|
25
29
|
|
|
26
30
|
logging.basicConfig(level=logging.INFO)
|
|
27
31
|
logger = logging.getLogger(__name__)
|
|
@@ -37,52 +41,11 @@ class WTLayoutGenerator:
|
|
|
37
41
|
def __init__(self, layout_config: LayoutConfig, session_name: str):
|
|
38
42
|
self.session_name: str = session_name
|
|
39
43
|
self.layout_config: LayoutConfig = layout_config.copy()
|
|
40
|
-
self.script_path: Optional[str] = None
|
|
41
|
-
|
|
42
|
-
@staticmethod
|
|
43
|
-
def _generate_random_suffix(length: int) -> str:
|
|
44
|
-
"""Generate a random string suffix for unique PowerShell script names."""
|
|
45
|
-
return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
|
46
|
-
|
|
47
|
-
@staticmethod
|
|
48
|
-
def _parse_command(command: str) -> tuple[str, list[str]]:
|
|
49
|
-
try:
|
|
50
|
-
parts = shlex.split(command)
|
|
51
|
-
if not parts:
|
|
52
|
-
raise ValueError("Empty command provided")
|
|
53
|
-
return parts[0], parts[1:] if len(parts) > 1 else []
|
|
54
|
-
except ValueError as e:
|
|
55
|
-
logger.error(f"Error parsing command '{command}': {e}")
|
|
56
|
-
parts = command.split()
|
|
57
|
-
return parts[0] if parts else "", parts[1:] if len(parts) > 1 else []
|
|
58
|
-
|
|
59
|
-
@staticmethod
|
|
60
|
-
def _escape_for_wt(text: str) -> str:
|
|
61
|
-
"""Escape text for use in Windows Terminal commands."""
|
|
62
|
-
# Windows Terminal uses PowerShell-style escaping
|
|
63
|
-
text = text.replace('"', '""') # Escape quotes for PowerShell
|
|
64
|
-
if " " in text or ";" in text or "&" in text or "|" in text:
|
|
65
|
-
return f'"{text}"'
|
|
66
|
-
return text
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
@staticmethod
|
|
71
|
-
def _validate_layout_config(layout_config: LayoutConfig) -> None:
|
|
72
|
-
"""Validate layout configuration format and content."""
|
|
73
|
-
if not layout_config["layoutTabs"]:
|
|
74
|
-
raise ValueError("Layout must contain at least one tab")
|
|
75
|
-
for tab in layout_config["layoutTabs"]:
|
|
76
|
-
if not tab["tabName"].strip():
|
|
77
|
-
raise ValueError(f"Invalid tab name: {tab['tabName']}")
|
|
78
|
-
if not tab["command"].strip():
|
|
79
|
-
raise ValueError(f"Invalid command for tab '{tab['tabName']}': {tab['command']}")
|
|
80
|
-
if not tab["startDir"].strip():
|
|
81
|
-
raise ValueError(f"Invalid startDir for tab '{tab['tabName']}': {tab['startDir']}")
|
|
44
|
+
self.script_path: Optional[str] = None
|
|
82
45
|
|
|
83
46
|
def create_layout_file(self) -> bool:
|
|
84
47
|
"""Create Windows Terminal layout file and return success status."""
|
|
85
|
-
|
|
48
|
+
validate_layout_config(self.layout_config)
|
|
86
49
|
tab_count = len(self.layout_config['layoutTabs'])
|
|
87
50
|
layout_name = self.layout_config['layoutName']
|
|
88
51
|
console.print(f"[bold cyan]📋 Creating Windows Terminal layout[/bold cyan] [bright_green]'{layout_name}' with {tab_count} tabs[/bright_green]")
|
|
@@ -90,17 +53,13 @@ class WTLayoutGenerator:
|
|
|
90
53
|
for tab in self.layout_config['layoutTabs']:
|
|
91
54
|
console.print(f" [yellow]→[/yellow] [bold]{tab['tabName']}[/bold] [dim]in[/dim] [blue]{tab['startDir']}[/blue]")
|
|
92
55
|
|
|
93
|
-
|
|
94
|
-
wt_command = self._generate_wt_command_string(self.layout_config, self.session_name)
|
|
56
|
+
wt_command = generate_wt_command_string(self.layout_config, self.session_name)
|
|
95
57
|
|
|
96
|
-
random_suffix =
|
|
97
|
-
# Create PowerShell script content
|
|
58
|
+
random_suffix = generate_random_suffix(8)
|
|
98
59
|
script_content = f"""# Windows Terminal layout for {self.session_name}
|
|
99
60
|
# Generated with random suffix: {random_suffix}
|
|
100
61
|
{wt_command}
|
|
101
62
|
"""
|
|
102
|
-
# Write to file
|
|
103
|
-
random_suffix = WTLayoutGenerator._generate_random_suffix(8)
|
|
104
63
|
tmp_dir = Path(TMP_LAYOUT_DIR)
|
|
105
64
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
106
65
|
script_file = tmp_dir / f"wt_layout_{self.session_name}_{random_suffix}.ps1"
|
|
@@ -110,46 +69,10 @@ class WTLayoutGenerator:
|
|
|
110
69
|
console.print(f"[bold green]✅ Layout created successfully:[/bold green] [cyan]{self.script_path}[/cyan]")
|
|
111
70
|
return True
|
|
112
71
|
|
|
113
|
-
def _generate_wt_command_string(self, layout_config: LayoutConfig, window_name: str) -> str:
|
|
114
|
-
"""Generate complete Windows Terminal command string."""
|
|
115
|
-
# Build the complete Windows Terminal command
|
|
116
|
-
command_parts = []
|
|
117
|
-
|
|
118
|
-
for i, tab in enumerate(layout_config["layoutTabs"]):
|
|
119
|
-
is_first = i == 0
|
|
120
|
-
|
|
121
|
-
if is_first:
|
|
122
|
-
# First tab: start with wt command and window name
|
|
123
|
-
tab_parts = ["wt", "-w", WTLayoutGenerator._escape_for_wt(window_name)]
|
|
124
|
-
else:
|
|
125
|
-
# Subsequent tabs: use new-tab
|
|
126
|
-
tab_parts = ["new-tab"]
|
|
127
|
-
|
|
128
|
-
# Add common tab arguments
|
|
129
|
-
tab_name = tab["tabName"]
|
|
130
|
-
cwd = tab["startDir"]
|
|
131
|
-
command = tab["command"]
|
|
132
|
-
|
|
133
|
-
# Convert paths to Windows format if needed
|
|
134
|
-
if cwd.startswith("~/"):
|
|
135
|
-
cwd = cwd.replace("~/", f"{Path.home()}/")
|
|
136
|
-
elif cwd == "~":
|
|
137
|
-
cwd = str(Path.home())
|
|
138
|
-
|
|
139
|
-
# Add arguments in the correct order
|
|
140
|
-
tab_parts.extend(["-d", WTLayoutGenerator._escape_for_wt(cwd)])
|
|
141
|
-
tab_parts.extend(["--title", WTLayoutGenerator._escape_for_wt(tab_name)])
|
|
142
|
-
tab_parts.append(WTLayoutGenerator._escape_for_wt(command))
|
|
143
|
-
|
|
144
|
-
command_parts.append(" ".join(tab_parts))
|
|
145
|
-
|
|
146
|
-
# Join all tab commands with escaped semicolons for PowerShell
|
|
147
|
-
return " `; ".join(command_parts)
|
|
148
|
-
|
|
149
72
|
def get_wt_layout_preview(self, layout_config: LayoutConfig) -> str:
|
|
150
73
|
"""Generate preview of the Windows Terminal command that would be created."""
|
|
151
|
-
|
|
152
|
-
return
|
|
74
|
+
validate_layout_config(layout_config)
|
|
75
|
+
return generate_wt_command_string(layout_config, "preview")
|
|
153
76
|
|
|
154
77
|
def check_all_commands_status(self) -> dict[str, dict[str, Any]]:
|
|
155
78
|
if not self.layout_config:
|
|
@@ -159,137 +82,13 @@ class WTLayoutGenerator:
|
|
|
159
82
|
status_report = {}
|
|
160
83
|
for tab in self.layout_config["layoutTabs"]:
|
|
161
84
|
tab_name = tab["tabName"]
|
|
162
|
-
status_report[tab_name] =
|
|
85
|
+
status_report[tab_name] = check_command_status(tab_name, self.layout_config)
|
|
163
86
|
|
|
164
87
|
return status_report
|
|
165
88
|
|
|
166
|
-
@staticmethod
|
|
167
|
-
def check_wt_session_status(session_name: str) -> dict[str, Any]:
|
|
168
|
-
try:
|
|
169
|
-
# Simplified Windows Terminal process check
|
|
170
|
-
ps_script = """
|
|
171
|
-
try {
|
|
172
|
-
$wtProcesses = Get-Process -Name 'WindowsTerminal' -ErrorAction SilentlyContinue
|
|
173
|
-
if ($wtProcesses) {
|
|
174
|
-
$processInfo = @()
|
|
175
|
-
$wtProcesses | ForEach-Object {
|
|
176
|
-
$info = @{
|
|
177
|
-
"Id" = $_.Id
|
|
178
|
-
"ProcessName" = $_.ProcessName
|
|
179
|
-
"StartTime" = $_.StartTime.ToString()
|
|
180
|
-
}
|
|
181
|
-
$processInfo += $info
|
|
182
|
-
}
|
|
183
|
-
$processInfo | ConvertTo-Json -Depth 2
|
|
184
|
-
}
|
|
185
|
-
} catch {
|
|
186
|
-
# No Windows Terminal processes found
|
|
187
|
-
}
|
|
188
|
-
"""
|
|
189
|
-
|
|
190
|
-
result = subprocess.run([POWERSHELL_CMD, "-Command", ps_script], capture_output=True, text=True, timeout=5)
|
|
191
|
-
|
|
192
|
-
if result.returncode == 0:
|
|
193
|
-
output = result.stdout.strip()
|
|
194
|
-
if output and output != "":
|
|
195
|
-
try:
|
|
196
|
-
processes = json.loads(output)
|
|
197
|
-
if not isinstance(processes, list):
|
|
198
|
-
processes = [processes]
|
|
199
|
-
|
|
200
|
-
# For simplicity, assume session exists if WT is running
|
|
201
|
-
return {
|
|
202
|
-
"wt_running": True,
|
|
203
|
-
"session_exists": len(processes) > 0,
|
|
204
|
-
"session_name": session_name,
|
|
205
|
-
"all_windows": processes,
|
|
206
|
-
"session_windows": processes # Simplified - assume all windows could be session windows
|
|
207
|
-
}
|
|
208
|
-
except Exception as e:
|
|
209
|
-
return {"wt_running": True, "session_exists": False, "error": f"Failed to parse process info: {e}", "session_name": session_name}
|
|
210
|
-
else:
|
|
211
|
-
return {"wt_running": False, "session_exists": False, "session_name": session_name, "all_windows": []}
|
|
212
|
-
else:
|
|
213
|
-
return {"wt_running": False, "error": result.stderr, "session_name": session_name}
|
|
214
|
-
|
|
215
|
-
except subprocess.TimeoutExpired:
|
|
216
|
-
return {"wt_running": False, "error": "Timeout while checking Windows Terminal processes", "session_name": session_name}
|
|
217
|
-
except FileNotFoundError:
|
|
218
|
-
return {"wt_running": False, "error": f"PowerShell ({POWERSHELL_CMD}) not found in PATH", "session_name": session_name}
|
|
219
|
-
except Exception as e:
|
|
220
|
-
return {"wt_running": False, "error": str(e), "session_name": session_name}
|
|
221
|
-
|
|
222
|
-
@staticmethod
|
|
223
|
-
def check_command_status(tab_name: str, layout_config: LayoutConfig) -> dict[str, Any]:
|
|
224
|
-
"""Check if a command is running by looking for processes."""
|
|
225
|
-
# Find the tab with the given name
|
|
226
|
-
tab_config = None
|
|
227
|
-
for tab in layout_config["layoutTabs"]:
|
|
228
|
-
if tab["tabName"] == tab_name:
|
|
229
|
-
tab_config = tab
|
|
230
|
-
break
|
|
231
|
-
|
|
232
|
-
if tab_config is None:
|
|
233
|
-
return {"status": "unknown", "error": f"Tab '{tab_name}' not found in layout config", "running": False, "pid": None, "command": None}
|
|
234
|
-
|
|
235
|
-
command = tab_config["command"]
|
|
236
|
-
|
|
237
|
-
try:
|
|
238
|
-
# Extract the primary executable name from command
|
|
239
|
-
primary_cmd = command.split()[0] if command.strip() else ""
|
|
240
|
-
if not primary_cmd:
|
|
241
|
-
return {"status": "error", "error": "Empty command", "running": False, "command": command, "tab_name": tab_name}
|
|
242
|
-
|
|
243
|
-
# Use a much simpler PowerShell script that just checks for process names
|
|
244
|
-
ps_script = f"""
|
|
245
|
-
try {{
|
|
246
|
-
$processes = Get-Process -Name '{primary_cmd}' -ErrorAction SilentlyContinue
|
|
247
|
-
if ($processes) {{
|
|
248
|
-
$processes | ForEach-Object {{
|
|
249
|
-
$procInfo = @{{
|
|
250
|
-
"pid" = $_.Id
|
|
251
|
-
"name" = $_.ProcessName
|
|
252
|
-
"start_time" = $_.StartTime.ToString()
|
|
253
|
-
}}
|
|
254
|
-
Write-Output ($procInfo | ConvertTo-Json -Compress)
|
|
255
|
-
}}
|
|
256
|
-
}}
|
|
257
|
-
}} catch {{
|
|
258
|
-
# No processes found or other error
|
|
259
|
-
}}
|
|
260
|
-
"""
|
|
261
|
-
|
|
262
|
-
result = subprocess.run([POWERSHELL_CMD, "-Command", ps_script], capture_output=True, text=True, timeout=5)
|
|
263
|
-
|
|
264
|
-
if result.returncode == 0:
|
|
265
|
-
output_lines = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()]
|
|
266
|
-
matching_processes = []
|
|
267
|
-
|
|
268
|
-
for line in output_lines:
|
|
269
|
-
if line.startswith("{") and line.endswith("}"):
|
|
270
|
-
try:
|
|
271
|
-
proc_info = json.loads(line)
|
|
272
|
-
matching_processes.append(proc_info)
|
|
273
|
-
except json.JSONDecodeError:
|
|
274
|
-
continue
|
|
275
|
-
|
|
276
|
-
if matching_processes:
|
|
277
|
-
return {"status": "running", "running": True, "processes": matching_processes, "command": command, "tab_name": tab_name}
|
|
278
|
-
else:
|
|
279
|
-
return {"status": "not_running", "running": False, "processes": [], "command": command, "tab_name": tab_name}
|
|
280
|
-
else:
|
|
281
|
-
return {"status": "error", "error": f"Command failed: {result.stderr}", "running": False, "command": command, "tab_name": tab_name}
|
|
282
|
-
|
|
283
|
-
except subprocess.TimeoutExpired:
|
|
284
|
-
logger.error(f"Timeout checking command status for tab '{tab_name}'")
|
|
285
|
-
return {"status": "timeout", "error": "Timeout checking process status", "running": False, "command": command, "tab_name": tab_name}
|
|
286
|
-
except Exception as e:
|
|
287
|
-
logger.error(f"Error checking command status for tab '{tab_name}': {e}")
|
|
288
|
-
return {"status": "error", "error": str(e), "running": False, "command": command, "tab_name": tab_name}
|
|
289
|
-
|
|
290
89
|
def get_status_report(self) -> dict[str, Any]:
|
|
291
90
|
"""Get status report for this layout including Windows Terminal and commands."""
|
|
292
|
-
wt_status =
|
|
91
|
+
wt_status = check_wt_session_status(self.session_name or "default")
|
|
293
92
|
commands_status = self.check_all_commands_status()
|
|
294
93
|
|
|
295
94
|
running_count = sum(1 for status in commands_status.values() if status.get("running", False))
|
|
@@ -382,14 +181,11 @@ def run_wt_layout(layout_config: LayoutConfig) -> None:
|
|
|
382
181
|
session_name = layout_config["layoutName"]
|
|
383
182
|
generator = WTLayoutGenerator(layout_config=layout_config, session_name=session_name)
|
|
384
183
|
generator.create_layout_file()
|
|
385
|
-
|
|
386
184
|
if generator.script_path is None:
|
|
387
185
|
raise RuntimeError("Script path was not set after creating layout file")
|
|
388
|
-
|
|
389
186
|
# Execute the script
|
|
390
187
|
cmd = f'powershell -ExecutionPolicy Bypass -File "{generator.script_path}"'
|
|
391
188
|
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
|
392
|
-
|
|
393
189
|
if result.returncode == 0:
|
|
394
190
|
print(f"Windows Terminal layout is running @ {layout_config['layoutName']}")
|
|
395
191
|
else:
|
|
@@ -400,7 +196,6 @@ def run_wt_layout(layout_config: LayoutConfig) -> None:
|
|
|
400
196
|
def run_command_in_wt_tab(command: str, tab_name: str, cwd: Optional[str]) -> str:
|
|
401
197
|
"""Create a command to run in a new Windows Terminal tab."""
|
|
402
198
|
cwd_part = f'-d "{cwd}"' if cwd else ""
|
|
403
|
-
|
|
404
199
|
return f"""
|
|
405
200
|
echo "Creating new Windows Terminal tab: {tab_name}"
|
|
406
201
|
wt new-tab --title "{tab_name}" {cwd_part} "{command}"
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
from datetime import datetime
|
|
3
|
-
import json
|
|
4
|
-
import uuid
|
|
5
3
|
import logging
|
|
6
4
|
import subprocess
|
|
7
5
|
from pathlib import Path
|
|
@@ -9,9 +7,14 @@ from typing import Optional, Any
|
|
|
9
7
|
from rich.console import Console
|
|
10
8
|
from machineconfig.utils.scheduler import Scheduler
|
|
11
9
|
from machineconfig.cluster.sessions_managers.wt_local import WTLayoutGenerator
|
|
10
|
+
from machineconfig.cluster.sessions_managers.wt_utils.wt_helpers import check_wt_session_status
|
|
12
11
|
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
13
|
-
from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import
|
|
14
|
-
|
|
12
|
+
from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import StartResult, ActiveSessionInfo
|
|
13
|
+
from machineconfig.cluster.sessions_managers.wt_utils.manager_persistence import (
|
|
14
|
+
generate_session_id, save_json_file, load_json_file, list_saved_sessions_in_dir, delete_session_dir, ensure_session_dir_exists
|
|
15
|
+
)
|
|
16
|
+
from machineconfig.cluster.sessions_managers.wt_utils.status_reporting import (
|
|
17
|
+
print_global_summary, print_session_health_status, print_commands_status, calculate_session_summary, calculate_global_summary_from_status
|
|
15
18
|
)
|
|
16
19
|
|
|
17
20
|
|
|
@@ -129,104 +132,29 @@ class WTLocalManager:
|
|
|
129
132
|
return "\n".join(commands)
|
|
130
133
|
|
|
131
134
|
def check_all_sessions_status(self) -> dict[str, dict[str, Any]]:
|
|
132
|
-
"""Check the status of all sessions and their commands."""
|
|
133
135
|
status_report = {}
|
|
134
|
-
|
|
135
136
|
for manager in self.managers:
|
|
136
137
|
session_name = manager.session_name or "default"
|
|
137
|
-
|
|
138
|
-
# Get session status
|
|
139
|
-
session_status = WTLayoutGenerator.check_wt_session_status(session_name)
|
|
140
|
-
|
|
141
|
-
# Get commands status for this session
|
|
138
|
+
session_status = check_wt_session_status(session_name)
|
|
142
139
|
commands_status = manager.check_all_commands_status()
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
running_count = sum(1 for status in commands_status.values() if status.get("running", False))
|
|
146
|
-
total_count = len(commands_status)
|
|
147
|
-
|
|
148
|
-
status_report[session_name] = {
|
|
149
|
-
"session_status": session_status,
|
|
150
|
-
"commands_status": commands_status,
|
|
151
|
-
"summary": {"total_commands": total_count, "running_commands": running_count, "stopped_commands": total_count - running_count, "session_healthy": session_status.get("session_exists", False)},
|
|
152
|
-
}
|
|
153
|
-
|
|
140
|
+
summary = calculate_session_summary(commands_status, session_status.get("session_exists", False))
|
|
141
|
+
status_report[session_name] = {"session_status": session_status, "commands_status": commands_status, "summary": summary}
|
|
154
142
|
return status_report
|
|
155
143
|
|
|
156
|
-
def get_global_summary(self) ->
|
|
157
|
-
"""Get a global summary across all sessions."""
|
|
144
|
+
def get_global_summary(self) -> dict[str, Any]:
|
|
158
145
|
all_status = self.check_all_sessions_status()
|
|
159
|
-
|
|
160
|
-
total_sessions = len(all_status)
|
|
161
|
-
healthy_sessions = sum(1 for status in all_status.values() if status["summary"]["session_healthy"])
|
|
162
|
-
total_commands = sum(status["summary"]["total_commands"] for status in all_status.values())
|
|
163
|
-
total_running = sum(status["summary"]["running_commands"] for status in all_status.values())
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
"total_sessions": total_sessions,
|
|
167
|
-
"healthy_sessions": healthy_sessions,
|
|
168
|
-
"unhealthy_sessions": total_sessions - healthy_sessions,
|
|
169
|
-
"total_commands": total_commands,
|
|
170
|
-
"running_commands": total_running,
|
|
171
|
-
"stopped_commands": total_commands - total_running,
|
|
172
|
-
"all_sessions_healthy": healthy_sessions == total_sessions,
|
|
173
|
-
"all_commands_running": total_running == total_commands,
|
|
174
|
-
}
|
|
146
|
+
return calculate_global_summary_from_status(all_status, include_remote_machines=False)
|
|
175
147
|
|
|
176
148
|
def print_status_report(self) -> None:
|
|
177
|
-
"""Print a comprehensive status report for all sessions."""
|
|
178
149
|
all_status = self.check_all_sessions_status()
|
|
179
150
|
global_summary = self.get_global_summary()
|
|
180
|
-
|
|
181
|
-
print("=" * 80)
|
|
182
|
-
print("🖥️ WINDOWS TERMINAL LOCAL MANAGER STATUS REPORT")
|
|
183
|
-
print("=" * 80)
|
|
184
|
-
|
|
185
|
-
# Global summary
|
|
186
|
-
print("🌐 GLOBAL SUMMARY:")
|
|
187
|
-
print(f" Total sessions: {global_summary['total_sessions']}")
|
|
188
|
-
print(f" Healthy sessions: {global_summary['healthy_sessions']}")
|
|
189
|
-
print(f" Total commands: {global_summary['total_commands']}")
|
|
190
|
-
print(f" Running commands: {global_summary['running_commands']}")
|
|
191
|
-
print(f" All healthy: {'✅' if global_summary['all_sessions_healthy'] else '❌'}")
|
|
192
|
-
print()
|
|
193
|
-
|
|
194
|
-
# Per-session details
|
|
151
|
+
print_global_summary(global_summary, "WINDOWS TERMINAL LOCAL MANAGER STATUS REPORT")
|
|
195
152
|
for session_name, status in all_status.items():
|
|
196
|
-
session_status = status["session_status"]
|
|
197
|
-
commands_status = status["commands_status"]
|
|
198
|
-
summary = status["summary"]
|
|
199
|
-
|
|
200
153
|
print(f"🪟 SESSION: {session_name}")
|
|
201
154
|
print("-" * 60)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if session_status.get("wt_running", False):
|
|
205
|
-
if session_status.get("session_exists", False):
|
|
206
|
-
session_windows = session_status.get("session_windows", [])
|
|
207
|
-
all_windows = session_status.get("all_windows", [])
|
|
208
|
-
print("✅ Windows Terminal is running")
|
|
209
|
-
print(f" Session windows: {len(session_windows)}")
|
|
210
|
-
print(f" Total WT windows: {len(all_windows)}")
|
|
211
|
-
else:
|
|
212
|
-
print("⚠️ Windows Terminal is running but no session windows found")
|
|
213
|
-
else:
|
|
214
|
-
print(f"❌ Windows Terminal session issue: {session_status.get('error', 'Unknown error')}")
|
|
215
|
-
|
|
216
|
-
# Commands in this session
|
|
217
|
-
print(f" Commands ({summary['running_commands']}/{summary['total_commands']} running):")
|
|
218
|
-
for tab_name, cmd_status in commands_status.items():
|
|
219
|
-
status_icon = "✅" if cmd_status.get("running", False) else "❌"
|
|
220
|
-
cmd_text = cmd_status.get("command", "Unknown")[:50]
|
|
221
|
-
if len(cmd_status.get("command", "")) > 50:
|
|
222
|
-
cmd_text += "..."
|
|
223
|
-
console.print(f" {status_icon} {tab_name}: {cmd_text}")
|
|
224
|
-
|
|
225
|
-
if cmd_status.get("processes"):
|
|
226
|
-
for proc in cmd_status["processes"][:2]: # Show first 2 processes
|
|
227
|
-
console.print(f" [dim]└─[/dim] PID {proc['pid']}: {proc['name']}")
|
|
155
|
+
print_session_health_status(status["session_status"], remote_name=None)
|
|
156
|
+
print_commands_status(status["commands_status"], status["summary"])
|
|
228
157
|
print()
|
|
229
|
-
|
|
230
158
|
print("=" * 80)
|
|
231
159
|
|
|
232
160
|
def run_monitoring_routine(self, wait_ms: int = 30000) -> None:
|
|
@@ -288,126 +216,59 @@ class WTLocalManager:
|
|
|
288
216
|
sched.run(max_cycles=None)
|
|
289
217
|
|
|
290
218
|
def save(self, session_id: Optional[str] = None) -> str:
|
|
291
|
-
"""Save the manager state to disk."""
|
|
292
219
|
if session_id is None:
|
|
293
|
-
session_id =
|
|
294
|
-
|
|
295
|
-
# Create session directory
|
|
220
|
+
session_id = generate_session_id()
|
|
296
221
|
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
297
|
-
session_dir
|
|
298
|
-
|
|
299
|
-
# Save the session2wt_tabs configuration
|
|
300
|
-
config_file = session_dir / "session_layouts.json"
|
|
301
|
-
text = json.dumps(self.session_layouts, indent=2, ensure_ascii=False)
|
|
302
|
-
config_file.write_text(text, encoding="utf-8")
|
|
303
|
-
|
|
304
|
-
# Save metadata
|
|
222
|
+
ensure_session_dir_exists(session_dir)
|
|
223
|
+
save_json_file(session_dir / "session_layouts.json", self.session_layouts, "session layouts")
|
|
305
224
|
metadata = {"session_name_prefix": self.session_name_prefix, "created_at": str(datetime.now()), "num_managers": len(self.managers), "sessions": [item["layoutName"] for item in self.session_layouts], "manager_type": "WTLocalManager"}
|
|
306
|
-
|
|
307
|
-
text = json.dumps(metadata, indent=2, ensure_ascii=False)
|
|
308
|
-
metadata_file.write_text(text, encoding="utf-8")
|
|
309
|
-
|
|
310
|
-
# Save each manager's state
|
|
225
|
+
save_json_file(session_dir / "metadata.json", metadata, "metadata")
|
|
311
226
|
managers_dir = session_dir / "managers"
|
|
312
227
|
managers_dir.mkdir(exist_ok=True)
|
|
313
|
-
|
|
314
228
|
for i, manager in enumerate(self.managers):
|
|
315
229
|
manager_data = {"session_name": manager.session_name, "layout_config": manager.layout_config, "script_path": manager.script_path}
|
|
316
|
-
|
|
317
|
-
text = json.dumps(manager_data, indent=2, ensure_ascii=False)
|
|
318
|
-
manager_file.write_text(text, encoding="utf-8")
|
|
319
|
-
|
|
230
|
+
save_json_file(managers_dir / f"manager_{i}_{manager.session_name}.json", manager_data, f"manager {i}")
|
|
320
231
|
logger.info(f"✅ Saved WTLocalManager session to: {session_dir}")
|
|
321
232
|
return session_id
|
|
322
233
|
|
|
323
234
|
@classmethod
|
|
324
235
|
def load(cls, session_id: str) -> "WTLocalManager":
|
|
325
|
-
"""Load a saved manager state from disk."""
|
|
326
236
|
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
327
|
-
|
|
328
237
|
if not session_dir.exists():
|
|
329
238
|
raise FileNotFoundError(f"Session directory not found: {session_dir}")
|
|
330
|
-
|
|
331
|
-
#
|
|
332
|
-
|
|
333
|
-
if
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
text = config_file.read_text(encoding="utf-8")
|
|
337
|
-
session_layouts = json.loads(text)
|
|
338
|
-
|
|
339
|
-
# Load metadata
|
|
340
|
-
metadata_file = session_dir / "metadata.json"
|
|
341
|
-
session_name_prefix = "LocalWTMgr" # default fallback
|
|
342
|
-
if metadata_file.exists():
|
|
343
|
-
text = metadata_file.read_text(encoding="utf-8")
|
|
344
|
-
metadata = json.loads(text)
|
|
345
|
-
session_name_prefix = metadata.get("session_name_prefix", "LocalWTMgr")
|
|
346
|
-
|
|
347
|
-
# Create new instance
|
|
239
|
+
loaded_data = load_json_file(session_dir / "session_layouts.json", "Configuration file")
|
|
240
|
+
session_layouts = loaded_data if isinstance(loaded_data, list) else [] # type: ignore[arg-type]
|
|
241
|
+
metadata_data = load_json_file(session_dir / "metadata.json", "Metadata file") if (session_dir / "metadata.json").exists() else {}
|
|
242
|
+
metadata = metadata_data if isinstance(metadata_data, dict) else {} # type: ignore[arg-type]
|
|
243
|
+
session_name_prefix = metadata.get("session_name_prefix", "LocalWTMgr") # type: ignore[union-attr]
|
|
348
244
|
instance = cls(session_layouts=session_layouts, session_name_prefix=session_name_prefix)
|
|
349
|
-
|
|
350
|
-
# Load saved manager states
|
|
351
245
|
managers_dir = session_dir / "managers"
|
|
352
246
|
if managers_dir.exists():
|
|
353
247
|
instance.managers = []
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
for manager_file in manager_files:
|
|
248
|
+
for manager_file in sorted(managers_dir.glob("manager_*.json")):
|
|
357
249
|
try:
|
|
358
|
-
|
|
359
|
-
manager_data =
|
|
360
|
-
|
|
361
|
-
#
|
|
362
|
-
manager = WTLayoutGenerator(layout_config=manager_data["layout_config"], session_name=manager_data["session_name"])
|
|
363
|
-
manager.script_path = manager_data["script_path"]
|
|
364
|
-
|
|
250
|
+
loaded_manager_data = load_json_file(manager_file, "Manager data")
|
|
251
|
+
manager_data = loaded_manager_data if isinstance(loaded_manager_data, dict) else {} # type: ignore[arg-type]
|
|
252
|
+
manager = WTLayoutGenerator(layout_config=manager_data["layout_config"], session_name=manager_data["session_name"]) # type: ignore[typeddict-item]
|
|
253
|
+
manager.script_path = manager_data["script_path"] # type: ignore[typeddict-item]
|
|
365
254
|
instance.managers.append(manager)
|
|
366
|
-
|
|
367
255
|
except Exception as e:
|
|
368
256
|
logger.warning(f"Failed to load manager from {manager_file}: {e}")
|
|
369
|
-
|
|
370
257
|
logger.info(f"✅ Loaded WTLocalManager session from: {session_dir}")
|
|
371
258
|
return instance
|
|
372
259
|
|
|
373
260
|
@staticmethod
|
|
374
261
|
def list_saved_sessions() -> list[str]:
|
|
375
|
-
|
|
376
|
-
if not TMP_SERIALIZATION_DIR.exists():
|
|
377
|
-
return []
|
|
378
|
-
|
|
379
|
-
sessions = []
|
|
380
|
-
for item in TMP_SERIALIZATION_DIR.iterdir():
|
|
381
|
-
if item.is_dir() and (item / "metadata.json").exists():
|
|
382
|
-
sessions.append(item.name)
|
|
383
|
-
|
|
384
|
-
return sorted(sessions)
|
|
262
|
+
return list_saved_sessions_in_dir(TMP_SERIALIZATION_DIR)
|
|
385
263
|
|
|
386
264
|
@staticmethod
|
|
387
265
|
def delete_session(session_id: str) -> bool:
|
|
388
|
-
|
|
389
|
-
session_dir = TMP_SERIALIZATION_DIR / session_id
|
|
390
|
-
|
|
391
|
-
if not session_dir.exists():
|
|
392
|
-
logger.warning(f"Session directory not found: {session_dir}")
|
|
393
|
-
return False
|
|
394
|
-
|
|
395
|
-
try:
|
|
396
|
-
import shutil
|
|
397
|
-
|
|
398
|
-
shutil.rmtree(session_dir)
|
|
399
|
-
logger.info(f"✅ Deleted session: {session_id}")
|
|
400
|
-
return True
|
|
401
|
-
except Exception as e:
|
|
402
|
-
logger.error(f"Failed to delete session {session_id}: {e}")
|
|
403
|
-
return False
|
|
266
|
+
return delete_session_dir(TMP_SERIALIZATION_DIR / session_id, session_id)
|
|
404
267
|
|
|
405
268
|
def list_active_sessions(self) -> list[ActiveSessionInfo]:
|
|
406
|
-
|
|
407
|
-
active_sessions = []
|
|
269
|
+
active_sessions: list[ActiveSessionInfo] = []
|
|
408
270
|
|
|
409
271
|
try:
|
|
410
|
-
# Get all running Windows Terminal processes
|
|
411
272
|
result = subprocess.run(
|
|
412
273
|
["powershell", "-Command", 'Get-Process -Name "WindowsTerminal" -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, StartTime, MainWindowTitle | ConvertTo-Json -Depth 2'], capture_output=True, text=True, timeout=10
|
|
413
274
|
)
|
|
@@ -419,7 +280,6 @@ class WTLocalManager:
|
|
|
419
280
|
if not isinstance(all_processes, list):
|
|
420
281
|
all_processes = [all_processes]
|
|
421
282
|
|
|
422
|
-
# Filter to only our managed sessions
|
|
423
283
|
for manager in self.managers:
|
|
424
284
|
session_name = manager.session_name
|
|
425
285
|
session_windows = []
|
|
@@ -435,7 +295,6 @@ class WTLocalManager:
|
|
|
435
295
|
"is_active": len(session_windows) > 0,
|
|
436
296
|
"tab_count": len(manager.layout_config["layoutTabs"]) if manager.layout_config else 0,
|
|
437
297
|
"tabs": [tab["tabName"] for tab in manager.layout_config["layoutTabs"]] if manager.layout_config else [],
|
|
438
|
-
"windows": session_windows,
|
|
439
298
|
}
|
|
440
299
|
)
|
|
441
300
|
|