machineconfig 3.3__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 (88) 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_manager.py +1 -1
  4. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +1 -1
  5. machineconfig/cluster/templates/utils.py +1 -1
  6. machineconfig/jobs/linux/msc/cli_agents.sh +18 -2
  7. machineconfig/jobs/python/python_ve_symlink.py +1 -1
  8. machineconfig/jobs/python/vscode/api.py +1 -1
  9. machineconfig/jobs/python/vscode/select_interpreter.py +2 -2
  10. machineconfig/jobs/python/vscode/sync_code.py +1 -1
  11. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +1 -1
  12. machineconfig/jobs/python_custom_installers/dev/espanso.py +1 -1
  13. machineconfig/jobs/python_custom_installers/hx.py +1 -1
  14. machineconfig/jobs/python_generic_installers/config.json +0 -11
  15. machineconfig/profile/create.py +3 -3
  16. machineconfig/profile/shell.py +1 -1
  17. machineconfig/scripts/python/ai/mcinit.py +23 -67
  18. machineconfig/scripts/python/ai/solutions/__init__.py +0 -0
  19. machineconfig/scripts/python/ai/solutions/_shared.py +5 -0
  20. machineconfig/scripts/python/ai/solutions/claude/claude.py +8 -0
  21. machineconfig/scripts/python/ai/solutions/cline/cline.py +10 -0
  22. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +35 -0
  23. machineconfig/scripts/python/ai/solutions/copilot/privacy.md +4 -0
  24. machineconfig/scripts/python/ai/solutions/crush/crush.json +216 -0
  25. machineconfig/scripts/python/ai/solutions/crush/crush.py +25 -0
  26. machineconfig/scripts/python/ai/solutions/crush/privacy.md +2 -0
  27. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +10 -0
  28. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +14 -0
  29. machineconfig/scripts/python/ai/solutions/generic.py +41 -0
  30. machineconfig/scripts/python/ai/solutions/kilocode/privacy.md +3 -0
  31. machineconfig/scripts/python/ai/solutions/opencode/opencode.json +4 -0
  32. machineconfig/scripts/python/ai/solutions/opencode/opencode.py +1 -0
  33. machineconfig/scripts/python/choose_wezterm_theme.py +1 -1
  34. machineconfig/scripts/python/cloud_copy.py +2 -2
  35. machineconfig/scripts/python/cloud_mount.py +2 -2
  36. machineconfig/scripts/python/cloud_repo_sync.py +3 -2
  37. machineconfig/scripts/python/croshell.py +12 -7
  38. machineconfig/scripts/python/devops_add_identity.py +1 -1
  39. machineconfig/scripts/python/devops_add_ssh_key.py +1 -1
  40. machineconfig/scripts/python/devops_backup_retrieve.py +4 -3
  41. machineconfig/scripts/python/devops_update_repos.py +2 -2
  42. machineconfig/scripts/python/dotfile.py +1 -1
  43. machineconfig/scripts/python/fire_agents.py +3 -3
  44. machineconfig/scripts/python/fire_agents_help_launch.py +2 -2
  45. machineconfig/scripts/python/fire_jobs.py +8 -8
  46. machineconfig/scripts/python/fire_jobs_layout_helper.py +2 -2
  47. machineconfig/scripts/python/ftpx.py +2 -2
  48. machineconfig/scripts/python/helpers/cloud_helpers.py +2 -1
  49. machineconfig/scripts/python/helpers/helpers2.py +4 -3
  50. machineconfig/scripts/python/helpers/helpers4.py +1 -1
  51. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  52. machineconfig/scripts/python/mount_nfs.py +1 -1
  53. machineconfig/scripts/python/mount_ssh.py +1 -1
  54. machineconfig/scripts/python/repos.py +6 -3
  55. machineconfig/scripts/python/repos_helper_clone.py +121 -0
  56. machineconfig/scripts/python/repos_helper_record.py +2 -2
  57. machineconfig/scripts/python/start_slidev.py +1 -1
  58. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  59. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +1 -1
  60. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -3
  61. machineconfig/utils/{utils2.py → accessories.py} +13 -29
  62. machineconfig/utils/code.py +2 -2
  63. machineconfig/utils/installer.py +2 -2
  64. machineconfig/utils/installer_utils/installer_abc.py +1 -1
  65. machineconfig/utils/installer_utils/installer_class.py +2 -2
  66. machineconfig/utils/io.py +94 -0
  67. machineconfig/utils/links.py +2 -2
  68. machineconfig/utils/notifications.py +0 -9
  69. machineconfig/utils/{path_reduced.py → path_extended.py} +1 -1
  70. machineconfig/utils/{path.py → path_helper.py} +1 -1
  71. machineconfig/utils/procs.py +1 -1
  72. machineconfig/utils/{utils5.py → scheduler.py} +3 -8
  73. machineconfig/utils/ssh.py +2 -2
  74. machineconfig/utils/terminal.py +12 -2
  75. machineconfig/utils/ve.py +2 -16
  76. {machineconfig-3.3.dist-info → machineconfig-3.5.dist-info}/METADATA +1 -4
  77. {machineconfig-3.3.dist-info → machineconfig-3.5.dist-info}/RECORD +87 -71
  78. machineconfig/utils/io_save.py +0 -95
  79. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Thinking-Beast-Mode.chatmode.md +0 -0
  80. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +0 -0
  81. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/deepResearch.chatmode.md +0 -0
  82. /machineconfig/scripts/python/ai/{instructions → solutions/copilot/instructions}/python/dev.instructions.md +0 -0
  83. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/allLintersAndTypeCheckers.prompt.md +0 -0
  84. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/research-report-skeleton.prompt.md +0 -0
  85. /machineconfig/scripts/python/ai/{configs/.gemini → solutions/gemini}/settings.json +0 -0
  86. {machineconfig-3.3.dist-info → machineconfig-3.5.dist-info}/WHEEL +0 -0
  87. {machineconfig-3.3.dist-info → machineconfig-3.5.dist-info}/entry_points.txt +0 -0
  88. {machineconfig-3.3.dist-info → machineconfig-3.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,14 @@
1
+ from pathlib import Path
2
+
3
+ from machineconfig.scripts.python.ai.solutions._shared import get_generic_instructions_path
4
+ from machineconfig.utils.source_of_truth import LIBRARY_ROOT
5
+
6
+ def build_configuration(repo_root: Path) -> None:
7
+ instructions_path = get_generic_instructions_path()
8
+ repo_root.joinpath("GEMINI.md").write_text(data=instructions_path.read_text(encoding="utf-8"), encoding="utf-8")
9
+
10
+ gemini_dir = repo_root.joinpath(".gemini")
11
+ settings_source_path = LIBRARY_ROOT.joinpath("scripts/python/ai/solutions/gemini/settings.json")
12
+ gemini_dir.mkdir(parents=True, exist_ok=True)
13
+
14
+ settings_source_path.write_text(data=settings_source_path.read_text(encoding="utf-8"), encoding="utf-8")
@@ -0,0 +1,41 @@
1
+ from pathlib import Path
2
+
3
+ from machineconfig.utils.source_of_truth import LIBRARY_ROOT
4
+
5
+
6
+ def create_dot_scripts(repo_root: Path) -> None:
7
+ scripts_dir = LIBRARY_ROOT.joinpath("scripts/python/ai/scripts")
8
+ target_dir = repo_root.joinpath(".scripts")
9
+ target_dir.mkdir(parents=True, exist_ok=True)
10
+ for script_path in scripts_dir.iterdir():
11
+ target_dir.joinpath(script_path.name).write_text(data=script_path.read_text(encoding="utf-8"), encoding="utf-8")
12
+
13
+
14
+ def adjust_gitignore(repo_root: Path) -> None:
15
+ dot_git_ignore_path = repo_root.joinpath(".gitignore")
16
+ if dot_git_ignore_path.exists() is False:
17
+ return
18
+
19
+ dot_git_ignore_content = dot_git_ignore_path.read_text(encoding="utf-8")
20
+ entries_to_add: list[str] = []
21
+ required_entries: list[str] = [
22
+ ".links",
23
+ "notebooks",
24
+ ".ai",
25
+ ".scripts",
26
+ "GEMINI.md",
27
+ "CLAUDE.md",
28
+ ".cursor",
29
+ ".github/instructions",
30
+ ".github/chatmodes",
31
+ ".github/prompts",
32
+ ]
33
+
34
+ for entry in required_entries:
35
+ if entry not in dot_git_ignore_content:
36
+ entries_to_add.append(entry)
37
+
38
+ if len(entries_to_add) == 0:
39
+ return
40
+
41
+ dot_git_ignore_path.write_text(data=dot_git_ignore_content + "\n" + "\n".join(entries_to_add), encoding="utf-8")
@@ -0,0 +1,3 @@
1
+
2
+ # disable usage and erroror reports to kilocode in About/Export page.
3
+ # Disable model api provider data collection.
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "instructions": [".github/instructions/*.md"]
4
+ }
@@ -3,7 +3,7 @@ Choose a theme for Wezterm
3
3
  """
4
4
 
5
5
  from machineconfig.utils.options import choose_one_option
6
- from machineconfig.utils.path_reduced import PathExtended
6
+ from machineconfig.utils.path_extended import PathExtended
7
7
  from typing import Any
8
8
  import time
9
9
  from rich.panel import Panel
@@ -2,7 +2,7 @@
2
2
  CC
3
3
  """
4
4
 
5
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
6
6
  from tenacity import retry, stop_after_attempt, wait_chain, wait_fixed
7
7
  import getpass
8
8
  import argparse
@@ -14,7 +14,7 @@ from machineconfig.scripts.python.helpers.cloud_helpers import ArgsDefaults, Arg
14
14
  from rich.console import Console
15
15
  from rich.panel import Panel
16
16
  from rich.progress import Progress
17
- from machineconfig.utils.utils2 import pprint
17
+ from machineconfig.utils.accessories import pprint
18
18
 
19
19
  console = Console()
20
20
 
@@ -1,8 +1,8 @@
1
1
  """Cloud mount script"""
2
2
 
3
3
  from machineconfig.utils.options import choose_one_option
4
- from machineconfig.utils.utils2 import read_ini
5
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
4
+ from machineconfig.utils.io import read_ini
5
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
6
6
 
7
7
  import platform
8
8
  import argparse
@@ -2,9 +2,10 @@
2
2
 
3
3
  from pathlib import Path
4
4
  import git
5
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
+ from machineconfig.utils.io import read_ini
6
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
6
7
  from machineconfig.utils.terminal import Terminal
7
- from machineconfig.utils.utils2 import randstr, read_ini
8
+ from machineconfig.utils.accessories import randstr
8
9
 
9
10
  from machineconfig.scripts.python.helpers.repo_sync_helpers import fetch_dotfiles
10
11
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
@@ -5,8 +5,8 @@ croshell
5
5
  """
6
6
 
7
7
  import argparse
8
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
9
- from machineconfig.utils.utils2 import randstr
8
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
9
+ from machineconfig.utils.accessories import randstr
10
10
 
11
11
  from machineconfig.utils.options import display_options
12
12
  from machineconfig.utils.ve import get_ve_activate_line
@@ -21,8 +21,10 @@ console = Console()
21
21
 
22
22
  def add_print_header_pycode(path: str, title: str):
23
23
  return f"""
24
- # from machineconfig.utils.path_reduced import P as PathExtended
25
- from crocodile.file_management import P as PathExtended
24
+ try:
25
+ from crocodile.file_management import P as PathExtended
26
+ except ImportError:
27
+ from machineconfig.utils.path_reduced import PathExtended
26
28
  pycode = PathExtended(r'{path}').read_text(encoding="utf-8")
27
29
  pycode = pycode.split("except Exception: print(pycode)")[2]
28
30
 
@@ -160,10 +162,13 @@ def build_parser():
160
162
  preprogram = """
161
163
 
162
164
  #%%
163
- from crocodile.croshell import *
165
+ try:
166
+ from crocodile.croshell import *
167
+ print_header()
168
+ print_logo(logo="crocodile")
169
+ except ImportError:
170
+ print("Crocodile not found, skipping import.")
164
171
  from pathlib import Path
165
- print_header()
166
- print_logo(logo="crocodile")
167
172
  print(f"🐊 Crocodile Shell | Running @ {Path.cwd()}")
168
173
  """
169
174
 
@@ -1,7 +1,7 @@
1
1
  """ID"""
2
2
 
3
3
  # from platform import system
4
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
4
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
5
5
  from machineconfig.utils.options import display_options
6
6
  from rich.panel import Panel
7
7
  from rich.text import Text
@@ -3,7 +3,7 @@
3
3
  from platform import system
4
4
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT
5
5
  from machineconfig.utils.options import display_options
6
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
6
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
7
7
  from rich.console import Console
8
8
  from rich.panel import Panel
9
9
  from rich import box # Import box
@@ -1,8 +1,8 @@
1
1
  """BR: Backup and Retrieve"""
2
2
 
3
3
  # import subprocess
4
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
5
- from machineconfig.utils.utils2 import read_ini, read_toml
4
+ from machineconfig.utils.io import read_ini
5
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
6
6
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT, DEFAULTS_PATH
7
7
  from machineconfig.utils.code import print_code
8
8
  from machineconfig.utils.options import choose_cloud_interactively, choose_multiple_options
@@ -11,6 +11,7 @@ from platform import system
11
11
  from typing import Any, Literal, Optional
12
12
  from rich.console import Console
13
13
  from rich.panel import Panel
14
+ import tomllib
14
15
 
15
16
 
16
17
  OPTIONS = Literal["BACKUP", "RETRIEVE"]
@@ -26,7 +27,7 @@ def main_backup_retrieve(direction: OPTIONS, which: Optional[str] = None) -> Non
26
27
  console.print(Panel("🔍 DEFAULT CLOUD NOT FOUND\n🔄 Please select a cloud configuration from the options below", title="[bold red]Error: Cloud Not Found[/bold red]", border_style="red"))
27
28
  cloud = choose_cloud_interactively()
28
29
 
29
- bu_file: dict[str, Any] = read_toml(LIBRARY_ROOT.joinpath("profile/backup.toml"))
30
+ bu_file: dict[str, Any] = tomllib.loads(LIBRARY_ROOT.joinpath("profile/backup.toml").read_text(encoding="utf-8"))
30
31
 
31
32
  console.print(Panel(f"🧰 LOADING BACKUP CONFIGURATION\n📄 File: {LIBRARY_ROOT.joinpath('profile/backup.toml')}", title="[bold blue]Backup Configuration[/bold blue]", border_style="blue"))
32
33
 
@@ -3,9 +3,9 @@
3
3
  import git
4
4
  from pathlib import Path
5
5
  from machineconfig.scripts.python.repos_helper_update import RepositoryUpdateResult, run_uv_sync, update_repository
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.source_of_truth import DEFAULTS_PATH
8
- from machineconfig.utils.utils2 import read_ini
8
+ from machineconfig.utils.io import read_ini
9
9
 
10
10
 
11
11
  def _display_summary(results: list[RepositoryUpdateResult]) -> None:
@@ -1,6 +1,6 @@
1
1
  """Like yadm and dotter."""
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.links import symlink_func
5
5
  from machineconfig.utils.source_of_truth import LIBRARY_ROOT, REPO_ROOT
6
6
  import argparse
@@ -17,7 +17,7 @@ from machineconfig.scripts.python.fire_agents_help_search import search_files_by
17
17
  from machineconfig.scripts.python.fire_agents_load_balancer import chunk_prompts, SPLITTING_STRATEGY, DEFAULT_AGENT_CAP
18
18
  from machineconfig.utils.options import choose_one_option
19
19
  from machineconfig.utils.schemas.layouts.layout_types import TabConfig, LayoutConfig
20
- from machineconfig.utils.ve import get_repo_root
20
+ from machineconfig.utils.accessories import get_repo_root
21
21
 
22
22
  SEARCH_STRATEGIES: TypeAlias = Literal["file_path", "keyword_search", "filename_pattern"]
23
23
 
@@ -151,7 +151,7 @@ manager.run_monitoring_routine()
151
151
 
152
152
 
153
153
  def split_too_many_tabs_to_run_in_sequential_sessions(layout_tabs: list[TabConfig], every: int):
154
- from machineconfig.utils.utils2 import split
154
+ from machineconfig.utils.accessories import split
155
155
  from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
156
156
 
157
157
  for idx, layout_tabs_chunk in enumerate(split(layout_tabs, every=every)):
@@ -163,7 +163,7 @@ def split_too_many_tabs_to_run_in_sequential_sessions(layout_tabs: list[TabConfi
163
163
 
164
164
 
165
165
  def split_too_many_layouts_to_run_in_sequential_sessions(layouts: list[LayoutConfig], every: int):
166
- from machineconfig.utils.utils2 import split
166
+ from machineconfig.utils.accessories import split
167
167
  from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
168
168
 
169
169
  for _idx, layout_chunk in enumerate(split(layouts, every=every)):
@@ -1,4 +1,4 @@
1
- from machineconfig.utils.utils2 import randstr
1
+ from machineconfig.utils.accessories import randstr
2
2
 
3
3
  import random
4
4
  import shlex
@@ -14,7 +14,7 @@ AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
14
14
 
15
15
 
16
16
  def get_gemini_api_keys() -> list[str]:
17
- from machineconfig.utils.utils2 import read_ini
17
+ from machineconfig.utils.io import read_ini
18
18
 
19
19
  config = read_ini(Path.home().joinpath("dotfiles/creds/llm/gemini/api_keys.ini"))
20
20
  res: list[str] = []
@@ -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,7 +4,7 @@ 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