machineconfig 7.49__py3-none-any.whl → 7.64__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 (93) hide show
  1. machineconfig/cluster/sessions_managers/utils/maker.py +21 -11
  2. machineconfig/jobs/installer/custom/boxes.py +2 -2
  3. machineconfig/jobs/installer/custom/hx.py +16 -12
  4. machineconfig/jobs/installer/custom_dev/brave.py +1 -1
  5. machineconfig/jobs/installer/custom_dev/cloudflare_warp_cli.py +23 -0
  6. machineconfig/jobs/installer/custom_dev/code.py +4 -1
  7. machineconfig/jobs/installer/custom_dev/dubdb_adbc.py +1 -1
  8. machineconfig/jobs/installer/custom_dev/nerfont_windows_helper.py +1 -10
  9. machineconfig/jobs/installer/custom_dev/sysabc.py +119 -0
  10. machineconfig/jobs/installer/custom_dev/wezterm.py +2 -19
  11. machineconfig/jobs/installer/installer_data.json +739 -25
  12. machineconfig/jobs/installer/linux_scripts/redis.sh +1 -0
  13. machineconfig/jobs/installer/package_groups.py +49 -83
  14. machineconfig/logger.py +0 -1
  15. machineconfig/profile/create_links_export.py +21 -7
  16. machineconfig/profile/mapper.toml +1 -4
  17. machineconfig/scripts/linux/wrap_mcfg +1 -1
  18. machineconfig/scripts/python/croshell.py +20 -43
  19. machineconfig/scripts/python/devops.py +3 -4
  20. machineconfig/scripts/python/env_manager/path_manager_tui.py +1 -1
  21. machineconfig/scripts/python/fire_jobs.py +53 -39
  22. machineconfig/scripts/python/ftpx.py +4 -2
  23. machineconfig/scripts/python/helpers_agents/agentic_frameworks/fire_qwen.py +0 -12
  24. machineconfig/scripts/python/helpers_croshell/crosh.py +3 -3
  25. machineconfig/scripts/python/helpers_devops/cli_config.py +3 -19
  26. machineconfig/scripts/python/helpers_devops/cli_config_dotfile.py +22 -13
  27. machineconfig/scripts/python/helpers_devops/cli_self.py +12 -6
  28. machineconfig/scripts/python/helpers_devops/cli_share_file.py +2 -2
  29. machineconfig/scripts/python/helpers_devops/cli_share_server.py +1 -1
  30. machineconfig/scripts/python/helpers_devops/cli_terminal.py +1 -1
  31. machineconfig/scripts/python/helpers_devops/cli_utils.py +1 -152
  32. machineconfig/scripts/python/helpers_devops/devops_backup_retrieve.py +4 -4
  33. machineconfig/scripts/python/helpers_fire_command/file_wrangler.py +2 -20
  34. machineconfig/scripts/python/helpers_fire_command/fire_jobs_route_helper.py +3 -4
  35. machineconfig/scripts/python/helpers_msearch/scripts_linux/fzfg +1 -1
  36. machineconfig/scripts/python/helpers_repos/clone.py +0 -1
  37. machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py +9 -3
  38. machineconfig/scripts/python/helpers_repos/count_lines_frontend.py +1 -1
  39. machineconfig/scripts/python/helpers_repos/entrypoint.py +2 -1
  40. machineconfig/scripts/python/helpers_repos/record.py +2 -1
  41. machineconfig/scripts/python/helpers_sessions/sessions_multiprocess.py +7 -7
  42. machineconfig/scripts/python/helpers_utils/download.py +151 -0
  43. machineconfig/scripts/python/helpers_utils/path.py +106 -0
  44. machineconfig/scripts/python/interactive.py +17 -26
  45. machineconfig/scripts/python/nw/devops_add_ssh_key.py +21 -5
  46. machineconfig/scripts/python/nw/ssh_debug_linux.py +7 -7
  47. machineconfig/scripts/python/nw/ssh_debug_windows.py +4 -4
  48. machineconfig/scripts/python/nw/wsl_windows_transfer.py +3 -2
  49. machineconfig/scripts/python/sessions.py +37 -22
  50. machineconfig/scripts/python/utils.py +8 -3
  51. machineconfig/scripts/windows/mounts/mount_ssh.ps1 +1 -1
  52. machineconfig/settings/shells/nushell/init.nu +2 -2
  53. machineconfig/settings/shells/wezterm/wezterm.lua +2 -0
  54. machineconfig/settings/shells/zsh/init.sh +1 -8
  55. machineconfig/settings/yazi/init.lua +45 -24
  56. machineconfig/settings/yazi/keymap_windows.toml +1 -2
  57. machineconfig/settings/yazi/shell/yazi_cd.ps1 +29 -5
  58. machineconfig/setup_linux/__init__.py +0 -1
  59. machineconfig/setup_linux/web_shortcuts/interactive.sh +12 -10
  60. machineconfig/setup_mac/__init__.py +2 -3
  61. machineconfig/setup_windows/__init__.py +0 -3
  62. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +12 -10
  63. machineconfig/setup_windows/web_shortcuts/quick_init.ps1 +16 -0
  64. machineconfig/utils/code.py +2 -2
  65. machineconfig/utils/files/headers.py +2 -2
  66. machineconfig/utils/installer_utils/installer_class.py +42 -40
  67. machineconfig/utils/installer_utils/{installer.py → installer_cli.py} +61 -101
  68. machineconfig/utils/installer_utils/{installer_abc.py → installer_locator_utils.py} +0 -68
  69. machineconfig/utils/{installer.py → installer_utils/installer_runner.py} +11 -51
  70. machineconfig/utils/io.py +0 -1
  71. machineconfig/utils/meta.py +29 -15
  72. machineconfig/utils/options.py +1 -1
  73. machineconfig/utils/path_extended.py +40 -19
  74. machineconfig/utils/path_helper.py +75 -21
  75. machineconfig/utils/schemas/layouts/layout_types.py +1 -1
  76. machineconfig/utils/ssh.py +3 -3
  77. machineconfig-7.64.dist-info/METADATA +124 -0
  78. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/RECORD +84 -87
  79. machineconfig/jobs/installer/linux_scripts/pgsql.sh +0 -41
  80. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +0 -71
  81. machineconfig/jobs/installer/powershell_scripts/archive_pygraphviz.ps1 +0 -12
  82. machineconfig/scripts/python/nw/add_ssh_key.py +0 -148
  83. machineconfig/settings/lf/linux/exe/fzf_nano.sh +0 -16
  84. machineconfig/setup_linux/apps.sh +0 -66
  85. machineconfig/setup_mac/apps.sh +0 -73
  86. machineconfig/setup_windows/apps.ps1 +0 -62
  87. machineconfig-7.49.dist-info/METADATA +0 -92
  88. /machineconfig/jobs/installer/linux_scripts/{warp-cli.sh → cloudflare_warp_cli.sh} +0 -0
  89. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_add_key.ps1 +0 -0
  90. /machineconfig/{jobs/installer/powershell_scripts → setup_windows/ssh}/openssh-server_copy-ssh-id.ps1 +0 -0
  91. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/WHEEL +0 -0
  92. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/entry_points.txt +0 -0
  93. {machineconfig-7.49.dist-info → machineconfig-7.64.dist-info}/top_level.txt +0 -0
@@ -3,16 +3,16 @@
3
3
  iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
4
4
  iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
5
5
 
6
- function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" devops $args }
7
- function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" cloud $args }
8
- function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" agents $args }
9
- function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" sessions $args }
10
- function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" ftpx $args }
11
- function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" fire $args }
12
- function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" croshell $args }
13
- function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" utils $args }
14
- function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" terminal $args }
15
- function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.49" msearch $args }
6
+ function devops { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" devops $args }
7
+ function cloud { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" cloud $args }
8
+ function agents { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" agents $args }
9
+ function sessions { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" sessions $args }
10
+ function ftpx { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" ftpx $args }
11
+ function fire { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" fire $args }
12
+ function croshell { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" croshell $args }
13
+ function utils { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" utils $args }
14
+ function terminal { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" terminal $args }
15
+ function msearch { & "$HOME\.local\bin\uvx.exe" --python 3.14 --from "machineconfig>=7.64" msearch $args }
16
16
 
17
17
  function d { wrap_in_shell_script devops @args }
18
18
  function c { wrap_in_shell_script cloud @args }
@@ -26,3 +26,5 @@ function t { wrap_in_shell_script terminal @args }
26
26
  function ms { wrap_in_shell_script msearch @args }
27
27
 
28
28
  Write-Host "mcfg command aliases are now defined in this PowerShell session."
29
+
30
+ devops self interactive
@@ -0,0 +1,16 @@
1
+
2
+ iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_windows/uv.ps1").Content
3
+ # iex (iwr "https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/scripts/windows/wrap_mcfg.ps1").Content
4
+ uv tool install --upgrade --python 3.14 machineconfig both
5
+
6
+ devops install --group sysabc
7
+
8
+ # configs
9
+ devops config copy-assets both
10
+ devops config public --method copy --on-conflict overwrite-default-path --which all
11
+ devops config shell
12
+ devops config shell --which nushell
13
+
14
+ wt # start Windows Terminal to pick up config changes
15
+ devops install --group termabc
16
+ devops install --group gui
@@ -132,7 +132,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
132
132
  op_program_path = Path.home().joinpath("tmp_results", "tmp_scripts", "manual_run", f"manual_script_{randstr()}{suffix}")
133
133
  op_program_path.parent.mkdir(parents=True, exist_ok=True)
134
134
  op_program_path.write_text(script, encoding="utf-8")
135
- print_code(script, lexer=lexer, desc="script to run manually")
135
+ print_code(code=script, lexer=lexer, desc="script to run manually")
136
136
  console.print("[bold yellow]⚠️ STRICT MODE:[/bold yellow] [cyan]Please run the script manually via your shell by executing:[/cyan]")
137
137
  console.print(f"[green]{str(op_program_path)}[/green]")
138
138
  console.print("[red]❌ OP_PROGRAM_PATH environment variable is not set in strict mode.[/red]")
@@ -145,7 +145,7 @@ def exit_then_run_shell_script(script: str, strict: bool = False):
145
145
  op_program_path.write_text(script, encoding="utf-8")
146
146
  console.print("[cyan]🚀 Handing over to shell script runner via OP_PROGRAM_PATH:[/cyan]")
147
147
  console.print(f"[bold green]{str(op_program_path)}[/bold green]")
148
- print_code(script, lexer="shell", desc="script to run via OP_PROGRAM_PATH")
148
+ print_code(code=script, lexer="shell", desc="script to run via OP_PROGRAM_PATH")
149
149
  else:
150
150
  if op_program_path is not None and exists:
151
151
  console.print(f"[yellow]⚠️ OP_PROGRAM_PATH @ {str(op_program_path)} already exists.[/yellow] [cyan]Falling back to direct execution.[/cyan]")
@@ -25,7 +25,7 @@ def print_header():
25
25
  table.add_row("Virtual Environment", os.getenv('VIRTUAL_ENV', 'None'))
26
26
  table.add_row("Running @", str(Path.cwd()))
27
27
 
28
- from machineconfig.utils.installer import get_machineconfig_version
28
+ from machineconfig.utils.installer_utils.installer_runner import get_machineconfig_version
29
29
 
30
30
  console.print(Panel(table, title=f"[bold blue]✨ 🐊 Machineconfig Shell {get_machineconfig_version()} ✨ Made with 🐍 | Built with ❤️[/bold blue]", border_style="blue"))
31
31
  def print_logo(logo: str):
@@ -44,7 +44,7 @@ def print_logo(logo: str):
44
44
  _default_art = Path(random.choice(glob.glob(str(Path(__file__).parent.joinpath("art", "*")))))
45
45
  print(_default_art.read_text())
46
46
  elif platform.system() in ["Linux", "Darwin"]: # Explicitly handle both Linux and macOS
47
- from machineconfig.utils.installer_utils.installer_abc import is_executable_in_path
47
+ from machineconfig.utils.installer_utils.installer_locator_utils import is_executable_in_path
48
48
  avail_cowsay = is_executable_in_path("cowsay")
49
49
  avail_lolcat = is_executable_in_path("lolcat")
50
50
  avail_boxes = is_executable_in_path("boxes")
@@ -1,7 +1,6 @@
1
- from machineconfig.utils.path_extended import PathExtended
2
- from machineconfig.utils.installer_utils.installer_abc import find_move_delete_linux, find_move_delete_windows
1
+ from machineconfig.utils.path_extended import PathExtended, DECOMPRESS_SUPPORTED_FORMATS
3
2
  from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR, INSTALL_VERSION_ROOT
4
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
3
+ from machineconfig.utils.installer_utils.installer_locator_utils import find_move_delete_linux, find_move_delete_windows, check_tool_exists
5
4
  from machineconfig.utils.schemas.installer.installer_types import InstallerData, get_os_name, get_normalized_arch
6
5
 
7
6
  import platform
@@ -11,6 +10,24 @@ from typing import Optional, Any
11
10
  from urllib.parse import urlparse
12
11
 
13
12
 
13
+
14
+ def install_deb_package(downloaded: PathExtended) -> None:
15
+ print(f"📦 Installing .deb package: {downloaded}")
16
+ assert platform.system() == "Linux"
17
+ result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
18
+ success = result.returncode == 0 and result.stderr == ""
19
+ if not success:
20
+ desc = "Installing .deb"
21
+ print(f"❌ {desc} failed")
22
+ if result.stdout:
23
+ print(f"STDOUT: {result.stdout}")
24
+ if result.stderr:
25
+ print(f"STDERR: {result.stderr}")
26
+ print(f"Return code: {result.returncode}")
27
+ print("🗑️ Cleaning up .deb package...")
28
+ downloaded.delete(sure=True)
29
+
30
+
14
31
  class Installer:
15
32
  def __init__(self, installer_data: InstallerData):
16
33
  self.installer_data: InstallerData = installer_data
@@ -42,10 +59,8 @@ class Installer:
42
59
  result_new = subprocess.run(f"{exe_name} --version", shell=True, capture_output=True, text=True)
43
60
  new_version_cli = result_new.stdout.strip()
44
61
  if old_version_cli == new_version_cli:
45
- # print(f"ℹ️ Same version detected: {old_version_cli}")
46
62
  return f"""📦️ 😑 {exe_name}, same version: {old_version_cli}"""
47
63
  else:
48
- # print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
49
64
  return f"""📦️ 🤩 {exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
50
65
  except Exception as ex:
51
66
  exe_name = self._get_exe_name()
@@ -69,8 +84,6 @@ class Installer:
69
84
  desc = package_manager + " installation"
70
85
  version_to_be_installed = package_manager + "Latest"
71
86
  result = subprocess.run(installer_arch_os, shell=True, capture_output=True, text=False)
72
- # from machineconfig.utils.code import run_shell_script
73
- # result = run_shell_script(installer_arch_os)
74
87
  success = result.returncode == 0 and result.stderr == "".encode()
75
88
  if not success:
76
89
  print(f"❌ {desc} failed")
@@ -80,7 +93,6 @@ class Installer:
80
93
  print(f"STDERR: {result.stderr}")
81
94
  print(f"Return code: {result.returncode}")
82
95
  elif installer_arch_os.endswith((".sh", ".py", ".ps1")):
83
- # search for the script, see which path ends with the script name
84
96
  import machineconfig.jobs.installer as module
85
97
  from pathlib import Path
86
98
  search_root = Path(module.__file__).parent
@@ -106,9 +118,14 @@ class Installer:
106
118
  runpy.run_path(str(installer_path), run_name=None)["main"](self.installer_data, version=version)
107
119
  version_to_be_installed = str(version)
108
120
  elif installer_arch_os.startswith("https://"): # its a url to be downloaded
109
- downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
121
+ # downloaded_object = PathExtended(installer_arch_os).download(folder=INSTALL_TMP_DIR)
122
+ from machineconfig.scripts.python.helpers_utils.download import download
123
+ downloaded_object = download(installer_arch_os, output_dir=str(INSTALL_TMP_DIR))
124
+ if downloaded_object is None:
125
+ raise ValueError(f"Failed to download from URL: {installer_arch_os}")
110
126
  # object is either a zip containing a binary or a straight out binary.
111
- if downloaded_object.suffix in [".zip", ".tar.gz"]:
127
+ downloaded_object = PathExtended(downloaded_object)
128
+ if downloaded_object.suffix in DECOMPRESS_SUPPORTED_FORMATS:
112
129
  downloaded_object = downloaded_object.decompress()
113
130
  if downloaded_object.suffix in [".exe", ""]: # likely an executable
114
131
  if platform.system() == "Windows":
@@ -131,26 +148,17 @@ class Installer:
131
148
  print(f"🔄 Renaming to correct name: {new_exe_name}")
132
149
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
133
150
  version_to_be_installed = "downloaded_binary"
151
+ elif downloaded_object.suffix in [".deb"]:
152
+ install_deb_package(downloaded_object)
153
+ version_to_be_installed = "downloaded_deb"
154
+ else:
155
+ raise ValueError(f"Downloaded file is not an executable: {downloaded_object}")
134
156
  else:
135
157
  raise NotImplementedError(f"CMD installation method not implemented for: {installer_arch_os}")
136
158
  else:
137
159
  assert repo_url.startswith("https://github.com/"), f"repoURL must be a GitHub URL, got {repo_url}"
138
- downloaded, version_to_be_installed = self.download(version=version)
139
- if str(downloaded).endswith(".deb"):
140
- print(f"📦 Installing .deb package: {downloaded}")
141
- assert platform.system() == "Linux"
142
- result = subprocess.run(f"sudo nala install -y {downloaded}", shell=True, capture_output=True, text=True)
143
- success = result.returncode == 0 and result.stderr == ""
144
- if not success:
145
- desc = "Installing .deb"
146
- print(f"❌ {desc} failed")
147
- if result.stdout:
148
- print(f"STDOUT: {result.stdout}")
149
- if result.stderr:
150
- print(f"STDERR: {result.stderr}")
151
- print(f"Return code: {result.returncode}")
152
- print("🗑️ Cleaning up .deb package...")
153
- downloaded.delete(sure=True)
160
+ downloaded, version_to_be_installed = self.binary_download(version=version)
161
+ if str(downloaded).endswith(".deb"): install_deb_package(downloaded)
154
162
  else:
155
163
  if platform.system() == "Windows":
156
164
  exe = find_move_delete_windows(downloaded_file_path=downloaded, exe_name=exe_name, delete=True, rename_to=exe_name.replace(".exe", "") + ".exe")
@@ -173,13 +181,13 @@ class Installer:
173
181
  exe.with_name(name=new_exe_name, inplace=True, overwrite=True)
174
182
  INSTALL_VERSION_ROOT.joinpath(exe_name).parent.mkdir(parents=True, exist_ok=True)
175
183
  INSTALL_VERSION_ROOT.joinpath(exe_name).write_text(version_to_be_installed or "unknown", encoding="utf-8")
176
- def download(self, version: Optional[str]) -> tuple[PathExtended, str]:
184
+ def binary_download(self, version: Optional[str]) -> tuple[PathExtended, str]:
177
185
  exe_name = self._get_exe_name()
178
186
  repo_url = self.installer_data["repoURL"]
179
187
  # app_name = self.installer_data["appName"]
180
188
  download_link: Optional[str] = None
181
189
  version_to_be_installed: Optional[str] = None
182
- if "github" not in repo_url or ".zip" in repo_url or ".tar.gz" in repo_url:
190
+ if "github" not in repo_url or (any(ext in repo_url for ext in DECOMPRESS_SUPPORTED_FORMATS)):
183
191
  # Direct download URL
184
192
  download_link = repo_url
185
193
  version_to_be_installed = "predefined_url"
@@ -200,10 +208,11 @@ class Installer:
200
208
  assert download_link is not None, "download_link must be set"
201
209
  assert version_to_be_installed is not None, "version_to_be_installed must be set"
202
210
  downloaded = PathExtended(download_link).download(folder=INSTALL_TMP_DIR).decompress()
211
+ if downloaded.is_dir() and len(downloaded.search("*", r=True)) == 1:
212
+ only_file_in = next(downloaded.glob("*"))
213
+ if only_file_in.is_file() and only_file_in.suffix in DECOMPRESS_SUPPORTED_FORMATS: # further decompress
214
+ downloaded = only_file_in.decompress()
203
215
  return downloaded, version_to_be_installed
204
-
205
- # --------------------------- Arch / template helpers ---------------------------
206
-
207
216
  @staticmethod
208
217
  def _get_repo_name_from_url(repo_url: str) -> str:
209
218
  """Extract owner/repo from GitHub URL."""
@@ -218,23 +227,16 @@ class Installer:
218
227
  def _fetch_github_release_data(repo_name: str, version: Optional[str] = None) -> Optional[dict[str, Any]]:
219
228
  """Fetch release data from GitHub API using requests."""
220
229
  import requests
221
-
222
230
  try:
223
- if version and version.lower() != "latest":
224
- # Fetch specific version
231
+ if version and version.lower() != "latest": # Fetch specific version
225
232
  url = f"https://api.github.com/repos/{repo_name}/releases/tags/{version}"
226
- else:
227
- # Fetch latest release
233
+ else: # Fetch latest release
228
234
  url = f"https://api.github.com/repos/{repo_name}/releases/latest"
229
-
230
235
  response = requests.get(url, timeout=30)
231
-
232
236
  if response.status_code != 200:
233
237
  print(f"❌ Failed to fetch data for {repo_name}: HTTP {response.status_code}")
234
238
  return None
235
-
236
239
  response_data = response.json()
237
-
238
240
  # Check if API returned an error
239
241
  if "message" in response_data:
240
242
  if "API rate limit exceeded" in response_data.get("message", ""):
@@ -1,56 +1,14 @@
1
1
  """Devops Devapps Install"""
2
2
 
3
- from machineconfig.utils.installer import dynamically_extract_installers_system_groups_from_scripts
4
3
  import typer
5
- from rich.console import Console
6
- from rich.panel import Panel
7
- from rich.table import Table
8
- from typing import Optional, cast, get_args, Annotated
9
- from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
10
-
11
- console = Console()
12
-
13
-
14
- def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
15
- """Handle installer not found with friendly suggestions using fuzzy matching."""
16
- from difflib import get_close_matches
17
- close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
18
- console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
19
- if close_matches:
20
- console.print("🤔 Did you mean one of these?", style="yellow")
21
- table = Table(show_header=False, box=None, pad_edge=False)
22
- for i, match in enumerate(close_matches, 1):
23
- table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
24
- console.print(table)
25
- else:
26
- console.print("📋 Here are some available options:", style="blue")
27
- # Show first 10 installers as examples
28
- if len(all_names) > 10:
29
- sample_names = all_names[:10]
30
- else:
31
- sample_names = all_names
32
- table = Table(show_header=False, box=None, pad_edge=False)
33
- for i, name in enumerate(sample_names, 1):
34
- table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
35
- console.print(table)
36
- if len(all_names) > 10:
37
- console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
38
-
39
- panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(get_args(PACKAGE_GROUPS))}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
40
- console.print(panel)
41
-
42
-
43
- def main_with_parser():
44
- import typer
45
- app = typer.Typer()
46
- app.command()(main)
47
- app()
4
+ from typing import Optional, Annotated
5
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUP2NAMES
48
6
 
49
7
 
50
8
  def main(
51
9
  which: Annotated[Optional[str], typer.Argument(..., help="Comma-separated list of program/groups names to install (if --group flag is set).")] = None,
52
10
  group: Annotated[bool, typer.Option(..., "--group", "-g", help="Treat 'which' as a group name. A group is bundle of apps.")] = False,
53
- interactive: Annotated[bool, typer.Option(..., "--interactive", "-ia", help="Interactive selection of programs to install.")] = False,
11
+ interactive: Annotated[bool, typer.Option(..., "--interactive", "-i", help="Interactive selection of programs to install.")] = False,
54
12
  ) -> None:
55
13
  if interactive:
56
14
  return install_interactively()
@@ -62,8 +20,12 @@ def main(
62
20
  return install_clis(clis_names=[x.strip() for x in which.split(",") if x.strip() != ""])
63
21
  else:
64
22
  if group:
23
+ from rich.console import Console
24
+ from rich.table import Table
25
+ console = Console()
26
+
65
27
  typer.echo("❌ You must provide a group name when using the --group/-g option.")
66
- res = get_static_groups_combined_with_dynamic_groups_extracted()
28
+ res = get_group_name_to_repr()
67
29
  console.print("[bold blue]Here are the available groups:[/bold blue]")
68
30
  table = Table(show_header=True, header_style="bold magenta")
69
31
  table.add_column("Group", style="cyan", no_wrap=True)
@@ -87,100 +49,99 @@ def main(
87
49
  raise typer.Exit(1)
88
50
 
89
51
 
90
- def get_static_groups_combined_with_dynamic_groups_extracted():
52
+ def get_group_name_to_repr() -> dict[str, str]:
91
53
  # Build category options and maintain a mapping from display text to actual category name
92
54
  category_display_to_name: dict[str, str] = {}
93
55
  for group_name, group_values in PACKAGE_GROUP2NAMES.items():
94
56
  display = f"📦 {group_name:<20}" + " -- " + f"{'|'.join(group_values):<60}"
95
57
  category_display_to_name[display] = group_name
96
- options_system = dynamically_extract_installers_system_groups_from_scripts()
97
- for item in options_system:
98
- display = f"📦 {item['appName']:<20} -- {item['doc']:<60}"
99
- category_display_to_name[display] = item['appName']
100
58
  return category_display_to_name
101
59
 
102
60
 
103
61
  def install_interactively():
104
62
  from machineconfig.utils.options import choose_from_options
105
63
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
106
- from machineconfig.utils.installer import get_installers
64
+ from machineconfig.utils.installer_utils.installer_runner import get_installers
107
65
  from machineconfig.utils.installer_utils.installer_class import Installer
66
+ from rich.console import Console
67
+ from rich.panel import Panel
68
+ # from rich.table import Table
108
69
  installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
109
- installer_options = []
110
- for x in installers:
111
- installer_options.append(Installer(installer_data=x).get_description())
112
-
113
- category_display_to_name = get_static_groups_combined_with_dynamic_groups_extracted()
114
- options = list(category_display_to_name.keys()) + ["─" * 50] + installer_options
115
- program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", default="📦 essentials", fzf=True)
70
+ installer_options = [Installer(installer_data=x).get_description() for x in installers]
71
+ category_display_to_name = get_group_name_to_repr()
72
+ options = list(category_display_to_name.keys()) + installer_options
73
+ program_names = choose_from_options(multi=True, msg="Categories are prefixed with 📦", options=options, header="🚀 CHOOSE DEV APP OR CATEGORY", fzf=True)
116
74
  installation_messages: list[str] = []
117
75
  for _an_idx, a_program_name in enumerate(program_names):
118
- if a_program_name.startswith("─"): # 50 dashes separator
119
- continue
120
76
  if a_program_name.startswith("📦 "):
121
77
  category_name = category_display_to_name.get(a_program_name)
122
78
  if category_name:
123
- install_group(package_group=cast(PACKAGE_GROUPS, category_name))
79
+ install_group(package_group=category_name)
124
80
  else:
125
81
  installer_idx = installer_options.index(a_program_name)
126
82
  an_installer_data = installers[installer_idx]
127
83
  status_message = Installer(an_installer_data).install_robust(version=None) # finish the task - this returns a status message, not a command
128
84
  installation_messages.append(status_message)
129
85
  if installation_messages:
86
+ console = Console()
87
+
130
88
  panel = Panel("\n".join([f"[blue]• {message}[/blue]" for message in installation_messages]), title="[bold green]📊 Installation Summary[/bold green]", border_style="green", padding=(1, 2))
131
89
  console.print(panel)
132
90
 
133
91
 
134
92
  def install_group(package_group: str):
135
- from machineconfig.utils.installer import get_installers, install_bulk
93
+ from machineconfig.utils.installer_utils.installer_runner import get_installers, install_bulk
136
94
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
95
+ from rich.console import Console
96
+ from rich.panel import Panel
97
+ # from rich.table import Table
137
98
  if package_group in PACKAGE_GROUP2NAMES:
138
99
  panel = Panel(f"[bold yellow]Installing programs from category: [green]{package_group}[/green][/bold yellow]", title="[bold blue]📦 Category Installation[/bold blue]", border_style="blue", padding=(1, 2))
100
+ console = Console()
139
101
  console.print(panel)
140
102
  installers_ = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=[package_group])
141
103
  install_bulk(installers_data=installers_)
142
104
  return
143
- options_system = dynamically_extract_installers_system_groups_from_scripts()
144
- from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
145
- from machineconfig.utils.code import run_shell_script
146
- for an_item in options_system:
147
- if an_item["appName"] == package_group:
148
- panel = Panel(f"[bold yellow]Installing programs from category: [green]{package_group}[/green][/bold yellow]", title="[bold blue]📦 Category Installation[/bold blue]", border_style="blue", padding=(1, 2))
149
- console.print(panel)
150
- program = an_item["fileNamePattern"][get_normalized_arch()][get_os_name()]
151
- if program is not None:
152
- run_shell_script(program)
153
- break
154
- else:
155
- console.print(f"❌ [red]Group '{package_group}' not found.[/red]", style="bold")
156
- _handle_installer_not_found(package_group, all_names=list(PACKAGE_GROUP2NAMES.keys()) + [item['appName'] for item in options_system])
157
-
105
+ console = Console()
106
+ console.print(f"❌ ERROR: Unknown package group: {package_group}. Available groups are: {list(PACKAGE_GROUP2NAMES.keys())}")
107
+ def _handle_installer_not_found(search_term: str, all_names: list[str]) -> None: # type: ignore
108
+ """Handle installer not found with friendly suggestions using fuzzy matching."""
109
+ from difflib import get_close_matches
110
+ from rich.console import Console
111
+ from rich.panel import Panel
112
+ from rich.table import Table
113
+ close_matches = get_close_matches(search_term, all_names, n=5, cutoff=0.4)
114
+ console = Console()
158
115
 
159
- def choose_from_system_package_groups(options_system: dict[str, tuple[str, str]]) -> str:
160
- from machineconfig.utils.options import choose_from_options
161
- display_options = []
162
- for group_name, (description, _) in options_system.items():
163
- if description:
164
- display_options.append(f"{group_name:<20} - {description}")
116
+ console.print(f"\n❌ '[red]{search_term}[/red]' was not found.", style="bold")
117
+ if close_matches:
118
+ console.print("🤔 Did you mean one of these?", style="yellow")
119
+ table = Table(show_header=False, box=None, pad_edge=False)
120
+ for i, match in enumerate(close_matches, 1):
121
+ table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{match}[/green]")
122
+ console.print(table)
123
+ else:
124
+ console.print("📋 Here are some available options:", style="blue")
125
+ # Show first 10 installers as examples
126
+ if len(all_names) > 10:
127
+ sample_names = all_names[:10]
165
128
  else:
166
- display_options.append(group_name)
167
- program_names = choose_from_options(multi=True, msg="", options=sorted(display_options), header="🚀 CHOOSE DEV APP", fzf=True)
168
- program = ""
169
- for display_name in program_names:
170
- # Extract the actual group name (everything before " - " if present)
171
- group_name = display_name.split(" - ")[0].strip() if " - " in display_name else display_name.strip()
172
- console.print(f"\n[bold cyan]⚙️ Installing: [yellow]{group_name}[/yellow][/bold cyan]", style="bold")
173
- _, sub_program = options_system[group_name] # Extract content from tuple
174
- if sub_program.startswith("#winget"):
175
- sub_program = sub_program[1:]
176
- program += "\n" + sub_program
177
- return program
129
+ sample_names = all_names
130
+ table = Table(show_header=False, box=None, pad_edge=False)
131
+ for i, name in enumerate(sample_names, 1):
132
+ table.add_row(f"[cyan]{i}.[/cyan]", f"[green]{name}[/green]")
133
+ console.print(table)
134
+ if len(all_names) > 10:
135
+ console.print(f" [dim]... and {len(all_names) - 10} more[/dim]")
178
136
 
137
+ panel = Panel(f"[bold blue]💡 Use 'ia' to interactively browse all available installers.[/bold blue]\n[bold blue]💡 Use one of the categories: {list(PACKAGE_GROUP2NAMES.keys())}[/bold blue]", title="[yellow]Helpful Tips[/yellow]", border_style="yellow")
138
+ console.print(panel)
179
139
 
180
140
  def install_clis(clis_names: list[str]):
181
141
  from machineconfig.utils.schemas.installer.installer_types import get_normalized_arch, get_os_name
182
- from machineconfig.utils.installer import get_installers
142
+ from machineconfig.utils.installer_utils.installer_runner import get_installers
183
143
  from machineconfig.utils.installer_utils.installer_class import Installer
144
+ from rich.console import Console
184
145
  total_messages: list[str] = []
185
146
  for a_which in clis_names:
186
147
  all_installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=None)
@@ -196,20 +157,19 @@ def install_clis(clis_names: list[str]):
196
157
  message = Installer(selected_installer).install_robust(version=None) # finish the task
197
158
  total_messages.append(message)
198
159
  if total_messages:
160
+ console = Console()
199
161
  console.print("\n[bold green]📊 Installation Results:[/bold green]")
200
162
  for a_message in total_messages:
201
163
  console.print(f"[blue]• {a_message}[/blue]")
202
164
  return None
203
-
204
-
205
165
  def install_if_missing(which: str):
206
- from machineconfig.utils.installer_utils.installer_abc import check_tool_exists
166
+ from machineconfig.utils.installer_utils.installer_locator_utils import check_tool_exists
207
167
  exists = check_tool_exists(which)
208
168
  if exists:
209
169
  print(f"✅ {which} is already installed.")
210
170
  return
211
171
  print(f"⏳ {which} not found. Installing...")
212
- from machineconfig.utils.installer_utils.installer import main
172
+ from machineconfig.utils.installer_utils.installer_cli import main
213
173
  main(which=which, interactive=False)
214
174
 
215
175
 
@@ -191,71 +191,3 @@ def check_if_installed_already(exe_name: str, version: Optional[str], use_cache:
191
191
  return ("⚠️ NotInstalled", "None", version or "unknown")
192
192
 
193
193
 
194
- def parse_apps_installer_linux(txt: str) -> dict[str, tuple[str, str]]:
195
- """Parse Linux shell installation scripts into logical chunks.
196
-
197
- Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
198
- mapping block names to (description, shell script content) tuples.
199
-
200
- Returns:
201
- dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
202
- """
203
- chunks = txt.split('# --GROUP:')
204
- res: dict[str, tuple[str, str]] = {}
205
-
206
- for chunk in chunks[1:]: # Skip first empty chunk before first group
207
- lines = chunk.split('\n')
208
- # First line contains the group name and description in format "NAME:DESCRIPTION"
209
- group_line = lines[0].strip()
210
-
211
- # Extract group name and description
212
- if ':' in group_line:
213
- parts = group_line.split(':', 1) # Split only on first colon
214
- group_name = parts[0].strip()
215
- group_description = parts[1].strip() if len(parts) > 1 else ""
216
- else:
217
- group_name = group_line
218
- group_description = ""
219
-
220
- # Rest is the content
221
- content = '\n'.join(lines[1:]).strip()
222
-
223
- if group_name and content:
224
- res[group_name] = (group_description, content)
225
-
226
- return res
227
-
228
-
229
- def parse_apps_installer_windows(txt: str) -> dict[str, tuple[str, str]]:
230
- """Parse Windows PowerShell installation scripts into logical chunks.
231
-
232
- Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
233
- mapping block names to (description, PowerShell script content) tuples.
234
-
235
- Returns:
236
- dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
237
- """
238
- chunks = txt.split('# --GROUP:')
239
- res: dict[str, tuple[str, str]] = {}
240
-
241
- for chunk in chunks[1:]: # Skip first chunk before first group
242
- lines = chunk.split('\n')
243
- # First line contains the group name and description in format "NAME:DESCRIPTION"
244
- group_line = lines[0].strip()
245
-
246
- # Extract group name and description
247
- if ':' in group_line:
248
- parts = group_line.split(':', 1) # Split only on first colon
249
- group_name = parts[0].strip()
250
- group_description = parts[1].strip() if len(parts) > 1 else ""
251
- else:
252
- group_name = group_line
253
- group_description = ""
254
-
255
- # Rest is the content
256
- content = '\n'.join(lines[1:]).strip()
257
-
258
- if group_name and content:
259
- res[group_name] = (group_description, content)
260
-
261
- return res