machineconfig 6.84__py3-none-any.whl → 6.85__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/ai/vscode_tasks.py +7 -2
- machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
- machineconfig/scripts/python/fire_jobs.py +30 -65
- 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 +1 -1
- machineconfig/scripts/python/helpers_fire/helpers4.py +15 -0
- machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +1 -1
- machineconfig/scripts/python/nw/mount_nfs +1 -1
- machineconfig/scripts/python/nw/wifi_conn.py +1 -53
- machineconfig/scripts/python/terminal.py +46 -17
- machineconfig/scripts/python/utils.py +2 -0
- 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/path_helper.py +1 -1
- machineconfig/utils/scheduling.py +0 -2
- machineconfig/utils/ssh.py +2 -2
- {machineconfig-6.84.dist-info → machineconfig-6.85.dist-info}/METADATA +1 -1
- {machineconfig-6.84.dist-info → machineconfig-6.85.dist-info}/RECORD +34 -31
- machineconfig/scripts/linux/warp-cli.sh +0 -122
- {machineconfig-6.84.dist-info → machineconfig-6.85.dist-info}/WHEEL +0 -0
- {machineconfig-6.84.dist-info → machineconfig-6.85.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.84.dist-info → machineconfig-6.85.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
import json
|
|
5
|
+
import shlex
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
POWERSHELL_CMD = "powershell" if __import__("platform").system().lower() == "windows" else "pwsh"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def generate_random_suffix(length: int) -> str:
|
|
18
|
+
"""Generate a random string suffix for unique PowerShell script names."""
|
|
19
|
+
return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_command(command: str) -> tuple[str, list[str]]:
|
|
23
|
+
try:
|
|
24
|
+
parts = shlex.split(command)
|
|
25
|
+
if not parts:
|
|
26
|
+
raise ValueError("Empty command provided")
|
|
27
|
+
return parts[0], parts[1:] if len(parts) > 1 else []
|
|
28
|
+
except ValueError as e:
|
|
29
|
+
logger.error(f"Error parsing command '{command}': {e}")
|
|
30
|
+
parts = command.split()
|
|
31
|
+
return parts[0] if parts else "", parts[1:] if len(parts) > 1 else []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def escape_for_wt(text: str) -> str:
|
|
35
|
+
"""Escape text for use in Windows Terminal commands."""
|
|
36
|
+
text = text.replace('"', '""')
|
|
37
|
+
if " " in text or ";" in text or "&" in text or "|" in text:
|
|
38
|
+
return f'"{text}"'
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_layout_config(layout_config: LayoutConfig) -> None:
|
|
43
|
+
"""Validate layout configuration format and content."""
|
|
44
|
+
if not layout_config["layoutTabs"]:
|
|
45
|
+
raise ValueError("Layout must contain at least one tab")
|
|
46
|
+
for tab in layout_config["layoutTabs"]:
|
|
47
|
+
if not tab["tabName"].strip():
|
|
48
|
+
raise ValueError(f"Invalid tab name: {tab['tabName']}")
|
|
49
|
+
if not tab["command"].strip():
|
|
50
|
+
raise ValueError(f"Invalid command for tab '{tab['tabName']}': {tab['command']}")
|
|
51
|
+
if not tab["startDir"].strip():
|
|
52
|
+
raise ValueError(f"Invalid startDir for tab '{tab['tabName']}': {tab['startDir']}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def generate_wt_command_string(layout_config: LayoutConfig, window_name: str) -> str:
|
|
56
|
+
"""Generate complete Windows Terminal command string."""
|
|
57
|
+
command_parts = []
|
|
58
|
+
|
|
59
|
+
for i, tab in enumerate(layout_config["layoutTabs"]):
|
|
60
|
+
is_first = i == 0
|
|
61
|
+
|
|
62
|
+
if is_first:
|
|
63
|
+
tab_parts = ["wt", "-w", escape_for_wt(window_name)]
|
|
64
|
+
else:
|
|
65
|
+
tab_parts = ["new-tab"]
|
|
66
|
+
|
|
67
|
+
tab_name = tab["tabName"]
|
|
68
|
+
cwd = tab["startDir"]
|
|
69
|
+
command = tab["command"]
|
|
70
|
+
|
|
71
|
+
if cwd.startswith("~/"):
|
|
72
|
+
cwd = cwd.replace("~/", f"{Path.home()}/")
|
|
73
|
+
elif cwd == "~":
|
|
74
|
+
cwd = str(Path.home())
|
|
75
|
+
|
|
76
|
+
tab_parts.extend(["-d", escape_for_wt(cwd)])
|
|
77
|
+
tab_parts.extend(["--title", escape_for_wt(tab_name)])
|
|
78
|
+
tab_parts.append("--")
|
|
79
|
+
|
|
80
|
+
# Split the command into arguments
|
|
81
|
+
command_args = shlex.split(command)
|
|
82
|
+
tab_parts.extend(command_args)
|
|
83
|
+
|
|
84
|
+
command_parts.append(" ".join(tab_parts))
|
|
85
|
+
|
|
86
|
+
return " `; ".join(command_parts)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def check_wt_session_status(session_name: str) -> dict[str, Any]:
|
|
90
|
+
try:
|
|
91
|
+
ps_script = """
|
|
92
|
+
try {
|
|
93
|
+
$wtProcesses = Get-Process -Name 'WindowsTerminal' -ErrorAction SilentlyContinue
|
|
94
|
+
if ($wtProcesses) {
|
|
95
|
+
$processInfo = @()
|
|
96
|
+
$wtProcesses | ForEach-Object {
|
|
97
|
+
$info = @{
|
|
98
|
+
"Id" = $_.Id
|
|
99
|
+
"ProcessName" = $_.ProcessName
|
|
100
|
+
"StartTime" = $_.StartTime.ToString()
|
|
101
|
+
}
|
|
102
|
+
$processInfo += $info
|
|
103
|
+
}
|
|
104
|
+
$processInfo | ConvertTo-Json -Depth 2
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
# No Windows Terminal processes found
|
|
108
|
+
}
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
result = subprocess.run([POWERSHELL_CMD, "-Command", ps_script], capture_output=True, text=True, timeout=5)
|
|
112
|
+
|
|
113
|
+
if result.returncode == 0:
|
|
114
|
+
output = result.stdout.strip()
|
|
115
|
+
if output and output != "":
|
|
116
|
+
try:
|
|
117
|
+
processes = json.loads(output)
|
|
118
|
+
if not isinstance(processes, list):
|
|
119
|
+
processes = [processes]
|
|
120
|
+
|
|
121
|
+
return {"wt_running": True, "session_exists": len(processes) > 0, "session_name": session_name, "all_windows": processes, "session_windows": processes}
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return {"wt_running": True, "session_exists": False, "error": f"Failed to parse process info: {e}", "session_name": session_name}
|
|
124
|
+
else:
|
|
125
|
+
return {"wt_running": False, "session_exists": False, "session_name": session_name, "all_windows": []}
|
|
126
|
+
else:
|
|
127
|
+
return {"wt_running": False, "error": result.stderr, "session_name": session_name}
|
|
128
|
+
|
|
129
|
+
except subprocess.TimeoutExpired:
|
|
130
|
+
return {"wt_running": False, "error": "Timeout while checking Windows Terminal processes", "session_name": session_name}
|
|
131
|
+
except FileNotFoundError:
|
|
132
|
+
return {"wt_running": False, "error": f"PowerShell ({POWERSHELL_CMD}) not found in PATH", "session_name": session_name}
|
|
133
|
+
except Exception as e:
|
|
134
|
+
return {"wt_running": False, "error": str(e), "session_name": session_name}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def check_command_status(tab_name: str, layout_config: LayoutConfig) -> dict[str, Any]:
|
|
138
|
+
"""Check if a command is running by looking for processes."""
|
|
139
|
+
tab_config = None
|
|
140
|
+
for tab in layout_config["layoutTabs"]:
|
|
141
|
+
if tab["tabName"] == tab_name:
|
|
142
|
+
tab_config = tab
|
|
143
|
+
break
|
|
144
|
+
|
|
145
|
+
if tab_config is None:
|
|
146
|
+
return {"status": "unknown", "error": f"Tab '{tab_name}' not found in layout config", "running": False, "pid": None, "command": None}
|
|
147
|
+
|
|
148
|
+
command = tab_config["command"]
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
primary_cmd = command.split()[0] if command.strip() else ""
|
|
152
|
+
if not primary_cmd:
|
|
153
|
+
return {"status": "error", "error": "Empty command", "running": False, "command": command, "tab_name": tab_name}
|
|
154
|
+
|
|
155
|
+
ps_script = f"""
|
|
156
|
+
try {{
|
|
157
|
+
$processes = Get-Process -Name '{primary_cmd}' -ErrorAction SilentlyContinue
|
|
158
|
+
if ($processes) {{
|
|
159
|
+
$processes | ForEach-Object {{
|
|
160
|
+
$procInfo = @{{
|
|
161
|
+
"pid" = $_.Id
|
|
162
|
+
"name" = $_.ProcessName
|
|
163
|
+
"start_time" = $_.StartTime.ToString()
|
|
164
|
+
}}
|
|
165
|
+
Write-Output ($procInfo | ConvertTo-Json -Compress)
|
|
166
|
+
}}
|
|
167
|
+
}}
|
|
168
|
+
}} catch {{
|
|
169
|
+
# No processes found or other error
|
|
170
|
+
}}
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
result = subprocess.run([POWERSHELL_CMD, "-Command", ps_script], capture_output=True, text=True, timeout=5)
|
|
174
|
+
|
|
175
|
+
if result.returncode == 0:
|
|
176
|
+
output_lines = [line.strip() for line in result.stdout.strip().split("\n") if line.strip()]
|
|
177
|
+
matching_processes = []
|
|
178
|
+
|
|
179
|
+
for line in output_lines:
|
|
180
|
+
if line.startswith("{") and line.endswith("}"):
|
|
181
|
+
try:
|
|
182
|
+
proc_info = json.loads(line)
|
|
183
|
+
matching_processes.append(proc_info)
|
|
184
|
+
except json.JSONDecodeError:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
if matching_processes:
|
|
188
|
+
return {"status": "running", "running": True, "processes": matching_processes, "command": command, "tab_name": tab_name}
|
|
189
|
+
else:
|
|
190
|
+
return {"status": "not_running", "running": False, "processes": [], "command": command, "tab_name": tab_name}
|
|
191
|
+
else:
|
|
192
|
+
return {"status": "error", "error": f"Command failed: {result.stderr}", "running": False, "command": command, "tab_name": tab_name}
|
|
193
|
+
|
|
194
|
+
except subprocess.TimeoutExpired:
|
|
195
|
+
logger.error(f"Timeout checking command status for tab '{tab_name}'")
|
|
196
|
+
return {"status": "timeout", "error": "Timeout checking process status", "running": False, "command": command, "tab_name": tab_name}
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error checking command status for tab '{tab_name}': {e}")
|
|
199
|
+
return {"status": "error", "error": str(e), "running": False, "command": command, "tab_name": tab_name}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def add_lint_and_type_check_task(repo_root: Path) -> None:
|
|
@@ -18,8 +19,12 @@ def add_lint_and_type_check_task(repo_root: Path) -> None:
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
if tasks_json_path.exists():
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
json_data = tasks_json_path.read_text(encoding="utf-8")
|
|
23
|
+
if not json_data.strip():
|
|
24
|
+
tasks_config: dict[str, Any] = {"version": "2.0.0", "tasks": []}
|
|
25
|
+
else:
|
|
26
|
+
tasks_config = json.loads(json_data)
|
|
27
|
+
assert isinstance(tasks_config, dict)
|
|
23
28
|
if "tasks" not in tasks_config:
|
|
24
29
|
tasks_config["tasks"] = []
|
|
25
30
|
existing_labels = {task.get("label") for task in tasks_config.get("tasks", [])}
|
|
@@ -7,7 +7,7 @@ fire
|
|
|
7
7
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
from machineconfig.utils.ve import
|
|
10
|
+
from machineconfig.utils.ve import get_ve_path_and_ipython_profile
|
|
11
11
|
from machineconfig.utils.options import choose_from_options
|
|
12
12
|
from machineconfig.utils.path_helper import match_file_name, sanitize_path
|
|
13
13
|
from machineconfig.utils.path_extended import PathExtended
|
|
@@ -38,10 +38,6 @@ def route(args: FireJobArgs, fire_args: str = "") -> None:
|
|
|
38
38
|
|
|
39
39
|
repo_root = get_repo_root(Path(choice_file))
|
|
40
40
|
print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
|
|
41
|
-
ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
|
|
42
|
-
if ipy_profile is None:
|
|
43
|
-
ipy_profile = "default"
|
|
44
|
-
|
|
45
41
|
if args.marimo:
|
|
46
42
|
tmp_dir = PathExtended.tmp().joinpath(f"tmp_scripts/marimo/{choice_file.stem}_{randstr()}")
|
|
47
43
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -58,15 +54,9 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
58
54
|
# ========================= preparing kwargs_dict
|
|
59
55
|
if choice_file.suffix == ".py":
|
|
60
56
|
kwargs_dict = extract_kwargs(args) # This now returns empty dict, but kept for compatibility
|
|
61
|
-
ve_root = args.ve or ve_root_from_file
|
|
62
|
-
if ve_root is None:
|
|
63
|
-
raise ValueError(f"Could not determine virtual environment for file {choice_file}. Please ensure it is within a recognized project structure or specify the `--ve` option.")
|
|
64
|
-
activate_ve_line = get_ve_activate_line(ve_root=ve_root)
|
|
65
57
|
else:
|
|
66
|
-
activate_ve_line = ""
|
|
67
58
|
kwargs_dict = {}
|
|
68
59
|
|
|
69
|
-
|
|
70
60
|
# ========================= choosing function to run
|
|
71
61
|
choice_function: Optional[str] = None # Initialize to avoid unbound variable
|
|
72
62
|
if args.choose_function:
|
|
@@ -77,58 +67,35 @@ uv run --project {repo_root} --with marimo marimo edit --host 0.0.0.0 marimo_nb.
|
|
|
77
67
|
|
|
78
68
|
if choice_file.suffix == ".py":
|
|
79
69
|
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import get_command_streamlit
|
|
80
|
-
if args.streamlit:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
if args.streamlit:
|
|
71
|
+
exe = get_command_streamlit(choice_file=choice_file, environment=args.environment, repo_root=repo_root)
|
|
72
|
+
exe = f"uv run {exe} "
|
|
73
|
+
elif args.jupyter: exe = "uv run jupyter-lab"
|
|
74
|
+
else:
|
|
75
|
+
if args.interactive:
|
|
76
|
+
_ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
|
|
77
|
+
if ipy_profile is None:
|
|
78
|
+
ipy_profile = "default"
|
|
79
|
+
exe = f"uv run ipython -i --no-banner --profile {ipy_profile} "
|
|
80
|
+
else:
|
|
81
|
+
exe = "uv run python "
|
|
84
82
|
elif choice_file.suffix == ".ps1" or choice_file.suffix == ".sh": exe = "."
|
|
85
83
|
elif choice_file.suffix == "": exe = ""
|
|
86
84
|
else: raise NotImplementedError(f"File type {choice_file.suffix} not supported, in the sense that I don't know how to fire it.")
|
|
87
85
|
|
|
88
86
|
if args.module or (args.debug and args.choose_function): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
|
|
89
87
|
assert choice_file.suffix == ".py", f"File must be a python file to be imported as a module. Got {choice_file}"
|
|
90
|
-
from machineconfig.scripts.python.helpers_fire.helpers4 import get_import_module_code
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
except (ImportError, ModuleNotFoundError) as ex:
|
|
100
|
-
print(fr"❌ Failed to import `{choice_file}` as a module: {{ex}} ")
|
|
101
|
-
print(fr"⚠️ Attempting import with ad-hoc `$PATH` manipulation. DO NOT pickle any objects in this session as correct deserialization cannot be guaranteed.")
|
|
102
|
-
import sys
|
|
103
|
-
sys.path.append(r'{PathExtended(choice_file).parent}')
|
|
104
|
-
{repo_root_add}
|
|
105
|
-
from {PathExtended(choice_file).stem} import *
|
|
106
|
-
print(fr"✅ Successfully imported `{choice_file}`")
|
|
107
|
-
"""
|
|
108
|
-
if choice_function is not None:
|
|
109
|
-
txt = (
|
|
110
|
-
txt
|
|
111
|
-
+ f"""
|
|
112
|
-
res = {choice_function}({("**" + str(kwargs_dict)) if kwargs_dict else ""})
|
|
113
|
-
"""
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
txt = (
|
|
117
|
-
f"""
|
|
118
|
-
try:
|
|
119
|
-
from rich.panel import Panel
|
|
120
|
-
from rich.console import Console
|
|
121
|
-
from rich.syntax import Syntax
|
|
122
|
-
console = Console()
|
|
123
|
-
console.print(Panel(Syntax(code=r'''{txt}''', lexer='python'), title='Import Script'), style="bold red")
|
|
124
|
-
except ImportError as _ex:
|
|
125
|
-
print(r'''{txt}''')
|
|
126
|
-
"""
|
|
127
|
-
+ txt
|
|
128
|
-
)
|
|
129
|
-
choice_file = PathExtended.tmp().joinpath(f"tmp_scripts/python/{PathExtended(choice_file).parent.name}_{PathExtended(choice_file).stem}_{randstr()}.py")
|
|
88
|
+
from machineconfig.scripts.python.helpers_fire.helpers4 import get_import_module_code, wrap_import_in_try_except
|
|
89
|
+
from machineconfig.utils.meta import lambda_to_python_script
|
|
90
|
+
from machineconfig.utils.code import print_code
|
|
91
|
+
import_code = get_import_module_code(str(choice_file))
|
|
92
|
+
import_code_robust = lambda_to_python_script(lambda: wrap_import_in_try_except(import_line=import_code, pyfile=str(choice_file), repo_root=str(repo_root) if repo_root is not None else None), in_global=True, import_module=False)
|
|
93
|
+
code_printing = lambda_to_python_script(lambda: print_code(code=import_code_robust, lexer="python", desc="import code"), in_global=True, import_module=False)
|
|
94
|
+
if choice_function is not None: calling = f"""res = {choice_function}({("**" + str(kwargs_dict)) if kwargs_dict else ""})"""
|
|
95
|
+
else: calling = """# No function selected to call. You can add your code here."""
|
|
96
|
+
choice_file = Path.home().joinpath(f"tmp_results/tmp_scripts/python/{Path(choice_file).parent.name}_{Path(choice_file).stem}_{randstr()}.py")
|
|
130
97
|
choice_file.parent.mkdir(parents=True, exist_ok=True)
|
|
131
|
-
choice_file.write_text(
|
|
98
|
+
choice_file.write_text(import_code_robust + "\n" + code_printing + "\n" + calling, encoding="utf-8")
|
|
132
99
|
|
|
133
100
|
# ========================= determining basic command structure: putting together exe & choice_file & choice_function & pdb
|
|
134
101
|
if args.debug:
|
|
@@ -149,26 +116,24 @@ except ImportError as _ex:
|
|
|
149
116
|
command = f"{exe} {choice_file}"
|
|
150
117
|
else:
|
|
151
118
|
command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {PathExtended.cwd()}"
|
|
152
|
-
|
|
153
119
|
elif args.cmd:
|
|
154
120
|
command = rf""" cd /d {choice_file.parent} & {exe} {choice_file.name} """
|
|
155
121
|
else:
|
|
156
122
|
if choice_file.suffix == "": command = f"{exe} {choice_file} {fire_args}"
|
|
157
123
|
else: command = f"{exe} {choice_file} "
|
|
158
124
|
|
|
159
|
-
|
|
125
|
+
|
|
126
|
+
if not args.cmd: pass
|
|
160
127
|
else:
|
|
161
128
|
new_line = "\n"
|
|
162
|
-
command = rf"""start cmd -Argument "/k {
|
|
129
|
+
command = rf"""start cmd -Argument "/k {command.replace(new_line, " & ")} " """ # this works from powershell
|
|
163
130
|
if args.submit_to_cloud:
|
|
164
|
-
command = f"""
|
|
165
|
-
{activate_ve_line}
|
|
166
|
-
python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
131
|
+
command = f"""uv run python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
167
132
|
if choice_function is not None:
|
|
168
133
|
command += f"--function {choice_function} "
|
|
169
134
|
|
|
170
|
-
if args.optimized:
|
|
171
|
-
|
|
135
|
+
if args.optimized: command = command.replace("python ", "python -OO ")
|
|
136
|
+
|
|
172
137
|
from rich.panel import Panel
|
|
173
138
|
from rich.console import Console
|
|
174
139
|
from rich.syntax import Syntax
|
|
@@ -207,7 +172,7 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
|
|
|
207
172
|
import os
|
|
208
173
|
op_program_path = os.environ.get("OP_PROGRAM_PATH", None)
|
|
209
174
|
if op_program_path is not None:
|
|
210
|
-
op_program_path =
|
|
175
|
+
op_program_path = Path(op_program_path)
|
|
211
176
|
op_program_path.parent.mkdir(parents=True, exist_ok=True)
|
|
212
177
|
op_program_path.write_text(command, encoding="utf-8")
|
|
213
178
|
else:
|
|
@@ -46,7 +46,7 @@ def path():
|
|
|
46
46
|
uv_with = ["textual"]
|
|
47
47
|
uv_project_dir = None
|
|
48
48
|
if not Path.home().joinpath("code/machineconfig").exists():
|
|
49
|
-
uv_with.append("machineconfig>=6.
|
|
49
|
+
uv_with.append("machineconfig>=6.85")
|
|
50
50
|
else:
|
|
51
51
|
uv_project_dir = str(Path.home().joinpath("code/machineconfig"))
|
|
52
52
|
run_shell_script(get_uv_command_executing_python_script(python_script=path.read_text(encoding="utf-8"), uv_with=uv_with, uv_project_dir=uv_project_dir)[0])
|
|
@@ -54,6 +54,52 @@ def debug_ssh():
|
|
|
54
54
|
else:
|
|
55
55
|
raise NotImplementedError(f"Platform {system()} is not supported.")
|
|
56
56
|
|
|
57
|
+
def wifi_select(
|
|
58
|
+
ssid: Annotated[str, typer.Option("-n", "--ssid", help="🔗 SSID of WiFi (from config)")] = "MyPhoneHotSpot",
|
|
59
|
+
manual: Annotated[bool, typer.Option("-m", "--manual", help="🔍 Manual network selection mode")] = False,
|
|
60
|
+
list_: Annotated[bool, typer.Option("-l", "--list", help="📡 List available networks only")] = False,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Main function with fallback network selection"""
|
|
63
|
+
from rich.panel import Panel
|
|
64
|
+
from rich.prompt import Confirm
|
|
65
|
+
from rich.console import Console
|
|
66
|
+
from machineconfig.scripts.python.nw.wifi_conn import try_config_connection, manual_network_selection, display_available_networks
|
|
67
|
+
console = Console()
|
|
68
|
+
console.print(Panel("📶 Welcome to the WiFi Connector Tool", title="[bold blue]WiFi Connection[/bold blue]", border_style="blue"))
|
|
69
|
+
|
|
70
|
+
# If user just wants to list networks
|
|
71
|
+
if list_:
|
|
72
|
+
display_available_networks()
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# If user wants manual mode, skip config and go straight to selection
|
|
76
|
+
if manual:
|
|
77
|
+
console.print("[blue]🔍 Manual network selection mode[/blue]")
|
|
78
|
+
if manual_network_selection():
|
|
79
|
+
console.print("[green]🎉 Successfully connected![/green]")
|
|
80
|
+
else:
|
|
81
|
+
console.print("[red]❌ Failed to connect[/red]")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
# Try to connect using configuration first
|
|
85
|
+
console.print(f"[blue]🔍 Attempting to connect to configured network: {ssid}[/blue]")
|
|
86
|
+
|
|
87
|
+
if try_config_connection(ssid):
|
|
88
|
+
console.print("[green]🎉 Successfully connected using configuration![/green]")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Configuration failed, offer fallback options
|
|
92
|
+
console.print("\n[yellow]⚠️ Configuration connection failed or not available[/yellow]")
|
|
93
|
+
|
|
94
|
+
if Confirm.ask("[blue]Would you like to manually select a network?[/blue]", default=True):
|
|
95
|
+
if manual_network_selection():
|
|
96
|
+
console.print("[green]🎉 Successfully connected![/green]")
|
|
97
|
+
else:
|
|
98
|
+
console.print("[red]❌ Failed to connect[/red]")
|
|
99
|
+
else:
|
|
100
|
+
console.print("[blue]👋 Goodbye![/blue]")
|
|
101
|
+
|
|
102
|
+
|
|
57
103
|
def get_app():
|
|
58
104
|
nw_apps = typer.Typer(help="🔐 [n] Network subcommands", no_args_is_help=True, add_help_option=False, add_completion=False)
|
|
59
105
|
nw_apps.command(name="share-terminal", help="📡 [t] Share terminal via web browser")(cli_terminal.main)
|
|
@@ -70,4 +116,8 @@ def get_app():
|
|
|
70
116
|
nw_apps.command(name="a", help="Show this computer addresses on network", hidden=True)(show_address)
|
|
71
117
|
nw_apps.command(name="debug-ssh", help="🐛 [d] Debug SSH connection")(debug_ssh)
|
|
72
118
|
nw_apps.command(name="d", help="Debug SSH connection", hidden=True)(debug_ssh)
|
|
119
|
+
|
|
120
|
+
nw_apps.command(name="wifi-select", no_args_is_help=True, help="[w] WiFi connection utility.")(wifi_select)
|
|
121
|
+
nw_apps.command(name="w", no_args_is_help=True, hidden=True)(wifi_select)
|
|
122
|
+
|
|
73
123
|
return nw_apps
|
|
@@ -41,9 +41,9 @@ def install(no_copy_assets: Annotated[bool, typer.Option("--no-assets-copy", "-n
|
|
|
41
41
|
else:
|
|
42
42
|
import platform
|
|
43
43
|
if platform.system() == "Windows":
|
|
44
|
-
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=6.
|
|
44
|
+
run_shell_script(r"""& "$HOME\.local\bin\uv.exe" tool install --upgrade "machineconfig>=6.85" """)
|
|
45
45
|
else:
|
|
46
|
-
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=6.
|
|
46
|
+
run_shell_script("""$HOME/.local/bin/uv tool install --upgrade "machineconfig>=6.85" """)
|
|
47
47
|
from machineconfig.profile.create_shell_profile import create_default_shell_profile
|
|
48
48
|
if not no_copy_assets:
|
|
49
49
|
create_default_shell_profile() # involves copying assets too
|
|
@@ -68,7 +68,7 @@ def navigate():
|
|
|
68
68
|
path = Path(navigator.__file__).resolve().parent.joinpath("devops_navigator.py")
|
|
69
69
|
from machineconfig.utils.code import run_shell_script
|
|
70
70
|
if Path.home().joinpath("code/machineconfig").exists(): executable = f"""--project "{str(Path.home().joinpath("code/machineconfig"))}" --with textual"""
|
|
71
|
-
else: executable = """--with "machineconfig>=6.
|
|
71
|
+
else: executable = """--with "machineconfig>=6.85,textual" """
|
|
72
72
|
run_shell_script(f"""uv run {executable} {path}""")
|
|
73
73
|
|
|
74
74
|
|
|
@@ -198,7 +198,7 @@ def init_project(python: Annotated[Literal["3.13", "3.14"], typer.Option("--pyth
|
|
|
198
198
|
if not (repo_root / "pyproject.toml").exists():
|
|
199
199
|
typer.echo("❌ Error: pyproject.toml not found.", err=True)
|
|
200
200
|
raise typer.Exit(code=1)
|
|
201
|
-
print(
|
|
201
|
+
print("Adding group `plot` with common data science and plotting packages...")
|
|
202
202
|
script = """
|
|
203
203
|
uv add --group plot \
|
|
204
204
|
# Data & computation
|
|
@@ -110,3 +110,18 @@ def get_import_module_code(module_path: str):
|
|
|
110
110
|
module_name = "IncorrectModuleName"
|
|
111
111
|
# TODO: use py_compile to check if the statement is valid code to avoid syntax errors that can't be caught.
|
|
112
112
|
return f"from {module_name} import *"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def wrap_import_in_try_except(import_line: str, pyfile: str, repo_root: Optional[str] = None) -> None:
|
|
116
|
+
try:
|
|
117
|
+
exec(import_line) # type: ignore
|
|
118
|
+
except (ImportError, ModuleNotFoundError) as ex:
|
|
119
|
+
print(fr"❌ Failed to import `{pyfile}` as a module: {ex} ")
|
|
120
|
+
print("⚠️ Attempting import with ad-hoc `$PATH` manipulation. DO NOT pickle any objects in this session as correct deserialization cannot be guaranteed.")
|
|
121
|
+
import sys
|
|
122
|
+
from pathlib import Path
|
|
123
|
+
sys.path.append(str(Path(pyfile).parent))
|
|
124
|
+
if repo_root is not None:
|
|
125
|
+
sys.path.append(repo_root)
|
|
126
|
+
exec(f"from {Path(pyfile).stem} import *")
|
|
127
|
+
print(fr"✅ Successfully imported `{pyfile}`")
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# mkdir ~/data/local
|
|
6
6
|
# sudo mount -o nolock,noatime,nodiratime,proto=tcp,timeo=600,retrans=2,noac alex-p51s-5:/home/alex/data/local ./data/local
|
|
7
7
|
|
|
8
|
-
uv run --python 3.14 --with "machineconfig>=6.
|
|
8
|
+
uv run --python 3.14 --with "machineconfig>=6.85" python -m machineconfig.scripts.python.mount_nfs
|
|
9
9
|
# Check if remote server is reachable and share folder exists
|
|
10
10
|
if ! ping -c 1 "$remote_server" &> /dev/null; then
|
|
11
11
|
echo "💥 Error: Remote server $remote_server is not reachable."
|
|
@@ -28,8 +28,6 @@ Usage examples:
|
|
|
28
28
|
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
-
from typing import Annotated
|
|
32
|
-
import typer
|
|
33
31
|
import configparser
|
|
34
32
|
from pathlib import Path
|
|
35
33
|
import os
|
|
@@ -38,8 +36,7 @@ import subprocess
|
|
|
38
36
|
import getpass
|
|
39
37
|
from typing import List, Dict, Optional
|
|
40
38
|
from rich.console import Console
|
|
41
|
-
from rich.
|
|
42
|
-
from rich.prompt import Prompt, Confirm
|
|
39
|
+
from rich.prompt import Prompt
|
|
43
40
|
from rich.table import Table
|
|
44
41
|
|
|
45
42
|
console = Console()
|
|
@@ -263,51 +260,6 @@ def manual_network_selection() -> bool:
|
|
|
263
260
|
return False
|
|
264
261
|
|
|
265
262
|
|
|
266
|
-
def main(
|
|
267
|
-
ssid: Annotated[str, typer.Option("-n", "--ssid", help="🔗 SSID of WiFi (from config)")] = "MyPhoneHotSpot",
|
|
268
|
-
manual: Annotated[bool, typer.Option("-m", "--manual", help="🔍 Manual network selection mode")] = False,
|
|
269
|
-
list_: Annotated[bool, typer.Option("-l", "--list", help="📡 List available networks only")] = False,
|
|
270
|
-
) -> None:
|
|
271
|
-
"""Main function with fallback network selection"""
|
|
272
|
-
console.print(Panel("📶 Welcome to the WiFi Connector Tool", title="[bold blue]WiFi Connection[/bold blue]", border_style="blue"))
|
|
273
|
-
|
|
274
|
-
# If user just wants to list networks
|
|
275
|
-
if list_:
|
|
276
|
-
display_available_networks()
|
|
277
|
-
return
|
|
278
|
-
|
|
279
|
-
# If user wants manual mode, skip config and go straight to selection
|
|
280
|
-
if manual:
|
|
281
|
-
console.print("[blue]🔍 Manual network selection mode[/blue]")
|
|
282
|
-
if manual_network_selection():
|
|
283
|
-
console.print("[green]🎉 Successfully connected![/green]")
|
|
284
|
-
else:
|
|
285
|
-
console.print("[red]❌ Failed to connect[/red]")
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
# Try to connect using configuration first
|
|
289
|
-
console.print(f"[blue]🔍 Attempting to connect to configured network: {ssid}[/blue]")
|
|
290
|
-
|
|
291
|
-
if try_config_connection(ssid):
|
|
292
|
-
console.print("[green]🎉 Successfully connected using configuration![/green]")
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
# Configuration failed, offer fallback options
|
|
296
|
-
console.print("\n[yellow]⚠️ Configuration connection failed or not available[/yellow]")
|
|
297
|
-
|
|
298
|
-
if Confirm.ask("[blue]Would you like to manually select a network?[/blue]", default=True):
|
|
299
|
-
if manual_network_selection():
|
|
300
|
-
console.print("[green]🎉 Successfully connected![/green]")
|
|
301
|
-
else:
|
|
302
|
-
console.print("[red]❌ Failed to connect[/red]")
|
|
303
|
-
else:
|
|
304
|
-
console.print("[blue]👋 Goodbye![/blue]")
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def arg_parser() -> None:
|
|
308
|
-
typer.run(main)
|
|
309
|
-
|
|
310
|
-
|
|
311
263
|
def get_current_wifi_name() -> str:
|
|
312
264
|
"""Get the name of the currently connected WiFi network"""
|
|
313
265
|
console.print("\n[blue]🔍 Checking current WiFi connection...[/blue]")
|
|
@@ -412,7 +364,3 @@ def create_new_connection(name: str, ssid: str, password: str):
|
|
|
412
364
|
except Exception as e:
|
|
413
365
|
console.print(f"[red]❌ Unexpected error: {e}[/red]")
|
|
414
366
|
raise
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if __name__ == "__main__":
|
|
418
|
-
arg_parser()
|