machineconfig 4.7__py3-none-any.whl → 4.9__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 (33) hide show
  1. machineconfig/cluster/sessions_managers/wt_local_manager.py +15 -6
  2. machineconfig/jobs/installer/custom_dev/winget.py +1 -0
  3. machineconfig/jobs/installer/installer_data.json +2403 -0
  4. machineconfig/jobs/installer/package_groups.py +154 -0
  5. machineconfig/scripts/python/devops.py +33 -42
  6. machineconfig/scripts/python/devops_devapps_install.py +87 -34
  7. machineconfig/scripts/python/fire_agents.py +49 -70
  8. machineconfig/scripts/python/fire_agents_help_launch.py +1 -8
  9. machineconfig/scripts/python/fire_agents_helper_types.py +12 -0
  10. machineconfig/scripts/python/fire_jobs.py +0 -6
  11. machineconfig/scripts/python/fire_jobs_args_helper.py +0 -1
  12. machineconfig/scripts/python/fire_jobs_layout_helper.py +100 -36
  13. machineconfig/scripts/python/interactive.py +10 -31
  14. machineconfig/scripts/python/repos.py +7 -6
  15. machineconfig/scripts/python/share_terminal.py +6 -4
  16. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  17. machineconfig/setup_windows/web_shortcuts/interactive.ps1 +1 -1
  18. machineconfig/utils/installer.py +15 -14
  19. machineconfig/utils/installer_utils/github_release_bulk.py +2 -12
  20. machineconfig/utils/installer_utils/installer_abc.py +56 -60
  21. machineconfig/utils/procs.py +0 -4
  22. machineconfig/utils/schemas/installer/installer_types.py +0 -1
  23. {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/METADATA +1 -1
  24. {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/RECORD +27 -30
  25. {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/entry_points.txt +0 -1
  26. machineconfig/jobs/installer/packages_custom_dev.json +0 -380
  27. machineconfig/jobs/installer/packages_custom_essential.json +0 -39
  28. machineconfig/jobs/installer/packages_github_dev.json +0 -1127
  29. machineconfig/jobs/installer/packages_github_essential.json +0 -787
  30. machineconfig/scripts/linux/fire_agents +0 -2
  31. machineconfig/scripts/linux/programs +0 -21
  32. {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/WHEEL +0 -0
  33. {machineconfig-4.7.dist-info → machineconfig-4.9.dist-info}/top_level.txt +0 -0
@@ -2,14 +2,7 @@
2
2
  import random
3
3
  import shlex
4
4
  from pathlib import Path
5
- from typing import Literal, TypeAlias
6
-
7
-
8
- AGENTS: TypeAlias = Literal[
9
- "cursor-agent", "gemini", "crush", "q"
10
- # warp terminal
11
- ]
12
- AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
5
+ from machineconfig.scripts.python.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER
13
6
 
14
7
 
15
8
  def get_gemini_api_keys() -> list[str]:
@@ -0,0 +1,12 @@
1
+
2
+ from typing import Literal, TypeAlias
3
+
4
+
5
+ AGENTS: TypeAlias = Literal[
6
+ "cursor-agent", "gemini", "crush", "q"
7
+ # warp terminal
8
+ ]
9
+ AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
10
+
11
+ SEARCH_STRATEGIES: TypeAlias = Literal["file_path", "keyword_search", "filename_pattern"]
12
+
@@ -26,10 +26,6 @@ import typer
26
26
 
27
27
 
28
28
  def route(args: FireJobArgs, fire_args: str = "") -> None:
29
- if args.layout:
30
- from machineconfig.scripts.python.fire_jobs_layout_helper import handle_layout_args
31
- return handle_layout_args(args.path, args.function)
32
-
33
29
  path_obj = sanitize_path(args.path)
34
30
  if not path_obj.exists():
35
31
  suffixes = {".py", ".sh", ".ps1"}
@@ -348,7 +344,6 @@ def main(
348
344
  Nprocess: Annotated[int, typer.Option("--Nprocess", "-p", help="Number of processes to use")] = 1,
349
345
  zellij_tab: Annotated[Optional[str], typer.Option("--zellij_tab", "-z", help="Open in a new zellij tab")] = None,
350
346
  watch: Annotated[bool, typer.Option("--watch", "-w", help="Watch the file for changes")] = False,
351
- layout: Annotated[bool, typer.Option("--layout", "-L", help="Use layout configuration (Zellij Or WindowsTerminal)")] = False,
352
347
  ) -> None:
353
348
  """Main function to process fire jobs arguments."""
354
349
 
@@ -377,7 +372,6 @@ def main(
377
372
  Nprocess=Nprocess,
378
373
  zellij_tab=zellij_tab,
379
374
  watch=watch,
380
- layout=layout,
381
375
  )
382
376
  try:
383
377
  route(args, fire_args)
@@ -27,7 +27,6 @@ class FireJobArgs:
27
27
  Nprocess: int = 1
28
28
  zellij_tab: Optional[str] = None
29
29
  watch: bool = False
30
- layout: bool = False
31
30
 
32
31
 
33
32
  def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
@@ -1,30 +1,45 @@
1
+
1
2
  from pathlib import Path
2
- from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, LayoutsFile
3
- from typing import Optional
4
- from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
5
- from machineconfig.utils.options import choose_from_options
6
- from machineconfig.utils.path_helper import match_file_name, sanitize_path
7
- from machineconfig.utils.path_extended import PathExtended as PathExtended
3
+ from typing import Optional, Literal
4
+ import typer
5
+
6
+
7
+ def load_balance(layout_path: Path = typer.Argument(..., help="Path to the layout.json file"),
8
+ max_thresh: int = typer.Option(..., help="Maximum tabs per layout"),
9
+ thresh_type: Literal['number', 'weight'] = typer.Option(..., help="Threshold type"),
10
+ breaking_method: Literal['moreLayouts', 'combineTabs'] = typer.Option(..., help="Breaking method"),
11
+ output_path: Optional[Path] = typer.Option(None, help="Path to write the adjusted layout.json file")):
12
+ """Adjust layout file to limit max tabs per layout, etc."""
13
+ from machineconfig.utils.schemas.layouts.layout_types import LayoutsFile
14
+ import json
15
+ layoutfile: LayoutsFile = json.loads(layout_path.read_text())
16
+ layout_configs = layoutfile["layouts"]
17
+ from machineconfig.cluster.sessions_managers.utils.load_balancer import limit_tab_num
18
+ new_layouts = limit_tab_num(layout_configs=layout_configs, max_thresh=max_thresh, threshold_type=thresh_type, breaking_method=breaking_method)
19
+ layoutfile["layouts"] = new_layouts
20
+ target_file = output_path if output_path is not None else layout_path.parent / f'{layout_path.stem}_adjusted_{max_thresh}_{thresh_type}_{breaking_method}.json'
21
+ target_file.parent.mkdir(parents=True, exist_ok=True)
22
+ target_file.write_text(data=json.dumps(layoutfile, indent=4), encoding="utf-8")
23
+ typer.echo(f"Adjusted layout saved to {target_file}")
8
24
 
9
25
 
10
- def select_layout(layouts_json_file: Path, layouts_name: Optional[list[str]]) -> list[LayoutConfig]:
26
+ def select_layout(layouts_json_file: Path, selected_layouts_names: Optional[list[str]], select_interactively: bool) -> list["LayoutConfig"]:
11
27
  import json
28
+ from machineconfig.utils.options import choose_from_options
29
+ from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, LayoutsFile
12
30
  layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
13
31
  if len(layout_file["layouts"]) == 0:
14
32
  raise ValueError(f"No layouts found in {layouts_json_file}")
15
- if layouts_name is None:
33
+ if selected_layouts_names is None: # choose all, or interactively
34
+ if not select_interactively:
35
+ return layout_file["layouts"]
16
36
  options = [layout["layoutName"] for layout in layout_file["layouts"]]
17
37
  from machineconfig.utils.options import choose_from_options
18
- layouts_name = choose_from_options(multi=True, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
19
- print(f"Selected layout(s): {layouts_name}")
20
- # layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layouts_name), None)
21
- # if layout_chosen is None:
22
- # layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layouts_name.lower()), None)
23
- # if layout_chosen is None:
24
- # available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
25
- # raise ValueError(f"Layout '{layouts_name}' not found. Available layouts: {available_layouts}")
38
+ selected_layouts_names = choose_from_options(multi=True, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
39
+ print(f"Selected layout(s): {selected_layouts_names}")
40
+ # Extract the configs from the names:
26
41
  layouts_chosen: list[LayoutConfig] = []
27
- for name in layouts_name:
42
+ for name in selected_layouts_names:
28
43
  layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == name), None)
29
44
  if layout_chosen is None:
30
45
  layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == name.lower()), None)
@@ -35,23 +50,11 @@ def select_layout(layouts_json_file: Path, layouts_name: Optional[list[str]]) ->
35
50
  return layouts_chosen
36
51
 
37
52
 
38
- def launch_layout(layout_config: LayoutConfig) -> Optional[Exception]:
39
- import platform
40
- if platform.system() == "Linux" or platform.system() == "Darwin":
41
- print("🧑‍💻 Launching layout using Zellij terminal multiplexer...")
42
- from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
43
- run_zellij_layout(layout_config=layout_config)
44
- elif platform.system() == "Windows":
45
- print("🧑‍💻 Launching layout using Windows Terminal...")
46
- from machineconfig.cluster.sessions_managers.wt_local import run_wt_layout
47
-
48
- run_wt_layout(layout_config=layout_config)
49
- else:
50
- print(f"❌ Unsupported platform: {platform.system()}")
51
- return None
52
-
53
-
54
- def handle_layout_args(layout_path: str, layouts: Optional[str]) -> None:
53
+ def handle_layout_args(layout_path: str, select_layouts: Optional[str], select_interactively: bool) -> list["LayoutConfig"]:
54
+ from machineconfig.utils.path_extended import PathExtended
55
+ from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
56
+ from machineconfig.utils.options import choose_from_options
57
+ from machineconfig.utils.path_helper import match_file_name, sanitize_path
55
58
  path_obj = sanitize_path(layout_path)
56
59
  if not path_obj.exists():
57
60
  choice_file = match_file_name(sub_string=layout_path, search_root=PathExtended.cwd(), suffixes={".json"})
@@ -63,5 +66,66 @@ def handle_layout_args(layout_path: str, layouts: Optional[str]) -> None:
63
66
  choice_file = PathExtended(choice_file)
64
67
  else:
65
68
  choice_file = path_obj
66
- for a_layout_config in select_layout(layouts_json_file=choice_file, layouts_name=layouts.split(",") if layouts else None):
67
- launch_layout(layout_config=a_layout_config)
69
+ selected_layouts = select_layout(layouts_json_file=choice_file, selected_layouts_names=select_layouts.split(",") if select_layouts else None, select_interactively=select_interactively)
70
+ return selected_layouts
71
+
72
+
73
+ def launch(layout_path: str = typer.Argument(..., help="Path to the layout.json file"),
74
+ max_tabs: int = typer.Option(10, help="A Sanity checker that throws an error if any layout exceeds the maximum number of tabs to launch."),
75
+ max_layouts: int = typer.Option(10, help="A Sanity checker that throws an error if the total number of layouts exceeds this number."),
76
+ sleep_inbetween: float = typer.Option(1.0, help="Sleep time in seconds between launching layouts"),
77
+ monitor: bool = typer.Option(False, help="Monitor the layout sessions for completion"),
78
+ parallel: bool = typer.Option(False, help="Launch multiple layouts in parallel"),
79
+ kill_upon_completion: bool = typer.Option(False, help="Kill session(s) upon completion (only relevant if monitor flag is set)"),
80
+ select: Optional[str] = typer.Option(None, help="Comma separated names of layouts to be selected from the layout file passed"),
81
+ select_interactively: bool = typer.Option(False, help="Select layouts interactively")
82
+ ):
83
+ """
84
+ """
85
+ layouts_selected = handle_layout_args(layout_path=layout_path, select_layouts=select, select_interactively=select_interactively)
86
+
87
+ # ============= Basic sanity checks =============
88
+ if len(layouts_selected) > max_layouts:
89
+ raise ValueError(f"Number of layouts {len(layouts_selected)} exceeds the maximum allowed {max_layouts}. Please adjust your layout file.")
90
+ for a_layout in layouts_selected:
91
+ if len(a_layout["layoutTabs"]) > max_tabs:
92
+ typer.echo(f"Layout '{a_layout.get('layoutName', 'Unnamed')}' has {len(a_layout['layoutTabs'])} tabs which exceeds the max of {max_tabs}.")
93
+ confirm = typer.confirm("Do you want to proceed with launching this layout?", default=False)
94
+ if not confirm:
95
+ typer.echo("Aborting launch.")
96
+ raise typer.Exit(0)
97
+
98
+ import time
99
+ import platform
100
+ if platform.system() == "Linux" or platform.system() == "Darwin":
101
+ from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
102
+ if not parallel: iterable = [[item] for item in layouts_selected]
103
+ else: iterable = [layouts_selected]
104
+ for i, a_layouts in enumerate(iterable):
105
+ manager = ZellijLocalManager(session_layouts=a_layouts)
106
+ manager.start_all_sessions(poll_interval=2, poll_seconds=2)
107
+ if monitor:
108
+ manager.run_monitoring_routine(wait_ms=2000)
109
+ if kill_upon_completion:
110
+ manager.kill_all_sessions()
111
+ if i < len(layouts_selected) - 1: # Don't sleep after the last layout
112
+ time.sleep(sleep_inbetween)
113
+ elif platform.system() == "Windows":
114
+ from machineconfig.cluster.sessions_managers.wt_local_manager import WTLocalManager
115
+ if not parallel: iterable = [[item] for item in layouts_selected]
116
+ else: iterable = [layouts_selected]
117
+ for i, a_layouts in enumerate(iterable):
118
+ manager = WTLocalManager(session_layouts=a_layouts)
119
+ manager.start_all_sessions()
120
+ if monitor:
121
+ manager.run_monitoring_routine(wait_ms=2000)
122
+ if kill_upon_completion:
123
+ manager.kill_all_sessions()
124
+ if i < len(layouts_selected) - 1: # Don't sleep after the last layout
125
+ time.sleep(sleep_inbetween)
126
+ else:
127
+ print(f"❌ Unsupported platform: {platform.system()}")
128
+
129
+
130
+ if __name__ == "__main__":
131
+ from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
@@ -81,24 +81,7 @@ def install_windows_desktop_apps() -> bool:
81
81
  """Install Windows desktop applications using winget."""
82
82
  if system() != "Windows":
83
83
  console.print("❌ This function is only available on Windows systems.", style="bold red")
84
- return False
85
-
86
- console.print(Panel("💻 [bold cyan]WINDOWS DESKTOP APPS[/bold cyan]\n[italic]Installing Brave, Windows Terminal, PowerShell, and VSCode[/italic]", border_style="cyan"))
87
-
88
- # Install winget applications
89
- winget_commands = [
90
- ('winget install --no-upgrade --name "Windows Terminal" --Id "Microsoft.WindowsTerminal" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Windows Terminal"),
91
- ('winget install --no-upgrade --name "Powershell" --Id "Microsoft.PowerShell" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing PowerShell"),
92
- ('winget install --no-upgrade --name "Brave" --Id "Brave.Brave" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Brave Browser"),
93
- ('winget install --no-upgrade --name "Microsoft Visual Studio Code" --Id "Microsoft.VisualStudioCode" --source winget --scope user --accept-package-agreements --accept-source-agreements', "Installing Visual Studio Code"),
94
- ]
95
-
96
- success = True
97
- for command, description in winget_commands:
98
- if not run_command(command, description):
99
- success = False
100
-
101
- # Install Nerd Fonts via Python
84
+ return False
102
85
  console.print("🔧 Installing Nerd Fonts", style="bold cyan")
103
86
  try:
104
87
  from machineconfig.jobs.installer.custom_dev.nerfont_windows_helper import install_nerd_fonts
@@ -106,19 +89,14 @@ def install_windows_desktop_apps() -> bool:
106
89
  console.print("✅ Nerd Fonts installed successfully", style="bold green")
107
90
  except Exception as e:
108
91
  console.print(f"❌ Error installing Nerd Fonts: {e}", style="bold red")
109
- success = False
110
-
111
- # Set Windows Terminal settings via Python
112
92
  console.print("🔧 Setting Windows Terminal settings", style="bold cyan")
113
93
  try:
114
94
  from machineconfig.setup_windows.wt_and_pwsh.set_wt_settings import main as set_wt_settings_main
115
95
  set_wt_settings_main()
116
96
  console.print("✅ Windows Terminal settings configured successfully", style="bold green")
117
97
  except Exception as e:
118
- console.print(f"❌ Error setting Windows Terminal settings: {e}", style="bold red")
119
- success = False
120
-
121
- return success
98
+ console.print(f"❌ Error setting Windows Terminal settings: {e}", style="bold red")
99
+ return True
122
100
 
123
101
 
124
102
  def get_installation_choices() -> list[str]:
@@ -162,9 +140,13 @@ def execute_installations(selected_options: list[str]) -> None:
162
140
  run_command(f"bash {script}", "Installing Linux base system applications")
163
141
 
164
142
  if "upgrade_system" in selected_options:
165
- console.print(Panel("🔄 [bold magenta]SYSTEM UPDATE[/bold magenta]\n[italic]Package management[/italic]", border_style="magenta"))
166
- run_command("sudo nala upgrade -y", "Upgrading system packages")
167
-
143
+ if system() == "Windows":
144
+ console.print(" System upgrade is not applicable on Windows via this script.", style="bold red")
145
+ elif system() == "Linux":
146
+ console.print(Panel("🔄 [bold magenta]SYSTEM UPDATE[/bold magenta]\n[italic]Package management[/italic]", border_style="magenta"))
147
+ run_command("sudo nala upgrade -y", "Upgrading system packages")
148
+ else:
149
+ console.print(f"❌ System upgrade not supported on {system()}.", style="bold red")
168
150
  if "install_uv_repos" in selected_options:
169
151
  console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Virtual environment setup[/italic]", border_style="green"))
170
152
  from machineconfig import setup_linux as module
@@ -212,9 +194,6 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
212
194
 
213
195
  if "install_dev_tools" in selected_options:
214
196
  console.print(Panel("🛠️ [bold bright_blue]DEVELOPMENT TOOLS[/bold bright_blue]\n[italic]Software development packages[/italic]", border_style="bright_blue"))
215
- run_command("(curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) || true", "Installing Rust toolchain")
216
- run_command("sudo nala install libssl-dev -y", "Installing libssl-dev")
217
- run_command("sudo nala install ffmpeg -y", "Installing ffmpeg")
218
197
  console.print("🔧 Installing development applications", style="bold cyan")
219
198
  try:
220
199
  from machineconfig.scripts.python.devops_devapps_install import main as devops_devapps_install_main
@@ -5,12 +5,6 @@ in the event that username@github.com is not mentioned in the remote url.
5
5
 
6
6
  """
7
7
 
8
- from machineconfig.utils.io import read_ini
9
- from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
10
- from machineconfig.utils.path_extended import PathExtended as PathExtended
11
- from machineconfig.scripts.python.repos_helper_record import main as record_repos
12
- from machineconfig.scripts.python.repos_helper_clone import clone_repos
13
- from machineconfig.scripts.python.repos_helper_action import perform_git_operations
14
8
 
15
9
  import typer
16
10
  from typing import Annotated, Optional
@@ -34,6 +28,13 @@ def main(
34
28
  print("📂 Welcome to the Repository Manager")
35
29
  print("=" * 50 + "\n")
36
30
 
31
+ from machineconfig.utils.io import read_ini
32
+ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
33
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
34
+ from machineconfig.scripts.python.repos_helper_record import main as record_repos
35
+ from machineconfig.scripts.python.repos_helper_clone import clone_repos
36
+ from machineconfig.scripts.python.repos_helper_action import perform_git_operations
37
+
37
38
  if directory == "":
38
39
  repos_root = PathExtended.home().joinpath("code") # it is a positional argument, can never be empty.
39
40
  else:
@@ -3,10 +3,7 @@
3
3
  from pathlib import Path
4
4
  from typing import Optional, Annotated
5
5
  import typer
6
- from rich.console import Console
7
- from rich.panel import Panel
8
- from rich.text import Text
9
- from rich.align import Align
6
+
10
7
 
11
8
 
12
9
  """
@@ -21,6 +18,11 @@ reference:
21
18
 
22
19
  def display_terminal_url(local_ip_v4: str, port: int, protocol: str = "http") -> None:
23
20
  """Display a flashy, unmissable terminal URL announcement."""
21
+
22
+ from rich.console import Console
23
+ from rich.panel import Panel
24
+ from rich.text import Text
25
+ from rich.align import Align
24
26
  console = Console()
25
27
 
26
28
  # Create the main message with styling
@@ -6,4 +6,4 @@ echo """
6
6
  ======================================================================="""
7
7
 
8
8
  curl https://raw.githubusercontent.com/thisismygitrepo/machineconfig/main/src/machineconfig/setup_linux/ve.sh | bash
9
- $HOME/.local/bin/uv run --python 3.13 --with machineconfig devops ia
9
+ $HOME/.local/bin/uv run --python 3.13 --with machineconfig devops interactive
@@ -9,4 +9,4 @@ Invoke-WebRequest -Uri "https://raw.githubusercontent.com/thisismygitrepo/machin
9
9
  .\ve.ps1
10
10
  rm ve.ps1
11
11
 
12
- uv run --python 3.13 --with machineconfig devops ia
12
+ uv run --python 3.13 --with machineconfig devops interactive
@@ -2,7 +2,8 @@
2
2
 
3
3
  from machineconfig.utils.installer_utils.installer_abc import check_if_installed_already
4
4
  from machineconfig.utils.installer_utils.installer_class import Installer
5
- from machineconfig.utils.schemas.installer.installer_types import APP_INSTALLER_CATEGORY, InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
5
+ from machineconfig.utils.schemas.installer.installer_types import InstallerData, InstallerDataFiles, get_normalized_arch, get_os_name, OPERATING_SYSTEMS, CPU_ARCHITECTURES
6
+ from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS, PACKAGE_GROUP2NAMES
6
7
  from rich.console import Console
7
8
  from rich.panel import Panel
8
9
 
@@ -18,7 +19,7 @@ from joblib import Parallel, delayed
18
19
  def check_latest():
19
20
  console = Console() # Added console initialization
20
21
  console.print(Panel("🔍 CHECKING FOR LATEST VERSIONS", title="Status", expand=False)) # Replaced print with Panel
21
- installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL"])
22
+ installers = get_installers(os=get_os_name(), arch=get_normalized_arch(), which_cats=["ESSENTIAL"])
22
23
  installers_github = []
23
24
  for inst__ in installers:
24
25
  app_name = inst__["appName"]
@@ -91,30 +92,30 @@ def get_installed_cli_apps():
91
92
  return apps
92
93
 
93
94
 
94
- def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: list[APP_INSTALLER_CATEGORY]) -> list[InstallerData]:
95
+ def get_installers(os: OPERATING_SYSTEMS, arch: CPU_ARCHITECTURES, which_cats: list[PACKAGE_GROUPS]) -> list[InstallerData]:
95
96
  print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
96
- res_all = get_all_installer_data_files(which_cats=which_cats)
97
+ res_all = get_all_installer_data_files()
98
+ acceptable_apps_names: list[str] = []
99
+ for cat in which_cats:
100
+ acceptable_apps_names += PACKAGE_GROUP2NAMES[cat]
97
101
  all_installers: list[InstallerData] = []
98
- for _category, installer_data_files in res_all.items():
99
- suitable_installers = []
100
- for an_installer in installer_data_files["installers"]:
101
- if an_installer["fileNamePattern"][arch][os] is None:
102
+ for installer_data in res_all:
103
+ if installer_data["appName"] in acceptable_apps_names:
104
+ if installer_data["fileNamePattern"][arch][os] is None:
102
105
  continue
103
- suitable_installers.append(an_installer)
104
- all_installers.extend(suitable_installers)
106
+ all_installers.append(installer_data)
105
107
  print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
106
108
  return all_installers
107
109
 
108
110
 
109
- def get_all_installer_data_files(which_cats: list[APP_INSTALLER_CATEGORY]) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
111
+ def get_all_installer_data_files() -> list[InstallerData]:
110
112
  print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
111
113
  import machineconfig.jobs.installer as module
112
114
  from pathlib import Path
113
115
  print("📂 Loading configuration files...")
114
- res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {key: read_json(Path(module.__file__).parent.joinpath(f"packages_{key.lower()}.json")) for key in which_cats}
116
+ res_raw: InstallerDataFiles = read_json(Path(module.__file__).parent.joinpath("installer_data.json"))
117
+ res_final: list[InstallerData] = res_raw["installers"]
115
118
  print(f"Loaded: {len(res_final)} installer categories")
116
- for k, v in res_final.items():
117
- print(f" - {k}: {len(v['installers'])} items")
118
119
  return res_final
119
120
 
120
121
 
@@ -109,8 +109,7 @@ def main() -> None:
109
109
  current_dir = Path(__file__).parent
110
110
  installer_dir = current_dir.parent.parent / "jobs" / "installer"
111
111
 
112
- standard_json = installer_dir / "packages_standard.json"
113
- dev_json = installer_dir / "packages_dev.json"
112
+ standard_json = installer_dir / "installer_data.json"
114
113
  output_json = current_dir / "github_releases.json"
115
114
 
116
115
  print("🔍 Starting GitHub release data extraction...")
@@ -125,16 +124,7 @@ def main() -> None:
125
124
  all_github_repos.update(repos)
126
125
  print(f" Found {len(repos)} GitHub repos")
127
126
  else:
128
- print(f"⚠️ File not found: {standard_json}")
129
-
130
- if dev_json.exists():
131
- print(f"📄 Reading {dev_json.name}...")
132
- repos = extract_github_repos_from_json(dev_json)
133
- all_github_repos.update(repos)
134
- print(f" Found {len(repos)} GitHub repos")
135
- else:
136
- print(f"⚠️ File not found: {dev_json}")
137
-
127
+ print(f"⚠️ File not found: {standard_json}")
138
128
  print(f"🎯 Total unique GitHub repositories found: {len(all_github_repos)}")
139
129
 
140
130
  if not all_github_repos:
@@ -3,7 +3,7 @@ from machineconfig.utils.path_extended import PathExtended as PathExtended
3
3
  from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH, INSTALL_VERSION_ROOT
4
4
 
5
5
  from pathlib import Path
6
- from typing import Any, Optional
6
+ from typing import Optional
7
7
  import subprocess
8
8
  import platform
9
9
 
@@ -166,75 +166,71 @@ def check_if_installed_already(exe_name: str, version: Optional[str], use_cache:
166
166
  return ("⚠️ NotInstalled", "None", version or "unknown")
167
167
 
168
168
 
169
- def parse_apps_installer_linux(txt: str) -> dict[str, Any]:
169
+ def parse_apps_installer_linux(txt: str) -> dict[str, tuple[str, str]]:
170
170
  """Parse Linux shell installation scripts into logical chunks.
171
171
 
172
- Supports two formats:
173
- 1. Legacy format with 'yes '' | sed 3q; echo "----------------------------- installing' delimiter
174
- 2. New format with # --BLOCK:<name>-- comment signatures
172
+ Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
173
+ mapping block names to (description, shell script content) tuples.
175
174
 
176
175
  Returns:
177
- dict[str, str]: Dictionary mapping block/section names to their installation scripts
176
+ dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
178
177
  """
179
- # Try new block format first
180
- if "# --BLOCK:" in txt:
181
- import re
182
- # Split by block signatures: # --BLOCK:<name>--
183
- blocks = re.split(r'# --BLOCK:([^-]+)--', txt)
184
- res: dict[str, str] = {}
178
+ chunks = txt.split('# --GROUP:')
179
+ res: dict[str, tuple[str, str]] = {}
180
+
181
+ for chunk in chunks[1:]: # Skip first empty chunk before first group
182
+ lines = chunk.split('\n')
183
+ # First line contains the group name and description in format "NAME:DESCRIPTION"
184
+ group_line = lines[0].strip()
185
+
186
+ # Extract group name and description
187
+ if ':' in group_line:
188
+ parts = group_line.split(':', 1) # Split only on first colon
189
+ group_name = parts[0].strip()
190
+ group_description = parts[1].strip() if len(parts) > 1 else ""
191
+ else:
192
+ group_name = group_line
193
+ group_description = ""
185
194
 
186
- # Process blocks in pairs (block_name, block_content)
187
- for i in range(1, len(blocks), 2):
188
- if i + 1 < len(blocks):
189
- block_name = blocks[i].strip()
190
- block_content = blocks[i + 1].strip()
191
- if block_content:
192
- res[block_name] = block_content
195
+ # Rest is the content
196
+ content = '\n'.join(lines[1:]).strip()
193
197
 
194
- return res
198
+ if group_name and content:
199
+ res[group_name] = (group_description, content)
195
200
 
196
- # Legacy format fallback
197
- txts = txt.split("""yes '' | sed 3q; echo "----------------------------- installing """)
198
- res = {}
199
- for chunk in txts[1:]:
200
- try:
201
- k = chunk.split("----")[0].rstrip().lstrip()
202
- v = "\n".join(chunk.split("\n")[1:])
203
- res[k] = v
204
- except IndexError as e:
205
- print(f"""
206
- ❌ Error parsing chunk:
207
- {"-" * 50}
208
- {chunk}
209
- {"-" * 50}""")
210
- raise e
211
201
  return res
212
202
 
213
203
 
214
- def parse_apps_installer_windows(txt: str) -> dict[str, Any]:
215
- chunks: list[str] = []
216
- for idx, item in enumerate(txt.split(sep="winget install")):
217
- if idx == 0:
218
- continue
219
- if idx == 1:
220
- chunks.append(item)
204
+ def parse_apps_installer_windows(txt: str) -> dict[str, tuple[str, str]]:
205
+ """Parse Windows PowerShell installation scripts into logical chunks.
206
+
207
+ Splits scripts by # --GROUP:<name>:<description> comment signatures into a dictionary
208
+ mapping block names to (description, PowerShell script content) tuples.
209
+
210
+ Returns:
211
+ dict[str, tuple[str, str]]: Dictionary mapping block/section names to (description, installation_script) tuples
212
+ """
213
+ chunks = txt.split('# --GROUP:')
214
+ res: dict[str, tuple[str, str]] = {}
215
+
216
+ for chunk in chunks[1:]: # Skip first chunk before first group
217
+ lines = chunk.split('\n')
218
+ # First line contains the group name and description in format "NAME:DESCRIPTION"
219
+ group_line = lines[0].strip()
220
+
221
+ # Extract group name and description
222
+ if ':' in group_line:
223
+ parts = group_line.split(':', 1) # Split only on first colon
224
+ group_name = parts[0].strip()
225
+ group_description = parts[1].strip() if len(parts) > 1 else ""
221
226
  else:
222
- chunks.append("winget install" + item)
223
- # progs = L(txt.splitlines()).filter(lambda x: x.startswith("winget ") or x.startswith("#winget"))
224
- res: dict[str, str] = {}
225
- for a_chunk in chunks:
226
- try:
227
- name = a_chunk.split("--name ")[1]
228
- if "--Id" not in name:
229
- print(f"⚠️ Warning: {name} does not have an Id, skipping")
230
- continue
231
- name = name.split(" --Id ", maxsplit=1)[0].strip('"').strip('"')
232
- res[name] = a_chunk
233
- except IndexError as e:
234
- print(f"""
235
- ❌ Error parsing chunk:
236
- {"-" * 50}
237
- {a_chunk}
238
- {"-" * 50}""")
239
- raise e
227
+ group_name = group_line
228
+ group_description = ""
229
+
230
+ # Rest is the content
231
+ content = '\n'.join(lines[1:]).strip()
232
+
233
+ if group_name and content:
234
+ res[group_name] = (group_description, content)
235
+
240
236
  return res
@@ -58,10 +58,8 @@ class ProcessManager:
58
58
  title = "📊 INITIALIZING PROCESS MANAGER"
59
59
  console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
60
60
  process_info = []
61
-
62
61
  with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}")) as progress:
63
62
  progress.add_task("🔍 Reading system processes...", total=None)
64
-
65
63
  for proc in psutil.process_iter():
66
64
  try:
67
65
  mem_usage_mb = proc.memory_info().rss / (1024 * 1024)
@@ -162,7 +160,6 @@ class ProcessManager:
162
160
  console.print(Panel("", title="[bold blue]Process Info[/bold blue]", border_style="blue"))
163
161
 
164
162
  def kill(self, names: Optional[list[str]] = None, pids: Optional[list[int]] = None, commands: Optional[list[str]] = None):
165
- # header for process termination
166
163
  title = "💀 PROCESS TERMINATION"
167
164
  console.print(Panel(title, title="[bold blue]Process Info[/bold blue]", border_style="blue"))
168
165
  if names is None and pids is None and commands is None:
@@ -241,7 +238,6 @@ def get_age(create_time: Any) -> str:
241
238
 
242
239
  def main():
243
240
  from machineconfig.utils.procs import ProcessManager
244
-
245
241
  ProcessManager().choose_and_kill()
246
242
 
247
243
 
@@ -2,7 +2,6 @@ from typing import TypedDict, Literal, TypeAlias, Optional
2
2
  import platform
3
3
 
4
4
 
5
- APP_INSTALLER_CATEGORY: TypeAlias = Literal["GITHUB_ESSENTIAL", "CUSTOM_ESSENTIAL", "GITHUB_DEV", "CUSTOM_DEV"]
6
5
  CPU_ARCHITECTURES: TypeAlias = Literal["amd64", "arm64"]
7
6
  OPERATING_SYSTEMS: TypeAlias = Literal["windows", "linux", "macos"]
8
7