machineconfig 3.2__py3-none-any.whl → 3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of machineconfig might be problematic. Click here for more details.

Files changed (122) hide show
  1. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  2. machineconfig/cluster/sessions_managers/wt_remote_manager.py +1 -1
  3. machineconfig/cluster/sessions_managers/zellij_local.py +2 -2
  4. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
  5. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +1 -1
  6. machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py +17 -7
  7. machineconfig/cluster/templates/utils.py +1 -1
  8. machineconfig/jobs/linux/msc/cli_agents.sh +18 -2
  9. machineconfig/jobs/python/python_ve_symlink.py +1 -1
  10. machineconfig/jobs/python/vscode/api.py +1 -1
  11. machineconfig/jobs/python/vscode/select_interpreter.py +2 -2
  12. machineconfig/jobs/python/vscode/sync_code.py +1 -1
  13. machineconfig/jobs/python_custom_installers/archive/ngrok.py +7 -6
  14. machineconfig/jobs/python_custom_installers/dev/aider.py +9 -1
  15. machineconfig/jobs/python_custom_installers/dev/alacritty.py +2 -1
  16. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -1
  17. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +12 -4
  18. machineconfig/jobs/python_custom_installers/dev/code.py +10 -3
  19. machineconfig/jobs/python_custom_installers/dev/cursor.py +2 -1
  20. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +7 -6
  21. machineconfig/jobs/python_custom_installers/dev/espanso.py +14 -6
  22. machineconfig/jobs/python_custom_installers/dev/goes.py +10 -1
  23. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -1
  24. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +9 -1
  25. machineconfig/jobs/python_custom_installers/dev/redis.py +2 -2
  26. machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -1
  27. machineconfig/jobs/python_custom_installers/dev/winget.py +2 -1
  28. machineconfig/jobs/python_custom_installers/docker.py +9 -1
  29. machineconfig/jobs/python_custom_installers/gh.py +11 -2
  30. machineconfig/jobs/python_custom_installers/hx.py +10 -9
  31. machineconfig/jobs/python_custom_installers/warp-cli.py +9 -1
  32. machineconfig/jobs/python_generic_installers/config.json +601 -412
  33. machineconfig/jobs/python_generic_installers/config.json.bak +414 -0
  34. machineconfig/jobs/python_generic_installers/dev/config.json +822 -562
  35. machineconfig/jobs/python_generic_installers/dev/config.json.bak +565 -0
  36. machineconfig/jobs/python_linux_installers/archive/config.json +16 -8
  37. machineconfig/jobs/python_linux_installers/archive/config.json.bak +10 -0
  38. machineconfig/jobs/python_linux_installers/config.json +134 -99
  39. machineconfig/jobs/python_linux_installers/config.json.bak +110 -0
  40. machineconfig/jobs/python_linux_installers/dev/config.json +273 -203
  41. machineconfig/jobs/python_linux_installers/dev/config.json.bak +206 -0
  42. machineconfig/jobs/python_windows_installers/config.json +74 -48
  43. machineconfig/jobs/python_windows_installers/config.json.bak +56 -0
  44. machineconfig/jobs/python_windows_installers/dev/config.json +3 -2
  45. machineconfig/jobs/python_windows_installers/dev/config.json.bak +3 -0
  46. machineconfig/profile/create.py +3 -3
  47. machineconfig/profile/shell.py +1 -1
  48. machineconfig/scripts/python/ai/mcinit.py +23 -67
  49. machineconfig/scripts/python/ai/solutions/__init__.py +0 -0
  50. machineconfig/scripts/python/ai/solutions/_shared.py +5 -0
  51. machineconfig/scripts/python/ai/solutions/claude/claude.py +8 -0
  52. machineconfig/scripts/python/ai/solutions/cline/cline.py +10 -0
  53. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +35 -0
  54. machineconfig/scripts/python/ai/solutions/copilot/privacy.md +4 -0
  55. machineconfig/scripts/python/ai/solutions/crush/crush.json +216 -0
  56. machineconfig/scripts/python/ai/solutions/crush/crush.py +25 -0
  57. machineconfig/scripts/python/ai/solutions/crush/privacy.md +2 -0
  58. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +10 -0
  59. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +14 -0
  60. machineconfig/scripts/python/ai/solutions/generic.py +41 -0
  61. machineconfig/scripts/python/ai/solutions/kilocode/privacy.md +3 -0
  62. machineconfig/scripts/python/ai/solutions/opencode/opencode.json +4 -0
  63. machineconfig/scripts/python/ai/solutions/opencode/opencode.py +1 -0
  64. machineconfig/scripts/python/choose_wezterm_theme.py +1 -1
  65. machineconfig/scripts/python/cloud_copy.py +2 -2
  66. machineconfig/scripts/python/cloud_mount.py +2 -2
  67. machineconfig/scripts/python/cloud_repo_sync.py +3 -2
  68. machineconfig/scripts/python/croshell.py +12 -7
  69. machineconfig/scripts/python/devops.py +1 -0
  70. machineconfig/scripts/python/devops_add_identity.py +1 -1
  71. machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
  72. machineconfig/scripts/python/devops_backup_retrieve.py +4 -3
  73. machineconfig/scripts/python/devops_devapps_install.py +39 -17
  74. machineconfig/scripts/python/devops_update_repos.py +2 -2
  75. machineconfig/scripts/python/dotfile.py +1 -1
  76. machineconfig/scripts/python/fire_agents.py +7 -3
  77. machineconfig/scripts/python/fire_agents_help_launch.py +2 -2
  78. machineconfig/scripts/python/fire_jobs.py +8 -8
  79. machineconfig/scripts/python/fire_jobs_layout_helper.py +2 -2
  80. machineconfig/scripts/python/ftpx.py +2 -2
  81. machineconfig/scripts/python/helpers/cloud_helpers.py +2 -1
  82. machineconfig/scripts/python/helpers/helpers2.py +4 -3
  83. machineconfig/scripts/python/helpers/helpers4.py +1 -1
  84. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  85. machineconfig/scripts/python/mount_nfs.py +1 -1
  86. machineconfig/scripts/python/mount_ssh.py +1 -1
  87. machineconfig/scripts/python/repos.py +6 -3
  88. machineconfig/scripts/python/repos_helper_clone.py +121 -0
  89. machineconfig/scripts/python/repos_helper_record.py +2 -2
  90. machineconfig/scripts/python/start_slidev.py +1 -1
  91. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  92. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +9 -8
  93. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -3
  94. machineconfig/utils/{utils2.py → accessories.py} +13 -27
  95. machineconfig/utils/code.py +2 -2
  96. machineconfig/utils/installer.py +47 -33
  97. machineconfig/utils/installer_utils/installer_abc.py +2 -5
  98. machineconfig/utils/installer_utils/installer_class.py +109 -103
  99. machineconfig/utils/io.py +94 -0
  100. machineconfig/utils/links.py +2 -2
  101. machineconfig/utils/notifications.py +0 -9
  102. machineconfig/utils/{path_reduced.py → path_extended.py} +2 -2
  103. machineconfig/utils/{path.py → path_helper.py} +1 -1
  104. machineconfig/utils/procs.py +2 -1
  105. machineconfig/utils/{utils5.py → scheduler.py} +3 -8
  106. machineconfig/utils/schemas/installer/installer_types.py +20 -0
  107. machineconfig/utils/ssh.py +2 -2
  108. machineconfig/utils/terminal.py +12 -2
  109. machineconfig/utils/ve.py +2 -16
  110. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/METADATA +1 -4
  111. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/RECORD +121 -97
  112. machineconfig/utils/io_save.py +0 -95
  113. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Thinking-Beast-Mode.chatmode.md +0 -0
  114. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +0 -0
  115. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/deepResearch.chatmode.md +0 -0
  116. /machineconfig/scripts/python/ai/{instructions → solutions/copilot/instructions}/python/dev.instructions.md +0 -0
  117. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/allLintersAndTypeCheckers.prompt.md +0 -0
  118. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/research-report-skeleton.prompt.md +0 -0
  119. /machineconfig/scripts/python/ai/{configs/.gemini → solutions/gemini}/settings.json +0 -0
  120. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/WHEEL +0 -0
  121. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/entry_points.txt +0 -0
  122. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/top_level.txt +0 -0
@@ -1,63 +1,38 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
2
  from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
3
3
  from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT
4
4
  from machineconfig.utils.options import check_tool_exists
5
- from machineconfig.utils.utils2 import pprint, read_json
5
+ from machineconfig.utils.io import read_json
6
+ from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles
6
7
 
7
8
  import platform
8
9
  import subprocess
9
- from typing import Any, Optional
10
+ from typing import Optional
10
11
  from pathlib import Path
11
12
 
12
13
 
13
14
  class Installer:
14
- def __init__(
15
- self,
16
- repo_url: str,
17
- name: str,
18
- doc: str,
19
- strip_v: bool,
20
- exe_name: str,
21
- filename_template_windows_amd_64: str,
22
- filename_template_linux_amd_64: str,
23
- filename_template_windows_arm_64: Optional[str] = None,
24
- filename_template_linux_arm_64: Optional[str] = None,
25
- filename_template_macos_amd_64: Optional[str] = None,
26
- filename_template_macos_arm_64: Optional[str] = None,
27
- ):
28
- self.repo_url: str = repo_url
29
- self.name: str = name
30
- self.doc: str = doc
31
- self.filename_template_windows_amd_64: str = filename_template_windows_amd_64
32
- self.filename_template_windows_arm_64: Optional[str] = filename_template_windows_arm_64
33
- self.filename_template_linux_arm_64: Optional[str] = filename_template_linux_arm_64
34
- self.filename_template_linux_amd_64: str = filename_template_linux_amd_64
35
- self.filename_template_macos_amd_64: Optional[str] = filename_template_macos_amd_64
36
- self.filename_template_macos_arm_64: Optional[str] = filename_template_macos_arm_64
37
- self.strip_v: bool = strip_v
38
- self.exe_name: str = exe_name
15
+ def __init__(self, installer_data: InstallerData):
16
+ self.installer_data: InstallerData = installer_data
39
17
 
40
18
  def __repr__(self) -> str:
41
- return f"Installer of {self.exe_name} {self.name} @ {self.repo_url}"
19
+ exe_name = self.installer_data.get("exeName", "unknown")
20
+ app_name = self.installer_data.get("appName", "unknown")
21
+ repo_url = self.installer_data.get("repoURL", "unknown")
22
+ return f"Installer of {exe_name} {app_name} @ {repo_url}"
42
23
 
43
24
  def get_description(self):
44
25
  # old_version_cli = Terminal().run(f"{self.exe_name} --version").op.replace("\n", "")
45
26
  # old_version_cli = os.system(f"{self.exe_name} --version").replace("\n", "")
46
- old_version_cli: bool = check_tool_exists(tool_name=self.exe_name)
27
+ exe_name = self.installer_data.get("exeName", "")
28
+ if not exe_name:
29
+ return "Invalid installer: missing exeName"
30
+
31
+ old_version_cli: bool = check_tool_exists(tool_name=exe_name)
47
32
  old_version_cli_str = "✅" if old_version_cli else "❌"
48
33
  # name_version = f"{self.exe_name} {old_version_cli_str}"
49
- return f"{self.exe_name:<12} {old_version_cli_str} {self.doc}"
50
-
51
- def to_dict(self):
52
- return self.__dict__
53
-
54
- @staticmethod
55
- def from_dict(d: dict[str, Any], name: str):
56
- try:
57
- return Installer(name=name, **d)
58
- except Exception as ex:
59
- pprint(d, "Installer Creation Error")
60
- raise ex
34
+ doc = self.installer_data.get("doc", "No description")
35
+ return f"{exe_name:<12} {old_version_cli_str} {doc}"
61
36
 
62
37
  @staticmethod
63
38
  def choose_app_and_install():
@@ -69,49 +44,69 @@ class Installer:
69
44
  config_paths = [Path(p) for p in jobs_dir.rglob("config.json")]
70
45
  path = choose_one_option(options=config_paths)
71
46
  print(f"📄 Loading configuration from: {path}")
72
- config: dict[str, Any] = read_json(path) # /python_generic_installers/config.json"))
47
+ config_data = read_json(path)
48
+ installer_data_files = InstallerDataFiles(config_data)
49
+
50
+ # Extract app names from the installers
51
+ app_names = [installer["appName"] for installer in installer_data_files["installers"]]
73
52
  print("🔍 Select an application to install:")
74
- app_name = choose_one_option(options=list(config.keys()), fzf=True)
75
- # for keys, dict_ in config.items():
76
- installer = Installer.from_dict(d=config[app_name], name=app_name)
77
- print(f"📦 Selected application: {installer.exe_name}")
78
- version = input(f"📝 Enter version to install for {installer.exe_name} [latest]: ") or None
79
- print(f"\n{'=' * 80}\n🚀 INSTALLING {installer.exe_name.upper()} 🚀\n{'=' * 80}")
53
+ app_name = choose_one_option(options=app_names, fzf=True)
54
+
55
+ # Find the selected installer data
56
+ selected_installer_data = None
57
+ for installer_data in installer_data_files["installers"]:
58
+ if installer_data["appName"] == app_name:
59
+ selected_installer_data = installer_data
60
+ break
61
+
62
+ if selected_installer_data is None:
63
+ raise ValueError(f"Could not find installer data for {app_name}")
64
+
65
+ installer = Installer(installer_data=selected_installer_data)
66
+ print(f"📦 Selected application: {selected_installer_data.get('exeName', 'unknown')}")
67
+ version = input(f"📝 Enter version to install for {selected_installer_data.get('exeName', 'unknown')} [latest]: ") or None
68
+ print(f"\n{'=' * 80}\n🚀 INSTALLING {selected_installer_data.get('exeName', 'UNKNOWN').upper()} 🚀\n{'=' * 80}")
80
69
  installer.install(version=version)
81
70
 
82
71
  def install_robust(self, version: Optional[str]):
83
72
  try:
84
- print(f"\n{'=' * 80}\n🚀 INSTALLING {self.exe_name.upper()} 🚀\n{'=' * 80}")
85
- result_old = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
73
+ exe_name = self.installer_data.get("exeName", "unknown")
74
+ print(f"\n{'=' * 80}\n🚀 INSTALLING {exe_name.upper()} 🚀\n{'=' * 80}")
75
+ result_old = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
86
76
  old_version_cli = result_old.stdout.strip()
87
77
  print(f"📊 Current version: {old_version_cli or 'Not installed'}")
88
78
 
89
79
  self.install(version=version)
90
80
 
91
- result_new = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
81
+ result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
92
82
  new_version_cli = result_new.stdout.strip()
93
83
  print(f"📊 New version: {new_version_cli}")
94
84
 
95
85
  if old_version_cli == new_version_cli:
96
86
  print(f"ℹ️ Same version detected: {old_version_cli}")
97
- return f"""📦️ 😑 {self.exe_name}, same version: {old_version_cli}"""
87
+ return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
98
88
  else:
99
89
  print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
100
- return f"""📦️ 🤩 {self.exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
90
+ return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
101
91
 
102
92
  except Exception as ex:
103
- print(f"❌ ERROR: Installation failed for {self.exe_name}: {ex}")
104
- return f"""📦️ ❌ Failed to install `{self.name}` with error: {ex}"""
93
+ exe_name = self.installer_data.get("exeName", "unknown")
94
+ app_name = self.installer_data.get("appName", "unknown")
95
+ print(f"❌ ERROR: Installation failed for {exe_name}: {ex}")
96
+ return f"""📦️ ❌ Failed to install `{app_name}` with error: {ex}"""
105
97
 
106
98
  def install(self, version: Optional[str]):
107
- print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {self.exe_name} 🔧\n{'=' * 80}")
108
- if self.repo_url == "CUSTOM":
109
- print(f"🧩 Using custom installer for {self.exe_name}")
99
+ exe_name = self.installer_data.get("exeName", "unknown")
100
+ repo_url = self.installer_data.get("repoURL", "")
101
+
102
+ print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {exe_name} 🔧\n{'=' * 80}")
103
+ if repo_url == "CUSTOM":
104
+ print(f"🧩 Using custom installer for {exe_name}")
110
105
  import machineconfig.jobs.python_custom_installers as python_custom_installers
111
106
 
112
- installer_path = Path(python_custom_installers.__file__).parent.joinpath(self.exe_name + ".py")
107
+ installer_path = Path(python_custom_installers.__file__).parent.joinpath(exe_name + ".py")
113
108
  if not installer_path.exists():
114
- installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", self.exe_name + ".py")
109
+ installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", exe_name + ".py")
115
110
  print(f"🔍 Looking for installer in dev folder: {installer_path}")
116
111
  else:
117
112
  print(f"🔍 Found installer at: {installer_path}")
@@ -138,13 +133,13 @@ class Installer:
138
133
  version_to_be_installed = str(version)
139
134
  print(f"✅ Custom installation completed\n{'=' * 80}")
140
135
 
141
- elif "npm " in self.repo_url or "pip " in self.repo_url or "winget " in self.repo_url:
142
- package_manager = self.repo_url.split(" ", maxsplit=1)[0]
136
+ elif "npm " in repo_url or "pip " in repo_url or "winget " in repo_url:
137
+ package_manager = repo_url.split(" ", maxsplit=1)[0]
143
138
  print(f"📦 Using package manager: {package_manager}")
144
139
  desc = package_manager + " installation"
145
140
  version_to_be_installed = package_manager + "Latest"
146
- print(f"🚀 Running: {self.repo_url}")
147
- result = subprocess.run(self.repo_url, shell=True, capture_output=True, text=True)
141
+ print(f"🚀 Running: {repo_url}")
142
+ result = subprocess.run(repo_url, shell=True, capture_output=True, text=True)
148
143
  success = result.returncode == 0 and result.stderr == ""
149
144
  if not success:
150
145
  print(f"❌ {desc} failed")
@@ -177,38 +172,43 @@ class Installer:
177
172
  else:
178
173
  if platform.system() == "Windows":
179
174
  print("🪟 Installing on Windows...")
180
- exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=self.exe_name, delete=True, rename_to=self.exe_name.replace(".exe", "") + ".exe")
175
+ exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
181
176
  elif platform.system() in ["Linux", "Darwin"]:
182
177
  system_name = "Linux" if platform.system() == "Linux" else "macOS"
183
178
  print(f"🐧 Installing on {system_name}...")
184
- exe = find_move_delete_linux(downloaded=downloaded, tool_name=self.exe_name, delete=True, rename_to=self.exe_name)
179
+ exe = find_move_delete_linux(downloaded=downloaded, tool_name=exe_name, delete=True, rename_to=exe_name)
185
180
  else:
186
181
  error_msg = f"❌ ERROR: System {platform.system()} not supported"
187
182
  print(error_msg)
188
183
  raise NotImplementedError(error_msg)
189
184
 
190
185
  _ = exe
191
- if exe.name.replace(".exe", "") != self.exe_name.replace(".exe", ""):
186
+ if exe.name.replace(".exe", "") != exe_name.replace(".exe", ""):
192
187
  from rich import print as pprint
193
188
  from rich.panel import Panel
194
189
 
195
190
  print("⚠️ Warning: Executable name mismatch")
196
- pprint(Panel(f"Expected exe name: [red]{self.exe_name}[/red] \nAttained name: [red]{exe.name.replace('.exe', '')}[/red]", title="exe name mismatch", subtitle=self.repo_url))
197
- new_exe_name = self.exe_name + ".exe" if platform.system() == "Windows" else self.exe_name
191
+ pprint(Panel(f"Expected exe name: [red]{exe_name}[/red] \nAttained name: [red]{exe.name.replace('.exe', '')}[/red]", title="exe name mismatch", subtitle=repo_url))
192
+ new_exe_name = exe_name + ".exe" if platform.system() == "Windows" else exe_name
198
193
  print(f"🔄 Renaming to correct name: {new_exe_name}")
199
194
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
200
195
 
201
- print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(self.exe_name)}")
202
- INSTALL_VERSION_ROOT.joinpath(self.exe_name).parent.mkdir(parents=True, exist_ok=True)
203
- INSTALL_VERSION_ROOT.joinpath(self.exe_name).write_text(version_to_be_installed, encoding="utf-8")
196
+ print(f"💾 Saving version information to: {INSTALL_VERSION_ROOT.joinpath(exe_name)}")
197
+ INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
198
+ INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed, encoding="utf-8")
204
199
  print(f"✅ Installation completed successfully!\n{'=' * 80}")
205
200
 
206
201
  def download(self, version: Optional[str]):
207
- print(f"\n{'=' * 80}\n📥 DOWNLOADING: {self.exe_name} 📥\n{'=' * 80}")
202
+ exe_name = self.installer_data.get("exeName", "unknown")
203
+ repo_url = self.installer_data.get("repoURL", "")
204
+ app_name = self.installer_data.get("appName", "unknown")
205
+ strip_v = self.installer_data.get("stripVersion", False)
206
+
207
+ print(f"\n{'=' * 80}\n📥 DOWNLOADING: {exe_name} 📥\n{'=' * 80}")
208
208
  download_link: Optional[Path] = None
209
209
  version_to_be_installed: Optional[str] = None
210
- if "github" not in self.repo_url or ".zip" in self.repo_url or ".tar.gz" in self.repo_url:
211
- download_link = Path(self.repo_url)
210
+ if "github" not in repo_url or ".zip" in repo_url or ".tar.gz" in repo_url:
211
+ download_link = Path(repo_url)
212
212
  version_to_be_installed = "predefined_url"
213
213
  print(f"🔗 Using direct download URL: {download_link}")
214
214
  print(f"📦 Version to be installed: {version_to_be_installed}")
@@ -228,11 +228,11 @@ class Installer:
228
228
 
229
229
  else:
230
230
  print("🌐 Retrieving release information from GitHub...")
231
- release_url, version_to_be_installed = Installer.get_github_release(repo_url=self.repo_url, version=version)
231
+ release_url, version_to_be_installed = Installer.get_github_release(repo_url=repo_url, version=version)
232
232
  print(f"📦 Version to be installed: {version_to_be_installed}")
233
233
  print(f"📦 Release URL: {release_url}")
234
234
 
235
- version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if self.strip_v else version_to_be_installed
235
+ version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if strip_v else version_to_be_installed
236
236
  version_to_be_installed_stripped = version_to_be_installed_stripped.replace("ipinfo-", "")
237
237
 
238
238
  template, arch = self._select_template()
@@ -247,7 +247,7 @@ class Installer:
247
247
 
248
248
  assert download_link is not None, "download_link must be set"
249
249
  assert version_to_be_installed is not None, "version_to_be_installed must be set"
250
- print(f"📥 Downloading {self.name} from: {download_link}")
250
+ print(f"📥 Downloading {app_name} from: {download_link}")
251
251
  downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
252
252
  print(f"✅ Download and extraction completed to: {downloaded}\n{'=' * 80}")
253
253
  return downloaded, version_to_be_installed
@@ -268,47 +268,53 @@ class Installer:
268
268
  return sys_
269
269
 
270
270
  def _any_direct_http_template(self) -> bool:
271
- templates: list[Optional[str]] = [
272
- self.filename_template_windows_amd_64,
273
- self.filename_template_windows_arm_64,
274
- self.filename_template_linux_amd_64,
275
- self.filename_template_linux_arm_64,
276
- self.filename_template_macos_amd_64,
277
- self.filename_template_macos_arm_64,
278
- ]
279
- return any(t for t in templates if t is not None and t.startswith("http"))
271
+ filename_templates = self.installer_data.get("filenameTemplate", {})
272
+ templates: list[str] = []
273
+
274
+ for arch_templates in filename_templates.values():
275
+ templates.extend([t for t in arch_templates.values() if t])
276
+
277
+ return any(t for t in templates if t.startswith("http"))
280
278
 
281
279
  def _select_template(self) -> tuple[str, str]:
282
280
  sys_name = platform.system()
283
281
  arch = self._normalized_arch()
282
+
283
+ filename_templates = self.installer_data.get("filenameTemplate", {})
284
+
285
+ # Get templates for each architecture
286
+ amd64_templates = filename_templates.get("amd64", {})
287
+ arm64_templates = filename_templates.get("arm64", {})
288
+
284
289
  # mapping logic
285
290
  candidates: list[str] = []
286
291
  template: Optional[str] = None
292
+
287
293
  if sys_name == "Windows":
288
- if arch == "arm64" and self.filename_template_windows_arm_64:
289
- template = self.filename_template_windows_arm_64
294
+ if arch == "arm64" and arm64_templates.get("windows"):
295
+ template = arm64_templates["windows"]
290
296
  else:
291
- template = self.filename_template_windows_amd_64
292
- candidates = ["filename_template_windows_arm_64", "filename_template_windows_amd_64"]
297
+ template = amd64_templates.get("windows", "")
298
+ candidates = ["arm64.windows", "amd64.windows"]
293
299
  elif sys_name == "Linux":
294
- if arch == "arm64" and self.filename_template_linux_arm_64:
295
- template = self.filename_template_linux_arm_64
300
+ if arch == "arm64" and arm64_templates.get("linux"):
301
+ template = arm64_templates["linux"]
296
302
  else:
297
- template = self.filename_template_linux_amd_64
298
- candidates = ["filename_template_linux_arm_64", "filename_template_linux_amd_64"]
303
+ template = amd64_templates.get("linux", "")
304
+ candidates = ["arm64.linux", "amd64.linux"]
299
305
  elif sys_name == "Darwin":
300
- if arch == "arm64" and self.filename_template_macos_arm_64:
301
- template = self.filename_template_macos_arm_64
302
- elif arch == "amd64" and self.filename_template_macos_amd_64:
303
- template = self.filename_template_macos_amd_64
306
+ if arch == "arm64" and arm64_templates.get("macos"):
307
+ template = arm64_templates["macos"]
308
+ elif arch == "amd64" and amd64_templates.get("macos"):
309
+ template = amd64_templates["macos"]
304
310
  else:
305
311
  # fallback between available mac templates
306
- template = self.filename_template_macos_arm_64 or self.filename_template_macos_amd_64
307
- candidates = ["filename_template_macos_arm_64", "filename_template_macos_amd_64"]
312
+ template = arm64_templates.get("macos") or amd64_templates.get("macos") or ""
313
+ candidates = ["arm64.macos", "amd64.macos"]
308
314
  else:
309
315
  raise NotImplementedError(f"System {sys_name} not supported")
310
316
 
311
- if template is None:
317
+ if not template:
312
318
  raise ValueError(f"No filename template available for system={sys_name} arch={arch}. Checked {candidates}")
313
319
 
314
320
  return template, arch
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Union, Optional, Mapping
4
+ from pathlib import Path
5
+ import json
6
+ import pickle
7
+ import configparser
8
+
9
+
10
+ PathLike = Union[str, Path]
11
+
12
+
13
+ def _ensure_parent(path: PathLike) -> Path:
14
+ path_obj = Path(path)
15
+ path_obj.parent.mkdir(parents=True, exist_ok=True)
16
+ return path_obj
17
+
18
+
19
+ def save_pickle(obj: Any, path: PathLike, verbose: bool = False) -> Path:
20
+ path_obj = _ensure_parent(path)
21
+ with open(path_obj, "wb") as fh:
22
+ pickle.dump(obj, fh, protocol=pickle.HIGHEST_PROTOCOL)
23
+ if verbose:
24
+ print(f"Saved pickle -> {path_obj}")
25
+ return Path(path_obj)
26
+
27
+
28
+ def save_json(obj: Any, path: PathLike, indent: Optional[int] = None, verbose: bool = False) -> Path:
29
+ path_obj = _ensure_parent(path)
30
+ with open(path_obj, "w", encoding="utf-8") as fh:
31
+ json.dump(obj, fh, indent=indent, ensure_ascii=False)
32
+ fh.write("\n")
33
+ if verbose:
34
+ print(f"Saved json -> {path_obj}")
35
+ return Path(path_obj)
36
+
37
+
38
+ # def save_toml(obj: Mapping[str, Any], path: PathLike, verbose: bool = False) -> Path:
39
+ # path_obj = _ensure_parent(path)
40
+ # with open(path_obj, "w", encoding="utf-8") as fh:
41
+ # toml.dump(obj, fh)
42
+ # if verbose:
43
+ # print(f"Saved toml -> {path_obj}")
44
+ # return Path(path_obj)
45
+
46
+
47
+ # def save_yaml(obj: Any, path: PathLike, verbose: bool = False) -> Path:
48
+ # path_obj = _ensure_parent(path)
49
+ # with open(path_obj, "w", encoding="utf-8") as fh:
50
+ # yaml.safe_dump(obj, fh, sort_keys=False)
51
+ # if verbose:
52
+ # print(f"Saved yaml -> {path_obj}")
53
+ # return Path(path_obj)
54
+
55
+
56
+ def save_ini(path: PathLike, obj: Mapping[str, Mapping[str, Any]], verbose: bool = False) -> Path:
57
+ cp = configparser.ConfigParser()
58
+ for section, values in obj.items():
59
+ cp[section] = {str(k): str(v) for k, v in values.items()}
60
+ path_obj = _ensure_parent(path)
61
+ with open(path_obj, "w", encoding="utf-8") as fh:
62
+ cp.write(fh)
63
+ if verbose:
64
+ print(f"Saved ini -> {path_obj}")
65
+ return Path(path_obj)
66
+
67
+
68
+ def read_ini(path: "Path", encoding: Optional[str] = None):
69
+ if not Path(path).exists() or Path(path).is_dir():
70
+ raise FileNotFoundError(f"File not found or is a directory: {path}")
71
+ import configparser
72
+
73
+ res = configparser.ConfigParser()
74
+ res.read(filenames=[str(path)], encoding=encoding)
75
+ return res
76
+
77
+
78
+ def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
79
+ import json
80
+
81
+ try:
82
+ mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
83
+ except Exception:
84
+ import pyjson5
85
+
86
+ mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
87
+ _ = r
88
+ return mydict
89
+
90
+
91
+ def from_pickle(path: Path) -> Any:
92
+ import pickle
93
+
94
+ return pickle.loads(path.read_bytes())
@@ -1,5 +1,5 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended, PLike
2
- from machineconfig.utils.utils2 import randstr
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended, PLike
2
+ from machineconfig.utils.accessories import randstr
3
3
  from rich.console import Console
4
4
  from rich.panel import Panel
5
5
 
@@ -1,16 +1,8 @@
1
1
  """Notifications Module"""
2
2
 
3
- # from crocodile.core import install_n_import
4
- # from crocodile.file_management import P, Read
5
3
  from pathlib import Path
6
-
7
- # from crocodile.meta import RepeatUntilNoException
8
4
  import smtplib
9
5
  import imaplib
10
-
11
- # from email import message
12
- # from email import encoders
13
- # from email.mime.base import MIMEBase
14
6
  from email.mime.text import MIMEText
15
7
  from email.mime.multipart import MIMEMultipart
16
8
  from typing import Optional, Any, Union
@@ -192,7 +184,6 @@ encryption = ssl
192
184
  # else:
193
185
  # body_file_path = None
194
186
  # assert body is not None, "You must pass either body or body_file."
195
- # from crocodile.meta import Terminal
196
187
 
197
188
  # to_str = ",".join(to)
198
189
  # attachments_str = " ".join([f"--attachment {str(p)}" for p in attachments]) if attachments is not None else ""
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.utils2 import randstr
1
+ from machineconfig.utils.accessories import randstr
2
2
 
3
3
  from datetime import datetime
4
4
  import time
@@ -395,7 +395,7 @@ class PathExtended(type(Path()), Path): # type: ignore # pylint: disable=E0241
395
395
  from machineconfig.utils.terminal import Terminal
396
396
 
397
397
  if system() == "Windows" and not Terminal.is_user_admin(): # you cannot create symlink without priviliages.
398
- import win32com.shell.shell
398
+ import win32com.shell.shell # type: ignore # pylint: disable=E0401
399
399
 
400
400
  _proce_info = win32com.shell.shell.ShellExecuteEx(lpVerb="runas", lpFile=sys.executable, lpParameters=f" -c \"from pathlib import Path; Path(r'{self.expanduser()}').symlink_to(r'{str(target_obj)}')\"")
401
401
  # TODO update PATH for this to take effect immediately.
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
2
  from machineconfig.utils.options import choose_one_option
3
3
  from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
4
4
  from rich.console import Console
@@ -8,7 +8,7 @@ from typing import Optional, Any
8
8
  from rich.console import Console
9
9
  from rich.panel import Panel
10
10
  from datetime import datetime, timezone
11
- from machineconfig.utils.utils2 import pprint
11
+ from machineconfig.utils.accessories import pprint
12
12
 
13
13
  console = Console()
14
14
 
@@ -241,6 +241,7 @@ def get_age(create_time: Any) -> str:
241
241
 
242
242
  def main():
243
243
  from machineconfig.utils.procs import ProcessManager
244
+
244
245
  ProcessManager().choose_and_kill()
245
246
 
246
247
 
@@ -3,7 +3,8 @@ from typing import Callable, Optional, Union, Any, NoReturn, TypeVar, Protocol,
3
3
  import logging
4
4
  import time
5
5
  from datetime import datetime, timezone, timedelta
6
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
+ from machineconfig.utils.io import from_pickle
7
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
7
8
 
8
9
 
9
10
  class LoggerTemplate(Protocol):
@@ -108,7 +109,7 @@ class Scheduler:
108
109
  # "logfile": self.logger.file_path
109
110
  }
110
111
  summ.update(sess_stats)
111
- from machineconfig.utils.utils2 import get_repr
112
+ from machineconfig.utils.accessories import get_repr
112
113
 
113
114
  tmp = get_repr(summ)
114
115
  self.logger.critical("\n--> Scheduler has finished running a session. \n" + tmp + "\n" + "-" * 100)
@@ -157,12 +158,6 @@ def to_pickle(obj: Any, path: Path) -> None:
157
158
  path.write_bytes(pickle.dumps(obj))
158
159
 
159
160
 
160
- def from_pickle(path: Path) -> Any:
161
- import pickle
162
-
163
- return pickle.loads(path.read_bytes())
164
-
165
-
166
161
  class Cache(Generic[T]): # This class helps to accelrate access to latest data coming from expensive function. The class has two flavours, memory-based and disk-based variants."""
167
162
  # source_func: Callable[[], T]
168
163
  def __init__(
@@ -0,0 +1,20 @@
1
+ from typing import TypedDict, Literal, TypeAlias
2
+
3
+
4
+ APP_INSTALLER_CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
5
+ CPU_ARCHITECTURES: TypeAlias = Literal["amd64", "arm64"]
6
+ OPERATING_SYSTEMS: TypeAlias = Literal["windows", "linux", "macos"]
7
+
8
+
9
+ class InstallerData(TypedDict):
10
+ appName: str
11
+ repoURL: str
12
+ doc: str
13
+ filenameTemplate: dict[CPU_ARCHITECTURES, dict[OPERATING_SYSTEMS, str]]
14
+ stripVersion: bool
15
+ exeName: str
16
+
17
+
18
+ class InstallerDataFiles(TypedDict):
19
+ version: str
20
+ installers: list[InstallerData]
@@ -3,8 +3,8 @@ import os
3
3
  from dataclasses import dataclass
4
4
  import rich.console
5
5
  from machineconfig.utils.terminal import Terminal, Response, MACHINE
6
- from machineconfig.utils.path_reduced import PathExtended, PLike, OPLike
7
- from machineconfig.utils.utils2 import pprint
6
+ from machineconfig.utils.path_extended import PathExtended, PLike, OPLike
7
+ from machineconfig.utils.accessories import pprint
8
8
  # from machineconfig.utils.ve import get_ve_activate_line
9
9
 
10
10
 
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.path_reduced import PathExtended, OPLike
1
+ from machineconfig.utils.path_extended import PathExtended, OPLike
2
2
  import subprocess
3
3
  from typing import Any, BinaryIO, Optional, Union
4
4
  import platform
@@ -219,9 +219,19 @@ class Terminal:
219
219
 
220
220
  @staticmethod
221
221
  def get_header(wdir: OPLike, toolbox: bool):
222
+ if toolbox:
223
+ toobox_code = """
224
+ try:
225
+ from crocodile.toolbox import *
226
+ except ImportError:
227
+ print("Crocodile not found, skipping import.")
228
+ pass
229
+ """
230
+ else:
231
+ toobox_code = "# No toolbox import."
222
232
  return f"""
223
233
  # >> Code prepended
224
- {"from crocodile.toolbox import *" if toolbox else "# No toolbox import."}
234
+ {toobox_code}
225
235
  {'''sys.path.insert(0, r'{wdir}') ''' if wdir is not None else "# No path insertion."}
226
236
  # >> End of header, start of script passed
227
237
  """
machineconfig/utils/ve.py CHANGED
@@ -1,8 +1,7 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
2
- from machineconfig.utils.utils2 import read_ini
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
+ from machineconfig.utils.io import read_ini
3
3
  import platform
4
4
  from typing import Optional
5
- from pathlib import Path
6
5
 
7
6
 
8
7
  def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[str], Optional[str]]:
@@ -39,19 +38,6 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
39
38
  return ve_path, ipy_profile
40
39
 
41
40
 
42
- def get_repo_root(path: Path) -> Optional[Path]:
43
- from git import Repo, InvalidGitRepositoryError
44
-
45
- try:
46
- repo = Repo(str(path), search_parent_directories=True)
47
- root = repo.working_tree_dir
48
- if root is not None:
49
- return Path(root)
50
- except InvalidGitRepositoryError:
51
- pass
52
- return None
53
-
54
-
55
41
  def get_ve_activate_line(ve_root: str):
56
42
  if platform.system() == "Windows":
57
43
  activate_ve_line = f". {ve_root}/Scripts/activate.ps1"