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

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

Potentially problematic release.


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

Files changed (122) hide show
  1. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  2. machineconfig/cluster/sessions_managers/wt_remote_manager.py +1 -1
  3. machineconfig/cluster/sessions_managers/zellij_local.py +2 -2
  4. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -1
  5. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +1 -1
  6. machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py +17 -7
  7. machineconfig/cluster/templates/utils.py +1 -1
  8. machineconfig/jobs/linux/msc/cli_agents.sh +18 -2
  9. machineconfig/jobs/python/python_ve_symlink.py +1 -1
  10. machineconfig/jobs/python/vscode/api.py +1 -1
  11. machineconfig/jobs/python/vscode/select_interpreter.py +2 -2
  12. machineconfig/jobs/python/vscode/sync_code.py +1 -1
  13. machineconfig/jobs/python_custom_installers/archive/ngrok.py +7 -6
  14. machineconfig/jobs/python_custom_installers/dev/aider.py +9 -1
  15. machineconfig/jobs/python_custom_installers/dev/alacritty.py +2 -1
  16. machineconfig/jobs/python_custom_installers/dev/brave.py +10 -1
  17. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +12 -4
  18. machineconfig/jobs/python_custom_installers/dev/code.py +10 -3
  19. machineconfig/jobs/python_custom_installers/dev/cursor.py +2 -1
  20. machineconfig/jobs/python_custom_installers/dev/docker_desktop.py +7 -6
  21. machineconfig/jobs/python_custom_installers/dev/espanso.py +14 -6
  22. machineconfig/jobs/python_custom_installers/dev/goes.py +10 -1
  23. machineconfig/jobs/python_custom_installers/dev/lvim.py +9 -1
  24. machineconfig/jobs/python_custom_installers/dev/nerdfont.py +9 -1
  25. machineconfig/jobs/python_custom_installers/dev/redis.py +2 -2
  26. machineconfig/jobs/python_custom_installers/dev/wezterm.py +3 -1
  27. machineconfig/jobs/python_custom_installers/dev/winget.py +2 -1
  28. machineconfig/jobs/python_custom_installers/docker.py +9 -1
  29. machineconfig/jobs/python_custom_installers/gh.py +11 -2
  30. machineconfig/jobs/python_custom_installers/hx.py +10 -9
  31. machineconfig/jobs/python_custom_installers/warp-cli.py +9 -1
  32. machineconfig/jobs/python_generic_installers/config.json +601 -412
  33. machineconfig/jobs/python_generic_installers/config.json.bak +414 -0
  34. machineconfig/jobs/python_generic_installers/dev/config.json +822 -562
  35. machineconfig/jobs/python_generic_installers/dev/config.json.bak +565 -0
  36. machineconfig/jobs/python_linux_installers/archive/config.json +16 -8
  37. machineconfig/jobs/python_linux_installers/archive/config.json.bak +10 -0
  38. machineconfig/jobs/python_linux_installers/config.json +134 -99
  39. machineconfig/jobs/python_linux_installers/config.json.bak +110 -0
  40. machineconfig/jobs/python_linux_installers/dev/config.json +273 -203
  41. machineconfig/jobs/python_linux_installers/dev/config.json.bak +206 -0
  42. machineconfig/jobs/python_windows_installers/config.json +74 -48
  43. machineconfig/jobs/python_windows_installers/config.json.bak +56 -0
  44. machineconfig/jobs/python_windows_installers/dev/config.json +3 -2
  45. machineconfig/jobs/python_windows_installers/dev/config.json.bak +3 -0
  46. machineconfig/profile/create.py +3 -3
  47. machineconfig/profile/shell.py +1 -1
  48. machineconfig/scripts/python/ai/mcinit.py +23 -67
  49. machineconfig/scripts/python/ai/solutions/__init__.py +0 -0
  50. machineconfig/scripts/python/ai/solutions/_shared.py +5 -0
  51. machineconfig/scripts/python/ai/solutions/claude/claude.py +8 -0
  52. machineconfig/scripts/python/ai/solutions/cline/cline.py +10 -0
  53. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +35 -0
  54. machineconfig/scripts/python/ai/solutions/copilot/privacy.md +4 -0
  55. machineconfig/scripts/python/ai/solutions/crush/crush.json +216 -0
  56. machineconfig/scripts/python/ai/solutions/crush/crush.py +25 -0
  57. machineconfig/scripts/python/ai/solutions/crush/privacy.md +2 -0
  58. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +10 -0
  59. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +14 -0
  60. machineconfig/scripts/python/ai/solutions/generic.py +41 -0
  61. machineconfig/scripts/python/ai/solutions/kilocode/privacy.md +3 -0
  62. machineconfig/scripts/python/ai/solutions/opencode/opencode.json +4 -0
  63. machineconfig/scripts/python/ai/solutions/opencode/opencode.py +1 -0
  64. machineconfig/scripts/python/choose_wezterm_theme.py +1 -1
  65. machineconfig/scripts/python/cloud_copy.py +2 -2
  66. machineconfig/scripts/python/cloud_mount.py +2 -2
  67. machineconfig/scripts/python/cloud_repo_sync.py +3 -2
  68. machineconfig/scripts/python/croshell.py +12 -7
  69. machineconfig/scripts/python/devops.py +1 -0
  70. machineconfig/scripts/python/devops_add_identity.py +1 -1
  71. machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
  72. machineconfig/scripts/python/devops_backup_retrieve.py +4 -3
  73. machineconfig/scripts/python/devops_devapps_install.py +39 -17
  74. machineconfig/scripts/python/devops_update_repos.py +2 -2
  75. machineconfig/scripts/python/dotfile.py +1 -1
  76. machineconfig/scripts/python/fire_agents.py +7 -3
  77. machineconfig/scripts/python/fire_agents_help_launch.py +2 -2
  78. machineconfig/scripts/python/fire_jobs.py +8 -8
  79. machineconfig/scripts/python/fire_jobs_layout_helper.py +2 -2
  80. machineconfig/scripts/python/ftpx.py +2 -2
  81. machineconfig/scripts/python/helpers/cloud_helpers.py +2 -1
  82. machineconfig/scripts/python/helpers/helpers2.py +4 -3
  83. machineconfig/scripts/python/helpers/helpers4.py +1 -1
  84. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  85. machineconfig/scripts/python/mount_nfs.py +1 -1
  86. machineconfig/scripts/python/mount_ssh.py +1 -1
  87. machineconfig/scripts/python/repos.py +6 -3
  88. machineconfig/scripts/python/repos_helper_clone.py +121 -0
  89. machineconfig/scripts/python/repos_helper_record.py +2 -2
  90. machineconfig/scripts/python/start_slidev.py +1 -1
  91. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  92. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +9 -8
  93. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -3
  94. machineconfig/utils/{utils2.py → accessories.py} +13 -27
  95. machineconfig/utils/code.py +2 -2
  96. machineconfig/utils/installer.py +47 -33
  97. machineconfig/utils/installer_utils/installer_abc.py +2 -5
  98. machineconfig/utils/installer_utils/installer_class.py +109 -103
  99. machineconfig/utils/io.py +94 -0
  100. machineconfig/utils/links.py +2 -2
  101. machineconfig/utils/notifications.py +0 -9
  102. machineconfig/utils/{path_reduced.py → path_extended.py} +2 -2
  103. machineconfig/utils/{path.py → path_helper.py} +1 -1
  104. machineconfig/utils/procs.py +2 -1
  105. machineconfig/utils/{utils5.py → scheduler.py} +3 -8
  106. machineconfig/utils/schemas/installer/installer_types.py +20 -0
  107. machineconfig/utils/ssh.py +2 -2
  108. machineconfig/utils/terminal.py +12 -2
  109. machineconfig/utils/ve.py +2 -16
  110. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/METADATA +1 -4
  111. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/RECORD +121 -97
  112. machineconfig/utils/io_save.py +0 -95
  113. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Thinking-Beast-Mode.chatmode.md +0 -0
  114. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +0 -0
  115. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/deepResearch.chatmode.md +0 -0
  116. /machineconfig/scripts/python/ai/{instructions → solutions/copilot/instructions}/python/dev.instructions.md +0 -0
  117. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/allLintersAndTypeCheckers.prompt.md +0 -0
  118. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/research-report-skeleton.prompt.md +0 -0
  119. /machineconfig/scripts/python/ai/{configs/.gemini → solutions/gemini}/settings.json +0 -0
  120. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/WHEEL +0 -0
  121. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/entry_points.txt +0 -0
  122. {machineconfig-3.2.dist-info → machineconfig-3.5.dist-info}/top_level.txt +0 -0
@@ -11,17 +11,17 @@ from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_in
11
11
  from machineconfig.scripts.python.helpers.helpers4 import convert_kwargs_to_fire_kwargs_str
12
12
  from machineconfig.scripts.python.helpers.helpers4 import parse_pyfile
13
13
  from machineconfig.scripts.python.helpers.helpers4 import get_import_module_code
14
- from machineconfig.utils.ve import get_repo_root, get_ve_activate_line, get_ve_path_and_ipython_profile
14
+ from machineconfig.utils.ve import get_ve_activate_line, get_ve_path_and_ipython_profile
15
15
  from machineconfig.utils.options import display_options, choose_one_option
16
- from machineconfig.utils.path import match_file_name, sanitize_path
16
+ from machineconfig.utils.path_helper import match_file_name, sanitize_path
17
17
 
18
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
19
- from machineconfig.utils.io_save import save_toml
20
- from machineconfig.utils.utils2 import randstr, read_toml
18
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
19
+ from machineconfig.utils.accessories import get_repo_root, randstr
21
20
  from machineconfig.scripts.python.fire_jobs_args_helper import get_args, FireJobArgs, extract_kwargs
22
21
  import platform
23
22
  from typing import Optional
24
23
  from pathlib import Path
24
+ import tomllib
25
25
  # import os
26
26
 
27
27
 
@@ -113,7 +113,7 @@ def route(args: FireJobArgs) -> None:
113
113
  toml_path = toml_path_maybe
114
114
  if toml_path is not None:
115
115
  print(f"📄 Reading config.toml @ {toml_path}")
116
- config = read_toml(toml_path)
116
+ config = tomllib.loads(toml_path.read_text(encoding="utf-8"))
117
117
  if "server" in config:
118
118
  if "port" in config["server"]:
119
119
  port = config["server"]["port"]
@@ -121,7 +121,7 @@ def route(args: FireJobArgs) -> None:
121
121
  if repo_root is not None:
122
122
  secrets_template_path = PathExtended.home().joinpath(f"dotfiles/creds/streamlit/{PathExtended(repo_root).name}/{choice_file.name}/secrets.toml")
123
123
  if args.environment != "" and not secrets_path.exists() and secrets_template_path.exists():
124
- secrets_template = read_toml(secrets_template_path)
124
+ secrets_template = tomllib.loads(secrets_template_path.read_text(encoding="utf-8"))
125
125
  if args.environment == "ip":
126
126
  host_url = f"http://{local_ip_v4}:{port}/oauth2callback"
127
127
  elif args.environment == "localhost":
@@ -134,7 +134,7 @@ def route(args: FireJobArgs) -> None:
134
134
  secrets_template["auth"]["redirect_uri"] = host_url
135
135
  secrets_template["auth"]["cookie_secret"] = randstr(35)
136
136
  secrets_template["auth"]["auth0"]["redirect_uri"] = host_url
137
- save_toml(obj=secrets_template, path=secrets_path)
137
+ # save_toml(obj=secrets_template, path=secrets_path)
138
138
  except Exception as ex:
139
139
  print(ex)
140
140
  raise ex
@@ -3,8 +3,8 @@ from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, Layou
3
3
  from typing import Optional, TYPE_CHECKING
4
4
  from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
5
5
  from machineconfig.utils.options import choose_one_option
6
- from machineconfig.utils.path import match_file_name, sanitize_path
7
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
+ from machineconfig.utils.path_helper import match_file_name, sanitize_path
7
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
@@ -8,9 +8,9 @@ Currently, the only way to work around this is to predifine the host in ~/.ssh/c
8
8
 
9
9
  import argparse
10
10
  from machineconfig.utils.ssh import SSH
11
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
11
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
12
12
  from machineconfig.scripts.python.helpers.helpers2 import ES
13
- from machineconfig.utils.utils2 import pprint
13
+ from machineconfig.utils.accessories import pprint
14
14
 
15
15
 
16
16
  def main():
@@ -1,5 +1,6 @@
1
1
  from pathlib import Path
2
- from machineconfig.utils.utils2 import pprint, read_json, read_ini
2
+ from machineconfig.utils.io import read_ini, read_json
3
+ from machineconfig.utils.accessories import pprint
3
4
  from typing import Optional
4
5
  import os
5
6
  from machineconfig.utils.source_of_truth import DEFAULTS_PATH
@@ -1,6 +1,7 @@
1
1
  from machineconfig.scripts.python.helpers.cloud_helpers import Args, ArgsDefaults, absolute, find_cloud_config, get_secure_share_cloud_config
2
+ from machineconfig.utils.io import read_ini
2
3
  from machineconfig.utils.source_of_truth import DEFAULTS_PATH
3
- from machineconfig.utils.utils2 import read_ini, pprint
4
+ from machineconfig.utils.accessories import pprint
4
5
  from typing import Optional
5
6
  from rich.console import Console
6
7
  from rich.panel import Panel
@@ -103,7 +104,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
103
104
  if len(source_parts) > 1 and source_parts[1] == ES: # the source path is to be inferred from target.
104
105
  assert ES not in target, f"You can't use expand symbol `{ES}` in both source and target. Cyclical inference dependency arised."
105
106
  target_obj = absolute(target)
106
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
107
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
107
108
 
108
109
  remote_path = PathExtended(target_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
109
110
  source = f"{cloud}:{remote_path.as_posix()}"
@@ -123,7 +124,7 @@ def parse_cloud_source_target(args: Args, source: str, target: str) -> tuple[str
123
124
  if len(target_parts) > 1 and target_parts[1] == ES: # the target path is to be inferred from source.
124
125
  assert ES not in source, "You can't use $ in both source and target. Cyclical inference dependency arised."
125
126
  source_obj = absolute(source)
126
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
127
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
127
128
 
128
129
  remote_path = PathExtended(source_obj).get_remote_path(os_specific=os_specific, root=root, rel2home=rel2home, strict=False)
129
130
  target = f"{cloud}:{remote_path.as_posix()}"
@@ -3,7 +3,7 @@ import inspect
3
3
  import os
4
4
 
5
5
  # import argparse
6
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
7
7
  # from machineconfig.utils.utils import choose_ssh_host
8
8
 
9
9
 
@@ -1,8 +1,8 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
2
  from machineconfig.utils.terminal import Terminal
3
3
  from machineconfig.scripts.python.get_zellij_cmd import get_zellij_cmd
4
4
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
5
- from machineconfig.utils.utils2 import read_ini
5
+ from machineconfig.utils.io import read_ini
6
6
  from machineconfig.utils.code import write_shell_script_to_file
7
7
  import platform
8
8
  from rich.console import Console
@@ -1,6 +1,6 @@
1
1
  """NFS mounting script"""
2
2
 
3
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
3
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
4
4
  from machineconfig.utils.ssh import SSH
5
5
  from machineconfig.utils.terminal import Terminal
6
6
  from machineconfig.utils.options import display_options, choose_ssh_host
@@ -3,7 +3,7 @@
3
3
  from platform import system
4
4
  from machineconfig.utils.ssh import SSH
5
5
  from machineconfig.utils.terminal import Terminal
6
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
7
7
 
8
8
  from machineconfig.utils.options import choose_ssh_host
9
9
 
@@ -5,11 +5,13 @@ 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
8
9
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
9
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
10
- from machineconfig.utils.utils2 import randstr, read_ini
10
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
11
+ from machineconfig.utils.accessories import randstr
11
12
  from machineconfig.scripts.python.repos_helper_update import update_repository
12
13
  from machineconfig.scripts.python.repos_helper_record import main as record_repos
14
+ from machineconfig.scripts.python.repos_helper_clone import clone_repos
13
15
 
14
16
  import argparse
15
17
  from enum import Enum
@@ -119,7 +121,7 @@ def main():
119
121
  elif args.clone or args.checkout or args.checkout_to_branch:
120
122
  print("\n📥 Cloning or checking out repositories...")
121
123
  print(">>>>>>>>> Cloning Repos")
122
- if not repos_root.exists() or repos_root.name != "repos.json": # Fixed: use name instead of stem
124
+ if not repos_root.exists() or repos_root.name != "repos.json":
123
125
  repos_root = PathExtended(CONFIG_PATH).joinpath("repos").joinpath(repos_root.rel2home()).joinpath("repos.json")
124
126
  if not repos_root.exists():
125
127
  if args.cloud is None:
@@ -130,6 +132,7 @@ def main():
130
132
  assert cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
131
133
  repos_root.from_cloud(cloud=cloud, rel2home=True)
132
134
  assert (repos_root.exists() and repos_root.name == "repos.json") or args.cloud is not None, f"Path {repos_root} does not exist and cloud was not passed. You can't clone without one of them."
135
+ clone_repos(spec_path=repos_root, preferred_remote=None, checkout_branch_flag=args.checkout_to_branch, checkout_commit_flag=args.checkout)
133
136
 
134
137
  elif args.all or args.commit or args.pull or args.push:
135
138
  print(f"\n🔄 Performing Git actions on repositories @ `{repos_root}`...")
@@ -0,0 +1,121 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, Optional, cast
4
+
5
+ from git import Repo as GitRepo
6
+ from git.exc import GitCommandError
7
+ from rich import print as pprint
8
+ from rich.progress import BarColumn, MofNCompleteColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
9
+
10
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
11
+ from machineconfig.utils.schemas.repos.repos_types import RepoRecordDict, RepoRecordFile, RepoRemote
12
+ from machineconfig.utils.io import read_json
13
+
14
+
15
+ CloneStatus = Literal["cloned", "skipped", "failed"]
16
+
17
+
18
+ def choose_remote(remotes: list[RepoRemote], preferred_remote: Optional[str]) -> Optional[RepoRemote]:
19
+ if preferred_remote is not None:
20
+ for remote in remotes:
21
+ if remote["name"] == preferred_remote:
22
+ return remote
23
+ for remote in remotes:
24
+ if remote["name"] == "origin":
25
+ return remote
26
+ return remotes[0] if len(remotes) > 0 else None
27
+
28
+
29
+ def ensure_destination(parent_dir: str, name: str) -> PathExtended:
30
+ parent_path = PathExtended(parent_dir).expanduser().absolute()
31
+ parent_path.mkdir(parents=True, exist_ok=True)
32
+ return parent_path.joinpath(name)
33
+
34
+
35
+ def checkout_branch(repo: GitRepo, branch: str) -> bool:
36
+ if branch == "DETACHED":
37
+ return False
38
+ current_branch = repo.active_branch.name if not repo.head.is_detached else None
39
+ if current_branch == branch:
40
+ return False
41
+ repo.git.checkout(branch)
42
+ return True
43
+
44
+
45
+ def checkout_commit(repo: GitRepo, commit: str) -> bool:
46
+ if commit in {"", "UNKNOWN"}:
47
+ return False
48
+ current_commit = repo.head.commit.hexsha
49
+ if current_commit == commit:
50
+ return False
51
+ repo.git.checkout(commit)
52
+ return True
53
+
54
+
55
+ def clone_single_repo(repo_spec: RepoRecordDict, preferred_remote: Optional[str], checkout_branch_flag: bool, checkout_commit_flag: bool) -> tuple[CloneStatus, str]:
56
+ destination = ensure_destination(parent_dir=repo_spec["parentDir"], name=repo_spec["name"])
57
+ repo_path = destination.joinpath(".git")
58
+ remotes = repo_spec["remotes"]
59
+ if len(remotes) == 0:
60
+ return ("failed", f"No remotes configured for {destination}")
61
+ remote = choose_remote(remotes=remotes, preferred_remote=preferred_remote)
62
+ if remote is None:
63
+ return ("failed", f"No usable remote for {destination}")
64
+ repo = None
65
+ status: CloneStatus
66
+ message: str
67
+ if destination.exists() and repo_path.exists():
68
+ status = "skipped"
69
+ repo = GitRepo(str(destination))
70
+ message = f"Skipped cloning for {destination}; existing repository reused"
71
+ elif destination.exists() and not repo_path.exists():
72
+ return ("failed", f"Destination exists but is not a git repository: {destination}")
73
+ else:
74
+ try:
75
+ pprint(f"📥 Cloning {repo_spec['name']} from {remote['url']}")
76
+ repo = GitRepo.clone_from(url=remote["url"], to_path=str(destination))
77
+ status = "cloned"
78
+ message = f"Cloned {destination}"
79
+ except Exception as err: # noqa: BLE001
80
+ return ("failed", f"Clone failed for {destination}: {err}")
81
+ assert repo is not None
82
+ checkout_summary: list[str] = []
83
+ try:
84
+ if checkout_branch_flag:
85
+ if checkout_branch(repo=repo, branch=repo_spec["version"]["branch"]):
86
+ checkout_summary.append(f"branch {repo_spec['version']['branch']}")
87
+ if checkout_commit_flag:
88
+ if checkout_commit(repo=repo, commit=repo_spec["version"]["commit"]):
89
+ checkout_summary.append(f"commit {repo_spec['version']['commit'][:8]}")
90
+ except GitCommandError as err:
91
+ return ("failed", f"Checkout failed for {destination}: {err}")
92
+ if len(checkout_summary) > 0:
93
+ message = f"{message} | Checked out {' & '.join(checkout_summary)}"
94
+ return (status, message)
95
+
96
+
97
+ def clone_repos(spec_path: PathExtended, preferred_remote: Optional[str], checkout_branch_flag: bool, checkout_commit_flag: bool) -> list[tuple[CloneStatus, str]]:
98
+ data = cast(RepoRecordFile, read_json(path=spec_path))
99
+ repos = data["repos"]
100
+ results: list[tuple[CloneStatus, str]] = []
101
+ with Progress(SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), MofNCompleteColumn(), TimeElapsedColumn()) as progress:
102
+ task_id = progress.add_task("Processing repositories...", total=len(repos))
103
+ for repo_spec in repos:
104
+ progress.update(task_id, description=f"Processing {repo_spec['name']}")
105
+ try:
106
+ result = clone_single_repo(repo_spec=repo_spec, preferred_remote=preferred_remote, checkout_branch_flag=checkout_branch_flag, checkout_commit_flag=checkout_commit_flag)
107
+ except Exception as err: # noqa: BLE001
108
+ result = ("failed", f"Unexpected error for {repo_spec['name']}: {err}")
109
+ results.append(result)
110
+ if result[0] == "failed":
111
+ pprint(f"❌ {result[1]}")
112
+ elif result[0] == "cloned":
113
+ pprint(f"✅ {result[1]}")
114
+ else:
115
+ pprint(f"⏭️ {result[1]}")
116
+ progress.update(task_id, advance=1)
117
+ success_count = len([status for status, _ in results if status == "cloned"])
118
+ skip_count = len([status for status, _ in results if status == "skipped"])
119
+ fail_count = len([status for status, _ in results if status == "failed"])
120
+ pprint(f"✅ Cloned: {success_count} | ⏭️ Skipped: {skip_count} | ❌ Failed: {fail_count}")
121
+ return results
@@ -1,9 +1,9 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
2
  from machineconfig.utils.schemas.repos.repos_types import GitVersionInfo, RepoRecordDict, RepoRemote
3
3
 
4
4
  from machineconfig.utils.schemas.repos.repos_types import RepoRecordFile
5
5
  from machineconfig.utils.source_of_truth import CONFIG_PATH
6
- from machineconfig.utils.io_save import save_json
6
+ from machineconfig.utils.io import save_json
7
7
 
8
8
  from typing import Optional
9
9
 
@@ -4,7 +4,7 @@ slidev
4
4
 
5
5
  from machineconfig.utils.source_of_truth import CONFIG_PATH
6
6
  from machineconfig.utils.code import print_code
7
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
8
8
  from machineconfig.utils.terminal import Terminal
9
9
  import subprocess
10
10
  import platform
@@ -1,6 +1,6 @@
1
1
  """TWSL"""
2
2
 
3
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
3
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
4
4
  import argparse
5
5
  import platform
6
6
  import getpass
@@ -4,20 +4,21 @@ https://glitchbone.github.io/vscode-base16-term/#/3024
4
4
 
5
5
  """
6
6
 
7
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
7
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
8
8
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT
9
9
  from machineconfig.utils.installer_utils.installer_class import Installer
10
10
  import subprocess
11
11
  from typing import Iterable
12
+ from machineconfig.utils.schemas.installer.installer_types import InstallerData
12
13
 
13
14
 
14
- nerd_fonts = {
15
- "repo_url": "https://github.com/ryanoasis/nerd-fonts",
15
+ nerd_fonts: InstallerData = {
16
+ "appName": "Cascadia Code Nerd Font",
17
+ "repoURL": "https://github.com/ryanoasis/nerd-fonts",
16
18
  "doc": "Nerd Fonts is a project that patches developer targeted fonts with a high number of glyphs (icons)",
17
- "filename_template_windows_amd_64": "CascadiaCode.zip",
18
- "filename_template_linux_amd_64": "CascadiaCode.zip",
19
- "strip_v": False,
20
- "exe_name": "nerd_fonts",
19
+ "filenameTemplate": {"amd64": {"windows": "CascadiaCode.zip", "linux": "CascadiaCode.zip", "macos": ""}, "arm64": {"windows": "", "linux": "", "macos": ""}},
20
+ "stripVersion": False,
21
+ "exeName": "nerd_fonts",
21
22
  }
22
23
 
23
24
 
@@ -64,7 +65,7 @@ def install_nerd_fonts() -> None:
64
65
  return
65
66
  print(f"🔍 Missing fonts detected: {', '.join(missing)}. Proceeding with installation...")
66
67
  print("🔍 Downloading Nerd Fonts package...")
67
- folder, _version_to_be_installed = Installer.from_dict(d=nerd_fonts, name="nerd_fonts").download(version=None)
68
+ folder, _version_to_be_installed = Installer(installer_data=nerd_fonts).download(version=None)
68
69
 
69
70
  print("🧹 Cleaning up unnecessary files...")
70
71
  [p.delete(sure=True) for p in folder.search("*Windows*")]
@@ -1,8 +1,8 @@
1
1
  """Set Windows Terminal Settings"""
2
2
 
3
- from machineconfig.utils.utils2 import randstr, read_json
4
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
- from machineconfig.utils.io_save import save_json
3
+ from machineconfig.utils.accessories import randstr
4
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
5
+ from machineconfig.utils.io import read_json, save_json
6
6
  import platform
7
7
 
8
8
  # from uuid import uuid4
@@ -32,33 +32,6 @@ def split[T](iterable: list[T], every: int = 1, to: Optional[int] = None) -> lis
32
32
  return list(res)
33
33
 
34
34
 
35
- def read_ini(path: "Path", encoding: Optional[str] = None):
36
- if not Path(path).exists() or Path(path).is_dir():
37
- raise FileNotFoundError(f"File not found or is a directory: {path}")
38
- import configparser
39
-
40
- res = configparser.ConfigParser()
41
- res.read(filenames=[str(path)], encoding=encoding)
42
- return res
43
-
44
-
45
- def read_json(path: "Path", r: bool = False, **kwargs: Any) -> Any: # return could be list or dict etc
46
- import json
47
- try:
48
- mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
49
- except Exception:
50
- import pyjson5
51
- mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
52
- _ = r
53
- return mydict
54
-
55
-
56
- def read_toml(path: "Path"):
57
- import tomli
58
-
59
- return tomli.loads(path.read_text(encoding="utf-8"))
60
-
61
-
62
35
  def pprint(obj: dict[Any, Any], title: str) -> None:
63
36
  from rich import inspect
64
37
 
@@ -84,3 +57,16 @@ def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
84
57
  else:
85
58
  result[k] = v
86
59
  return result
60
+
61
+
62
+ def get_repo_root(path: Path) -> Optional[Path]:
63
+ from git import Repo, InvalidGitRepositoryError
64
+
65
+ try:
66
+ repo = Repo(str(path), search_parent_directories=True)
67
+ root = repo.working_tree_dir
68
+ if root is not None:
69
+ return Path(root)
70
+ except InvalidGitRepositoryError:
71
+ pass
72
+ return None
@@ -5,9 +5,9 @@ from rich.console import Console
5
5
  from rich.panel import Panel
6
6
  from rich.syntax import Syntax
7
7
 
8
- from machineconfig.utils.utils2 import randstr
8
+ from machineconfig.utils.accessories import randstr
9
9
  from machineconfig.utils.ve import get_ve_activate_line
10
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
10
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
11
11
 
12
12
 
13
13
  def get_shell_script_executing_python_file(python_file: str, func: Optional[str], ve_path: str, strict_execution: bool = True):
@@ -1,15 +1,15 @@
1
1
  """package manager"""
2
2
 
3
- from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH, CATEGORY
3
+ from machineconfig.utils.installer_utils.installer_abc import LINUX_INSTALL_PATH
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
5
6
  from rich.console import Console
6
7
  from rich.panel import Panel # Added import
7
8
 
8
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
9
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
9
10
  from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT
10
- from machineconfig.utils.utils2 import read_json
11
+ from machineconfig.utils.io import read_json
11
12
 
12
- # from dataclasses import dataclass
13
13
  from typing import Any
14
14
  import platform
15
15
  from joblib import Parallel, delayed
@@ -22,21 +22,25 @@ def check_latest():
22
22
  # installers += get_installers(system=platform.system(), dev=True)
23
23
  installers_github = []
24
24
  for inst__ in installers:
25
- if "ntop" in inst__.name:
26
- print(f"⏭️ Skipping {inst__.name} (ntop)")
25
+ app_name = inst__.installer_data.get("appName", "unknown")
26
+ repo_url = inst__.installer_data.get("repoURL", "")
27
+ if "ntop" in app_name:
28
+ print(f"⏭️ Skipping {app_name} (ntop)")
27
29
  continue
28
- if "github" not in inst__.repo_url:
29
- print(f"⏭️ Skipping {inst__.name} (not a GitHub release)")
30
+ if "github" not in repo_url:
31
+ print(f"⏭️ Skipping {app_name} (not a GitHub release)")
30
32
  continue
31
33
  installers_github.append(inst__)
32
34
 
33
35
  print(f"\n🔍 Checking {len(installers_github)} GitHub-based installers...\n")
34
36
 
35
37
  def func(inst: Installer):
36
- print(f"🔎 Checking {inst.exe_name}...")
37
- _release_url, version_to_be_installed = inst.get_github_release(repo_url=inst.repo_url, version=None)
38
- verdict, current_ver, new_ver = inst.check_if_installed_already(exe_name=inst.exe_name, version=version_to_be_installed, use_cache=False)
39
- return inst.exe_name, verdict, current_ver, new_ver
38
+ exe_name = inst.installer_data.get("exeName", "unknown")
39
+ repo_url = inst.installer_data.get("repoURL", "")
40
+ print(f"🔎 Checking {exe_name}...")
41
+ _release_url, version_to_be_installed = inst.get_github_release(repo_url=repo_url, version=None)
42
+ verdict, current_ver, new_ver = inst.check_if_installed_already(exe_name=exe_name, version=version_to_be_installed, use_cache=False)
43
+ return exe_name, verdict, current_ver, new_ver
40
44
 
41
45
  print("\n⏳ Processing installers...\n")
42
46
  res = [func(inst) for inst in installers_github]
@@ -90,20 +94,23 @@ def get_installed_cli_apps():
90
94
 
91
95
  def get_installers(system: str, dev: bool) -> list[Installer]:
92
96
  print(f"\n{'=' * 80}\n🔍 LOADING INSTALLER CONFIGURATIONS 🔍\n{'=' * 80}")
93
- res_all = get_all_dicts(system=system)
97
+ res_all = get_all_installer_data_files(system=system)
94
98
  if not dev:
95
99
  print("ℹ️ Excluding development installers...")
96
100
  del res_all["CUSTOM_DEV"]
97
101
  del res_all["OS_SPECIFIC_DEV"]
98
102
  del res_all["OS_GENERIC_DEV"]
99
- res_final = {}
100
- for _k, v in res_all.items():
101
- res_final.update(v)
102
- print(f"✅ Loaded {len(res_final)} installer configurations\n{'=' * 80}")
103
- return [Installer.from_dict(d=vd, name=k) for k, vd in res_final.items()]
104
103
 
104
+ # Flatten the installer data from all categories
105
+ all_installers: list[InstallerData] = []
106
+ for _category, installer_data_files in res_all.items():
107
+ all_installers.extend(installer_data_files["installers"])
105
108
 
106
- def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
109
+ print(f"✅ Loaded {len(all_installers)} installer configurations\n{'=' * 80}")
110
+ return [Installer(installer_data=installer_data) for installer_data in all_installers]
111
+
112
+
113
+ def get_all_installer_data_files(system: str) -> dict[APP_INSTALLER_CATEGORY, InstallerDataFiles]:
107
114
  print(f"\n{'=' * 80}\n📂 LOADING CONFIGURATION FILES 📂\n{'=' * 80}")
108
115
 
109
116
  print(f"🔍 Importing OS-specific installers for {system}...")
@@ -122,18 +129,23 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
122
129
  path_os_generic_dev = path_os_generic.joinpath("dev")
123
130
 
124
131
  print("📂 Loading configuration files...")
125
- res_final: dict[CATEGORY, dict[str, dict[str, Any]]] = {}
132
+ res_final: dict[APP_INSTALLER_CATEGORY, InstallerDataFiles] = {}
133
+
126
134
  print(f"""📄 Loading OS-specific config from: {path_os_specific.joinpath("config.json")}""")
127
- res_final["OS_SPECIFIC"] = read_json(path=path_os_specific.joinpath("config.json"))
135
+ os_specific_data = read_json(path=path_os_specific.joinpath("config.json"))
136
+ res_final["OS_SPECIFIC"] = InstallerDataFiles(os_specific_data)
128
137
 
129
138
  print(f"""📄 Loading OS-generic config from: {path_os_generic.joinpath("config.json")}""")
130
- res_final["OS_GENERIC"] = read_json(path=path_os_generic.joinpath("config.json"))
139
+ os_generic_data = read_json(path=path_os_generic.joinpath("config.json"))
140
+ res_final["OS_GENERIC"] = InstallerDataFiles(os_generic_data)
131
141
 
132
142
  print(f"""📄 Loading OS-specific dev config from: {path_os_specific_dev.joinpath("config.json")}""")
133
- res_final["OS_SPECIFIC_DEV"] = read_json(path=path_os_specific_dev.joinpath("config.json"))
143
+ os_specific_dev_data = read_json(path=path_os_specific_dev.joinpath("config.json"))
144
+ res_final["OS_SPECIFIC_DEV"] = InstallerDataFiles(os_specific_dev_data)
134
145
 
135
146
  print(f"""📄 Loading OS-generic dev config from: {path_os_generic_dev.joinpath("config.json")}""")
136
- res_final["OS_GENERIC_DEV"] = read_json(path=path_os_generic_dev.joinpath("config.json"))
147
+ os_generic_dev_data = read_json(path=path_os_generic_dev.joinpath("config.json"))
148
+ res_final["OS_GENERIC_DEV"] = InstallerDataFiles(os_generic_dev_data)
137
149
 
138
150
  path_custom_installer = path_os_generic.with_name("python_custom_installers")
139
151
  path_custom_installer_dev = path_custom_installer.joinpath("dev")
@@ -141,29 +153,31 @@ def get_all_dicts(system: str) -> dict[CATEGORY, dict[str, dict[str, Any]]]:
141
153
  print(f"🔍 Loading custom installers from: {path_custom_installer}")
142
154
  import runpy
143
155
 
144
- res_custom: dict[str, dict[str, Any]] = {}
156
+ res_custom_installers: list[InstallerData] = []
145
157
  for item in path_custom_installer.search("*.py", r=False, not_in=["__init__"]):
146
158
  try:
147
159
  print(f"📄 Loading custom installer: {item.name}")
148
- config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
149
- res_custom[item.stem] = config_dict
160
+ installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
161
+ res_custom_installers.append(installer_data)
150
162
  except Exception as ex:
151
163
  print(f"❌ Failed to load {item}: {ex}")
152
164
 
153
165
  print(f"🔍 Loading custom dev installers from: {path_custom_installer_dev}")
154
- res_custom_dev: dict[str, dict[str, Any]] = {}
166
+ res_custom_dev_installers: list[InstallerData] = []
155
167
  for item in path_custom_installer_dev.search("*.py", r=False, not_in=["__init__"]):
156
168
  try:
157
169
  print(f"📄 Loading custom dev installer: {item.name}")
158
- config_dict = runpy.run_path(str(item), run_name=None)["config_dict"]
159
- res_custom_dev[item.stem] = config_dict
170
+ installer_data: InstallerData = runpy.run_path(str(item), run_name=None)["config_dict"]
171
+ res_custom_dev_installers.append(installer_data)
160
172
  except Exception as ex:
161
173
  print(f"❌ Failed to load {item}: {ex}")
162
174
 
163
- res_final["CUSTOM"] = res_custom
164
- res_final["CUSTOM_DEV"] = res_custom_dev
175
+ res_final["CUSTOM"] = InstallerDataFiles({"version": "1", "installers": res_custom_installers})
176
+ res_final["CUSTOM_DEV"] = InstallerDataFiles({"version": "1", "installers": res_custom_dev_installers})
165
177
 
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}")
178
+ print(
179
+ f"✅ Configuration loading complete:\n - OS_SPECIFIC: {len(res_final['OS_SPECIFIC']['installers'])} items\n - OS_GENERIC: {len(res_final['OS_GENERIC']['installers'])} items\n - CUSTOM: {len(res_final['CUSTOM']['installers'])} items\n{'=' * 80}"
180
+ )
167
181
  return res_final
168
182
 
169
183
 
@@ -1,12 +1,9 @@
1
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
1
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
2
2
  from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
3
- from typing import Optional, TypeAlias, Literal
3
+ from typing import Optional
4
4
  import subprocess
5
5
 
6
6
 
7
- CATEGORY: TypeAlias = Literal["OS_SPECIFIC", "OS_GENERIC", "CUSTOM", "OS_SPECIFIC_DEV", "OS_GENERIC_DEV", "CUSTOM_DEV"]
8
-
9
-
10
7
  def find_move_delete_windows(downloaded_file_path: PathExtended, exe_name: Optional[str] = None, delete: bool = True, rename_to: Optional[str] = None):
11
8
  print(f"\n{'=' * 80}\n🔍 PROCESSING WINDOWS EXECUTABLE 🔍\n{'=' * 80}")
12
9
  if exe_name is not None and ".exe" in exe_name: