machineconfig 2.0__py3-none-any.whl → 2.2__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 (253) hide show
  1. machineconfig/cluster/cloud_manager.py +0 -3
  2. machineconfig/cluster/data_transfer.py +0 -1
  3. machineconfig/cluster/file_manager.py +0 -1
  4. machineconfig/cluster/job_params.py +0 -3
  5. machineconfig/cluster/loader_runner.py +0 -3
  6. machineconfig/cluster/remote_machine.py +0 -1
  7. machineconfig/cluster/script_notify_upon_completion.py +0 -1
  8. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +5 -6
  9. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -1
  10. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +17 -57
  11. machineconfig/cluster/sessions_managers/wt_local.py +36 -110
  12. machineconfig/cluster/sessions_managers/wt_local_manager.py +42 -112
  13. machineconfig/cluster/sessions_managers/wt_remote.py +23 -30
  14. machineconfig/cluster/sessions_managers/wt_remote_manager.py +20 -62
  15. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +10 -15
  16. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +27 -127
  17. machineconfig/cluster/sessions_managers/wt_utils/remote_executor.py +10 -43
  18. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +22 -101
  19. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +11 -39
  20. machineconfig/cluster/sessions_managers/zellij_local.py +49 -102
  21. machineconfig/cluster/sessions_managers/zellij_local_manager.py +34 -78
  22. machineconfig/cluster/sessions_managers/zellij_remote.py +17 -24
  23. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +7 -13
  24. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +4 -2
  25. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +6 -6
  26. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +18 -88
  27. machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py +2 -6
  28. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +12 -40
  29. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +3 -2
  30. machineconfig/cluster/templates/cli_click.py +0 -1
  31. machineconfig/cluster/templates/cli_gooey.py +0 -2
  32. machineconfig/cluster/templates/cli_trogon.py +0 -1
  33. machineconfig/cluster/templates/run_cloud.py +0 -1
  34. machineconfig/cluster/templates/run_cluster.py +0 -1
  35. machineconfig/cluster/templates/run_remote.py +0 -1
  36. machineconfig/cluster/templates/utils.py +27 -46
  37. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  38. machineconfig/jobs/linux/msc/cli_agents.sh +16 -0
  39. machineconfig/jobs/python/check_installations.py +2 -1
  40. machineconfig/jobs/python/create_bootable_media.py +0 -2
  41. machineconfig/jobs/python/python_ve_symlink.py +9 -11
  42. machineconfig/jobs/python/tasks.py +0 -1
  43. machineconfig/jobs/python/vscode/api.py +5 -5
  44. machineconfig/jobs/python/vscode/link_ve.py +13 -14
  45. machineconfig/jobs/python/vscode/select_interpreter.py +21 -22
  46. machineconfig/jobs/python/vscode/sync_code.py +9 -13
  47. machineconfig/jobs/python_custom_installers/archive/ngrok.py +13 -13
  48. machineconfig/jobs/python_custom_installers/dev/aider.py +7 -15
  49. machineconfig/jobs/python_custom_installers/dev/alacritty.py +9 -18
  50. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -19
  51. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +8 -15
  52. machineconfig/jobs/python_custom_installers/dev/code.py +12 -32
  53. machineconfig/jobs/python_custom_installers/dev/cursor.py +3 -14
  54. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +8 -7
  55. machineconfig/jobs/python_custom_installers/dev/espanso.py +15 -19
  56. machineconfig/jobs/python_custom_installers/dev/goes.py +5 -12
  57. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -17
  58. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +12 -19
  59. machineconfig/jobs/python_custom_installers/dev/redis.py +12 -20
  60. machineconfig/jobs/python_custom_installers/dev/wezterm.py +12 -19
  61. machineconfig/jobs/python_custom_installers/dev/winget.py +5 -23
  62. machineconfig/jobs/python_custom_installers/docker.py +12 -21
  63. machineconfig/jobs/python_custom_installers/gh.py +11 -19
  64. machineconfig/jobs/python_custom_installers/hx.py +32 -16
  65. machineconfig/jobs/python_custom_installers/warp-cli.py +12 -20
  66. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  67. machineconfig/jobs/python_generic_installers/config.json +1 -1
  68. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  69. machineconfig/jobs/windows/archive/archive_pygraphviz.ps1 +1 -1
  70. machineconfig/jobs/windows/msc/cli_agents.bat +0 -0
  71. machineconfig/jobs/windows/msc/cli_agents.ps1 +0 -0
  72. machineconfig/jobs/windows/start_terminal.ps1 +1 -1
  73. machineconfig/profile/create.py +38 -26
  74. machineconfig/profile/create_hardlinks.py +29 -20
  75. machineconfig/profile/shell.py +56 -32
  76. machineconfig/scripts/__init__.py +0 -2
  77. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  78. machineconfig/scripts/cloud/init.sh +2 -2
  79. machineconfig/scripts/linux/checkout_versions +1 -1
  80. machineconfig/scripts/linux/choose_wezterm_theme +1 -1
  81. machineconfig/scripts/linux/cloud_copy +1 -1
  82. machineconfig/scripts/linux/cloud_manager +1 -1
  83. machineconfig/scripts/linux/cloud_mount +1 -1
  84. machineconfig/scripts/linux/cloud_repo_sync +1 -1
  85. machineconfig/scripts/linux/cloud_sync +1 -1
  86. machineconfig/scripts/linux/croshell +1 -1
  87. machineconfig/scripts/linux/devops +7 -7
  88. machineconfig/scripts/linux/fire +1 -1
  89. machineconfig/scripts/linux/fire_agents +3 -2
  90. machineconfig/scripts/linux/ftpx +1 -1
  91. machineconfig/scripts/linux/gh_models +1 -1
  92. machineconfig/scripts/linux/kill_process +1 -1
  93. machineconfig/scripts/linux/mcinit +1 -1
  94. machineconfig/scripts/linux/repos +1 -1
  95. machineconfig/scripts/linux/scheduler +1 -1
  96. machineconfig/scripts/linux/start_slidev +1 -1
  97. machineconfig/scripts/linux/start_terminals +1 -1
  98. machineconfig/scripts/linux/url2md +1 -1
  99. machineconfig/scripts/linux/warp-cli.sh +122 -0
  100. machineconfig/scripts/linux/wifi_conn +1 -1
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/ai/__init__.py +0 -0
  107. machineconfig/scripts/python/ai/generate_files.py +83 -0
  108. machineconfig/scripts/python/ai/instructions/python/dev.instructions.md +2 -2
  109. machineconfig/scripts/python/ai/mcinit.py +14 -7
  110. machineconfig/scripts/python/ai/scripts/lint_and_type_check.sh +10 -5
  111. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  112. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  113. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  114. machineconfig/scripts/python/cloud_copy.py +23 -14
  115. machineconfig/scripts/python/cloud_mount.py +36 -24
  116. machineconfig/scripts/python/cloud_repo_sync.py +40 -27
  117. machineconfig/scripts/python/cloud_sync.py +4 -4
  118. machineconfig/scripts/python/croshell.py +40 -29
  119. machineconfig/scripts/python/devops.py +45 -27
  120. machineconfig/scripts/python/devops_add_identity.py +15 -25
  121. machineconfig/scripts/python/devops_add_ssh_key.py +8 -8
  122. machineconfig/scripts/python/devops_backup_retrieve.py +18 -16
  123. machineconfig/scripts/python/devops_devapps_install.py +25 -20
  124. machineconfig/scripts/python/devops_update_repos.py +232 -59
  125. machineconfig/scripts/python/dotfile.py +17 -15
  126. machineconfig/scripts/python/fire_agents.py +48 -22
  127. machineconfig/scripts/python/fire_jobs.py +93 -58
  128. machineconfig/scripts/python/ftpx.py +26 -15
  129. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  130. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  131. machineconfig/scripts/python/helpers/helpers2.py +27 -16
  132. machineconfig/scripts/python/helpers/helpers4.py +45 -32
  133. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  134. machineconfig/scripts/python/helpers/repo_sync_helpers.py +32 -10
  135. machineconfig/scripts/python/mount_nfs.py +9 -16
  136. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  137. machineconfig/scripts/python/mount_ssh.py +9 -7
  138. machineconfig/scripts/python/repos.py +216 -58
  139. machineconfig/scripts/python/snapshot.py +0 -1
  140. machineconfig/scripts/python/start_slidev.py +11 -6
  141. machineconfig/scripts/python/start_terminals.py +22 -16
  142. machineconfig/scripts/python/viewer_template.py +0 -1
  143. machineconfig/scripts/python/wifi_conn.py +49 -75
  144. machineconfig/scripts/python/wsl_windows_transfer.py +9 -7
  145. machineconfig/scripts/windows/checkout_version.ps1 +1 -3
  146. machineconfig/scripts/windows/choose_wezterm_theme.ps1 +1 -3
  147. machineconfig/scripts/windows/cloud_copy.ps1 +2 -6
  148. machineconfig/scripts/windows/cloud_manager.ps1 +1 -1
  149. machineconfig/scripts/windows/cloud_repo_sync.ps1 +1 -2
  150. machineconfig/scripts/windows/cloud_sync.ps1 +2 -2
  151. machineconfig/scripts/windows/croshell.ps1 +2 -2
  152. machineconfig/scripts/windows/devops.ps1 +1 -4
  153. machineconfig/scripts/windows/dotfile.ps1 +1 -3
  154. machineconfig/scripts/windows/fire.ps1 +1 -1
  155. machineconfig/scripts/windows/ftpx.ps1 +2 -2
  156. machineconfig/scripts/windows/gpt.ps1 +1 -1
  157. machineconfig/scripts/windows/kill_process.ps1 +1 -2
  158. machineconfig/scripts/windows/mcinit.ps1 +1 -1
  159. machineconfig/scripts/windows/mount_nfs.ps1 +1 -1
  160. machineconfig/scripts/windows/mount_ssh.ps1 +1 -1
  161. machineconfig/scripts/windows/pomodoro.ps1 +1 -1
  162. machineconfig/scripts/windows/py2exe.ps1 +1 -3
  163. machineconfig/scripts/windows/repos.ps1 +1 -1
  164. machineconfig/scripts/windows/scheduler.ps1 +1 -1
  165. machineconfig/scripts/windows/snapshot.ps1 +2 -2
  166. machineconfig/scripts/windows/start_slidev.ps1 +1 -1
  167. machineconfig/scripts/windows/start_terminals.ps1 +1 -1
  168. machineconfig/scripts/windows/wifi_conn.ps1 +1 -1
  169. machineconfig/scripts/windows/wsl_windows_transfer.ps1 +1 -3
  170. machineconfig/settings/lf/linux/lfrc +2 -1
  171. machineconfig/settings/linters/.ruff_cache/.gitignore +2 -0
  172. machineconfig/settings/linters/.ruff_cache/CACHEDIR.TAG +1 -0
  173. machineconfig/settings/lvim/windows/archive/config_additional.lua +1 -1
  174. machineconfig/settings/svim/linux/init.toml +1 -1
  175. machineconfig/settings/svim/windows/init.toml +1 -1
  176. machineconfig/setup_linux/web_shortcuts/croshell.sh +3 -52
  177. machineconfig/setup_linux/web_shortcuts/interactive.sh +6 -6
  178. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  179. machineconfig/setup_windows/web_shortcuts/all.ps1 +2 -2
  180. machineconfig/setup_windows/web_shortcuts/ascii_art.ps1 +1 -1
  181. machineconfig/setup_windows/web_shortcuts/croshell.ps1 +1 -1
  182. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +5 -5
  183. machineconfig/setup_windows/wt_and_pwsh/install_fonts.ps1 +51 -15
  184. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +58 -13
  185. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +45 -37
  186. machineconfig/utils/ai/generate_file_checklist.py +8 -10
  187. machineconfig/utils/ai/url2md.py +4 -2
  188. machineconfig/utils/cloud/onedrive/setup_oauth.py +1 -0
  189. machineconfig/utils/cloud/onedrive/transaction.py +63 -98
  190. machineconfig/utils/code.py +62 -41
  191. machineconfig/utils/installer.py +29 -35
  192. machineconfig/utils/installer_utils/installer_abc.py +11 -11
  193. machineconfig/utils/installer_utils/installer_class.py +155 -74
  194. machineconfig/utils/links.py +112 -31
  195. machineconfig/utils/notifications.py +211 -0
  196. machineconfig/utils/options.py +41 -42
  197. machineconfig/utils/path.py +13 -6
  198. machineconfig/utils/path_reduced.py +614 -311
  199. machineconfig/utils/procs.py +48 -42
  200. machineconfig/utils/scheduling.py +0 -1
  201. machineconfig/utils/source_of_truth.py +27 -0
  202. machineconfig/utils/ssh.py +146 -85
  203. machineconfig/utils/terminal.py +84 -37
  204. machineconfig/utils/upgrade_packages.py +91 -0
  205. machineconfig/utils/utils2.py +39 -50
  206. machineconfig/utils/utils5.py +195 -116
  207. machineconfig/utils/ve.py +13 -5
  208. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/METADATA +14 -13
  209. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/RECORD +212 -237
  210. machineconfig/jobs/__pycache__/__init__.cpython-311.pyc +0 -0
  211. machineconfig/jobs/python/__pycache__/__init__.cpython-311.pyc +0 -0
  212. machineconfig/jobs/python/__pycache__/python_ve_symlink.cpython-311.pyc +0 -0
  213. machineconfig/jobs/python/archive/python_tools.txt +0 -12
  214. machineconfig/jobs/python/vscode/__pycache__/select_interpreter.cpython-311.pyc +0 -0
  215. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  216. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  217. machineconfig/jobs/python_generic_installers/update.py +0 -3
  218. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-311.pyc +0 -0
  219. machineconfig/profile/__pycache__/__init__.cpython-311.pyc +0 -0
  220. machineconfig/profile/__pycache__/create.cpython-311.pyc +0 -0
  221. machineconfig/profile/__pycache__/shell.cpython-311.pyc +0 -0
  222. machineconfig/scripts/__pycache__/__init__.cpython-311.pyc +0 -0
  223. machineconfig/scripts/linux/activate_ve +0 -87
  224. machineconfig/scripts/python/__pycache__/__init__.cpython-311.pyc +0 -0
  225. machineconfig/scripts/python/__pycache__/cloud_copy.cpython-311.pyc +0 -0
  226. machineconfig/scripts/python/__pycache__/cloud_mount.cpython-311.pyc +0 -0
  227. machineconfig/scripts/python/__pycache__/cloud_sync.cpython-311.pyc +0 -0
  228. machineconfig/scripts/python/__pycache__/croshell.cpython-311.pyc +0 -0
  229. machineconfig/scripts/python/__pycache__/devops.cpython-311.pyc +0 -0
  230. machineconfig/scripts/python/__pycache__/devops_backup_retrieve.cpython-311.pyc +0 -0
  231. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-311.pyc +0 -0
  232. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-311.pyc +0 -0
  233. machineconfig/scripts/python/__pycache__/fire_agents.cpython-311.pyc +0 -0
  234. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-311.pyc +0 -0
  235. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  236. machineconfig/scripts/python/__pycache__/get_zellij_cmd.cpython-311.pyc +0 -0
  237. machineconfig/scripts/python/__pycache__/repos.cpython-311.pyc +0 -0
  238. machineconfig/scripts/python/ai/__pycache__/init.cpython-311.pyc +0 -0
  239. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-311.pyc +0 -0
  240. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-311.pyc +0 -0
  241. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  242. machineconfig/scripts/python/helpers/__pycache__/cloud_helpers.cpython-311.pyc +0 -0
  243. machineconfig/scripts/python/helpers/__pycache__/helpers2.cpython-311.pyc +0 -0
  244. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-311.pyc +0 -0
  245. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  246. machineconfig/scripts/python/helpers/__pycache__/repo_sync_helpers.cpython-311.pyc +0 -0
  247. machineconfig/scripts/windows/activate_ve.ps1 +0 -54
  248. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  249. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  250. machineconfig/utils/utils.py +0 -95
  251. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  252. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/WHEEL +0 -0
  253. {machineconfig-2.0.dist-info → machineconfig-2.2.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,12 @@
1
- """package manager
2
- """
1
+ """package manager"""
2
+
3
3
  from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH, CATEGORY
4
4
  from machineconfig.utils.installer_utils.installer_class import Installer
5
5
  from rich.console import Console
6
- from rich.panel import Panel # Added import
6
+ from rich.panel import Panel # Added import
7
7
 
8
- from machineconfig.utils.path_reduced import P as PathExtended
9
- from machineconfig.utils.utils import INSTALL_VERSION_ROOT
8
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
9
+ from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT
10
10
  from machineconfig.utils.utils2 import read_json
11
11
 
12
12
  # from dataclasses import dataclass
@@ -16,8 +16,8 @@ from joblib import Parallel, delayed
16
16
 
17
17
 
18
18
  def check_latest():
19
- console = Console() # Added console initialization
20
- console.print(Panel("šŸ” CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
19
+ console = Console() # Added console initialization
20
+ console.print(Panel("šŸ” CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
21
21
  installers = get_installers(system=platform.system(), dev=False)
22
22
  # installers += get_installers(system=platform.system(), dev=True)
23
23
  installers_github = []
@@ -46,12 +46,7 @@ def check_latest():
46
46
  # Convert to list of dictionaries and group by status
47
47
  result_data = []
48
48
  for tool, status, current_ver, new_ver in res:
49
- result_data.append({
50
- "Tool": tool,
51
- "Status": status,
52
- "Current Version": current_ver,
53
- "New Version": new_ver
54
- })
49
+ result_data.append({"Tool": tool, "Status": status, "Current Version": current_ver, "New Version": new_ver})
55
50
 
56
51
  # Group by status
57
52
  grouped_data: dict[str, list[dict[str, Any]]] = {}
@@ -70,11 +65,11 @@ def check_latest():
70
65
  for item in items:
71
66
  print(f" {item['Tool']:<20} | Current: {item['Current Version']:<15} | New: {item['New Version']}")
72
67
  print("-" * 60)
73
- print(f"{'═'*80}")
68
+ print(f"{'═' * 80}")
74
69
 
75
70
 
76
71
  def get_installed_cli_apps():
77
- print(f"\n{'='*80}\nšŸ” LISTING INSTALLED CLI APPS šŸ”\n{'='*80}")
72
+ print(f"\n{'=' * 80}\nšŸ” LISTING INSTALLED CLI APPS šŸ”\n{'=' * 80}")
78
73
  if platform.system() == "Windows":
79
74
  print("🪟 Searching for Windows executables...")
80
75
  apps = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps").search("*.exe", not_in=["notepad"])
@@ -89,12 +84,12 @@ def get_installed_cli_apps():
89
84
  print(error_msg)
90
85
  raise NotImplementedError(error_msg)
91
86
  apps = [app for app in apps if app.size("kb") > 0.1 and not app.is_symlink()] # no symlinks like paint and wsl and bash
92
- print(f"āœ… Found {len(apps)} installed applications\n{'='*80}")
87
+ print(f"āœ… Found {len(apps)} installed applications\n{'=' * 80}")
93
88
  return apps
94
89
 
95
90
 
96
91
  def get_installers(system: str, dev: bool) -> list[Installer]:
97
- print(f"\n{'='*80}\nšŸ” LOADING INSTALLER CONFIGURATIONS šŸ”\n{'='*80}")
92
+ print(f"\n{'=' * 80}\nšŸ” LOADING INSTALLER CONFIGURATIONS šŸ”\n{'=' * 80}")
98
93
  res_all = get_all_dicts(system=system)
99
94
  if not dev:
100
95
  print("ā„¹ļø Excluding development installers...")
@@ -104,12 +99,12 @@ def get_installers(system: str, dev: bool) -> list[Installer]:
104
99
  res_final = {}
105
100
  for _k, v in res_all.items():
106
101
  res_final.update(v)
107
- print(f"āœ… Loaded {len(res_final)} installer configurations\n{'='*80}")
102
+ print(f"āœ… Loaded {len(res_final)} installer configurations\n{'=' * 80}")
108
103
  return [Installer.from_dict(d=vd, name=k) for k, vd in res_final.items()]
109
104
 
110
105
 
111
106
  def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
112
- print(f"\n{'='*80}\nšŸ“‚ LOADING CONFIGURATION FILES šŸ“‚\n{'='*80}")
107
+ print(f"\n{'=' * 80}\nšŸ“‚ LOADING CONFIGURATION FILES šŸ“‚\n{'=' * 80}")
113
108
 
114
109
  print(f"šŸ” Importing OS-specific installers for {system}...")
115
110
  if system == "Windows":
@@ -119,6 +114,7 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
119
114
 
120
115
  print("šŸ” Importing generic installers...")
121
116
  import machineconfig.jobs.python_generic_installers as generic_installer
117
+
122
118
  path_os_specific = PathExtended(os_specific_installer.__file__).parent
123
119
  path_os_generic = PathExtended(generic_installer.__file__).parent
124
120
 
@@ -127,7 +123,7 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
127
123
 
128
124
  print("šŸ“‚ Loading configuration files...")
129
125
  res_final: dict[CATEGORY, dict[str, dict[str, Any]]] = {}
130
- print(f"""šŸ“„ Loading OS-specific config from: {path_os_specific.joinpath('config.json')}""")
126
+ print(f"""šŸ“„ Loading OS-specific config from: {path_os_specific.joinpath("config.json")}""")
131
127
  res_final["OS_SPECIFIC"] = read_json(path=path_os_specific.joinpath("config.json"))
132
128
 
133
129
  print(f"""šŸ“„ Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
@@ -144,11 +140,12 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
144
140
 
145
141
  print(f"šŸ” Loading custom installers from: {path_custom_installer}")
146
142
  import runpy
143
+
147
144
  res_custom: dict[str, dict[str, Any]] = {}
148
145
  for item in path_custom_installer.search("*.py", r=False, not_in=["__init__"]):
149
146
  try:
150
147
  print(f"šŸ“„ Loading custom installer: {item.name}")
151
- config_dict = runpy.run_path(str(item), run_name=None)['config_dict']
148
+ config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
152
149
  res_custom[item.stem] = config_dict
153
150
  except Exception as ex:
154
151
  print(f"āŒ Failed to load {item}: {ex}")
@@ -158,7 +155,7 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
158
155
  for item in path_custom_installer_dev.search("*.py", r=False, not_in=["__init__"]):
159
156
  try:
160
157
  print(f"šŸ“„ Loading custom dev installer: {item.name}")
161
- config_dict = runpy.run_path(str(item), run_name=None)['config_dict']
158
+ config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
162
159
  res_custom_dev[item.stem] = config_dict
163
160
  except Exception as ex:
164
161
  print(f"āŒ Failed to load {item}: {ex}")
@@ -166,15 +163,15 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
166
163
  res_final["CUSTOM"] = res_custom
167
164
  res_final["CUSTOM_DEV"] = res_custom_dev
168
165
 
169
- print(f"āœ… Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC'])} items\n - CUSTOM: {len(res_final['CUSTOM'])} items\n{'='*80}")
166
+ print(f"āœ… Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC'])} items\n - CUSTOM: {len(res_final['CUSTOM'])} items\n{'=' * 80}")
170
167
  return res_final
171
168
 
172
169
 
173
- def install_all(installers: list[Installer], safe: bool=False, jobs: int = 10, fresh: bool=False):
174
- print(f"\n{'='*80}\nšŸš€ BULK INSTALLATION PROCESS šŸš€\n{'='*80}")
170
+ def install_all(installers: list[Installer], safe: bool = False, jobs: int = 10, fresh: bool = False):
171
+ print(f"\n{'=' * 80}\nšŸš€ BULK INSTALLATION PROCESS šŸš€\n{'=' * 80}")
175
172
  if fresh:
176
173
  print("🧹 Fresh install requested - clearing version cache...")
177
- INSTALL_VERSION_ROOT.delete(sure=True)
174
+ PathExtended(INSTALL_VERSION_ROOT).delete(sure=True)
178
175
  print("āœ… Version cache cleared")
179
176
 
180
177
  if safe:
@@ -204,16 +201,13 @@ def install_all(installers: list[Installer], safe: bool=False, jobs: int = 10, f
204
201
  # return None
205
202
 
206
203
  print(f"šŸš€ Starting installation of {len(installers)} packages...")
207
- print(f"\n{'='*80}\nšŸ“¦ INSTALLING FIRST PACKAGE šŸ“¦\n{'='*80}")
204
+ print(f"\n{'=' * 80}\nšŸ“¦ INSTALLING FIRST PACKAGE šŸ“¦\n{'=' * 80}")
208
205
  installers[0].install(version=None)
209
206
  installers_remaining = installers[1:]
210
- print(f"\n{'='*80}\nšŸ“¦ INSTALLING REMAINING PACKAGES šŸ“¦\n{'='*80}")
207
+ print(f"\n{'=' * 80}\nšŸ“¦ INSTALLING REMAINING PACKAGES šŸ“¦\n{'=' * 80}")
211
208
 
212
209
  # Use joblib for parallel processing of remaining installers
213
- res = Parallel(n_jobs=jobs)(
214
- delayed(lambda x: x.install_robust(version=None))(installer)
215
- for installer in installers_remaining
216
- )
210
+ res = Parallel(n_jobs=jobs)(delayed(lambda x: x.install_robust(version=None))(installer) for installer in installers_remaining)
217
211
 
218
212
  console = Console()
219
213
 
@@ -222,19 +216,19 @@ def install_all(installers: list[Installer], safe: bool=False, jobs: int = 10, f
222
216
 
223
217
  print("\n")
224
218
  console.rule("āœ“ Same Version Apps")
225
- same_version_results = [r for r in res if r and 'same version' in str(r)]
219
+ same_version_results = [r for r in res if r and "same version" in str(r)]
226
220
  for result in same_version_results:
227
221
  print(f" {result}")
228
222
 
229
223
  print("\n")
230
224
  console.rule("ā¬†ļø Updated Apps")
231
- updated_results = [r for r in res if r and 'updated from' in str(r)]
225
+ updated_results = [r for r in res if r and "updated from" in str(r)]
232
226
  for result in updated_results:
233
227
  print(f" {result}")
234
228
 
235
229
  print("\n")
236
230
  console.rule("āŒ Failed Apps")
237
- failed_results = [r for r in res if r and 'Failed at' in str(r)]
231
+ failed_results = [r for r in res if r and "Failed at" in str(r)]
238
232
  for result in failed_results:
239
233
  print(f" {result}")
240
234
 
@@ -1,17 +1,17 @@
1
- from machineconfig.utils.path_reduced import P as PathExtended
1
+
2
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
3
+ from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
2
4
  from typing import Optional, TypeAlias, Literal
3
5
  import subprocess
4
6
 
5
- # LINUX_INSTALL_PATH = '/usr/local/bin'
6
- # LINUX_INSTALL_PATH = '~/.local/bin'
7
- LINUX_INSTALL_PATH = PathExtended.home().joinpath(".local/bin").__str__()
8
- WINDOWS_INSTALL_PATH = PathExtended.home().joinpath("AppData/Local/Microsoft/WindowsApps").__str__()
7
+
9
8
  CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
10
9
 
11
10
 
12
- def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool=True, rename_to: Optional[str] = None):
13
- print(f"\n{'='*80}\nšŸ” PROCESSING WINDOWS EXECUTABLE šŸ”\n{'='*80}")
14
- if exe_name is not None and ".exe" in exe_name: exe_name = exe_name.replace(".exe", "")
11
+ def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool = True, rename_to: Optional[str] = None):
12
+ print(f"\n{'=' * 80}\nšŸ” PROCESSING WINDOWS EXECUTABLE šŸ”\n{'=' * 80}")
13
+ if exe_name is not None and ".exe" in exe_name:
14
+ exe_name = exe_name.replace(".exe", "")
15
15
  if downloaded_file_path.is_file():
16
16
  exe = downloaded_file_path
17
17
  print(f"šŸ“„ Found direct executable file: {exe}")
@@ -49,12 +49,12 @@ def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optio
49
49
  downloaded_file_path.delete(sure=True)
50
50
  print("āœ… Temporary files removed")
51
51
 
52
- print(f"{'='*80}")
52
+ print(f"{'=' * 80}")
53
53
  return exe_new_location
54
54
 
55
55
 
56
56
  def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Optional[bool] = True, rename_to: Optional[str] = None):
57
- print(f"\n{'='*80}\nšŸ” PROCESSING LINUX EXECUTABLE šŸ”\n{'='*80}")
57
+ print(f"\n{'=' * 80}\nšŸ” PROCESSING LINUX EXECUTABLE šŸ”\n{'=' * 80}")
58
58
  if downloaded.is_file():
59
59
  exe = downloaded
60
60
  print(f"šŸ“„ Found direct executable file: {exe}")
@@ -108,5 +108,5 @@ def find_move_delete_linux(downloaded: PathExtended, tool_name: str, delete: Opt
108
108
  print("āœ… Temporary files removed")
109
109
 
110
110
  exe_new_location = PathExtended(LINUX_INSTALL_PATH).joinpath(exe.name)
111
- print(f"āœ… Executable installed at: {exe_new_location}\n{'='*80}")
111
+ print(f"āœ… Executable installed at: {exe_new_location}\n{'=' * 80}")
112
112
  return exe_new_location
@@ -1,7 +1,7 @@
1
-
2
- from machineconfig.utils.path_reduced import P as PathExtended
1
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
3
2
  from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
4
- from machineconfig.utils.utils import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT, check_tool_exists
3
+ from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT, LIBRARY_ROOT
4
+ from machineconfig.utils.options import check_tool_exists
5
5
  from machineconfig.utils.utils2 import pprint, read_json
6
6
 
7
7
  import platform
@@ -11,45 +11,59 @@ from pathlib import Path
11
11
 
12
12
 
13
13
  class Installer:
14
- def __init__(self, repo_url: str, name: str, doc: str,
15
- strip_v: bool, exe_name: str,
16
- filename_template_windows_amd_64: str,
17
- filename_template_linux_amd_64: str,
18
- filename_template_windows_arm_64: Optional[str] = None,
19
- filename_template_linux_arm_64: Optional[str] = None,
20
- filename_template_macos_amd_64: Optional[str] = None,
21
- filename_template_macos_arm_64: Optional[str] = None,
22
- ):
23
- self.repo_url: str=repo_url
24
- self.name: str=name
25
- self.doc: str=doc
26
- self.filename_template_windows_amd_64: str=filename_template_windows_amd_64
27
- self.filename_template_linux_amd_64: str=filename_template_linux_amd_64
28
- self.filename_template_windows_arm_64: Optional[str]=filename_template_windows_arm_64
29
- self.filename_template_linux_arm_64: Optional[str]=filename_template_linux_arm_64
30
- self.filename_template_macos_amd_64: Optional[str]=filename_template_macos_amd_64
31
- self.filename_template_macos_arm_64: Optional[str]=filename_template_macos_arm_64
32
- self.strip_v: bool=strip_v
33
- self.exe_name: str=exe_name
34
- def __repr__(self) -> str: return f"Installer of {self.repo_url}"
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
39
+
40
+ def __repr__(self) -> str:
41
+ return f"Installer of {self.exe_name} {self.name} @ {self.repo_url}"
42
+
35
43
  def get_description(self):
36
44
  # old_version_cli = Terminal().run(f"{self.exe_name} --version").op.replace("\n", "")
37
45
  # old_version_cli = os.system(f"{self.exe_name} --version").replace("\n", "")
38
- old_version_cli: bool=check_tool_exists(tool_name=self.exe_name)
46
+ old_version_cli: bool = check_tool_exists(tool_name=self.exe_name)
39
47
  old_version_cli_str = "āœ…" if old_version_cli else "āŒ"
40
48
  # name_version = f"{self.exe_name} {old_version_cli_str}"
41
49
  return f"{self.exe_name:<12} {old_version_cli_str} {self.doc}"
42
- def to_dict(self): return self.__dict__
50
+
51
+ def to_dict(self):
52
+ return self.__dict__
53
+
43
54
  @staticmethod
44
55
  def from_dict(d: dict[str, Any], name: str):
45
- try: return Installer(name=name, **d)
56
+ try:
57
+ return Installer(name=name, **d)
46
58
  except Exception as ex:
47
59
  pprint(d, "Installer Creation Error")
48
60
  raise ex
61
+
49
62
  @staticmethod
50
63
  def choose_app_and_install():
51
- print(f"\n{'='*80}\nšŸ” SELECT APPLICATION TO INSTALL šŸ”\n{'='*80}")
52
- from machineconfig.utils.utils import choose_one_option
64
+ print(f"\n{'=' * 80}\nšŸ” SELECT APPLICATION TO INSTALL šŸ”\n{'=' * 80}")
65
+ from machineconfig.utils.options import choose_one_option
66
+
53
67
  print("šŸ“‚ Searching for configuration files...")
54
68
  jobs_dir = Path(LIBRARY_ROOT.joinpath("jobs"))
55
69
  config_paths = [Path(p) for p in jobs_dir.rglob("config.json")]
@@ -62,12 +76,12 @@ class Installer:
62
76
  installer = Installer.from_dict(d=config[app_name], name=app_name)
63
77
  print(f"šŸ“¦ Selected application: {installer.exe_name}")
64
78
  version = input(f"šŸ“ Enter version to install for {installer.exe_name} [latest]: ") or None
65
- print(f"\n{'='*80}\nšŸš€ INSTALLING {installer.exe_name.upper()} šŸš€\n{'='*80}")
79
+ print(f"\n{'=' * 80}\nšŸš€ INSTALLING {installer.exe_name.upper()} šŸš€\n{'=' * 80}")
66
80
  installer.install(version=version)
67
81
 
68
82
  def install_robust(self, version: Optional[str]):
69
83
  try:
70
- print(f"\n{'='*80}\nšŸš€ INSTALLING {self.exe_name.upper()} šŸš€\n{'='*80}")
84
+ print(f"\n{'=' * 80}\nšŸš€ INSTALLING {self.exe_name.upper()} šŸš€\n{'=' * 80}")
71
85
  result_old = subprocess.run(f"{self.exe_name} --version", shell=True, capture_output=True, text=True)
72
86
  old_version_cli = result_old.stdout.strip()
73
87
  print(f"šŸ“Š Current version: {old_version_cli or 'Not installed'}")
@@ -90,10 +104,11 @@ class Installer:
90
104
  return f"""echo "šŸ“¦ļø āŒ Failed to install `{self.name}` with error: {ex}" """
91
105
 
92
106
  def install(self, version: Optional[str]):
93
- print(f"\n{'='*80}\nšŸ”§ INSTALLATION PROCESS: {self.exe_name} šŸ”§\n{'='*80}")
107
+ print(f"\n{'=' * 80}\nšŸ”§ INSTALLATION PROCESS: {self.exe_name} šŸ”§\n{'=' * 80}")
94
108
  if self.repo_url == "CUSTOM":
95
109
  print(f"🧩 Using custom installer for {self.exe_name}")
96
110
  import machineconfig.jobs.python_custom_installers as python_custom_installers
111
+
97
112
  installer_path = Path(python_custom_installers.__file__).parent.joinpath(self.exe_name + ".py")
98
113
  if not installer_path.exists():
99
114
  installer_path = Path(python_custom_installers.__file__).parent.joinpath("dev", self.exe_name + ".py")
@@ -102,13 +117,17 @@ class Installer:
102
117
  print(f"šŸ” Found installer at: {installer_path}")
103
118
 
104
119
  import runpy
120
+
105
121
  print(f"āš™ļø Executing function 'main' from '{installer_path}'...")
106
- program: str = runpy.run_path(str(installer_path), run_name=None)['main'](version=version)
122
+ program: str = runpy.run_path(str(installer_path), run_name=None)["main"](version=version)
107
123
  # print(program)
108
124
  print("šŸš€ Running installation script...")
109
- if platform.system() == "Linux": script = "#!/bin/bash" + "\n" + program
110
- else: script = program
111
- script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if platform.system() == "Windows" else ".sh", folder="tmp_scripts").write_text(script, newline=None if platform.system() == "Windows" else "\n")
125
+ if platform.system() == "Linux":
126
+ script = "#!/bin/bash" + "\n" + program
127
+ else:
128
+ script = program
129
+ script_file = PathExtended.tmpfile(name="tmp_shell_script", suffix=".ps1" if platform.system() == "Windows" else ".sh", folder="tmp_scripts")
130
+ script_file.write_text(script, newline=None if platform.system() == "Windows" else "\n")
112
131
  if platform.system() == "Windows":
113
132
  start_cmd = "powershell"
114
133
  full_command = f"{start_cmd} {script_file}"
@@ -117,7 +136,7 @@ class Installer:
117
136
  full_command = f"{start_cmd} {script_file}"
118
137
  subprocess.run(full_command, stdin=None, stdout=None, stderr=None, shell=True, text=True)
119
138
  version_to_be_installed = str(version)
120
- print(f"āœ… Custom installation completed\n{'='*80}")
139
+ print(f"āœ… Custom installation completed\n{'=' * 80}")
121
140
 
122
141
  elif "npm " in self.repo_url or "pip " in self.repo_url or "winget " in self.repo_url:
123
142
  package_manager = self.repo_url.split(" ", maxsplit=1)[0]
@@ -134,7 +153,7 @@ class Installer:
134
153
  if result.stderr:
135
154
  print(f"STDERR: {result.stderr}")
136
155
  print(f"Return code: {result.returncode}")
137
- print(f"āœ… Package manager installation completed\n{'='*80}")
156
+ print(f"āœ… Package manager installation completed\n{'=' * 80}")
138
157
 
139
158
  else:
140
159
  print("šŸ“„ Downloading from repository...")
@@ -154,7 +173,7 @@ class Installer:
154
173
  print(f"Return code: {result.returncode}")
155
174
  print("šŸ—‘ļø Cleaning up .deb package...")
156
175
  downloaded.delete(sure=True)
157
- print(f"āœ… DEB package installation completed\n{'='*80}")
176
+ print(f"āœ… DEB package installation completed\n{'=' * 80}")
158
177
  else:
159
178
  if platform.system() == "Windows":
160
179
  print("🪟 Installing on Windows...")
@@ -172,6 +191,7 @@ class Installer:
172
191
  if exe.name.replace(".exe", "") != self.exe_name.replace(".exe", ""):
173
192
  from rich import print as pprint
174
193
  from rich.panel import Panel
194
+
175
195
  print("āš ļø Warning: Executable name mismatch")
176
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))
177
197
  new_exe_name = self.exe_name + ".exe" if platform.system() == "Windows" else self.exe_name
@@ -181,30 +201,30 @@ class Installer:
181
201
  print(f"šŸ’¾ Saving version information to: {INSTALL_VERSION_ROOT.joinpath(self.exe_name)}")
182
202
  INSTALL_VERSION_ROOT.joinpath(self.exe_name).parent.mkdir(parents=True, exist_ok=True)
183
203
  INSTALL_VERSION_ROOT.joinpath(self.exe_name).write_text(version_to_be_installed, encoding="utf-8")
184
- print(f"āœ… Installation completed successfully!\n{'='*80}")
204
+ print(f"āœ… Installation completed successfully!\n{'=' * 80}")
185
205
 
186
206
  def download(self, version: Optional[str]):
187
- print(f"\n{'='*80}\nšŸ“„ DOWNLOADING: {self.exe_name} šŸ“„\n{'='*80}")
207
+ print(f"\n{'=' * 80}\nšŸ“„ DOWNLOADING: {self.exe_name} šŸ“„\n{'=' * 80}")
208
+ download_link: Optional[Path] = None
209
+ version_to_be_installed: Optional[str] = None
188
210
  if "github" not in self.repo_url or ".zip" in self.repo_url or ".tar.gz" in self.repo_url:
189
211
  download_link = Path(self.repo_url)
190
212
  version_to_be_installed = "predefined_url"
191
213
  print(f"šŸ”— Using direct download URL: {download_link}")
192
214
  print(f"šŸ“¦ Version to be installed: {version_to_be_installed}")
193
-
194
- elif "http" in self.filename_template_linux_amd_64 or "http" in self.filename_template_windows_amd_64:
195
- if platform.system() == "Windows":
196
- download_link = Path(self.filename_template_windows_amd_64)
197
- print(f"🪟 Using Windows-specific download URL: {download_link}")
198
- elif platform.system() in ["Linux", "Darwin"]:
199
- download_link = Path(self.filename_template_linux_amd_64)
200
- system_name = "Linux" if platform.system() == "Linux" else "macOS"
201
- print(f"🐧 Using {system_name}-specific download URL: {download_link}")
215
+ elif self._any_direct_http_template():
216
+ template, arch = self._select_template()
217
+ if not template.startswith("http"):
218
+ # Fall back to github-style handling below
219
+ pass
202
220
  else:
203
- error_msg = f"āŒ ERROR: System {platform.system()} not supported"
204
- print(error_msg)
205
- raise NotImplementedError(error_msg)
206
-
207
- version_to_be_installed = "predefined_url"
221
+ download_link = Path(template)
222
+ version_to_be_installed = "predefined_url"
223
+ system_name = self._system_name()
224
+ print(f"🧭 Detected system={system_name} arch={arch}")
225
+ print(f"šŸ”— Using architecture-specific direct URL: {download_link}")
226
+ print(f"šŸ“¦ Version to be installed: {version_to_be_installed}")
227
+ # continue to unified download logic below
208
228
 
209
229
  else:
210
230
  print("🌐 Retrieving release information from GitHub...")
@@ -215,36 +235,97 @@ class Installer:
215
235
  version_to_be_installed_stripped = version_to_be_installed.replace("v", "") if self.strip_v else version_to_be_installed
216
236
  version_to_be_installed_stripped = version_to_be_installed_stripped.replace("ipinfo-", "")
217
237
 
218
- if platform.system() == "Windows":
219
- file_name = self.filename_template_windows_amd_64.format(version_to_be_installed_stripped)
220
- print(f"🪟 Windows file name: {file_name}")
221
- elif platform.system() in ["Linux", "Darwin"]:
222
- file_name = self.filename_template_linux_amd_64.format(version_to_be_installed_stripped)
223
- system_name = "Linux" if platform.system() == "Linux" else "macOS"
224
- print(f"🐧 {system_name} file name: {file_name}")
225
- else:
226
- error_msg = f"āŒ ERROR: System {platform.system()} not supported"
227
- print(error_msg)
228
- raise NotImplementedError(error_msg)
238
+ template, arch = self._select_template()
239
+ system_name = self._system_name()
240
+ file_name = template.format(version_to_be_installed_stripped)
241
+ print(f"🧭 Detected system={system_name} arch={arch}")
242
+ print(f"šŸ“„ Using template: {template}")
243
+ print(f"šŸ—‚ļø Resolved file name: {file_name}")
229
244
 
230
245
  print(f"šŸ“„ File name: {file_name}")
231
246
  download_link = release_url.joinpath(file_name)
232
247
 
248
+ assert download_link is not None, "download_link must be set"
249
+ assert version_to_be_installed is not None, "version_to_be_installed must be set"
233
250
  print(f"šŸ“„ Downloading {self.name} from: {download_link}")
234
251
  downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
235
- print(f"āœ… Download and extraction completed to: {downloaded}\n{'='*80}")
252
+ print(f"āœ… Download and extraction completed to: {downloaded}\n{'=' * 80}")
236
253
  return downloaded, version_to_be_installed
237
254
 
255
+ # --------------------------- Arch / template helpers ---------------------------
256
+ def _normalized_arch(self) -> str:
257
+ arch_raw = platform.machine().lower()
258
+ if arch_raw in ("x86_64", "amd64"):
259
+ return "amd64"
260
+ if arch_raw in ("aarch64", "arm64", "armv8", "armv8l"):
261
+ return "arm64"
262
+ return arch_raw
263
+
264
+ def _system_name(self) -> str:
265
+ sys_ = platform.system()
266
+ if sys_ == "Darwin":
267
+ return "macOS"
268
+ return sys_
269
+
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"))
280
+
281
+ def _select_template(self) -> tuple[str, str]:
282
+ sys_name = platform.system()
283
+ arch = self._normalized_arch()
284
+ # mapping logic
285
+ candidates: list[str] = []
286
+ template: Optional[str] = None
287
+ if sys_name == "Windows":
288
+ if arch == "arm64" and self.filename_template_windows_arm_64:
289
+ template = self.filename_template_windows_arm_64
290
+ else:
291
+ template = self.filename_template_windows_amd_64
292
+ candidates = ["filename_template_windows_arm_64", "filename_template_windows_amd_64"]
293
+ elif sys_name == "Linux":
294
+ if arch == "arm64" and self.filename_template_linux_arm_64:
295
+ template = self.filename_template_linux_arm_64
296
+ else:
297
+ template = self.filename_template_linux_amd_64
298
+ candidates = ["filename_template_linux_arm_64", "filename_template_linux_amd_64"]
299
+ 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
304
+ else:
305
+ # 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"]
308
+ else:
309
+ raise NotImplementedError(f"System {sys_name} not supported")
310
+
311
+ if template is None:
312
+ raise ValueError(f"No filename template available for system={sys_name} arch={arch}. Checked {candidates}")
313
+
314
+ return template, arch
315
+
238
316
  @staticmethod
239
317
  def get_github_release(repo_url: str, version: Optional[str] = None):
240
- print(f"\n{'='*80}\nšŸ” GITHUB RELEASE DETECTION šŸ”\n{'='*80}")
318
+ print(f"\n{'=' * 80}\nšŸ” GITHUB RELEASE DETECTION šŸ”\n{'=' * 80}")
241
319
  print(f"🌐 Inspecting releases at: {repo_url}")
242
320
  # with console.status("Installing..."): # makes troubles on linux when prompt asks for password to move file to /usr/bin
243
321
  if version is None:
244
322
  # see this: https://api.github.com/repos/cointop-sh/cointop/releases/latest
245
323
  print("šŸ” Finding latest version...")
246
324
  import requests # https://docs.github.com/en/repositories/releasing-projects-on-github/linking-to-releases
247
- _latest_version = requests.get(str(repo_url) + "/releases/latest", timeout=10).url.split("/")[-1] # this is to resolve the redirection that occures: https://stackoverflow.com/questions/36070821/how-to-get-redirect-url-using-python-requests
325
+
326
+ _latest_version = requests.get(str(repo_url) + "/releases/latest", timeout=10).url.split("/")[
327
+ -1
328
+ ] # this is to resolve the redirection that occures: https://stackoverflow.com/questions/36070821/how-to-get-redirect-url-using-python-requests
248
329
  version_to_be_installed = _latest_version
249
330
  print(f"āœ… Latest version detected: {version_to_be_installed}")
250
331
  # print(version_to_be_installed)
@@ -253,12 +334,12 @@ class Installer:
253
334
  print(f"šŸ“ Using specified version: {version_to_be_installed}")
254
335
 
255
336
  release_url = Path(repo_url + "/releases/download/" + version_to_be_installed)
256
- print(f"šŸ”— Release download URL: {release_url}\n{'='*80}")
337
+ print(f"šŸ”— Release download URL: {release_url}\n{'=' * 80}")
257
338
  return release_url, version_to_be_installed
258
339
 
259
340
  @staticmethod
260
341
  def check_if_installed_already(exe_name: str, version: str, use_cache: bool):
261
- print(f"\n{'='*80}\nšŸ” CHECKING INSTALLATION STATUS: {exe_name} šŸ”\n{'='*80}")
342
+ print(f"\n{'=' * 80}\nšŸ” CHECKING INSTALLATION STATUS: {exe_name} šŸ”\n{'=' * 80}")
262
343
  version_to_be_installed = version
263
344
  INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
264
345
  tmp_path = INSTALL_VERSION_ROOT.joinpath(exe_name)
@@ -274,7 +355,7 @@ class Installer:
274
355
  else:
275
356
  print("šŸ” Checking installed version directly...")
276
357
  result = subprocess.run([exe_name, "--version"], check=False, capture_output=True, text=True)
277
- if result.stdout.strip() == '':
358
+ if result.stdout.strip() == "":
278
359
  existing_version = None
279
360
  print("ā„¹ļø Could not detect installed version")
280
361
  else:
@@ -285,7 +366,7 @@ class Installer:
285
366
  if existing_version == version_to_be_installed:
286
367
  print(f"āœ… {exe_name} is up to date (version {version_to_be_installed})")
287
368
  print(f"šŸ“‚ Version information stored at: {INSTALL_VERSION_ROOT}")
288
- return ("āœ… Uptodate", version.strip(), version_to_be_installed.strip())
369
+ return ("āœ… Up to date", version.strip(), version_to_be_installed.strip())
289
370
  else:
290
371
  print(f"šŸ”„ {exe_name} needs update: {existing_version.rstrip()} → {version_to_be_installed}")
291
372
  tmp_path.write_text(version_to_be_installed, encoding="utf-8")
@@ -294,5 +375,5 @@ class Installer:
294
375
  print(f"šŸ“¦ {exe_name} is not installed. Will install version: {version_to_be_installed}")
295
376
  tmp_path.write_text(version_to_be_installed, encoding="utf-8")
296
377
 
297
- print(f"{'='*80}")
378
+ print(f"{'=' * 80}")
298
379
  return ("āš ļø NotInstalled", "None", version_to_be_installed.strip())