machineconfig 3.3__py3-none-any.whl → 3.7__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 (95) hide show
  1. machineconfig/cluster/remote/cloud_manager.py +1 -1
  2. machineconfig/cluster/sessions_managers/wt_local_manager.py +1 -1
  3. machineconfig/cluster/sessions_managers/wt_remote_manager.py +1 -1
  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/templates/utils.py +1 -1
  7. machineconfig/jobs/linux/msc/cli_agents.sh +18 -2
  8. machineconfig/jobs/python/python_ve_symlink.py +2 -2
  9. machineconfig/jobs/python/vscode/api.py +1 -1
  10. machineconfig/jobs/python/vscode/select_interpreter.py +2 -2
  11. machineconfig/jobs/python/vscode/sync_code.py +1 -1
  12. machineconfig/jobs/python_custom_installers/dev/bypass_paywall.py +1 -1
  13. machineconfig/jobs/python_custom_installers/dev/espanso.py +1 -1
  14. machineconfig/jobs/python_custom_installers/hx.py +1 -1
  15. machineconfig/jobs/python_generic_installers/config.json +0 -11
  16. machineconfig/profile/create.py +6 -6
  17. machineconfig/profile/shell.py +3 -3
  18. machineconfig/scripts/python/ai/mcinit.py +23 -67
  19. machineconfig/scripts/python/ai/solutions/__init__.py +0 -0
  20. machineconfig/scripts/python/ai/solutions/_shared.py +5 -0
  21. machineconfig/scripts/python/ai/solutions/claude/claude.py +8 -0
  22. machineconfig/scripts/python/ai/solutions/cline/cline.py +10 -0
  23. machineconfig/scripts/python/ai/solutions/copilot/github_copilot.py +35 -0
  24. machineconfig/scripts/python/ai/solutions/copilot/privacy.md +4 -0
  25. machineconfig/scripts/python/ai/solutions/crush/crush.json +216 -0
  26. machineconfig/scripts/python/ai/solutions/crush/crush.py +25 -0
  27. machineconfig/scripts/python/ai/solutions/crush/privacy.md +2 -0
  28. machineconfig/scripts/python/ai/solutions/cursor/cursors.py +10 -0
  29. machineconfig/scripts/python/ai/solutions/gemini/gemini.py +14 -0
  30. machineconfig/scripts/python/ai/solutions/generic.py +41 -0
  31. machineconfig/scripts/python/ai/solutions/kilocode/privacy.md +3 -0
  32. machineconfig/scripts/python/ai/solutions/opencode/opencode.json +4 -0
  33. machineconfig/scripts/python/ai/solutions/opencode/opencode.py +1 -0
  34. machineconfig/scripts/python/choose_wezterm_theme.py +3 -3
  35. machineconfig/scripts/python/cloud_copy.py +2 -2
  36. machineconfig/scripts/python/cloud_mount.py +4 -4
  37. machineconfig/scripts/python/cloud_repo_sync.py +5 -4
  38. machineconfig/scripts/python/croshell.py +14 -9
  39. machineconfig/scripts/python/devops.py +2 -2
  40. machineconfig/scripts/python/devops_add_identity.py +4 -9
  41. machineconfig/scripts/python/devops_add_ssh_key.py +3 -5
  42. machineconfig/scripts/python/devops_backup_retrieve.py +6 -5
  43. machineconfig/scripts/python/devops_devapps_install.py +4 -4
  44. machineconfig/scripts/python/devops_update_repos.py +2 -2
  45. machineconfig/scripts/python/dotfile.py +1 -1
  46. machineconfig/scripts/python/fire_agents.py +7 -7
  47. machineconfig/scripts/python/fire_agents_help_launch.py +2 -2
  48. machineconfig/scripts/python/fire_jobs.py +13 -13
  49. machineconfig/scripts/python/fire_jobs_layout_helper.py +6 -6
  50. machineconfig/scripts/python/ftpx.py +2 -2
  51. machineconfig/scripts/python/helpers/cloud_helpers.py +2 -1
  52. machineconfig/scripts/python/helpers/helpers2.py +4 -3
  53. machineconfig/scripts/python/helpers/helpers4.py +1 -1
  54. machineconfig/scripts/python/helpers/repo_sync_helpers.py +2 -2
  55. machineconfig/scripts/python/mount_nfs.py +4 -4
  56. machineconfig/scripts/python/mount_ssh.py +1 -1
  57. machineconfig/scripts/python/repos.py +6 -3
  58. machineconfig/scripts/python/repos_helper_clone.py +121 -0
  59. machineconfig/scripts/python/repos_helper_record.py +2 -2
  60. machineconfig/scripts/python/start_slidev.py +1 -1
  61. machineconfig/scripts/python/start_terminals.py +2 -2
  62. machineconfig/scripts/python/wsl_windows_transfer.py +1 -1
  63. machineconfig/settings/shells/ipy/profiles/default/startup/playext.py +2 -2
  64. machineconfig/setup_windows/wt_and_pwsh/install_nerd_fonts.py +1 -1
  65. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +3 -3
  66. machineconfig/utils/{utils2.py → accessories.py} +13 -29
  67. machineconfig/utils/code.py +2 -2
  68. machineconfig/utils/installer.py +2 -2
  69. machineconfig/utils/installer_utils/installer_abc.py +1 -1
  70. machineconfig/utils/installer_utils/installer_class.py +5 -5
  71. machineconfig/utils/io.py +94 -0
  72. machineconfig/utils/links.py +2 -2
  73. machineconfig/utils/notifications.py +0 -9
  74. machineconfig/utils/options.py +25 -18
  75. machineconfig/utils/{path_reduced.py → path_extended.py} +1 -1
  76. machineconfig/utils/{path.py → path_helper.py} +3 -3
  77. machineconfig/utils/procs.py +3 -3
  78. machineconfig/utils/{utils5.py → scheduler.py} +3 -8
  79. machineconfig/utils/scheduling.py +2 -2
  80. machineconfig/utils/ssh.py +2 -2
  81. machineconfig/utils/terminal.py +12 -2
  82. machineconfig/utils/ve.py +2 -16
  83. {machineconfig-3.3.dist-info → machineconfig-3.7.dist-info}/METADATA +1 -4
  84. {machineconfig-3.3.dist-info → machineconfig-3.7.dist-info}/RECORD +94 -78
  85. machineconfig/utils/io_save.py +0 -95
  86. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Thinking-Beast-Mode.chatmode.md +0 -0
  87. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/Ultimate-Transparent-Thinking-Beast-Mode.chatmode.md +0 -0
  88. /machineconfig/scripts/python/ai/{chatmodes → solutions/copilot/chatmodes}/deepResearch.chatmode.md +0 -0
  89. /machineconfig/scripts/python/ai/{instructions → solutions/copilot/instructions}/python/dev.instructions.md +0 -0
  90. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/allLintersAndTypeCheckers.prompt.md +0 -0
  91. /machineconfig/scripts/python/ai/{prompts → solutions/copilot/prompts}/research-report-skeleton.prompt.md +0 -0
  92. /machineconfig/scripts/python/ai/{configs/.gemini → solutions/gemini}/settings.json +0 -0
  93. {machineconfig-3.3.dist-info → machineconfig-3.7.dist-info}/WHEEL +0 -0
  94. {machineconfig-3.3.dist-info → machineconfig-3.7.dist-info}/entry_points.txt +0 -0
  95. {machineconfig-3.3.dist-info → machineconfig-3.7.dist-info}/top_level.txt +0 -0
@@ -15,9 +15,9 @@ import sys
15
15
  from machineconfig.scripts.python.fire_agents_help_launch import prep_agent_launch, get_agents_launch_layout, AGENTS
16
16
  from machineconfig.scripts.python.fire_agents_help_search import search_files_by_pattern, search_python_files
17
17
  from machineconfig.scripts.python.fire_agents_load_balancer import chunk_prompts, SPLITTING_STRATEGY, DEFAULT_AGENT_CAP
18
- from machineconfig.utils.options import choose_one_option
18
+ from machineconfig.utils.options import choose_from_options
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
 
@@ -80,9 +80,9 @@ def main(): # noqa: C901 - (complexity acceptable for CLI glue)
80
80
  sys.exit(1)
81
81
  print(f"Operating @ {repo_root}")
82
82
 
83
- search_strategy = cast(SEARCH_STRATEGIES, choose_one_option(header="Choose search strategy:", options=get_args(SEARCH_STRATEGIES)))
84
- splitting_strategy = cast(SPLITTING_STRATEGY, choose_one_option(header="Choose prompt splitting strategy:", options=get_args(SPLITTING_STRATEGY)))
85
- agent_selected = cast(AGENTS, choose_one_option(header="Select agent type", options=get_args(AGENTS)))
83
+ search_strategy = cast(SEARCH_STRATEGIES, choose_from_options(multi=False, msg="Choose one option", header="Choose search strategy:", options=get_args(SEARCH_STRATEGIES)))
84
+ splitting_strategy = cast(SPLITTING_STRATEGY, choose_from_options(multi=False, msg="Choose one option", header="Choose prompt splitting strategy:", options=get_args(SPLITTING_STRATEGY)))
85
+ agent_selected = cast(AGENTS, choose_from_options(multi=False, msg="Choose one option", header="Select agent type", options=get_args(AGENTS)))
86
86
  print("Enter prefix prompt (end with Ctrl-D / Ctrl-Z):")
87
87
  prompt_prefix = "\n".join(sys.stdin.readlines())
88
88
  job_name = input("Enter job name [AI_AGENTS]: ") or "AI_Agents"
@@ -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] = []
@@ -2,7 +2,7 @@
2
2
  fire
3
3
 
4
4
  # https://github.com/pallets/click combine with fire. Consider
5
- # https://github.com/ceccopierangiolieugenio/pyTermTk for display_options build TUI
5
+ # https://github.com/ceccopierangiolieugenio/pyTermTk for choose_from_options build TUI
6
6
  # https://github.com/chriskiehl/Gooey build commandline interface
7
7
 
8
8
  """
@@ -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
15
- from machineconfig.utils.options import display_options, choose_one_option
16
- from machineconfig.utils.path import match_file_name, sanitize_path
14
+ from machineconfig.utils.ve import get_ve_activate_line, get_ve_path_and_ipython_profile
15
+ from machineconfig.utils.options import choose_from_options
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
 
@@ -39,7 +39,7 @@ def route(args: FireJobArgs) -> None:
39
39
  print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
40
40
  files = search_for_files_of_interest(path_obj)
41
41
  print(f"🔍 Got #{len(files)} results.")
42
- choice_file = choose_one_option(options=files, fzf=True)
42
+ choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
43
43
  choice_file = PathExtended(choice_file)
44
44
  else:
45
45
  choice_file = path_obj
@@ -60,7 +60,7 @@ def route(args: FireJobArgs) -> None:
60
60
  if args.choose_function or args.submit_to_cloud:
61
61
  if choice_file.suffix == ".py":
62
62
  options, func_args = parse_pyfile(file_path=str(choice_file))
63
- choice_function_tmp = display_options(msg="Choose a function to run", options=options, fzf=True, multi=False)
63
+ choice_function_tmp = choose_from_options(msg="Choose a function to run", options=options, fzf=True, multi=False)
64
64
  assert isinstance(choice_function_tmp, str), f"choice_function must be a string. Got {type(choice_function_tmp)}"
65
65
  choice_index = options.index(choice_function_tmp)
66
66
  choice_function = choice_function_tmp.split(" -- ")[0]
@@ -81,7 +81,7 @@ def route(args: FireJobArgs) -> None:
81
81
  if line.startswith("echo"):
82
82
  continue
83
83
  options.append(line)
84
- chosen_lines = display_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
84
+ chosen_lines = choose_from_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
85
85
  choice_file = PathExtended.tmpfile(suffix=".sh")
86
86
  choice_file.parent.mkdir(parents=True, exist_ok=True)
87
87
  choice_file.write_text("\n".join(chosen_lines), encoding="utf-8")
@@ -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
@@ -2,9 +2,9 @@ from pathlib import Path
2
2
  from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, LayoutsFile
3
3
  from typing import Optional, TYPE_CHECKING
4
4
  from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
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
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
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
@@ -18,9 +18,9 @@ def select_layout(layouts_json_file: Path, layout_name: Optional[str]):
18
18
  raise ValueError(f"No layouts found in {layouts_json_file}")
19
19
  if layout_name is None:
20
20
  options = [layout["layoutName"] for layout in layout_file["layouts"]]
21
- from machineconfig.utils.options import choose_one_option
21
+ from machineconfig.utils.options import choose_from_options
22
22
 
23
- layout_name = choose_one_option(options=options, prompt="Choose a layout configuration:", fzf=True)
23
+ layout_name = choose_from_options(multi=False, options=options, prompt="Choose a layout configuration:", fzf=True, msg="Choose one option")
24
24
  print(f"Selected layout: {layout_name}")
25
25
  layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layout_name), None)
26
26
  if layout_chosen is None:
@@ -59,7 +59,7 @@ def handle_layout_args(args: "FireJobArgs") -> None:
59
59
  print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
60
60
  files = search_for_files_of_interest(path_obj)
61
61
  print(f"🔍 Got #{len(files)} results.")
62
- choice_file = choose_one_option(options=files, fzf=True)
62
+ choice_file = choose_from_options(multi=False, options=files, fzf=True, msg="Choose one option")
63
63
  choice_file = PathExtended(choice_file)
64
64
  else:
65
65
  choice_file = path_obj
@@ -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,9 +1,9 @@
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
- from machineconfig.utils.options import display_options, choose_ssh_host
6
+ from machineconfig.utils.options import choose_from_options, choose_ssh_host
7
7
 
8
8
  import platform
9
9
 
@@ -20,7 +20,7 @@ def main():
20
20
  assert isinstance(tmp, str)
21
21
  ssh = SSH(tmp)
22
22
  default = f"{ssh.hostname}:{ssh.run('echo $HOME').op}/data/share_nfs"
23
- share_info = display_options("📂 Choose a share path:", options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default], default=default)
23
+ share_info = choose_from_options(msg="📂 Choose a share path:", options=[f"{ssh.hostname}:{item.split(' ')[0]}" for item in ssh.run("cat /etc/exports").op.split("\n") if not item.startswith("#")] + [default], default=default, multi=False)
24
24
  assert isinstance(share_info, str), f"❌ share_info must be a string. Got {type(share_info)}"
25
25
 
26
26
  remote_server = share_info.split(":")[0]
@@ -38,7 +38,7 @@ def main():
38
38
  mount_path_3 = mount_path_2
39
39
 
40
40
  print("🔧 Preparing mount paths...")
41
- local_mount_point = display_options(msg="📂 Choose mount path OR input custom one:", options=[mount_path_1, mount_path_2, mount_path_3], default=mount_path_2, custom_input=True)
41
+ local_mount_point = choose_from_options(msg="📂 Choose mount path OR input custom one:", options=[mount_path_1, mount_path_2, mount_path_3], default=mount_path_2, custom_input=True, multi=False)
42
42
  assert isinstance(local_mount_point, PathExtended), f"❌ local_mount_point must be a pathlib.Path. Got {type(local_mount_point)}"
43
43
  local_mount_point = PathExtended(local_mount_point).expanduser()
44
44
 
@@ -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
  """Script to start terminals on windows and wsl"""
2
2
 
3
- from machineconfig.utils.options import display_options, get_ssh_hosts
3
+ from machineconfig.utils.options import choose_from_options, get_ssh_hosts
4
4
  import platform
5
5
  from itertools import cycle
6
6
  from typing import Literal
@@ -98,7 +98,7 @@ def main():
98
98
  else:
99
99
  if args.hosts is None:
100
100
  print("🌐 No hosts provided. Displaying options...")
101
- hosts = display_options(msg="Select hosts:", options=get_ssh_hosts() + [THIS_MACHINE], multi=True, fzf=True)
101
+ hosts = choose_from_options(msg="Select hosts:", options=get_ssh_hosts() + [THIS_MACHINE], multi=True, fzf=True)
102
102
  else:
103
103
  print("🌐 Using provided hosts:", args.hosts)
104
104
  hosts = args.hosts
@@ -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
@@ -26,8 +26,8 @@
26
26
  # @register_line_magic("codei") # type: ignore
27
27
  # def print_code_interactive(_):
28
28
  # res = get_names()
29
- # from machineconfig.utils.utils import choose_one_option
30
- # choice = choose_one_option(options=res["<class 'function'>"], msg="Choose a type to inspect", fzf=True)
29
+ # from machineconfig.utils.utils import choose_from_options
30
+ # choice = choose_from_options(multi=False, options=res["<class 'function'>"], msg="Choose a type to inspect", fzf=True)
31
31
  # obj = eval(choice, globals(), locals()) # type: ignore # pylint: disable=eval-used
32
32
  # from rich.syntax import Syntax
33
33
  # import inspect
@@ -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
@@ -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,35 +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
-
48
- try:
49
- mydict = json.loads(Path(path).read_text(encoding="utf-8"), **kwargs)
50
- except Exception:
51
- import pyjson5
52
-
53
- mydict = pyjson5.loads(Path(path).read_text(encoding="utf-8"), **kwargs) # file has C-style comments.
54
- _ = r
55
- return mydict
56
-
57
-
58
- def read_toml(path: "Path"):
59
- import tomli
60
-
61
- return tomli.loads(path.read_text(encoding="utf-8"))
62
-
63
-
64
35
  def pprint(obj: dict[Any, Any], title: str) -> None:
65
36
  from rich import inspect
66
37
 
@@ -86,3 +57,16 @@ def human_friendly_dict(d: dict[str, Any]) -> dict[str, Any]:
86
57
  else:
87
58
  result[k] = v
88
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):
@@ -6,9 +6,9 @@ from machineconfig.utils.schemas.installer.installer_types import APP_INSTALLER_
6
6
  from rich.console import Console
7
7
  from rich.panel import Panel # Added import
8
8
 
9
- from machineconfig.utils.path_reduced import PathExtended as PathExtended
9
+ from machineconfig.utils.path_extended import PathExtended as PathExtended
10
10
  from machineconfig.utils.source_of_truth import INSTALL_VERSION_ROOT
11
- from machineconfig.utils.utils2 import read_json
11
+ from machineconfig.utils.io import read_json
12
12
 
13
13
  from typing import Any
14
14
  import platform
@@ -1,4 +1,4 @@
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
3
  from typing import Optional
4
4
  import subprocess